最近又用到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;
}
}