了解反射之前,先来了解Java中的两个概念:编译期和运行期。
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作。比如:检查语法错误。
运行期:是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
反射(Reflection)是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。
Java反射机制主要提供以下功能:
1.在运行时获取任意一个对象所属的类。
2.在运行时构造任意一个类的对象。
3.在运行时获取任意一个类所具有的成员变量和方法。
4.在运行时调用任意一个类对象的方法。
5.生成动态代理。
在JDK中,主要由以下类来实现Java反射机制:
Field类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的属性的类。
Constructor类:提供有关类的构造方法信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类。
Method类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的类。
Class类:表示正在运行的Java应用程序中的Class类型信息的实例。
Object类:Object是所有Java类的父类。所有对象都默认继承Object类。
第一种方式:Class.forName(类名),是通过类的完全限定名字符串获取Class对象,这也是平时最常用的反射获取Class对象的方法。
try {
Class cls = Class.forName("com.fulian.demo.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
第二种方式:类名.class,通过类名的class属性。
Class cls = String.class;
第三种方式:对象.getClass(),通过对象的getClass()方法,该方法由Object提供。
String s = "just do it";
Class cls = s.getClass();
类型 |
访问方法 |
返回值类型 |
说明 |
包路径 |
getPackage() |
Package 对象 |
获取该类的存放路径 |
类名称 |
getName() |
String 对象 |
获取该类的名称 |
继承类 |
getSuperclass() |
Class 对象 |
获取该类继承的类 |
实现接口 |
getlnterfaces() |
Class 型数组 |
获取该类实现的所有接口 |
构造方法 |
getConstructors() |
Constructor 型数组 |
获取所有权限为 public 的构造方法 |
getDeclaredContruectors() |
Constructor 型数组 |
获取当前对象的所有构造方法 |
|
newInstance() | Object对象 | 调用当前对象的public无参构造方法 | |
方法 |
getMethods() |
Methods 型数组 |
获取所有权限为 public 的方法 |
getDeclaredMethods() |
Methods 型数组 |
获取当前对象的所有方法 |
|
invoke(对象名,参数) | Object对象 | 调用当前对象的方法 | |
成员变量 |
getFields() |
Field 型数组 |
获取所有权限为 public 的成员变量 |
getDeclareFileds() |
Field 型数组 |
获取当前对象的所有成员变量 |
|
get(对象名) | Object对象 | 获取当前成员变量的值 | |
set(对象名,参数) | 无 | 设置当前成员变量的值 |
优点:运行期类型的判断,动态加载类,提高代码灵活度。
缺点:
1.性能瓶颈:反射相当于一系列的解释操作,通知JVM要做的事情,性能比直接的java代码要慢很多。
2.安全问题,通过反射可以动态操作私有属性或方法,破坏封装,同时也增加了类的安全隐患。
在平时的项目开发过程中,基本很少直接使用到反射机制;
反射是框架设计的灵魂,Spring/Mybatis等框架底层,大量使用到反射机制进行框架设计;
举例:
1.使用JDBC连接数据库时,使用Class.forName(驱动类名),通过反射加载数据库的驱动类;
2.Spring框架的IOC(动态加载管理Bean)创建对象以及AOP(动态代理)使用反射进行底层实现;
3.MyBatis框架的Mapper接口代理对象的创建,也是通过反射实现。
动态代理(Dynamic Proxy): 可以在运行期,动态创建某个interface的实例,实际上是JVM在运行期动态创建class字节码并加载的过程。动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。
代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。
代理模式角色分为 3 种:
●Subject(抽象主题角色):定义代理类和真实主题的公共对外方法。通常被设计成接口
●RealSubject(真实主题角色):真正实现业务逻辑的类;
●Proxy(代理主题角色):用来代理和封装真实主题;
代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。
如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
●所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
●而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。
Subject接口
//代理模式中的“subject”
//定义公共的行为方法
public interface Subject {
void request();
}
RealSubject实现类
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("RealSubject类中的request()方法");
}
}
Proxy代理类:增强日志功能
import java.time.LocalDateTime;
public class Proxy implements Subject{
// 每个代理对象一定“持有”一个“RealSubject”目标对象
private RealSubject target = new RealSubject();
@Override
public void request() {
before("request()"); // 附加逻辑
target.request();; // 核心调用
after("request"); // 附加逻辑
}
public void before(String methodName) {
System.out.println("方法" + methodName + "开始执行。。。");
}
public void after(String methodName) {
System.out.println("方法" + methodName + "结束执行。。。" + LocalDateTime.now());
}
}
客户端
public class Client {
public static void main(String[] args) {
// 通过代理对象,进行方法的调用
Subject sub = new Proxy();
sub.request();
}
}
// 运行结果
方法request()开始执行。。。
RealSubject类中的request()方法
方法request结束执行。。。2022-07-27T21:14:33.051
通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
●只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
●新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理(第三方开源类库)。
JDK动态代理 JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。我们通过编写一个调用逻辑处理器 LogHandler 类来提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke()方法中编写方法调用的逻辑处理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
// 日志组件
// 作用 : 实现日志逻辑
public class LogHandler implements InvocationHandler{
private Object target;
public LogHandler(Object obj) {
this.target = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.printf("%s方法开始执行。。。\n",method.getName());
// 调用目标方法
Object result = method.invoke(target, args);
System.out.printf("%s方法结束执行[%s]\n",method.getName(),LocalDateTime.now());
return result;
}
}
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 动态产生UserService接口的实现类(Proxy动态代理)
// 类加载器
ClassLoader loader = Subject.class.getClassLoader();
// 接口列表
Class[] interfaces = {Subject.class};
// 日志
// 传入目标对象
LogHandler handler = new LogHandler(new RealSubject());
// 通过Proxy的newProxyInstance()方法
// 动态产生代理类 : com.sun.proxy.$Proxy0
// 创建代理类对象
Subject sub = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
sub.request();
}
}
// 运行结果
request方法开始执行。。。
RealSubject类中的request()方法
request方法结束执行[2022-07-27T21:19:28.678]