【Java】AOP使用示例 之 统一异常处理

 

本文将使用 Spring AOP 提供一个关于统一异常处理的 AOP 技术应用示例。

(可直接跳到 2.2 查看代码示例:Maven依赖 + 切面类 + Bean配置)

 

1. AOP简述

AOP 全称为“Aspect Oriented Programming”,中文译名为“面向切面编程”。乍一看非常令人费解。但是你真正理解这项技术(或者说这个思想)要做什么后,你又会觉得“切面”(Aspect)这个词还真有些贴切。“切面”指的是这项技术(AOP)的控制代码并不是直接与业务代码结合在一起;而是分开编写,再由AOP技术根据控制代码中(对目标业务代码)的描述将两者结合在一起。如果我们把“控制代码与业务代码直接编写在一起”称为“正面操作”,那么AOP技术的方式可以称为“侧面操作”或“切面操作”,也就是“面向切面编程”。

 

AOP 可以被看作是 OOP 的一个补充。面向对象编程的模式对于跨越不同对象或类的逻辑处理能力有限。AOP的动态代理机制可以大幅提高代码的抽象程度和复用度。

 

【Java】AOP使用示例 之 统一异常处理

 

AOP结合控制代码与业务代码的模式有“编译时结合”与“运行时结合”。

本示例中所使用的Spring AOP技术属于运行时结合模式。它基于动态代理(用到了cglib 和 JDK Proxy),不需要特殊的编译器来“织入”“切面”,但只支持方法拦截。

如果需要更强大和细粒度的控制(如,对构造器或属性进行拦截),则需要采用更强大的AspectJ技术。

 

【Java】AOP使用示例 之 统一异常处理

 

AOP有很多专有术语非常不直观,需要细细理解。如,“通知(Advice)”、“连接点(Join point)”、“切点(Pointcut)”、“切面(Aspect)”、“引入(Introduction)”、“织入(Weaving)”等。

(《Spring实战》中就有对AOP简介(面向切面的Spring))

虽然一时半会儿理解不了,但不妨碍我们实用主义者通过示例,以写代码的方式快速上手。

 

 

2. 示例

该示例中的需求可能不是很恰当,主要是为了提供一种AOP的使用方法示例。

2.1 背景

现有一个程序 AppServer。它实现了程序包 my-api.jar 中 命名空间“my.api”下的所有接口。

如:my.api.XService、my.api.YService、my.api.ZService。

AppServer中的 XServiceImpl、YServiceImpl、ZServiceImpl 等类分别实现了这些 my.api 接口。当然这些不重要,因为我们现在介绍的AOP技术不会侵入到对这些的代码的编写。

接口示例:

 

Java代码

 【Java】AOP使用示例 之 统一异常处理

  1. public interface XService {  

  2.   X getX(String id);  

  3.   String addX(String name);  

  4.   void deleteX(String id);  

  5.   void updateXName(String id, String newName);  

  6. }  

 

现在有一个需求:将这些接口实现类中所抛出异常的信息统一提取出来,生成 MyException 再抛出。

包装 MyException 的方法如下:

 

Java代码

 【Java】AOP使用示例 之 统一异常处理

  1. MyException createMyException(Throwable e) {  

  2.   return new MyException(e.getMessage());  

  3. }  

 

2.2 利用Spring AOP实现需求

2.2.1 Maven依赖

目标工程基于Spring(Boot),相关配置详情此处不表。现需要引入对AspectJ的依赖。

当然,如果你已经依赖了 spring-boot-starter-aop,就不需要再引入了。因为 spring-boot-starter-aop 就是依赖于 aspectjweaver。

 

Xml代码

 【Java】AOP使用示例 之 统一异常处理

  1. <dependency>  

  2.   <groupId>org.aspectj</groupId>  

  3.   <artifactId>aspectjweaver</artifactId>  

  4.   <version>1.9.2</version>  

  5. </dependency>  

 

2.2.2 编写AOP控制代码类

Java代码

 【Java】AOP使用示例 之 统一异常处理

  1. @Aspect  

  2. public class MyServiceExceptionHandler {  

  3.   @Around("execution(* my.api.*.*(..))")  

  4.   public Object invokeService(ProceedingJoinPoint jp) {  

  5.     try {  

  6.       return jp.proceed();  

  7.     } catch (Throwable e) {  

  8.       throw createMyException(e);  

  9.     }  

  10.   }  

  11.   

  12.   private MyException createMyException(Throwable e) {  

  13.     return new MyException(e.getMessage());  

  14.   }  

  15. }  

 

  • 用 @Aspect 注解表明这是一个切面类。我们之前引入的 AspectJ 框架会在运行时识别该注解,并利用该Handler类对那些服务类(如,XServiceImpl)进行包装。

  • @Around 注解表明我们采用的是环绕通知。因为我们需要补获Service方法抛出的异常。

    • 环绕通知(@Around)是最强大的通知类型。它会将方法封装起来。

    • 其它通知类型有:

      • @Before:方法执行前调用

      • @After:方法返回或抛出异常后调用

      • @AfterReturning:方法返回后调用

      • @AfterThrowing:方法抛出异常后调用

      • 可以自己编写样例验证它们的先后调用顺序

  • @Around括号中的内容表示我们控制的目标方法是什么样的

    • execution(* my.api.*.*(..))

    • execution 表示在方法执行时触发该控制代码

    • 第一个 * 表示匹配任意的返回类型

    • my.api 表示目标方法所述类的所在命名空间

    • 第二个 * 表示匹配任意类(包括XService、YService等目标命名空间下的所有类)

    • 第三个 * 表示匹配任意方法

    • .. 表示匹配任意参数

  • jp.proceed() 表示调用真实的方法。如,XService.getX()

 

2.2.3 最后的Bean配置

Java代码

 【Java】AOP使用示例 之 统一异常处理

  1. @Configuration  

  2. @EnableAspectJAutoProxy  

  3. public class Config {  

  4.   @Bean  

  5.   public MyServiceExceptionHandler myServiceExceptionHandler() {  

  6.     return new MyServiceExceptionHandler();  

  7.   }  

  8.   

  9.   @Bean  

  10.   public XService xService() {  

  11.     return new XServiceImpl();  

  12.   }  

  13.   ...  

  14. }  

 

@EnableAspectJAtuoProxy 注解表示启用 AspectJ 自动代理,也就是利用 @Aspect类(MyServiceExceptionHandler) 对原Service类进行包装。