反射是java中比较重要的一块知识,虽然平时的开发中可能不太用到反射,但是在框架的开发中,反射是非常重要的,而且各个框架中大量使用的注解,在解析注解的时候也需要用到反射,所以说反射是框架的灵魂。说了这么多,其实反射做的事情很简单,就是在运行时获取类或者对象的信息。
1.获取class对象
我们将.java结尾的文件称为源文件,源文件最终会被编译。class文件,也就是字节码文件,反射的作用就是操作字节码文件来获取类和对象的信息。
为了能够利用反射获取想要的信息,首先要做的就是获取类对象,一般通过以下三种方式来获取:
- Class.forName(“类的全称”)
- 类名.class
- 对象.getClass()
*通常情况下建议使用第二种方式来获取class对象,与另外两种方式比较,他有两个优势:
(1)代码更安全,在编译阶段就可以确认class是否存在;
(2)性能更好,无需调用方法;
很多情况下也通过第一种方式来获取class对象,比如通过读取配置文件的类路径来读取对象。
2.获取class中的各个组成部分
获取class对象,只是为了方便我们能够拿到这个类中各个组成部分的信息,在了解这些具体方法之前,我们先来了解下一个类的组成,看下具体有哪些组成部分:
如上图,通常一个类可能的组成部分有这些,还有一些比如内部类,静态代码块之类的没有列在其中。
接下来,我们就通过反射一一获取:
-
包名
getPackage()
System.out.println(dog.getPackage().toString());
- 类名
getName()
System.out.println(dog.getName());
- 父类
getSuperClass()
System.out.println(dog.getSuperclass());
- 接口
getInterfaces()
λ for(Class inter : dog.getInterfaces()){
System.out.println(inter);
}
- 成员变量
获取成员变量时可以注意到有两个方法提供:getFields()和getDeclaredFields(),我们执行下看看区别:
public class TestDogReflection {
public static void main(String[] args) {
Class dog = Dog.class;
for (Field field:dog.getFields()){
System.out.println(field);
}
System.out.println("=========");
for (Field field:dog.getDeclaredFields()){
System.out.println(field);
}
}
}
使用getFields()方法时,父类和当前类中的成员变量都被打印出来了,但是被private修饰符修饰的变量没有输出,而使用getDeclaredFields()方法则是输出了当前类的所有成员变量
- 构造器
和获取成员变量类似,获取构造器同样提供了getConstructors()和getDeclaredConstructors()两个方法:
public static void main(String[] args) {
Class dog = Dog.class;
for (Constructor constructor:dog.getConstructors()){
System.out.println(constructor);
}
System.out.println("=========");
for (Constructor constructor:dog.getDeclaredConstructors()){
System.out.println(constructor);
}
}
需要获取执行构造器的时候,只需要将参数类型传进去即可:
public static void main(String[] args) throws NoSuchMethodException {
Class dog = Dog.class;
Constructor constructor = dog.getDeclaredConstructor(String.class,int.class);
System.out.println(constructor);
}
- 方法
获取方法同样也会有getMethods()和getDeclaresMethods():
public static void main(String[] args) throws NoSuchMethodException {
Class dog = Dog.class;
for(Method method : dog.getMethods()){
System.out.println(method);
}
System.out.println("============");
for(Method method : dog.getDeclaredMethods()){
System.out.println(method);
}
}
可以看到,使用getMethods()方法,出了当前类的公共方法以外,所有的父类包括Object中的方法也都被打印出来了。
通过方法名和具体参数类型,可以获取指定方法:
public static void main(String[] args) throws NoSuchMethodException {
Class dog = Dog.class;
Method method = dog.getDeclaredMethod("makeNoise");
System.out.println(method);
}
- 注解
getAnnotations()可以获取方法或者类上的使用的注解
Method method = dog.getDeclaredMethod("run");
for (Annotation annotation : method.getAnnotations()){
System.out.println(annotation);
}
- 修饰符
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
Class dog = Dog.class;
Field field = dog.getDeclaredField("age");
int mod = field.getModifiers();
System.out.println(Modifier.toString(mod));
}
3.通过反射操作对象
上面简单介绍了获取class对象的内容,接下来我们操作clss对象,主要就是实例化对象,调用对象的方法。
还是直接看例子吧:
通过newInstance()方法调用无参构造方法,需要调用有参的只需要加入参数即可:
Class clazz = Class.forName("com.ljw.reflection.Dog");
Dog dog = (Dog) clazz.newInstance();
Method method = clazz.getMethod("run");
method.invoke(dog);
通过方法名可以直接获取到指定方法,通过method.invoke(对象,参数列表)方法,可以执行方法,需要注意的时,这样操作需要遵守访问修饰符的权限,如果需要调用私有方法,需要设计访问权限:
Class clazz = Class.forName("com.ljw.reflection.Dog");
Dog dog = (Dog) clazz.newInstance();
Method method = clazz.getDeclaredMethod("fly",int.class);
method.setAccessible(true);
method.invoke(dog,5);
同样的,改变私有成员变量,也需要对field设置访问权限,从这点上来讲,反射在一定程度上破坏了java的封装性。
4.代理
除了以上,利用反射还可以生成jdk动态代理。(有关代理模式具体内容参考设计模式这篇博客)。
java开发的基本原则就是开闭原则,对修改封闭,对扩展开放。代理类做的工作就是如此,jdk动态代理只能代理有接口的类,只不过他是通过实现InvocationHandler接口来实现对目标的代理.我们一起开看下代码:
java中提供了一个Proxy代理类,在java.lang.reflect包下,可以看下这个类的说明:
通过提供的静态方法对接口实现代理,而且官方还给出了示例代码:
* <pre>
* InvocationHandler handler = new MyInvocationHandler(...);
* Class proxyClass = Proxy.getProxyClass(
* Foo.class.getClassLoader(), new Class[] { Foo.class });
* Foo f = (Foo) proxyClass.
* getConstructor(new Class[] { InvocationHandler.class }).
* newInstance(new Object[] { handler });
* </pre>
* or more simply:
* <pre>
* Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
* new Class[] { Foo.class },
* handler);
* </pre>
可以看到,想要实现jdk动态代理,只需要实现InvocationHandler接口,再调用Proxy的newProxyInstance方法对具体的接口方法进行代理即可。
我们看一个具体例子:
接口类:
package com.ljw.reflection;
/**
* Created by liujiawei on 2018/8/1.
*/
public interface AnimalAction {
public void run();
}
实现类:
package com.ljw.reflection;
/**
* Created by liujiawei on 2018/7/26.
*/
public class Dog implements AnimalAction {
@Override
public void run() {
System.out.println("dog run");
}
}
代理类:
package com.ljw.reflection;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by liujiawei on 2018/8/1.
*/
public class DynamicProxy implements InvocationHandler {
private Object target; //代理类
public DynamicProxy(Object target) {
this.target = target;
}
//重写invode实现代理工作 aop
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(target, args);
System.out.println("after");
return null;
}
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
}
}
测试类:
package com.ljw.reflection;
/**
* Created by liujiawei on 2018/8/1.
*/
public class TestDogActionByDynamicProxy {
public static void main(String[] args) {
Dog dog = new Dog();
DynamicProxy invocationHandler = new DynamicProxy(dog);
AnimalAction proxy = (AnimalAction) invocationHandler.getProxy();
proxy.run();
}
}
可以看到代理类起到的作用,在没有修改对象的情况下,增加了部分实现,通过重新InvocationHandler的invoke()方法完成代理工作,通过Proxy类的newProxyInstance指定目标类完成整个动态代理工作,jdk动态代理只针对有接口的类,没有接口的类实现代理需要通过cglib生成代理。