手机用户请
横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。
平台  | 地址  | 
|---|---|
CSDN  | https://blog.csdn.net/sinat_28690417  | 
简书  | https://www.jianshu.com/u/3032cc862300  | 
个人博客  | https://yiyuery.github.io/NoteBooks/  | 
Spring AOP 实现原理剖析
此处不对这几个术语做冗长的介绍,为了便于记忆,上面的元素在AOP中充当何种角色?我们随着实战的深入慢慢来讲。
AOP的工作重心在于如何将增强应用于目标对象的连接点上,这里主要包括2个工作:
AOP工具的设计目标是把业务的模块化分割通过横切来实现。使用类似OOP的方式进行切面的编程工作。位于
是连接点模型,它提供一种机制,可以定位到需要在何处发生横切。
Spring AOP
使用2种代理机制的主要原因是因为JDK本身只提供了接口的代理,而不支持类的代理。
先写个简单的横切逻辑
/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 8:26 下午
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.spring.aop.simple;
import lombok.extern.slf4j.Slf4j;
import sun.nio.cs.UTF_32LE;
/**
 * <p>
 * 业务方式执行监视器
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 8:26 下午
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
public class BusinessLogMonitor {
    /**
     * 线程同步(缓存监视器)
     */
    private static ThreadLocal<BusinessLogHandler> handlerThreadLocal = new ThreadLocal<>();
    /**
     * 初始化业务日志处理器
     * @param method
     */
    public static void begin(String method){
        log.info("begin monitor...");
        BusinessLogHandler handler = new  BusinessLogHandler(method);
        handlerThreadLocal.set(handler);
    }
    /**
     * 结束并打印业务日志
     */
    public static void end(){
        log.info("end monitor....");
        BusinessLogHandler handler = handlerThreadLocal.get();
        handler.businessLog();
        handlerThreadLocal.remove();
    }
}
/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 8:28 下午
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.spring.aop.simple;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
 * <p>
 *  记录业务方法执行信息
 *  如执行耗时等
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 8:28 下午
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Setter
@Getter
@Slf4j
public class BusinessLogHandler {
    private long begin;
    private long end;
    private String serviceMethod;
    /**
     * 构造函数
     * 记录开始时间和业务方法名称
     * @param serviceMethod
     */
    public BusinessLogHandler(String serviceMethod) {
        this.serviceMethod = serviceMethod;
        setBegin(System.currentTimeMillis());
    }
    /**
     *
     * 记录结束时间
     * 打印业务方法执行耗时日志
     */
    public void businessLog() {
        setEnd(System.currentTimeMillis());
        log.info(getServiceMethod() + "执行耗时" + getElapse() + "毫秒");
    }
    /**
     * 计算耗时
     * @return 方法运行耗时
     */
    private long getElapse() {
        return getEnd() - getBegin();◊
    }
}
定义简单的个人员管理服务类,并把我们写的这个简单的业务日志监听逻辑写入
/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 8:47 下午
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.spring.aop.service.impl;
import com.example.spring.aop.service.IPersonManagerService;
import com.example.spring.aop.simple.BusinessLogMonitor;
import lombok.extern.slf4j.Slf4j;
/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 8:47 下午
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
public class PersonManagerServiceImpl implements IPersonManagerService {
    @Override
    public void addPerson() {
        BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
        log.info("模拟人员数据添加");
        try {
            Thread.sleep(3000);
        } catch (Exception e) {
            log.error("PersonManagerServiceImpl addPerson failed!");
        }
        BusinessLogMonitor.end();
    }
}
输出测试
20:54:01.621 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
20:54:01.626 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模拟人员数据添加
20:54:04.631 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
20:54:04.632 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson执行耗时3006毫秒
问题很明显,如果所有的业务方法都加上这两行,无疑是件很恐怖的事情。那么AOP就是为了解决这个问题的。
 BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
//....
BusinessLogMonitor.end();
在方法的执行前后,填充增强逻辑。这也就是我们常看到的"横切"、"织入"操作。
先来补充下基本知识
JDK动态代理主要涉及java.lang.reflect包中的两个类:
Proxy 利用接口InvocationHandler动态创建一个符合某一接口定义的实例,生成目标类的代理对象。InvocationHandler 接口,可以通过该接口实现横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。接下来,我们尝试利用JDK的动态代理来替代下面的逻辑:
BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
//....
BusinessLogMonitor.end();
首先,定义接口InvocationHandler的实现类,用来充当代理类的实现:
/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 10:07 下午
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.spring.aop.jdk;
import com.example.spring.aop.simple.BusinessLogMonitor;
import lombok.Setter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 10:07 下午
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Setter
public class BusinessLogInvocationHandler implements InvocationHandler {
    private Object target;
    public BusinessLogInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        BusinessLogMonitor.begin(target.getClass().getName()+"."+method.getName());
        Object obj = method.invoke(target,args);
        BusinessLogMonitor.end();
        return obj;
    }
}
补充定义个人员删除的方法来测试JDK动态代理的效果
@Override
public void deletePerson() {
    log.info("模拟人员数据删除");
    try {
        Thread.sleep(3000);
    } catch (Exception e) {
        log.error("PersonManagerServiceImpl deletePerson failed!");
    }
}
测试输出
/**
 * 通过JDK代理执行业务方法
 */
@Test
public void deletePersonWithInvocationHandler() {
    PersonManagerServiceImpl personManagerService = new PersonManagerServiceImpl();
    BusinessLogInvocationHandler businessLogInvocationHandler = new BusinessLogInvocationHandler(personManagerService);
    //创建代理实例
    IPersonManagerService proxy = (IPersonManagerService)Proxy.newProxyInstance(personManagerService.getClass().getClassLoader(),
            personManagerService.getClass().getInterfaces(), businessLogInvocationHandler);
    //调用代理实例
    proxy.deletePerson();
}
//22:17:48.012 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//22:17:48.015 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模拟人员数据删除
//22:17:51.020 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
//22:17:51.021 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - com.example.spring.aop.service.impl.PersonManagerServiceImpl.deletePerson执行耗时3005毫秒
可以比对下两次,的确实现了一样的功能。
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
newProxyInstance方法有三个入参,第一个是类加载器,第二个是需要代理实例实现的接口列表。
即要使用JDK的动态代理,需要定义需要实现的接口,代理类实际是将该接口实现了一遍,并在增强逻辑插入后,通过invoke方法调用被代理类的方法中的业务逻辑。
没有实现接口的对象调用自身getClass().getInterfaces()返回的接口信息是空的,无法使用JDK动态代理。
那么,如何在不定义多余接口的情况下,直接是实现代理实例的创建呢?CGLib给出了份完美的答案。
先引入下相关依赖
dependencies {
    //...
    compile ('cglib:cglib:3.3.0')
    //...
}
首先实现接口MethodInterceptor,传入被代理对象的实例类型,并对暴露获取代理类实例的方法。
/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 10:43 下午
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.spring.aop.cglib;
import com.example.spring.aop.simple.BusinessLogMonitor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 10:43 下午
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    /**
     * 创建代理类
     * @param clazz
     * @return
     */
    public Object createProxy(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        BusinessLogMonitor.begin(obj.getClass().getName()+"."+method.getName());
        Object result = proxy.invokeSuper(obj, args);
        BusinessLogMonitor.end();
        return result;
    }
}
测试输出
/**
 * 通过CGLib代理执行业务方法
 */
@Test
public void deletePersonWithCglibProxy() {
    CglibProxy proxy = new CglibProxy();
    //创建代理类
    PersonManagerServiceImpl personManagerServiceProxy = (PersonManagerServiceImpl)proxy.createProxy(PersonManagerServiceImpl.class);
    //调用代理实例
    personManagerServiceProxy.deletePerson();
}
//22:57:53.103 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//22:57:53.117 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模拟人员数据删除
//22:57:56.119 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
//22:57:56.120 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - com.example.spring.aop.service.impl.PersonManagerServiceImpl$$EnhancerByCGLIB$$77eaf4f6.deletePerson执行耗时3014毫秒
两点需要注意:
PersonManagerServiceImpl$$EnhancerByCGLIB$$77eaf4f6,这个和之前JDK动态代理实例输出的结果PersonManagerServiceImpl有所差异。这个带有CGLIB关键信息的实例其实是Cglib对象为PersonManagerServiceImpl动态创建的一个织入业务日志输出逻辑的代理对象,并调用该代理类的业务方法。该对象EnhancerByCGLIB$$77eaf4f6是PersonManagerServiceImpl的一个之类。
因此,由于CGLib采用动态创建之类的方式生成代理对象,所以不能对目标类的final或是private方法进行代理。 而且子类实现增强逻辑,并在增强逻辑调用的中间,调用被代理类(父类)的方法。
核心源码
//step-1 CglibProxy
private Enhancer enhancer = new Enhancer();
/**
    * 创建代理类
    * @param clazz
    * @return
    */
public Object createProxy(Class clazz){
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(this);
    return enhancer.create();
}
//step-2 Enhancer
private Object createHelper() {
    preValidate();
    Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
            ReflectUtils.getNames(interfaces),
            filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
            callbackTypes,
            useFactory,
            interceptDuringConstruction,
            serialVersionUID);
    this.currentKey = key;
    Object result = super.create(key);
    return result;
}
在这里插入图片描述
//step-3  AbstractClassGenerator 构造子类
protected Object create(Object key) {
    try {
        ClassLoader loader = getClassLoader();
        Map<ClassLoader, ClassLoaderData> cache = CACHE;
        ClassLoaderData data = cache.get(loader);
        if (data == null) {
            synchronized (AbstractClassGenerator.class) {
                cache = CACHE;
                data = cache.get(loader);
                if (data == null) {
                    Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                    data = new ClassLoaderData(loader);
                    newCache.put(loader, data);
                    CACHE = newCache;
                }
            }
        }
        this.key = key;
        Object obj = data.get(this, getUseCache());
        if (obj instanceof Class) {
            return firstInstance((Class) obj);
        }
        return nextInstance(obj);
    } catch (RuntimeException e) {
        throw e;
    } catch (Error e) {
        throw e;
    } catch (Exception e) {
        throw new CodeGenerationException(e);
    }
}
//step-4 Enhancer
protected Object nextInstance(Object instance) {
    EnhancerFactoryData data = (EnhancerFactoryData) instance;
    if (classOnly) {
        return data.generatedClass;
    }
    Class[] argumentTypes = this.argumentTypes;
    Object[] arguments = this.arguments;
    if (argumentTypes == null) {
        argumentTypes = Constants.EMPTY_CLASS_ARRAY;
        arguments = null;
    }
    return data.newInstance(argumentTypes, arguments, callbacks);
}
public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
    setThreadCallbacks(callbacks);
    try {
        // Explicit reference equality is added here just in case Arrays.equals does not have one
        if (primaryConstructorArgTypes == argumentTypes ||
                Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
            // If we have relevant Constructor instance at hand, just call it
            // This skips "get constructors" machinery
            return ReflectUtils.newInstance(primaryConstructor, arguments);
        }
        // Take a slow path if observing unexpected argument types
        return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
    } finally {
        // clear thread callbacks to allow them to be gc'd
        setThreadCallbacks(null);
    }
}
Spring AOP 其实也是利用JDK或是CGLib动态代理技术为目标bean实现目标Bean织入横切逻辑的。本文就底层使用几个技术,分别实现了业务日志输出通过代理织入业务逻辑。但是仍然有需要改进的地方:
这三个问题在AOP中占有很重要的地位,下一篇文章将开始讲述Spring AOP是如何优雅的解决这三个问题的。
To be continue.....