在最开始第一篇文章中我就给大家介绍了Spring中的AOP,大概都是一些基本概念和使用方法。附上地址:https://blog.csdn.net/bicheng4769/article/details/79501263
其实讲这么多,大家可能看一遍就会忘记,所以我习惯通过实战去加深对这个知识点的理解。现学现用嘛,所以在项目中就加上了日志管理。
为什么要使用AOP作为日志管理
想想如果我们不用AOP这种方式来处理日志,那么我们就要在每个需要加LOG的地方植入我们的代码。或许有人说,可以将重复的代码封装成方法,然后再去调用这个方法就可以了。但是如果我还要记录这个方法的参数呢?方法名呢?
需要了解的知识点
- 自定义注解
- java反射机制
- 注解形式使用AOP
开始准备
在着手写代码之前,我们可以想想可能会遇到什么样的问题:
1. 注解是否可以作为切点来使用:
切面、通知以及切点。切面、通知 其实都是一样的,唯一不同的是(切点是@Pointcut("execution(* com.storm.controller.*.*(..))")
)这次采用的是注解来作为切点,并没有具体的切点位置。那么注解是否可以作为切点来使用呢?遇到这种问题,第一反应肯定先去Spring官网文档查看内容:
https://docs.spring.io/spring/docs/5.0.4.RELEASE/spring-framework-reference/core.html#aop
我们可以看到@Pointcut
有几种类型(execution、@within、@annotation。。。。)其中关于@annotation
是这么写的:
@annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation
@within和@target针对类的注解,@annotation是针对方法的注解
所以切点的定义我们就可以使用@Pointcut(@annotation(com.test.annotation.log))
这种形式。
2. 在通知中如何获取到方法的参数和方法名:
毫无疑问我们肯定是利用java的反射机制。
编写代码
1. Spring开启注解模式:
<aop:aspectj-autoproxy proxy-target-class="true"/>
2. 自定义注解:
package com.perf.annotation;
import java.lang.annotation.*;
/**
* @author cj34920
* Date: 2018/03/08
*/
/**
* 注解用于什么地方
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
/**
* 注解是否将包含在JavaDoc中
*/
@Documented
/**
* 定义该注解的生命周期
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemControllerLog {
String description() default "";
}
3. 编写切面类
package com.perf.aop;
import com.perf.annotation.SystemControllerLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author cj34920
*/
@Aspect
@Component
public class SystemLogAspect {
@Pointcut("@annotation(com.perf.annotation.SystemControllerLog)")
public void controllerAspect() {
System.out.println("我是一个切入点");
}
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) throws Exception {
joinPoint.getArgs();
for (int i = 0; i < joinPoint.getArgs().length; i++) {
System.out.println(joinPoint.getArgs()[i]);
}
Map<String, Object> map = getAnnotationDescription(joinPoint);
String userName = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession().getAttribute("username").toString();
System.out.println("方法名" + joinPoint.getSignature().getName() + "登录人" + userName + "描述" + map.get("description"));
System.out.println("方法开始");
}
@After("controllerAspect()")
public void doAfter(JoinPoint joinPoint) throws Exception {
System.out.println("方法结束");
}
@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("方法出错了 " + e.getStackTrace()[0].getMethodName() + getErrorInfoFromException(e));
}
public String getErrorInfoFromException(Throwable e) {
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
return "\r\n" + sw.toString() + "\r\n";
} catch (Exception e2) {
return "ErrorInfoFromException";
}
}
/**
* 获取注解内容
*
* @param joinPoint
* @return
* @throws ClassNotFoundException
*/
public Map<String, Object> getAnnotationDescription(JoinPoint joinPoint) throws ClassNotFoundException {
Map<String, Object> map = new HashMap<String, Object>();
String targetName = joinPoint.getTarget().getClass().getName();
String targetMethod = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
for (Method method : methods) {
//判断是否是这个方法
if (method.getName().equals(targetMethod)) {
Class[] clazzs = method.getParameterTypes();
//判断参数是否一样
if (clazzs.length == arguments.length) {
map.put("description", method.getAnnotation(SystemControllerLog.class).description());
break;
}
}
}
return map;
}
}
4.controller测试类
@RequestMapping("test/apm")
@SystemControllerLog(description = "获取apm数据")
public void testApm(HttpServletResponse response) {
try {
int I = 1 / 0;
response.getWriter().write("OK");
} catch (IOException e) {
e.printStackTrace();
}
}
结束语:
在学习一个新的知识点的时候,我希望是先具体后抽象,在实际应用中掌握了解这个知识点,在掌握的基础上在对原理进行分析和理解。