您的位置:首页 > 编程语言 > Java开发

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

2016-07-20 14:12 483 查看
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 的配置文件中 。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: