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 动态代理来对其进行代理。

相关推荐
liudongyang123几秒前
jenkins 启动报错
java·运维·jenkins
曹牧7 分钟前
JSON 实体属性映射的最佳实践
java
qq_5432485219 分钟前
Tomcat服务部署
java·tomcat
Auc2436 分钟前
OJ判题系统第4期之判题机模块架构——设计思路、实现步骤、代码实现(工厂模式、代理模式的实践)
java·spring cloud·log4j·mybatis·代理模式·工厂模式
熊大如如38 分钟前
Java NIO 文件处理接口
java·linux·nio
Chandler241 小时前
Go语言即时通讯系统 开发日志day1
开发语言·后端·golang
有梦想的攻城狮1 小时前
spring中的@Lazy注解详解
java·后端·spring
前端小巷子1 小时前
CSS3 遮罩
前端·css·面试·css3
野犬寒鸦2 小时前
Linux常用命令详解(下):打包压缩、文本编辑与查找命令
linux·运维·服务器·数据库·后端·github
huohuopro2 小时前
thinkphp模板文件缺失没有报错/thinkphp无法正常访问控制器
后端·thinkphp