【spring】spring reference doc 4.3.1 研读<三> Spring aop

来源:互联网 发布:千里眼软件是什么 编辑:程序博客网 时间:2024/06/05 17:34

1. Spring 的 AOP

       1.1 介绍

                面向切面编程(Aspect Oriented Programming) ,是对面向对象编程(OOP)的补充。 OOP 是 面向 类 , AOP 是面向 切面。

              AOP 在 Spring 中的作用 :

                          ★ 提供 声明式的企业服务 ,尤其是 替代 EJB 的声明式服务 。最重要的比如  声明式事物

                          ★ 用户可以实现自定义的 切面 ,使用 AOP 补充 OOP


             1.1.1  AOP 概念


             ★  Aspect  : 切面  ,横切多个类的一个模块化关注点  。 通常通过有规律的类或者类的注解 @Aspect 来实现 切面

             ★  Join Point : 程序执行过程中的一个连接点 ,例如方法的执行 , 异常的处理 。

             ★  Advice : 通知 。 切面在指定的一个连接点采取的动作。 通常包括  around(环绕),before(前置),after(后置)通知 ,作为一个拦截器提供通知 。

             ★ Pointcut : 切点 。 匹配 Join Point 的 断言 。通知 是和  切点表达式相关联的 并且在通过切点 匹配的任意连接点执行。

             ★ Introduction :

             ★  Target object :被一个或者多个切面通知的对象 。因为 Spring AOP 是通过使用运行时代理来实现的 ,所以这个对象永远是 一个被代理的对象。

             ★ AOP Proxy : AOP 代理 , 为了实现  切面规定而被 AOP 框架创建的对象 。 在 Spring 框架中, AOP 是一个 JDK 动态代理 或者  CGLIB 代理 ,可手动配置。

             ★ Weaing :  编织 ,将 切面和其他应用类型或者对象 连接起来创建 一个被通知的对象 。这个过程在编译时期(例如使用 AspectJ 编译器实现) ,加载时期或者运行时期可以完成。 Spring AOP 像其他纯 Java 的 AOP 框架 一样,在运行时期进行编织 。


             通知类型 :

                 ★ Before advice :    在 Join Point 之前执行的通知 ,但是没有能力阻止 Join Point 的执行(除非抛出异常)

                 ★ After returning advice :  在 连接点正常完成之后执行的通知 :例如 ,如果方法没有抛出异常正常返回后执行该通知。

                 ★ After throwing advice : 如果一个方法因为抛出异常 退出,执行 通知

                 ★ After (finally) advice :  不管连接点 退出与否都会执行的通知 (正常或者异常返回)

                 ★ Around advice : 环绕 一个方法调用的连接点的通知 。能够在方法调用之前和方法调用之后自定义行为 ,而且还负责是否继续执行 join point ,还是返回自定义的值或者是抛出异常 。都可以自定义。


                   1.1.2 Spring AOP 的 功能和目标

                           Spring AOP 是 用纯 Java 实现的,适合在 Servlet 容器 / Application server 使用 。

                          Spring AOP  的 目标不仅仅是 实现 AOP ,而是为 AOP 实现和 Spring Ioc 提供紧密集成 ,从而解决企业级应用中的一些常见问题。

                          Spring AOP  需要集成 AspectJ


                  1.1.3  AOP 代理

                           Spring AOP 默认使用 标准的 JDK 动态代理 作为 AOP 的代理 ,使 任意接口(或者一组接口)被代理 。

                           Spring AOP 同时也可以使用 CGLIB 代理 ,相对于 JDK 代理的接口,代理类更有必要 。当 业务对象 没有实现接口 时 会默认使用 CGLIB 代理 。因为编程到接口而不是类是一个很好的变成习惯,因此 类通常 会实现一个或者多个接口 。 当然也可以强制使用 CGLIB 代理 。

                

    1.2 @AspectJ 支持

 

                   1.2.1 开启 @AspectJ 支持

                            需要 aspectj  和  aspectjweaver 两个  jar 包 ,开启可通过以下两种方式 开启

使用 java  配置开启 使用 @EnableAspectJAutoProxy:

@Configuration@EnableAspectJAutoProxypublic class AppConfig {}

使用 XML 配置开启:

<aop:aspectj-autoproxy/>

        需要加入 schema


                   1.2.2 声明一个 切面

定义类并在类上加上注解@Aspect

package org.xyz;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class NotVeryUsefulAspect {}


配置到 Spring 的配置文件(或者扫描相关包):

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">    <!-- configure properties of aspect here as normal --></bean>


                    1.2.3 声明 一个切点 

@Pointcut("execution(* transfer(..))")// the pointcut expressionprivate void anyOldTransfer() {}// the pointcut signature

                          支持的 切入点指示符 (PCD)

                   ★ execution :  匹配方法执行连接点

                   ★  within   :

    @Pointcut("within(com.peptalk.controller.*)")     public void controllerPoint(){    }

                   ★  this:

                   ★  target:

                   ★  args:

                   ★  @target:

                   ★  @args:

                   ★  @within:

                   ★  @annotation:

    @Pointcut("@annotation(com.vastio.aop.OperationLog)") // 匹配标注该注解的连接点    public void methodCachePointcut() {      }

                  切点表达式的组合 (&& 、 || 、 !):  (XML 中使用 and 、 or 、 not 组合 多个切点表达式)

@Pointcut("execution(public * *(..))")private void anyPublicOperation() {}@Pointcut("within(com.xyz.someapp.trading..*)")private void inTrading() {}@Pointcut("anyPublicOperation() && inTrading()")private void tradingOperation() {}

                 一些表达式的栗子 :

execution(public * *(..))  // 匹配任意 public 类型方法的执行execution(* set*(..))  // 匹配 任意以 set开头的方法的执行execution(* com.xyz.service.AccountService.*(..)) // 匹配 AccountService 类中的任意方法的执行execution(* com.xyz.service.*.*(..)) // 匹配 service 包中任意方法的执行execution(* com.xyz.service..*.*(..))  // 匹配 service 包及其子包中 的任意方法的执行within(com.xyz.service.*) // service包内执行的任意连接点(spring aop 中的方法的执行)within(com.xyz.service..*) // service及其子包中的连接点this(com.xyz.service.AccountService) //实现 AccountService 接口的 代理的任意连接点target(com.xyz.service.AccountService) //实现 AccountService接口的目标对象的任意连接点args(java.io.Serializable)  //一个参数并且在运行传递的序列化的参数的任意连接点@target(org.springframework.transaction.annotation.Transactional) //目标对象有 @Transactional注解的任意连接点@within(org.springframework.transaction.annotation.Transactional)@annotation(org.springframework.transaction.annotation.Transactional)@args(com.xyz.security.Classified) 一个参数并且运行时传递的标有 @Classified的标注bean(tradeService)bean(*Service) spring bean 的名称 以 Service 结尾的任意连接点

                1.2.4  声明式的通知


                      ★ Before advce  : @Before

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class BeforeExample {    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") //在连接点之前执行    public void doAccessCheck() {        // ...    }}

XML 对应

<aop:aspect id="beforeExample" ref="aBean">    <aop:before        pointcut-ref="dataAccessOperation"        method="doAccessCheck"/>    ...</aop:aspect>


                   ★ After  returning advice  : @AfterReturning   返回值可以作为参数

                         returning 属性的名称必须要和 通知方法里面的参数名称对应,才能传递

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.AfterReturning;@Aspectpublic class AfterReturningExample {    @AfterReturning(        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",        returning="retVal")            //在连接点之后执行,可以将返回值作为参数    public void doAccessCheck(Object retVal) {        // ...    }}
XML 中对应

<aop:aspect id="afterReturningExample" ref="aBean">    <aop:after-returning        pointcut-ref="dataAccessOperation"        returning="retVal"        method="doAccessCheck"/>    ...</aop:aspect>



                 ★ After throwing advice :   @AfterThrowing   异常可以作为参数

                           throwing 属性的 名称 必须和通知方法里面的参数的 名称对应 ,才能传递

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.AfterThrowing;@Aspectpublic class AfterThrowingExample {    @AfterThrowing(        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",        throwing="ex")                  // 抛出异常后执行    public void doRecoveryActions(DataAccessException ex) {        // ...    }}
XML 中对应

<aop:aspect id="afterThrowingExample" ref="aBean">    <aop:after-throwing        pointcut-ref="dataAccessOperation"        throwing="dataAccessEx"        method="doRecoveryActions"/>    ...</aop:aspect>

                ★ After (finally) advice  : @After

                      匹配的方法是否顺利执行 通知都会执行。 所以 该通知 必须 处理 正常和 异常 两种情况 ,通常用作资源的释放 。

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.After;@Aspectpublic class AfterFinallyExample {    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")    public void doReleaseLock() {        // ...    }}
XML 中对应

<aop:aspect id="afterFinallyExample" ref="aBean">    <aop:after        pointcut-ref="dataAccessOperation"        method="doReleaseLock"/>    ...</aop:aspect>


              ★ Around advice  :@Around    ProceedingJoinPoint  可以作为参数  ,JoinPoint 的 子类

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.ProceedingJoinPoint;@Aspectpublic class AroundExample {    @Around("com.xyz.myapp.SystemArchitecture.businessService()")    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {        // start stopwatch    连接点之前执行的代码        Object retVal = pjp.proceed();        // stop stopwatch  连接点之后执行的代码        return retVal;    }}

 XML中对应

<aop:aspect id="aroundExample" ref="aBean">    <aop:around        pointcut-ref="businessService"        method="doBasicProfiling"/>    ...</aop:aspect>


        取得当前的连接点 JoinPoint

                     每个通知方法都 可以声明 JoinPoint 类型的参数作为 第一个参数 , 环绕 通知 声明的是 JoinPoint 的 子类 ProceedingJoinPoint

JoinPoint 提供的方法 :

Object [] getArgs();  //返回方法参数Object getThis();   // 返回代理对象Signature getSignature();  // 返回被通知的方法的描述Object getTarget(); // 返回目标对象


        传递参数给 advice

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")public void validateAccount(Account account) {    // ...}

或者

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")private void accountDataAccessOperation(Account account) {}@Before("accountDataAccessOperation(account)")public void validateAccount(Account account) {    // ...}

            Advice parameters and generics

generic type

public interface Sample<T> {    void sampleGenericMethod(T param);    void sampleGenericCollectionMethod(Collection<T> param);}

可以这样使用

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")public void beforeSampleMethod(MyType param) {    // Advice implementation}

不可以这样使用

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")public void beforeSampleMethod(Collection<MyType> param) {    // Advice implementation}

          确定参数的名称

                参数名称通过 java  反射 不能获取,所以使用如下的策略 :通过可选的 argNames 属性可以指定参数名称和注解名称

如果第一个参数为 JoinPoint 可以不用加入

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",        argNames="bean,auditable")public void audit(Object bean, Auditable auditable) {    AuditCode code = auditable.value();    // ... use code and bean}


  1.3 基于 schema 的 aop 支持


                1.3.1 定义切面

<aop:config>    <aop:aspect id="myAspect" ref="aBean">        ...    </aop:aspect></aop:config><bean id="aBean" class="...">    ...</bean>

                 1.3.2 声明切点

<aop:config>    <aop:pointcut id="businessService"        expression="execution(* com.xyz.myapp.service.*.*(..))"/></aop:config>

整体

<aop:config>    <aop:aspect id="myAspect" ref="aBean">        <aop:pointcut id="businessService"            expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>        <aop:before pointcut-ref="businessService" method="monitor"/>        ...    </aop:aspect></aop:config>


        1.6 代理机制

                     JDK 动态代理  或者  CGLIB 代理   :如果目标对象实现了至少一个 接口 那么默认使用 JDK 的动态代理 ,被目标对象实现的所有接口都会被代理 ;如果目标对象没有实现任何接口 ,那么则会默认使用 CGLIB 代理 。

            以下几个需要考虑的问题:

            ★   被 final 修饰的方法 不能被通知 ,因为他们不能被重写 。

            ★ Spring 3.2 之后 , CGLIB jar 包已经整合到 spring 的 核心包中 。意味着 基于 CGLIB 代理 可以像 JDK 动态代理那样 一直存在 。

            ★ Spring 4.0  之后,被代理的对象的构造函数不会再被调用两次 。

            强制使用 CGLIB 的 XML 配置 :

<aop:config proxy-target-class="true">    <!-- other beans defined here... --></aop:config>

      或者
<aop:aspectj-autoproxy proxy-target-class="true"/>

基于注解:

@EnableAspectJAutoProxy(proxyTargetClass = true)public class AppConfig {}

<一下内容暂略------->


<补充 :  Spring  AOP 拦截 controller

             正常我们使用 Spring AOP 横切  service 层很容易实现 ,而在横切 controller 层时却不起作用 。因为  service 层 是由 Spring 负责 扫描管理的 ,而 controller 层是由 Spring mvc 负责扫描管理的 ,如果要保证在横切 controller 层 时 也能够实现 aop 功能 ,就必须保证  Spring AOP 的 注解由 Spring mvc 负责扫描 ,即 开启 Spring aop 的配置要配置在 Spring mvc 的配置文件中 。


0 0