封装BeanMap以及java的反射和内省

BeanMap

学习具体的技术工具的好办法就是些Demo、造轮子。因此,我实现了一个称为BeanMap的类来应用java反射API。java

这个BeanMap的功能是将一个Bean包装成Map来使用。对调用者来讲,是以操做Map的方式来操做BeanMap,
可是,实际上的数据是存储在被包装的Bean对象中的。git

这种思路相似适配器模式,可让你以Map的接口操做Bean对象。
但又有点像“视图”思想,真正的数据是存储在Bean对象中的,BeanMap只是对它进行操做的“视图”。对BeanMap的全部操做都会反映在后面的Bean对象中。github

下面是BeanMap的一个使用例子。缓存

@Getter
    @Setter
    class Point {
        private Integer x = 2;
        private Integer y = 1;
    }
BeanMap map = new BeanMap(new Point());
map.put("x", 10);
map.get("y");

示例中的Point这个Bean类拥有属性x和y。可是被BeanMap包装后,它就变成了一个拥有键"x"和键"y"的Map了。oracle

那这个BeanMap类有什么实际应用呢?哈哈这只是我为了写反射的DEMO本身设计出来的一个类。 app

java反射

稍微思考下不难发现,BeanMap实现的关键点在于,
BeanMap接受外部传入的键,这是一个字符串。以后,它获得找到Bean对象中对应的getter和setter,并操做Bean对象。函数

将这个要求向通用化的方向分析,也即提供一个与函数匹配的字符串,获得对该函数的引用。工具

经过反射,就可以实现上面的要求。
如今流行的语言大都支持反射。反射可以在运行时获得程序元数据,好比某类的信息。
还可以根据这些元信息来修改程序状态或逻辑。
因为反射是在 运行 时获得的信息,那么支持反射的语言也必然要在程序运行时将这些元信息存放在内存某处。性能

java语言提供了反射API,这里是官方完整的文档:https://docs.oracle.com/javas...
对于反射出来的信息,Java的反射API将其以类的形式包装提供。
Java的反射机制提供了4个类:学习

  • Class 类
  • Constructor 构造器
  • Field 属性
  • Method 方法

如今,试图利用反射API,获得一个POJO类的全部属性名称。以下:

private List<String> names() {
        Method[] methods = bean.getClass().getMethods();
        List<String> result = new ArrayList<>();
        for (Method getter : methods) {
            int mod = getter.getModifiers();
            if (getter.getName().startsWith("get") && !Modifier.isPublic(mod)) {
                String name = getter.getName().substring("key".length());
                name = name.substring(0, 1).toLowerCase() + key.substring(1);
                result.add(name);
            }
        }
        return result;
    }
  1. 上述代码的思路很简单,java提供的反射API,可以获得该类的全部方法列表。按照约定,POJO类的属性xxx的getter方法都命名为GetXxx。那么,遍历这个表,找出全部getter,字符串处理下,就获得了全部的属性。
  2. 经过调用对象的getClass()方法获得一个Class对象,也即反射出该对象的Class信息。Class::getMethods函数则是反射出该类的全部方法。
  3. Method::getModifiers获得方法的修饰符信息。它是一个整数,我猜想是用位标记各个修饰符的,处理它须要用到位运算。不过可使用Modifier这个类的工具方法去处理它。

不过,这里是想实现BeanMap。一种思路是,经过对反射出的列表进行一次处理,除了获得每一个属性的名称外,还要获得它们的getter和setter。下面是一种粗暴的实现:

private List<Item> fileds() {
        Method[] fields = bean.getClass().getMethods();
        List<Item> result = new ArrayList<>();
        for (Method getter : fields) {
            if (getter.getName().startsWith("get") && isVaildModifier(getter.getModifiers())) {
                String setterName = "set" + getter.getName().substring("get".length());
                for (Method setter : fields) {
                    if (setter.getName().equals(setterName) && isVaildModifier(setter.getModifiers())) {
                        result.add(new Item(this.bean, getter, setter));
                    }
                }
            }
        }
        return result;
    }

固然,上面的实现思路缺点不少。
首先,是性能问题。上面的代码虽然能实现功能,可是太暴力了。
虽然一个POJO类的方法顶多几十个,可是考虑到在具体的实践中,这些都是做为较为底层的基础设施,项目中可能会频繁被业务代码调用,所以对其进行性能上的分析是有必要的。

其次,上面这段逻辑考虑的也不全面。上面的逻辑是对公开的getter进行进一步的处理的,那么,若是getter是static的呢?
若是getter被native修饰呢?若是是超类所拥有的属性,那么该如何处理这种状况呢?

java内省API

为了方便使用,java针对反射API进行了封装,提供了一组内省API。
这组内省API主要是针对POJO类进行操做的,可以获取POJO类的属性信息。

那么,有了jdk自带的用于对Bean进行反射的工具后,上面的逻辑既能够简化了:

private List<Item> fileds() throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(List.class);
        return Stream.of(beanInfo.getPropertyDescriptors()).map((pd) -> {
            return new Item(this.bean, pd.getReadMethod(), pd.getWriteMethod());
        }).collect(Collectors.toList());
    }
  1. 使用getBeanInfo方法获取某个Bean类的内省信息,这些信息封装在BeanInfo对象中。
  2. PropertyDescriptor是对Bean类中的一个属性的封装,经过它能够获取该属性的名称、getter方法、setter方法等信息。
  3. beanInfo::getPropertyDescriptors获取Bean类的全部的PropertyDescriptor

能够看到,经过java的内省机制,解决了BeanMap的最关键的问题。并且,使用java自带的内省机制比本身经过反射API处理有如下好处:

  1. 内省API基于反射API进行的封装,使用更高层次的接口,固然更省心,开发效率更高。
  2. jdk在封装反射API的时候,会充分考虑到各类状况。如考虑到继承这一问题,Introspector::getBeanInfo函数可接收第二个参数,只反射出继承链中该类到第二个参数指定类之间类的属性。
  3. 内省API也充分考虑到了性能,其中拥有缓存机制,以提高性能。

最后

解决了最关键的逻辑后,剩下的部分,就是对Map接口进行实现,填充一些封装目的的代码。在这里,我将核心的逻辑放在BeanMapImpl类中,而BeanMap仅仅负责实现Map接口,相关操做转发到BeanMapImpl相应的方法中实现。这样显得程序结构更为清晰:
https://github.com/frapples/j...

相关文章
相关标签/搜索