Java 动态代理

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 动态代理通过继承目标类来创建代理对象,它使用字节码技术在运行时生成目标类的子类,并重写父类的方法来实现代理逻辑。

  1. 查找目标类上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 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 动态代理来对其进行代理。

相关推荐
xixixin_12 分钟前
【uniapp】uni.setClipboardData 方法失效 bug 解决方案
java·前端·uni-app
狂炫一碗大米饭12 分钟前
大厂一面,刨析题型,把握趋势🔭💯
前端·javascript·面试
工业互联网专业16 分钟前
基于springboot+vue的校园二手物品交易平台
java·vue.js·spring boot·毕业设计·源码·课程设计·校园二手物品交易平台
isfox23 分钟前
一文拆解 Java CAS:从原理到避坑全攻略
java
JPC客栈30 分钟前
LeetCode面试经典 150 题(Java题解)
java·leetcode·面试
HyperAI超神经40 分钟前
【vLLM 学习】Aqlm 示例
java·开发语言·数据库·人工智能·学习·教程·vllm
异常驯兽师40 分钟前
IntelliJ IDEA 项目导入后 Java 文件图标显示为红色小写 j 的解决方法
java·路径配置
纪元A梦43 分钟前
华为OD机试真题——数据分类(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·javascript·c++·python·华为od·go·华为od机试题
常年游走在bug的边缘1 小时前
基于spring boot 集成 deepseek 流式输出 的vue3使用指南
java·spring boot·后端·ai
Moment1 小时前
前端远程面试全记录:项目、思维、管理一个不落 😔😔😔
前端·javascript·面试