1. 前言
前边介绍了 AOP 机制的两个基础功能,分别是增强和切面,但目标对象仍然游离于整个体系之外。先前在测试代码需要通过反射的方式寻找需要增强的方法,但这种做法只是权宜之计,我们需要一种解决方案将目标对象也纳入整个体系中来。Spring 框架提供了 AOP 代理完成这一工作,具体的做法使用代理包装目标对象,当调用目标对象的方法时,由代理对象完成相关的增强逻辑,目标方法只需要关心本身的业务即可。
2. 整体分析
2.1 继承结构
AOP 代理的继承结构比较复杂,按照功能划分为以下四组:文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
- 蓝色区域表示代理工厂,创建代理对象是一项复杂的工作,使用工厂类屏蔽创建的细节
- 紫色区域表示代理对象,这里需要先通过工厂拿到 AOP 代理,进一步才能获取通常意义上的代理对象
- 黄色区域表示目标对象,也就是被增强的对象
- 绿色区域表示增强和切面功能,
Advisor
组件已经在上一节详解论述过了
文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
2.2 代理模式
代理模式是指为某个对象提供一个代理对象,并由代理对象接管对该对象的访问。代理模式的结构由三个角色组成:文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
- 抽象主题角色:声明了真实主题和代理的共同接口,这样可以在任何使用真实主题的地方都可以使用代理角色
- 真实主题角色:定义了代理角色所代表的真实对象
- 代理角色:内部包含一个对真实主题的引用,在将客户端的调用传递给真实主题时,代理角色可以完成额外的逻辑
文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
我们发现,代理模式有两个显著的特点。其一,对目标对象的访问都是经由代理对象处理的。其二,代理对象在是否调用目标对象的方法时有很大的决策权,比如在权限访问系统的设计中,代理对象甚至可以不调用目标对象的方法。这两个特点都适用于 AOP,前者在调用方法时检查是否需要增强,后者可以在调用方法时执行增强逻辑。因此,Spring 使用代理模式来实现 AOP 功能。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
2.3 代理分类
代理可以分为两大类,静态代理和动态代理。静态代理是指代理类在程序运行之前已经存在,所谓的静态是针对编译期而言的。一般来说,静态代理应用的场景较少,仅了解即可。动态代理是指在运行时动态地生成代理类,动态代理有两种主要的实现方式,一是 JDK 提供的动态代理功能,二是通过 CGLIB 框架创建代理。两者的区别在于,JDK 动态代理必须指定一个接口,CGLIB 代理是通过生成目标类的子类,并重写父类方法实现的。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
3. TargetSource
TargetSource
接口的作用是封装 AOP 调用的目标对象,getTarget
方法的作用是获取目标对象,这说明目标对象的来源可能有多种途径。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
java
public interface TargetSource {
Class<?> getTargetClass();
Object getTarget() throws Exception;
}
SingletonTargetSource
实现了 TargetSource
接口,从类名中可以看出,SingletonTargetSource
是对 Spring 容器管理的单例进行包装。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
java
public class SingletonTargetSource implements TargetSource {
private final Object target;
public SingletonTargetSource(Object target) {
this.target = target;
}
@Override
public Class<?> getTargetClass() {
return this.target.getClass();
}
@Override
public Object getTarget() {
return this.target;
}
}
4. 代理工厂
4.1 ProxyConfig
ProxyConfig
定义了创建 AOP 代理的配置信息,从功能上来说与 BeanDefinition
类似。该类比较重要的属性有两个,简单介绍如下:文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
proxyTargetClass
:表示创建代理的方式。如果为 true,表示代理整个类,即 Cglib 代理。如果为 false 表示代理接口,也就是 JDK 动态代理。exposeProxy
:表示是否将代理对象暴露给外界,允许通过AopContext
来访问
java
public class ProxyConfig {
private boolean proxyTargetClass;
boolean exposeProxy;
}
4.2 Advised
Advised
接口的作用是对代理工厂进行操作,在创建代理时需要设置一些必要的信息。比如设置目标对象,获取目标对象实现的接口类型,添加增强组件等。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/16760.html
setTargetSource
方法:设置目标对象getProxiedInterfaces
方法:获取获取需要代理的类或接口的集合addAdvisor
方法:添加Advisor
实例addAdvice
方法:添加任意类型的通知,并包装成DefaultPointcutAdvisor
实例
java
public interface Advised {
void setTargetSource(TargetSource targetSource);
TargetSource getTargetSource();
Class<?> getTargetClass();
Class<?>[] getProxiedInterfaces();
void addAdvisor(Advisor advisor);
void addAdvice(Advice advice);
}
4.3 AdvisedSupport
AdvisedSupport
实现了 Advised
接口的相关方法,起到了辅助的作用。AdvisedSupport
以组合的方式成为 AOP 代理的一部分,基本属性如下:
targetSource
:目标对象interfaces
:代理对象需要实现的接口advisors
:缓存了目标对象所适用的Advisor
集合methodCache
:缓存了适用于每个方法的拦截器集合registry
:对各种类型的增强提供通用的适配
java
public class AdvisedSupport extends ProxyConfig implements Advised {
TargetSource targetSource;
private List<Class<?>> interfaces = new ArrayList<>();
private List<Advisor> advisors = new LinkedList<>();
private transient Map<MethodCacheKey, List<Object>> methodCache = new ConcurrentHashMap<>(32);
private static AdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry();
}
4.4 ProxyFactory
ProxyFactory
扩展了 AdvisedSupport
,提供了创建代理对象的功能。getProxy
方法分为两步,先通过工厂类 AopProxyFactory
创建 AopProxy
实例,然后调用该实例的 getProxy
方法创建代理对象。这里用到了两个工厂方法,先创建 AopProxy
实例,然后通过 AopProxy
实例创建真正的代理对象,也就是最终的返回值。
java
public class ProxyFactory extends AdvisedSupport {
private AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
public Object getProxy(){
return createAopProxy().getProxy();
}
protected final synchronized AopProxy createAopProxy() {
return this.aopProxyFactory.createAopProxy(this);
}
}
DefaultAopProxyFactory
是 AopProxyFactory
接口的唯一实现类,createAopProxy
方法支持创建两种动态代理。如果 proxyTargetClass
属性为 true
,且目标类型不是一个接口,则创建 ObjenesisCglibAopProxy
实例,也就是 Cglib 代理。其余情况则创建 JdkDynamicAopProxy
实例,即 JDK 动态代理。
java
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) {
//如果proxyTargetClass属性为true,需要进一步检查是否能使用Cglib类代理
if (config.isProxyTargetClass()) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new RuntimeException("TargetClass不存在,创建代理需要一个接口或目标类");
}
//如果目标类型是一个接口或Proxy的子类,只能使用JDK动态代理
if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)){
return new JdkDynamicAopProxy(config);
}
//排除所有不符合的条件,使用Cglib代理
else{
return new ObjenesisCglibAopProxy(config);
}
}
//默认是JDK动态代理
return new JdkDynamicAopProxy(config);
}
}
5. AOP 代理
5.1 概述
AOP 代理的继承结构可以分为两个部分,一是 AopProxy
接口的实现,包括 JDK 动态代理和 Cglib 代理。二是 MethodInvocation
接口的实现,负责增强逻辑与目标方法的具体调用。本节我们先实现 JdkDynamicAopProxy
的相关内容,下一节介绍 CglibAopProxy
的实现。
5.2 创建代理对象
AopProxy
接口的实现类必须完成两个工作,一是创建代理对象,二是代理对象实现的增强逻辑。先来看第一步,JdkDynamicAopProxy
实现了 AopProxy
接口的 getProxy
方法,完成了创建代理对象的工作。具体来说,首先获取代理对象需要实现哪些接口,然后创建 JDK 动态代理对象。
java
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
private final AdvisedSupport advised;
public JdkDynamicAopProxy(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object getProxy() {
//获取需要代理的接口类型
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
//通过反射的方式创建代理对象
return Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), proxiedInterfaces, this);
}
}
5.3 调用过程
JdkDynamicAopProxy
实现了 InvocationHandler
接口,当代理对象的方法被调用的时候,会自动执行 invoke
方法。invoke
方法主要做了三件事,一是对外暴露代理对象,二是获取拦截器链,三是执行拦截操作,最终调用目标方法。
第一步,准备工作。如果 exposeProxy
属性为 true,则将代理对象绑定到当前线程上。有时候我们需要在代码中获取代理对象的引用,AopContext
的 currentProxy
方法就会非常有用。需要注意的是,只有在目标方法的内部使用时,才能拿到代理对象的引用。当方法执行完毕,finally 块中解除了代理对象与当前线程的绑定。
java
//所属类[cn.stimd.spring.aop.framework.JdkDynamicAopProxy]
//调用目标对象的方法前,先执行代理对象的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target;
try {
//1. 如果exposeProxy属性为true,将代理对象绑定到ThreadLocal上
Object retVal;
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
//获取目标对象及其类型
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
//2. 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
//3. 执行拦截操作,最终调用目标方法
//如果拦截器链为空,当作普通方法调用
if (chain.isEmpty()) {
ReflectionUtils.makeAccessible(method);
retVal = method.invoke(target, args);
}else{
//拦截器不为空,执行增强逻辑并调用目标方法
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
return retVal;
} finally {
//解除代理对象与当前线程的绑定
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
第二步,获取目标方法对应的拦截器链。AdvisedSupport
的 advisors
属性保存了一个类所有的拦截器对象,而不是只针对某个方法,因此需要寻找适用于指定方法的拦截器链。Advisor
分为切点和引入两大类,这里我们只关心切点。对于切点来说,需要检查 ClassFilter
和 MethodMatcher
。一般情况下,ClassFilter
是默认匹配的,因此我们只关心 MethodMatcher
即可。
java
//所属类[cn.stimd.spring.aop.framework.AdvisedSupport]
//获取与方法相对应的拦截器集合
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if(cached == null){
List<Object> interceptorList = new ArrayList<>(this.advisors.size());
for (Advisor advisor : this.advisors) {
//处理PointcutAdvisor
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pa = (PointcutAdvisor) advisor;
//检查ClassFilter和MethodMatcher是否符合条件
if (pa.getPointcut().getClassFilter().matches(targetClass)) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (pa.getPointcut().getMethodMatcher().matches(method, targetClass)) {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
//处理IntroductionAdvisor,略
}
cached = interceptorList;
this.methodCache.put(cacheKey, cached); //加入缓存
}
return cached;
}
第三步,执行拦截操作。如果拦截器链为空,把目标方法当成普通方法正常调用即可。这里有一个问题,普通方法为什么也会被拦截?首先我们要明确,一个类被代理只能说明它获得了拦截的资格,至于执行哪些拦截逻辑要视具体的方法而定。当一个类至少有一个增强方法,Spring 就认为这个类应该被代理。换句话说,目标类中除了增强方法,还可以有普通方法。
我们来看增强方法是如何处理的。ReflectiveMethodInvocation
是 MethodInvocation
接口的实现类,通过反射的方式来调用方法。该类持有一系列字段,包括代理对象、目标对象、目标方法,传入的参数、拦截器链。在 proceed
方法的执行过程中,先依次执行拦截器链中的每个拦截器,最后通过 invokeJoinpoint
方法完成对目标方法的调用。
java
public class ReflectiveMethodInvocation implements ProxyMethodInvocation {
protected final Object proxy; //代理对象
protected final Object target; //目标对象
private final Class<?> targetClass; //目标对象的类型
protected final Method method; //调用的方法
protected Object[] arguments; //方法参数
protected final List<?> interceptorsAndDynamicMethodMatchers; //拦截器链
private int currentInterceptorIndex = -1; //记录当前拦截器的下标
@Override
public Object proceed() throws Throwable {
//2. 当拦截器链的所有拦截器都执行完毕后调用,即执行目标方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
//1. 执行拦截器链中的下一个拦截器
Object advice = this.interceptorsAndDynamicMethodMatchers.get(++currentInterceptorIndex);
return ((MethodInterceptor) advice).invoke(this);
}
//默认使用反射调用,允许子类重写
protected Object invokeJoinpoint() throws Throwable {
ReflectionUtils.makeAccessible(this.method);
try {
return this.method.invoke(this.target, this.arguments);
}catch (InvocationTargetException ex){
throw ex.getTargetException(); //抛出原始异常
}
}
}
6. 测试
6.1 JDK 动态代理
JDK 动态代理需要先定义接口,ProxyTarget
接口声明了 execute
方法,ProxyTargetImpl
类实现了 ProxyTarget
接口。
java
//测试类
public interface ProxyTarget {
void execute();
}
public class ProxyTargetImpl implements ProxyTarget {
@Override
public void execute() {
System.out.println("执行execute方法");
}
}
JDK 动态代理的功能是由 Proxy
和 InvocationHandler
这两个 API 提供的,其中 Proxy
类负责创建代理对象,InvocationHandler
接口表示一个调用处理器。当代理对象执行方法时,首先会执行调用处理器的 invoke
方法,可以在目标方法的前后进行额外的操作,此处仅打印一条日志。
java
//测试类,简单的处理器实现
public class SimpleInvocationHandler implements InvocationHandler {
private Object target;
public SimpleInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理测试...");
method.invoke(this.target, args);
return null;
}
}
在测试方法中,创建了目标对象 ProxyTargetImpl
和调用处理器 SimpleInvocationHandler
的实例,然后调用 Proxy
类的静态方法 newProxyInstance
创建代理对象。这里需要注意,proxy
变量的强转类型必须是接口类型,如果声明成实现类 ProxyTargetImpl
,会抛出 ClassCastException
异常。
java
//测试方法
@Test
public void testJdkProxy() {
ProxyTargetImpl target = new ProxyTargetImpl();
SimpleInvocationHandler handler = new SimpleInvocationHandler(target);
ProxyTarget proxy = (ProxyTarget) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
proxy.execute();
}
从测试结果可以看到,先打印了代理拦截的日志,然后才是目标方法,说明 JDK 动态代理对象创建成功。
java
JDK动态代理测试...
执行execute方法
6.2 JdkAopProxy
在测试方法中,首先创建 ProxyFactory
实例,然后设置需要代理的接口类型,这是比较重要的一步。接下来设置目标对象,并添加前置通知和返回通知,然后调用代理工厂的 getProxy
方法,完成代理创建的流程。
java
//测试方法
@Test
public void testJdkAopProxy(){
ProxyFactory factory = new ProxyFactory();
//使用JDK代理需要指定代理接口类型
factory.addInterface(ProxyTarget.class);
factory.setTarget(new ProxyTargetImpl());
factory.addAdvice(new SimpleAfterReturningAdvice());
factory.addAdvice(new SimpleMethodBeforeAdvice());
ProxyTarget proxy = (ProxyTarget) factory.getProxy();
proxy.execute();
}
从测试结果中可以看出,在执行目标方法前后,分别执行了前置通知和返回通知的逻辑。
lua
前置通知,方法名:execute
执行execute方法
返回通知,方法名:execute
7. 总结
我们实现了增强和切面两大功能,其中增强作用于方法执行的前后,切面则是对符合条件的方法进行筛选。鉴于此,我们可以说 AOP 功能最终的落脚点是一个对象(的方法),Spring 通过代理模式实现了对目标对象的整合。一般来说,代理可以分为静态代理和动态代理,其中动态代理有两种实现,一是 JDK 动态代理,二是 CGLIB 代理。
AOP 代理的创建流程比较复杂,Spring 通过工厂模式屏蔽了创建的细节,代理工厂的实现可以分为四个部分:
ProxyFactory
负责设置创建代理的相关属性,以及创建代理对象,相当于兼具BeanDefinition
与BeanFactory
的功能。AopProxy
表示一个 AOP 代理对象,根据创建方式分为两种,即 JDK 动态代理和 Cglib 代理。TargetSource
表示目标对象,通常来源于 Spring 容器托管的单例。Advisor
组件整合了增强和切面的功能,与AopProxy
一起实现了 Spring AOP 的基本功能。
8. 项目信息
新增修改一览,新增(18),修改(0)
scss
aop
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.springwheel.aop
│ ├─ framework
│ │ ├─ Advised.java (+)
│ │ ├─ AdvisedSupport.java (+)
│ │ ├─ AopContext.java (+)
│ │ ├─ AopProxy.java (+)
│ │ ├─ AopProxyFactory.java (+)
│ │ ├─ AopProxyUtils.java (+)
│ │ ├─ DefaultAopProxyFactory.java (+)
│ │ ├─ JdkDynamicAopProxy.java (+)
│ │ ├─ ProxyConfig.java (+)
│ │ ├─ ProxyFactory.java (+)
│ │ └─ ReflectiveMethodInvocation.java (+)
│ ├─ target
│ │ └─ SingletonTargetSource.java (+)
│ ├─ ProxyMethodInvocation.java (+)
│ └─ TargetSource.java (+)
└─ test
└─ java
└─ cn.stimd.springwheel.aop.test
└─ proxy
├─ ProxyTarget.java (+)
├─ ProxyTargetImpl.java (+)
├─ ProxyTest.java (+)
└─ SimpleInvocationHandler.java (+)
注:+号表示新增、*表示修改
评论