AOP:面向切面编程,它是一种思想,对某一类事情集中处理。
OOP:面向对象编程。
AOP是一种思想,Spring AOP是一个框架,提供了一种对AOP思想的实现。
举例:一个系统中,很多页面都需要先验证用户登录,这种情况下,如果采用都写一遍用户登录验证,当功能越来越多,登录验证也越来越多,代码修改和维护成本就比较高了,对于功能统一且使用地方较多的,就可以考虑AOP统一处理。
AOP可以实现:
1.统一的用户登录判断;
2.统一方法执行时间统计;
3.统一日志记录;
4.统一的返回格式设置;
5.统一的异常处理;
6.事务的开启和提交等;
AOP可以扩充多个对象的某个能力,AOP可以说是OOP的补充和完善。
AOP的组成:
1.切面:针对与某一个功能的具体定义,某一个功能可能是登录验证功能,也有可能是日志记录功能。
2.切点:切面中的某一个方法。
3.连接点:用来匹配切面的多个点。
4.通知:在AOP的术语当中,切面的工作被称为通知。
通知可以分为:
前置通知使用@Before:通知方法会在目标方法调用之前执行;
后置通知使用@After:通知方法会在目标方法返回或者抛出异常后调用。
返回之后通知使用@AfterReturning:通知方法会在目标方法返回后调用。
抛出异常后通知使用@AfterThrowing:通知方法会在目标方法抛出异常后调用。
环绕通知使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为。
Spring AOP实现步骤:
1.添加Spring AOP框架支持;
2.定义切面和切点;
3.定义通知。
1.添加Spring AOP框架支持;
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.6</version>
</dependency>
2.定义切面和切点
切点表达式说明:
AspectJ支持三种通配符
1*:匹配任意字符,只匹配一个元素(包、类、方法、参数)
2 … :匹配任意字符,可以匹配多个元素,在表示类时,必须和*联合使用。
3 +:表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身
切点表达式由切点函数组成,其中execution()是最常用的切点函数,用来匹配方法,语法为:
execution(<修饰符><返回类型><包.类.方法(参数)><异常>)
修饰符和异常可以省略
3.定义通知(前置通知、后置通知、环绕通知)
1.Spring AOP是构建在动态代理基础上,因此Spring对AOP的支持局限于方法级别的拦截。
2.动态代理分为两类:
1).JDK Porxy:JDK动态代理机制
2).CGLIB动态代理
3.代理的生成时机:
a)编译期;
b)类加载期;
c)运行期.
1.JDK实现,要求被代理类必须实现接口,之后是通过InvocationHandler及Proxy,在运行时动态的在内存中生成了代理类对象,该代理对象是通过实现同样的接口实现,只是在该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成。
2.CGLIB实现,被代理类可以不实现接口,通过实现被代理类,在运行时动态的生成代理类对象。
用户登录拦截器实现:
1.创建一个拦截器:(判断用户是否登录,实现HandlerInterceptor接口)并重写preHandle
package com.example.demo.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 登录拦截器
*/
public class LoginInterretcept implements HandlerInterceptor {
/**
* 拦截方法
*/
@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;
}
//401表示没有权限
response.setStatus(401);
return false;
}
}
package com.example.demo.config;
/**
* 全局配置文件
*/
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
/**
* 配置拦截器和拦截规则
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterretcept()) //创建一个拦截器
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/**/*.html")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/user/*.login")
.excludePathPatterns("/user/reg");
}
//设置api统一的访问前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api",c->true);
}
}
统一异常处理使用的是@ControllerAdvice+@ExceptionHandler来实现的,@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
1.先给类上添加一个@ControllerAdvice注解,标识当前的类为一个控制器的增强类。
2.添加具体的异常返回业务代码,并标识为@ExceptionHandler为异常统一处理方法。
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
/**
* 统一异常处理
*/
@ControllerAdvice //1.标识为控制器增强类
@ResponseBody
public class ErrorAdvice {
//2.添加异常统一处理方法
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullException(NullPointerException e){
HashMap<String,Object> result=new HashMap<>();
result.put("succ",200); //http请求成功了,(大状态)
result.put("state",-1); //报错了(业务状态,小状态)
result.put("message",e.toString());
return result;
}
@ExceptionHandler(Exception.class)
public HashMap<String,Object> exception(Exception e){
HashMap<String,Object> result=new HashMap<>();
result.put("succ",200);
result.put("state",-1);
result.put("message",e.toString());
return result;
}
}
1.为什么需要统一数据返回格式?
a)方便前端程序员更好的接收和解析后端数据接口返回的数据;
b)减低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的;
c)有利于项目统一数据的维护和修改;
d)有利于后端技术部门的统一规范的标准制定。
2.统一数据返回格式的实现:@ControllerAdvice+ResponseBodyAdvice的方式实现
package com.example.demo.config;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
/**
* 统一数据格式处理
*/
@ControllerAdvice //1.
public class ResponseAdvice implements ResponseBodyAdvice {
//是否要进行内容重写
@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("succ",200); //返回大状态
result.put("state",1);
result.put("data",body);
return result;
}
}