文章目录
【README】
本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
1)AOP基本要素回顾:
- Joinpoint切点:被织入横切逻辑的程序位置(执行点)
- Pointcut切点表达式:描述切点位置的表达式;
- Advice通知:横切逻辑(或切面逻辑);
- Aspect切面:包含多个Pointcut切点表达式和Advice通知;
- 织入器:把切面逻辑织入到切点上下文的对象;
2)本文介绍了spring对上述aop基本要素的抽象(接口与具体实现)
3)在基本要素抽象基础上,本文总结了通知与切面织入到目标类或目标对象的代码实现;
【1】spring aop中的Joinpoint切点
1)Joinpoint 切点: 被织入逻辑的程序位置(执行点) ;
public interface Joinpoint {
@Nullable
Object proceed() throws Throwable;
@Nullable
Object getThis();
@Nonnull
AccessibleObject getStaticPart();
}
【2】spring aop中的Pointcut描述切点的表达式
1)Pointcut: 描述切点位置的表达式; 即spring扫描系统中的类,找出与Pointcut表达式匹配的类或方法,称为目标类(或目标方法),最后把横切逻辑(由Advice通知来定义)织入到目标类(或目标方法);
2)Pointcut有2种匹配模式:
- ClassFilter:根据class类型匹配;
- MethodMatcher:根据方法匹配;
【Pointcut】
public interface Pointcut {
Pointcut TRUE = TruePointcut.INSTANCE; // 默认对系统中所有对象,以及对象上所有的Joinpoint进行匹配
ClassFilter getClassFilter(); // 根据class类型匹配
MethodMatcher getMethodMatcher(); // 根据方法匹配
}
【2.1】ClassFilter:根据class类型匹配
@FunctionalInterface
public interface ClassFilter {
ClassFilter TRUE = TrueClassFilter.INSTANCE; // 匹配系统中所有目标类
boolean matches(Class<?> clazz);
}
【使用场景】当matches返回true,则织入横切逻辑到clazz对应的类;
public class ClassFilterImpl implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
return IEmailSender.class.equals(clazz);
}
}
上述代码表示为IEmailSender目类的所有切点都织入横切逻辑;
【2.2】MethodMatcher:根据方法匹配
public interface MethodMatcher {
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; // 匹配目标类中的所有方法
boolean matches(Method method, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object... args);
}
【使用场景】 匹配名称为send的方法
public class MethodMatcherImpl implements MethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "send".equals(method.getName());
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
}
【2.2.1】StaticMethodMatcher 静态方法匹配器
StaticMethodMatcher: isRuntime() 返回false ; 表示不匹配具体Joinpoint的方法参数 ;
- 即仅通过2个参数的matches() 方法来做匹配运算;
【2.2.2】DynamicMethodMatcher 动态方法匹配器
DynamicMethodMatcher: isRuntime()方法返回true; 表示要匹配具体Joinpoint的方法参数 ;
- 先通过2个参数的matches() 方法来做匹配运算;若成功,才执行带有3个参数的matches() 做匹配运算;若失败,则不执行带有3个参数的matches() ;
- 即需要2个参数的matches() 方法与3个参数的matches() 方法都返回true,则才会织入横切逻辑到匹配成功的方法;
【2.3】常见Pointcut
【2.3.1】NameMatchMethodPointcut: 仅根据名字匹配目标方法
1)NameMatchMethodPointcut是StaticMethodMatcherPointcut,静态方法匹配器Pointcut
public static void nameMatchMethodPointcut(String[] args) {
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("send");
pointcut.setMappedNames("sendMsg","sendEmail");
}
【2.3.2】JdkRegexpMethodPointcut: 根据正则表达式匹配目标方法
1)JdkRegexpMethodPointcut 是StaticMethodMatcherPointcut,静态方法匹配器Pointcut
public static void jdkRegexpMethodPointcut(String[] args) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*.match.*");
pointcut.setPatterns(".*.match.*", ".*.matches");
}
【2.3.3】Perl5RegexpMethodPointcut: 根据正则表达式匹配目标方法
1)Perl5RegexpMethodPointcut 是StaticMethodMatcherPointcut,静态方法匹配器Pointcut
【2.3.4】 AnnotaionMatchingPointcut:根据注解匹配目标类型与目标方法
1)AnnotaionMatchingPointcut:根据注解匹配目标类型与目标方法;
【annotaionMatchingPointcut】匹配 @ControllerAdvice标注的方法
public static void annotaionMatchingPointcut(String[] args) {
AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(ControllerAdvice.class);
}
【2.3.5】ComposablePointcut:复合切点表达式
1)ComposablePointcut:复合切点表达式, 可以做并或交的逻辑运算;
public static void composablePointcut(String[] args) {
ClassFilterImpl classFilterImpl = new ClassFilterImpl();
MethodMatcherImpl methodMatcher = new MethodMatcherImpl();
ComposablePointcut pointcut1 = new ComposablePointcut(classFilterImpl);
ComposablePointcut pointcut2 = new ComposablePointcut(classFilterImpl, methodMatcher);
// 逻辑运算, 求并运算
ComposablePointcut unionedPointcut = pointcut1.union(pointcut2);
}
【2.3.6】ControlFlowPointcut:控制流切点表达式
1) ControlFlowPointcut:控制流切点表达式;匹配程序调用流程, 而不是只要切点被调用就进行匹配;即匹配上游调用方;如上游有A1 A2 共计2个调用方法,下游目标方法是B1;而控制流切点表达式仅匹配A1,则当A1调用B1才会织入逻辑,而A2调用B1不会织入;
【2.4】自定义Pointcut
1) 自定义静态方法匹配器Pointcut: 实现 StaticMethodMatcherPointcut 抽象类;
public class CustomStaticMethodMatcherPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "send".equals(method.getName());
}
}
2)自定义动态方法匹配器Pointcut: 实现 DynamicMethodMatcherPointcut抽象类;
public class CustomDynamicMethodMatcherPointcut extends DynamicMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return method.getName().startsWith("send");
}
}
【3】spring aop中的Advice通知(横切逻辑)
1) spring aop加入了 开源组织AOP Alliance (AOP联盟) 目的在于标准化AOP的使用 ;
2)spring aop中的Advice实现全部遵循AOP Alliance规定的接口。
3)Advice按照其自身能否在目标对象类的所有实例中共享, Advice划分为两类,包括 per-class 和 per-instance
- per-class Advice:该advice 可以在目标对象类的所有实例共享; 即一个Advice对应一个目标对象类;
- 应用场景:新增sql执行耗时; 就需要对所有dao 方法织入耗时统计横切逻辑( 所有实例的方法都织入 );
- per-instance Advice:该advice 不可以在目标对象类的所有实例共享; 即一个Advice对应一个目标对象;
- 应用场景: 公办学校老师本职工作是在学校上课, 但部分老师会在课外培训学校兼职培训老师; 具体实现是仅给兼职课外培训的公办学校老师织入课外上课次数统计横切逻辑,没有兼职的公办学校老师不织入; (部分实例织入,部分实例不织入,并不是所有公办学校老师都织入 )
4)本文中的通知与横切逻辑等价;其中 Advice通知是spring aop对横切逻辑的抽象;
【3.1】 per-class类型的Advice通知(非引入型通知)
1) per-class Advice:该advice 可以在目标对象类的所有实例共享 (即Advice切面逻辑一经织入,则会对这个类的所有bean进行拦截);
2)常用的Advice列表:
- BeforeAdvice:前置通知或前置横切逻辑;
- ThrowsAdvice:运行时异常处理横切逻辑; (后置)
- AfterReturningAdvice:正常返回横切逻辑;(后置)
- AroundAdvice:环绕横切逻辑(包括前置与后置)
【3.1.1】BeforeAdvice前置通知 (实现MethodBeforeAdvice接口)
1)BeforeAdvice:所实现的横切逻辑在切点之前执行;(前置通知);
2)在前置通知执行完成后, 程序执行流程会从切点处继续执行; 所有前置通知不会打断程序执行流程;
3)自定义前置通知: 实现 MethodBeforeAdvice 接口 或者BeforeAdvice接口 ; 而 MethodBeforeAdvice 继承了 BeforeAdvice接口;
【MethodBeforeAdvice 】接口定义
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
public interface BeforeAdvice extends Advice {
}
public interface Advice {
}
【BeforeAdviceImpl】自定义前置通知,实现MethodBeforeAdvice接口,新建资源
public class BeforeAdviceImpl implements MethodBeforeAdvice {
private Resource resource;
public BeforeAdviceImpl(Resource resource) {
this.resource = resource;
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
if (!resource.exists()) {
System.out.println("新建资源");
return ;
}
System.out.println("资源已存在,无需二次新建");
}
}
【3.1.2】ThrowsAdvice异常处理后置通知(实现ThrowsAdvice接口)
1) ThrowsAdvice:监控系统异常情况,以统一方式处理异常;
虽然 ThrowsAdvice 接口没有任何方法,但在实现ThrowsAdvice 接口时,需要遵循如下原则:
void afterThrowing([Method method, Object[] args, Object target], ThrowableSubclass)
其中 Method, args, target 不是必输的;method是代理方法, args是方法参数,target是目标对象;
2)接口定义:
public interface ThrowsAdvice extends AfterAdvice {
}
public interface AfterAdvice extends Advice {
}
3)在同一个ThrowsAdvice实现类中,可以实现多个重载的afterThrowing方法;框架将会使用反射来调用这些方法;
【ExceptionHandleThrowsAdviceImpl】 自定义异常处理后置通知,实现ThrowsAdvice接口
public class ExceptionHandleThrowsAdviceImpl implements ThrowsAdvice {
public void afterThrowing(Throwable t) {
// 普通异常处理逻辑
System.out.println("普通异常处理逻辑");
}
public void afterThrowing(RuntimeException e) {
// 运行时异常处理逻辑
System.out.println("运行时异常处理逻辑");
}
public void afterThrowing(Method method, Object[] args, Object target, ApplicationContextException e) {
// ApplicationContext应用程序异常
System.out.println("ApplicationContext应用程序异常");
}
}
【3.1.3】AfterReturningAdvice方法成功执行后置通知(实现AfterReturningAdvice)
1)AfterReturningAdvice:目标方法成功执行后,执行通知表示的横切逻辑;
注意:AfterReturningAdvice通知不能修改方法返回值; 若需要修改方法返回值,需要使用 AroundAdvice通知;
【AfterReturningAdviceImpl】自定义方法成功执行后置通知,实现AfterReturningAdvice接口
public class AfterReturningAdviceImpl implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
// 方法执行成功后发送通知
}
}
【3.1.4】AroundAdvice环绕通知(实现MethodInterceptor接口)
1)AroundAdvice环绕通知:在方法执行点(切点)的上文或下文或上下文织入(执行)的横切逻辑;
- AroundAdvice通知不是spring定义的, 而是 AOP Alliance(AOP联盟)定义的;
- AroundAdvice通知可以完成之前几个通知的功能(包括前置,后置通知) ;包括 BeforeAdvice, ThrowsAdvice,AfterReturningAdvice;
【MethodInterceptorImpl】自定义环绕通知, 实现 MethodInterceptor 接口;
public class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch stopWatch = new StopWatch();
try {
stopWatch.start();
return invocation.proceed();
} finally {
stopWatch.stop();
}
}
}
【注意】一定要调用 invocation.proceed()方法, 可以让程序继续沿着调用链传播 ;
3)为什么AroundAdvice通知可以完成之前几个通知的功能(包括前置,后置通知) ?
- 在 调用 invocation.proceed()方法之前织入横切逻辑, 就是前置通知;
- 在 调用 invocation.proceed()方法之后织入横切逻辑, 就是后置通知;
- 在 调用 invocation.proceed()方法异常处理中织入横切逻辑, 就是异常处理后置通知;
【3.1.5】例:织入收集请求日志通知(横切逻辑)到目标对象
上文介绍了切点位置表达式pointcut, 横切逻辑Advice, 还需要定义切面封装pointcut和advice,及需要织入器把切面织入到目标对象; 本文提前剧透,即使用DefaultPointcutAdvisor作为切面抽象类,使用 ProxyFactory 作为织入器,代码示例如下。
【DefaultPointcutAdvisorMain】织入示例入口main ( 目标对象类 ManNoItfCallTask )
public class DefaultPointcutAdvisorMain {
public static void main(String[] args) {
// 构建匹配切点位置的表达式
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("call");
// 构建横切逻辑
Advice beforeAdvice = new MethodRequestLogBeforeAdviceImpl();
// 组装切面
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);
// 新建织入器,并装配目标对象和切面
ManNoItfCallTask target = new ManNoItfCallTask();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvisor(advisor);
// 织入器织入并获取代理对象
ManNoItfCallTask proxy = (ManNoItfCallTask) proxyFactory.getProxy();
proxy.call(BusiMessage.build("task001", "您有待办任务需要处理"));
}
}
【MethodRequestLogBeforeAdviceImpl】前置通知实现 (收集方法请求日志)
public class MethodRequestLogBeforeAdviceImpl implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.printf("MethodBeforeAdvice#before():请求日志,方法名=[%s]\n", method.getName());
}
}
【ManNoItfCallTask】业务方法:拨打电话 ( 没有实现接口 )
public class ManNoItfCallTask {
public void call(BusiMessage message) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("抛出异常");
throw new RuntimeException(e);
}
System.out.println("人工拨打电话#call(): " + message);
}
}
【打印日志】
MethodBeforeAdvice#before():请求日志,方法名=[call]
人工拨打电话#call(): BusiMessage{msgId='task001', msgText='您有待办任务需要处理'}
【3.2】per-instance类型的Advice通知
1) per-instance Advice:该advice 不可以在目标对象类的所有实例共享,而是会为不同实例对象保存它们各自的状态以及相关逻辑 (部分实例织入,部分实例不织入;并不是所有实例都织入通知);
2)spring aop中, Introduction Advice就是唯一的一种 per-instance Advice通知 ;
【3.2.1】Introduction Advice概述(引入型通知)
1)Introduction Advice可以在不改动目标类定义的情况下, 为目标类添加新的属性和行为;
- Introduction引入型通知: 在不改动目标类的情况下,为目标类引入新的属性和方法 ( 这是引入型通知的作用,即织入新方法和属性,织入动作不影响目标对象已有方法逻辑,本文理解为横向织入 ); 参见: https://blog.csdn.net/PacosonSWJTU/article/details/141369445
2)在spring aop中, 使用Introduction通知为目标对象添加新的属性和行为,要求目标对象类必须声明相应的接口及实现类;
- 再通过特定拦截器 【IntroductionInterceptor】 把接口实现类的新属性和方法织入到目标对象之上; 目标对象(或代理对象)就拥有了新的状态和行为;
- Introduction Advice引入型通知应用场景: 公办学校老师主要工作是在学校讲课, 方法为 teach(); 部分老师在课外培训学校兼职讲课,新接口为ITrainingSchoolTeacher,新方法为 trainAfterSchool() ; 即培训学校新接口及新方法作为引入型通知织入到公办学校老师目标对象,实现公办学校老师兼职课外培训的目的(注意:引入型通知是横向织入,织入动作不影响目标对象已有方法) ;
3)自定义 Introduction Advice,实现IntroductionInterceptor接口
public class IntroductionInterceptorImpl implements IntroductionInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before");
Object result = invocation.proceed();
System.out.println("after");
return result;
}
@Override
public boolean implementsInterface(Class<?> intf) {
return false;
}
}
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}
@FunctionalInterface
public interface MethodInterceptor extends Interceptor { // 方法拦截器(实现环绕通知的底层原理)
@Nullable
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}
public interface DynamicIntroductionAdvice extends Advice { // 动态引入型通知
boolean implementsInterface(Class<?> intf);
}
【小结】
简单理解:自定义引入型通知实现IntroductionInterceptor接口, 自定义非引入型通知(如BeforeAdvice等)实现 MethodInterceptor 接口; 其中 IntroductionInterceptor继承了 MethodInterceptor 接口;
4)Introduction引入型通知类图
5)自定义引入型通知,有3种方法:
- 实现IntroductionInterceptor接口;
- 继承 DelegatingIntroductionInterceptor
- 继承 DelegatingPerTargetObjectIntroductionInterceptor
6)引入型通知织入时,不管是静态还是动态,是为目标对象类型添加新方法,是对象级别的aop,而不是方法级别的aop,所以不需要传入pointcut,仅传入advice到织入器即可 ;
【3.2.2】静态引入型通知实现类-DelegatingIntroductionInterceptor
1)DelegatingIntroductionInterceptor 不会自己实现添加到目标对象的新逻辑(横切逻辑),而是委派给横切逻辑实现类delegate;
2)DelegatingIntroductionInterceptor 称为静态引入型通知:因为 DelegatingIntroductionInterceptor 始终会持有同一个 delegate实例(Introduction通知实现类对象),供同一个目标类的所有实例(目标对象)共享使用; (静态指的仅有一个delegate委派实例, delegate委派实例如 TrainingSchoolTeacherIntroducationInterceptorImpl ,TrainingSchoolTeacherImpl,参见下文 )
- 若目标对象新方法的每次调用都生成一个委派接口实现类实例,需要使用 DelegatingPerTargetObjectIntroductionInterceptor;
3)静态引入型通知应用场景: 公办学校老师PublicSchoolTeacher本职工作是在学校上课, 但部分老师会在课外培训学校兼职培训老师;具体实现是仅给兼职课外培训的公办学校老师织入课外辅导横切逻辑(实现自定义接口ITrainingSchoolTeacher);
4)例:使用静态引入型通知实现为公办学校老师PublicSchoolTeacher织入兼职课外培训功能(具体实现是trainAfterSchool方法)。
- 简单理解:兼职课外培训逻辑就是通知,就是横切逻辑,通知接口为ITrainingSchoolTeacher)
【StaticIntroductionAdviceMain】静态引入通知测试入口main
public class StaticIntroductionAdviceMain {
public static void main(String[] args) {
// 新建织入器
PublicSchoolTeacher target = new PublicSchoolTeacher();
ProxyFactory weaver = new ProxyFactory(target);
// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)
weaver.setProxyTargetClass(true);
// 设置引入型横切逻辑(新增逻辑)的接口 ITrainingSchoolTeacher, 新建引入型横切逻辑
// 引入型横切逻辑继承了 DelegatingIntroductionInterceptor, 实现了接口 ITrainingSchoolTeacher
weaver.setInterfaces(ITrainingSchoolTeacher.class);
TrainingSchoolTeacherIntroducationInterceptorImpl advice = new TrainingSchoolTeacherIntroducationInterceptorImpl();
// 织入器装配引入型横切逻辑
weaver.addAdvice(advice);
// 织入器织入并获取代理对象
Object proxy = weaver.getProxy();
((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法
((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法
}
}
【PublicSchoolTeacher】 公共学校老师,目标对象类, 没有实现接口,所以使用CGLIB实现动态代理
public class PublicSchoolTeacher {
public void teach() {
System.out.println("PublicSchoolTeacherImpl#teach(): 公办学校老师:课堂教学");
}
}
【ITrainingSchoolTeacher】培训学校老师接口,因为引入型通知或横切逻辑必须要声明新接口(ITrainingSchoolTeacher)及实现类
public interface ITrainingSchoolTeacher {
void trainAfterSchool();
}
【TrainingSchoolTeacherIntroducationInterceptorImpl】培训学校老师实现类 (自定义引入型通知接口实现类)
该类既实现了自定义接口ITrainingSchoolTeacher,还继承了 DelegatingIntroductionInterceptor静态引入型通知; Delegate顾名思义说明,DelegatingIntroductionInterceptor并不会自己实现横切逻辑,而是委派给具体的横切逻辑对应新接口ITrainingSchoolTeacher的实现类TrainingSchoolTeacherIntroducationInterceptorImpl;
public class TrainingSchoolTeacherIntroducationInterceptorImpl extends DelegatingIntroductionInterceptor implements ITrainingSchoolTeacher {
@Override
public void trainAfterSchool() {
System.out.println("TrainingSchoolTeacherImpl#trainAfterSchool: 兼职课后培训老师,辅导课后作业");
}
}
【打印日志】
PublicSchoolTeacherImpl#teach(): 公办学校老师:课堂教学
TrainingSchoolTeacherImpl#trainAfterSchool: 兼职课后培训老师,辅导课后作业
【补充1】
StaticIntroductionAdviceMain中, 引入型横切逻辑TrainingSchoolTeacherIntroducationInterceptorImpl 继承了 DelegatingIntroductionInterceptor, 实现了 ITrainingSchoolTeacher接口;
此外,引入型横切逻辑还可以不用继承 DelegatingIntroductionInterceptor,仅实现 ITrainingSchoolTeacher接口 ;把引入型横切逻辑作为DelegatingIntroductionInterceptor构造器参数传入;
【StaticIntroductionAdviceMain2】
public class StaticIntroductionAdviceMain2 {
public static void main(String[] args) {
// 新建织入器
PublicSchoolTeacher target = new PublicSchoolTeacher();
ProxyFactory weaver = new ProxyFactory(target);
// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)
weaver.setProxyTargetClass(true);
// 设置引入型横切逻辑(新增逻辑)的接口 新建引入型横切逻辑
// 引入型横切逻辑实现了接口 ITrainingSchoolTeacher,作为DelegatingIntroductionInterceptor构造器参数传入
weaver.setInterfaces(ITrainingSchoolTeacher.class);
TrainingSchoolTeacherImpl delegate = new TrainingSchoolTeacherImpl();
DelegatingIntroductionInterceptor advice = new DelegatingIntroductionInterceptor(delegate);
// 织入器装配引入型横切逻辑
weaver.addAdvice(advice);
// 织入器织入并获取代理对象
Object proxy = weaver.getProxy();
((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法
((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法
}
}
【补充2】 织入横切逻辑:weaver.addAdvice(advice) 底层实现
public void addAdvice(Advice advice) throws AopConfigException {
int pos = this.advisors.size();
this.addAdvice(pos, advice);
}
public void addAdvice(int pos, Advice advice) throws AopConfigException {
Assert.notNull(advice, "Advice must not be null");
if (advice instanceof IntroductionInfo introductionInfo) {
this.addAdvisor(pos, new DefaultIntroductionAdvisor(advice, introductionInfo));
} else {
if (advice instanceof DynamicIntroductionAdvice) {
throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
}
this.addAdvisor(pos, new DefaultPointcutAdvisor(advice));
}
}
虽然我们传入了通知给织入器,但织入器本身新建了引入型切面 DefaultIntroductionAdvisor (默认引入型切面),用于装配通知 ;
【3.2.3】动态引入型通知实现类-DelegatingPerTargetObjectIntroductionInterceptor
1)DelegatingPerTargetObjectIntroductionInterceptor: 动态引入型通知实现类;该类会在内部维护目标对象与横切逻辑接口实现类间的映射关系;(例:目标对象target, 横切逻辑接口ITrainingSchoolTeacher,接口实现类TrainingSchoolTeacherImpl)
- 当每个目标对象的横切逻辑接口上的方法被调用时, DelegatingPerTargetObjectIntroductionInterceptor 会拦截这些调用, 然后以目标对象作为键,到映射关系查找横切逻辑接口的实现类实例(如key=target, value=delegate); 然后把方法调用委派给实现类实例delegate去执行;
2)DelegatingPerTargetObjectIntroductionInterceptor 与 DelegatingIntroductionInterceptor区别:
DelegatingPerTargetObjectIntroductionInterceptor 只有一个构造器如下。需要传入通知实现类class和通知接口class。
【DelegatePerTargetObjectIntroductionInterceptor】
public class DelegatePerTargetObjectIntroductionInterceptor extends IntroductionInfoSupport implements IntroductionInterceptor {
private final Map<Object, Object> delegateMap = new WeakHashMap();
private final Class<?> defaultImplType;
private final Class<?> interfaceType;
public DelegatePerTargetObjectIntroductionInterceptor(Class<?> defaultImplType, Class<?> interfaceType) {
this.defaultImplType = defaultImplType;
this.interfaceType = interfaceType;
Object delegate = this.createNewDelegate();
this.implementInterfacesOnObject(delegate);
this.suppressInterface(IntroductionInterceptor.class);
this.suppressInterface(DynamicIntroductionAdvice.class);
}
// ......
3)例:使用动态引入型通知实现为公办学校老师PublicSchoolTeacher织入兼职课外培训功能(trainAfterSchool方法)
【DynamicIntroductionAdviceMain】动态引入型通知测试入口main
public class DynamicIntroductionAdviceMain {
public static void main(String[] args) {
// 新建目标对象
PublicSchoolTeacher target = new PublicSchoolTeacher();
// 新建织入器
ProxyFactory weaver = new ProxyFactory(target);
// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)
weaver.setProxyTargetClass(true);
// 设置动态引入型通知横切逻辑的接口, 织入器装配动态引入型通知
weaver.setInterfaces(ITrainingSchoolTeacher.class);
DelegatePerTargetObjectIntroductionInterceptor delegateIntroductionAdvice =
new DelegatePerTargetObjectIntroductionInterceptor(TrainingSchoolTeacherImpl.class, ITrainingSchoolTeacher.class);
weaver.addAdvice(delegateIntroductionAdvice);
// 织入器织入并获取代理对象
Object proxy = weaver.getProxy();
((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法
((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法
}
}
【打印日志】
PublicSchoolTeacherImpl#teach(): 公办学校老师:课堂教学
TrainingSchoolTeacherImpl#trainAfterSchool: 兼职课后培训老师,辅导课后作业
【3.2.4】 引入型通知性能:
1)性能:spring aop 通过JDK动态代理或CGLIB动态代理实现的 引入型通知,因为是运行时织入, 其性能比AspectJ版本的引入型通知逊色不少;
【3.3】Advice通知总结(非常重要)
1)Advice按照其自身能否在目标对象类的所有实例中共享, Advice划分为两类,包括 per-class 和 per-instance
- per-class Advice:该advice 可以在目标对象类的所有实例共享; 即一个Advice对应一个目标类;
- per-instance Advice:该advice 不可以在目标对象类的所有实例共享; 即一个Advice对应一个目标对象;
2)per-class通知:
- 横向织入: 织入横切逻辑到目标对象已有方法上下文;织入动作影响已有方法;
- 方法级别织入:针对目标对象的方法织入横切逻辑(通知); 织入动作传入 pointcut和advice 到织入器;
- 举例: BeforeAdvice, ThrowsAdvice,AfterReturningAdvice, AroundAdvice等;
3)per-instance通知:
- 纵向织入: 织入横切逻辑到目标对象上,横切逻辑定义在新接口上;织入动作不影响已有方法;
- 对象级别织入:针对目标对象织入横切逻辑(通知); 织入动作仅传入 advice 到织入器;无需传入pointcut,因为不需要匹配需要织入的方法;
- 举例: 只有一种类型,即Introduction引入型通知,实现类包括 静态引入型通知实现类-DelegatingIntroductionInterceptor, 动态引入型通知实现类-DelegatingPerTargetObjectIntroductionInterceptor ;
【4】spring aop中的Aspect切面
1)Advisor:spring对切面Aspect的抽象;
2)Aspect定义中可以有多个 Pointcut和多个 Advice;
- 但Advisor 通常只持有一个 Pointcut和 一个Advice ;
3)Advisor分类;
- PointcutAdvisor; 非引入型切面;
- IntroductionAdvisor;引入型切面;
【4.1】 PointcutAdvisor切面
1)PointcutAdvisor接口: 是对切面的抽象;其仅定义一个Pointcut和一个Advice ;其实现类如下:
- DefaultPointcutAdvisor;最通用;传入 pointcut 和 advice 2个参数;
- NameMatchMethodPointcutAdvisor;固定使用 NameMatchMethodPointcut(通过名字匹配), 仅可以传入advice参数;
- RegexpMatchMethodPointcutAdvisor;固定使用 AbstractRegexpMethodPointcut实现类(通过正则表达式匹配), 仅可以传入advice参数;
- AbstractRegexpMethodPointcut有2个实现类,包括 JdkRegexpMethodPointcut(默认), Perl5RegexpMethodPointcut (需要手工设置);
- DefaultlBeanFactoryPointcutAdvisor;可以通过容器中的Advice注册的beanName来关联对应的Advice; 只有当对应的Pointcut匹配成功后, 才会去实例化对应的Advice,减少容器启动初期Advisor与Advice之间的耦合性;
【DefaultPointcutAdvisorMain】默认pointcut切面测试main (详情参见上文的 DefaultPointcutAdvisorMain )
public class DefaultPointcutAdvisorMain {
public static void main(String[] args) {
// 构建匹配切点位置的表达式
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("call");
// 构建横切逻辑
Advice beforeAdvice = new MethodRequestLogBeforeAdviceImpl();
// 组装切面
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);
// 新建织入器,并装配目标对象和切面
ManNoItfCallTask target = new ManNoItfCallTask();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvisor(advisor);
// 织入器织入并获取代理对象
ManNoItfCallTask proxy = (ManNoItfCallTask) proxyFactory.getProxy();
proxy.call(BusiMessage.build("task001", "您有待办任务需要处理"));
}
}
【4.2】IntroductionAdvisor切面
1)IntroductionAdvisor切面:只能应用到对象级别的拦截(即部分对象拦截织入,部分对象不拦截织入), 只能使用Introduction类型的Advice;
- 而 PointcutAdvisor切面可以使用任何类型 Pointcut,以及差不多任何类型的Advice ;
2)封装引入型切面: 使用 DefaultIntroductionAdvisor ;
3) 在设置织入器参数时, 可以向织入器传入通知,也可以传入切面;
- 其中: 织入器接收到通知后,底层会新建切面DefaultIntroductionAdvisor以封装通知; 参见 “【补充2】 织入横切逻辑:weaver.addAdvice(advice) 底层实现”;
【DefaultIntroductionAdvisorMain】 传入引入型切面给织入器测试main
public class DefaultIntroductionAdvisorMain {
public static void main(String[] args) {
// 新建目标对象
PublicSchoolTeacher target = new PublicSchoolTeacher();
// 新建织入器
ProxyFactory weaver = new ProxyFactory(target);
// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)
weaver.setProxyTargetClass(true);
// 新建动态引入型通知
DelegatePerTargetObjectIntroductionInterceptor delegateIntroductionAdvice =
new DelegatePerTargetObjectIntroductionInterceptor(TrainingSchoolTeacherImpl.class, ITrainingSchoolTeacher.class);
// 新建引用型切面,封装引用型通知
DefaultIntroductionAdvisor defaultIntroductionAdvisor = new DefaultIntroductionAdvisor(delegateIntroductionAdvice);
// 织入器装配引入型切面
weaver.addAdvisor(defaultIntroductionAdvisor);
// 织入器织入切面并获取代理对象
Object proxy = weaver.getProxy();
((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法
((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法
}
}
【4.3】Ordered的作用(仅针对pre-class通知)
1)ordered:定义表示同一个切点(Joinpoint)的多个通知的执行顺序;
- 顺序号,值越小,优先级越高,越先执行;
- 仅针对per-class通知; 因为per-class通知是方法级别织入,才有Joinpoint切点;
2)DefaultPointcutAdvisor:已经实现了Ordered接口;调用 setOrder方法赋值即可;
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
public abstract class AbstractGenericPointcutAdvisor extends AbstractPointcutAdvisor {
public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordered, Serializable {