Skip to content

Spring面向切面编程 #19

@superstonne

Description

@superstonne

面向切面编程是面向对象编程的一个补充和辅助。在应用程序中,有一些功能是横跨多个业务模块的,例如日志,事务,安全等功能,它会横跨我们的很多模块,例如用户模块,订单模块,库存模块等等。如果将这些横跨业务模块的功能,在每个模块里面都去实现,那么这些代码会造成冗余,而且业务模块的本身的逻辑和非业务的代码混合在一起,使得业务逻辑编写起来更加复杂。所以我们可以将这些横跨业务模块的功能抽象成一个切面,切入到各个业务模块当中。这样业务模块可以专心业务逻辑的实现,无需关心和业务无关的功能,使得我们的功能模块的重用率提高,业务逻辑的实现变得简单。Spring提供4种类型的切面编程支持:经典的基于代理的Spring AOP,纯Java Bean的切面,Aspect-J注解驱动的切面,注入Aspect-J 切面

切面编程中的名词概念

  • Advice 指的是切面的具体功能(如日志切面,事务切面),Spring提供了5中类型的切面功能:Before,After,After-returning,After-throwing,Around
  • Join Points 指的是所有可以切入点,例如日志切面可以切入很多地方,这些所有可以切入的地方叫做Join Points
  • Point Cuts 指的是这些所有的Join Points中需要我们去切入切面的点
  • Aspects 指的是一个切面,由Advice和Point Cuts组成,得到一个切面,我们就知道切面的功能,切面的切入点
  • Introductions 指的是引入到我们需要增强的业务模块的新方法,新的属性等。
  • Weaving 指的是我们将切面植入到业务模块的过程,我们有多种植入方法:编译期植入(需要特殊编编译器的支持),类加载期植入(需要特殊类加载器的支持(LTW)),运行时植入(Spring AOP使用这种方式植入)。

使用注解来创建切面

  1. 使用Aspect注解在类的层面,使得该类成为一个切面
  2. 使用Before,AfterReturning,AfterThrowing来在方法层面使得方法变为不同种类的切面,注解参数中加上表达式来确定切入点,execution(* org.jinlong.study.spring.aop.Performance.perform(..))
package org.jinlong.study.spring.aop;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class Audience {

    @Before("execution(* org.jinlong.study.spring.aop.Performance.perform(..))")
    public void silenceCellPhones() {
        System.out.println("Silencing cell phones...");
    }

    @Before("execution(* org.jinlong.study.spring.aop.Performance.perform(..))")
    public void takeSeats() {
        System.out.println("Taking seats...");
    }

    @AfterReturning("execution(* org.jinlong.study.spring.aop.Performance.perform(..))")
    public void applause() {
        System.out.println("Clip Clip Clip.");
    }

    @AfterThrowing("execution(* org.jinlong.study.spring.aop.Performance.perform(..))")
    public void demandRefund() {
        System.out.println("Demanding a refund.");
    }
}

从上面的代码可以看出,每个切面的切点表达式是相同的,所以我们可以将切点表达式定义为一个变量,通过如下方式:

package org.jinlong.study.spring.aop;

import org.aspectj.lang.annotation.*;

@Aspect
public class Audience {

    @Pointcut("execution(* org.jinlong.study.spring.aop.Performance.perform(..))")
    public void performance() {
    }

    @Before("performance()")
    public void silenceCellPhones() {
        System.out.println("Silencing cell phones...");
    }

    @Before("performance()")
    public void takeSeats() {
        System.out.println("Taking seats...");
    }

    @AfterReturning("performance()")
    public void applause() {
        System.out.println("Clip Clip Clip.");
    }

    @AfterThrowing("performance()")
    public void demandRefund() {
        System.out.println("Demanding a refund.");
    }
}
  1. 使用Around类型的切面来代替其他类型的切面
    上例中所有的切面使用的切入点是同一个切入点,此时我们可以使用Around类型切面来达到同样的效果
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinPoint) {
       try {
            System.out.println("before performance start");
            joinPoint.proceed();
            System.out.println("after performance end");
        } catch (Throwable throwable) {
            System.out.println("demanding a refund.");;
        }
    }
  1. 可以处理参数的切面
    如果需要处理参数的,那么我们的切点表达式应该这样写:@pointcut("execution(* org.jinlong.study.spring.aop.TrackNumberPerformance.perform(int)) && args(trackNumber)"),应用切点
    @before("trackPlay(trackNumber)")

  2. 类增强
    前面的几种增强都是在方法的前后,或者出现异常时候加入我们的切面逻辑,或者对参数进行拦截。那么我们有没有办法对类添加一些额外的方法呢?使用DeclareParents可以实现添加方法到需要增强的类中。
    使用时候将增强类强制转换为增强方法的接口即可使用添加的方法。

@DeclareParents(value = "org.jinlong.study.spring.aop.Performance+", defaultImpl = DefaultEncoreable.class)
 public static Encoreable encoreable;

使用XML方式来创建切面

  1. 定义切面Bean和切面
<bean id="audience" class="org.jinlong.study.spring.aop.Audience"/>
    <aop:config>
        <aop:aspect ref="audience">
            <aop:before
                    pointcut="execution(* org.jinlong.study.spring.aop.Performance.perform(..))"
                    method="silenceCellPhones"/>
            <aop:before
                    pointcut="execution(* org.jinlong.study.spring.aop.Performance.perform(..))"
                    method="takeSeats"/>
            <aop:after-returning
                    pointcut="execution(* org.jinlong.study.spring.aop.Performance.perform(..))"
                    method="applause"/>
            <aop:after-throwing
                    pointcut="execution(* org.jinlong.study.spring.aop.Performance.perform(..))"
                    method="demandRefund"/>
        </aop:aspect>
    </aop:config>

XML方式定义切点,以供重用切点

<aop:config>
        <aop:pointcut id="performance" expression="execution(* org.jinlong.study.spring.aop.Performance.perform(..))"/>
        <aop:aspect ref="audience">
            <aop:before
                    pointcut-ref="performance"
                    method="silenceCellPhones"/>
            <aop:before
                    pointcut-ref="performance"
                    method="takeSeats"/>
            <aop:after-returning
                    pointcut-ref="performance"
                    method="applause"/>
            <aop:after-throwing
                    pointcut-ref="performance"
                    method="demandRefund"/>
        </aop:aspect>
    </aop:config>
  1. XML中使用Around advice
<aop:config>
        <aop:pointcut id="performance" expression="execution(* org.jinlong.study.spring.aop.Performance.perform(..))"/>
            <aop:around method="watchPerformance"
                        pointcut-ref="performance"/>
        </aop:aspect>
    </aop:config>
  1. XML 处理有参数的切面
<bean id="trackNumber" class="org.jinlong.study.spring.aop.TrackTools"/>
    <aop:config>
        <aop:pointcut id="trackPlayed" expression="execution(* org.jinlong.study.spring.aop.TrackNumberPerformance.perform(int)) and args(trackNumber)"/>
        <aop:aspect ref="trackNumber">
            <aop:before method="trackCount" pointcut-ref="trackPlayed"/>
        </aop:aspect>
    </aop:config>
  1. XML处理添加方法增强
<bean id="encoreable" class="org.jinlong.study.spring.aop.DefaultEncoreable" />
    <aop:config>
        <aop:aspect>
            <aop:declare-parents types-matching="org.jinlong.study.spring.aop.Performance" implement-interface="org.jinlong.study.spring.aop.Encoreable"
                                 delegate-ref="encoreable"/>
        </aop:aspect>
    </aop:config>

注入Aspect-J提供的切面

通常情况下Spring提供的AOP已经可以解决我们的问题,但是相对于Aspect-J强大的AOP功能,Spring的AOP相对功能较弱。此时我们通过定义Aspect-J的切面,然后通过依赖注入将切面注入到我们的应用即可获得Aspect-J的强大AOP功能。

public aspect CriticalAspect {
    public CriticalAspect() {}
    pointcut performance() : execution(* perform(..));
    afterReturning() : performance() {
        System.out.println(criticismEngine.getCriticism());
    }
    private CriticismEngine criticismEngine;
    public void setCriticismEngine(CriticismEngine criticismEngine) {
        this.criticismEngine = criticismEngine;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions