AOP 简述
相信用过 Spring 的朋友应该都使用过 AOP,AOP 是 (Aspect Oriented Programming 的缩写),意思为面向切面编程,通过 预编译方式 和运行期间动态代理 实现程序功能的统一维护的一种技。
AOP 是 OOP(面向对象) 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的 耦合度 降低,提高程序的可重用性,同时提高了开发效率
SpringBoot 整合 AOP
Maven 依赖
<dependency>
<!-- 集成 SpringMVC -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 集成 Aspect -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建日志拦截器
这个日志拦截器主要是使用 AOP 来实现
@Aspect
@Component
public class LogAspect {
}
需要在类上加上 @Aspect 注解,表示这是一个切面类,还需要加上 @Component 注解,可以被扫描到
@Aspect
@Component
public class LogAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.example.filedemo.controller.*.*(..))")
public void log() {
}
}
定义一个 log() 方法,标上 @Pointcut 注解,表示这是一个切入点,execution() 是最常用的切点函数,其语法如下:
- execution():表达式主体
- 第一个 * 表示返回类型,* 号 表示所有类型
- 包名:表示需要拦截的包名,后面的两个小数点表示当前包和当前包的所有子包,com.example.filedemo.controller 包以及该包下的子包和所有类的方法
- 第二个 * 号表示类名,* 号表示所有的类
- *(…) 这个星号表示方法名, * 号表示所有的方法,括号里的小数点表示方法的参数,两个小数点表示任何参数
比如现在我们有这么一个需求,在用户请求时获取用户操作信息,例如:用户访问了哪些内容,URL,输入了什么参数,请求的是哪个方法。我们需要在方法请求前就输出该日志,那么我们应该这么做:
@Aspect
@Component
public class LogAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 切入点
*/
@Pointcut("execution(* com.example.filedemo.controller.*.*(..))")
public void log() {
}
/**
* 方法请求前
*/
@Before("log()")
public void doBefore() {
log.info("-------来请求了-------");
}
/**
* 方法请求后
* 说明:用户请求信息打印
* @param joinPoint 连接点
*/
@After("log()")
public void doAfter(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
RequestMethod requestLog = new RequestMethod(url, ip, classMethod, args);
log.info("Request : {}", requestLog);
}
}
可以将用户请求信息封装成一个类
private class RequestMethod {
private String url;
private String ip;
private String classMethod;
private Object[] args;
public RequestMethod(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}
@Override
public String toString() {
return "RequestMethod{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
也可以来个方法执行结果输出
/**
* 返回执行结果,在执行方法后返回
* pointcut:连接切入点
* returing:返回结果
* @param result
*/
@AfterReturning(returning = "result", pointcut = "log()")
public void doAfterReturn(Object result) {
log.info("Result : {}", result);
}
完整代码
package com.example.filedemo.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @author Woo_home
* @create 2020/9/17 21:04
*/
@Aspect
@Component
public class LogAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 切入点
*/
@Pointcut("execution(* com.example.filedemo.controller.*.*(..))")
public void log() {
}
/**
* 方法请求前
*/
@Before("log()")
public void doBefore() {
log.info("-------来请求了-------");
}
/**
* 方法请求后
* 说明:用户信息打印
* @param joinPoint 连接点
*/
@After("log()")
public void doAfter(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
RequestMethod requestLog = new RequestMethod(url, ip, classMethod, args);
log.info("Request : {}", requestLog);
}
/**
* 返回执行结果,在执行方法后返回
* @param result
*/
@AfterReturning(returning = "result", pointcut = "log()")
public void doAfterReturn(Object result) {
log.info("Result : {}", result);
}
private class RequestMethod {
private String url; // 请求 url
private String ip; // 请求 ip
private String classMethod; // 请求方法
private Object[] args; // 请求参数
public RequestMethod(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}
@Override
public String toString() {
return "RequestMethod{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
}
测试
我们写个简单的 Controller
package com.example.filedemo.controller;
import com.example.filedemo.util.ResultSet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* @author Woo_home
* @create 2020/9/17 21:42
*/
@Controller
public class TestController {
private Map<String, Object> map = new HashMap<>();
@GetMapping("/info1")
@ResponseBody
public Map info1() {
map.put("1", "zhangsan");
map.put("2", "lisa");
return map;
}
@GetMapping("/userInfo/{id}/{name}")
@ResponseBody
public Map userInfo(@PathVariable Integer id, @PathVariable String name) {
map.put(String.valueOf(id), name);
return map;
}
}
然后我们将 SpringBoot 跑起来测试一下,首先我们先访问 info1 这个接口
请求没什么问题,然后我们看下控制台的输出日志
2020-09-18 12:38:24.759 INFO 10192 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect : -------来请求了-------
2020-09-18 12:38:24.933 INFO 10192 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect : Request : RequestMethod{url='http://localhost:8080/info1', ip='0:0:0:0:0:0:0:1', classMethod='com.example.filedemo.controller.TestController.info1', args=[]}
2020-09-18 12:38:24.940 INFO 10192 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect : Result : {1=zhangsan, 2=lisa}
注意:这里的 ip 0:0:0:0:0:0:0:1 是因为服务器和客户端都在同一台电脑上所以才会出现的
OK,我们再访问 info2 这个接口
请求没什么问题,然后我们看下控制台的输出日志
2020-09-18 12:53:41.438 INFO 12616 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect : -------来请求了-------
2020-09-18 12:53:41.442 INFO 12616 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect : Request : RequestMethod{url='http://localhost:8080/userInfo/100/John', ip='0:0:0:0:0:0:0:1', classMethod='com.example.filedemo.controller.TestController.userInfo', args=[100, John]}
2020-09-18 12:53:41.447 INFO 12616 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect : Result : {100=John}
OK,关于 SpringBoot 整合 AOP 的内容就到这了