项目中很多时候需要去打印方法入参和出参的日志,有助于排查错误。
注解需要操作简单。
常用的方式之一就是使用切面来切日志。

步骤:

  1. 定义自定义注解
  2. 编写自定义注解的切面方法
  3. 使用注解在需要输出日志的方法上

1.自定义注解

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
/**
*
* controller 注解切面
* @author liukai
* @data 2018/8/7 15:26.
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})//目标是方法
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Documented//文档生成时,该注解将被包含在javadoc中,可去掉
@Inherited
public @interface ControllerLog {


/**
* 操作描述 业务名称business
*
* @return
*/
String description() default "";

/**
* 操作模块
*
* @return
*/
OperateModule module();

/**
* 操作类型 create modify delete
*
* @return
*/
OperateType opType();

/**
* 主键入参参数名称,入参中的哪个参数为主键
*
* @return
*/
String primaryKeyName() default "";

/**
* 主键在参数中的顺序,从0开始,默认0
*/
int primaryKeySort() default 0;

/**
* 业务类型
* @return
*/
String business() default "";
}

2.模块枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @author 操作类型
* @date 2017/7/26.
*/
public enum OperateModule {
/**
* 枚举状态码
*/
LOGIN("登陆"),
LOGOUT("退出登陆"),
DEMAND("需求"),
ITERATION("迭代");
private String text;

OperateModule(String text) {
this.text = text;
}

public String getText() {
return text;
}
}

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
package com.group.core.web.log;

/**
* @author 操作类型
* @date 2017/7/26.
*/
public enum OperateModule {
/**
* 枚举状态码
*/
LOGIN("登陆"),
LOGOUT("退出登陆"),
DEMAND("需求"),
ITERATION("迭代");
private String text;

OperateModule(String text) {
this.text = text;
}

public String getText() {
return text;
}
}

4.日志切面

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package com.group.core.web.log;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.group.common.constants.Constants;
import com.group.core.model.SysLog;
import com.group.core.service.SysLogService;
import com.group.core.web.vo.ResultVo;
import com.group.core.web.log.annotation.ControllerLog;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.validation.BindingResult;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;

/**
* 日志切面
*
* @author liukai
* @data 2018/8/7 15:50.
*/
@Aspect
@Component
public class WebLogAspect {

public static final String MODULE = "module";
@Resource
private SysLogService logService;

@Resource
private SecurityManager securityManager;

private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);

/**
* 定义一个切入点.
* ("execution(public * com.group.*.web..*.*(..))")
* 解释下:
* 第一个 * 代表任意修饰符及任意返回值.
* 第二个 * 任意包名
* 第三个 * 代表任意方法.
* 第四个 * 定义在web包或者子包
* 第五个 * 任意方法
* .. 匹配任意数量的参数.
*/
@Pointcut("execution(public * com.group..*.controller..*.*(..)) && @annotation(com.group.core.web.log.annotation.ControllerLog)")
public void webLog() {
}


/**
* round
* 环境切面方法,切日方法调用的出入时的操作
* author liukai
* date 2018/8/7 16:16
*
* @param joinPoint
* @return java.lang.Object
*/
@Around("webLog()")
public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
LOGGER.info("环绕日志切面开始");
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
String user = (String) subject.getPrincipal();
if (StringUtils.isNotEmpty(user)) {
JSONObject jsonObject = JSONObject.parseObject(user.substring(4));
user = jsonObject.getString("sub");
}
Map<String, Object> controllerAnnotationValues = getControllerAnnotationValue(joinPoint);
if (StringUtils.isNotEmpty(user)) {
controllerAnnotationValues.put("user", user);
LOGGER.info("用户 {}-- 操作:{} -- 模块: {}", user, controllerAnnotationValues.get("operateName"), controllerAnnotationValues.get("moduleName"));
// //需求目志特殊处理
// if (controllerAnnotationValues.get(MODULE).equals(OperateModule.DEMAND.toString())) {
//// demandLogger.insertLog(controllerAnnotationValues, getParameter(joinPoint));
// } else {
insertLog(controllerAnnotationValues);
// }

}
//切面返回值
Object returnValue = joinPoint.proceed();
//用户登陆,登陆后 subject 才会包含用户信息。
if (returnValue instanceof ResultVo && user == null) {
Subject logSubject = SecurityUtils.getSubject();
String logUser = (String) logSubject.getPrincipal();
if (StringUtils.isNotEmpty(logUser)) {
JSONObject jsonObject = JSONObject.parseObject(logUser.substring(4));
logUser = jsonObject.getString("sub");
}
controllerAnnotationValues.put("user", logUser);
insertLog(controllerAnnotationValues);
}
return returnValue;
}


/**
* 插入通用日志
* <p>
* author liukai
* date 2018/8/10 17:33
*
* @param controllerParam
* @return void
*/
private void insertLog(Map<String, Object> controllerParam) {
SysLog sysLog = new SysLog();
String moduleName = (String) controllerParam.get("moduleName");
String operateName = (String) controllerParam.get("operateName");
controllerParam.get("primaryKeyName");
controllerParam.get("primaryKeySort");
String user = (String) controllerParam.get("user");
sysLog.setOperationUser(user);
sysLog.setModifyUserId(user);
sysLog.setCreateUserId(user);
sysLog.setOperation(operateName);
sysLog.setModel(moduleName);
sysLog.setFlag(Constants.DELETE_TYPE_FALSE);
logService.insert(sysLog);
}


/**
* 获取日志注解的方法
* <p>
* author liukai
* date 2018/8/7 16:41
*
* @param joinPoint
* @return java.util.Map<java.lang.String , java.lang.Object>
*/
public static Map<String, Object> getControllerAnnotationValue(JoinPoint joinPoint) throws Exception {
//取切点相关参数
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
LOGGER.info("targetName: {} - methodName: {} - arguments: {}", targetName, methodName, arguments.toString());
//实例化该
Class targetClass = Class.forName(targetName);
//获取该类的所有方法
Method[] methods = targetClass.getMethods();
Map<String, Object> map = Maps.newHashMapWithExpectedSize(7);
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] classes = method.getParameterTypes();
if (classes.length == arguments.length) {
String description = method.getAnnotation(ControllerLog.class).description();
String module = method.getAnnotation(ControllerLog.class).module().name();
String operateType = method.getAnnotation(ControllerLog.class).opType().name();
String primaryKeyName = method.getAnnotation(ControllerLog.class).primaryKeyName();
int primaryKeySort = method.getAnnotation(ControllerLog.class).primaryKeySort();
String operateName = getOpName(operateType);
String moduleName = getModelName(module);
map.put("module", module);
map.put("moduleName", moduleName);
map.put("operateType", operateType);
map.put("operateName", operateName);
map.put("business", description);
map.put("primaryKeyName", primaryKeyName);
map.put("primaryKeySort", primaryKeySort);
break;
}
}
}
return map;
}

/**
* 获取模块名
* <p>
* author liukai
* date 2018/8/7 21:11
*
* @param module
* @return java.lang.String
*/
private static String getModelName(String module) {
String operate = null;
for (OperateModule model : OperateModule.values()) {
if (model.name().equals(module)) {
operate = model.getText();
return operate;
}
}
return operate;
}


/**
* 获取类型名
* <p>
* author liukai
* date 2018/8/7 21:06
*
* @param operateType
* @return java.lang.String
*/
private static String getOpName(String operateType) {
String operate = null;
for (OperateType opType : OperateType.values()) {
if (opType.name().equals(operateType)) {
operate = opType.getMsg();
return operate;
}
}
return operate;
}


private Map<String, Object> getParameter(JoinPoint joinPoint) {
//入参 value
Object[] args = joinPoint.getArgs();
//入参名称
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map<String, Object> params = Maps.newHashMapWithExpectedSize(7);
//获取所有参数对象
for (int i = 0; i < args.length; i++) {
if (null != args[i]) {
if (args[i] instanceof BindingResult) {
params.put(paramNames[i], "bindingResult");
} else {
params.put(paramNames[i], args[i]);
}
} else {
params.put(paramNames[i], "");
}
}
return params;
}
}

5.使用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 退出
*
* @param session
* @return
* @throws
* @author liukai
* @date 2018/8/2 上午9:05
*/
@RequestMapping(value = "/logout", method = RequestMethod.POST)
@ResponseBody
@ControllerLog(module = OperateModule.LOGOUT, opType = OperateType.logout)
public ResultVo logOut(HttpSession session) {
ResultVo resultVo = new ResultVo();
Subject subject = SecurityUtils.getSubject();
resultVo.setCode(SUCCESS_CODE);
resultVo.setMsg("退出登陆");
logger.info("退出登陆");
subject.logout();
return resultVo;
}