原文链接:https://www.cnblogs.com/luckyplj/p/15839125.html
1.拦截器Interceptor定义
拦截器是在面向切面编程中应用的,就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法。是基于JAVA的反射机制。
1.1 拦截器(Interceptor)执行顺序
(1)请求到达 DispatcherServlet
(2)DispatcherServlet 发送至 Interceptor ,执行 preHandle
(3)请求达到 Controller
(4)请求结束后,postHandle 执行
1.2 使用方法
HandlerInterceptorAdapter 这个适配器是由Spring MVC提供的(org.springframework.web.servlet.handler.HandlerInterceptorAdapter)继承此类,可以非常方便的实现自己的拦截器,而且不仅可实现Filter的所有功能,还可以更精确的控制拦截精度(shiro中的拦截器可以说是基于此实现的)
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
public HandlerInterceptorAdapter() {
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//preHandle在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制等处理;
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
//在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView;
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
//在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面),可以根据ex是否为null判断是否发生了异常,进行日志记录;
}
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
}
}
2.Spring Boot配置方式
2.1 自定义拦截器,需要继承HandlerInterceptorAdapter类
具体案例实现如下:
(1)拦截器AccessLogInterceptor
@Slf4j
public class AccessLogInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入到拦截器AccessLogInterceptor中:preHandle() 方法");
String remoteAddr=getRequestIp(request);
log.info("接收到来自[{}]请求",remoteAddr);
return true;
}
private String getRequestIp(HttpServletRequest request) {
String requestIp = request.getHeader("x-forwarded-for");
return requestIp;
}
}
(2)拦截器AuthorityInterceptor
@Slf4j
public class AuthorityInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入到拦截器AuthorityInterceptor中:preHandle() 方法");
return true;
}
}
2.2 注册拦截器,需要实现WebMvcConfigurer接口
WebMvcConfigurer配置类其实是Spring
内部的一种配置方式,采用JavaBean
的形式来代替传统的xml
配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer
接口;
在Spring Boot 1.5版本都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等。SpringBoot 2.0 后,该类被标记为@Deprecated(弃用)。官方推荐直接实现WebMvcConfigurer。
WebMvcConfigurer接口代码如下:
public interface WebMvcConfigurer {
void configurePathMatch(PathMatchConfigurer var1);
void configureContentNegotiation(ContentNegotiationConfigurer var1);
void configureAsyncSupport(AsyncSupportConfigurer var1);
void configureDefaultServletHandling(DefaultServletHandlerConfigurer var1);
void addFormatters(FormatterRegistry var1);
void addInterceptors(InterceptorRegistry var1);
void addResourceHandlers(ResourceHandlerRegistry var1);
void addCorsMappings(CorsRegistry var1);
void addViewControllers(ViewControllerRegistry var1);
void configureViewResolvers(ViewResolverRegistry var1);
void addArgumentResolvers(List<HandlerMethodArgumentResolver> var1);
void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> var1);
void configureMessageConverters(List<HttpMessageConverter<?>> var1);
void extendMessageConverters(List<HttpMessageConverter<?>> var1);
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> var1);
void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> var1);
Validator getValidator();
MessageCodesResolver getMessageCodesResolver();
}
需要重写addInterceptors方法,这里是对根目录"/"进行拦截,可以指定拦截url请求目录。
具体案例实现如下:
@Configuration
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
addV1Rule(registry);
}
private void addV1Rule(InterceptorRegistry registry) {
//注册自己的拦截器并设置拦截的请求路径
registry.addInterceptor(new AccessLogInterceptor()).addPathPatterns("/**"); //拦截所有请求
registry.addInterceptor(new AuthorityInterceptor()).addPathPatterns("/student/getStudentName"); //拦截student相关请求
}
}
2.3 案例测试结果分析
用户请求的url如下所示:
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
@Autowired
private IStudentService studentService;
@PostMapping("/getStudentName")
public void getStudentName(){
log.info("studentName:lucky");
}
}
postman请求的url: http://127.0.0.1:7010/student/getStudentName
控制台输出:
2022-01-24 13:47:15.599 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.interceptor.AccessLogInterceptor:18 | [] -进入到拦截器AccessLogInterceptor中:preHandle() 方法
2022-01-24 13:47:15.600 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.interceptor.AccessLogInterceptor:20 | [] -接收到来自[11112222]请求
2022-01-24 13:47:15.600 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.interceptor.AuthorityInterceptor:17 | [] -进入到拦截器AuthorityInterceptor中:preHandle() 方法
2022-01-24 13:47:15.605 | INFO | http-nio-7010-exec-1 | com.ttbank.flep.core.controller.StudentController:41 | [] -studentName:lucky
3. Springboot拦截器中获取request post请求中@RequestBody注解的Json请求参数
最近有一个需要从拦截器中获取post请求的参数的需求,这里记录一下处理过程中出现的问题。
于是,想到了使用流的方式,调用request.getInputStream()获取流,然后从流中读取参数;但是,经过拦截器后,参数经过@RequestBody注解赋值给controller中的方法的时候,却抛出了一个这样的异常:
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing
Spring 中的 request.getInputStream()和 request.getReader()只能被读取一次,而@RequestBody注解底层也是通过流来请求数据,所以需要把拦截器中的数据流保存下来,让 controller 层可以读取的数据。
3.1 自定义一个 RequestWrapper 子类
package com.ttbank.flep.core.interceptor;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* @Author lucky
* @Date 2022/12/5 14:26
*/
public class RequestWrapper extends HttpServletRequestWrapper {
private final String body;
public RequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
body = stringBuilder.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
}
3.2 拦截器层面
package com.ttbank.flep.core.interceptor;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @Author lucky
* @Date 2022/1/24 9:58
*/
@Slf4j
public class AccessLogInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入到拦截器AccessLogInterceptor中:preHandle() 方法");
String remoteAddr=getRequestIp(request);
log.info("接收到来自[{}]请求",remoteAddr);
RequestWrapper requestWrapper = new RequestWrapper(request);
String body = requestWrapper.getBody();
System.out.println(body);
return true;
}
private String getRequestIp(HttpServletRequest request) {
String requestIp = request.getHeader("x-forwarded-for");
if(StringUtils.isEmpty(requestIp)){
//获取请求封装的ip
requestIp=request.getRemoteAddr();
}
return requestIp;
}
}
3.3 过滤器Filter,用来把request传递下去
package com.ttbank.flep.core.filter;
import com.ttbank.flep.core.interceptor.RequestWrapper;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @Author lucky
* @Date 2022/12/5 14:30
*/
@Component
@WebFilter(urlPatterns = "/*")
public class ChannelFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(servletRequest instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
if(requestWrapper == null) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {
}
}
3.4 在启动类中注册拦截器
/**
* @Author lucky
* @Date 2021/11/25 16:44
*/
@SpringBootApplication(scanBasePackages="com.ttbank")
@EnableDiscoveryClient
@EnableScheduling
@MapperScan("com.ttbank.flep.core.mapper")
@ServletComponentScan //注册过滤器注解
public class FileFlepApplication {
public static void main(String[] args) {
SpringApplication.run(FileFlepApplication.class,args);
}
}
此时,利用postman测试;
可见,controller中可以正常获取json内容了;
参考文献:
https://blog.csdn.net/zhangpower1993/article/details/89016503
https://blog.csdn.net/kuishao1314aa/article/details/109777304
https://www.mianshigee.com/note/detail/19996jdn/
https://www.jianshu.com/p/69c6fba08c92