AOP简单总结

简单看了下其他网站的aop都写的很简单啊,感觉理解困难,于是跟了廖雪峰的教程,写的很容易懂

https://liaoxuefeng.com/books/java/spring/aop/index.html

跟完之后手搓一下自己的理解


什么是aop

Aspect Oriented Programming,面向切面编程。什么是切面呢?我们不妨思考一下,日志在业务场景场景中的实现——系统每进行一次服务,日志就应该自动打印出来。这些服务调用的方法,可能分散在各个文件中,如果为了在每次执行业务后打印出日志,那我们需要在每个方法的后面加上打印日志服务,势必会造成冗余和修改困难。于是我们想,日志打印在每个业务方法中执行的逻辑是一样的,我们可不可以把这种同样实现的方法抽象出来,写在一起,然后在每次执行需要打印日志的业务方法时再注入呢?这就是aop。面对同样的逻辑实现的方法,我们可以提出一个切面。在业务逻辑执行前/后,Ioc执行到相应注解的时候,会自动帮我们
注入。这样,我们只需要在需要日志打印的方法前加对应的annotaion,再统一写一个Aspect就可以了。所以aop的实现其实是一种动态代理。


aop拦截器

aop的拦截器展示了aop是如何实现拦截、注入切面代码的。
拦截器有以下类型:

  1. @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
  2. @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
  3. @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
  4. @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
  5. @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.aop;

@Aspect
@Component
public class LogginAspect {
//在执行UserService的每个方法之前执行
@Before("execution(public * com.example.aop.UserService.*(..))")
public void doAccessCheck(){
System.err.println("[Before] do access check ...");
}

//在执行MialService的每个方法前后执行;
//@Around注解,它和@Before不同,@Around可以决定是否执行目标方法
@Around("execution(public * com.example.aop.MailService.*(..))")
public Object doLogging(ProceedingjoinPoint pjp) throws Throwable {
//我们在doLogging()内部先打印日志,再调用方法,最后打印日志后返回结果。
System.err.println("[Around] start " + pjp.getSignature());
Object retVal = pjp.proceed();
System.err.println("[Around] done " + pjp.getSignature());
return retVal;
}

}

aop具体是如何实现的

深入了解aop的实现,我们会发现,实际上aop是spring使用CGLIB生成proxy。
什么意思呢?AspectJ将Aspect织入原类的时候,并没有改变原类中的任何字段/方法,实际上是创建了一个新的类,继承了原类,并做了对应方法的覆盖修改,实现新的方法。
需要注意的是,注册在Ioc容器中的Bean不是原始类而是proxy。而因为Aspect是针对方法设计的,而不是针对成员变量的共享设计的,因此,在proxy的创建中,proxy中的成员变量是不会被做初始化操作的!因此,当注入proxy且使用” . “访问proxy的成员变量时,会NPE。
如何修复这个问题?我们需要使用getter/setter让proxy把访问成员变量的事情转交给原类的方法去做,这样得到的就是原类中已被初始化的数值。
在proxy这个特性下,我们也了解到,原来java写这么多getter/setter不是摆设!


Commentarii · 评论