前言
上一篇讲了jdk和cglib动态代理,实现了一个简单的责任链模式,并模拟了aop几种注解的调用顺序,这里就详细讲述aop
AOP
面向切面编程,网上有很多定义,面向对象的补充,只关注一个切点。我只举一个项目中遇到的切面,平台对设备读写定义了两个顶层接口ReadService、WriteService,读写service中的每个方法调用前都需要判断设备是否在线,且WriteService中的接口还需要判断设备是否处于私钥状态(写操作需要加密,设备必须安装了私钥)。以前是将两种判断写成两个方法,在读写service中到处可见,有时可能还会遗漏。使用切面就能很好的解决这个问题。我理解的切面:在业务中出现很多相同的功能,若在使用抽离方法仍显得冗余时可考虑切面,切面在进入目标方法前会加入一层逻辑,或者在目标方法执行后加入一层逻辑,进一步提高类聚。编程思想:抽离变化点,提取公因式
术语
先上代码,后面看看代码与术语的对应关系
@Aspect
@Component
public class F1AspectJ {
@Pointcut("execution(* com.jm.demo.demo1.User.f1(..))")
public void f1PointCut(){
}
@Before("execution(* com.jm.demo.demo1.User.f1(..))")
public void before() {
System.out.println("before...");
}
@After("f1PointCut()")
public void After() {
System.out.println("After...");
}
@AfterReturning("f1PointCut()")
public void AfterReturning() {
System.out.println("AfterReturning...");
}
@Around("f1PointCut()")
public void aroud(ProceedingJoinPoint pj){
try{
System.out.println("around 之前");
pj.proceed();
System.out.println("around 之后");
}catch (Throwable e){
e.getMessage();
}
}
@AfterThrowing(pointcut = "f1PointCut()",throwing = "ex")
public void ex(Throwable ex){
System.out.println("有异常:"+ex.getMessage());
}
}
public class UserA implements User, InitializingBean, ApplicationContextAware, DisposableBean {
private int order = 0;
@Autowired
private UserB userB;
public UserA() {
System.out.println("constructor order:" + order++);
}
@PostConstruct
public void postConstruct() {
System.out.println("postContruct order:" + order++);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean order:" + order++);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("ApplicationContextAware order:" + order++);
}
public void initMethod() {
System.out.println("initMethod..." + order++);
}
public void destroyMethod() {
System.out.println("destroyMethod..." + order++);
}
@PreDestroy
public void preDestroy() {
System.out.println("preDestroy..." + order++);
}
@Override
public void destroy() throws Exception {
System.out.println("destroy..." + order++);
}
@Override
public void f1() {
System.out.println("userA f1...");
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.registerShutdownHook();
UserA userA = context.getBean("userA", UserA.class);
userA.f1();
}
}
输出结果:
Advice 通知
Tag interface for Advice. Implementations can be any type of advice, such as Interceptors
用于通知的标记接口,实现可以是任何类型的通知,比如拦截器。可以理解为上一篇文章责任链模式图中的拦截器,所有类型的通知都会变成一个Advice接口的实现,在userA.f1()处debug
可以看到所有的通知变成了Advice的实现,且添加拦截器的顺序为Around、Before、After、AfterReturning、AfterThrowing
-
@Around 目标方法的前后添加逻辑,如统计方法调用调用时长
-
@Before 目标方法调用前执行
mi.proceed()进入链条逻辑,触发下一个拦截器
-
@After 目标方法执行后最终执行,被放在了finally中
-
@AfterReturning 目标方法返回后调用
-
@AfterThrowing 目标方法异常时被执行
执行流程:
通过顶层的链条遍历拦截器达到拦截器层层调用的效果。很多文章说这里是递归,其实不是,递归是自己调用自己,并在一定条件推出,而这里只是拦截器的层层调用,调用链比较长而已
JointPoint 连接点
能够被切入的位置,代码中execution定义的就是连接点。JoinPoint属于org.aspectj包中源码,被spring中的MethodInvocationProceedingJoinPoint实现,用于解析和处理execution中的内容
PointCut 切点
可理解为连接点的别名,若没有PointCut,某个目标方法存在多种通知时,都需要在注解里面定义execution连接点,使用PointCut给连接点定一个别名,通知注解可以直接使用别名,减少冗余代码
Aspect
标注某个类为切面类
思考题
@Service
public class TxService {
@Autowired
private TxService txService;
@PostConstruct
public void init(){
//是否相等,为什么
System.out.println(txService == this);
}
}
@Service
public class TxService {
@Autowired
private TxService txService;
@PostConstruct
public void init(){
//是否相等,为什么
System.out.println(txService == this);
}
@Transactional
public void f1(int x){
}
}