深入理解Spring容器:从基础到原理(四十一)
一、引言
在我们对Spring容器中AOP实现的持续探索中,已经详细剖析了AnnotationAwareAspectJAutoProxyCreator
在获取增强器过程中的buildAspectJAdvisors
方法,了解了如何从Spring容器中扫描并提取AspectJ注解类的增强器。此刻,我们将深入研究增强器的具体实现,包括普通增强器的获取逻辑以及不同类型增强器(如前置增强器、后置增强器)的内部实现细节。理解这些内容对于全面掌握Spring AOP如何将切面逻辑准确地织入目标对象的方法调用中至关重要。通过深入解析增强器的实现,我们将能够更好地运用Spring AOP来实现精确、高效的横切关注点增强,提升应用程序的开发效率和质量。
二、普通增强器的获取:深入细节
(一)getAdvisor
方法的核心功能
-
在
AnnotationAwareAspectJAutoProxyCreator
获取增强器的过程中,getAdvisor
方法承担着从AspectJ注解方法中获取切点信息并生成相应增强器的关键任务。它是构建增强器的核心逻辑所在,通过解析方法上的注解,获取切点表达式等信息,然后根据这些信息创建合适的增强器实例,为后续将切面逻辑织入目标对象的方法调用做好准备。 -
例如,在一个包含多个切面和目标对象的Spring应用中,对于每个被
@Aspect
注解标记的类中的方法,getAdvisor
方法会被调用来分析方法上的注解,如@Before
、@After
等,确定切点信息,进而创建对应的增强器。假设我们有一个LoggingAspect
切面类,其中有一个@Before
注解的方法,getAdvisor
方法会解析该注解,获取切点表达式,创建一个前置增强器,用于在目标对象方法执行前执行日志记录逻辑。(二)切点信息的获取步骤
1. 查找注解并封装
-
首先,通过
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod
方法查找给定方法(candidateAdviceMethod
)上的AspectJ注解。该方法会在一组敏感的注解类(包括Before.class
、Around.class
、After.class
、AfterReturning.class
、AfterThrowing.class
、Pointcut.class
)中查找,并返回找到的第一个注解(使用AspectJAnnotation
封装)。如果没有找到任何注解,则返回null
。 -
例如,对于一个
@Before
注解的方法,findAspectJAnnotationOnMethod
会在该方法上找到Before
注解,并将其封装为AspectJAnnotation
对象。这个对象包含了注解的详细信息,如注解类型、注解属性等,为后续获取切点表达式提供了基础。2. 创建
AspectJExpressionPointcut
实例并设置表达式 -
如果找到了AspectJ注解,会创建一个
AspectJExpressionPointcut
实例(ajexp
),并将其与AspectJ注解类(candidateAspectClass
)和空的字符串数组、类数组关联起来。然后,通过AspectJAnnotation.getPointcutExpression
方法获取注解中的切点表达式,并使用ajexp.setExpression
方法将其设置到AspectJExpressionPointcut
实例中。 -
假设我们有一个
@Pointcut("execution(* com.example.ServiceImpl.*(..))")
注解,getPointcut
方法会创建一个AspectJExpressionPointcut
实例,并将切点表达式"execution(* com.example.ServiceImpl.*(..))"
设置到该实例中。这个AspectJExpressionPointcut
实例将用于后续判断目标对象的方法是否匹配该切点,从而决定是否应用切面逻辑。(三)根据切点信息生成增强器
-
在获取了切点信息后,会创建一个
InstantiationModelAwarePointcutAdvisorImpl
实例来封装增强器相关信息。这个实例包含了切面工厂(af
)、切点表达式(ajexp
)、切面实例工厂(aif
)、方法(method
)、声明顺序(declarationOrderInAspect
)和切面名称(aspectName
)等重要信息。这些信息将在增强器的执行过程中发挥关键作用,用于确定增强器的行为和应用时机。 -
例如,对于一个
ValidationAspect
切面类中的方法,getAdvisor
方法会根据该方法上的注解和相关信息创建一个InstantiationModelAwarePointcutAdvisorImpl
实例,其中包含了用于验证参数的逻辑、切点表达式以及切面实例工厂等信息。这个增强器将在目标对象的方法执行前,根据切点表达式判断是否需要进行参数验证,如果需要,则执行验证逻辑。(四)增强器的初始化与不同类型增强的处理
-
在
InstantiationModelAwarePointcutAdvisorImpl
的初始化过程中,还会根据切面实例工厂(aif
)的实例化策略(是否延迟初始化)对增强器进行进一步的处理。如果是延迟初始化(通过aif.getAspectMetadata().isLazilyInstantiated()
判断),会创建一个特殊的PerTargetInstantiationModelPointcut
实例作为切点,并标记增强器为延迟初始化(lazy = true
);如果不是延迟初始化,则立即实例化增强器(通过instantiateAdvice
方法),并将其标记为非延迟初始化(lazy = false
)。 -
在
instantiateAdvice
方法中,会根据注解类型(通过aspectJAnnotation.getAnnotationType()
获取)创建不同类型的增强器。例如,对于@Before
注解,会创建AspectJMethodBeforeAdvice
类型的增强器;对于@After
注解,会创建AspectJAfterAdvice
类型的增强器;对于@Around
注解,会创建AspectJAroundAdvice
类型的增强器等。这使得Spring AOP能够根据不同的注解类型,准确地实现相应的增强逻辑,如前置增强、后置增强、环绕增强等。(五)代码示例展示普通增强器获取过程
-
假设我们有一个简单的Spring应用,包含一个
UserService
接口和其实现类UserServiceImpl
,以及一个SecurityAspect
切面类用于权限验证。我们希望通过AOP在UserServiceImpl
的方法执行前进行权限验证。 -
首先,定义
UserService
接口:public interface UserService { void addUser(String username); void updateUser(String username); }
-
然后,实现
UserServiceImpl
类:import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("Adding user: " + username); } @Override public void updateUser(String username) { System.out.println("Updating user: " + username); } }
-
接着,定义
SecurityAspect
切面类:import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class SecurityAspect { @Before("execution(* com.example.UserServiceImpl.*(..))") public void checkPermissions() { System.out.println("Checking permissions"); } }
-
在Spring配置文件中,配置AOP相关信息(这里假设使用XML配置与注解配置混合的方式,仅展示关键部分):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframeworkframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframeworkframework.org/schema/aop" xsi:schemaLocation="http://www.springframeworkframework.org/schema/beans http://www.springframeworkframework.org/schema/beans/Spring-beans.xsd http://www.springframeworkframework.org/schema/aop http://www.springframeworkframework.org/schema/aop/Spring-aop.xsd"> <bean id="userService" class="com.example.UserServiceImpl" /> <bean id="securityAspect" class="com.example.SecurityAspect" /> <aop:aspectj-autoproxy /> </beans>
-
当Spring容器启动并执行到
getAdvisor
方法时(在buildAspectJAdvisors
方法中会调用getAdvisor
方法来获取SecurityAspect
切面类中方法的增强器):-
首先,在
getAdvisor
方法中,对于SecurityAspect
类中的checkPermissions
方法,会通过findAspectJAnnotationOnMethod
方法查找@Before
注解,并将其封装为AspectJAnnotation
对象。 -
然后,创建
AspectJExpressionPointcut
实例,获取@Before
注解中的切点表达式"execution(* com.example.UserServiceImpl.*(..))"
,并设置到AspectJExpressionPointcut
实例中。 -
接着,创建
InstantiationModelAwarePointcutAdvisorImpl
实例,将相关信息(如切面工厂、切点表达式、切面实例工厂、checkPermissions
方法、声明顺序和切面名称等)封装进去。由于默认情况下不是延迟初始化,会调用instantiateAdvice
方法。 -
在
instantiateAdvice
方法中,根据@Before
注解类型,创建AspectJMethodBeforeAdvice
类型的增强器。这个增强器包含了checkPermissions
方法的逻辑,以及用于判断目标对象方法是否匹配切点的AspectJExpressionPointcut
实例等信息。 -
最后,这个增强器将被添加到增强器列表中,当为
UserServiceImpl
创建AOP代理时,会将该增强器应用到代理对象中。在调用UserServiceImpl
的addUser
或updateUser
方法时,代理对象会首先执行SecurityAspect
中的checkPermissions
方法进行权限验证,如果验证通过,才会继续执行addUser
或updateUser
方法的实际业务逻辑。这展示了getAdvisor
方法如何从AspectJ注解方法中获取切点信息并生成相应的增强器,实现AOP的前置增强功能。三、不同类型增强器的内部实现:深入剖析
(一)
MethodBeforeAdviceInterceptor
:前置增强器的实现
-
-
MethodBeforeAdviceInterceptor
是实现前置增强逻辑的关键类,它实现了MethodInterceptor
接口,这使得它能够在目标对象方法调用的拦截链中发挥作用。在其构造函数中,接受一个MethodBeforeAdvice
类型的参数(通常是AspectJMethodBeforeAdvice
实例),并将其保存为属性。这个MethodBeforeAdvice
包含了前置增强的具体逻辑,即切面类中@Before
注解标记的方法的逻辑。 -
当
MethodBeforeAdviceInterceptor
的invoke
方法被调用时(在代理对象方法调用时触发),它首先会调用advice.before
方法(其中advice
是AspectJMethodBeforeAdvice
实例)。在before
方法中,会通过invokeAdviceMethod
方法调用增强方法,而invokeAdviceMethod
方法最终会调用invokeAdviceMethodWithGivenArgs
方法来执行实际的增强逻辑。在invokeAdviceMethodWithGivenArgs
方法中,会获取切面实例(通过aspectInstanceFactory.getAspectInstance()
),并使用反射机制调用aspectJAdviceMethod
(即@Before
注解标记的方法),将切面实例和方法参数(如果有)传递进去。这样,就实现了在目标对象方法执行前执行切面的前置增强逻辑。 -
例如,在上述
SecurityAspect
的例子中,当调用UserServiceImpl
的addUser
方法时,代理对象会调用MethodBeforeAdviceInterceptor
的invoke
方法。在invoke
方法中,先执行SecurityAspect
中的checkPermissions
方法进行权限验证,只有在权限验证通过后,才会继续调用addUser
方法的实际业务逻辑。这确保了在执行关键业务操作前,先进行必要的权限检查,增强了系统的安全性。(二)
AspectJAfterAdvice
:后置增强器的实现 -
AspectJAfterAdvice
用于实现后置增强逻辑,它直接实现了MethodInterceptor
和AfterAdvice
接口。在其构造函数中,接受AspectJ方法(aspectJBeforeAdviceMethod
)、切点表达式(pointcut
)和切面实例工厂(aif
)作为参数,并调用父类AbstractAspectJAdvice
的构造函数进行初始化。 -
当
AspectJAfterAdvice
的invoke
方法被调用时,它首先会调用mi.proceed()
方法来执行目标对象的方法,这确保了目标对象的业务逻辑先执行。然后,在finally
块中,会调用invokeAdviceMethod
方法来执行后置增强逻辑。这与前置增强器的执行顺序不同,前置增强器是在目标对象方法执行前执行增强逻辑,而后置增强器是在目标对象方法执行后执行。这种设计确保了切面逻辑在目标对象方法执行的前后都能得到正确的应用,实现了对目标对象方法的全面增强。 -
例如,假设我们在
UserService
中添加一个@After
注解的方法到SecurityAspect
中,用于记录方法执行后的一些信息。当调用UserServiceImpl
的addUser
方法时,代理对象会先执行addUser
方法的业务逻辑,然后在finally
块中执行SecurityAspect
中的后置增强方法,记录方法执行后的相关信息,如执行时间、执行结果等。这展示了AspectJAfterAdvice
如何实现后置增强逻辑,在目标对象方法执行后进行额外的处理。(三)增强器的多样性与灵活性
-
Spring AOP通过提供多种类型的增强器(如前置增强器、后置增强器、环绕增强器等),实现了对目标对象方法增强的多样性和灵活性。开发者可以根据不同的需求,选择合适的注解(如
@Before
、@After
、@Around
等)来定义切面方法,Spring AOP会根据注解类型创建相应的增强器,并将其织入到目标对象的方法调用中。这种机制使得在不修改目标对象源代码的情况下,能够轻松地为目标对象添加各种横切关注点的增强逻辑,如日志记录、事务管理、权限验证、性能监控等。 -
例如,在一个复杂的企业级应用中,可以使用
@Before
注解实现参数验证和权限检查,使用@After
注解记录方法执行结果和日志,使用@Around
注解实现事务管理等。通过灵活组合这些不同类型的增强器,可以构建出功能强大、易于维护的应用程序,提高代码的复用性和可扩展性。同时,这种基于注解的方式使得AOP的使用更加简洁和直观,降低了开发难度,提高了开发效率。四、总结与展望
通过对普通增强器获取逻辑以及不同类型增强器内部实现的深入剖析,我们全面了解了Spring AOP如何根据AspectJ注解创建和执行增强器,将切面逻辑准确地织入目标对象的方法调用中。从
getAdvisor
方法的切点信息获取和增强器生成,到MethodBeforeAdviceInterceptor
和AspectJAfterAdvice
等增强器的具体实现,每个环节都展示了Spring AOP在增强器实现方面的精细设计和强大功能。在后续的学习中,我们将继续探索Spring AOP的更多高级特性,如增强器的优先级控制、切面的执行顺序、AOP与其他Spring组件的集成细节以及在实际应用中的性能优化等。这些内容将进一步拓展我们对Spring AOP的理解和应用能力,使我们能够构建更加健壮、高效和灵活的Java应用程序。希望本文能够帮助码龄1 - 5年的程序员更好地理解Spring AOP的这些重要实现过程,为开发高质量的应用提供有力的技术支持。让我们共同期待下一次的深入探索之旅,继续挖掘Spring框架的无尽潜力。