leftso 3674 0 2018-01-02 17:35:56

文章位置:左搜> 编程技术> 正文

引言

    在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环绕日志和异常日志的处理。