这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
反射
Java反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,大家应该先了解两个概念,编译期和运行期。
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
运行期:是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java 反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,在 ORM 中间件的实现中,运用 Java 反射机制可以读取任意一个 JavaBean 的所有属性,或者给这些属性赋值。
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect
包。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件(.class)对应的 Class 类型的对象.
1.Class 对象
在你想检查一个类的信息之前,你首先需要获取类的 Class 对象。Java 中的所有类型包括基本类型(int, long, float等等),即使是数组都有与之关联的 Class 类的对象。如果你在编译期知道一个类的名字的话,那么你可以使用如下的方式获取一个类的 Class 对象。
现在,.class文件被类加载器加载到内存中,并且JVM根据其字节数组创建了对应的Class对象。所以,我们来研究一下Class对象。
Class对象是Class类的实例,我们将在这一小节一步步分析Class类的结构。
但是,在看源码之前,我想问问聪明的各位,如果你是JDK源码设计者,你会如何设计Class类?
假设现在有个BaseDto类
上面类至少包括以下信息(按顺序):
- 权限修饰符
- 类名
- 参数化类型(泛型信息)
- 接口
- 注解
- 字段(重点)
- 构造器(重点)
- 方法(重点)
最终这些信息在.class文件中都会以0101表示:
整个.class文件最终都成为字节数组byte[] b,里面的构造器、方法等各个“组件”,其实也是字节。
2.构造器
我们可以通 过 Class 对象来获取 Constructor 类的实例:
Class aClass = ...//获取Class对象
Constructor[] constructors = aClass.getConstructors();
返回的 Constructor 数组包含每一个声明为公有的(Public)构造方法。 如果你知道你要访问的构造方法的方法参数类型,你可以用下面的方法获取指定的构造方法,这例子返回的构造方法的方法参数为 String 类型:
Class aClass = ...//获取Class对象
Constructor constructor =
aClass.getConstructor(new Class[]{String.class});
3.方法
可以通过 Class 对象获取 Method 对象,如下例:
Class aClass = ...//获取Class对象
Method[] methods = aClass.getMethods();
返回的 Method 对象数组包含了指定类中声明为公有的(public)的所有变量集合。
如果你知道你要调用方法的具体参数类型,你就可以直接通过参数类型来获取指定的方法,下面这个例子中返回方法对象名称是“doSomething”,他的方法参数是 String 类型:
Class aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});
如果根据给定的方法名称以及参数类型无法匹配到相应的方法,则会抛出 NoSuchMethodException。
反射实际应用
经过上面的反射的原理介绍,下面就要开始反射的实际场景的应用,所有的技术,你知道的该技术的应用场景永远是最值钱。这个是越多越好,知道的场景越多思路就越多。
反射的实际场景的应用,这里主要列举这几个方面:
- 动态代理
- JDBC 的数据库的连接
- Spring 框架的使用
动态代理实际就是使用反射的技术来实现,在程序运行时创建一个代理类,用来代理给定的接口,实现动态处理对其所代理的方法的调用。
实现动态代理主要有以下几个步骤:
- 实现
InvocationHandler
接口,重写invoke
方法,实现被代理对象的方法调用的逻辑。 Proxy.getProxyClass
获取代理类- 执行方法,代理成功
动态代理的实现代码如下所示,首先创建自己类DynamicProxyHandler
实现 InvocationHandler
:
public class DynamicProxyHandler implements InvocationHandler {
private Object targetObj;
public DynamicProxyHandler() {
super();
}
public DynamicProxyHandler(Object targetObj) {
super();
this.targetObj= targetObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.err.println("开始执行targetObj的方法");
//执行被代理的targetObj的方法
method.invoke(targetObj, args);
System.out.println("执行方法结束");
return null;
}
}
然后执行Proxy.newProxyInstance
方法创建代理对象,最后执行代理对象的方法,代码实现如下:
User user = new UserImpl();
DynamicProxyHandler dynamicProxy = new DynamicProxyHandler(user);
//第一个参数:类加载器;第二个参数:user.getClass().getInterfaces():被代理对象的接口;第三个参数:代理对象
User userProxy = (User ) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(), dynamicProxy);
userProxy.login();
userProxy.logout();
以上的实现是jdk的动态代理方式,还有一种动态代理是Cglib
的动态代理方式,Cglib
动态代理也是被广泛的使用,比如Spring AOP
框架中,实现了方法的拦截功能。