使用SpringAOP
面向切面编程
Aspect Oriented Programming(AOP)面向切面编程是一种范式和思想,并不是特指某一种编程语言。下面这句话是知乎上的一个总结:
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
可以看到,将某一种通用的非功能代码,从业务逻辑中剥离,形成一个切面并在指定的时机切入到指定位置上的思想,就是AOP得思想。同样,减少重复代码、专注业务、分离功能代码和非功能代码就是AOP的主要职责。SpringAOP框架可以在运行时将切面代码植入切点,在功能代码周围切入非功能代码,达到不侵入业务代码的情况下,完成指定操作。可以说,AOP是OOP的延续和补充,大锤砸墙、小锤抠缝,配合好了才能达到想要的效果。
应用场景
- 权限控制
- 缓存控制
- 事物控制
- 审计日志
- 性能监控
- 分布式追踪
- 异常处理
AOP的主要应用场景就是在某一业务逻辑的前后(切入点),插入一些与业务逻辑无关却必要的工作(切面)。想象一下在某一层方法前后,都添加调用该方法的Log信息,以往的做法会在每个方法前后分别添加一行log.info(...)
,像这样:
funA (args) {
log.info(funA start);
{funAbody}
log.info(funA end);
}
可是这样会产生大量重复代码,因为每个方法前后都需要添加这两行代码。
Don’t Repeat Yourself.
这是在我们常听到的一句话,也是我们义不容辞的使命。在学习面向对象后,就会想,应该可以把这个重复的代码可以抽象到父类里面,子类实现父类的抽象方法,像这样:
abstract class BaseClass {
fun (args) {
log.info(funA start);
funA(args);
log.info(funA end);
}
abstract funA(args);
}
这样代码是少了,可是使用的灵活性上面收到了很大的限制。我们必须要继承父类才可以实现方法,同时方法调用时也只能通过父类的包装方法来完成调用。在使用的时候就会很不方便。于是,面向切面就出现了。AOP解决此类问题的方法是把这些与方法逻辑无关,却对整体有帮助的代码,抽象到一个切面中,切入到每个方法调用的前后。这样,这些重复的代码成功的完成了抽象,剩下的就只需要在合适的切入点将这些切面切入到方法调用前后。
AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
SpringAOP
SpringAOP使用示例
想象如下场景:想要对Service的方法调用前,对参数进行校验。接下来通过一下的代码,来看一下在SpringAOP中怎么将校验的代码织入到制定的位置:
/**
* 定义一个切面,切点和要织入的方法
* 1 使用@Conponent将类托管给Spring (必须要有,不然不会织入切面代码)
* 2 使用@Aspect注解声明切面
* 3 使用@Pointcut 声明切点(代码织入点)
* 4 声明Advice: 注解声明织入代码时机(@After,@Before,@Around,@AfterThrowing,@AfterReturning)
* 方法体内写具体的织入代码
*/
@Component
@Aspect
public class ParamCheck {
@Pointcut("within(com.lieh666.cloud.aop..*Service*)")
public void matchPak(){}
@Before(value = "matchPak()")
public void checkParamBefore() {
System.out.println("####check param before");
}
@After(value = "matchPak()")
public void checkParamAfter() {
System.out.println("####check param after");
}
}
/**
* 定义一个被切入的类,此次为Service
*/
@Service
public class UserService {
public void sayHi (String name) {
System.out.println("hi," + name);
}
}
/**
* 实现一个Test来测试是否织入成功
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {SpringAOPService.class})
public class UserServiceTest {
@Autowired
UserService userService;
@Test
public void sayHi() throws Exception {
userService.sayHi("lieh");
}
}
原理
AOP主要是基于动态代理来实现的,同时AOP也是动态代理模式的一种典型应用。Spring在此基础上加以扩展,添加了Pointcut、Advisor等一些接口使其更加灵活。
SpringAOP基于以下两种实现方式:
1. jdk动态代理
2. Cglib动态代理
SpringAOP对两种代理方式的选择策略如下:
1. 如果目标对象实现了接口,则默认选择JDK动态代理
2. 如果目标对象没有实现接口,则采用Cglib进行代理
3. 如果目标对象实现了接口,且强制Cglib代理,则使用Cglib代理
AOP的主要思想,就是拆分、解耦功能模块和非功能模块。并且提高了非功能模块代码的复用。
问题及注意事项
1、区分业务逻辑代码和非业务逻辑代码,不要将业务逻辑代码放在AOP中,不然会导致业务功能不清晰。
2、不能代理内部方法调用,内部调用会使用this关键字,而不是生成的代理类。
3、不能代理private、final、static方法。
参考文章:
- JavaWeb过滤器.监听器.拦截器
- 什么是面向切面编程AOP? - 知乎
- java动态代理原理及解析