代理模式-动态代理模式

目录

JDK动态代理

CGLIB动态代理

Javassist实现动态代理

使用Javassist提供的动态代理接口实现动态代理

 使用Javassist提供的字节码API实现


动态代理是一种在运行时动态创建代理对象的技术,它可以在不修改源代码的情况下为原始对象添加额外的功能或控制其行为。动态代理通常用于实现AOP(面向切面编程)中的切面,以及在不同的框架和库中实现各种功能,例如远程调用、事务管理等。

在Java中,动态代理主要通过两种方式实现:基于接口的动态代理和基于类的动态代理。

  1. 基于接口的动态代理:Java标准库中的 java.lang.reflect.Proxy 类提供了创建基于接口的动态代理的能力。通过 Proxy.newProxyInstance 方法,可以传入一个类加载器、一组接口和一个 InvocationHandler 对象来创建代理对象。InvocationHandler 接口中的 invoke 方法在代理对象的方法被调用时会被触发,可以在这个方法中实现对原始方法的增强或控制。

  2. 基于类的动态代理:基于类的动态代理是通过字节码操作库(如CGLIB)来实现的。它可以在运行时动态生成一个子类来作为原始类的代理,而不需要原始类实现任何接口。基于类的动态代理通常比基于接口的动态代理更灵活,但也更复杂一些。

本文主要介绍基于接口的JDK动态代理、基于类的动态代理,Java字节码实现的javasist

JDK动态代理

Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。

JDK实现动态代理需要两个组件

  • 第一个是InvocationHandler接口。我们在使用JDK的动态代理时,需要编写一个类,去实现这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。
  • 第二个组件是Proxy这个类,我们可以通过这个类的newProxyInstance方法,返回一个代理对象。生成的代理类对象实现了原来那个类的所有接口,并对接口的方法进行了代理,我们通过代理对象调用这些方法时,底层将通过反射,调用我们实现的invoke方法。
  • 原理

JDK的动态代理是基于反射实现。JDK通过反射,生成一个代理类,这个代理类实现了原来那个类的全部接口,并对接口中定义的所有方法进行了代理。当我们通过代理对象执行原来那个类的方法时,代理类底层会通过反射机制,回调我们实现的InvocationHandler接口的invoke方法。并且这个代理类是Proxy类的子类

优点

  • JDK动态代理是JDK原生的,不需要任何依赖即可使用
  • 通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快

缺点

  • 如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理
  • JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入
  • JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低

举例:

我们需要一个接口和一个实现类来演示代理

public interface UserService {
    void save();
}
public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println(".............新增用户信息.........");
    }
}

 JDK动态代理的实例代码

/**
 * JDK 动态代理示例
 */
public class JDKDynamicProxyExample {
 
    /**
     * 主函数入口
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 创建实现UserService接口的具体对象
        UserServiceImpl userService = new UserServiceImpl();
        // 通过Proxy生成一个动态代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                UserServiceImpl.class.getClassLoader(), // 使用目标对象的类加载器
                UserServiceImpl.class.getInterfaces(), // 获取目标对象实现的接口列表
                new MyInvocationHandler(userService) // 设置InvocationHandler处理代理对象的方法调用
        );
        // 调用代理对象的方法,此时会触发MyInvocationHandler的invoke方法
        proxy.save();
    }
 
    /**
     * 自定义的InvocationHandler,实现对方法调用的拦截
     */
    static class MyInvocationHandler implements InvocationHandler {
        private final Object target;
 
        /**
         * 构造函数
         * @param target 被代理的对象
         */
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
 
        /**
         * 处理代理对象的方法调用
         * @param proxy 代理对象
         * @param method 被调用的方法
         * @param args 方法调用的参数
         * @return 方法的返回值
         * @throws Throwable 方法调用中产生的异常
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 方法调用前的逻辑
            System.out.println("Before method call...");
            // 调用目标对象的相应方法
            Object result = method.invoke(target, args);
            // 方法调用后的逻辑
            System.out.println("After method call...");
            return result;
        }
    }
}

CGLIB动态代理

JDK的动态代理存在限制,那就是被代理的类必须是一个实现了接口的类,代理类需要实现相同的接口,代理接口中声明的方法。

若需要代理的类没有实现接口,此时JDK的动态代理将没有办法使用,于是Spring会使用CGLib的动态代理来生成代理对象。

CGLib直接操作字节码,生成类的子类,重写类的方法完成代理。

原理

CGLib实现动态代理的原理是,底层采用了ASM字节码生成框架,直接对需要代理的类的字节码进行操作,生成这个类的一个子类,并重写了类的所有可以重写的方法,在重写的过程中,将我们定义的额外的逻辑(切面)织入到方法中,对方法进行了增强。

通过字节码操作生成的代理类,和我们自己编写并编译后的类没有什么区别。

优点

  • 使用CGLib代理的类,不需要实现接口,因为CGLib生成的代理类是直接继承自需要被代理的类
  • CGLib生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中所有能够被子类重写的方法进行代理
  • CGLib生成的代理类,和我们自己编写并编译的类没有太大区别,对方法的调用和直接调用普通类的方式一致,所以CGLib执行代理方法的效率要高于JDK的动态代理

缺点

  • 由于CGLib的代理类使用的是继承,这也就意味着如果需要被代理的类是一个final类,则无法使用CGLib代理
  • 由于CGLib实现代理方法的方式是重写父类的方法,所以无法对final方法,或者private方法进行代理,因为子类无法重写这些方法
  • CGLib生成代理类的方式是通过操作字节码,这种方式生成代理类的速度要比JDK通过反射生成代理类的速度更慢

举例

使用CGLIB动态代理前先导入依赖

<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.1</version>
</dependency>

代码示例

/**
 * CGLIB代理示例类
 */
public class CGLIBProxyExample {
 
    /**
     * 主函数,演示如何使用CGLIB创建一个动态代理。
     * 
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 创建Enhancer对象,并设置基础信息
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
        // 调用代理对象的方法
        proxy.save();
    }
 
    /**
     * 自定义方法拦截器,实现MethodInterceptor接口。
     * 用于在方法执行前后添加日志或其他逻辑。
     */
    static class MyMethodInterceptor implements MethodInterceptor {
        /**
         * 当方法被调用时,此方法会拦截方法的执行,并允许在执行前后添加自定义逻辑。
         * 
         * @param obj 被代理的对象
         * @param method 被调用的方法
         * @param args 方法调用时传入的参数
         * @param proxy 方法调用的代理对象
         * @return 方法执行的结果
         * @throws Throwable 如果方法执行中出现异常,则抛出
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            // 方法执行前的逻辑
            System.out.println("CGLIB Before method call...");
            // 调用真实对象的方法
            Object result = proxy.invokeSuper(obj, args);
            // 方法执行后的逻辑
            System.out.println("CGLIB After method call...");
            return result;
        }
    }
}

注意:Java 9 引入了模块化系统,其中模块默认是封闭的,即模块之间不能访问对方的内部。在这种情况下,cglib 库无法访问 java.lang.reflect.AccessibleObject 中的 setAccessible 方法,导致无法生成代理类而抛出异常。

要解决这个问题,可以在运行时通过添加 --add-opens 参数来打开 java.base 模块中 java.lang.reflect 包的访问权限。

在VMOption中添加以下内容:

--add-opens java.base/java.lang=ALL-UNNAMED

运行结果

Javassist实现动态代理

Javassist(Java Programming Assistant)是另一种用于动态生成Java字节码的工具库,也可以用于实现动态代理。与基于接口的动态代理和基于类的动态代理不同,Javassist可以直接操作Java字节码,从而可以更灵活地生成代理类。

Javassist相对于基于反射的代理实现,通常更高效,因为它直接操作字节码而不需要反射调用。但是,与基于反射的代理相比,Javassist的语法和使用方式可能更复杂一些。

总的来说,Javassist提供了一种更底层的动态代理实现方式,适合对字节码操作较为熟悉的开发者使用。

使用Javassist提供的动态代理接口实现动态代理

1.导入依赖

<dependency>
     <groupId>org.javassist</groupId>
     <artifactId>javassist</artifactId>
     <version>3.27.0-GA</version>
</dependency>

2.Javassist代理接口

    /**
     * 主函数,演示如何使用Javassist动态代理。
     * @param args 命令行参数
     * @throws Exception 抛出异常的范围
     */
    public static void main(String[] args) throws Exception {
        UserService proxy = createJavassistDynamicProxy();
        proxy.save(); // 调用代理对象的方法
    }

    /**
     * 通过Javassist创建动态代理对象。
     * @return UserService 动态代理实现的UserService接口对象
     * @throws Exception 抛出异常的范围
     */
    private static UserService createJavassistDynamicProxy() throws Exception {
        // 使用ProxyFactory创建代理
        ProxyFactory proxyFactory = new ProxyFactory(); 
        // 设置需要代理的接口
        proxyFactory.setInterfaces(new Class[]{UserService.class});
        // 创建代理类
        Class<?> proxyClass = proxyFactory.createClass(); 
        // 实例化代理类
        UserService javassistProxy = (UserService) proxyClass.getDeclaredConstructor().newInstance(); 
        // 设置代理对象的行为
        ((ProxyObject) javassistProxy).setHandler(new JavassistInterceptor(new UserServiceImpl()));
        return javassistProxy;
    }

    /**
     * Javassist动态代理的拦截器,用于在方法调用前后添加额外逻辑。
     */
    private static class JavassistInterceptor implements MethodHandler {

        // 被代理的对象
        private Object delegate;

        /**
         * 拦截器构造函数。
         * @param delegate 被代理的对象实例
         */
        private JavassistInterceptor(Object delegate) {
            this.delegate = delegate;
        }

        /**
         * 在方法调用时被调用,可用于方法调用前后的拦截。
         * @param self 代理对象本身
         * @param method 被调用的方法
         * @param proceed 如果代理接口,此参数为null,如果代理类,此参数为父类的方法
         * @param args 方法参数
         * @return 方法返回值
         * @throws Throwable 抛出的任何异常
         */
        @Override
        public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable {
            // 方法调用前的逻辑
            System.out.println("javassist proxy before save"); 
            // 调用真实对象的方法
            Object ret = method.invoke(delegate, args); 
            // 方法调用后的逻辑
            System.out.println("javassist proxy after save"); 
            return ret;
        }
    }

3.使用Javassist代理类

    /**
     * 主函数,演示如何使用Javassist动态代理。
     * @param args 命令行参数
     * @throws Exception 抛出异常的条件不明确,因为具体实现方法可能抛出各种异常
     */
    public static void main(String[] args) throws Exception {
        UserService proxy = createJavassistDynamicProxy();
        // 使用动态代理对象调用方法
        proxy.save();
    }

    /**
     * 通过Javassist创建UserService的动态代理实例。
     * @return 返回UserService接口的动态代理实现类实例
     * @throws Exception 抛出异常的条件不明确,因为具体实现过程中可能抛出各种异常
     */
    private static UserService createJavassistDynamicProxy() throws Exception {
        ProxyFactory proxyFactory = new ProxyFactory();
        // 设置代理对象的父类,即要代理哪个类的方法
        proxyFactory.setSuperclass(UserServiceImpl.class);
        // 创建代理类
        Class<?> proxyClass = proxyFactory.createClass();
        // 实例化代理类
        UserServiceImpl javassistProxy = (UserServiceImpl) proxyClass.getDeclaredConstructor().newInstance();
        // 设置代理对象的方法拦截器
        ((ProxyObject) javassistProxy).setHandler(new JavassistInterceptor());
        return javassistProxy;
    }

    /**
     * Javassist动态代理的方法拦截器实现,用于在调用方法前后添加额外逻辑。
     */
    private static class JavassistInterceptor implements MethodHandler {

        /**
         * 当代理对象的方法被调用时,此方法会被执行。
         * @param self 创建的代理对象
         * @param method 被代理方法
         * @param proceed 如果代理接口,此参数为null,如果代理类,此参数为父类的方法
         * @param args 方法参数
         * @return 返回方法执行结果
         * @throws Throwable 抛出的异常类型不确定,取决于被代理方法的实现
         */
        @Override
        public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable {
            // 方法调用前的逻辑
            System.out.println("javassist proxy before save");
            // 调用父类的save方法
            Object ret = proceed.invoke(self, args);
            // 方法调用后的逻辑
            System.out.println("javassist proxy after save");
            return ret;
        }
    }

 使用Javassist提供的字节码API实现

/**
 * 主程序入口,演示了如何使用Javassist字节码动态代理创建一个UserService的代理实例,并调用其方法。
 * @param args 命令行参数
 * @throws Exception 可能抛出的异常
 */
public static void main(String[] args) throws Exception {
    UserService proxy = createJavassistBytecodeDynamicProxy(new UserServiceImpl());
    proxy.save(); // 调用动态代理的save方法
}

/**
 * 使用Javassist字节码技术创建一个动态代理。
 * 这个方法动态地创建一个实现了UserService接口的类,该类在调用save方法时会前后打印特定日志,并委托给传入的delegate对象执行实际的save操作。
 * @param delegate 代理对象实际执行操作的实例,即被代理对象。
 * @return 返回一个UserService类型的动态代理实例。
 * @throws Exception 可能抛出的异常,包括类定义错误、反射异常等。
 */
private static UserService createJavassistBytecodeDynamicProxy(UserService delegate) throws Exception {
    // 创建类池,并基于UserService定义动态代理类
    ClassPool mPool = new ClassPool(true);
    CtClass mCtc = mPool.makeClass(UserService.class.getName() + "JavaassistProxy");
    mCtc.addInterface(mPool.get(UserService.class.getName())); // 添加UserService接口
    mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc)); // 添加默认构造函数
    mCtc.addField(CtField.make("public " + UserService.class.getName() + " delegate;", mCtc)); // 添加delegate字段

    // 定义并添加save方法的实现,实现前后打印日志并调用delegate的save方法
    String src = "public void save() { "
            + "System.out.println(\"javassist bytecode proxy before save\");"
            + "delegate.save();"
            + "System.out.println(\"javassist bytecode proxy after save\"); "
            + "}";
    mCtc.addMethod(CtNewMethod.make(src, mCtc)); // 添加方法

    // 将动态定义的类转换为Class对象,并实例化,最后设置delegate字段的值
    Class<?> pc = mCtc.toClass();
    UserService bytecodeProxy = (UserService) pc.getDeclaredConstructor().newInstance();
    Field filed = bytecodeProxy.getClass().getField("delegate");
    filed.set(bytecodeProxy, delegate);

    return bytecodeProxy; // 返回动态代理实例
}

更多关于Javassist的用法

  • 43
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值