一.拦截器使用.
1.什么是拦截器?
- 拦截器是Spring框架提供的核心功能之⼀, 主要用来拦截用户的请求, 在指定方法前后, 根据业务需要执行预先设定的代码
- 也就是说, 允许开发人员提前预定义一些逻辑, 在用户的请求响应前后执行. 也可以在用户请求前阻止其执行. 在拦截器当中,开发人员可以在应用程序中做一些通用性的操作, 比如通过拦截器来拦截前端发来的请求, 判断Session中是否有登录用户的信息. 如果有就可以放行, 如果没有就进行拦截.也就是当前端请求发来的时候,不会直接执行,而是判断此时用户是否是处于登录状态,如果不是则进行拦截, 如果是则进行放行
- 比如我们去银行办理业务, 在办理业务前后, 就可以加一些拦截操作, 办理业务之前, 先取号, 如果带身份证了就取号成功, 业务办理结束, 给业务办理⼈员的服务进行评价.这些就是"拦截器"做的工作.
2.拦截器的入门程序.
1.定义拦截器.
- 自定义用户登录的拦截器,实现HandlerInterceptor接口,并重写所有方法.
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
//返回true表示不进行拦截
log.info("执行了登录拦截器的preHandle方法");
//获取session,判断session存储的userinfo信息是否为空
//true表示如果有session就返回session,如果没有就创建
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
UserInfo attribute = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY);
if (attribute == null||attribute.getId()<=0) {
response.setStatus(401);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("执行了登录拦截器的postHandle方法");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("执行了登录拦截器的afterCompletion方法");
}
}
- preHandle()方法: 目标方法执行前执行, 如果返回true则不进行拦截; 返回false, 进行拦截
- postHandle()方法: 目标方法执行后执行.
- afterCompletion()方法: 视图渲染完毕后执行, 最后执行(后端人员几乎不用管视图如何操作,交给前端即可,所以此处不做过多的介绍).
2.注册配置拦截器.
- 实现WebMvcConfigurer接口,并重写addInterceptors()方法.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器,并告诉他拦截路径
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/css/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/pic/**")
.excludePathPatterns("/**/*.html")
;
}
}
- 启动服务, 试试访问任意请求, 观察后端日志:
二.拦截器的使用细节.
1. 拦截器的拦截路径配置
- 拦截路径是指我们定义的这个拦截器, 对哪些请求生效.
- 我们在注册配置拦截器的时候, 通过 addPathPatterns() 方法指定要拦截哪些请求. 也可以通过excludePathPatterns() 指定不拦截哪些请求. 上述代码中, 我们配置的是 /** , 表示拦截所有的请求
- 比如用户登录校验, 我们希望可以对除了登录之外所有的路径生效.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login");//设置拦截器拦截的请求路径
(/** 表⽰拦截所有请求)
}
}
- 在拦截器中除了可以设置 /** 拦截所有资源外,还有一些常见拦截路径设置:
拦截路径 | 含义 | 举例 |
---|---|---|
/* | 一级路径 | 能匹配/user,/book,/login,不能匹配 /user/login |
/** | 任意级路径 | 能匹配/user,/user/login,/user/reg |
/book/* | /book下的一级路径 | 能匹配/book/addBook,不能匹配/book/addBook/1,/book |
/book/** | /book下的任意级路径 | 能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login |
- 以上拦截规则可以拦截此项目中的使用 URL,包括静态文件(图片文件, JS 和 CSS 等文件).
2. 拦截器执行流程
- 正常的调用顺序:
- 有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图:
- 添加拦截器后, 执行Controller的方法之前, 请求会先被拦截器拦截住. 执行preHandle() 方法, 这个方法需要返回⼀个布尔类型的值. 如果返回true, 就表示放行本次操作, 继续访问controller中的方法. 如果返回false,则不会放行(controller中的方法也不会执行).
- controller当中的方法执行完毕后,再回过来执行 postHandle() 这个方法以及
afterCompletion() 方法,执行完毕之后,最终给浏览器响应数据
三.适配器模式
什么是适配器模式:
适配器模式, 也叫包装器模式. 将一个类的接口,转换成客户期望的另一个接口, 适配器让原本接口不兼容的类可以合作无间. 简单来说就是==目标类不能直接使用, 通过一个新类进行包装一下, 适配调用方使用. 把两个不兼容的接口通过一定的方式使之兼容.
- 适配器模式角色:
- Target: 目标接口 (可以是抽象类或接口), 客户希望直接用的接口
- Adaptee: 适配者, 但是与Target不兼容
- Adapter: 适配器类, 此模式的核心. 通过继承或者引用适配者的对象, 把适配者转为目标接口
- client: 需要使用适配器的对象
举一个使用适配器模式的例子.
前面学习的slf4j 就使用了适配器模式, slf4j提供了一系列打印日志的api, 底层调用的是log4j 或者 logback来打日志, 我们作为调用者, 只需要调用slf4j的api就行了.
- 基本结构:
public interface Slf4jLog {
void log(String msg);
}
public class Log4j {
public void log(String msg) {
System.out.println("我是Log4j,需要打印的信息为: "+msg);
}
}
public class Log4jAdapter implements Slf4jLog{
private Log4j log4j;
public Log4jAdapter(Log4j log4j){
this.log4j = log4j;
}
@Override
public void log(String msg) {
//使用Log4j进行打印
log4j.log("我是适配器,打印的信息为: "+msg);
}
}
public class Main {
public static void main(String[] args) {
Slf4jLog slf4jLog = new Log4jAdapter(new Log4j());
slf4jLog.log("我是客户端");
}
}
运行结果:
- 可以看出, 我们不需要改变log4j的api,只需要通过适配器转换下, 就可以更换日志框架, 保障系统的平稳运行.
- 适配器模式应用场景
- 一般来说,适配器模式可以看作一种"补偿模式",用来补救设计上的缺陷. 应用这种模式算是"无奈之举", 如果在设计初期,我们就能协调规避接口不兼容的问题, 就不需要使用适配器模式了. 所以适配器模式更多的应用场景主要是对正在运行的代码进行改造, 并且希望可以复用原有代码实现新的功能. 比如版本升级等.
四. 统一数据返回格式.
- 强制登录案例中, 我们共做了两部分工作
- 通过Session来判断用户是否登录.
- 对后端返回数据进行封装, 告知前端处理的结果.
快速入门:
统一的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现
@ControllerAdvice 表示控制器通知类, 添加类 ResponseAdvice , 实现ResponseBodyAdvice 接口, 并在类上添加@ControllerAdvice 注解.
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
//序列化
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// if(returnType.getMember().)
//是否需要处理
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
}
if (body instanceof String) {
//序列化String
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
- supports方法: 判断是否要执行beforeBodyWrite方法. true为执行, false不执行. 通过该方法可以选择哪些类或哪些方法的response要进行处理, 其他的不进行处理.
- beforeBodyWrite方法: 对response方法进行具体操作处理.
添加统一数据返回格式之前:
添加统一数据返回格式之后:
- 优点:
- 方便前端程序员更好的接收和解析后端数据接口返回的数据
- 降低前端程序员和后端程序员的沟通成本, 按照某个格式实现就可以了, 因为所有接口都是这样返回的.
- 有利于项目统一数据的维护和修改.
- 有利于后端技术部门的统一规范的标准制定, 不会出现稀奇古怪的返回内容.
String问题.(源码分析)
五.统一异常处理
统⼀异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,
@ControllerAdvice 表示控制器通知类, @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件
- 代码实现:
@Slf4j
@ControllerAdvice
@ResponseBody
//如果不加会认为你返回的是一个视图,这个视图显然不存在,就会出现404的错误
public class ExceptionAdvice {
@ExceptionHandler(value = Exception.class)
public Result handleException(Exception e) {
log.error("发生异常, e : "+ e);
return Result.fail("内部错误");
}
@ExceptionHandler()
public Result handleException(NullPointerException e) {
log.error("发生空指针异常, e : "+ e);
return Result.fail("内部错误");
}
@ExceptionHandler(ArithmeticException.class)
public Result handleException(ArithmeticException e) {
log.error("发生算术异常, e : "+ e);
return Result.fail("内部错误");
}
}
模拟制造异常:
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1(){
int result = 10/0;
return "string";
}
@RequestMapping("/t2")
public Integer t2(){
String str = null;
System.out.println(str.length());
return 1;
}
@RequestMapping("/t3")
public Boolean t3(){
Integer[] integers = new Integer[]{1,2,3,4};
System.out.println(integers[5]);
return true;
}
}
- 当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配/test/t2 抛出ArithmeticException, 运行结果如下:
- /test/t3 抛出NullPointerException, 运行结果如下: