大家好,欢迎来到IT知识分享网。
铁子们,快扫码关注啦!或 wx搜索:“聊5毛钱的java”,关注可领取博主的Java学习视频+资料,保证都是干货
讨论内省的前提是需要了解Java中的反射,如果需要了解反射的话,可以点击下方的文章
Java中的反射机制介绍
为什么要学内省?
开发框架时,经常需要使用java对象的属性来封装程序的数据(其实就是操作对象的set/get方法来设值或取值),每次都使用反射来完成此类操作过于麻烦,所以JDK里提供了一套API,专门用于操作java对象的属性(set/get方法)。既然内省是专门用于操作java对象属性的,那首先得搞懂什么是对象的属性
1、什么是java对象的属性呢?
说到属性,大家觉得很熟悉,属性不就是类里最上边的那些全局变量吗?
private String name;
private int age;
这种不都是属性吗?
其实,这是不对的!
刚才说的 private String name;private int age; 准确的来说它们应该称为:字段,而不是咱们所说的属性
那什么才是属性?
☆☆☆☆☆Java中的属性是指:设置和读取字段的方法,说白了就是咱们平常见到的set和get方法
只要是set和get开头的方法在java里都认为它是属性(请注意这句话,等下后边会写代码做验证)
属性名称就是set和get方法名 去掉”set”和”get”后的内容
比如:
它的属性名称是:name(也就是方法名称”setName”去掉“set”)
当然setName( );和getName( )是指同一个属性 name,
所以,咱们平常说的类里的全局变量,它并不是属性,正确的来说,它应该是字段,只不过咱们平常set和get方法写的名字和字段保持一致,所以导致大家把字段和属性认为是同一个东西
所以说白了,其实内省就是操作set和get方法的
那怎么才能得到类中的set和get方法并去操作它呢?通过反射肯定可以,但是在文章开头就已经说了,每次通过反射做的话过于麻烦,所以就出现了下边要讲的内省(Introspector),它就是专门做这个的,它底层也是用反射,只不过给咱们封装了,简化了操作
我们看下JDK的API文档里的 Introspector 这个类
写代码验证一下
package com.cj.study.introspector; import java.util.Date; public class Student { private String name = "张三";//这是字段 private int age;//这是字段 private Date birthday; public String getName() {//这才是属性,属性指的是设置setter和读取getter字段的方法 return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } //虽然上边的字段里没定义abc这个字段 //但这也是属性:属性名称是abc,注意:只要是set或者get开头的方法都叫属性 public String getAbc(){ return "abc"; } }
package com.cj.study.introspector; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import org.junit.Test; //内省:操作属性的(类中的getter和setter方法) public class Demo1 { //属性名称:getClass,他的属性名称class //getAbc --->abc @Test public void test1() throws Exception{ //得到Student类中的属性,被封装到了BeanInfo中 BeanInfo bi = Introspector.getBeanInfo(Student.class); //得到类中的所有的属性描述器 PropertyDescriptor[] pds = bi.getPropertyDescriptors(); System.out.println("属性的个数:"+pds.length); for(PropertyDescriptor pd:pds){ System.out.println("属性:"+pd.getName()); } } }
从运行结果上来看,一共得到了5个属性,除了name,age,birthday 外还打印出了abc
上边的代码验证了咱们刚才说的:“属性其实是set、get方法”,而并不是类上边的那些字段,不然的话abc不会被打印出来的
但是name,age,birthday再加上abc应该是4个才对,那为什么会打印出5个呢?
原因很简单,因为Object类是所有类的父类,Object类里有个方法叫 getClass();
所以这也验证了咱们刚才说的: “只要是set或者get开头的方法都叫属性”。
2、使用内省操作属性
刚才的代码里用到了PropertyDescriptor 这个类
PropertyDescriptor顾名思义,就是属性描述之意。
它通过反射 快速操作JavaBean的getter/setter方法。 也就是说它底层也是反射去操作set和get方法,只不过它给咱们封装了,用起来更方便
PropertyDescriptor中重要的方法:
(1).写方法:getWriteMethod() – 对应set方法,它的返回值是Method对像
(2).读方法:getReadMethod() – 对应get方法,它的返回值是Method对像
代码操作一下
package com.cj.study.introspector; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import org.junit.Test; //内省:操作属性的(类中的getter和setter方法) public class Demo1 { @Test public void test2() throws Exception{ //Student s = new Student(); //利用反射生成对象 //com.cj.study.introspector.Student该参数可以配置到配置文件里,这才是我们想要的 //是不是很熟悉?很多框架都是这么做的 Class clazz = Class.forName("com.cj.study.introspector.Student"); Student s = (Student)clazz.newInstance(); PropertyDescriptor pd = new PropertyDescriptor("name", Student.class); Method m = pd.getReadMethod();//得到getName()方法 String value = (String)m.invoke(s, null);//调用getName()方法 System.out.println("调用get方法得到name的值:"+value); //改变name的值 Method m1 = pd.getWriteMethod();//得到setName()方法 m1.invoke(s, "李四");//调用setName()方法去修改name的值 System.out.println("调用set方法改变name的值:"+s.getName()); } }
最后的执行结果:
这就是Java JDK里提供的内省功能(其实就是操作JavaBean里的set和get方法)
但是JDK里提供的内省还不够简单,于是乎,apache出了一套更为简单的内省框架 —— BeanUtils
3、BeanUtils内省操作
3.1BeanUtils操作属性
操作之前,首先需要导入BeanUtils的jar包,以及它以来的jar包
commons-beanutils-1.8.3.jar
commons-logging-1.1.1.jar
导入jar包后,可以看到BeanUtils里有两个重要的方法:
(1).BeanUtils.getProperty(s, “name”);//调用getName方法
(2).BeanUtils.setProperty(s, “name”, “王五”);//调用setName方法
代码操作一下
package com.cj.study.introspector; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import org.apache.commons.beanutils.BeanUtils; import org.junit.Test; //内省:操作属性的(类中的getter和setter方法) public class Demo1 { //利用BeanUtils框架操作属性:实现原理类似test2 @Test public void test3() throws Exception{ Student s = new Student(); //为什么要返回字符串:用户的所有输入都是字符串 String str = BeanUtils.getProperty(s, "name");//调用getName方法 System.out.println(str); //设置值 BeanUtils.setProperty(s, "name", "王五"); System.out.println(s.getName()); } }
最后输出的结果
可以看到用BeanUtils操作更加的简单了
3.2BeanUtils类型转换的问题
有个问题需要注意:BeanUtils可以进行类型的自动转换,但仅限基本类型
比如说本来需要int型,给个字符串 “28”,是可以的
但是仅限基本数据类型,像Date 这种的就不行,会报错,下边用代码体现一下:
基本数据类型自动转换
//基本数据类型自动转换 @Test public void test4() throws Exception{ Student s = new Student(); String str = BeanUtils.getProperty(s, "age"); System.out.println(str); BeanUtils.setProperty(s, "age", "19"); System.out.println(s.getAge()); }
发现OK
非基本数据类型
//非基本数据类型 @Test public void test5() throws Exception{ Student s = new Student(); String str = BeanUtils.getProperty(s, "birthday"); System.out.println(str); BeanUtils.setProperty(s, "birthday", "1989-10-09"); System.out.println(s.getBirthday()); }
发现非基本数据类型值没set进去,而且有错误提示
所以这里涉及到了BeanUtils里的String和其他类型间的互相转换的问题
要想解决这个问题,需要给BeanUtils注册一个类型转换器
代码实现一下
//非基本类型的属性设置 //用户的输入都是String //String <----->其他类型间的互相转换 //用户看到的结果都是String @Test public void test6() throws Exception{ Student s = new Student(); //给BeanUtils注册类型转换器:自定义的转换器 ConvertUtils.register(new Converter() { //type:目标类型 //value:当前传入的值 public Object convert(Class type, Object value) { // if(type.equals(Date.class)){ // //字符串转换为Date // }else{ // //Date转换为字符串 // } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); if(value instanceof String){ //字符串转换为Date String v = (String)value; Date d; try { d = df.parse(v); } catch (ParseException e) { throw new RuntimeException(e); } return d; }else{ //Date转换为字符串 Date d = (Date)value; return df.format(d); } } }, Date.class); BeanUtils.setProperty(s, "birthday", "1989-10-09"); System.out.println(s.getBirthday()); }
发现问题解决了。
但是这么手动的去写一个类型转换器,是不是太麻烦了,所以BeanUtils提供了Converter接口很多的实现类
其中有一个DateLocaleConverter类
所以上边的代码可以直接用DateLocaleConverter
@Test//转换器原理参考test6 public void test7() throws Exception{ Student s = new Student(); ConvertUtils.register(new DateLocaleConverter(), Date.class); BeanUtils.setProperty(s, "birthday", "1999-10-09"); System.out.println(s.getBirthday()); }
运行结果
发现用了它提供的DateLocaleConverter类后变得很简单,DateLocaleConverter实现的功能就是咱们test6里实现的
它的内部实现,其实和咱们test6里的原理一样。
3.3BeanUtils将Map属性自动放到Bean中
package com.cj.study.introspector; import java.util.Date; public class Person { private String name1;//请注意这里我写的是name1,并不是name private int age; private Date birthday; public String getName() { return name1; } public void setName(String name) {//这里的属性写的才是name,进一步验证了属性的定义 this.name1 = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Person [age=" + age + ", birthday=" + birthday + ", name1=" + name1 + "]"; } }
package com.cj.study.introspector; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.beanutils.locale.converters.DateLocaleConverter; import org.junit.Test; //利用BeanUtils封装数据 public class Demo2 { @Test public void test1() throws Exception{ Map map = new HashMap(); //map中的key与属性一致,为了做区分请注意Person类里的字段我写的是name1,进一步验证了对属性的定义 map.put("name", "王小二"); map.put("age", "36"); map.put("birthday", "1979-10-09"); Person p = new Person(); System.out.println("封装数据前:"+p); ConvertUtils.register(new DateLocaleConverter(), Date.class); BeanUtils.populate(p, map); System.out.println("封装数据后:"+p); } }
运行结果
可以看到值被set进去了
正如大家看到的一样,很多的框架都用到了BeanUtils这个jar包
关于框架中怎么使用BeanUtils,我之前写过一篇手写代码模拟Struts2框架的文章,那里用到了BeanUtils
利用Java反射模拟一个Struts2框架 Struts2主要核心设计 手动实现Struts2核心代码
感兴趣的朋友可以看一下
好了,关于Java的内省,就介绍到这,欢迎大家留言,一起讨论,学习,一起进步
铁子们,如果觉得文章对你有所帮助,可以点关注,点赞
也可以关注下公众号:扫码或 wx搜索:“聊5毛钱的java”,欢迎一起学习交流,关注公众号可领取博主的Java学习视频+资料,保证都是干货
3Q~
纯手敲原创不易,如果觉得对你有帮助,可以打赏支持一下,哈哈,感谢~
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/140090.html