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

2019-07-25  本文已影响0人  向天葵

参考文章

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

代理模式分类

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

静态代理

静态代理是通过一个代理类,在不破坏原来代码的结构基础上,增强功能。
例如我们有一个对学生对象进行操作的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 字节码)的途径有以下几种:

动态代理

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

  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. 总结一遍后,心里比以前清晰些了,其中动手编码非常有效。
上一篇下一篇

猜你喜欢

热点阅读