介绍:
AOP:面向编程,相对于OOP面向对象编程
Spring AOP 的存在就是用来解耦的 ,AOP可以让一组类共享相同的行为.在OOP中只能通过继承和实现接口,来使代码的耦合度增强,且类继承只能为单继承,阻碍更多行为添加进一组类上.AOP拟补了OOP的不足
AOP中的相关概念
Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
举个例子:(感觉不是很好)
就举最近出现的肺炎疫情来讲吧. 比如说我们到了三月份就可以进行上班了, 公司所属大楼,大楼门外 就是一个切面. 因为我们也不知道哪个员工得肺炎,那么物业就会在出入口进行设卡,其检测设备就是体温枪,红外感温摄像头,对进大楼的每个员工进行检测. 而根据肺炎症状是体温超过37度,未带口罩被感染引擎.物业老王就有权限阻止其进入大厦,报警对其进行隔离.
在 Spring AOP 中 Joint point
指代的是所有方法的执行点, 而 pointcut 是一个描述信息, 它修饰的是 Joint point
, 通过 pointcut, 我们就可以确定哪些 Joint point
可以被织入 Advice
.
Joint point
指代的是所有方法的执行点 上面例子中,就是所有要这栋大厦上班的员工,在Spring AOP中所有要被执行的方法执行点.
Pointcut(切点):我们知道所有要被执行的方法都被织入Advice中,我们一般都会根据需要加入织入Advice中,而Pointcut(切点)的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice
.那么上面例子中,根据肺炎症状:体温超过37度,未带口罩者 的员工物业老王就有权限阻止其进入大厦,报警对其进行隔离.
Advice(增强): 那就是对体温超过37度,未带口罩者的员工进行一些处理.比如说 进大厦之前先测体温,对应的是before前置增强;对未带口罩的员工体温正常后然后进行放行进入大厦,送一个口罩 ,送的口罩就是after后置增强了.
Aspect
::Aspect
是 point cut 与 Advice
的组合
我们来用代码实现一个统一日志栗子来实践理论知识:
Spring AOP 依赖 引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- google 工具包 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<!--Swagger-UI API文档生产工具-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!--Hutool Java工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.2</version>
</dependency>
另外那些都是使用的工具包.在工作中能给您很大的帮助.希望掌握.
做个controller层
AopDemoController.java
package com.example.demo.controller;
import com.example.demo.annotation.AopAnnotation;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AopDemoController {
@ApiOperation("这个是swagger注释")
@AopAnnotation(name = "方法注释")
@PostMapping("/test")
public Object getPath( Long s) {
System.out.println(s);
return s;
}
}
自定义注解
package com.example.demo.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;
/**
*注解本身是没有任何功能的,注解跟xml都是一种元数据,既解释数据的数据
*@author: Garol
*@create: 2020/5/26 11:38
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AopAnnotation {
String name( );
}
AOP核心配置 (注释很详细了,这就不啰嗦了)
AOPespect
package com.example.demo.config;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.example.demo.annotation.AopAnnotation;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.HttpRequestHandlerServlet;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author Administrator
*/
@Aspect
@Component
@Order(1)
public class AopAspect {
/**
* 注解声明切点 : 就是需要对某个文件的增强路径
*/
@Pointcut("execution(public * com.example.demo.controller.*.*(..))")
public void test() {
}
/**
* 前置增强: 方法执行前执行增强
*/
@Before("test()")
public void beforeTest(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
//获取注解信息
AopAnnotation annotation = methodSignature.getMethod().getAnnotation(AopAnnotation.class);
System.out.println("前置通知获取注解信息:" + annotation.name());
System.out.println("beforeTest");
}
/**
* 后置增强:
*/
@AfterReturning("test()")
public void afterTest() {
System.out.println("beforeTest21");
}
/**
* 环绕增强, 既可以前置增强,后置增强,异常增强. (建议使用)
*
* @param joinPoint
* @return
*/
@Around("test()")
public Object aroundTest(ProceedingJoinPoint joinPoint) {
//获取当前请求对象
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
//前置增强
long start = System.currentTimeMillis();
//一般我们可以记录些访问者信息比如ip 登录,操作用户,操作时间类的,我们可以创建个实体类来记录
//这边直接使用map存储了
Map map = Maps.newHashMap();
String url = request.getRequestURL().toString();
map.put("url", url);
map.put("uri", request.getRequestURI());
map.put("startTime", start);
map.put("bashPath", StrUtil.removeSuffix(url, URLUtil.url(url).getPath()));
map.put("ip", request.getRemoteAddr());
System.out.println("before1");
Object proceed = null;
try {
proceed = joinPoint.proceed();
//下面是后置增强
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
//判断是否有swagger注解
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation annotation = method.getAnnotation(ApiOperation.class);
String value = annotation.value();
//存储swagger注解
map.put("operation", value);
}
//获取注解信息
AopAnnotation annotation = method.getAnnotation(AopAnnotation.class);
//打印注解名称
System.out.println(annotation.name());
//打印方法的名称
System.out.println("方法名:" + method.getName());
System.out.println("after1");
long endTime = System.currentTimeMillis();
long spendTime = endTime - start;
System.out.println("总执行时间:" + (endTime - start));
//设置结束时间
map.put("endTime", endTime);
//执行时间
map.put("spendTime", spendTime);
//设置增强后的body
map.put("result", proceed);
System.out.println(map);
} catch (Throwable throwable) {
System.out.println("异常增强: ...");
//TODO 可以做异常增强
throwable.printStackTrace();
}
//将增强后的对象返回
return proceed;
}
}
执行程序结果:
before1
前置通知获取注解信息:方法注释
beforeTest
1
方法注释
方法名:getPath
after1
总执行时间:39
{result=1, bashPath=http://localhost:8080, ip=127.0.0.1, startTime=1590472592787, endTime=1590472592826, uri=/test, url=http://localhost:8080/test, spendTime=39}
beforeTest21
总结
在AOP中切面就是与业务逻辑独立,但又垂直存在于业务逻辑的代码结构中的通用功能组合;切面与业务逻辑相交的点就是切点;连接点就是把业务逻辑离散化后的关键节点;切点属于连接点,是连接点的子集;Advice(增强)就是切面在切点上要执行的功能增加的具体操作;在切点上可以把要完成增强操作的目标对象(Target)连接到切面里,这个连接的方式就叫织入