第一步,定义两个注解,Controller层注解,Service层注解。
package com.iie.log;
import java.lang.annotation.*;
/**
* Created by bo on 2017/12/25.
* 用户拦截Controller层操作注解,起切点表达式作用,明确切面应该从哪里注入
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ControllerLog {
String type() default "";
String description() default "";
}
package com.iie.log;
import java.lang.annotation.*;
/**
* Created by bo on 2017/12/25.
* 用户拦截Service操作注解,起切点表达式作用,明确切面应该从哪里注入
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ServiceLog {
String type() default "";
String description() default "";
}
第二步,创建一个切点类。
package com.iie.log;
import com.alibaba.fastjson.JSONObject;
import com.iie.common.AjaxResult;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
/**
* Created by bo on 2017/12/25.
* 切面类,主要定义了几个通知,在调用被代理对象目标方法前后使用
*/
@Aspect
@Component
public class LogAspect {
@Autowired
private LogService logService;
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
/**
* Controller层切点 使用到了我们定义的 ControllerLog 作为切点表达式。
* 而且我们可以看出此表达式是基于 annotation 的。
*/
@Pointcut("@annotation(com.iie.log.ControllerLog)")
public void controllerAspect() {
}
/**
* Service层切点 使用到了我们定义的 ServiceLog 作为切点表达式。
* 而且我们可以看出此表达式基于 annotation。
*/
@Pointcut("@annotation(com.iie.log.ServiceLog)")
public void serviceAspect() {
}
/**
* 后置通知 用于拦截Controller层记录用户的操作
* @param joinPoint 连接点
*/
@AfterReturning(pointcut = "controllerAspect()", returning = "ajaxResult")
public void doAfterReturningController(JoinPoint joinPoint, AjaxResult ajaxResult) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
User user = (User) request.getSession().getAttribute("session_user");
try {
if (ajaxResult.getBizNo() > 0) {
String params = "";
/*
* 如果需要区分角色,从session中取出保存的用户,则Controller函数的第一个参数为HttpServletRequest
* 而对于登录、修改密码不记录参数,防止日志管理员看到不同角色的用户参数
*/
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
for (int i = 0; i < joinPoint.getArgs().length; i++) {
params += JSONObject.toJSON(joinPoint.getArgs()[i]).toString() + ";";
}
}
//构造数据库日志对象
Log log = newLogObject(user, request.getRemoteAddr(), params, getControllerMethodAnnotationValue(joinPoint));
//保存数据库
logService.saveLog(log);
}
} catch (Exception e) {
//记录本地异常日志
logger.error("==后置Controller通知异常==");
logger.error("异常信息:{}", e.getMessage());
}
}
/**
* 后置通知 用于拦截Service层记录用户的操作,用于记录系统自动清空日志操作
* @param joinPoint 连接点
*/
@AfterReturning("serviceAspect()")
public void doAfterService(JoinPoint joinPoint) {
try {
//构造数据库日志对象
Log log = newLogObject(null, "", "", getServiceMethodAnnotationValue(joinPoint));
//保存数据库
logService.saveLog(log);
} catch (Exception e) {
//记录本地异常日志
logger.error("==后置Service通知异常==");
logger.error("异常信息:{}", e.getMessage());
}
}
//构造数据库日志对象
private Log newLogObject(User user, String remoteIp, String params, AnnotationValue annotationValue) {
Log log = new Log();
if (user != null) {
log.setUserId(user.getId());
log.setUsername(user.getUsername());
log.setUserRole(user.getRole());
}
log.setRemoteIp(remoteIp);
log.setParams(params);
log.setType(annotationValue.getType());
log.setDesc(annotationValue.getDesc());
log.setCreateTime(new Date());
return log;
}
//获取Controller中注解值
public static AnnotationValue getControllerMethodAnnotationValue(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
AnnotationValue annotationValue = new AnnotationValue();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
String type = method.getAnnotation(ControllerLog.class).type();
String description = method.getAnnotation(ControllerLog.class).description();
annotationValue.setType(type);
annotationValue.setDesc(description);
break;
}
}
}
return annotationValue;
}
//获取Service中注解值
public static AnnotationValue getServiceMethodAnnotationValue(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
AnnotationValue annotationValue = new AnnotationValue();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
String type = method.getAnnotation(ServiceLog.class).type();
String description = method.getAnnotation(ServiceLog.class).description();
annotationValue.setType(type);
annotationValue.setDesc(description);
break;
}
}
}
return annotationValue;
}
}
第三步,在springmvc.xml中启用配置。
<!--开启aopproxy-target-class="true"默认是false,更改为true时使用的是cglib动态代理,这样只能实现对Controller层的日志记录-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--扫描日志记录切面-->
<context:component-scan base-package="com.iie.log" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
第四步,在Controller中使用。
//用户登录校验
@RequestMapping(value = "/login-check", method = RequestMethod.POST)
@ControllerLog(type = LogConst.TYPE_LOG_LOGIN, description = "用户登录系统")
@ResponseBody
public AjaxResult loginCheck(HttpServletRequest request, @RequestParam("username") String userName, @RequestParam("passwd") String passwd){
Object[] params = {userName, passwd, request.getRemoteAddr()};
logger.info("LoginController loginCheck:/login-check?username={}&password={}&remoteip={}", params);
authShiroService.loginCheck(userName, passwd);
User user = userService.getUserByUserName(userName);
super.setSessionUser(request, user);
//把当前用户角色响应到前端,前端根据不同角色用户跳转到不同首页
Map<String, String> roleMap = new HashMap<>();
roleMap.put("role", user.getRole());
return AjaxResult.success(roleMap);
}