秋天里的春天-spring笔记(三):AOP in Spring

本文详细介绍Spring框架中的面向切面编程(AOP)特性,包括基于注解、基于Schema及基于API三种方式的AOP支持,涵盖了切面、切入点、通知、引入等核心概念,并提供了丰富的配置示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1           基于注解的AOP

 

1.1          启用基于注解的AOP

l  Spring的配置中引入<aop:aspectj-autoproxy/>元素来启用Spring@AspectJ的支持。

<?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-2.5.xsd

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

 

    <aop:aspectj-autoproxy/>

</beans>

l  在应用程序的classpath中引入两个AspectJ库:aspectjweaver.jaraspectjrt.jar

 

l  使用DTD,可以通过在application context中添加如下定义来启用@AspectJ支持:

<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />

 

1.2          组织切面

通过给类加org.aspectj.lang.annotation.Aspect注解来标识该类为一个切面。注意,在Spring AOP中,拥有切面的类本身不可能是其它切面中通知的目标。一个类上面的@Aspect注解标识它为一个切面,并且从自动代理中排除它。

 

一个完整的切面由切点和通知组成,在类中给方法加org.aspectj.lang.annotation.Pointcut注解将使该方法成为一个切点。切点方法必须是一个返回值为void的方法。

切点由两部分组成:切点签名和切点表达式。切点签名即该方法的签名,而切点表达式由Pointcut注解来表示。

 

1.3          切入点指示符(PCD

 

分类

方法切点函数

execution()

方法匹配模式串

表示满足某一匹配模式的所有目标类方法连接点。

execution(* greetTo(..))表示所有目标类中的greetTo()方法,使用AspectJ切点表达式来匹配连接点

@annotation()

方法注解类名

表示标注了特定注解的目标方法连接点。如

@annotation(cn.wangjicn.anno.NeedTest)表示任何标注了@NeedTest注解的目标类方法,标注了某一注解的方法,包括子类上未被覆盖的方法

方法入参切点函数

args()

类名

通过判别目标类方法运行时入参对象的类型定义指定连接点。如args(cn.wangjicn.Waiter)表示所有有且仅有一个按类型匹配于Waiter入参的方法,运行时实参为某一类型的方法

@args

类型注解类名

通过判别目标方法运行时入参对象的类是否标注特定注解来指定连接点。

@args(cn.wangjicn.Monitorable)表示任何这样的一个目标方法:它有一个入参且入参对象的类标注@Monitorable注解,运行时实参类型上带有某一注解的方法

目标类切点函数

within()

类名匹配串

表示特定域下的所有连接点。如

within(cn.wangjicn.service.*)表示cn.wangjicn.service包中的所有连接点, 即包中所有类的所有方法,

within(cn.wangjicn.service.*Service)

表示在cn.wangjicn.service包中所有以Service结尾的类的所有连接点,某一包下一些类(通配符指定)的所有方法

target()

类名

假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点。

如通过target(cn.wangjicn.Waiter)定义的切点、Waiter、以及Waiter实现类NaiveWaiter中所有连接点都匹配该切点,某一类(包括类的子类)上的所有方法

@within()

类型注解类名

假如目标类按类型匹配于某个类A,且类A标注了特定注解,则目标类的所有连接点匹配这个切点,如 @within(cn.wangjicn.Monitorable)定义的切点,假如Waiter类标注了@Monitorable注解,则Waiter 以及Waiter实现类NaiveWaiter类的所有连接点都匹配,标注了某一注解的所有类及其子类上的所有方法

目标类切点函数

@target()

类型注解类名

目标类标注了特定的注解,则目标类所有连接点匹配该切点。如@target(cn.wangjicn.Monitorable),假如NaiveWaiter标注了@Monitorable,则NaiveWaiter所有连接点匹配切点,标注了某一注解的所有类但不包括子类上的所有方法

代理类切点函数

this()

类名

代理类按类型匹配于指定类,则被代理的目标类所有连接点匹配切点。

 

l  execution 匹配方法执行的连接点,格式如下

execution(

modifiers-pattern?

ret-type-pattern

declaring-type-pattern?

name-patternparam-pattern

throws-pattern?)

 

下面是一些例子

任意公共方法的执行:execution(public * *(..))

任何一个名字以“set”开始的方法的执行:execution(* set*(..))

AccountService上所有方法的执行:execution(* com.xyz.service.AccountService.*(..))

service包全部类的上的全部方法的执行:execution(* com.xyz.service.*.*(..))

service包或其子包中定义的任意方法的执行:execution(* com.xyz.service..*.*(..))

 

l  within

service包中的任意连接点:within(com.xyz.service.*)

service包或其子包中的任意连接点:within(com.xyz.service..*)

 

l  this

实现了AccountService接口的代理对象的任意连接点 this(com.xyz.service.AccountService)

 

l  target

实现AccountService接口的目标对象的任意连接点:target(com.xyz.service.AccountService)

 

l  args

任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点:args(java.io.Serializable)

 

注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)) args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。

 

l  @target

目标对象中有一个 @Transactional 注解的任意连接点:

@target(org.springframework.transaction.annotation.Transactional)

 

l  @within

任何一个目标对象声明的类型有一个@Transactional注解的连接点:

@within(org.springframework.transaction.annotation.Transactional)

 

l  @annotation

任何一个执行的方法有一个@Transactional 注解的连接点:

@annotation(org.springframework.transaction.annotation.Transactional)

 

l  @args

任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点

@args(com.xyz.security.Classified)

 

l  bean

任何一个在名为'tradeService'Spring bean之上的连接点:

bean(tradeService)

任何一个在名字匹配通配符表达式'*Service'Spring bean之上的连接点:

bean(*Service)

 

1.4          组合切入点

切入点表达式可以使用'&', '||' '!'来组合。还可以通过名字来指向切入点表达式,如:

@Pointcut"executionpublic * *..))"

private void anyPublicOperation() {}

 

@Pointcut"withincom.xyz.someapp.trading..*"

private void inTrading() {}

 

@Pointcut"anyPublicOperation() && inTrading()"

private void tradingOperation() {}

 

成员可视性访问规则不影响到切入点的匹配。

 

1.5          通知

l  前置通知

@Before("test.AspectBean.pointcut()")

public void before() { }

 

l  后置通知

@AfterReturning(pointcut="test.AspectBean.pointcut()",returning="retVal")

public void afterReturning( Object retVal ) {}

 

l  异常通知

@AfterThrowing(pointcut="test.AspectBean.pointcut()",throwing="ex")

public void afterThrowing(DataAccessException ex) {}

 

l  最终通知

@After("test.AspectBean.pointcut()")

public void after() {}

 

l  环绕通知

@Around("test.AspectBean.pointcut()")

public Object around(ProceedingJoinPoint pjp) throws Throwable

{

    Object rel = pjp.proceed() ;

    return rel ;

}

 

l  通知顺序

1.环绕通知前

2.前置通知

->方法执行

3.环绕通知后

4.后置通知

5.最终通知

 

当定义在不同的切面里的两个通知都需要在一个相同的连接点中运行, 那么除非你指定,否则执行的顺序是未知的。你可以通过指定优先级来控制执行顺序。 在标准的Spring方法中可以在切面类中实现org.springframework.core.Ordered 接口或者用Order注解做到这一点。在两个切面中, Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。

当定义在相同的切面里的两个通知都需要在一个相同的连接点中运行, 执行的顺序是未知的

 

1.6          引入

使用@DeclareParents注解来定义引入。比如,给定一个接口UsageTracked,和接口的具体实现DefaultUsageTracked类,接下来的切面声明了所有的service接口的实现都实现了UsageTracked接口:

@Aspect

public class UsageTracking {

 

  @DeclareParentsvalue="com.xzy.myapp.service.*+",

                  defaultImpl=DefaultUsageTracked.class

  public static UsageTracked mixin;

}

 

2           基于SchemaAOP支持

  

Spring的配置文件中,所有的切面和通知都必须定义在<aop:config>元素内部。一个<aop:config>可以包含pointcutadvisoraspect元素 (注意这三个元素必须按照这个顺序进行声明)。

 

2.1          声明一个切面

切面使用<aop:aspect>来声明,backing bean(支持bean)通过 ref 属性来引用:

<aop:config>

  <aop:aspect id="myAspect" ref="aBean">

    ...

  </aop:aspect>

</aop:config>

 

<bean id="aBean" class="...">

  ...

</bean>

 

2.2          声明一个切入点

一个命名切入点可以在<aop:config>元素中定义,这样多个切面和通知就可以共享该切入点。

<aop:config>

  <aop:pointcut id="businessService"

        expression="execution* com.xyz.myapp.service.*.*..))"/>

</aop:config>

 

当需要连接子表达式的时候,'&&'XML中用起来非常不方便,所以关键字'and', 'or' 'not'可以分别用来代替'&&', '||' '!'

 

2.3          声明通知

l  前置通知

<aop:aspect id="beforeExample" ref="aBean">

 

    <aop:before

      pointcut-ref="dataAccessOperation"

      method="doAccessCheck"/>

</aop:aspect>

    

Method属性标识了提供通知主体的方法(doAccessCheck)。 这个方法必须定义在包含通知的切面元素所引用的bean中。

 

l  后置通知

<aop:aspect id="afterReturningExample" ref="aBean">

 

    <aop:after-returning

      pointcut-ref="dataAccessOperation"

      returning="retVal"

      method="doAccessCheck"/>

</aop:aspect>

 

l  异常通知

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing

      pointcut-ref="dataAccessOperation"

      throwing="dataAccessEx"

      method="doRecoveryActions"/>

</aop:aspect>

 

l  最终通知

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after

      pointcut-ref="dataAccessOperation"

      method="doReleaseLock"/>

</aop:aspect>

 

l  环绕通知

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around

      pointcut-ref="businessService"

      method="doBasicProfiling"/>

</aop:aspect>

 

l  引入

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

  <aop:declare-parents

      types-matching="com.xzy.myapp.service.*+"

      implement-interface="com.xyz.myapp.service.tracking.UsageTracked"

      default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before

    pointcut="com.xyz.myapp.SystemArchitecture.businessService()

              and thisusageTracked"

    method="recordUsage"/>

</aop:aspect>

 

2.4           Advisor

Spring 2.0通过<aop:advisor>元素来支持advisor概念

<bean id="aspectBean" class="test.AspectBean" />

<aop:config>

             <aop:pointcut expression="execution(* test..TestBean.*(..))" id="topPointcut"/>

             <aop:advisor pointcut-ref="topPointcut" advice-ref="aspectBean"/>

             <aop:aspect id="aspect" ref="aspectBean">

                 <aop:before pointcut-ref="topPointcut" method="before"/>

                 <aop:after method="after" pointcut="execution(* test..TestBean.*(..))"/>

             </aop:aspect>

</aop:config>

 

3           基于API风格的(1.x)版本的AOP

 

3.1          切入点API

l  切入点接口

public interface Pointcut

{

    ClassFilter getClassFilter();       // 类匹配

    MethodMatcher getMethodMatcher();   // 方法匹配

}

 

l  类匹配接口

public interface ClassFilter

{

    boolean matches(Class clazz); //传入的类类型是否匹配

}

 

l  方法匹配接口

public interface MethodMatcher

{

    boolean matches(Method m, Class targetClass); //静态匹配

    boolean isRuntime();//是否为动态切入

    boolean matches(Method m, Class targetClass, Object[] args); //动态匹配

}

isRuntime返回false时,匹配只在代理被创建的时候进行运算,动态匹配方法将不会被调用;否则在每次方法调用的时候都调用动态匹配方法进行匹配判断。

 

l  Spring中实现的切入点:

Perl5RegexpMethodPointcut JdkRegexpMethodPointcut 正则表达式切点

AspectJExpressionPointcut AspectJ切点表达式切点

NameMatchMethodPointcut 名称切入点

StaticMethodMatcherPointcut 静态切入点基类

DynamicMethodMatcherPointcut 动态切入点基类

ControlFlowPointcut 流程切入点

ComposablePointcut 组合切入点

 

3.2          通知API

l  前置通知

public interface MethodBeforeAdvice extends BeforeAdvice

{

    void before(Method m, Object[] args, Object target) throws Throwable;

}

 

l  后置通知

public interface AfterReturningAdvice extends Advice

{

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)  throws Throwable;

}

 

l  抛出通知

ThrowsAdvice接口是个标识接口

afterThrowing([Method, args, target], subclassOfThrowable)

 

l  环绕通知

public interface MethodInterceptor extends Interceptor

{

    Object invoke(MethodInvocation invocation) throws Throwable;

}

 

l  引入通知

public interface IntroductionInterceptor extends MethodInterceptor

{

    boolean implementsInterface(Class intf);

}

 

3.3          Advisor API

最常用的是org.springframework.aop.support.DefaultPointcutAdvisor。用来组合一个切点跟一个通知。

 

其他还有一些便利的Advisor实现:

NameMatchMethodPointcutAdvisor名称匹配advisor

RegexpMethodPointcutAdvisor 正则表达式advisor

StaticMethodMatcherPointcutAdvisor 静态匹配advisor

 

3.4          使用ProxyFactoryBean配置AOP

<bean id="proxyBean" class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="target">

<bean class="com.plusir.TargetBean" />

</property>

<property name="interceptorNames">

<list>

<value>beforeAdvice</value>

</list>

</property>

</bean>

注意interceptorNameslist值中,不能使用ref

 

3.5          使用自动代理

l  BeanNameAutoProxyCreator 名字匹配字符串或者通配符的bean自动创建AOP代理

l  DefaultAdvisorAutoProxyCreator 它自动应用当前上下文中适当的通知器,无需在自动代理通知器的bean定义中包括bean的名字。

l  TransactionAttributeSourceAdvisor  使用元数据驱动的自动代理

 

3.6          spring提供的目标源

 

l  热交换目标源

<bean id="initialTarget" class="mycompany.OldTarget"/>

    <bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">

        <constructor-arg ref="initialTarget"/>

    </bean>

    <bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">

        <property name="targetSource" ref="swapper"/>

</bean>

</bean>

 

l  池化目标源

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"

scope="prototype">

... properties omitted

</bean>

<bean id="poolTargetSource"

class="org.springframework.aop.target.CommonsPoolTargetSource">

<property name="targetBeanName" value="businessObjectTarget"/>

<property name="maxSize" value="25"/>

</bean>

<bean id="businessObject"

 class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="targetSource" ref="poolTargetSource"/>

<property name="interceptorNames" value="myInterceptor"/>

</bean>

 

l  原型目标源

<bean id="prototypeTargetSource"

class="org.springframework.aop.target.PrototypeTargetSource">

<property name="targetBeanName" ref="businessObjectTarget"/>

</bean>

 

l  ThreadLocal目标源

<bean id="threadlocalTargetSource"

class="org.springframework.aop.target.ThreadLocalTargetSource">

<property name="targetBeanName" value="businessObjectTarget"/>

</bean>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值