切面通知应用增强
1. 通知类型
在基于Spring AOP编程的过程中,基于AspectJ框架标准,Spring中定义了五种类型的通知(通知描述的是一种扩展业务),它们分别是:
- 前置通知 (@Before)
- 返回通知 (@AfterReturning)
- 异常通知 (@AfterThrowing)
- 后置通知 (@After)
- 环绕通知 (@Around) : 重点掌握(优先级最高)
1.2 通知执行顺序及实践
代码实践如下:
@Component @Aspect public class SysTimeAspect { @Pointcut("bean(productController)") public void doTime(){} @Before("doTime()") public void doBefore(JoinPoint jp){ System.out.println("time doBefore()"); } @After("doTime()") public void doAfter(){ System.out.println("time doAfter()"); } /**核心业务正常结束时执行 执行returning*/ @AfterReturning("doTime()") public void doAfterReturning(){ System.out.println("time doAfterReturning"); } /**核心业务出现异常时执行 执行Throwing*/ @AfterThrowing("doTime()") public void doAfterThrowing(){ System.out.println("time doAfterThrowing"); } @Around("doTime()") public Object doAround(ProceedingJoinPoint jp) throws Throwable{ System.out.println("doAround.before"); Object obj=jp.proceed(); System.out.println("doAround.after"); return obj; } }
假如这些通知全部写到一个切面对象中,其执行顺序及过程 :
-
正常时 : @Around –> @Before –> @AfterReturning –> @After –> @Around
-
异常时 : @Around –> @Before –> @AfterThrowing –> @After –> @Around
说明 : 实际项目中可能不会在切面中定义所有的通知,具体定义哪些通知要结合业务进行实现。SpringBoot不同版本中通知的执行顺序以项目实际运行结果为准。
1.3切入点表达式增强
Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
1.3.1 bean表达式(重点)
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
- bean(“productController”) 指定一个 productController 类中所有方法
- bean("*Controller") 指定所有后缀为 Controller 的类中所有方法
说明 : bean表达式内部的对象是由 Spring 容器管理的一个bean对象 , 表达式内部的名字应该是 Spring 容器中某个bean的name。
1.3.2 within表达式(了解)
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
- within(“aop.controller.ProductController”) 指定当前包中这个类内部的所有方法
- within(“aop.controller . *”) 指定当前目录下的所有类的所有方法
- within(“aop.controller. .*”) 指定当前目录以及子目录中类的所有方法
1.3.3 execution表达式(了解)
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析: 语法:execution(返回值类型 包名.类名.方法名(参数列表))。
- execution(void aop.service.UserServiceImpl.addUser()) 匹配addUser方法
- execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法
- execution(* aop.service. .* .*(. .)) 万能配置
1.3.4 @annotation表达式(重点)
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
- @annotation(anno.RequiredLog) 匹配有此注解描述的方法
- @annotation(anno.RequiredCache) 匹配有此注解描述的方法
说明 : @RequiredLog 为我们自己定义的注解 , 当我们使用 @RequiredLog 注解修饰业务层方法时 , 系统底层会在执行此方法时进行日志扩展操作。
1.4 @annotation表达式练习
定义 Cache 相关切面 , 使用注解表达式定义切入点 , 并使用此注解对需要使用Cache的业务方法进行描述 , 代码分析如下 :
第一步 : 定义注解 @RequiredCache
/** * 自定义注解,一个特殊的类,所有注解都默认继承Annotation接口 */ @Retention(RetentionPolicy.RUNTIME) //运行时有效 @Target(ElementType.METHOD) //修饰方法 public @interface RequiredCache { }
定义注解 @ClearCache
/** * 自定义注解,一个特殊的类,所有注解都默认继承Annotation接口 */ @Retention(RetentionPolicy.RUNTIME) //运行时有效 @Target(ElementType.METHOD) //修饰方法 public @interface ClearCache { }
第二步 : 定义 CacheAspect 切面对象
@Aspect @Component public class CacheAspect { private Map<Object,Object> cache = new ConcurrentHashMap<>(); @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)") public void doCache() {} @Pointcut("@annotation(com.cy.pj.common.annotation.ClearCache)") public void doClear() {} //注意,这里选用返回通知,因为只有业务代码执行成功才能执行此通知 @AfterReturning("doClear()") public void doAfterReturning() { cache.clear(); } @Around("doCache()") public Object doAround(ProceedingJoinPoint jp) throws Throwable { System.out.println("Get Data from Cache"); Object result = cache.get("dataKey"); if(result!=null) return result; result = jp.proceed(); //执行目标方法 System.out.println("Put Data from Cache"); cache.put("dataKey", result); return result; } }
第三步 : 使用 @RequiredCache 注解对特定业务目标对象中的查询方法进行描述。
@RequiredCache /**查询前端系统首页商品列表信息*/ @GetMapping("/product/list/index") public List<Product> doSelectIndex(){ return productMapper.selectIndex(); }
使用 @ClearCache 注解对特定业务目标对象中的查询方法进行描述。
@ClearCache /**发布新商品*/ @PostMapping("/product/insert") public void insert(@RequestBody Product product){ product.setCreated(new Date()); productMapper.insert(product); }