Springcloud學習筆記39--攔截器Interceptor詳細使用


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM