Spring学习笔记-面向切面(AOP)-04

什么是面向切面编程

先大概了解一下部分术语

横切关注点:软件开发中,散布于多出的功能称为横切关注点(cross-cutting concern),简单的可以描述为可以影响应用多处的功能,比如日志、安全。

切面:切面能帮我们模块化横切关注点,如图所示,三个不同的模块,每个模块都是为特定业务服务,但是这些模块都需要类似的辅助功能,例如安全、事务管理。面向切面,可以使我们在一个地方定义通用功能,以声明的方式定义这个功能要以何种方式在何处使用,无需修改受影响的类,横切关注点可以被模块化为一个特殊的类,这些类被称为切面。

 

AOP带来的好处:

  1、每个关注点集中于一个地方,而不是分散到多处代码。

  2、服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,次要关注点的代码被转移到切面中了。

AOP术语

 常见术语:通知(advice)、切点(pointcut)、连接点(point)

1、通知(advice):切面的所做的工作被称为通知(什么时候做)

  通知定义了切面是什么以及何时使用,描述切面要完成的工作和何时执行这个工作。

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

  1、前置通知(Before):在目标方法被调用之前调用通知;

  2、后置通知(After):在目标方法被调用之后调用通知;

  3、返回通知(After-returning):在目标方法成功执行之后调用通知;

  4、异常通知(After-throwing):在目标方法抛出异常后调用通知;

  5、环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义行为。

2、连接点(Join point):插入切面的时机,在应用执行“过程中”能够插入切面的一个点(做的时机)

   应用可能有无数的时机应用通知,这些时机被称为连接点。这个点可以使调用方法时,抛出异常时,甚至是修改一个字段时,切面代码可以利用这些连接点插入到应用的正常流程中。

3、切点(Poincut):插入切面的位置(在什么地方做)

  明确要织入的一个或多个连接点,通常使用明确的类和方法名称,或正则表达式匹配类和方法名,来指定切点。

4、切面(Aspect):通知和切点的结合。(在什么时候什么地方做)

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

5、引入(Introduction):不修改仙有泪的情况下,向现有类引入新功能

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

6、织入(Weaving):把切面应用到目标对象,并创建新的代理对象的过程。

  切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可织入:

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

  2、类加载期:切面在目标类加载到JVM中被织入,此方式需要特殊的类加载器(ClassLoader),其可以在目标类被引入应用之前增强该类的字节码。AspectJ 5的加载时织入(LTW)就是这种方式织入的。

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

 通过切点来选择连接点

Spring AOP中可以使用AspectJ切点表达式语言定义切点,如下为Spring AOP所支持的AspectJ切点指示器:

编写切点

首先写一个接口用于演示如何定义切点,如下接口可以代表任何类型的现场表演,如舞台剧、电影等。

public interface Performance {
    public void perform();
}

假设我们向编写上面接口的perform()方法触发通知,如下展示了一个接口表达式

execution()指示器选择Performance的perform方法,方法表达式以“ * ” 号开始,表示我们不关心方法的返回值类型,可以理解为一个通配符。

*号之后指定了全限定的类名和方法名,对于方法的参数列表使用2个点好(..)表示切点选择任意参数签名的perform()方法。

 

 

现在加入我们需要配置的切点仅匹配concert包,可使用within()指示器来限制匹配,如下图。

使用“&&”操作符连接execution()和within()指示器,形成“与”(AND)的关系,(也可以使用||(或)、!(非)标识操作,也可以使用and、or、not来代替)

在切点中选择bean

 除了表4.1所列的指示器外,Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标识bean。bean()使用bean ID或bean名称作为参数来限制切点只匹配特定的bean。例如,考虑如下的切点:在这里,我们希望在执行Performance的perform()方法时应用通知,但限定bean的ID为woodstock。在某些场景下,限定切点为指定的bean或许很有意义,但我们还可以使用非操作为除了特定ID以外的其他bean应用通知:在此场景下,切面的通知会被编织到所有ID不为woodstock的bean中。

使用注解创建切面

使用我们定义的Performance接口,作为切面中切点的目标对象,然后开始使用AspecJ注解定义切面

定义切面

如下Audience类使用AspectJ注解定义了一个切面

@Aspect
public class Audience {
    //前置通知 
    @Before("execution(** Performance.perform(..))")
    public void silenceCellPhones(){
        System.out.println("表演开始,观众就坐(silenceCellPhones)");
    }
    //前置通知
    @Before("execution(** Performance.perform(..))")
    public void takeSeats(){
        System.out.println("表演开始,手机调至静音(takeSeats)");
    }
    //返回通知
    @AfterReturning("execution(** Performance.perform(..))")
    public void applause(){
        System.out.println("表演结束,并且很精彩,观众鼓掌喝彩(applause)");
    }
    //异常通知
    @AfterThrowing("execution(** Performance.perform)")
    public void demandRefund(){
        System.out.println("表演失败,观众要求退款(demandRefund)");
    }
}

上面有多个重复的切点,可以使用@Pointcut注解进行优化,performance方法内容不重要,在这里的作用实际上只是一个标识,供@Pointcut注解依附,供其他通知注解使用,避免重复使用相同的切点表达式。

@Aspect
public class Audience2 {    
    @Pointcut("execution(** Performance.perform(..))")
    public void performance(){};
    //前置通知 
    @Before("performance()")
    public void silenceCellPhones(){
        System.out.println("表演开始,观众就坐(silenceCellPhones)");
    }
    //前置通知
    @Before("performance()")
    public void takeSeats(){
        System.out.println("表演开始,手机调至静音(takeSeats)");
    }
    //返回通知
    @AfterReturning("performance()")
    public void applause(){
        System.out.println("表演结束,并且很精彩,观众鼓掌喝彩(applause)");
    }
    //异常通知
    @AfterThrowing("execution(** Performance.perform)")
    public void demandRefund(){
        System.out.println("表演失败,观众要求退款(demandRefund)");
    }
}

现在定义了切点,但是如果就此而已那么Audience只会是Spring容器的一个bean,并不会视为一个切面,不会被Spring解析,也不会创建将其转换为切面的代理,需要配置以启用。

JavaConfig方式:可在配置类级别上使用@EnableAspectJ-AutoProxy注解启用自动代理。

//JavaConfig配置类
@Configurable        
@EnableAspectJAutoProxy    //启用自动代理
@ComponentScan  //组件扫描
public class ConcertConfig {
    @Bean
    public Audience audience(){
        return new Audience();
    }
}

XML方式:使用Spring  aop命名空间中的<aop:aspectj-autoproxy>元素。

<!--启用AspectJ自动代理-->
<aop:aspectj-autoproxy />
<!--声明Audience bean-->
<bean class= "concert.Audience"/>

 环绕通知

  能让你所编写的逻辑将被通知的目标方法完全包装起来,就像同时在一个通知方法中编写前置和后置通知。如下代码,@Around注解表明watchPerformance()方法会作为performance()切点的环绕通知,其中通知方法接受一个ProceedingJoinPoint类型的参数,这个参数不许要提供,因为要在通知中通过它调用被通知的方法,在通知方法完成工作之后,需要调用ProceedingJoinPoint的oriceed()方法,否则这个通知会阻塞被通知方法(目标方法)的调用。

处理通知中的参数

目前为止,所有的切面都没有任何参数,除了环绕通知,在如下通知代码中,切点表达式匹配带参数的目标方法,其中&& args(trackNumber)表示传递给playTrack()方法的int类型参数也会传递到通知方法中,参数中的trackNumberca也与通知方法签名中的参数相匹配,此

 

posted on 2018-05-24 20:10  奚云刀  阅读(278)  评论(0编辑  收藏  举报

导航