美文网首页
Spring AOP 学习笔记(1) ---- 代理模式

Spring AOP 学习笔记(1) ---- 代理模式

作者: 向天葵 | 来源:发表于2019-07-25 16:49 被阅读0次

    参考文章

    1. spring aop 官方文档
    2. 掘金spring aop 教程
    3. 掘金动态代理

    代理模式分类

    根据代理类的创建时机来分类

    • 静态代理:所谓静态代理就是代理类在运行之前就已经存在字节码文件,也就是代理类和被代理类在程序运行之前就已经确定了关系。
    • 动态代理:相对的,动态代理就是代理类在程序运行时通过JVM反射机制生成,运行前不存在字节码文件。

    静态代理

    静态代理是通过一个代理类,在不破坏原来代码的结构基础上,增强功能。
    例如我们有一个对学生对象进行操作的Service类
    对学生操作的接口和该接口的具体实现:

    package service;
    
    public interface StudentService {
        void select();
        void update();
    
    }
    
    /**********************************************************/
    package service;
    
    public class StudentServiceImpl implements StudentService{
    
        @Override
        public void select() {
            System.out.println("select student ");
        }
    
        @Override
        public void update() {
            System.out.println("update student ");      
        }
    
    }
    

    这个时候,我们有一个在相关对学生的业务操作前后输出日志的需求,我们通过新建一个代理类来实现:

    package proxy;
    
    import java.util.Date;
    import service.StudentService;
    
    public clas StudentServiceProxy implements StudentService{
        private StudentService target;
        public StudentServiceProxy(StudentService target) {
            super();
            this.target = target;
        }
        @Override
        public void select() {
            before();
            target.select();
            after();
        }
    
        @Override
        public void update() {
            before();
            target.update();
            after();
        }
        private void before() {
            System.out.println("In before : " + new Date());
        }
        private void after() {
            System.out.println("In after : " + new Date());
        }
    
    }
    
    

    这个时候通过测试类进行结果输出

    public class Test {
        public static void main(String[] args) {
            StudentServiceImpl studentServiceImpl = new StudentServiceImpl();
            StudentServiceProxy proxy = new StudentServiceProxy(studentServiceImpl);
            proxy.select();
            proxy.update();
        }
    }
    
    

    输出结果:

    In before : Wed Jul 24 22:25:20 CST 2019
    select student 
    In after : Wed Jul 24 22:25:20 CST 2019
    In before : Wed Jul 24 22:25:20 CST 2019
    update student 
    In after : Wed Jul 24 22:25:20 CST 2019
    
    

    上面通过静态代理,增强了原有类的函数功能,并且不破坏函数结构。
    但是静态代理有以下几个明显的缺点

    1. 如果有多个类需要被代理的时候,代理类只能通过两种方法实现代理:
      • 继承多个接口,并实现对应的方法来完成代理功能。这种方式如果接口较多时,会导致该代理类变得臃肿
      • 新建多个代理类,分别代理不同接口。这种方式缺点明显,在代理类不断增加时,会导致项目代理类过多
    2. 被代理类中的方法有修改、增加、删除时,其对应的代理类也要做相应的变化,导致维护难度增加。

    对于代理类过多或臃肿问题,可以通过在运行时动态生成代理类来解决。

    为什么可以动态生成?
    这个涉及了JVM的类加载机制。JVM类加载机制分为加载、验证、准备、 解析、初始化五个阶段。类的动态生成就在加载阶段,这一阶段又包含以下三个步骤:

    1. 通过类的全限定名得到类的二进制字节流。
    2. 通过类的二进制字节流,将类转换为方法去的运行时数据结构。
    3. 在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的数据访问入口。
      第一点中,JVM对二进制字节流的获取方式具体实现没有规定,因此可以 根据具体情况而灵活实现,获取二进制字节流(class 字节码)的途径有以下几种:
    • 从zip包获取,如jar、war、EAR等格式
    • 从网络中获取,典型的应用时Applet
    • 运行时生成,这种方式最多的使用场景就是动态代理。
    • 数据库获取
    • 有其他文件生成,如JSP

    动态代理

    动态代理可以分为以下两种方式

    1. JDK动态代理
    2. CGLIB

    JDK动态代理

    JDK动态代理中涉及两个关键的类

    1. java.lang.reflect.InvocationHandler。
      具体的实现中,需要继承该类,并在该类的方法invoke()中加入额外的代理逻辑。就是说,新增的功能等逻辑会加入在该类继承类中。
    2. java.lang.reflect.Proxy
      主要时通过Proxy.newInstance()方法动态生成代理类。
      案例:
    package proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Date;
    public class LogHandler implements InvocationHandler{
    
        Object target;
        
        public LogHandler(Object target) {
            this.target = target;
        }
    
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            Object result = method.invoke(target, args);
            after();
            return result;
        }
        
        private void before() {
            System.out.println("before doing st, date:" + new Date());
        }
        
        private void after() {
            System.out.println("after doing st, date:" + new Date());
        }
    
    }
    
    

    测试类

    package test;
    
    import java.lang.reflect.Proxy;
    
    import proxy.LogHandler;
    import service.StudentService;
    import service.StudentServiceImpl;
    
    public class DynamiTest {
        public static void main(String[] args) {
            StudentServiceImpl impl = new StudentServiceImpl();
            LogHandler logHandler = new LogHandler(impl);
            
            StudentService service = (StudentService) Proxy.newProxyInstance(impl.getClass().getClassLoader(), 
                    impl.getClass().getInterfaces(), logHandler);
            
            service.select();
            service.update();
            
        }
    }
    /* 控制台输出
    before doing st, date:Thu Jul 25 09:59:06 CST 2019
    select student 
    after doing st, date:Thu Jul 25 09:59:06 CST 2019
    before doing st, date:Thu Jul 25 09:59:06 CST 2019
    update student 
    after doing st, date:Thu Jul 25 09:59:06 CST 2019
    */
    

    测试类通过Prxoxy.newProxyInstance方法,动态生成了StudentService关于impl的代理类。在这个过程中,newProxyInstance内部会生成对应代理类的字节码并将字节码实例化为在内存中的class对象,并将proxy与handler绑定。

    newProxyInstance方法源码解析:

       @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            ......  省略部分代码
             final Class<?>[] intfs = interfaces.clone();
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);           ......1
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
                ......省略
                final Constructor<?> cons = cl.getConstructor(constructorParams);.......2
           
                return cons.newInstance(new Object[]{h});            ......3
                ......省略
        }
    
    1. 调用getProxyClass0(loader,intfs)生成代理类class对象
    2. 获得代理类构造器
    3. 实例化代理类
      重点介绍下class对象生成的源码流程。首先进入getProxyClass0方法,源码如下,该方法会调用proxyClassCache,通过该方法先访问缓存中的代理类class对象。
     private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
     
            // If the proxy class defined by the given loader implementing
            // the given interfaces exists, this will simply return the cached copy;
            // otherwise, it will create the proxy class via the ProxyClassFactory
            return proxyClassCache.get(loader, interfaces);
        }
    
    

    如果缓存中不存在这个代理类,那么会通过ProxyClassFactory中的apply方法通过ProxyGenerator生成对应字节码二进制流,最后返回可用的class对象。

       @Override
            public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                /*
                 * Generate the specified proxy class.
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
               //省略异常处理及一些判断的代码
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
          
            }
        
    
    

    流程图:


    代理类class动态生成流程

    动态生成的代理类可以通过一个工具将代理类导出,并查看,具体可参考这篇文章

    CGLIB动态代理

    这种代理的实现方式使用方法类似,都是通过一个中间代理,写好你的逻辑。
    CGLIB是通过继承MethodInterceptor,然后在intercept()方法中写好处理逻辑,如下:

    public class LogInterceptor implements MethodInterceptor {
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            before();
            methodProxy.invokeSuper(o,objects);
            after();
            return null;
        }
    
        public void before(){
            System.out.println("before....");
        }
    
        public void after(){
            System.out.println("after....");
        }
    
    }
    

    CGLIB是利用继承,继承被代理类,生成一个新的类来完成代理类的创建的。并对被代理类方法执行前后执行一些操作,这些操作的通常就是一些回调操作,可以是MethodInterceptor,LazyLoader,CallbackFilter,其中MethodInterceptor是最常用的。

    public class CGLibTest {
        public static void main(String[] args) {
    
            Enhancer enhancer = new Enhancer();
    
            enhancer.setSuperclass(StudentServiceImpl.class);
            enhancer.setCallback(new LogInterceptor());
    
            StudentServiceImpl student = (StudentServiceImpl) enhancer.create();
            student.select();
        }
    }
    

    输出

    before....
    select student 
    after....
    
    
    JDK动态代理和CGLIB动态代理优缺点
    jdk
    1. 基于java反射机制实现,必须要实现了接口的业务类才可以使用该种方法进行动态代理
    2. JDK版本升级平滑,而CGLIB库的方式需要等JDK更新后推出自己的更新以保证在最新版JAVA上的使用
    3. 代码实现简单
    CGLIB
    1. 无需接口,可以直接通过继承被代理类达到非侵入式增强代码功能的母的
    2. 高性能。其中的反射甚至比java.reflect的反射性能还要好。

    总结

    1. 以上是代理模式的简单笔记
    2. CGLIB的笔记中较为简单,CGLIB其实是基于ASM字节码操作框架来进行字节码的转化的,需要了解的还是要到网上去查找资料
    3. 总结一遍后,心里比以前清晰些了,其中动手编码非常有效。

    相关文章

      网友评论

          本文标题:Spring AOP 学习笔记(1) ---- 代理模式

          本文链接:https://www.haomeiwen.com/subject/dfgxrctx.html