上一篇:Spring学习笔记(七、Spring AOP API)
一、AspectJ介绍与Pointcut注解应用
1. AspectJ
- @AspectJ的风格类似纯java注解的普通java类。
- Spring可以使用AspectJ来做切入点分析。
- AOP的运行时仍旧是纯的Spring AOP,对AspectJ的编译器或者织入无依赖性。
2. Spring中配置AspectJ
- 对@AspectJ支持可以使用XML或Java风格配置。
-
确保AspectJ的aspectjweaver.jar库包含在应用程序(版本1.6.8或更高版本)的classpath中。
3. @Aspect注解
- AspectJ切面使用@Aspect注解配置,拥有@Aspect注解的任何bean,将被Spring自动识别并应用。
- 用@Aspect注解的类可以有方法和字段,他们也可能包括切入点(pointcut)、通知(advice)、和引入(introduction)声明。
- @Aspect注解是不能通过类路径自动检测发现的,所以需要配合使用@Component注释或者在xml中配置bean。
- 一个类中的@Aspect注解标识它为一个切面,并且将自己从自动代理中排除。
4. pointcut
- 一个切入点通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解,方法返回类型必须为void。
指示符 | 说明 |
---|---|
execution | 匹配方法执行的连接点 |
within | 限定匹配特定类型的连接点 |
this | 匹配特定连接点的bean引用,是指定类型的实例的限制 |
target | 限定匹配特定连接点的目标对象是指定类型的实例 |
args | 限定匹配特定连接点的参数是给定类型的实例 |
@target | 限定匹配特定连接点的类执行对象的具有给定类型的注解 |
@args | 限定匹配特定连接点实际传入的参数的类型具有给定类型的注解 |
@within | 限定匹配到内具有给定的注释类型的连接点 |
@annotation | 限定匹配特定连接点的主体具有给定的注解 |
创建一个切面类:AmberAspect
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
}
5. 组合pointcut
- 切入点表达式可以通过&&、||和!进行组合,也可以通过名字引用切入点表达式。
-
通过组合,可以建立更加复杂的切入点表达式。
6. 定义良好的pointcuts
- AspectJ是编译器的AOP。
- 检查代码并匹配连接点与切入点的代价是昂贵的。
- 一个好的切入点应该包括以下几点:
- 选择特定类型的连接点。如:execution、get、set、call、handler
- 确定连接点范围,如:within、withincode
- 匹配上下文信息,如:this、target、@annotation
二、Advice定义及实例
1. Before advice
更新AmberAspect 切面类:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("execution(* com.amber.aop.biz.*Biz.*(..))")
public void before(){
System.out.println("前置通知!");
}
}
创建业务类AspectBiz :
package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
@Service
public class AspectBiz {
public String save(String args){
System.out.println("执行AspectBiz的save方法,参数:"+args);
return "Save Success";
}
}
applicationContext:
测试类:
@Test
public void test17() {
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.save("淡雅如菊,温润如玉");
}
结果:
修改AmberAspect 切面类,将前置通知的表达式替换成同样表达式的pointcut()方法:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
}
结果:
2. After Returning Advice
- 有时候需要在通知体内得到返回的实际值,可以使用@AfterReturning绑定返回值的形式。
更新AmberAspect 切面类,增加After Returning通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值为:"+returnValue);
}
}
结果:
3. After throwing advice
- 有时候需要在通知体内得到返回的实际值,可以使用@AfterThrowing绑定返回值的形式。
更新AmberAspect 切面类,增加After Throwing通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值为:"+returnValue);
}
@AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("异常后通知,异常为:"+e);
}
}
修改类:
package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
@Service
public class AspectBiz {
public String save(String args){
System.out.println("执行AspectBiz的save方法,参数:"+args);
throw new RuntimeException("Save failed");
//return "Save Success";
}
}
结果:
3. After(finally) advice
- 最终通知必须准备处理正常和异常两种返回情况,它通常用于释放资源。
更新AmberAspect 切面类,增加After通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值为:"+returnValue);
}
@AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("异常后通知,异常为:"+e);
}
@After("bizPointcut()")
public void after(){
System.out.println("后置通知!");
}
}
结果:
将业务类AspectBiz异常注释:
package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
@Service
public class AspectBiz {
public String save(String args){
System.out.println("执行AspectBiz的save方法,参数:"+args);
// throw new RuntimeException("Save failed");
return "Save Success";
}
}
结果:
4. Around advice
- 环绕通知使用@Around注解来声明,通知方法的第一个参数必须是ProceedingJoinPoint类型。
- 在通知内部会调用ProceedingJoinPoint的proceed()方法会导致执行真正的方法,传入一个Object[]对象,数组中的值将被作为参数传递给方法。
更新AmberAspect ,增加Around通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspect {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut(){
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("前置通知!");
}
@AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("返回后通知,返回值为:"+returnValue);
}
@AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("异常后通知,异常为:"+e);
}
@After("bizPointcut()")
public void after(){
System.out.println("后置通知!");
}
@Around("bizPointcut()")
public Object around(ProceedingJoinPoint pjp)throws Throwable{
System.out.println("环绕通知,pjp.proceed();执行前!");
Object obj=pjp.proceed();
System.out.println("环绕通知,pjp.proceed();执行后! 返回值为:"+obj);
return obj;
}
}
结果:
三、给Advice传递参数
1. 给Advice传递参数
- 创建一个AmberAspectTwo切面类,增加带参的前置通知,传入普通参数,将之前的AmberAspect类的@Aspect注释掉:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspectTwo{
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut() {
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut() {
}
@Pointcut("pointcut()&&args(arg)")
public void before(String arg) {
System.out.println("前置通知,获取参数为:" + arg);
}
}
结果:
- 创建一个自定义注解:
package com.amber.aop.aspectj;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by amber on 2017/6/20.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AmberMethod {
String value();
}
修改业务类方法,为其添加一个自定义注解:
package com.amber.aop.biz;
import com.amber.aop.aspectj.AmberMethod;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
* Created by amber on 2017/6/18.
* 业务类
*/
@Service
public class AspectBiz {
@AmberMethod("这是我自定义的注解")
public String save(String args){
System.out.println("执行AspectBiz的save方法,参数:"+args);
// throw new RuntimeException("Save failed");
return "Save Success";
}
}
更新AmberAspectTwo切面类,增加带注解参数的后置通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspectTwo {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut() {
}
@Pointcut("within(com.amber.aop.biz.*)")
public void bizPointcut() {
}
@Before("pointcut()&&args(arg)")
public void beforeWithArgs(String arg) {
System.out.println("前置通知,获取参数为:" + arg);
}
@After("pointcut()&&@annotation(amberMethod)")
public void afterWithAnnotation(AmberMethod amberMethod) {
System.out.println("后置通知,获取参数为:" + amberMethod.value());
}
}
结果:
修改AmberAspectTwo 切面类修改其后置通知的值引用bizPointcut()方法的组合切入点。
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* Created by amber on 2017/6/20.
*/
@Component
@Aspect
public class AmberAspectTwo {
@Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
public void pointcut() {
}
@Pointcut("within(com.amber.aop.biz.*) && @annotation(amberMethod)")
public void bizPointcut(AmberMethod amberMethod) {
}
@Before("pointcut()&&args(arg)")
public void beforeWithArgs(String arg) {
System.out.println("前置通知,获取参数为:" + arg);
}
@After("bizPointcut(amberMethod)")
public void afterWithAnnotation(AmberMethod amberMethod) {
System.out.println("后置通知,获取参数为:" + amberMethod.value());
}
}
结果:
2. Advice的参数及泛型
-
Spring AOP可以处理泛型类的声明和使用方法的参数。
3. Advice参数名称
-
通知和切入点有一个额外的“argName”属性,它可以用来指定所注解的方法的参数名。
-
如果第一个参数是JoinPoint,ProceedingJoinPoint,JoinPoint.StaticPart,那么可以忽略它。
3. Introductions
- 允许一个切面声明一个通知对象实现指定接口,并且提供了一个接口实现类来代表这些对象。
-
Introduction使用@DeclareParents进行注解,这个注解用来定义匹配的类型拥有一个新的parent。
4. 切面实例化模型
- 这是一个高级主题
- “perthis” 切面通过指定@Aspect注解perthis子句实现。
- 每个独立的service对象执行时都会创建一个切面实例。
-
service对象的每个方法在第一次执行的时候创建切面实例,切面在service对象失效的时候同时失效。