项目中使用SpringAOP切面记录日志

  • Post author:
  • Post category:其他




一.背景

项目中需要保存所有操作的日志,决定引入AOP面向切面编程,日志需要记录完整的一次请求,包括请求参数:url、请求方式、类路径、方法、参数名等。



二.AOP简介

AOP即Aspect Oriented Program面向切面编程,在面向切面编程的思想里面,把功能分为

核心业务

功能和

周边功能


  1. 所谓的核心业务

    ,比如登录,增加数据,删除数据都叫核心业务;

  2. 所谓的周边业务

    ,比如性能统计、日志、事务管理等等;

在面向切面编程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 版权协议,转载请附上原文出处链接和本声明。