Spring(四):面向切面编程AOP

  • 横切关注点:分布于应用中多处的功能

  • 面向切面编程AOP:将横切关注点与业务逻辑相分离


    在使用面向切面编程时,仍在一个地方定义通用功能,但是可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类。

    横切关注点可以被模块化为特殊的类,这些类被称为切面。

好处:

  • 每个关注点集中于一处,而不是分散到多处代码中

  • 服务模块更加简洁,因为它们只包含主要关注点的代码,次要关注点被转移到切面中了


1、定义AOP术语

1.1、通知(Advice)

    切面的工作被称为通知。

    通知定义了切面是什么以及何时使用。


Spring切面可以应用的5种类型的通知:

  • Before——在方法被调用之前调用通知

  • After——在方法完成之后调用通知,无论方法执行是否成功。

  • After-returning——在方法成功执行之后调用通知。

  • After-throwing——在方法抛出异常后调用通知。

  • Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

1.2、连接点(Joinpoint)

    连接点是在应用执行过程中能够插入切面的一个点,这个店可以是调用方法时、抛出异常时、甚至修改一个字段时。

1.3、切点(Pointcut)

    切点有助于缩小切面所通知连接点的范围,切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类名和方法名来指定这些切点,或是利用正则表达式定义匹配的类和方法名来指定这些切点。

1.4、切面(Aspect)

    切面是通知和切点的结合,通知和切点共同定义了关于切面的全部内容——它是什么、在何时和何处完成其功能。

1.5、引入(Introduction)

    引入允许我们向现有的类添加新方法或属性。

1.6、织入(Weaving)

    织入是将切面应用到目标对象来创建新的代理对象的过程。

在目标对象的生命周期里有多个点可以进行织入:

  • 编译期——切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。

  • 类加载期——切面在目标类加载到JVM时被织入。

  • 运行期——切面在应用运行的某个时候被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象,Spring AOP就是以这种方式织入切面的。


2、Spring对AOP的支持

    并不是所有AOP框架都是一样的,有些允许对字段修饰符级别应用通知,而另一些只支持与方法调用相关的连接点。它们在织入切面的方式和时机也有不同。但无论如何,创建切点来定义切面织入的连接点是AOP框架的基本功能。

    AOP世界三足鼎立格局:

  • AspectJ(http://eclipse.org/aspectj);

  • JBoss AOP(http://www.jboss.org/jbossaop);

  • Spring AOP(http://www.springframework.org)


    Spring提供了4种各具特色的AOP支持:

  • 基于代理的经典AOP;

  • @AspectJ注解驱动的切面;

  • 纯POJO切面;

  • 注入式AspectJ切面(适合Spring各版本)。

    前3种都是Spring基于代理的AOP变体,因此,Spring对AOP的支持局限于方法拦截。超过了简单方法拦截的范畴,考虑在AspectJ里面实现切面,利用Spring的DI把Spring Bean注入到AspectJ切面中。


    Spring AOP框架的关键点:

  • Spring通知是Java类编写的,定义通知所应用的切点通常在Spring配置文件里采用XML来编写的。

  • Spring在运行期通知对象,代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标Bean。当拦截到方法调用时,在调用目标Bean方法之前,代理会执行切面逻辑。

  • Spring只支持方法连接点,因为Spring基于动态代理,所以Spring只支持方法连接点。这与其他AOP框架是不同的,例如AspectJ和Jboss,除了方法切点,它们还提供了字段和构造器接入点。


2.1、使用切点选择连接点

    切点用于准确定位应该在什么地方应用切面的通知,切点和通知是切面的最基本元素。

    在Spring AOP中需要使用AspectJ的切点表达式语言来定义切点。关于Spring AOP的AspectJ切点,最重要的一点是Spring仅支持AspectJ切点指示器(pointcut designator)的一个子集。Spring是基于代理的,而某些切点表达式是与基于代理的AOP无关的。

Spring AOP 所支持的AspectJ切点指示器:

AspectJ指示器描述
arg()
限制连接点匹配参数为指定类型的执行方法
@arg()限制连接点匹配参数由指定注解标注的执行方法
execution()用于匹配是连接点的执行方法
this()限制连接点匹配AOP代理的Bean引用为指定类型的类
target()限制连接点匹配目标对象为指定类型的类
@target()限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
within()限制连接点匹配指定的类型
@within()限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义由指定的注解所标注的类里)
@annotation限制匹配带有指定注解连接点

    在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。

    execution指示器是唯一的执行匹配,而其他的指示器都用于限制匹配的,它是最主要使用的指示器。

2.1.1、编写切点

使用AspectJ切点表达式来定位:

102310_aNM0_1020238.png

使用within()指示器来限制匹配:

102346_clzD_1020238.png

    因为在xml中&有特殊含义,所以在使用Spring的基于xml配置来描述切点时,可以使用and代替&&,or代替||和not代替!。

2.1.2、使用Spring的bean()指示器

    该指示器允许我们在切点表达式中使用Bean的ID来标识Bean。

如:

102737_SyAM_1020238.png

    在执行Instrument的play()方法时应用通知,但限定Bean的ID为eddie。

2.2、在xml中声明切面

Spring的AOP配置元素:

AOP配置元素描述
<aop:advisor>定义AOP通知器
<aop:after>定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning>定义AOP after-returning通知
<aop:after-throwing>定义AOP after-throwing 通知
<aop:around>定义AOP环绕通知
<aop:aspect>定义切面
<aop:aspectj-autoproxy>启用@AspectJ注解驱动的切面
<aop:before>定义AOP前置通知
<aop:config>顶层的AOP配置元素,大多数<aop:*>元素必须包含在<aop:config>元素内
<aop:declare-parents>为被通知的对象引入额外的接口,并透明地实现
<aop:pointcut>定义切点
2.2.1、声明前置和后置通知
<aop:config>
    <!--引用audience Bean-->
    <aop:aspect ref="audience">
    
    <!--表演之前-->
    <aop:before pointcut="execution(* com.springinaction.springdol.Performer.perform(..))"
        method="takeSeats" />
        
    <!--表演之后-->
    <aop:after-returning pointcut="execution(* com.springinaction.springdol.Performer.perform(..))"
        method="applaud" />
    
    <!--表演失败之后-->
    <aop:after-throwing pointcut="execution(* com.springinaction.springdol.Performer.perform(..))"
        method="demandRefund" />
</aop:config>

    大多数的AOP配置元素必须在<aop:config>元素的上下文内使用,在<aop:config>元素内,可以声明一个或多个通知器、切面或者切点。


    为避免重复定义切点,可以使用<aop:pointcut>元素定义一个命名切点:

<aop:config>
    <!--引用audience Bean-->
    <aop:aspect ref="audience">
    
        <!--定义切点-->
        <aop:pointcut id="performance" 
            expression="(* com.springinaction.springdol.Performer.perform(..))" />
        
        <!--表演之前-->
        <aop:before pointcut-ref="performance"
            method="takeSeats" />
            
        <!--表演之后-->
        <aop:after-returning pointcut-ref="performance"
            method="applaud" />
        
        <!--表演失败之后-->
        <aop:after-throwing pointcut-ref="performance"
            method="demandRefund" />
    </aop:aspect>
</aop:config>
2.2.2、声明环绕通知

    利用<aop:around>来声明环绕通知,可以完成前置通知和后置通知所实现的相同功能,但是只需要在一个方法中实现。

例如:

在切面audience中,watchPerformance()方法提供了AOP环绕通知,包含了之前前置和后置等通知方法的所有逻辑。

public void watchPerformance(ProceedingJoinPoint joinpoint) {
    try{
        //表演之前 do something
        //...
        
        //执行被通知的方法
        joinpoint.proceed();
        
        //表演之后 do something
        //...
    }catch (Throwable t) {
        //表演失败之后 do something
        //...
    }
}

    ProceedingJoinPoint作为方法的入参,这个对象能让我们在通知里调用被通知方法。必须调用proceed()方法,否则通知将会阻止被通知的方法。

声明环绕通知:

<aop:config>
    <!--引用audience Bean-->
    <aop:aspect ref="audience">
    
        <!--定义切点-->
        <aop:pointcut id="performance" 
            expression="(* com.springinaction.springdol.Performer.perform(..))" />
    
        <!--声明环绕通知-->
        <aop:around pointcut-ref="performance"
            method="watchPerformance" />
    </aop:aspect>
</aop:config
2.2.3、为通知传递参数

112304_ty8v_1020238.png

2.2.4、通过切面注入新功能

利用<aop:declare-parents>

132720_ncrL_1020238.png

2.3、注解切面

122637_nA5Z_1020238.png

    通过使用@AspectJ注解,使其不需要任何额外的类或Bean声明就能将Audience类转换为一个切面。

    @Pointcut注解用于定义一个可以在@AspectJ切面内可重用的切点,其值是一个AspectJ切点表达式。切点的名称来源于注解所应用的方法名称,因此,该切点的名称为performance(),该方法的实际内容不重要,可以为空的,本身只是一个标识,供@Pointcut注解依附。


    需要在Spring上下文中声明一个自动代理Bean,该Bean知道如何把@AspectJ注解所标注的Bean转变为代理通知。为此,Spring自带了名为AnnotationAwareAspectJAutoProxyCreator的自动代理创建类,我们可以在Spring上下文中把AnnotationAwareAspectJAutoProxyCreator注册为一个Bean,为了简化,Spring在aop命名空间中提供了一个自定义的配置元素:<aop:aspectj-autoproxy />。

    <aop:aspectj-autoproxy />将在Spring上下文中创建一个AnnotationAwareAspectJAutoProxyCreator类,它会自动代理一些Bean,这些Bean的方法需要与使用@AspectJ注解的Bean中所定义的切点相匹配,而这些切点又是@Pointcut注解定义出来的。

    记住,<aop:aspectj-autoproxy>仅仅使用@AspectJ注解作为指引来创建基于代理的切面,但本质上仍是一个Spring风格的切面。

2.3.1、注解环绕通知

131828_LcoX_1020238.png

    在这里,@Around注解标识了watchPerformance()方法将被作为环绕通知应用于performance()切点。

2.3.2、传递参数给所标注的通知

132245_fyis_1020238.png

    <aop:pointcut>元素变为@Pointcut注解,而<aop:before>元素变为@Before注解。在这里,唯一发生显著变化的是@AspectJ能够依靠Java语法来判断为通知所传递参数的细节,因此这里并不需要与<aop:before>元素的arg-names属性所对应的注解。

2.3.3、标注引入

    等价于<aop:declare-parents>的注解是@AspectJ的@DeclareParents,在基于@AspectJ注解所标注的类内使用时,@DeclareParents工作方式几乎等同于<aop:declare-parents>。

133009_R8rU_1020238.png

@DeclareParents注解由3部分组成:

  • value属性等同于<aop:declare-parents>的type-matching属性,标识应该被注入指定接口的Bean的类型;

  • defaultImpl属性等同于<aop:declare-parents>的default-impl属性,标识该类提供所引入接口的实现;

  • 由@DeclareParents注解所标注的static属性指定了将被引入的接口。

2.4、注入AspectJ切面

    虽然Spring AOP能够满足许多切面需求,但与AspectJ相比,Spring AOP是一个功能比较弱的AOP解决方案,AspectJ提供了Spring AOP所不能支持的许多类型的切点。


转载于:https://my.oschina.net/u/1020238/blog/503967

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值