AspectJ中Advice定义
Before advice
@Component
@Aspect
public class MoocAspect {
@Before("execution(*com.imooc.aop.aspectj.biz.Biz.*(..))")
public void before() {
//...
}
}
用@Before这个注解,里边可以有表达式,这里边的意思是在执行com.imooc.aop.aspectj包下以Biz结尾的类的所有方法时匹配Advice。
然后写一下自己的代码,在前一篇例子的基础上,MoocAspect类中加上代码:
@Component
@Aspect
public class MoocAspect {
@Pointcut("execution(* com.imooc.aop.aspectj.biz.*Biz.*(..))")
public void pointcut() {}
@Pointcut("within(com.imooc.aop.aspectj.biz.*)")
public void bizPointcut() {}
@Before("execution(* com.imooc.aop.aspectj.biz.*Biz.*(..))")
public void before() {
System.out.println("Before.");
}
}
然后在MoocBiz这个以Biz结尾的类中定义方法
@Service
public class MoocBiz {
public String save(String arg) {
System.out.println("MoocBiz save : " + arg);
return " Save success!";
}
}
然后看一下单元测试方法
@RunWith(BlockJUnit4ClassRunner.class)
public class TestAspectJ extends UnitTestBase {
public TestAspectJ() {
super("classpath:spring-aop-aspectj.xml");
}
@Test
public void test() {
MoocBiz biz = getBean("moocBiz");
biz.save("This is test.");
}
}
先输出Before.
然后输出MoocBiz save : This is test.
@Before中还可以写为”pointcut()”,输出的是相同的结果,这就是使用advice的两种方式,一种是直接定义expression表达式,另一种是使用已经存在的pointcut,也就是引用一个已经定义好的切面。
After returning advice
@AspectJ
public class AfterReturningExample{
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck(){
//...
}
}
在方法上用@AfterReturning注解,然后里边有expression的表达式,这个例子的是指定某个类的某一个方法。
有时候需要在通知体内得到返回的实际值,可以使用@AfterReturning绑定返回值的形式
@AspectJ
public class AfterReturningExample{
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal){
//...
}
}
returning对应的就是返回值,里边的内容在方法里声明。由于不确定返回值类型所以可以写成Object,若确定类型也可以写具体的。
继续之前的例子
在MoocAspect类中加上代码
@AfterReturning(pointcut="bizPointcut()", returning="returnValue")
public void afterReturning(Object returnValue) {
System.out.println("AfterReturning : " + returnValue);
}
单元测试类输出结果如下:
Before.
MoocBiz save : This is test.
AfterReturning : Save success!
After throwing advice
@AspectJ
public class AfterThrowingExample{
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions(){
//...
}
}
有时候想知道具体的异常是什么,或者得到异常信息,可以使用AfterThrowing绑定返回值的形式
@AspectJ
public class AfterThrowingExample{
@AfterThrowing(
point="com.xyz.myapp.SystemArchitecture.dataAccessOperation()"
throwing="ex")
public void doRecoveryActions(DataAccessException ex){
//...
}
}
继续例子,在切面类定义方法
@AfterThrowing(pointcut="pointcut()", throwing="e")
public void afterThrowing(RuntimeException e) {
System.out.println("AfterThrowing : " + e.getMessage());
}
然后在MoocBiz类中的save方法中修改,在返回之前抛出运行时异常
@Service
public class MoocBiz {
public String save(String arg) {
System.out.println("MoocBiz save : " + arg);
throw new RuntimeException(" Save failed!");
return " Save success!";
}
}
执行单元测试方法,输出:
Before.
MoocBiz save : This is test.
AfterThrowing : Save failed!
然后把抛出异常的部分注释掉
After(finally) advice
最终通知必须准备处理正常和异常两种返回情况,它通常用于释放资源。就像try、catch、finally中的finally。
@AspectJ
public class AfterFinallyExample{
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock(){
//...
}
}
回到例子,在切面类定义方法
@After("pointcut()")
public void after() {
System.out.println("After.");
}
执行单元测试输出结果
Before.
MoocBiz save : This is test.
After.
AfterReturning : Save success!
可以看到执行的顺序,这里的after和afterreturnin和之前顺序不同,原因的话通过网上查找,用注解的方式after本来就是在after-returning之前。但是如果在配置文件中,after在after-returning之后。
Around advice
环绕通知使用@Around注解来声明,通知方法的第一个参数必须是ProceedingJoinPoint类型
在通知内部调用ProceedingJoinPoint的proceed()方法会导致执行真正的方法,传入一个Object[]对象,数组中的值将被作为参数传递给方法
@AspectJ
public class AroundExample{
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp)throws Throwable{
//...
Object retVal=pjp.proceed();
//...
return retVal;
}
}
例子
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Around 1.");
Object obj = pjp.proceed();
System.out.println("Around 2.");
System.out.println("Around : " + obj);
return obj;
}
输出为:
Around 1.
Before.
MoocBiz save : This is test.
Around 2.
Around : Save success!
After.
AfterReturning : Save success!
我们发现around和before之间的顺序也有所变化,这应该也是使用注解和使用xml配置方式的差别。
对Advice的扩展
给advice传递参数
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account){
//...
}
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void accountDataAccessOperation(Account account){}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account){
//...
}
第一种是@Before中用了表达式组合了args,args是要声明的是要传入的参数,然后在切面的方法里去接收这个参数,里边的内容是和名称一样,而不是和类型相同,这里边都是account。
另外一种是将切入点和advice分开写的方式,在advice中引用pointcut,两种方式是等效的。
这是一种方式,使用具体类型的参数。这里Account account,方法参数可以是任何类的对象。
还有另一种方式,是先定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable{
AuditCode value();
}
这是一个运行时注解,注解在方法上,然后在advice中引用这个注解
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable){
AuditCode code=auditable.value();
//...
}
@Before中还是一个组合的表达式,@annotation指定这是一个注解,注解对应的参数名称是下边这个auditable这个参数名称,然后从中得到注解的值auditable.value()。在某一个方法上如果用了这个注解,肯定会给value赋一个值,然后在@Before里就能得到这个值。
对于第一种方式,通常用来获取参数,做一下判断或者记录。对于第二种的注解形式,都常有两种用法,一种是做一种记录(有可能只是记录这个注解运用了哪些方法),另外一种是判断方法上是否加了某注解或者方法上加了注解对应的值,根据注解的不同或者注解的value的不同,在切面里做一些操作。
来看例子:
第一种,现在切面类写如下代码
@Before("pointcut() && args(arg)")
public void beforeWithParam(String arg) {
System.out.println("BeforeWithParam." + arg);
}
单元测试类运行后会对应输出
BeforeWithParam.This is test.
然后是第二种注解的形式,先定义一个注解
package com.imooc.aop.aspectj;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MoocMethod {
String value();
}
然后在方法上加上这个注解
@Service
public class MoocBiz {
@MoocMethod("MoocBiz save with MoocMethod.")
public String save(String arg) {
System.out.println("MoocBiz save : " + arg);
return " Save success!";
}
}
然后在切面类添加代码
@Before("pointcut() && @annotation(moocMethod)")
public void beforeWithAnnotaion(MoocMethod moocMethod) {
System.out.println("BeforeWithAnnotation." + moocMethod.value());
}
这里边的value就是应该为刚才写的MoocBiz save with MoocMethod.
单元测试结果为:
BeforeWithAnnotation.MoocBiz save with MoocMethod.
Advice的参数及泛型
Spring AOP可以处理泛型类的声明和使用方法的参数
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
}
在这里确定了T是MyType类型,param与之对应
对于集合的使用方式也差不多
@Before("execution(*..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param){
//Advice implementation
}
Advice参数名称
通知和切入点注解有一个额外的“argNames”属性,它可以用来指定所注解的方法的参数名
如果第一参数时JoinPoint、ProceedingJoinPoint、JoinPoint.StaticPart,那么可以忽略它
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean,Auditable auditable){
Auditable code=auditable.value();
//...use code and bean
}
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp,Object bean,Auditable auditable){
Auditable code=auditable.value();
//...use code and bean
}
Introductions
在前边介绍Spring AOP时候介绍的Introduction内容是非常接近的,不过是另一种实现方式
允许一个切面声明一个通知对象实现指定接口,并且提供了一个接口实现类来代表这些对象
introduction使用@DeclareParents进行注解,这个注解用来定义匹配的类型拥有一个新的parent
例如:给定一个接口UsageTracked,并且该接口拥有DefaultUsageTracked的实现,接下来的切面声明了所有的service接口的实现都实现了UsageTracked接口
@Aspect
public class UsageTracking{
@DeclareParents(value="com.xyz.myapp.service.*+",defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before(com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked){
usageTracked.incrementUseCount();
}
}
DeclareParents匹配com.xyz.myapp.service这个包下所有的接口,然后指定他们都有一个默认的实现,都实现了UsageTracked接口,也就是它们都有一个公共的父类,这个公共的父类有一个实现DefaultUsageTracked。接下来在使用的时候,每当调用这个接口的时候,都会有一个默认的父类,我们都可以把这个接口强制转换成UsageTracked来进行调用。