• JDK动态代理和CGLIB字节码增强


    一、JDK动态代理

    Java 在 java.lang.reflect 包中有自己的代理支持,该类(Proxy.java)用于动态生成代理类,只需传入目标接口、目标接口的类加载器以及 InvocationHandler 便可为目标接口生成代理类及代理对象。我们称这个Java技术为:动态代理

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    //...
    }
    

    在 Java 中规定,要想产生一个对象的代理对象,那么这个对象必须要有一个接口,因此 interfaces 必须是一个接口。

    在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的 InvocationHandler 的 invoke 方法(这相当于 invoke 方法拦截到了代理对象的方法调用)。

    因此 JDK 动态代理的整体流程为:

    1. 实现 InvocationHandler,用来处理对象拦截后的逻辑。(该对象必须是接口,或者父类是接口)
    2. 使用 Proxy.newProxyInstance 产生代理对象。

    以下是一个用 JDK 动态代码实现 AOP 的具体例子:

    1.目标(Target)类

    public interface UserService {
        void eat();
    }
    
    public class UserServiceImpl implements UserService {
        @Override
        public void eat() {
            System.out.println("吃东西");
        }
    }
    

    2.切面(Aspect)类

    public class MyAspect {
        /**
         * 前置通知
         */
        public void before() {
            System.out.print("先洗手再");
        }
    }
    

    3. 织入(Weaving)过程

    /**
     * 产生代理对象的工厂类
     */
    public class MyFactoryBean {
    
        private MyFactoryBean() {
        }
        
        public static UserService getInstance() {
            // target : 目标类
            final UserService userService = new UserServiceImpl();
            // Aspect : 切面类
            final MyAspect myAspect = new MyAspect();
            // Weaving : 织入,也就是产生代理的过程
            UserService proxyInstance = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),
                    new Class[]{UserService.class}, (Object proxy, Method method, Object[] args) -> {
                        // 模拟切点 - pointcut
                        if ("eat".equals(method.getName())) {
                            myAspect.before();
                        }
                        return method.invoke(userService, args);
                    });
            return proxyInstance;
        }
    }
    
        public static void main(String[] args) {
            UserService userService = MyFactoryBean.getInstance();
            // 先洗手再吃东西
            userService.eat();
        }
    

    想想看,这其实跟我们平常使用的 AOP 已经很相似了,Spring 里面定义了前置通知(@Before)、异常通知(@AfterThrowing)等等,Spring 只是换成了甄别这些注解来选择什么时候调用通知方法,另外,Spring 还通过切点表达式来选择目标类和切入点。

    二、CGLIB动态代理

    CGLIB 动态代理需要引入第三方的库,它通过修改代理对象生成子类的方式来实现调用拦截,代理对象不需要实现接口,但是代理类不能是 final,代理的方法也不能是 final。

    /**
     * 产生代理对象的工厂类
     */
    public class MyFactoryBean {
    
        private MyFactoryBean() {
        }
    
        public static UserService getInstance() {
            // target : 目标类
            final UserService userService = new UserServiceImpl();
            // Aspect : 切面类
            final MyAspect myAspect = new MyAspect();
            // Weaving : 织入,也就是产生代理的过程
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(userService.getClass());
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    // 模拟 pointcut-切点
                    if ("eat".equals(method.getName())) {
                        myAspect.before();
                    }
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            return (UserService) enhancer.create();
        }
    
        public static void main(String[] args) {
            UserService proxyInstance = MyFactoryBean.getInstance();
            // 先洗手再吃东西
            proxyInstance.eat();
        }
    }
    

    三、总结

    在 JDK 中实现动态代理时,要求代理类必须是接口或继承接口的类,因为 JDK 最后生成的 proxy class 其实就是实现了代理类所代理的接口并且继承了 java 中的 Proxy 类(继承 Proxy 类是为了判断该类是否为代理类),通过反射找到接口的方法,调用 InvocationHandler的invoke 方法实现拦截。

    CGLIB 字节码增强是JDK动态代理的一个很好的补充, CGLIB 中最后生成的 proxy class 是一个继承代理类所代理的 class,通过重写被代理类中的非 final 的方法实现代理。

    总结为:

    • JDK 动态代理:代理类必须是接口或继承接口的类。
    • CGLIB 字节码增强: 代理类不能是 final,代理的方法也不能是 final(继承限制) 。

    关于在 Spring 的 AOP 中采用何种代理手段,我们不强加限制的话,会根据类是否有接口来区别对待:

    1. 当一个类有接口的时候,就会选用 JDK 的动态代理。
    2. 当一个类没有实现接口的时候,就会选用 CGLIB 代理的方式。
  • 相关阅读:
    maven部署tomcat项目,403错误解决
    Android 常见问题收集 (持续更新)
    android 图片进度条
    jquery 监听radio选中,取值
    [转] 解决HttpServletResponse输出的中文乱码问题
    国内外最全的asp.net开源项目 (转)
    c# 模拟 网页实现12306登陆、自动刷票、自动抢票完全篇(转)
    C#网页自动登录和提交POST信息的多种方法(转)
    C# 网页自动填表自动登录(转)
    23个.NET开源项目
  • 原文地址:https://www.cnblogs.com/jmcui/p/11968698.html
Copyright © 2020-2023  润新知