目录
简介
AOP 是一种思想 , Spring AOP 框架 是具体实现 .
AOP (Aspect Oriented Programming) : 面向切面编程 .它是对某一类事情的集中处理 (针对某一方面的问题主动进行处理 ) .
举个栗子 :
当用户点到博客进行发布文章时 , 后端就要判断是否已经登录 , 当去删除一个文章时 , 也需要去判断当前是否是登录状态 . 每次涉及到权限问题时都需要判断用户登录状态 . 有了AOP之后 , 我们只需要在某一处配置一下 . 就可以实现所有需要判断用户登录状态的页面的功能.
因此, AOP 就可以用在 : 对于功能统一 , 且使用的地方较多的功能 , 就可以考虑 AOP 来统一处理了 .
AOP 可以实现 :
1. 统一日志记录 2. 统一方法执行时间统计 3. 统一的返回格式设置 4. 统一的异常处理 5. 事务的开启和提交等 .
AOP 组成
AOP 由 切面 , 切点 , 连接点 , 通知 组成 .
切面 : 简单来说 , 切面在程序中就是一个处理某方面具体问题的一个类 , 类里面包含了很多方法 , 而这些方法就是切点和通知 . (这个类是专门处理某一个问题的, 例如 :登录问题)
切点 : 用来进行主动拦截的规则(配置) .
连接点 : 可能会触发 AOP 规则的所有点 (所有请求).
通知 : (AOP 具体的执行动作 ).
前置通知 : 在执行目标方法之前执行的方法就叫做前置通知 .
后置通知 : 在执行了目标方法之前执行的方法就叫做后置通知 .
异常通知 : 在执行目标方法出现异常时 , 执行的通知 .
返回通知 : 目标方法执行了返回数据 (return) 时 , 执行的通知 .
环绕通知 : 在目标方法执行的周期范围内 (执行之前 , 执行时 , 执行后) 都可以执行的方法叫做环绕通知 .
Spring AOP 实现
实现步骤 :
1 . 添加 Spring AOP 依赖
2 . 定义切面 (创建切面类)
3 . 定义切点 (配置拦截规则)
4 . 定义通知的实现
1 . 添加依赖
注意 : 依赖中的版本号要对应Spring Boot 的版本号.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<!--对应Spring Boot 的版本-->
<version>2.7.13</version>
</dependency>
2 . 定义切面
使用 @Aspect 注解 : 意思就是告诉了框架这是一个切面类
使用 @Componet 注解 : 加上五大类注解 让这个类随着框架的启动而启动 .
@Aspect // 表示这是一个切面类
@Component // 随着框架一起启动
public class UserAspect {
}
3 . 定义切点 与 通知
切点 : @Pointcut 关键字来配置拦截的规则
通知 : 前置通知 @Before 通知方法会在目标方法调用之前执行 .
后置通知 @After 通知方法会在目标方法返回或者 抛出异常后调用 .
返回之后通知 @AfterReturning 通知方法会在目标方法返回之后调用.
抛异常后通知 @AfterThrowing 通知方法会在目标方法抛出异常后调用 .
环绕通知 @Around 通知包括了 通知方法之前 , 方法执行时 , 调用方法后 .
@Aspect // 表示这是一个切面类
@Component // 随着框架一起启动
public class UserAspect {
/**
* 切点 (拦截配置)
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut() {
}
/**
* 前置通知
*/
@Before("pointcut()")
public void beforeAdvice() {
System.out.println("前置通知已执行.");
}
/**
* 后置通知
*/
@After("pointcut()")
public void afterAdvice() {
System.out.println("后置通知已执行.");
}
}
通过网页去访问 UserController 类中的方法 :
/**
* 环绕通知
*/
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("进入环绕通知");
Object obj = null;
// 执行目标方法
obj = joinPoint.proceed();
System.out.println("退出环绕通知");
return obj;
}
动态代理
动态代理在Spring 中有两种实现方式 : JDK Proxy及 CFLIB
JDK Proxy实现 , 要求被代理类必须实现接口 , 之后是通过 InvocationHandler 及 Proxy , 在运行时动态的在内存中生成了代理类对象 , 该代理对象是通过实现同样的接口实现 (类似静态代理接口实现的方式) , 只是该代理类是在运行期时 , 动态的织入统一的业务逻辑字节码来完成 .
CGLIB 实现 , 被代理类可以不实现接口 , 是通过继承被代理类 , 在运行时动态的生成代理类对象 .
Spring 拦截器实现
步骤 : 首先要实现 HandlerInterceptor 接口 ,重写 preHeadler 方法 , 在方法中编写自己的业务代码 . 其次 将拦截器添加到配置文件中 , 并且设置拦截的规则 .
Spring 拦截器 充当了这个代理对象 , 也是通过动态代理和环绕通知的思想实现的 .
拦截器的实现 :
/**
* 自定义一个拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 返回一个 boolean 值 , true表示 验证成功了, false 表示验证失败
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 用户登录业务判断
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 证明用户已经登录
return true;
}
// 可以调整到登录页面
response.sendRedirect("/login.html");
// 或者去返回一个 401/403 没有权限码
// response.setStatus(401);
return false;
}
}
将拦截器添加到配置文件中 , 设置拦截规则 .
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/user/login"); // 排除的url地址 (不拦截的url 地址)
}
}
统一的异常处理
统一异常处理的实现是由 @ControllerAdvice + @ExceptionHandler 来实现的 .
@ControllerAdvice 表示控制器通知类.
@ExceptionHandler 是异常处理器 .
@ControllerAdvice
@ResponseBody
public class MyExHandler {
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullException(NullPointerException e) {
HashMap<String,Object> result = new HashMap<>();
result.put("code","-1");
// 错误码的秒睡信息
result.put("msg","空指针异常" + e.getMessage());
result.put("data",null);
return result;
}
}
统一的格式返回
实现步骤 : 创建类 , 添加 @ControllerAdvice , 实现 ResponseBodyAdvice 接口 .
并重写 supports 和 beforeBodyWrite 方法.
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg"," ");
result.put("data",body);
if (body instanceof String) {
// 当 body是 String时,需要进行特殊转换 .
try {
return objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return result;
}
}
End....