Spring Boot 入门 AOP 日志处理

位置:首页>文章>详情   分类: 教程分享 > Java教程   阅读(12564)   2023-03-28 11:29:14

引言

    在Java编程开发中,经常会遇到用户业务日志的记录处理,如果每一个地方都去写大量的日志信息,那么工作量也是惊人的。这里主要讲解spring boot 中采用AOP方式进行日志的统一处理。spring mvc框架也可以参考。

一.spring boot 项目结构图



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日志执行结果从上图中可以看见,spring boot aop方式的日志已经拦截成功。并且注意观察在异常的时候并没有进行around日志拦截,也证实了代码中的备注。
综合来讲,本文主要讲解了spring boot aop 拦截 AOP预处理日志/AOP后处理日志/AOP环绕日志和异常日志的处理。
地址:https://www.leftso.com/article/304.html

相关阅读

spring boot又一个spring框架的经典项目,本文讲解spring boot入门的环境配置以及第一个项目,Spring Boot 入门教程
spring boot入门,spring boot是一个崭新的spring框架分支项目,本文讲解其属性配置相关
spring boot是一个崭新的spring框架分支项目,本文讲解基本的数据库配置
Spring Boot 入门 AOP 日志处理,这里主要讲解spring boot 中采用AOP方式进行日志的统一处理。spring 框架的处理也是一样。综合来讲,本文主要讲解了spring b...
Java编程中Spring Boot整合RabbitMQ实现消息中间件RabbitMQ的使用
Java编程中spring boot项目如何获取spring容器applicationContext
spring boot是一个崭新的spring框架分支项目,本文讲解spring boot中controller的常用注解使用
Spring boot 入门之CORS 跨域配置详解,spring 跨域配置详解。
spring boot 入门之security oauth2 jwt完美整合例子,Java编程中spring boot框架+spring security框架+spring security o...
spring boot 入门 使用spring.profiles.active来分区配置,,在spring boot中可以存在多个环境的配置文件通过配置spring.profiles.activ...