共计 2326 个字符,预计需要花费 6 分钟才能阅读完成。
上一节我们讲解了使用 AspectJ 的注解,并配合一个复杂的 execution(* xxx.Xyz.*(..))
语法来定义应该如何装配 AOP。
在实际项目中,这种写法其实很少使用。假设你写了一个SecurityAspect
:
@Aspect
@Component
public class SecurityAspect {@Before("execution(public * com.itranswarp.learnjava.service.*.*(..))")
public void check() {if (SecurityContext.getCurrentUser() == null) {throw new RuntimeException("check failed");
}
}
}
基本能实现无差别全覆盖,即某个包下面的所有 Bean 的所有方法都会被这个 check()
方法拦截。
还有的童鞋喜欢用方法名前缀进行拦截:
@Around("execution(public * update*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {// 对 update 开头的方法切换数据源:
String old = setCurrentDataSource("master");
Object retVal = pjp.proceed();
restoreCurrentDataSource(old);
return retVal;
}
这种非精准打击误伤面更大,因为从方法前缀区分是否是数据库操作是非常不可取的。
我们在使用 AOP 时,要注意到虽然 Spring 容器可以把指定的方法通过 AOP 规则装配到指定的 Bean 的指定方法前后,但是,如果自动装配时,因为不恰当的范围,容易导致意想不到的结果,即很多不需要 AOP 代理的 Bean 也被自动代理了,并且,后续新增的 Bean,如果不清楚现有的 AOP 装配规则,容易被强迫装配。
使用 AOP 时,被装配的 Bean 最好自己能清清楚楚地知道自己被安排了。例如,Spring 提供的 @Transactional
就是一个非常好的例子。如果我们自己写的 Bean 希望在一个数据库事务中被调用,就标注上@Transactional
:
@Component
public class UserService {// 有事务:
@Transactional
public User createUser(String name) {...}
// 无事务:
public boolean isValidName(String name) {...}
// 有事务:
@Transactional
public void updateUser(User user) {...}
}
或者直接在 class 级别注解,表示“所有 public 方法都被安排了”:
@Component
@Transactional
public class UserService {...}
通过@Transactional
,某个方法是否启用了事务就一清二楚了。因此,装配 AOP 的时候,使用注解是最好的方式。
我们以一个实际例子演示如何使用注解实现 AOP 装配。为了监控应用程序的性能,我们定义一个性能监控的注解:
@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {String value();
}
在需要被监控的关键方法上标注该注解:
@Component
public class UserService {// 监控 register()方法性能:
@MetricTime("register")
public User register(String email, String password, String name) {...}
...
}
然后,我们定义MetricAspect
:
@Aspect
@Component
public class MetricAspect {@Around("@annotation(metricTime)")
public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {String name = metricTime.value();
long start = System.currentTimeMillis();
try {return joinPoint.proceed();} finally {long t = System.currentTimeMillis() - start;
// 写入日志或发送至 JMX:
System.err.println("[Metrics]" + name + ":" + t + "ms");
}
}
}
注意 metric()
方法标注了 @Around("@annotation(metricTime)")
,它的意思是,符合条件的目标方法是带有@MetricTime
注解的方法,因为 metric()
方法参数类型是 MetricTime
(注意参数名是metricTime
不是MetricTime
),我们通过它获取性能监控的名称。
有了 @MetricTime
注解,再配合 MetricAspect
,任何 Bean,只要方法标注了@MetricTime
注解,就可以自动实现性能监控。运行代码,输出结果如下:
Welcome, Bob!
[Metrics] register: 16ms
练习
使用注解 +AOP 实现性能监控。
下载练习
小结
使用注解实现 AOP 需要先定义注解,然后使用 @Around("@annotation(name)")
实现装配;
使用注解既简单,又能明确标识 AOP 装配,是使用 AOP 推荐的方式。