spring揭秘09-aop基本要素抽象与通知及切面织入

2024-08-22 10:36

本文主要是介绍spring揭秘09-aop基本要素抽象与通知及切面织入,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 【README】
  • 【1】spring aop中的Joinpoint切点
  • 【2】spring aop中的Pointcut描述切点的表达式
    • 【2.1】ClassFilter:根据class类型匹配
    • 【2.2】MethodMatcher:根据方法匹配
      • 【2.2.1】StaticMethodMatcher 静态方法匹配器
      • 【2.2.2】DynamicMethodMatcher 动态方法匹配器
    • 【2.3】常见Pointcut
      • 【2.3.1】NameMatchMethodPointcut: 仅根据名字匹配目标方法
      • 【2.3.2】JdkRegexpMethodPointcut: 根据正则表达式匹配目标方法
      • 【2.3.3】Perl5RegexpMethodPointcut: 根据正则表达式匹配目标方法
      • 【2.3.4】 AnnotaionMatchingPointcut:根据注解匹配目标类型与目标方法
      • 【2.3.5】ComposablePointcut:复合切点表达式
      • 【2.3.6】ControlFlowPointcut:控制流切点表达式
    • 【2.4】自定义Pointcut
  • 【3】spring aop中的Advice通知(横切逻辑)
    • 【3.1】 per-class类型的Advice通知(非引入型通知)
      • 【3.1.1】BeforeAdvice前置通知 (实现MethodBeforeAdvice接口)
      • 【3.1.2】ThrowsAdvice异常处理后置通知(实现ThrowsAdvice接口)
      • 【3.1.3】AfterReturningAdvice方法成功执行后置通知(实现AfterReturningAdvice)
      • 【3.1.4】AroundAdvice环绕通知(实现MethodInterceptor接口)
      • 【3.1.5】例:织入收集请求日志通知(横切逻辑)到目标对象
    • 【3.2】per-instance类型的Advice通知
      • 【3.2.1】Introduction Advice概述(引入型通知)
      • 【3.2.2】静态引入型通知实现类-DelegatingIntroductionInterceptor
      • 【3.2.3】动态引入型通知实现类-DelegatingPerTargetObjectIntroductionInterceptor
      • 【3.2.4】 引入型通知性能:
    • 【3.3】Advice通知总结(非常重要)
  • 【4】spring aop中的Aspect切面
    • 【4.1】 PointcutAdvisor切面
    • 【4.2】IntroductionAdvisor切面
    • 【4.3】Ordered的作用(仅针对pre-class通知)

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

1)AOP基本要素回顾:

  • Joinpoint切点:被织入横切逻辑的程序位置(执行点)
  • Pointcut切点表达式:描述切点位置的表达式;
  • Advice通知:横切逻辑(或切面逻辑);
  • Aspect切面:包含多个Pointcut切点表达式和Advice通知;
  • 织入器:把切面逻辑织入到切点上下文的对象;

2)本文介绍了spring对上述aop基本要素的抽象(接口与具体实现)
3)在基本要素抽象基础上,本文总结了通知与切面织入到目标类或目标对象的代码实现;


【1】spring aop中的Joinpoint切点

1)Joinpoint 切点: 被织入逻辑的程序位置(执行点) ;

public interface Joinpoint {@NullableObject proceed() throws Throwable;@NullableObject getThis();@NonnullAccessibleObject 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 {@Overridepublic 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 {@Overridepublic boolean matches(Method method, Class<?> targetClass) {return "send".equals(method.getName());}@Overridepublic boolean isRuntime() {return false;}@Overridepublic 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 {@Overridepublic boolean matches(Method method, Class<?> targetClass) {return "send".equals(method.getName());}
}

2)自定义动态方法匹配器Pointcut: 实现 DynamicMethodMatcherPointcut抽象类;

public class CustomDynamicMethodMatcherPointcut extends DynamicMethodMatcherPointcut {@Overridepublic 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;}@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("before");Object result = invocation.proceed();System.out.println("after");return result;}@Overridepublic boolean implementsInterface(Class<?> intf) {return false;}
}
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}@FunctionalInterface
public interface MethodInterceptor extends Interceptor { // 方法拦截器(实现环绕通知的底层原理)@NullableObject 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, 实现了接口 ITrainingSchoolTeacherweaver.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 {@Overridepublic 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 {


这篇关于spring揭秘09-aop基本要素抽象与通知及切面织入的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1096033

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听