手机用户请
横屏
获取最佳阅读体验,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个工作:
- 如何通过切点和增强定位到连接点?
- 如何在增强中编写切面的代码?
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
的实现类,用来充当代理类的实现:
/*
* @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
,传入被代理对象的实例类型,并对暴露获取代理类实例的方法。
/*
* @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$$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;
}
在这里插入图片描述
代码语言: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.....