为什么要用切面
现在面向切面编程(AOP)早就是非常normal的概念了,不光是java(Spring),其他很多语言框架也借鉴了AOP的思想,实现了面向切面编程的功能。AOP将一些非常繁琐的、通用的检查收敛到某些点上(Pointcut),由各个业务流程自行选择使用,而且这种使用通常来说是对业务代码无侵入性的(或者侵入很少),使得业务的开发人员们可以更集中精力在具体的业务流程上,这种业务与条件检查的分离,对于业务代码的可读性、可维护性都大有好处,这也是AOP概念得以流行的原因。
为什么要使用基于注解的AOP
由于Spring家族的不断膨胀,以前基于xml配置的使用方式使各个开发者头疼不已,维护一个大型系统的配置文件居然成了一笔不小的人力成本,显然,从Spring framework到SpringBoot,从以前的集中一站式应用到现在的微服务应用,技术的发展的趋势是不断的应用拆分和精简,将单个系统的规模控制在一个可控的范围内,而基于注解的配置方式显然要比以前的基于xml的方式精简太多,而且注解通常是与代码放在一起,提升了代码的可读性。
Spring中的AOP实现示例
Spring中的AOP默认通过AspectJ实现,关于AspectJ的设计和实现我们以后再讨论,今天在这里只展示一个简单的切面实现示例。
假设我们要实现的切面功能是打印方法名,首先我们定义一个切面注解@PrintMethodName,它可以使用在Class和Method上。
1 package com.chenrui.aspect;
2
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 @Target({ElementType.TYPE, ElementType.METHOD})
9 @Retention(RetentionPolicy.RUNTIME)
10 public @interface PrintMethodName {
11
12 }
然后我们实现我们的切面逻辑,需要在我们的Class上加上@Aspect注解,用于标注这是一个切面实现,注意也要加上@Component注解,否则不会被Spring识别。
1 package com.chenrui.aspect;
2
3 import org.aspectj.lang.ProceedingJoinPoint;
4 import org.aspectj.lang.annotation.Around;
5 import org.aspectj.lang.annotation.Aspect;
6 import org.aspectj.lang.annotation.Pointcut;
7 import org.springframework.stereotype.Component;
8
9 @Aspect
10 @Component
11 public class PrintMethodNameAspect {
12
13 @Pointcut("@within(com.chenrui.aspect.PrintMethodName) || @annotation(com.chenrui.aspect.PrintMethodName)")
14 public void pointcut() {
15
16 }
17
18 @Around("pointcut()")
19 public Object process(ProceedingJoinPoint point) throws Throwable {
20 System.out.println(point.getSignature().getName());
21 return point.proceed();
22 }
23
24 }
其中
1 @Pointcut("@within(com.chenrui.aspect.PrintMethodName) || @annotation(com.chenrui.aspect.PrintMethodName)")
是我们定义的一个检查点(Pointcut),@within表示的是在PrintMethodName注解所标记的Class内,而@annotation表示的是被PrintMethodName注解标记的方法。因此,不管我们将切面注解标注在Class上还是Method上,我们都可以拦截到。下面我们定义一个简单的实现类。
1 package com.chenrui.aspect;
2
3 import org.springframework.stereotype.Component;
4
5 @Component
6 public class AspectService {
7
8 @PrintMethodName
9 public void firstMethod() {
10 System.out.println("Into the first method.");
11 innerCallMethod();
12 }
13
14 @PrintMethodName
15 public void secondMethod() {
16 System.out.println("Into the second method.");
17 }
18
19 @PrintMethodName
20 public void innerCallMethod() {
21 System.out.println("Into the inner call method.");
22 }
23
24 }
三个方法firstMethod,secondMethod和innerCallMethod我们都加上了@PrintMethodName注解,现在我们来看一看效果。
1 package com.chenrui.aspect;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.web.bind.annotation.RequestMapping;
5 import org.springframework.web.bind.annotation.RestController;
6
7 @RestController
8 @RequestMapping("/aspect")
9 public class AspectTestController {
10
11 @Autowired
12 private AspectService aspectService;
13
14 @RequestMapping("/test")
15 public Object testPrintMethod() {
16 aspectService.firstMethod();
17 aspectService.secondMethod();
18 return "OK";
19 }
20
21 }
我们只调用了firstMethod和secondMethod,现在来看看控制台会打印出什么。
firstMethod
Into the first method.
Into the inner call method.
secondMethod
Into the second method.
显然,切面在firstMethod和secondMethod处都起作用了,但是在innerCallMethod处失效了,下一篇文章我将从Spring的实现方式上来说明为什么切面对于innerCallMethod方法会失效。