Spring的AOP功能就是面向切面编程.我们从Spring容器取出的值,就是已经被重新包装过代理对象
概念
- 通知: 要切入的内容
- 切点: 要切入的地方
- 切面织入: 将切面织入类的方法中,切面=通知+切点
通知的类
在该类中声明各自通知,每个通知+切点,都能组成一个切面
public class MyAdvice {
//前置通知
public void before() {
System.out.println("前置通知");
}
//后置通知
public void afterReturning() {
System.out.println("后置通知");
}
//最终通知
public void after() {
System.out.println("最终通知");
}
//异常通知
public void afterThrowing() {
System.out.println("异常通知");
}
}
AOP的配置
- 注入通知类的bean
- 设置aop–声明切点
- 设置aop–织入切面
<!-- 注入通知类的bean -->
<bean id="myAdvice" class="dao.impl.MyAdvice"/>
<aop:config>
<!-- 设置1个切点by execution表达式 -->
<aop:pointcut expression="execution(* *..*.*myDowork*(..))" id="pc"/>
<!-- 织入4个切面 -->
<aop:aspect ref="myAdvice">
<aop:after method="after" pointcut-ref="pc"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc"/>
<aop:before method="before" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
1. execution表达式
expression="execution(表达式)"
作用:定位到符合表达式的方法,将其声明为切入点;
定位的条件有五个:
- 方法的形参类型
- 方法的返回值类型
- 方法所在的包
- 方法所在的类
- 方法的名称
例:execution(* *..*.*myDowork*(..))
:
表示任意包,类,形参类型,返回值类型.方法名含有myDowork的方法.
例:execution(java.lang.String a.b.A.*(..))
:
表示a.b包下的A类,返回值为String的所有方法,
注意:由于我们需要导入很多jar,所以会有很多方法名重复,尽量使用execution表达式时候,加入包名.
2. 通知类型
<aop:before>
:前置通知,方法执行前执行<aop:after-returning>
:后置通知, 方法执行完执行,(出现异常不执行)<aop:after>
:最终通知,方法执行完执行,(出现异常依然执行)<aop:after-throwing>
异常通知,方法出现异常后执行<aop:around>
环绕通知,可以一次性完成所有通知,可以修改形参,一般配合注解使用
AOP的注解
不用在Spring的配置文件中配置,直接在通知的类中用注解方式告诉Spring织入切面方式.
注解所在的包org.aspectj.lang.annotation.
- 声明使用注解
<aop:aspectj-autoproxy/>
- 通知的类:@Aspect
- 切入点的方法(方法名=id);@Pointcut(“execution表达式”)
- 织入切面:在通知类的方法上
- @通知类型(“切点id()”)或@通知类型(“execution表达式”的String)
- 通知类型中的属性
- value/pointcut=切点
- throwing=”ex”,异常通知的Throwable 对象为ex
- -
<!-- 注入通知类的bean -->
<bean id="myAdvice" class="dao.impl.MyAdvice"/>
普通的切面织入
@Aspect
public class MyAdvice {
//声明切点方式1 //static+final
public static final String EXP="execution(* *..*.*myDowork*(..))";
//声明切点方式2
@Pointcut("execution(* *..*.*myDowork*(..))")
public void pc() {}
@Before("pc()")
public void before() {
System.out.println("前置通知");
}
@AfterReturning(EXP)
public void afterReturning() {
System.out.println("后置通知");
}
@After("pc()")
public void after() {
System.out.println("最终通知");
}
@AfterThrowing("pc()")
public void afterThrowing() {
System.out.println("异常通知");
}
}
异常的切面织入
@Aspect
public class MyAdvice {
//声明切点
@Pointcut("execution(* *..*.*myDowork*(..))")
public void pc() {}
//throwing="ex"表示声明异常的对象
@AfterThrowing(pointcut="pc()",throwing="ex")
public void afterThrowing(Throwable ex) {
System.out.println("异常通知");
System.out.println(ex.getMessage());
}
}
环绕的切面织入
@Aspect
public class MyAdvice {
//声明切点
public static final String EXP="execution(* *..*.*myDowork*(..))";
@Around(EXP)
public Object around(ProceedingJoinPoint pjp) {
try {
System.out.println("前置通知");
Object ret = pjp.proceed(); //执行目标对象的方法
System.out.println("后置通知");
return ret; //返回值
}catch(Throwable ex) {
System.out.println("异常通知");
System.out.println(ex.getMessage());
}finally {
System.out.println("最终通知");
}
return null;
}}
参数的传递
作用:通过判断参数值,来选择不同的处理机制
1. 表达式中需要加入参数
2. 利用argNames属性,声明参数
3. 对于符合execution表达式,但不符合参数类型的方法,不会被织入切面
@Aspect
public class MyAdvice {
//声明切点方式1 static+final
public static final String EXP="execution(* *..*.*myDowork*(..))&& args(str,i)";
@Before(value=EXP,argNames="str,i")
public void before2(String str,Integer i) {
System.out.println("123");
System.out.println(str+i);
}
//声明切点方式2
@Pointcut(value="execution(* *..*.*myDowork*(..)) && args(str,i)",argNames="str,i")
public void pc(String str,Integer i) {}
@Before(value="pc(str,i)",argNames="str,i")
public void before(String str,Integer i) {
System.out.println(str+i);
}
}
参数的修改
只有在环绕通知中可以修改参数.
1.获得参数的数组:Object[] args = pjp.getArgs();
2.修改参数的数组元素:args[i]=XXX;
3.执行目标对象方法时候,传入新数组;Object ret = pjp.proceed(args);
@Aspect
public class MyAdvice {
public static final String EXP = "execution(* *..*.*myDowork*(..))";
@Around(EXP)
public Object around(ProceedingJoinPoint pjp) {
try {
Object[] args = pjp.getArgs();
args[0]="hahah";
Object ret = pjp.proceed(args);
return ret;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
注意点:环绕对所有符合表达式的方法,都会进行织入切面
即没有形参的方法,且符合表达式,也会被织入切面 而加入形参pjp.proceed(args);
会报错.
- 要严格写好execution表达式
- 判断数组对象长度和大小来进行选择处理方式
pjp.getSignature().getDeclaringTypeName()
目标对象的权限类名pjp.getSignature().getName()
目标对象调用的方法名pjp.getSignature().getDeclaringType()
目标对象的字节码对象