一.背景
项目中需要保存所有操作的日志,决定引入AOP面向切面编程,日志需要记录完整的一次请求,包括请求参数:url、请求方式、类路径、方法、参数名等。
二.AOP简介
AOP即Aspect Oriented Program面向切面编程,在面向切面编程的思想里面,把功能分为
核心业务
功能和
周边功能
:
-
所谓的核心业务
,比如登录,增加数据,删除数据都叫核心业务; -
所谓的周边业务
,比如性能统计、日志、事务管理等等;
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能“编织”在一起叫AOP。
三.AOP应用场景以及常用术语
- 权限控制、缓存控制、事务控制、分布式追踪、异常处理等
- Target:目标类,即需要被代理的类。例如:UserService
- Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
- PointCut 切入点:已经被增强的连接点。例如:addUser()
- Advice 通知/增强,增强代码。例如:after、before
- Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
- Aspect(切面): 是切入点pointcut和通知advice的结合
四.在项目中使用
4.1添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
4.2 web注解
/**
* controller层切面日志注解
* @author chenf
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemControllerLog {
}
4.3 切面
package org.fh.aop;
import org.apache.shiro.session.Session;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import com.alibaba.fastjson.JSON;
import org.fh.common.util.ExceptionUtil;
import org.fh.common.vo.LogVO;
import org.fh.entity.PageData;
import org.fh.service.promager.LogService;
import org.fh.util.Const;
import org.fh.util.Jurisdiction;
import org.fh.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;
/**
* 日志切面类
* @author chenf
*
*/
@Aspect
@Component
public class SystemLogAspect {
private static Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");
private static final ThreadLocal<LogVO> logThreadLocal = new NamedThreadLocal<LogVO>("ThreadLocal log");
@Autowired(required=false)
HttpServletRequest request;
@Autowired
private LogService logService;
//Service层切点
@Pointcut("@annotation(org.fh.aop.annotation.SystemServiceLog)")
public void serviceAspect(){
}
//Controller层切点
@Pointcut("@annotation(org.fh.aop.annotation.SystemControllerLog)")
public void controllerAspect() {
}
/**
* 前置通知 用于拦截Controller层记录用户的操作的开始时间
* @param joinPoint 切点
* @throws InterruptedException
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) throws InterruptedException{
logger.info("进入日志切面前置通知!");
Date beginTime = new Date();
//线程绑定变量(该数据只有当前请求的线程可见)
beginTimeThreadLocal.set(beginTime);
setLogObject(joinPoint);
}
@After("controllerAspect()")
public void doAfter(JoinPoint joinPoint) {
logger.info("进入日志切面后置通知!");
logger.info("设置日志信息存储到表中!");
LogVO log = logThreadLocal.get();
//判断是否有异常,若是则不修改值
if("记录".equals(log.getState())) {
log.setDescribe("操作成功");
}
//1.直接执行保存操作
this.saveLog(log);
}
/***
* 异常通知 记录操作报错日志
* * @param joinPoint
* * @param e
* */
@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
logger.info("进入日志切面异常通知!!");
logger.info("异常信息:"+e.getMessage());
String strackTrace = ExceptionUtil.getStrackTrace(e);
if(strackTrace.length() >= 5000) {
strackTrace = strackTrace.substring(0,5000);
}
strackTrace = null == strackTrace || "".equals(strackTrace)
? e.getMessage() : strackTrace;
LogVO log = logThreadLocal.get();
log.setState("错误");
log.setDescribe(strackTrace);
}
public void setLogObject(JoinPoint joinPoint) {
Session session = Jurisdiction.getSession();
String userName = (String) session.getAttribute(Const.SESSION_USERNAME);
//请求的IP
String remoteAddr = request.getRemoteAddr();
//请求提交的参数
Map<String,String[]> params = request.getParameterMap();
Signature signature = joinPoint.getSignature();
//方法名称
String methodName = signature.getName();
String classPath = signature.getDeclaringTypeName();
classPath = classPath.substring(classPath.lastIndexOf(".")+1);
//模块
String module = classPath.replace("org.fh.", "");
LogVO log = new LogVO();
log.setUsername(userName);
log.setState("记录");
log.setData(JSON.toJSONString(params));
log.setDescribe(null);
log.setIp(remoteAddr);
log.setAction(methodName);
log.setModule(module);
Date operateDate = beginTimeThreadLocal.get();
log.setTime(operateDate);
logThreadLocal.set(log);
}
/**
* 记录日志
*/
private void saveLog(LogVO logVO) {
try
{
PageData pu = new PageData();
pu.put("LOG_ID", this.get32UUID()); //主键
pu.put("TIME",logVO.getTime());
pu.put("USERNAME",logVO.getUsername());
pu.put("IP",logVO.getIp());
pu.put("MODULE",logVO.getModule());
pu.put("ACTION",logVO.getAction());
pu.put("DATA",logVO.getData());
pu.put("STATE",logVO.getState());
pu.put("DESCRIB",logVO.getDescribe());
pu.put("VERSION","1");
logService.save(pu);
} catch (Exception e)
{
e.printStackTrace();
}
}
/**得到32位的uuid
* @return
*/
public String get32UUID(){
return UuidUtil.get32UUID().substring(0,25);
}
}
4.4 异常堆栈记录工具类
package org.fh.common.util;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 异常工具类
* @author chenf
*
*/
public class ExceptionUtil {
/**
* 获取异常堆栈信息
* @param throwable
* @return
*/
public static String getStrackTrace(Throwable throwable)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try
{
throwable.printStackTrace(pw);
return sw.toString();
} finally
{
pw.close();
}
}
}
五.总结
日志记录只是一个简单的示例,而 Spring AOP 的应用让整个系统变的更加有条不紊,在其他场景应用也很强大。 它帮助我们降低模块间耦合度,提高程序复用性,提高开发效率,提高系统可做性和可维护性。
版权声明:本文为cf082430原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。