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

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
@EnableAspectJAutoProxy
public class AppConfig {

}


使用 XML 配置开启:

 

 

<aop:aspectj-autoproxy/>


        需要加入 schema

 

 

                   1.2.2 声明一个 切面

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

 

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public 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 expression
private 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;

@Aspect
public 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;

@Aspect
public 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;

@Aspect
public 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;

@Aspect
public 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;

@Aspect
public 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 的配置文件中 。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值