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("异常通知");
}
}
<bean id="myAdvice" class="dao.impl.MyAdvice"/>
<aop:config>
<aop:pointcut expression="execution(* *..*.*myDowork*(..))" id="pc"/>
<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>
expression="execution(表达式)"
作用:定位到符合表达式的方法,将其声明为切入点;
定位的条件有五个:
例:execution(* *..*.*myDowork*(..))
:
表示任意包,类,形参类型,返回值类型.方法名含有myDowork的方法.
例:execution(java.lang.String a.b.A.*(..))
:
表示a.b包下的A类,返回值为String的所有方法,
注意:由于我们需要导入很多jar,所以会有很多方法名重复,尽量使用execution表达式时候,加入包名.
:前置通知,方法执行前执行
:后置通知, 方法执行完执行,(出现异常不执行)
:最终通知,方法执行完执行,(出现异常依然执行)
异常通知,方法出现异常后执行
环绕通知,可以一次性完成所有通知,可以修改形参,一般配合注解使用不用在Spring的配置文件中配置,直接在通知的类中用注解方式告诉Spring织入切面方式.
注解所在的包org.aspectj.lang.annotation.
<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);
会报错.
pjp.getSignature().getDeclaringTypeName()
目标对象的权限类名pjp.getSignature().getName()
目标对象调用的方法名pjp.getSignature().getDeclaringType()
目标对象的字节码对象