学习Java的时间也过了这么久了,反射这个名词耳熟于心,在与小伙伴讨论时也能时常提起。说来惭愧,一直以来浮在技术表面,总是以了解了某个新技术的名词为傲,但当被问起其底层实现时却无话可说。为了改变现状,同时记录自己学习过程。接下来这篇文章我将谈谈对反射的认识、反射能获取的信息、反射的应用。那么,让我们开始挖掘反射相关的知识点吧^_^
谈及反射之前我想说说对象在Java中是怎样存在的以及存放在哪里。想必很多初学Java的小伙伴会接触到对象这个概念,Java是面向对象的语言,Java程序就是通过一个又一个对象构建起来的。我们可以通过 Object o = new Object()
直接生成对象,那么这个o对象是存放在哪里的呢?
如果了解过JMM的同学肯定会很清楚,对象是存放在Java堆上的,我们可以通过直接 new Object()
的方式在堆上生成一个对象, 这种方式生成的对象是在编译时就确定了的
,那么有没有可以让我们在Java程序运行时生成对象的方法? 没错,通过反射我们就可以在运行期动态的生成一个对象了
。
反射带给我们的程序很多便利,譬如运行期间检查类、接口、变量或者方法等信息。我能通过反射做什么呢?对一个运行期对象的值进行改变,也可以拿到类方法操作对象,是不是很有趣额 ~( ̄0 ̄)/
我认为的最重要的 Class 对象,有了这个对象,我们就可以为非作歹了(⊙ ▽ ⊙)哈哈不要想歪了,我只是找不到形容词来描述它了。我们可以通过如下三种方式获取一个类的Class对象:
注意第三种获取Class对象的方式传入的字符串需要 完整的包名以及类名 。 既然这个Class对象那么滴重要,What can it take for us?
通过类的构造器,我们可以生成一个对象。在java.lang.reflect包下有一个Constructor类,这个类生成的实例可以存放关于构造器的一些信息。 通过上面pigClass对象获取的Constructor对象存放构造器的一些信息如下所示:
public String pigConstructorTest() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { Class<Pig> pigClass = Pig.class; //get constructor from pigClass Constructor<Pig> pigConstructor = pigClass.getDeclaredConstructor(); //access private-constructor pigConstructor.setAccessible(true); //new-instance through constructor Pig pig = pigConstructor.newInstance(); return pig.getAge() + " " + pig.getWeight() + " " + pigConstructor.getName(); }
这里调用的是无参构造器,所以生成的对象初始值为默认值。Constructor还有一些譬如获取构造器修饰符、构造器参数类型、构造器参数个数等方法。
能够获取到类的字段,然后改变运行期对象的值是不是很cool喔(≧▽≦)/
public void pigFiledTest() throws NoSuchFieldException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { //get class's all fields Field[] fields = pigClass.getDeclaredFields(); //get one field through certain string Field weight = pigClass.getDeclaredField("weight"); weight.setAccessible(true); for (Field f : fields) { System.out.print(f.getType() + " " + f.getName()); System.out.println(); } Pig pig = pigConstructorTest(); System.out.print(weight.get(pig) + " after filed setting"); weight.set(pig,1); System.out.print(" "+ weight.get(pig)); }
Pig类的字段是私有的,即只有Pig对应的对象能访问,所以通过普通的 pigClass.getFields()
并不能获取其field数组,通过getDeclaredFields()方法即可获取其私有字段。getDeclaredFields()与getDeclaredField()的区别是一个获取所有字段,一个根据传入的字段名获取相应的field对象。Field字段对应的实例还可以在运行期间给改变对象的私有属性,上面的代码运行后结果如下:
int age int weight 0 after filed setting 1
public void pigMethodTest() { //same to field,the getMethods() can not obtain private-method Method[] methods = pigClass.getMethods(); for (Method method : methods) { System.out.print(method.getName() + " " + Arrays.toString(method.getParameters())); System.out.println(); } }
同样的,要想获取类的私有方法只有通过 getDeclaredMethods()
。下面来看看method的一个比较重要的方法:
public void pigMethodInvoke() throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { Method setAge = pigClass.getMethod("setAge", int.class); Pig pig = pigConstructorTest(); System.out.println("before method invoke:" + pig.getAge()); setAge.invoke(pig,10); System.out.println("after method invoke:" + pig.getAge()); }
对应的输出为:
before method invoke:0 after method invoke:10
可以看到,通过 setAge.invoke(pig,10);
我们成功地将猪的年龄设置为0。 invoke
方法的使用需要传入两个参数:
IllegalArgumentException:object is not an instance of declaring class
反射能够带给我们,如果一个类、方法、字段上面打上了注解,那么我们也可以通过反射拿到对应的注解内容。同上,若是一个类、方法、字段上面有泛型数据,反射能帮我们拿到已经处于运行期时候的泛型信息吗?答案是肯定的,毕竟反射这个利器能够带给我们的好处不是一星半点。反射的功能太多了这里我就不一一演示了~~~
我初次对反射感兴趣是在查看AOP相关的知识时,对于invoke方法里面的参数以及method.invoke()到底是个什么鬼一窍不通/(ㄒoㄒ)/~~ 看过别人写的博客,了解AOP、反射相关的知识后大概有了些理解,下面来看看AOP动态代理的基于接口的实现:
public interface BookFacade { void addBook(); }
所有基于JDK实现的动态代理都需要一个接口,其次是具体的委托类:
public class BookFacadeImpl implements BookFacade { @Override public void addBook() { System.out.println("增加图书方法。。。"); } }
这个 addBook()
方法就是我们需要委托出去的方法,我们怎么将这个方法委托出去呢?需要用到一个代理类进行:
public class BookFacadeProxy implements InvocationHandler { /** * 不确定委托类是谁,被代理对象 */ private Object target; public BookFacadeProxy(Object target){ this.target = target; } public BookFacadeProxy(){} /** * 绑定一个委托类,并返回代理类的实例 * @param target * @return */ public Object createProxy(Object target) { this.target = target; //需要target实现了接口 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , this); } /** * * @param proxy 代理对象 * @param method 委托类实例的方法 * @param args 委托类方法参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before proxy"); //target是要调用method方法的对象,args是传入的参数 //这里的method是BookFacade的addBook()方法 //result返回invoke执行结果,可能为null Object result = method.invoke(target,args); System.out.println("after proxy"); //从委托类方法调用返回的值 return result; } }
通过实现 InvocationHandler
类并重写其 invoke
方法来实现对委托类委托的方法增强。具体的看看 invoke
方法,
Object proxy
,这个参数一般是用不到的; Method method
是委托类具体想要执行的方法, Object[] args
是方法传入的实参。 method.invoke(target,args);
这里的 target
就是传入的委托类实际对象。
对于这个类的 createProxy()
方法,其实没有必要非得在代理类里面实现,里面的 Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , this)
主要用于生成具体的代理对象, this
传递的是 BookFacadeProxy
代理类本身的实例,不过想要调用委托类的委托方法,需要将返回的对象强转为接口类型:
public class BookFacadeTest { public static void main(String[] args) { BookFacadeProxy proxy = new BookFacadeProxy(); BookFacade bookFacade = (BookFacade) proxy.createProxy(new BookFacadeImpl()); bookFacade.addBook(); BookFacadeImpl target = new BookFacadeImpl(); BookFacade newProxyInstance = (BookFacade) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new BookFacadeProxy(target)); newProxyInstance.addBook(); } }
可以看到只有将返回的代理类实例强转为接口类型,我们才能调用委托出去的方法。 有了动态代理,我们能够对一个实现了接口的类的方法的使用做一些增强,在方法的运行的前后做一些处理。