[Spring] Spring AOP 实现原理剖析(一)

2019-12-17 18:08:30 浏览数 (1)

手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。

平台

地址

CSDN

https://blog.csdn.net/sinat_28690417

简书

https://www.jianshu.com/u/3032cc862300

个人博客

https://yiyuery.github.io/NoteBooks/

[Spring] Spring AOP 实现原理剖析(一)

Spring AOP 实现原理剖析

AOP 几个重要术语

  • 连接点 Joinpoint
  • 切点 Pointcut
  • 增强 Advice
  • 目标对象 Target
  • 引介 Introduction
  • 织入 Weaving
  • 代理 Proxy
  • 切面 Aspect

此处不对这几个术语做冗长的介绍,为了便于记忆,上面的元素在AOP中充当何种角色?我们随着实战的深入慢慢来讲。

AOP的工作重心在于如何将增强应用于目标对象的连接点上,这里主要包括2个工作:

  1. 如何通过切点和增强定位到连接点?
  2. 如何在增强中编写切面的代码?

AOP的常见工具

  • AspectJ
  • AspectWerkz
  • JBoss AOP
  • Spring AOP

AOP工具的设计目标是把业务的模块化分割通过横切来实现。使用类似OOP的方式进行切面的编程工作。位于

是连接点模型,它提供一种机制,可以定位到需要在何处发生横切。

基础知识

Spring AOP

  • 运用动态代理技术在运行期织入增强的代码
  • Spring AOP 运用了2种代理机制:一种是基于JDK的动态代理,另一种是基于CGLib的动态代理。

使用2种代理机制的主要原因是因为JDK本身只提供了接口的代理,而不支持类的代理。

横切逻辑

先写个简单的横切逻辑

代码语言:javascript复制
/*
 * @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();◊
    }
}

定义简单的个人员管理服务类,并把我们写的这个简单的业务日志监听逻辑写入

代码语言:javascript复制
/*
 * @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();
    }
}

输出测试

代码语言:javascript复制
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就是为了解决这个问题的。

代码语言:javascript复制
 BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
//....
BusinessLogMonitor.end();

在方法的执行前后,填充增强逻辑。这也就是我们常看到的"横切"、"织入"操作。

JDK 动态代理实现横切逻辑

先来补充下基本知识

JDK动态代理主要涉及java.lang.reflect包中的两个类:

  • Proxy 利用接口InvocationHandler动态创建一个符合某一接口定义的实例,生成目标类的代理对象。
  • InvocationHandler 接口,可以通过该接口实现横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。

接下来,我们尝试利用JDK的动态代理来替代下面的逻辑:

代码语言:javascript复制
BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
//....
BusinessLogMonitor.end();

首先,定义接口InvocationHandler的实现类,用来充当代理类的实现:

代码语言:javascript复制
/*
 * @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动态代理的效果

代码语言:javascript复制
@Override
public void deletePerson() {
    log.info("模拟人员数据删除");
    try {
        Thread.sleep(3000);
    } catch (Exception e) {
        log.error("PersonManagerServiceImpl deletePerson failed!");
    }
}

测试输出

代码语言:javascript复制
/**
 * 通过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毫秒

可以比对下两次,的确实现了一样的功能。

代码语言:javascript复制
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

newProxyInstance方法有三个入参,第一个是类加载器,第二个是需要代理实例实现的接口列表。

即要使用JDK的动态代理,需要定义需要实现的接口,代理类实际是将该接口实现了一遍,并在增强逻辑插入后,通过invoke方法调用被代理类的方法中的业务逻辑。

没有实现接口的对象调用自身getClass().getInterfaces()返回的接口信息是空的,无法使用JDK动态代理。

那么,如何在不定义多余接口的情况下,直接是实现代理实例的创建呢?CGLib给出了份完美的答案。

CGLib 动态代理实现横切逻辑

先引入下相关依赖

代码语言:javascript复制
dependencies {
    //...
    compile ('cglib:cglib:3.3.0')
    //...
}

首先实现接口MethodInterceptor,传入被代理对象的实例类型,并对暴露获取代理类实例的方法。

代码语言:javascript复制
/*
 * @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;
    }
}

测试输出

代码语言:javascript复制
/**
 * 通过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毫秒

两点需要注意:

  • 功能上也实现了之前JDK动态代理的横切逻辑
  • 但是在CGLib代理输出的结果中可以看到PersonManagerServiceImpl$$EnhancerByCGLIB$$77eaf4f6,这个和之前JDK动态代理实例输出的结果PersonManagerServiceImpl有所差异。

这个带有CGLIB关键信息的实例其实是Cglib对象为PersonManagerServiceImpl动态创建的一个织入业务日志输出逻辑的代理对象,并调用该代理类的业务方法。该对象EnhancerByCGLIB$$77eaf4f6PersonManagerServiceImpl的一个之类。

因此,由于CGLib采用动态创建之类的方式生成代理对象,所以不能对目标类的final或是private方法进行代理。 而且子类实现增强逻辑,并在增强逻辑调用的中间,调用被代理类(父类)的方法。

核心源码

代码语言:javascript复制
//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;
}

在这里插入图片描述

代码语言:javascript复制
//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织入横切逻辑的。本文就底层使用几个技术,分别实现了业务日志输出通过代理织入业务逻辑。但是仍然有需要改进的地方:

  • 目标类都加入了横切逻辑,每个都需要单独调用CglibProxy来构造代理类,我们往往希望可以在某些特定的方法中实现横切逻辑
  • 通过硬编码实现织入横切逻辑的织入点,不够灵活
  • 手工编写代理实例的创建过程,在为不同类创建代理时,需要编写大量冗余代码,不够通用。

这三个问题在AOP中占有很重要的地位,下一篇文章将开始讲述Spring AOP是如何优雅的解决这三个问题的。

To be continue.....


0 人点赞