引言
在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>
这里只是简单的实现AOP日志功能所以只引入了AOP和web包,实际业务情况可能会有数据库操作。
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 拦截 AOP预处理日志/AOP后处理日志/AOP环绕日志和异常日志的处理。
https://www.leftso.com/article/304.html