前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Spring] Spring AOP 实现原理剖析(一)

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

作者头像
架构探险之道
发布2019-12-17 18:08:30
3220
发布2019-12-17 18:08:30
举报
文章被收录于专栏:架构探险之道架构探险之道

手机用户请横屏获取最佳阅读体验,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.....


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构探险之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • [Spring] Spring AOP 实现原理剖析(一)
    • AOP 几个重要术语
      • AOP的常见工具
        • 基础知识
          • 横切逻辑
          • JDK 动态代理实现横切逻辑
          • CGLib 动态代理实现横切逻辑
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档