目录
一、手动编码实现spring-aop
1. demo代码
(1)aop相关实体类:
advice接口的实现类:描述了aop代理要做什么事;
pointcut接口的实现类:描述了aop代理在哪里做事;
advisor则是对advice、pointcut的整合。
package com.abc;
//工人接口
public interface Worker {
void work();
}
//普通打工人
public class OrdinaryWorker implements Worker {
@Override
public void work() {
System.out.println("* * * * * 搬砖 * * * * *");
}
}
//难过的打工人
public class SadWorker implements Worker {
@Override
public void work() {
System.out.println("* * * * 咬牙搬砖 * * * *");
}
}
//资本家类
public class Capitalist {
public void work() {
System.out.println("$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $");
}
}
//工人增强器
public class WorkerAdvisor extends AbstractPointcutAdvisor {
@Override
public Advice getAdvice() {
return new WorkerAdvice();
}
@Override
public Pointcut getPointcut() {
String pattern = "com.abc.OrdinaryWorker.*";
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(pattern);
return pointcut;
}
static class WorkerAdvice implements AfterReturningAdvice, MethodBeforeAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("21:00 - 滴,下班:没有困难的工作,只有勇敢的打工人!");
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("9:00 - 滴,上班:早安打工人!");
}
}
}
//资本家增强器
public class CapitalistAdvisor extends AbstractPointcutAdvisor {
@Override
public Advice getAdvice() {
return new CapitalistAdvice();
}
@Override
public Pointcut getPointcut() {
String pattern = "com.mytest.aop.bean.Capitalist.*";
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(pattern);
return pointcut;
}
static class CapitalistAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("又是愉快的一天 (*^▽^*) ");
}
}
}
(2)spring配置文件aopxml.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="workerAdvisor" class="com.mytest.aop.bean.WorkerAdvisor"/>
<bean id="capitalistAdvisor" class="com.mytest.aop.bean.CapitalistAdvisor"/>
<bean id="worker" class="com.mytest.aop.bean.OrdinaryWorker"/>
<bean id="capitalist" class="com.mytest.aop.bean.Capitalist"/>
<bean id="aopWorker" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="worker"></ref>
</property>
<property name="interceptorNames">
<list>
<value>workerAdvisor</value>
<value>capitalistAdvisor</value>
</list>
</property>
<property name="targetSource">
<ref bean="hotSwappableTargetSource"/>
</property>
</bean>
<bean id="aopCapitalist" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="capitalist"></ref>
</property>
<property name="interceptorNames">
<list>
<value>workerAdvisor</value>
<value>capitalistAdvisor</value>
</list>
</property>
</bean>
<bean id="hotSwappableTargetSource" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="worker"/>
</bean>
</beans>
(3)执行逻辑:
public class AopTest {
public static void main(String[] args) {
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("mytest/aop/aopxml.xml"));
Object aopWorker = bf.getBean("aopWorker");
Object aopCapitalist = bf.getBean("aopCapitalist");
assert !(aopCapitalist instanceof Proxy);
assert aopWorker instanceof Proxy;
Capitalist capitalist = (Capitalist) aopCapitalist;
capitalist.work();
System.out.println("\n普通的打工人:");
Worker worker = (Worker) aopWorker;
worker.work();
System.out.println("\n难过的打工人:");
SadWorker sadWorker = new SadWorker();
HotSwappableTargetSource targetSource = bf.getBean("hotSwappableTargetSource", HotSwappableTargetSource.class);
targetSource.swap(sadWorker);
worker.work();
}
}
(4)执行结果:
$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
又是愉快的一天 (*^▽^*)
普通的打工人:
9:00 - 滴,上班:早安打工人!
* * * * * 搬砖 * * * * *
21:00 - 滴,下班:没有困难的工作,只有勇敢的打工人!
难过的打工人:
9:00 - 滴,上班:早安打工人!
* * * * 咬牙搬砖 * * * *
21:00 - 滴,下班:没有困难的工作,只有勇敢的打工人!
问题:为什么断言能顺利执行 —— aopCapitalist不是Proxy的实例,而aopWorker是Proxy的实例???且看下面分解……
2. 实现原理
下面从两个角度分析aop的实现,一是如何生成代理对象,二是代理对象如何执行切面逻辑,也就是在业务逻辑中织入切面逻辑。
2.1 如何生成代理
spring是如何生成目标对象的代理,即解读:Object aopWorker = bf.getBean("aopWorker");
(1)ProxyFactoryBean的UML
"aopWorker"的类型是ProxyFactoryBean,UML图如下:
根据1:ProxyFactoryBean是工厂Bean,因此,执行bf.getBean("aopWorker")会调用ProxyFactoryBean的getObject方法;
根据2:ProxyFactoryBean能感知自己所在的BeanFactory,这是因为初始化bean时,会执行invokeAwareMethods,对各种aware类接口的实现类进行处理;
根据3:ProxyFactoryBean继承AdvisedSupport,有几个关键的成员变量:targetSource(被代理的目标对象的包装)、interfaces(代理的接口)、advisors(增强器)
(2)ProxyFactoryBean的getObject()
主要逻辑如下:
从上图的逻辑,可以知道为什么demo中两个代理对象的断言不同:这是因为Capitalist类没有实现接口,采用cglib创建代理对象,而aopWorker是基于jdk的动态代理。
考虑到继承关系,通过spring的property标签,既可以配置诸如:interceptorNames、targetName等来自ProxyFactoryBean的成员变量,也可以配置诸如targetSource、interfaces、advisors等来自AdvisedSupport的成员变量。
无论是基于jdk动态代理的JdkDynamicAopProxy,还是基于cglib的ObjenesisCglibAopProxy,都有1个成员变量:AdvisedSupport advised,记录了代理目标的配置信息。
2.2 如何执行切面逻辑
spring生成目标对象的代理后,代理对象是如何执行aop的切面逻辑,即解读:((Worker) aopWorker).work()。
从1.2节可知,AopProxy有2种实现:JdkDynamicAopProxy、ObjenesisCglibAopProxy,下面就分析一下JdkDynamicAopProxy的情况:
Java动态代理对象执行被代理的接口方法时,会执行InvocationHandler接口的invoke()方法。
JdkDynamicAopProxy实现了InvocationHandler接口,invoke()方法的主要逻辑如下:
从整体大局了解代码逻辑后,很自然的明白为什么chain的泛型是Object,而不是Interceptor,因为元素还可能是InterceptorAndDynamicMethodMatcher类型。而isRuntime这种情况,应该是针对方法重载的情况,相同的方法签名信息,入参类型不同,需要运行时再判断是否匹配。
aop对于不同advice转成不同MethodInterceptor,采用了适配器模式。aop的几个关键元素关系大致如下:
3. spring-aop的高级特性
了解spring-aop的基本实现后,就能够理解targetSource属性的意义:为什么用targetSource包装被代理的目标对象target?
开发者可以实现TargetSource接口,实现定制功能,比如demo中的HotSwappableTargetSource提供了热交换能力。
二、注解实现spring-aop
1. demo代码
(1)切面相关:
@Aspect
public class SleepAspect {
@Pointcut("execution(* *.work(..))")
public void pointCutExpression() {
}
@Around("pointCutExpression()")
public Object around(ProceedingJoinPoint jp){
Object result = null;
try{
result = jp.proceed();
System.out.println("z z z z z 晚安 z z z z z");
} catch (Throwable t){
}
return result;
}
}
(2)spring配置文件aopannotation.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean class="com.mytest.aop.aspect.SleepAspect"></bean>
<bean id="worker" class="com.mytest.aop.bean.OrdinaryWorker"/>
<bean id="capitalist" class="com.mytest.aop.bean.Capitalist"/>
</beans>
(3)执行逻辑:
public class AopAnnotationTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("mytest/aop/aopannotation.xml");
Worker worker = (Worker) ac.getBean("worker");
worker.work();
System.out.println();
Capitalist capitalist = (Capitalist) ac.getBean("capitalist");
capitalist.work();
}
}
(4)执行结果:
* * * * * 搬砖 * * * * *
z z z z z 晚安 z z z z z
$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
z z z z z 晚安 z z z z z
2. 实现原理
注解实现aop与手动编码实现aop的差别在于代理对象的创建,下图描述了代理对象创建的主要流程:
参考资料
“手动编码实现spring-aop”:参考《spring技术内幕(第2版)》
“注解实现spring-aop”:参考《spring源码深度解析(第2版)》