引言
在Java编程开发中,经常会遇到用户业务日志的记录处理,如果每一个地方都去写大量的日志信息,那么工作量也是惊人的。这里主要讲解spring boot 中采用AOP方式进行日志的统一处理。spring mvc框架也可以参考。一.spring boot 项目结构图
 
二.代码讲解
2.1 spring boot mavn pom.xml依赖
pom.xml:<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>net.xqlee.project</groupId>
	<artifactId>demo-springboot-aop-log</artifactId>
	<version>2.0</version>
	<packaging>jar</packaging>
	<name>demo-springboot-aop-log</name>
	<description>demo project</description>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<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>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
2.2 spring boot 配置启用AOP
AspectConfig.java:package net.xqlee.project.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
 * 启用切面
 * 
 * @author xqlee
 *
 */
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AspectConfig {
}
2.3 spring boot中自定义一个注解,用于处理日志
package net.xqlee.project.aop.annotation;
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 xqlee
 *
 */
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ServiceLog {
	/**
	 * 操作类型,新增用户?删除用户 ?调用xx服务?使用接口?...
	 * 
	 * @return
	 */
	public String operation();
	/**
	 * 日志级别
	 * 
	 * @return
	 */
	public LogType level() default LogType.INFO;
}
package net.xqlee.project.aop.annotation;
public enum LogType {
	INFO, WARN, ERROR
}
2.4 spring boot 入门之编写处理AOP日志
package net.xqlee.project.aop.aspect;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import net.xqlee.project.aop.annotation.ServiceLog;
@Component
@Aspect
public class LogAspect {
	private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
	/**
	 * 切入点
	 */
	@Pointcut("@annotation(net.xqlee.project.aop.annotation.ServiceLog) ")
	public void entryPoint() {
		// 无需内容
	}
	@Before("entryPoint()")
	public void before(JoinPoint joinPoint) {
		log.info("=====================开始执行前置通知==================");
		try {
			String targetName = joinPoint.getTarget().getClass().getName();
			String methodName = joinPoint.getSignature().getName();
			Object[] arguments = joinPoint.getArgs();
			Class<?> targetClass = Class.forName(targetName);
			Method[] methods = targetClass.getMethods();
			String operation = "";
			for (Method method : methods) {
				if (method.getName().equals(methodName)) {
					Class<?>[] clazzs = method.getParameterTypes();
					if (clazzs.length == arguments.length) {
						operation = method.getAnnotation(ServiceLog.class).operation();// 操作人
						break;
					}
				}
			}
			StringBuilder paramsBuf = new StringBuilder();
			for (Object arg : arguments) {
				paramsBuf.append(arg);
				paramsBuf.append("&");
			}
			// *========控制台输出=========*//
			log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:"
					+ paramsBuf.toString());
			log.info("=====================执行前置通知结束==================");
		} catch (Throwable e) {
			log.info("around " + joinPoint + " with exception : " + e.getMessage());
		}
	}
	/**
	 * 环绕通知处理处理
	 * 
	 * @param joinPoint
	 * @throws Throwable
	 */
	@Around("entryPoint()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		// 先执行业务,注意:业务这样写业务发生异常不会拦截日志。
		Object result = point.proceed();
		try {
			handleAround(point);// 处理日志
		} catch (Exception e) {
			log.error("日志记录异常", e);
		}
		return result;
	}
	/**
	 * around日志记录
	 * 
	 * @param point
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 */
	public void handleAround(ProceedingJoinPoint point) throws Exception {
		Signature sig = point.getSignature();
		MethodSignature msig = null;
		if (!(sig instanceof MethodSignature)) {
			throw new IllegalArgumentException("该注解只能用于方法");
		}
		msig = (MethodSignature) sig;
		Object target = point.getTarget();
		Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
		// 方法名称
		String methodName = currentMethod.getName();
		// 获取注解对象
		ServiceLog aLog = currentMethod.getAnnotation(ServiceLog.class);
		// 类名
		String className = point.getTarget().getClass().getName();
		// 方法的参数
		Object[] params = point.getArgs();
		StringBuilder paramsBuf = new StringBuilder();
		for (Object arg : params) {
			paramsBuf.append(arg);
			paramsBuf.append("&");
		}
		// 处理log。。。。
		log.info("[X用户]执行了[" + aLog.operation() + "],类:" + className + ",方法名:" + methodName + ",参数:"
				+ paramsBuf.toString());
	}
	@AfterThrowing(pointcut = "entryPoint()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		// 通过request获取登陆用户信息
		// HttpServletRequest request = ((ServletRequestAttributes)
		// RequestContextHolder.getRequestAttributes()).getRequest();
		try {
			String targetName = joinPoint.getTarget().getClass().getName();
			String className = joinPoint.getTarget().getClass().getName();
			String methodName = joinPoint.getSignature().getName();
			Object[] arguments = joinPoint.getArgs();
			Class<?> targetClass = Class.forName(targetName);
			Method[] methods = targetClass.getMethods();
			String operation = "";
			for (Method method : methods) {
				if (method.getName().equals(methodName)) {
					Class<?>[] clazzs = method.getParameterTypes();
					if (clazzs.length == arguments.length) {
						operation = method.getAnnotation(ServiceLog.class).operation();
						break;
					}
				}
			}
			StringBuilder paramsBuf = new StringBuilder();
			for (Object arg : arguments) {
				paramsBuf.append(arg);
				paramsBuf.append("&");
			}
			log.info("异常方法:" + className + "." + methodName + "();参数:" + paramsBuf.toString() + ",处理了:" + operation);
			log.info("异常信息:" + e.getMessage());
		} catch (Exception ex) {
			log.error("异常信息:{}", ex.getMessage());
		}
	}
}
三.测试AOP日志
3.1 编写一个service来进行日志测试
接口:package net.xqlee.project.service;
import java.util.Map;
public interface TestService {
	public boolean insert(Map<String, Object> params, String id);
	public boolean update(String name, String id);
	public boolean delete(String id);
	public boolean doError(String id);
}
package net.xqlee.project.service;
import java.util.Map;
import org.springframework.stereotype.Service;
import net.xqlee.project.aop.annotation.ServiceLog;
import net.xqlee.project.aop.annotation.LogType;
@Service
public class TestServiceImp implements TestService {
	@ServiceLog(operation = "新增用户信息测试操作。。。。。")
	@Override
	public boolean insert(Map<String, Object> params, String id) {
		return false;
	}
	@ServiceLog(operation = "更新用户信息操作....")
	@Override
	public boolean update(String name, String id) {
		return false;
	}
	@ServiceLog(operation = "删除操作。。。。")
	@Override
	public boolean delete(String id) {
		return false;
	}
	@ServiceLog(operation = "异常操作测试", level = LogType.ERROR)
	@Override
	public boolean doError(String id) {
		try {
			@SuppressWarnings("unused")
			int i = 1 / 0;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
		return false;
	}
}
3.2 编写并执行spring boot的测试类
package net.xqlee.project;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import net.xqlee.project.service.TestService;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoSpringbootAopLogApplicationTests {
	@Autowired
	TestService testService;
	@Test
	public void contextLoads() {
		Map<String, Object> params = new HashMap<>();
		params.put("key1", "v1");
		params.put("key2", "v2");
		testService.insert(params, "000");
		testService.update("name", "id");
		testService.delete("leftso");
		testService.doError("leftso.com");
	}
}
执行核心控制台结果:
 从上图中可以看见,spring boot aop方式的日志已经拦截成功。并且注意观察在异常的时候并没有进行around日志拦截,也证实了代码中的备注。
从上图中可以看见,spring boot aop方式的日志已经拦截成功。并且注意观察在异常的时候并没有进行around日志拦截,也证实了代码中的备注。综合来讲,本文主要讲解了spring boot aop 拦截 AOP预处理日志/AOP后处理日志/AOP环绕日志和异常日志的处理。
https://www.leftso.com/article/304.html
 
                