Java获取set/get方法新姿势---内省

1. 传统反射用法

一般来说,我们获取某个Java对象属性的get/set方法是通过反射。
具体如下:

首先设定一个User类,包含Name跟Sex两个私有属性,并且实现了get/set方法

public class User {
    private String name;
    private String sex;
    private int age;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void test(){}
}


使用反射调用get/set方法

```java
//属性名称
StringpropertyName="name";
//属性值
StringpropertyValue="黎富强";
//拼接getName方法名可以有其他这里举例
StringgetMethodName="get"+propertyName.substring(0,1).toUpperCase()+propertyName.substring(1);
StringsetMethodName="set"+propertyName.substring(0,1).toUpperCase()+propertyName.substring(1);

Class<?>c1=Class.forName("model.User");
Objectobj=c1.newInstance();
//获取指定成员方法
MethodsetMethod=c1.getMethod(setMethodName,String.class);
MethodgetMethod=c1.getMethod(getMethodName);
System.out.println("getName方法");
System.out.println(getMethod);
System.out.println("setName方法");
System.out.println(setMethod);
//执行set方法设值
setMethod.invoke(obj,propertyValue);
//执行get方法获取值
System.out.println("User.Name="+getMethod.invoke(obj));

输出结果:
在这里插入图片描述

使用反射调用get/set方法的实现,不是不可以,但是很麻烦,我们看c1.getMethod(setMethodName,String.class);在这一句上面,必须把方法的参数属性交代后才行,否则就报错找不到方法。如果是Boolean类型,开头为"is",我们还需要去根据属性类型去判断,所以实在是不方便,
这时候就应该使出新姿势—内省。

2. 内省的用法

2.1 内省的定义

内省是什么意思?
先说反射吧,
反射: Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法。
内省:内省就是反射的儿子,内省只对JavaBean属性方法有效,
JavaBean: Java Bean 实际是就是一个普通的 Java Class,但是bean的对象属性为 private,且只允许通过getter方法和setter方法访问对象的属性 ,必须具有一个无参的构造函数以及实现serializable接口。

2.2 内省的姿势

到这里,在座可能对内省的姿势怎么摆,还不是很熟悉,我们先摆一个!
还是上面的User对象。
代码如下:

Useruser=newUser();
user.setName("我没有名称");
PropertyDescriptordescriptor=newPropertyDescriptor(propertyName,User.class);
MethodreadMethod=descriptor.getReadMethod();
Objecto1=readMethod.invoke(user);
System.out.println("读取属性值:"+o1);

MethodwriteMethod=descriptor.getWriteMethod();
Objecto2=writeMethod.invoke(user,propertyValue);
System.out.println("设置属性值:"+user.getName());

结果如下:
在这里插入图片描述

这里主要是使用PropertyDescriptor,通过属性名称加载readMethod相当于get/is,以及writeMethod相当于set,不需要去提前知道属性的类型,也不用判断get/is,完美的解决我们上面提到传统反射的问题。

通过 Introspector内省器加载User类所有能操作的属性以及方法。

BeanInfobeanInfo=Introspector.getBeanInfo(User.class);
BeanDescriptorbeanDescriptor=beanInfo.getBeanDescriptor();
MethodDescriptor[]methodDescriptors=beanInfo.getMethodDescriptors();
PropertyDescriptor[]propertyDescriptors=beanInfo.getPropertyDescriptors();

System.out.println("-------------START-----------------");
System.out.println("输出beanDescriptor");
System.out.println(beanDescriptor);
System.out.println("输出User类所有的方法");
Arrays.stream(methodDescriptors).forEach(item->System.out.println(item));
System.out.println("输出User类所有的属性");
Arrays.stream(propertyDescriptors).forEach(item->System.out.println(item));
System.out.println("-------------END-----------------");

输出结果如下:
在这里插入图片描述

可以看到内省可以获取的方法除了Object的方法,还有自身属性的get/set方法,属性也只有私有且带get/set的属性,有Object也就是他是包含父类的方法以及属性的。

2.2 探究内省

2.2.1 Introspector

内省的类比较常用的有Introspector,PropertyDescriptor
Introspector就是内省的大类,我们先看看大类。

根据我们上面的操作,可以看到首先是通过Bean Class获取一个BeanInfo,然后再继续根据BeanInfo获取属性与方法。
BeanInfobeanInfo=Introspector.getBeanInfo(User.class);

看看实现
在这里插入图片描述

可以看到getBeanInfo主要是实例化一个Introspector对象,里面除了将beanClass属性赋值还将这个beanClass的父类也放进对象。

核心在于下面这两段代码:

   ThreadGroupContext context = ThreadGroupContext.getContext();
        BeanInfo beanInfo;
        synchronized (declaredMethodCache) {
            beanInfo = context.getBeanInfo(beanClass);
        }

在这里插入图片描述

这里将加载的beanInfo放进缓存对象,下次去加载的时候就不需要重新加载了,同时这个缓存对象还是一个WeakCache,就是在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。加快了内省且避免了OOM 学到了。

当然不仅仅BeanInfo,Method,Property都是放进Cache里面。

Introspector getBeanInfo()的具体实现:

在这里插入图片描述

他加载了6个对象/属性,用他们新建一个实例化的BeanInfo,这个Beaninfo包含了一个Bean的方法、Event以及不同类型属性、看来我们想看的内容都在这里了。

2.2.2 BeanDescriptor

他所调用的是getTargetBeanDescriptor

简单来说,就是通过classLoader获取类。

2.2.3 methodDescriptors

methodDescriptors实际也是通过class的反射获取当前JavaBean的所有方法以及其父类所有方法。

2.2.4 EventSetDescriptor

EventSetDescriptor是获取javabean的事件监听器,主要是获取add,get,remove方法开头的监听事件。

2.2.5 PropertyDescriptor

我们查看getTargetPropertyInfo的实现,主要根据上面加载的method,获取参数数量,解析方法名,只要是get\is,set的满足条件的实例化一个PropertyDescriptor对象

在这里插入图片描述

PropertyDescriptor主要包含read write(对应get/set)的方法、属性对象,提供调用read/write的入口。

3. 内省推荐用法

最常用之一的场景就是表单赋值,一般表单传回来我们直接用参数名称找writeMethod就行了,实现如下

public void insertUser(HttpServletRequest request) throws Exception {
    User user = new User();
   
    PropertyDescriptor[] pds = Introspector.getBeanInfo(User.class).getPropertyDescriptors();
    for (PropertyDescriptor pd : pds) {
        pd.getWriteMethod().invoke(user, request.getParameter(pd.getName()));
    }
}

4. 总结

内省的使用确实在某些情况下比反射好用,当然有个重要的点就是调用内省read/write,必须保证JavaBean里面含有对应属性名称以及包含get/set方法,否则报错。

发布了20 篇原创文章 · 获赞 17 · 访问量 5081

猜你喜欢

转载自blog.csdn.net/qq_28540443/article/details/104527683