学习Java的时间也过了这么久了,反射这个名词耳熟于心,在与小伙伴讨论时也能时常提起。说来惭愧,一直以来浮在技术表面,总是以了解了某个新技术的名词为傲,但当被问起其底层实现时却无话可说。为了改变现状,同时记录自己学习过程,希望通过这个平台写下自己对技术的认识。接下来这篇文章我将谈谈对反射的认识、反射能获取的信息、反射的应用。那么,让我们开始挖掘反射相关的知识点吧^_^
关于反射的理解
谈及反射之前我想说说对象在Java中是怎样存在的以及存放在哪里。想必很多初学Java的小伙伴会接触到对象这个概念,Java是面向对象的语言,Java程序就是通过一个又一个对象构建起来的。我们可以通过Object o = new Object()
直接生成对象,那么这个o对象是存放在哪里的呢?
如果了解过JMM的同学肯定会很清楚,对象是存放在Java堆上的,我们可以通过直接new Object()
的方式在堆上生成一个对象,这种方式生成的对象是在编译时就确定了的,那么有没有可以让我们在Java程序运行时生成对象的方法?没错,通过反射我们就可以在运行期动态的生成一个对象了。
反射带给我们的程序很多便利,譬如运行期间检查类、接口、变量或者方法等信息。我能通过反射做什么呢?对一个运行期对象的值进行改变,也可以拿到类方法操作对象,是不是很有趣额 ~( ̄0 ̄)/
反射获取到的信息
我认为的最重要的Class对象,有了这个对象,我们就可以为非作歹了(⊙ ▽ ⊙)哈哈不要想歪了,我只是找不到形容词来描述它了。我们可以通过如下三种方式获取一个类的Class对象:
- Class pigClass = Pig.class;
- Class pigClass = new Pig().getClass();
- Class pigClass = Class.forName("Pig");
注意第三种获取Class对象的方式传入的字符串需要完整的包名以及类名。
既然这个Class对象那么滴重要,What can it take for us?
构造器
通过类的构造器,我们可以生成一个对象。在java.lang.reflect包下有一个Constructor类,这个类生成的实例可以存放关于构造器的一些信息。
通过上面pigClass对象获取的Constructor对象存放构造器的一些信息如下所示:
public String pigConstructorTest() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
Class pigClass = Pig.class;
//get constructor from pigClass
Constructor 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
方法的使用需要传入两个参数:
- Object obj,需要对方法进行调用的对象,如果该对象中没有该方法那么会抛出一个
IllegalArgumentException:object is not an instance of declaring class
。 - Object... args,方法需要传递的实际参数。
其他
反射能够带给我们,如果一个类/方法/字段上面打上了注解,那么我们也可以通过反射拿到对应的注解内容。同上,若是一个类/方法/字段上面有泛型数据,反射能帮我们拿到已经处于运行期时候的泛型信息吗?答案是肯定的,毕竟反射这个利器能够带给我们的好处不是一星半点。反射的功能太多了这里我就不一一演示了~~~
反射的应用
我初次对反射感兴趣是在查看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();
}
}
可以看到只有将返回的代理类实例强转为接口类型,我们才能调用委托出去的方法。
有了动态代理,我们能够对一个实现了接口的类的方法的使用做一些增强,在方法的运行的前后做一些处理。
后记
天之道,损有余而补不足,是故虚胜实,不足胜有余。
漫漫代码路,磕磕盼盼Review,在搞技术的这条路上,希望你我都能去除浮躁内心。