spring学习笔记3--AOP

来源:互联网 发布:如何学好高中政治知乎 编辑:程序博客网 时间:2024/06/05 01:19

概念术语

通知:切面可能有很多个工作,每个具体的工作成为通知。通知定义了切面做什么以及何时做。

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

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关
  • 心方法的输出是什么;
  • 返回通知(After-returning):在目标方法成功执行之后调用通
    知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方
    法调用之前和调用之后执行自定义的行为。

连接点

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

切点(Poincut)

切点定义了在什么地方执行切面功能。一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。切点的定义会匹配通知所要织入的一个或多个连接点。

切面(Aspect)

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

引入(Introduction)

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

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。
切面在指
定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点
可以进行织入:

  • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译
    器。AspectJ的织入编译器就是以这种方式织入切面的。
  • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特
    殊的类加载器(ClassLoader),它可以在目标类被引入应用
    之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time
    weaving,LTW)就支持以这种方式织入切面。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织
    入切面时,AOP容器会为目标对象动态地创建一个代理对象。
    Spring AOP就是以这种方式织入切面的。

spring aop

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

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

Spring AOP构建在动态代理基础
之上,因此,Spring对AOP的支持局限于方法拦截。

spring在运行时通知对象

通过在代理类中包裹切面,spring在运行期把切面织入到spring管理的bean中。代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理类拦截到方法调用时,在调动目标bean方法之前,会执行切面逻辑。
因为spring在运行时才创建代理对象,所以我们不需要特殊的编译器来织人spring AOP的切面。

通过切点来选择连接点

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

spring aop支持的AspectJ切点指示器:

  • arg():限制连接点匹配参数为指定类型的执行方法
  • @args():限制连接点匹配参数由指定注解标注的执行方法。
  • execution():用于匹配是连接点的执行方法
  • this():限制连接点匹配AOP代理的bean引用为指定类型的类。
  • target:限制连接点匹配目标对象为指定类型的类。
  • @target():限制连接点匹配特定的连接对象。这些对象对应的的类要具有指定类型的注解。
  • within():限制连接点为指定的类型
  • @within():限制连接点匹配指定注解所标注的类型
  • @annotaiton:限定匹配带有指定注解的连接点。

以上只有execution是实际执行匹配的,其他都是限制匹配的。

编写切点

需要有个主题来定义切面的切点。为此我们定义了一个performance接口。

package concert;public interface Performance{    public void perform();}

编写perform()方法触发的通知:

execution(* concert.Performance.perform(..))&&within(concert.*)

within()指定在concert包下的类。也可以用and or not || ! 等操作符。

execution(* concert.Performance.perform(..)) and bean(‘mybeanid’)

bean()限定bean的id为mybeanid.
也可以用!表示除了特定ID以外的其它bean应用通知。

execution(* concert.Performance.perform(..)) and !bean('mybeanid')

使用注解创建切面

我们已经定义了Performance接口,它是切面中切点的目标对象。将观众定义为一个个切面并应用到演出上。

@Aspectpublic class Audience {@Before("execution(** concert.Performance.perform(..))")    public void silenceCellPhones(){        System.out.println("silencing cell phones");    }    @Before("execution(** concert.Performance.perform(..))")    public void takeSeats(){        System.out.println("taking seats");    }    @AfterReturning("execution(** concert.Performance.perform(..))")    public void applause(){        System.out.println("applause");    }    @AfterThrowing("execution(** concert.Performance.perform(..))")    public void demandRefund(){        System.out.println("demanding a refund");    }}

AspectJ的五个通知注解:
1. @After
2. @Before
3. AfterReturning
4. AfterThrowing
5. Around

@Pointcut可以定义一个切点,然后就可以把切点表达式替换为切点引用。

@Pointcut("execution(** concert.Performance.perform(..))")    public void performance(){}    @Before("performance()")    public void silenceCellPhones(){        System.out.println("silencing cell phones");    }    @Around("performance()")    public void watchPerformance(ProceedingJoinPoint jp){        System.out.println("silencing cell phones");        System.out.println("taking seats");        try {            jp.proceed();        } catch (Throwable e) {            System.out.println("demanding a refund");        }    }

在JavaConfig上启动自动代理功能。

@Configuration@EnableAspectJAutoProxy@ComponentScanpublic class ConcertConfig {    @Bean    public Audience audience(){        return new Audience();    }}

xml的就要用
AspectJ自动代理会为使用@AspectJ注解的bean创建一个代理。这个代理会围绕着所有该切面的切点所匹配的bean。

环绕通知

@Around("performance()")public void watch(ProceedingJoinPoint jp){    //调用前业务    jp.proceed();    //调用后业务}

处理通知中的参数

@Aspectpublic class bean1{    @PointCut("execution(* x.x.x.method1(int) && args(parametername)")    public void play(int parametername){}    @Before("play(parametername")    public void play2(int parametername){        //这样可以取到parametername    }}

通过注解引入新功能

利用被称为引入的AOP概念,切面可以为spring bean添加新方法。在spring中,切面只是实现了他们所包装bean相同接口的代理。如果代理也能暴露新接口的话那么看起来就像是切面所通知的bean实现了新的接口。

当引入接口的方法被调用时,代理会把次调用委托给实现了新接口的某个其它对象。

package concert;public interface Encoreale{    void performance();}@Aspectpublic class EncorealeIntroducer{    @DeclareParents(value="concert.Performance+",defaultImpl=DefaultEncoreable.class)    public static Encoreable encoreable;}

@DeclareParents注解由三部分组成:
1. value属性指定了那种类型的bean要引入新接口。
2. defaultImpl属性指定了为引入功能提供实现的类。
3. 注解所标注的静态属性指明了要引入的接口。
和其它切面一样要将它声明为一个bean.这样就可以将一个相同的bean转换为两个不同接口的实现。例如:

  ApplicationContext cxt=new ClassPathXmlApplicationContext(                   "classpath:/spring/spring.xml");          Performer performer =(Performer)cxt.getBean("juggler");          performer.perform();          Encoreale contestant =(Encoreale)cxt.getBean("juggler");          contestant.performance();  

使用xml声明切面

<aop:config>        <aop:aspect ref="audience">            <aop:pointcut expression="execution(** concert.Performance.perform(..))" id="performance"/>            <aop:before method="silenceCellPhones"            pointcut-ref="performance"/>            <aop:after-returning method="applause"            pointcut="execution(** concert.Performance.perform(..))"/>            <aop:after-throwing method="demandfund"            pointcut="execution(** concert.Performance.perform(..))"/>        </aop:aspect>    </aop:config>

引入新功能:

<aop:aspect>            <aop:declare-parents             types-matching="concert.Performance+"             implement-interface="concert.Encoreable"            default-impl="concert.DefaultEncoreable"/></aop:aspect>//可以把上面的default-impl替换成delegate-ref,引用spring bean作为引入的委托。

注入Aspectj切面

AspectJ切面不需要Spring就可以织入到应用中,如果向用Spring的依赖注入为AspectJ切面注入协作者。就需要使用Spring把切面声明为一个bean.如下:

<bean class="aop.CriticAspect" factory-method="aspectOf">    <property name="criticismEngine" ref="criticismEngine"/>    </bean>

spring bean由spring容器初始化,但是AspectJ切面是由AspectJ在运行期创建的。等到spring有机会为切面类注入协作者时,切面类已经被实例化了。
因此这种情况下spring不能负责创建切面类,而是引用AspectJ创建好的切面类实例。然后为它注入。所有的AspectJ都有一个静态的aspectOf()方法,该方法返回切面的一个单例。所以必须调用factory-method来调用aspectOf()而不是切面类的构造方法。