利用 spring aop 的 around 来实现日志拦截器,此拦截器负责打印抛出到顶层的异常日志。

具体实现

引入相关切面依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.9</version>
</dependency>
// 实现基于类和接口的动态代理
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>

实现日志拦截器

拦截异常打印日志,注意用线程本地变量startLocal,来做一个是否为入口方法的标志。这样做的目的是为了避免重复在每个方法里catch异常, 抛出异常操作的时候打印异常。注意catch的是 java.lang.Throwable级别的异常。包括所有的errors 和 exceptions。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class LogInterceptor {
private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

/**
* 首次进入标志
*/
private static final ThreadLocal<Boolean> startLocal = new ThreadLocal<Boolean>();

public Object doLog(ProceedingJoinPoint jp) throws Throwable {
Boolean isStart = startLocal.get();
// 做源头标记
if (isStart == null) {
startLocal.set(true);

if (logger.isDebugEnabled()) {
LogUtils.debug(logger, "----------开始进入全局日志记录拦截器-------------");
}
}

try {
// 执行目标方法
return jp.proceed();
} catch (Throwable e) {
if (isStart == null) {
logger.warn("业务执行出现未知异常:", e);
}

throw e;
} finally {
if (isStart == null) {
startLocal.remove();
}
}
}
}

日志拦截器的配置

配置拦截器,配置切面作用的范围的表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<aop:aspectj-autoproxy proxy-target-class="true"/>

<bean id="log_Interceptor" class="com.iplatform.common.interceptor.LogInterceptor"/>
<aop:config>
<aop:aspect order="5" id="log_interceptor_aspect" ref="log_Interceptor">
<aop:pointcut id="log_interceptor_pointcut" expression="execution(* com.tem.*.service..*.*(..)) || execution(* com.tem..*.action.*.*(..)) || execution(* com.tem..*.*Controller.*(..))"/>
<aop:around method="doLog" pointcut-ref="log_interceptor_pointcut"/>
</aop:aspect>
</aop:config>

</beans>

知识点扩展

Spring Aop

AOP(Aspect Oriented Programming)既面向切面编程。解决面向对象编程(OOP)所缺乏的横向逻辑处理的部分。例如每个方法都需要的对日志的支持,对事物的处理,对异常的处理等。这种散落在各处的重复逻辑的代码被称为横切(cross cutting)。AOP剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用的模块,并将其命名为切面(Aspect)。

核心概念

  • 横切关注点

    对那些方法继续拦截,拦截后怎么处理,这些关注点称之为横切关注点

  • 切面(aspect)

    类是对物理特征的抽象,切面就是对横切关注点的抽象

  • 连接点(joinpoint)

    被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

  • 切入点(pointcut)

    对连接点进行拦截的定义,支持execution 表达式

  • 通知(advice)

    所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为 前置后置异常最终环绕 通知五类

  • 目标对象

    代理的目标对象

  • 织入(weave)

    将切面应用到目标对象并导致代理对象创建的过程

  • 引入(introduction)

    在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

execution表达式

1
execution(* com.tem.*.service..*.*(..)) || execution(* com.tem..*.action.*.*(..)) || execution(* com.tem..*.*Controller.*(..))
标识符 含义
execution() 表达式的主体
第一个* 表示返回值的类型任意
com.tem.*.service AOP所切的服务的包名,即,需要进行横切的业务类
包名后面的 “..” 表示当前包及子包
“..” 后面的 “*” 表示类名,“*” 即所有类
.*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型
  • 通过方法修饰符定义切点,匹配所有的public 修饰符的方法:

    1
    execution(public * *(..))
  • 通过方法名定义切点,匹配所有”set”开头的方法:

    1
    execution(* set*(..))
  • 通过类定义切点,匹配AccountService 接口的所有方法

    1
    execution(* com.xyz.service.AccountService.*(..))
  • 通过包定义切点,

    • 匹配service 包中的所有方法

      1
      execution(* com.xyz.service.*.*(..))
    • 匹配service包及其子包中的所有方法

      1
      execution(* com.xyz.service..*.*(..))
  • 通过方法入参定义切点

    • 该方法第一个入参为String,第二个入参可以是任意类型

      1
      execution(* joke(String,*))
    • 该方法第 一个入参为String,后面可以有任意个入参且入参类型不限

      1
      execution(* joke(String,..))
    • 该方法有一个入参,且入参是Object类型或该类的子类。它匹配joke(String s1)和joke(Client c)。如果我们定义的切点是execution(* joke(Object))。则只匹配joke(Object object)。

      1
      execution(* joke(Object+))

java.lang.Throwable

// TODO

java.lang.ThreadLocal

// TODO

参考

五月的仓颉,spring3: aop

Spring面向切面编程(AOP-execution表达式)