1.何为反射
对于web的表单数据保存,最开始的印象应该就是从web端拿到数据塞入到对应的数据结构,然后存入到数据库。在招商平台的子活动相关数据保存中,看到代码端是以如下的方式来将对应的表单数据保存到数据结构里面,相比不停的调用set方法优雅了不少。group.setProperties(contentBlockDO);
//group,com.alibaba.citrus.service.form.Group
//contentBlockDO 自定义的DO
上面简单的一行代码就将页面上填写的表单项赋值到对应的类实例里面。去看了下setProperties方法的实现,里面就是用的反射,动态获取类对象,循环遍历其set方法,将对应的值赋值给对象。
java反射就是获取动态对象,并获取其中的属性、方法。不仅可以动态的获取对象,也能动态的创建对象,同时能够访问对象的方法,修改对象的属性值。反射有这样一些常用的用处。
a)反射方式调用方法 动态代理 扩展API,例如sping ioc中的依赖注入。
b)反射可以操作某个类的私有变量和方法
c)反射操作对象更加灵活,例如如上述的例子中只要有了form对象和 property名字就可以利用反射给property赋值和取值 对这类操作 一个方法就可以搞定。
d)运行过程中对未知类进行初始化。
2.java反射的实现
介绍java的反射,首先需要了解下Class类型。Class对象是java类(包括类和接口)在装入jvm的时候产生的一个与之关联的类,可以通过这个被装入的对象来访问装入类的详细信息(这块的内容在后续看jvm的时候再补充下)。Class对象的身影会一直贯穿java反射的使用过程。
写了一个很简单的基本类来测试java反射的各种使用,这里的话就列举下如何使用反射来给对象的属性进行赋值,类对象路径 reflect.Base。
//首先获取Class对象,如下三种方式,可以看出可以由类名、路径、实例对象获取Clss对象
Class base = Base.class;
Base baseTest = new Base();
Class base1 = baseTest.getClass();
Class base2 = Class.forName("reflect.Base"); //需要捕获抛出异常
//获取类的属性,getDeclaredXxx和getXxx的区别是getDeclaredXxx可以获取私有的
Field[] fields = base.getFields();
Field[] fieldsDeclared = base.getDeclaredFields();
//获取类里面的方法,getMethods可以获取父类的方法
try {
Method[] methods = base.getMethods();
Method[] methods1 = base.getDeclaredMethods();
//实例化一个Base对象
Base test = (Base)base.newInstance();
//通过setB方法对属性赋值
Method m = base.getDeclaredMethod("setB", String.class);
//设置权限,如果是private类型的属性和方法必须设置此项才能访问
m.setAccessible(true);
m.invoke(test, "2");
m.invoke(baseTest, "2");
//通过属性名称对属性值赋值
Field f = base.getDeclaredField("b");
f.setAccessible(true);
f.set(test,"hello");
}catch (Exception e){
System.out.println("exception" + e);
}
对于java反射里面用到的setAccessible()方法刚接触到的时候感觉很强大又很奇怪。看了一下源码。
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
throw new SecurityException("Can not make a java.lang.Class" +
" constructor accessible");
}
}
obj.override = flag;
里面涉及到了AccessibleObject这个类,这个类是Field、Method、Constructor三个的父类,这个函数主要是将里面的 override 设置为对应的值,值的含义就是调用相应的方法时候跳过java的权限检查。
感觉强大的是这个方法居然能去操作java的私有对象,奇怪的是这不是破坏了java的封装性嘛。看了看其他一些人的说法,整体上的感觉就是java封装也是防君子不防小人,而setAccessible()在很多场合下又是需要用的上的,例如不科学的封装、性能测试之类的。暂且这样理解吧,后面有更好更能说服自己的理解再来更新。
3.java反射应用——spring依赖注入实现
接触spring的经常会听到IOC( Inversion of Control )的概念。控制反转,简单理解就是之前传统的程序里面的对象及其依赖对象都是由自己创建、自己注入,而在spring的IOC里面由依赖对象的创建、管理、注入都由容器去管理了。 这样的好处是在大型的系统里面,不再需要自己去管理创建多层次的对象管理逻辑。
spring IOC设计思想里面的依赖注入 DI( Dependency Injection )就是java反射应用的一个典型案例。
IOC是一个设计思想上提出来的概念 DI 是一个具体的实现方式。
当然spring的IOC肯定比这里介绍的复杂,因为中间涉及到各种bean的初始化、加载顺序。这里就只讨论下spring里面DI的实现原理。
使用过spring的都知道,spring里面的bean都是配置在 beans.xml 类似的一个配置文件里面,可以在
DefaultListableBeanFactory源码里面看到这个数据结构,以Map<String,BeanDefinition>的方式来存储.xml配置文件配置的bean,依赖注入的时候注入的对象就是从这个Map里面获取的。
/** Map of bean definition objects, keyed by bean name */
private final Map beanDefinitionMap = CollectionFactory.createConcurrentMapIfPossible(16);
分析下源码整个依赖注入的流程的入口可以参考
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)
public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException {
// Use non-singleton bean definition, to avoid registering bean as dependent bean.
RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
return createBean(beanClass.getName(), bd, null);
}
追踪下去发现底层执行了两个关键的函数
.createBeanInstance:生成Bean所包含的java对象实例
.populateBean :对Bean属性的依赖注入进行处理
这里介绍下 populateBean 注入的过程,这里用到的就是java的反射。其中org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues 来进行bean属性的注入。
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#autowireByName
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#autowireByType
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues
使用反射来将bean里面依赖的bean、属性注入到bean里面。
总结下来,整个spring的DI注入可以分为两部分,第一部分是将bean存储在一个hashMap里面,第二部分是根据名字或类型去取出对应的bean使用反射注入到对应的实例里面去。
4.java反射应用——动态代理
java反射的另一个重要应用处就是动态代理,最近学习反射的过程中看到了java的动态代理,越发感觉到java反射的强大。
java的动态代理有如下作用
b)远程调用,比如现在有Java接口,这个接口的实现部署在其它服务器上,在编写客户端代码的时候,没办法直接调用接口方法,因为接口是不能直接生成对象的,这个时候就可以考虑代理模式(动态代理)了,通过 Proxy.newProxyInstance代理一个该接口对应的 InvocationHandler对象,然后在 InvocationHandler的invoke方法内封装通讯细节就可以了
java动态代理类的实现主要涉及到两个类:java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler
其中InvocationHandler类里面只有一个 invoke(Object proxy, Method method, Object[] args)方法需要实现,看到invoke肯定就想到了反射了。一个典型常见的代理模式实现如下,在典型的反射invoke()调用动态对象的方法前后做部分其他处理措施。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
。。。
//调用之前
doBefore();
//调用原始对象的方法
result=method.invoke(obj, args);
//调用之后
doAfter();
。。。
}
5.收获
最近看了java反射并写了一些小例子测试以后,最大的感受是理解了动态对象的方法、属性的访问。理解了对象的优雅赋值转换、理解spring的注入过程、知道了java的动态代理,后续听到团队其他人员讨论使用代理的方式来做xxx的时候也可以有自己的思考。