文章目录
  1. 1. 1. 启用@AspectJ支持
  2. 2. 2. 编写自定义注解
  3. 3. 3. 编写操作日志切面通知实现类
  4. 4. 4. 使用注解标注需要记录日志的接口

考虑到调用日志的记录要兼顾灵活性和改造复杂度。采用了注解的方式来实现,只要将注解添加到想要记录日志的方法体上就可以实现操作日志的记录。

1. 启用@AspectJ支持

在你的工程lib目录下添加aspectjweaver.jaraspectjrt.jar并在spring 的配置文件中添加如下代码:

1
<aop:aspectj-autoproxy/>

此步完成了@AspectJ的支持,从而可以实现通过注解方式将通知编织到非公共方法中。但是在添加时还要注意在Spring配置文件的命名空间中添加aop支持。

2. 编写自定义注解

实现对方法所实现的功能进行描述,以便在通知中获取描述信息,代码如下:

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
/**
* 接口调用日志注解
*
* @author lcw
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WsLog {

/**
* 接口功能项说明
* @return
*/
String desc() default "无描述信息";

/**
* 此接口调用失败时对应的错误级别。
* 0:正常;1:警告;2错误;3严重错误;(4和5备用扩展)。
* 默认错误等级为2
*
* @return
*/
String errLevel() default "2";
}

3. 编写操作日志切面通知实现类

在编写切面通知实现类之前,我们需要搞清楚我们需要哪些通知类型,是前置通知、后置通知、环绕通知或异常通知?根据我的需求,我们知道我们记录的操作日志有两种情况,一种是操作成功,一种是操作失败。操作成功时则方法肯定已经执行完成,故我们需要实现一个后置通知;操作失败时则说明方法出现异常无法正常执行完成,故还需要一个异常通知。因此我们就需要实现这两个通知即可。代码如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
* AOP日志实现类
*
* @author lcw
*/

@Aspect
@Component
public class WsLogAspect {

private static Log wsLogger = LogFactory.getLog("webServiceLog");
/**
* 采用后置通知方式,目标方法执行成功后执行
* @param jp
* @param wl
*/

@AfterReturning("within(com.openeap.modules..*) && @annotation(wl)")
public void successLog(JoinPoint jp, WsLog wl){
Object[] parames = jp.getArgs(); //获取目标方法体参数
String className = jp.getTarget().getClass().toString(); //获取目标类名
className = className.substring(className.indexOf("com"));
String signature = jp.getSignature().toString(); //获取目标方法签名
String methodName = signature.substring(signature.lastIndexOf(".")+1, signature.indexOf("("));

WsLogVO logvo = new WsLogVO();
logvo.setClassName(className);
logvo.setMethodName(methodName);
logvo.setArgument(JsonMapper.getInstance().toJson(parames));//解析目标方法体的参数
logvo.setDescription(wl.desc());
logvo.setOperationTime(DateUtils.getDateTime());
logvo.setResult("0");
logvo.setErrorLevel("0");
logvo.setErrorMsg("");

outputLog(logvo);
}

/**
* 异常通知方式,当目标方法出现异常时执行
*
* @param jp
* @param wl
* @param ex
*/

@AfterThrowing(pointcut="within(com.openeap.modules..*) && @annotation(wl)", throwing="ex")
public void throwLog(JoinPoint jp, WsLog wl, Exception ex){
Object[] parames = jp.getArgs();
String className = jp.getTarget().getClass().toString();
className = className.substring(className.indexOf("com"));
String signature = jp.getSignature().toString();
String methodName = signature.substring(signature.lastIndexOf(".")+1, signature.indexOf("("));

WsLogVO logvo = new WsLogVO();
logvo.setClassName(className);
logvo.setMethodName(methodName);
logvo.setArgument(JsonMapper.getInstance().toJson(parames));
logvo.setDescription(wl.desc());
logvo.setOperationTime(DateUtils.getDateTime());
logvo.setResult("1");
logvo.setErrorLevel("2");
logvo.setErrorMsg(ex.getMessage());

outputLog(logvo);
}

/**
* 按统一日志格式输出日志
*
*
* 日志输出格式说明:调用接口名称|接口参数|调用时间|调用结果|错误等级|接口说明
* 调用结果:0:成功;1:失败
* 错误等级:0:正常;1:警告;2错误;3严重错误。(4和5备用扩展)。
*
* @param logvo
* @return
*/

private void outputLog(WsLogVO logvo) {
StringBuffer logStr = new StringBuffer();
logStr.append(logvo.getClassName() + "." + logvo.getMethodName() + "()").append("|") //调用接口类及接口名称
.append(logvo.getArgument()).append("|") //接口参数
.append(logvo.getOperationTime()).append("|") //调用时间
.append(logvo.getResult()).append("|") //调用结果
.append(logvo.getErrorLevel()).append("|") //错误等级
.append(logvo.getDescription()); //说明

wsLogger.info(logStr.toString());
}

}

看上面的代码会发现这两个方法体:

1
2
@AfterReturning("within(com.openeap.modules..*) && @annotation(wl)")
public void successLog(JoinPoint jp, WsLog wl){...}

1
2
@AfterThrowing(pointcut="within(com.openeap.modules..*) && @annotation(wl)", throwing="ex")
public void throwLog(JoinPoint jp, WsLog wl, Exception ex){...}

这两个方法体分别是后置通知和异常通知的实现。它们有两个相同的参数jp和wl,jp是切点对象,通过该对象可以获取切点所切入方法所在的类,方法名、参数等信息,具体方法可以看方法体的实现;wl则是我们的自定义注解的对象,通过该对象我们可以获取注解中参数值,从而获取方法的描述信息。在异常通知中多出了一个ex参数,该参数是方法执行时所抛出的异常,从而可以获取相应的异常信息。此处可以自定义异常。注意:如果指定异常参数,则异常对象必须与通知所切入的方法体抛出的异常保持一致,否则该通知不会执行。

successLog方法签名上的@AfterReturning(“within(com.openeap.modules..*) && @annotation(wl)”)注解,是指定该方法体为后置通知,其有一个表达式参数,用来检索符合条件的切点。该表达式指定com/openeap/modules目录下及其所有子目录下的所有带有@WsLog注解的方法体为切点。

throwLog方法签名上的@AfterThrowing(pointcut=”within(com.openeap.modules..*) && @annotation(wl)”, throwing=”ex”)注解,是指定方法体为异常通知,其有一个表达式参数和一个抛出异常参数。表达式参数与后置通知的表达式参数含义相同,而抛出异常参数,则表示如果com/openeap/modules目录下及其所有子目录下的所有带有@WsLog注解的方法体在执行时抛出异常时该通知便会执行。

获取前面配置的log4j配置生成日志文件,对接口调用日志文件进行输出:

1
private static Log wsLogger = LogFactory.getLog("webServiceLog");

4. 使用注解标注需要记录日志的接口

在com/openeap/modules目录下及其所有子目录下任意找到一个service层的某个类的方法或者webservice中的服务方法,在其方法体上添加@WsLog(desc=”描述信息”)即可。代码如下:

1
2
@WsLog(desc="创建用户")
public GetUserResult getUser(Long id) {...}

最终日志生成结果如下:

com.openeap.modules.demo.webservice.soap.AccountSoapServiceImpl.getUser()|[1]|2014-05-07 11:32:45|0|0|创建用户
文章目录
  1. 1. 1. 启用@AspectJ支持
  2. 2. 2. 编写自定义注解
  3. 3. 3. 编写操作日志切面通知实现类
  4. 4. 4. 使用注解标注需要记录日志的接口