2021年7月30日星期五

深入了解 Spring AOP

一、AOP的简单介绍

1.1 什么是AOP

AOP的全称是Aspect Oriented Programming,即面向切面编程,AOP是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP 是一种编程思想,是面向对象编程(OOP)的一种延续和补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面(Aspect)。


如图所示,AOP采取横向抽取机制,将一整套功能代码抽离出来,形成切面(Aspect),然后通过拦截等方式穿插到对象中去执行。

AOP相关的术语:

  • Aspect(切面):封装用于横向插入的系统功能(如事务、日志等)的类
  • Joinpoint(连接点):在程序执行过程中的某个阶段点,在Spring中通常指方法
  • Pointcut(切入点):切面与程序流程的交叉点,即用于匹配那些需要处理的连接点
  • Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码
  • Target Object(目标对象):指所有被通知的对象,也被称为被增强对象
  • Proxy(代理):将通知应用到目标对象之后,被动态创建的对象,即被增强对象之后的对象
  • Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程
  • Introduction(引入):允许我们向现有的类添加新的方法或者属性

1.2 为什么需要AOP

在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。

虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。

而AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。

AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。

1.3 AOP的实现方式

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。在 Java 中通常是通过预编译(静态代理)或运行期间动态代理来实现 AOP 。

静态代理:静态代理是指 AOP 框架在编译阶段对程序的源代码进行了修改,然后生成静态的 AOP 代理类,这个过程需要使用特定的编译器来修改源代码,代表框架有 AspectJ等。

动态代理: 动态代理是指 AOP 框架在运行阶段对需要增强的目标对象进行修改,然后动态的生成代理对象,代表框架有 Spring AOP。

通常情况下,静态代理的性能远比动态代理的性能要好一点。

二、Spring AOP的简单介绍

2.1 Spring AOP 的特点

Spring AOP 是纯 Java 实现的,并且无编译时特殊处理,也不修改和控制 ClassLoader。

在 Spring AOP 中动态代理的实现方式可分为 JDK 动态代理和 CGLIB 字节码提升,JDK 动态代理只能对接口进行代理,而 CGLIB 字节码提升对类也是可以进行代理的。

Spring AOP 与 AspectJ 相比,Spring AOP 只实现了部分的 AOP 功能,而 AspectJ 完整的实现了AOP的所有功能。Spring AOP 仅支持方法级别的连接点,而 AspectJ 还支持字段级别和构造器级别的连接点。

在 Spring AOP 中,可以使用 AspectJ 的一些相关注解,并使用 AspectJ 来做切入点的解析和匹配,但 Spring AOP 的实现方式还是动态代理,并不依赖于AspectJ 的编译器或者织入器。

2.2 Joinpoint

Joinpoint(连接点) 在 AOP 中是指在程序执行过程中的某个阶段点,简而言之就是指类中的方法、构造器、字段。


Joinpoint 接口表示一个通用的运行时连接点,是具有一定业务逻辑的,其接口定义如下:

public interface Joinpoint { // 执行连接点的业务逻辑,并进入下一个拦截器	Object proceed() throws Throwable;	Object getThis();	AccessibleObject getStaticPart();}

Invocation 接口表示对程序的调用。对于 AOP 而言,是通过拦截程序中连接点,然后对连接点进行增强,但增强的阶段可能发生在连接点之前,也可能发生在连接点之后,所以对连接点原有的业务逻辑进行调用也是至关重要的。其接口定义如下:

public interface Invocation extends Joinpoint { // 返回调用的参数	Object[] getArguments();}

MethodInvocation 接口表示对方法的调用,由于 Spring AOP 仅支持方法级别的连接点,所以 Spring 对 AOP 的实现基本都是基于 MethodInvocation 的。其接口的定义如下:

public interface MethodInvocation extends Invocation { // 被调用的方法	Method getMethod();}

ReflectiveMethodInvocation 和 CglibMethodInvocation 都是 MethodInvocation 的子类,ReflectiveMethodInvocation 是 Spring 基于 JDK 动态代理实现的 Joinpoint ,而 CglibMethodInvocation 是Spring 基于 CGLIB 实现的 Joinpoint。

2.3 Pointcut

Pointcut(切入点)在 AOP 中是指切面与程序流程的交叉点,即用于匹配那些需要增强的连接点,简而言之就是充当过滤器的角色。


Pointcut 接口是 Spring AOP 中切入点的抽象,由ClassFilter和MethodMatcher组成,其接口定义如下:

public interface Pointcut { 	ClassFilter getClassFilter();	MethodMatcher getMethodMatcher();	Pointcut TRUE = TruePointcut.INSTANCE;}

ClassFilter 接口表示限制 Pointcut (切入点)或Introduction(引入)与给定目标类集的匹配的过滤器,ClassFilter 中定义了用于匹配类的逻辑条件,其接口定义如下:

public interface ClassFilter { // 匹配的逻辑条件	boolean matches(Class<?> clazz);	ClassFilter TRUE = TrueClassFilter.INSTANCE;}

MethodMatcher 接口用于检查目标方法是否符合通知的条件 ,MethodMatcher 中定义了了用于匹配方法的逻辑条件,其接口定义如下:

public interface MethodMatcher { // 匹配的逻辑条件,静态检查,AspectJ	boolean matches(Method method, Class<?> targetClass);  // 是否是运行时检查	boolean isRuntime(); // 匹配的逻辑条件,动态检查,Spring AOP	boolean matches(Method method, Class<?> targetClass, Object... args);	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;}

Pointcut 接口的实现类有很多,但最为常见的实现类是 AspectJExpressionPointcut,AspectJExpressionPointcut 类会对 @Pointcut 注解或者 <aop:pointcut> 标签中定义的表达式进行解析,然后来匹配连接点。

2.4 Advice

Advice(通知/增强处理)表示 AOP 框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。


Advice 接口是 Spring AOP 中通知的抽象,是一个标记接口,其定义如下:

public interface Advice {}

Interceptor 接口表示一个通用的拦截器,继承了 Advice 接口,相当于一个标记接口,其定义如下:

public interface Interceptor extends Advice {}

MethodInterceptor 接口是 Interceptor 接口的子类,在 Spring AOP 内部,Advice 的子接口(如:BeforeAdvice、AfterAdvice等)都会转换成 MethodInterceptor 来使用,其接口定义如下:

public interface MethodInterceptor extends Interceptor { // 在执行连接点的业务逻辑( Joinpoint.proceed() )前后,进行额外的处理	Object invoke(MethodInvocation invocation) throws Throwable;}

在 AOP 中根据 Advice 在 Jointpoint 的位置,可将 Advice 分为5种类型,分别为:

  • 前置通知:在目标方法执行前实施增强,对应实现是 BeforeAdvice
  • 后置通知:在目标方法执行成功后实施增强,对应实现是 AfterReturningAdvice
  • 最终通知:在目标方法完全执行完后实施增强,对应实现是 AfterAdvice
  • 环绕通知:在目标方法执行前后实施增强,在 Spring AOP 中并没有独立的实现,通常是通过 MethodIntercepto 来实现的
  • 异常抛出通知:在方法抛出异常后实施增强,ThrowsAdvice

2.5 AopProxy

AopProxy 接口用于创建代理对象,其接口定义为:

public interface AopProxy { 	Object getProxy();	Object getProxy(@Nullable ClassLoader classLoader);}

AopProxy 接口有两种类型的实现,一种是基于 JDK 动态代理实现的 JdkDynamicAopProxy,另一种是基于 CGLIB 字节码提升实现的 CglibAopProxy 和 ObjenesisCglibAopProxy。

AopProxyFactory 接口是 AopProxy 的抽象接口工厂,它会根据 AdvisedSupport(AOP的配置类)创建 AopProxy 接口的实现类,其接口定义为:

public interface AopProxyFactory {	AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;}

AopProxyFactory 接口的默认的实现类是 DefaultAopProxyFactory。

2.6 TargetSource

TargetSource 接口用于获取AOP调用的目标对象,其接口定义如下:

public interface TargetSource extends TargetClassAware {	Class<?> getTargetClass();	boolean isStatic();	Object getTarget() throws Exception; 	void releaseTarget(Object target) throws Exception;}

常见的实现类有:

  • HotSwappableTargetSource:允许热交换的 TargetSource 实现
  • SingletonTargetSource:单例的 TargetSource 实现,是 TargetSource 的默认实现
  • AbstractPoolingTargetSource:基于池(比如:CommonsPool )技术实现的 TargetSource
  • ThreadLocalTargetSource:基于 ThreadLocal 技术实现的 TargetSource
  • PrototypeTargetSource:基于 IoC 容器技术实现的 TargetSource

其中 AbstractPoolingTargetSource、ThreadLocalTargetSource、PrototypeTargetSource 都是AbstractPrototypeBasedTargetSource的子类,是原型的 TargetSource 实现。

2.7 Advisor

Advisor 接口是 Advice 的连接器,用于连接 Advice 和 Pointcut 或者连接 Advice 和 Introduction,其接口定义如下:

public interface Advisor {	Advice EMPTY_ADVICE = new Advice() {};	Advice getAdvice();	boolean isPerInstance();}

常见的实现类有:


Advisor 的作用类似于 AOP 中 Aspect,它们都是封装用于横向插入的系统功能(Advice)。

2.8 AdvisorAdapter

AdvisorAdapter 接口用于将指定的 Advice 转换为 Interceptor,其接口定义为:

public interface AdvisorAdapter {	boolean supportsAdvice(Advice advice);	MethodInterceptor getInterceptor(Advisor advisor);}

常见的实现类有:

  • MethodBeforeAdviceAdapter:用于将 MethodBeforeAdvice 转换成 MethodInterceptor
  • ThrowsAdviceAdapter:用于将 ThrowsAdvice 转换成 MethodInterceptor
  • AfterReturningAdviceAdapter:用于将 AfterReturningAdvice 转换成 MethodInterceptor

AdvisorAdapterRegistry 接口是 AdvisorAdapter 的注册中心,其接口定义为:

public interface AdvisorAdapterRegistry {	Advisor wrap(Object advice) throws UnknownAdviceTypeException;	MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException;	void registerAdvisorAdapter(AdvisorAdapter adapter);}

AdvisorAdapterRegistry 接口的默认实现是 DefaultAdvisorAdapterRegistry,AdvisorAdapterRegistry 可以根据给定的的 Advisor 获取对应的 MethodInterceptor。

三、Spring AOP的简单使用

3.1 通过注解声明切入点

@Pointcut 注解用于声明一个切入点,标注在方法上面,其注解定义:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Pointcut { // 切入点表达式 String value() default "";  String argNames() default "";}

切入点表达式的内容:

AspectJ 指示器描述
arg()限制连接点匹配参数为指定类型
@args()限制连接点匹配参数由指定注解标注
execution()匹配连接点的表达式
this()限制连接点匹配 AOP 代理的 Bean 引用为指定类型的类
target()限制连接点匹配目标对象为指定类型的类
@target()限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
within()限制连接点匹配指定的类型
@withi限制连接点匹配指定注解所标注的类型
@annotation限制匹配带有指定注解的连接点

execution 指示器中表达式的格式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

括号中各个pattern分别表示:

  • modifier-pattern:修饰符匹配,如:public
  • ret-type-pattern:返回值匹配,可以用 * 表示任何返回值
  • declaring-type-pattern:类路径匹配
  • name-pattern:方法名匹配,可以指定方法名,或者用 * 指代所有的方法,set* 代表以所有以set开头的方法
  • param-pattern:参数匹配,可以指定具体的参数类型,多个参数间用,隔开,也可以用 * 来表示匹配任意类型的参数。如:(String)表示匹配一个 String 类型参数的方法、(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是 String 类型、(..)表示零个或多个任意参数类型
  • throws-pattern:异常类型匹配
  • 其中后面跟着 ? 的是可选项

比如:匹配 com.wlp包及其子包中所有的公有方法

@Pointcut(execution(public * com.wlp..*.*(..))) 

类路径与方法名连接处用.连接

多个指示器可以用&&||来表示"且"、"或"、"非"的关系,但是在

3.2 通过注解声明通知

通过前面的介绍,可以知道在 Spring AOP 中有5种通知类型,它们同样可以通过注解来声明,对应的注解分别为:

  • 前置通知:@Before
  • 后置通知:@AfterReturning
  • 最终通知:@After
  • 环绕通知:@Around
  • 异常抛出通知:@AfterThrowing

简单示例:

// 切入点,匹配 com.wulianpu.spring.aop包及其子包下所有类的所有公开方法@Pointcut("execution(public * com.wulianpu.spring.aop..*.*(..))")public void pointcut() {}// 前置通知@Before("pointcut()")public void before(JoinPoint joinPoint) { System.out.printf("[前置通知]准备运行长征九号运载火箭的 %s 程序完成火箭发射\n", joinPoint.getSignature().getName());}// 环绕通知@Around("pointcut()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.printf("[环绕通知]北京时间:%s,准备发射长征九号运载火箭\n", LocalDateTime.now(ZoneId.of("Asia/Shanghai"))); Object proceed = proceedingJoinPoint.proceed(); System.out.printf("[环绕通知]北京时间:%s,长征九号运载火箭发射成功\n", LocalDateTime.now(ZoneId.of("Asia/Shanghai"))); return proceed;}// 后置通知@AfterReturning(value = "pointcut()", returning = "returnValue")public void afterReturning(JoinPoint joinPoint, Object returnValue) { System.out.printf("[后置通知]长征九号运载火箭的 %s 程序已经执行完成,返回值为:%s\n", joinPoint.getSignature().getName(), returnValue);}// 异常通知@AfterThrowing(value = "pointcut()", throwing = "exception")public void afterThrowing(JoinPoint joinPoint, Throwable exception) { System.out.printf("[异常通知]长征九号运载火箭的 %s 程序在执行中发生了异常:%s\n", joinPoint.getSignature().getName(), exception.getMessage());}// 最终通知@After("pointcut()")public void after(JoinPoint joinPoint) { System.out.println("[最终通知]长征九号运载火箭发射成功");}

3.3 通过注解声明自动代理

在 Spring 中可以通过 @EnableAspectJAutoProxy 注解开启 Spring AOP自动代理。开启后,Spring 会自动扫描IoC 容器中定义的 Pointcut、Advice等,来完成自动代理。

@EnableAspectJAutoProxypublic class SpringAopDemo { public static void main(String[] args) {  AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();  // 将指定的类注册为配置类  applicationContext.register(SpringAopDemo.class, AspectConfig.class);  // 启动 Spring 上下文  applicationContext.refresh();    // 获取的对象  SpringAopDemo aspectDemo = applicationContext.getBean(SpringAopDemo.class);  aspectDemo.launch();    // 关闭 Spring 上下文  applicationContext.close(); } public String launch() {  System.out.println("长征九号运载火箭发射程序执行中...");  return "执行完成"; } @Aspect static class AspectConfig {  // 切入点,匹配 com.wulianpu.spring.aop包及其子包下所有类的所有公开方法  @Pointcut("execution(public * com.wulianpu.spring.aop..*.*(..))")  public void pointcut() {  }  @Before("pointcut()")  public void before(JoinPoint joinPoint) {   System.out.printf("[前置通知]准备运行长征九号运载火箭的 %s 程序完成火箭发射\n", joinPoint.getSignature().getName());  }  @Around("pointcut()")  public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {   System.out.printf("[环绕通知]北京时间:%s,准备发射长征九号运载火箭\n", LocalDateTime.now(ZoneId.of("Asia/Shanghai")));   Object proceed = proceedingJoinPoint.proceed();   System.out.printf("[环绕通知]北京时间:%s,长征九号运载火箭发射成功\n", LocalDateTime.now(ZoneId.of("Asia/Shanghai")));   return proceed;  }  @AfterReturning(value = "pointcut()", returning = "returnValue")  public void afterReturning(JoinPoint joinPoint, Object returnValue) {   System.out.printf("[后置通知]长征九号运载火箭的 %s 程序已经执行完成,返回值为:%s\n", joinPoint.getSignature().getName(), returnValue);  }  @AfterThrowing(value = "pointcut()", throwing = "exception")  public void afterThrowing(JoinPoint joinPoint, Throwable exception) {   System.out.printf("[异常通知]长征九号运载火箭的 %s 程序在执行中发生了异常:%s\n", joinPoint.getSignature().getName(), exception.getMessage());  }  @After("pointcut()")  public void after(JoinPoint joinPoint) {   System.out.println("[最终通知]长征九号运载火箭发射成功");  } }}

运行结果:

[环绕通知]北京时间:2021-07-30T17:16:17.169697300,准备发射长征九号运载火箭[前置通知]准备运行长征九号运载火箭的 launch 程序完成火箭发射长征九号运载火箭发射程序执行中...[后置通知]长征九号运载火箭的 launch 程序已经执行完成,返回值为:执行完成[最终通知]长征九号运载火箭发射成功[环绕通知]北京时间:2021-07-30T17:16:17.189693300,长征九号运载火箭发射成功

对一个切面(Aspect)来说通知的执行顺序是固定,对于不同的切面(Aspect)可以将 @Order 注解标注到通知上或继承 Ordered 接口来改变顺序,其值越小优先级越高。

四、Spring AOP的深入了解

4.1 ProxyConfig

ProxyConfig 是 AOP 代理配置类的超类,其中定义一些基础的 AOP 代理配置,以确保所有代理创建者(AopProxy)具有一致的属性。


AdvisedSupport 是 ProxyConfig 的子类,同时也实现了 Advised 接口。Advised 接口中定义了获取TargetSource 和 Advisor 的方法,从 Spring 获得的任何 AOP 代理对象都可以转换到这个接口,以便对其支持 Advice 进行操作。

值得注的是,在 AdvisedSupport 中有一个这样的方法:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) { MethodCacheKey cacheKey = new MethodCacheKey(method); List<Object> cached = this.methodCache.get(cacheKey); if (cached == null) {  cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(   this, method, targetClass);  this.methodCache.put(cacheKey, cached); } return cached;}

这个方法可以根据给定连接点获取对应的 MethodInterceptor。这个方法又会去调用DefaultAdvisorChainFactory类的getInterceptorsAndDynamicInterceptionAdvice方法,其实现如下:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, @Nullable Class<?> targetClass) {  // 获取 AdvisorAdapterRegistry,用于将 Advisor 转换为 MethodInterceptor AdvisorAdapterRegistr......

原文转载:http://www.shaoqun.com/a/895454.html

跨境电商:https://www.ikjzd.com/

blibli:https://www.ikjzd.com/w/1676

livingsocial:https://www.ikjzd.com/w/714.html

marks spencer:https://www.ikjzd.com/w/2385


一、AOP的简单介绍1.1什么是AOPAOP的全称是AspectOrientedProgramming,即面向切面编程,AOP是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是一种编程思想,是面向对象编程(OOP)的一种延续和补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面(Aspect)。如图所示,AOP采取横向抽取机制,将一整套功能代
马莎:https://www.ikjzd.com/w/2385
trax:https://www.ikjzd.com/w/1489
威廉王子今娶凯特 新世纪"童话婚礼"登场 :http://www.30bags.com/a/410433.html
威马将"318国道"搬到厦门,足不出城,经历"披荆斩棘":http://www.30bags.com/a/224477.html
威马逊台风香港影响 威马逊香港路径图介绍:http://www.30bags.com/a/429173.html
威尼斯 狂欢全年无休 - :http://www.30bags.com/a/408011.html
妈妈说一周一次 妈妈用身体帮孩子度过青春期:http://lady.shaoqun.com/a/247594.html
把腿张开学长都给你 坐在学长的巨大上娇喘:http://www.30bags.com/m/a/249753.html
深圳CIPE授权展什么时候:http://www.30bags.com/a/520747.html
​快认领!10条旺季备战策略,教你体验旺季爆发的快感!:https://www.ikjzd.com/articles/147082
如何使用亚马逊MYCE电子邮件管理工具?:https://www.ikjzd.com/articles/147081
亚马逊9大站点开放促销,一大波流量涌来!:https://www.ikjzd.com/articles/147080

没有评论:

发表评论