Java 动态代理
Java 中有 JDK 动态代理 和 CGLIB 动态代理这两种常见的动态代理方式
JDK 动态代理
JDK 动态代理是基于 Java 的反射机制实现的。它通过 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来创建代理对象。Proxy 类的 newProxyInstance
方法可以在运行时动态生成一个实现了指定接口的代理类,而 InvocationHandler
接口中的 invoke
方法则用于处理代理对象方法的调用。
代理接口:
java
public interface TeacherService {
String teach(Teacher teacher);
}
教师实体类: 就是目标对象
其中有一个 teach () 教学的方法
java
@Data
public class Teacher implements TeacherService{
public String name;
public String sex;
public String age;
public String toString() {
return "Teacher{name = " + name + ", sex = " + sex + ", age = " + age + "}";
}
public String teach(Teacher teacher) {
System.out.println(teacher.getAge()+"岁"+teacher.getSex()+"性别的"+teacher.getName() + "老师正在授课");
return teacher.getName()+"老师讲的很好";
}
}
代理工厂类:
createProxy(Teacher teacher ) 为 teacher 对象创建一个代理对象
如果是 teach() 方法 就进行一个代理操作,就相当于帮忙做一些操作
然后将代理的对象和参数再返回出去
java
public class ProxyUtil {
public static TeacherService createProxy(Teacher teacher){ //参数代表为谁创建代理
TeacherService teacherProxy = (TeacherService)Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(), //代理对象加载的类加载器
new Class[]{TeacherService.class}, // 代理对象要实现的接口
new InvocationHandler() { // 代理对象要干什么事情
@Override
public Object invoke(Object proxy, // 第一个参数是代理对象
Method method, // 第二个参数是方法对象
Object[] args) // 第三个参数是方法的参数
throws Throwable {
if (method.getName().equals("teach")){
System.out.println("给老师讲课准备讲台");
}
return method.invoke(teacher,args);
}
}
);
return teacherProxy; // 返回代理对象
}
}
测试:
先创建一个 teacher 对象
然后调用 ProxyUtil 代理工厂的 createProxy() 方法创建 代理对象
代理对象调用 方法 (这样就可以在对象执行 teach() 方法之前,帮助做一些事情 )
java
public class TestTeacher {
public static void main(String[] args) {
Teacher teacher = new Teacher("小明", "男", "18");
TeacherService proxy = ProxyUtil.createProxy(teacher); // 创建代理对象
String teach = proxy.teach(teacher); // teach就是输出的代理对象的方法
System.out.println(teach); //teach就是输出的代理对象的方法 输出的是代理对象的方法
}
}
执行结果:
给老师讲课准备讲台
18岁男性别的小明老师正在授课
小明老师讲的很好
CGLIB 动态代理
CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库。CGLIB 动态代理通过继承目标类来创建代理对象,它使用字节码技术在运行时生成目标类的子类,并重写父类的方法来实现代理逻辑。
- 查找目标类上的所有非final 的public类型的方法定义;
- 将这些方法的定义转换成字节码;
- 将组成的字节码转换成相应的代理的class对象;
- 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求
示例:
步骤:
首先创建一个 IT 类, 也就是代理的目标对象
java
@Data
@AllArgsConstructor
public class IT {
private String name;
private String age;
public void fix(String bug){
System.out.println("IT正在修复" + bug + "的bug");
}
public void test(String program){
System.out.println("IT正在测试" + program + "的功能");
}
}
创建代理对象:
java
public class CGLIBInterceptor implements MethodInterceptor {
/**
* @param o 表示要进行增强的对象
* @param method 表示拦截的方法
* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return 执行结果
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,
// methodProxy.invokeSuper执行的是原始类的方法,method.invoke执行的是子类的方法
Object result = methodProxy.invokeSuper(o, objects); // 执行原始类的方法 返回的是被代理对象的方法执行结果
return result;
}
}
CallbackFilter
的作用就是为每个被代理的方法选择合适的回调。CallbackFilter
接口只有一个方法 accept(Method method)
,该方法接收一个 Method
对象作为参数,返回一个整数值,这个整数值表示该方法应该使用回调数组中哪个索引位置的回调。
可以拦截到 目标对象也就是 IT 的每一个 非 final、非 private、非 static 方法
可以通过 CallbackFilter 来灵活控制具体哪些方法被代理和增强。
java
public class ITFilter implements CallbackFilter {
/**
* @param method 是要代理的方法 例如 IT.fix(String bug)
* @return
*/
@Override
public int accept(Method method) {
String name = method.getName();
System.out.println("name = " + name);//getAge、getName、test、fix、setName、setAge、toString、hashCode、equals
if ("fix".equals(method.getName())) {
System.out.println("fix");
return 0;
}
return 0;
}
}
测试:
java
public class TestCGLIB {
public static void main(String[] args) {
CGLIBInterceptor interceptor = new CGLIBInterceptor();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(IT.class);
enhancer.setCallback(interceptor);
enhancer.setCallbackFilter(new ITFilter());
//带参数的构造 构造带有参数的目标对象的代理对象
Object[] objects = {"小新", "23"};
Class<?>[] classes = {String.class , String.class};
IT ITProxy = (IT) enhancer.create(classes , objects); //创建了 IT 的代理对象
ITProxy.fix("权限验证");
ITProxy.test("权限验证");
}
}
结果:
ini
name = equals
name = toString
name = hashCode
name = getName
name = setName
name = test
name = fix
fix
name = canEqual
name = setAge
name = getAge
name = finalize
name = clone
IT正在修复权限验证的bug
IT正在测试权限验证的功能
JDK动态代理与CGLIB动态代理对比
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 cglib 框架的优势:
- 无需实现接口,达到代理类无侵入
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- 高性能
JDK 动态代理为什么只能代理有接口的类
首先动态代理是通过 Proxy.newProxyInstance()
这个方法来实现的,需要传入被动态代理的一个接口类,之所以要传入接口而不是传入类,取决于 JDK 动态代理的一个底层实现,JDK动态代理会在程序运行期间,去动态生成一个代理类,这个代理类,会去继承 java.lang.reflect.Proxy
这样一个类。同时还会去实现被代理类的接口,在Java 里面是不支持多继承的,而每一个动态代理类都继承了一个 Proxy 所以就导致 JDK 里面的动态代理只能代理接口,而不能代理实现类。
因为 JDK 动态代理是基于接口实现的。在 JDK 动态代理中,代理对象必须实现与被代理对象相同的接口,通过实现 InvocationHandler 接口的 invoke 方法来对被代理对象的方法进行增强。如果一个类没有实现任何接口,那么就无法使用 JDK 动态代理来对其进行代理。