我们在日常编程中,应该保持高内聚,低耦合,函数功能单一,代码可复用性,可移植性性高。当涉及到日志,缓存等功能是,我们可以根据业务需求,利用spring aop面向切面编程,把核心的功能和附加功能剥离开来使代码达到高复用的效果,比如用注解记录操作日志。为了避免无代码侵入并实现通用,于是定义一个注解,如果要统计哪个方法,只需在方法上标记上注解即可,通过注解可以获取到方法的参数、方法名、返回值等等信息。
一、引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
以下是一个简单的保存系统日志的实现:
二、自定义系统日志注解
该注解有一个默认的value属性,value值为方法名或自定义的描述
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 系统日志注解.
*
* @author arjun
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
三、使用Aspect定义该注解的切面处理类
定义好注解后,需要对该注解使用的类进行监听,利用Spring框架Aspect实现切面,定义后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作,获取到方法的参数、方法名等信息,便于统计所需。定义异常通知,对执行方法进行异常处理,实现操作日志逻辑,避免方法异常无法记录。代码如下:
/**
* 系统日志,切面处理类
*/
@Aspect
@Component
public class SysDealLogAspect {
private Long beginTime;
@Autowired
private SysAccessLogService sysAccessLogService;
@Resource(name = "asyncTaskExecutor")
Executor asyncTaskExecutor;
/**
* 定义切入点,注解方式
*/
@Pointcut("@annotation(com.arjun.subjective.demo.annotation.Syslog)")
public void logPointCut() {
}
/**
* 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
*/
@Before("logPointCut()")
public void before() {
beginTime = System.currentTimeMillis();
}
/**
* 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
*
* @param joinPoint 切点
*/
@AfterReturning("logPointCut()")
public void afterReturning(JoinPoint joinPoint) {
log.info("正常执行,保存日志信息");
HttpServletRequest request = WebUtils.getRequest();
asyncTaskExecutor.execute(() -> saveSysAccessLog(joinPoint, request, null));
}
/**
* 异常通知 用于拦截异常日志
*
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "logPointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
log.info("系统抛出异常,保存日志信息");
HttpServletRequest request = WebUtils.getRequest();
asyncTaskExecutor.execute(() -> saveSysAccessLog(joinPoint, request, e));
}
/**
* 用于记录用户的操作日志描述
*/
private void saveSysAccessLog(JoinPoint joinPoint, HttpServletRequest request, Throwable e) {
SysAccessLog accessLog = new SysAccessLog();
// 执行时间
long time = System.currentTimeMillis() - beginTime;
accessLog.setElapsedTime(time);
// 用户操作描述
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String value = method.getAnnotation(Syslog.class).value();
accessLog.setOperation(value);
// 请求方法名
String typeName = signature.getDeclaringTypeName();
String methodName = signature.getName();
accessLog.setMethod(typeName + "." + methodName + "()");
// 请求参数
Map<String, String> parameterMap = WebUtils.getParameterMap(request);
accessLog.setParams(parameterMap.toString());
// 请求路径
String requestPath = request.getRequestURI();
accessLog.setPath(requestPath);
// 请求方式
String requestType = request.getMethod();
accessLog.setRequestType(requestType);
// 请求时间
Date date = new Date();
accessLog.setRequestTime(date);
// ip地址
String ip = WebUtils.getIpAddress(request);
accessLog.setIp(ip);
User user = (User) request.getSession().getAttribute("user");
accessLog.setUsername(user == null ? null : user.getName());
if (e != null) {
accessLog.setMessage(e.toString());
}
//保存系统日志
sysAccessLogService.insert(accessLog);
}
}
ProceedingJoinPoint仅支持around通知
四、数据库表
CREATE TABLE `sys_access_log` (
`id` bigint