项目中很多时候需要去打印方法入参和出参的日志,有助于排查错误。 注解需要操作简单。 常用的方式之一就是使用切面来切日志。
步骤:
- 定义自定义注解
- 编写自定义注解的切面方法
- 使用注解在需要输出日志的方法上
1.自定义注解
代码语言:javascript复制/**
*
* 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.模块枚举
代码语言:javascript复制/**
* @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.操作类型
代码语言:javascript复制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.日志切面
代码语言:javascript复制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.使用注解
代码语言:javascript复制/**
* 退出
*
* @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;
}