什么是面向切面编程

我们现在可以把之前分散在应用各处的行为放入可重用的模块中。我们显示地声明在何处如何应用该行为。这有效减少了代码冗余,并让我们的类关注自身的主要功能。
image.png

图 4.1展现了一个被划分为模块的典型应用。每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理。

定义AOP术语

描述切面的常用术语有通知(advice)、切点(pointcut)和连接点(join point)。图 4.2 展示了这些概念是如何关联在一起的。
image.png

通知(Advice)

通知定义了切面的工作是以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。

连接点(Join point)

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

切点(Poincut)

切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

切面(Aspect)

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

Spring对AOP的支持

Spring 提供了 4 种类型的 AOP 支持:

  • 基于代理的经典 Spring AOP;
  • 纯 POJO 切面;
  • @AspectJ 注解驱动的切面;
  • 注入式 AspectJ 切面(适用于 Spring 各版本)。

需要注意的是前三种都是 Spring AOP 实现的变体,Spring 对 AOP 的支持局限于方法拦截。如果你还需要构造器或属性拦截,就需要考虑使用第四种,采用AspectJ 来实现切面。

通过切点来选择连接点

在 Spring AOP 中,要使用 AspectJ 的切点表达式语言来定义切点。使用AspectJ 指示器来执行或限制切点表达式。

AspectJ 指示器 描叙
args() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类 型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方 法定义在由指定的注解所标注的类里)
@annotation 限定匹配带有指定注解的连接点
bean() bean ID 或 bean 名称作为参数来限制切点只匹配特定的 bean。

编写切点

先定义一个接口,它可以代表任何类型的现场表演,如舞台剧、电影或音乐会:

public interface Performance {  
    public void perform();  
}

设置当 perform() 方法执行时触发通知的调用的一个切点表达式:

execution(* com.springAop.Performance.perform(..))

//还可以使用操作符连接多个指示器
execution(* com.springAop.Performance.perform(..)) && bean('woodstock')

image.png

使用注解创建切面

定义切面

将观众定义为一个切面,并将其应用到演出上:

@Aspect  
public class Audience {  
      
    @Pointcut("execution(* com.springAop.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("CLAP CLAP CLAP!!!");  
    }  
  
    @AfterThrowing("performance()")  
    public void demandRefund() {  
        System.out.println("Demanding a refund");  
    }  
}

@Aspect 表明Audience 不仅仅是一个 POJO,还是一个切面。

@Pointcut注解声明频繁使用的切点表达式,performance该方法本身只是一个标识,供 @Pointcut 注解依附,execution()执行一个切点表达式,用来指定一个切点,表示一个目标方法

silenceCellPhones()被定义为了通知方法

AspectJ 提供了五个注解来定义通知:

注解 通知
@After 通知方法会在目标方法返回或抛出异常后调用
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来
@Before 通知方法会在目标方法调用之前执行

在JavaConfig中启用AspectJ注解的自动代理:

@Configuration  
@EnableAspectJAutoProxy //启用 AspectJ 注解的自动代理。会为@Aspect注解的bean创建一个代理  
public class ConcertConfig {  
  
    @Bean //声明bean     
  public Audience audience(){  
        return new Audience();  
    }
  
  @Bean  
  public Performance opera(){  
        return new Opera();  
    }  
  
}

执行perform():

public class Main {  
    public static void main(String[] args) {  
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConcertConfig.class);  
        Performance opera = (Performance) ctx.getBean("opera");  
        opera.perform();  
    }  
}

创建环绕通知

它能够让你所编写的逻辑将被通知的目标方法完全包装起来。

@Aspect  
public class Audience {  
  
    @Pointcut("execution(* com.springAop.Performance.perform(..))")
  public void performance() { }  
  
  
    @Around("performance()")  
    public void watchPerformance(ProceedingJoinPoint jp) {  
        try {  
            System.out.println("Silencing cell phones");  
            System.out.println("Taking seats");  
            jp.proceed();//某个点上执行目标方法  
  System.out.println("CLAP CLAP CLAP!!!");  
        } catch (Throwable e) {  
            System.out.println("Demanding a refund");  
        }  
    }  
}

处理通知中的参数

切点声明了要提供给通知方法的参数:

//切点方法
public interface Performance {  
    public void perform(int data);  
}

@Aspect  
public class Audience {  
    @Pointcut("execution(* com.springAop.Performance.perform(int)) && args(data)")//切点定义
    public void performance(int data){}  
  
    @Before("performance(data)")  
    public void methom(int data){  
    }  

image.png

通过注解引入新功能

我们为示例中的所有的 Performance 实现引入下面的 Encoreable 接口:

public interface Encoreable {  
    public void performEncore();  
  
}

我们现在假设你能够访问 Performance 的所有实现,并对其进行修改,让它们都实现 Encoreable 接口。但是,从设计的角度来看,这并不是最好的做法,并不是所有的 Performance 都是具有 Encoreable 特性的。另外一方面,有可能无法修改所有的 Performance 实现,当使用第三方实现并且没有源码的时候更是如此。

为了实现该功能,我们要创建一个新的切面:

@Aspect  
public class EncodeableIntroducer {  
  
    @DeclareParents(value = "com.springAop.Performance+", defaultImpl = DefaultEncoreable.class)  
    public static Encoreable encoreable;  
}

@DeclareParents 注解由三部分组成:

  • value 属性指定了哪种类型的 bean 要引入该接口。在本例中,也就是所有实现 Performance 的类型。(标记符后面的加号表示是 Performance 的所有子类型,而不是 Performance 本身。)
  • defaultImpl 属性指定了为引入功能提供实现的类。在这里,我们指定的是 DefaultEncoreable 提供实现。
  • @DeclareParents 注解所标注的静态属性指明了要引入了接口。在这里,我们所引入的是 Encoreable 接口。

和其他的切面一样,在JavaConfig中将EncoreableIntroducer 声明为一个 bean:

@Bean  
public EncodeableIntroducer encodeableIntroducer(){return new EncodeableIntroducer();}

执行一下:

public class Main {  
    public static void main(String[] args) {  
  
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConcertConfig.class);  
        Performance opera = (Performance) ctx.getBean("opera");   
  
        Encoreable encoreable = (Encoreable) opera;  
        encoreable.performEncore();  
    }  
}

WinRT
21 声望4 粉丝

临渊羡鱼,不如退而结网