目录
一、拦截器
在做很多网页或者项目时都要用到强制登录功能达到不登陆无法访问一些功能的效果,后端程序根据session来判断用户是否登录,通过将判断方法封装成一个类的实现方法也是比较麻烦的,需要修改每个接口的处理逻辑/修改每个接口的返回结果/接口定义修改,前端代码也跟着修改;
此时使用拦截器同一拦截所有的请求并进行session校验就可以进一步的优化代码
1、拦截器快速入门
什么是拦截器?
拦截器是Spring框架的提供的核心功能之一,主要用来拦截用户的请求,在指定的方法前后根据业务需要执行预先设定的代码。
也就是说允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行也可以在用户请求前阻止其执行
在拦截器中开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的请求判断session中是否有登录用户信息,有就放行,没有就进行拦截
比如小区的大门口会有保安,如果不是户主就会进行拦截,出示身份信息后显示为户主就会放行,拦截器做的就是保安的工作。
拦截器的基本使用
(1)定义拦截器(保安要做什么) (2)注册配置拦截器(保安在哪工作)
自定义拦截器:实现HandlerInterceptor接口并重写其所有方法
package com.example.bookmanager.Interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
log.info("视图已渲染");
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
log.info("目标方法后执行");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("目标方法前执行");
return true;
}
}
preHandle()方法:目标方法前执行,如果返回true继续执行后续操作,返回false中断后续操作/
postHandle()方法:目标方法执行后执行
afterCompletion()方法:视图渲染完毕后执行,最后执行。
注册配置拦截器:实现WebMvcConfigurer接口并重写addInterceptors,将我们自定义的拦截器添加进去再设置拦截器拦截的请求路径
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");
}
}
启动服务访问任意请求观察日志:
可以看到preHandle方法在目标方法前执行,返回true后就放行了,开始执行目标方法,目标方法执行完之后执行postHandle和afterCompletion方法
我们将preHandle返回的设为false再看看日志
此时只执行了preHandle后就没有放行,拦截器拦截了请求,没有进行响应。
2、拦截器详解
拦截路径
拦截路径是指我们定义的这个拦截器对哪些请求生效。
我们在注册配置拦截器时通过addPathPatterns()方法指定要拦截哪些请求,也可以使用excludPathPatterns()指定不拦截哪些请求,刚才的代码中配置的/**表示拦截所有请求
比如说:用户登录校验,我们希望可以对除了登录之外的所有路径生效(我们期望的是拦截未登录的用户,如果连登录界面都拦截了相当于保安把自己拦截了)
@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,不能匹配/user/login /** 任意级路径 能匹配/user,/user/login /book/* /book下的一级路径 能匹配/book/addBook,不能匹配/book/addBook/1
以上拦截规则可以拦截此项目中的使用的URL,包括静态图片等
拦截器执行流程
正常的调用顺序:
有了拦截器后会在Controller之前进行相应的业务处理,执行的流程如下:
1、添加拦截器后执行Controller的方法之前请求会先被拦截器拦下,执行preHandle方法,这个方法返回一个布尔类型的值,如果返回true就表示放行本次操作,继续访问Controller中的方法,如果返回false则不会放行。
2、controller当中的方法执行完毕后再回过来执行postHandle这个方法以及afterCompletion方法,执行完毕后最终给浏览器响应数据。
登录校验
通过拦截器来完成图书管理系统中的登录校验功能
定义拦截器:
从session中获取用户信息,如果session不存在则返回false并设置http状态码为401,否则返回true
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("目标方法前执行");
HttpSession session = request.getSession();
if(session != null && session.getAttribute(session.getId()) != null) {
return true;
}
response.setStatus(401);
return false;
}
}
注册配置拦截器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/**/*.js") //排除前端静态资源
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/*.html");
}
}
原本获取图书列表的功能时需要进行登录校验,此时可以修改代码为:
@RequestMapping("/getListByPage")
public Result getBookList(PageResult pageResult, HttpSession session) {
log.info("getBookList");
PageResult<BookInfo> result = bookService.getBookList(pageResult);
return Result.success(result);
}
这样再通过postman发送请求进行测试:
1、查看图书列表:
可以看到状态码为401
2、登录
3、再次查看图书列表
数据进行了返回
二、适配器模式
定义
适配器模式也叫包装器模式,将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以相互兼容;
简单来说就是目标类不能直接使用,通过一个新类进行包装,适配调用方使用,把两个不兼容的接口通过一定的方式使之兼容
适配器模式角色
Target:目标接口(可以是抽象类或接口),客户希望直接用的接口
Adaptee:适配者,但是与Target不兼容
Adapter:适配器类,此模式的核心,通过继承或者引用适配者的对象把适配者转为目标接口
client:需要使用适配器的对象
适配器模式的实现
打印日志使用的slf4j就使用了适配器模式,SLF4J提供了一系列打印日志的api,底层调用的是log4j或者logback,我们作为调用者只需要调用slf4j的api就行;
slf4j接口:
public interface Slf4jApi {
public void log(String message);
}
log4j类:
public class Log4j {
void log4jFun(String msg) {
System.out.println(msg);
}
}
适配器:
public class Slf4jLog4jAdapter implements Slf4jApi {
Log4j log4j;
public Slf4jLog4jAdapter(Log4j log4j) {
this.log4j = log4j;
}
@Override
public void log(String message) {
log4j.log4jFun(message);
}
}
这样适配器模式就构造好了,接下来使用客户端调用:
public class Slf4jDemo {
public static void main(String[] args) {
Slf4jApi slf4jApi = new Slf4jLog4jAdapter(new Log4j());
slf4jApi.log("Hello World");
}
}
可以看出,我们不需要改变log4j的api,只需要通过适配器的转换下就可以更换日志框架,保障系统平稳运行
适配器模式的应用场景:
一般来说,适配器模式可以看作是一种“补偿模式”,用来补救设计上的缺陷,应用这种模式算是无奈之举,如果在设计初期就能协调规避接口不兼容的问题就不需要使用适配器模式了,所以适配器模式更多的应用场景主要是对正在运行的代码进行改造,并且希望可以复用原有代码实现新的功能。
三、统一数据返回格式
在图书管理系统的强制登陆功能中,我们共做了两部分工作:通过session判断用户是否登录/对后端返回数据进行封装并告知前端处理的结果;拦截器刚才帮我们实现了第一个功能,接下来看SpringBoot对第二个功能如何支持:
快速入门
统一的数据返回格式使用@ControllerAdvice注解和实现ResponseBodyAdvice接口的方式实现
@ControllerAdvice表示控制器通知类
创建ResponseAdvice实现ResponBodyAdvice接口,并在类上添加@ControllerAdvice注解
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
return null;
}
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return false;
}
}
supports方法:判断是否要执行beforeBodyWrite方法,true为执行/false为不执行,通过该方法可以选择哪些类或哪些方法的response要进行处理,其他的不进行处理。
beforeBodyWrite方法:对response方法进行具体操作处理。
从supports的returnType参数可以获取类名和方法名:
//获取执行的类
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
//获取执行的方法
Method method = returnType.getMethod();
添加统一数据返回格式前:
同一返回数据格式后:
存在的问题
SpringMVC默认会注册一些自带的HttpMessageConverter,从先后顺序分别是:ByteArrayHttpMessageConverter // StringHttpMessageConverter // SourceHttpMessageConverter // AllEncompassingFormHttpMessageConverter;
其中AllEncompassingHttpMessageConverter会根据项目依赖情况添加对应的HttpMessageConverter,在依赖中引入jackson包后容器会把MappingJacksonHttpMessageConverter自动注册到messageConverters链的末尾,Spring会根据返回的数据类型从messageConverters链选择合适的HttpMessageConverter,
当返回的数据是非字符串时使用的MappingJackonHttpMessageConverter写入返回对象
当返回的数据是字符串时StringHttpMessageConverter会先被遍历到,这时会认为StringHttpMessageConverter可以使用
在((HttpMessageConverter) converter).write的处理中会调用父类的write方法,由于SpringHttpMessageConverter重写了addDefaultHeaders方法所以会执行子类的方法,然而子类StringHttpMessageConverter的addDefaultHeaders方法定义接收的参数为String,此时t为result类型,所以会出现类型不匹配“Result cannot be cast to java.lang.String”的异常
统一返回的优点
1、方便前端程序员更好的接收和解析后端数据接口返回的数据
2、降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的
3、有利于项目统一数据的维护和修改
4、有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容
统一异常处理
统一异常处理使用的是@ControllerAdvice+@ExceptionHandler注解来实现的,@ControllerAdvjice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表示当出现异常时执行某个通知,也就是执行某个方法事件,如下:
@ControllerAdvice
public class ErrorDemo {
@ExceptionHandler
public Object handleException(Exception e) {
e.printStackTrace();
return e;
}
}
类名方法名返回值都可以自定义,重要的是注解
接口返回为数据时需要加@ResponseBody注解
以上代码表示出现exception异常就将异常返回,在实际开发中可以返回一个Result对象,对象中包含不同的错误信息,针对不同的异常返回不同的解结果
总结
本章节主要介绍了SpringBoot对一些统一功能的处理支持:
1、拦截器的实现主要分两部分:定义拦截器(实现HandlerInterceptor接口)/配置拦截器
2、统一数据返回格式通过@ControllerAdvice+ResponseBodyAdvice来实现
3、统一异常处理使用@ControllerAdvice +@ExceptionHandler来实现,并且可以分异常来处理
感谢观看
道阻且长,行则将至