【Java】AOP使用示例 之 统一异常处理
本文将使用 Spring AOP 提供一个关于统一异常处理的 AOP 技术应用示例。
(可直接跳到 2.2 查看代码示例:Maven依赖 + 切面类 + Bean配置)
1. AOP简述
AOP 全称为“Aspect Oriented Programming”,中文译名为“面向切面编程”。乍一看非常令人费解。但是你真正理解这项技术(或者说这个思想)要做什么后,你又会觉得“切面”(Aspect)这个词还真有些贴切。“切面”指的是这项技术(AOP)的控制代码并不是直接与业务代码结合在一起;而是分开编写,再由AOP技术根据控制代码中(对目标业务代码)的描述将两者结合在一起。如果我们把“控制代码与业务代码直接编写在一起”称为“正面操作”,那么AOP技术的方式可以称为“侧面操作”或“切面操作”,也就是“面向切面编程”。
AOP 可以被看作是 OOP 的一个补充。面向对象编程的模式对于跨越不同对象或类的逻辑处理能力有限。AOP的动态代理机制可以大幅提高代码的抽象程度和复用度。
AOP结合控制代码与业务代码的模式有“编译时结合”与“运行时结合”。
本示例中所使用的Spring AOP技术属于运行时结合模式。它基于动态代理(用到了cglib 和 JDK Proxy),不需要特殊的编译器来“织入”“切面”,但只支持方法拦截。
如果需要更强大和细粒度的控制(如,对构造器或属性进行拦截),则需要采用更强大的AspectJ技术。
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代码
-
public interface XService {
-
X getX(String id);
-
String addX(String name);
-
void deleteX(String id);
-
void updateXName(String id, String newName);
-
}
现在有一个需求:将这些接口实现类中所抛出异常的信息统一提取出来,生成 MyException 再抛出。
包装 MyException 的方法如下:
Java代码
-
MyException createMyException(Throwable e) {
-
return new MyException(e.getMessage());
-
}
2.2 利用Spring AOP实现需求
2.2.1 Maven依赖
目标工程基于Spring(Boot),相关配置详情此处不表。现需要引入对AspectJ的依赖。
当然,如果你已经依赖了 spring-boot-starter-aop,就不需要再引入了。因为 spring-boot-starter-aop 就是依赖于 aspectjweaver。
Xml代码
-
<dependency>
-
<groupId>org.aspectj</groupId>
-
<artifactId>aspectjweaver</artifactId>
-
<version>1.9.2</version>
-
</dependency>
2.2.2 编写AOP控制代码类
Java代码
-
@Aspect
-
public class MyServiceExceptionHandler {
-
@Around("execution(* my.api.*.*(..))")
-
public Object invokeService(ProceedingJoinPoint jp) {
-
try {
-
return jp.proceed();
-
} catch (Throwable e) {
-
throw createMyException(e);
-
}
-
}
-
-
private MyException createMyException(Throwable e) {
-
return new MyException(e.getMessage());
-
}
-
}
-
用 @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代码
-
@Configuration
-
@EnableAspectJAutoProxy
-
public class Config {
-
@Bean
-
public MyServiceExceptionHandler myServiceExceptionHandler() {
-
return new MyServiceExceptionHandler();
-
}
-
-
@Bean
-
public XService xService() {
-
return new XServiceImpl();
-
}
-
...
-
}
@EnableAspectJAtuoProxy 注解表示启用 AspectJ 自动代理,也就是利用 @Aspect类(MyServiceExceptionHandler) 对原Service类进行包装。