Spring AOP面向切面详解,记录操作日志

本文详细介绍如何使用AOP(面向切面编程)在Spring框架中记录操作日志,包括自定义注解、切面类定义及配置过程,实现业务逻辑与日志记录的分离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近又用到AOP记录操作日志,虽然很多年以前就已经用过了。但是那时候多数是百度的来的,并没有去深入研究。说白了,只是实现了功能,可能睡一觉又忘了。

AOP(Aspect-Oriented Programming)面向切面,切开封装的对象内部,并将那些影响了多个类并且与具体业务无关的公共行为 封装成一个独立的模块(称为切面)。更重要的是它又能巧妙将这些切面复原,不留痕迹的融入核心业务逻辑中。这样,对于日后横切功能的编辑和重用都能够带来极大的方便。AOP技术的具体实现,无非也就是通过动态代理技术或者是在程序编译期间进行静态的"织入"方式。下面是这方面技术的几个基本术语:    

1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。    

2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。    

3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。(around,before ,after)

4、aspect(切面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。   

* 另外配置该放在 application.xml 还是 SpringMvc.xml 中,请点击下面链接详细解析。

Spring AOP 注解失效,配置该放在application.xml还是springMvc.xml_邹田聪的博客的博客-优快云博客

因为个人比较喜欢用AOP的注解,下面就先介绍一下注解的使用

一、springMvc.xml配置

     <aop:aspectj-autoproxy proxy-target-class="true">
        <aop:include name="ControllerAspect" />
    </aop:aspectj-autoproxy>
    <bean id="ControllerAspect" class="cn.leatc.modules.log.LogAspect"></bean>

或者

 <aop:aspectj-autoproxy />

这里要注意头部beans里面有没有引入Aop哦,如果没有引入就引入

xmlns:aop="http://www.springframework.org/schema/aop"

xsi:schemaLocation="http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"

二、创建自定义注解AopLog

package cn.leatc.modules.log;

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 Zou Tiancong
 * @version 创建时间:2019年3月5日 上午11:30:03
 * @return AOP自定义注解
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AopLog {
	String description() default "";
}

三、定义切面类LogAspect

package cn.leatc.modules.log;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import cn.leatc.dev.common.Application;
import cn.leatc.dev.system.base.entity.SysLog;
import cn.leatc.dev.system.base.service.SysLogService;

/**
 * @author Zou Tiancong
 * @version 创建时间:2019年3月5日 上午11:30:28
 * @return
 */
@Component
@Aspect
public class LogAspect {
	private static final HttpServletRequest[] HttpServletRequest = null;
	// 注入service,用来将日志信息保存在数据库
	@Autowired
	private SysLogService sysLogService;

	// 配置切入点
	@Pointcut("@annotation(cn.leatc.modules.log.AopLog)")
	private void ControllerAspect() {
	}

	/*
	 * @Before("ControllerAspect()")//spring中Before通知   public void readBefore() {
	 * System.out.println("readBefore:现在时间是:" + new Date()); }
	 */

	@Around(value = "ControllerAspect()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable {
		// 日志实体对象
		SysLog log = new SysLog();

		// 方法通知前获取时间,为什么要记录这个时间呢?当然是用来计算模块执行时间的
		long start = System.currentTimeMillis();

		// 拦截的实体类,当前正在执行的controller
		Object modulename = pjp.getTarget();

		// 拦截的方法名称,当前正在执行的方法
		String operation = pjp.getSignature().getName();

		// 获取登录用户名称
		String username = Application.getShiroUser().getUserName();

		// 获取系统用户id
		String userid = Application.getShiroUser().getId();

		// 拦截方法参数
		List<Object> args = Arrays.asList(pjp.getArgs());

		// 获取request
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();

		// 定义一个list来装request的所有参数
		List<String> listparam = new ArrayList<String>();

		// 循环取request的值
		Enumeration<String> enu = request.getParameterNames();
		while (enu.hasMoreElements()) {
			String paraName = (String) enu.nextElement();
			listparam.add(paraName + ":" + request.getParameter(paraName));
		}

		// 拦截放参数类型
		Signature sig = pjp.getSignature();
		MethodSignature msig = null;
		if (!(sig instanceof MethodSignature)) {
			throw new IllegalArgumentException("该注解只能用于方法");
		}
		msig = (MethodSignature) sig;
		Class[] parameterTypes = msig.getMethod().getParameterTypes();
		Object object = null;

		// 获得被拦截的方法
		Method method = null;
		try {
			method = modulename.getClass().getMethod(operation, parameterTypes);
		} catch (NoSuchMethodException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (SecurityException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		// 为log实体对象赋值
		log.setUserid(userid);
		log.setOpttime(new Date());
		log.setOptip(cn.leatc.dev.common.WebUtils.getLocalIp(request));

		if (null != method) {
			// 判断是否包含自定义的注解,Aop就是我自己自定义的注解
			if (method.isAnnotationPresent(AopLog.class)) {
				AopLog aopLog = method.getAnnotation(AopLog.class);

				// 获取方法自定义注解的名称
				String methodNameString = aopLog.description();
				// 获取自定义注解的执行类型,增删改查
				int optype = aopLog.optype();
				log.setEventtype(optype);

				// try cath,判断方法是否执行成功
				try {
					object = pjp.proceed();
					// long end = System.currentTimeMillis();
					// 将计算好的时间保存在实体中
					log.setLogdesc(username + "    执行操作:" + methodNameString + "    执行结果: " + pjp.proceed()
							+ "    接收参数:" + listparam);
					// 保存进数据库
					sysLogService.insert(log);
				} catch (Throwable e) {
					// TODO Auto-generated catch block
					// long end = System.currentTimeMillis();;
					log.setLogdesc(username + "    执行操作:" + methodNameString + "    执行结果:" + new RuntimeException(e)
							+ "    接收参数:" + listparam);
					sysLogService.insert(log);
				}
			} else {// 没有包含注解
				object = pjp.proceed();
			}
		} else { // 不需要拦截直接执行
			object = pjp.proceed();
		}
		return object;
	}
}

四、现在就是去Controller加入自定义注解。@AopLog(description = "........")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邹田聪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值