1. 程式人生 > >springMvc使用攔截器自定義處理引數

springMvc使用攔截器自定義處理引數

前言:

  這兩天開發遇到一個需求,那就是在後臺接收到請求後,能不能自定義新增引數呢?

  我們知道request中是能獲取到前端獲取的引數的,但是在後端不能再往parameter中新增引數了,雖然可以使用request.setAttribute()方法往request中存放資料,但是這些資料springmvc在解析request中的資料時是不會解析的;

  而且,如果我們想對前端傳來的資料做處理這個方法也是無法實現的,我查看了一下網上的資料,並沒有發現springmvc有預留這樣的介面可以擴充套件,所以,我就考慮自己來處理一下它吧;

1,先找到springmvc是如何獲取引數的

  首先,要找到springmvc是在哪獲取引數的,於是我們通過debug模式查詢一次請求中,參與處理的類有哪些,終於,我們找到了這樣一個類:org.springframework.web.util.WebUtil,這個類中有這樣一個方法:

public static Map<String, Object> getParametersStartingWith(ServletRequest request, @Nullable String prefix) {
        Assert.notNull(request, "Request must not be null");
        Enumeration<String> paramNames = request.getParameterNames();
        Map<String, Object> params = new
TreeMap<>(); if (prefix == null) { prefix = ""; } while (paramNames != null && paramNames.hasMoreElements()) { String paramName = paramNames.nextElement(); if ("".equals(prefix) || paramName.startsWith(prefix)) { String unprefixed
= paramName.substring(prefix.length()); String[] values = request.getParameterValues(paramName); if (values == null || values.length == 0) { // Do nothing, no values found at all. } else if (values.length > 1) { params.put(unprefixed, values); } else { params.put(unprefixed, values[0]); } } } return params; }

springmvc就是使用這個方法獲取到request中所有的引數,那麼有讀者就問了,直接重寫一下這個方法不就得了,問題就解決啦;然而我得說,如果我們還想繼續用springmvc,那麼就不要這麼想,因為呼叫這個方法是這樣呼叫得:

1 public ServletRequestParameterPropertyValues(
2             ServletRequest request, @Nullable String prefix, @Nullable String prefixSeparator) {
3 
4         super(WebUtils.getParametersStartingWith(
5                 request, (prefix != null ? prefix + prefixSeparator : null)));
6     }
呼叫WebUtils類得位置

這是一個靜態方法,因此也不需要宣告例項,所以我們沒辦法自己重寫WebUtils類以後再交由這個類來呼叫,因此我們要找到可以注入得地方下手來做這件事,所以我就開始找,過程走了不少得彎路,終於,我找到了啦!

2,所有擴充套件得類(ps:我是使用得@requestMapping來處理的http請求,如果是其他方式,請自行去繼承相應的adapter就可以的)

(1)建立一個RequestMappingHandlerAdapter類的子類,並且交給spring去管理,這樣才能把這個類註冊到dispatchServlet中作為介面卡使用

 1 @Component
 2 public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
 3 
 4     protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
 5         return new MyServletInvocableHandlerMethod(handlerMethod);
 6     }
 7 
 8     protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
 9             throws Exception {
10 
11         return new MyWebDataBinderFactory(binderMethods, getWebBindingInitializer());
12     }
13 }
MyRequestMappingHandlerAdapter

(2)建立一個ServletRequestDataBinderFactory類的子類

 1 public class MyWebDataBinderFactory extends ServletRequestDataBinderFactory {
 2     /**
 3      * Create a new instance.
 4      *
 5      * @param binderMethods one or more {@code @InitBinder} methods
 6      * @param initializer   provides global data binder initialization
 7      */
 8     public MyWebDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer initializer) {
 9         super(binderMethods, initializer);
10     }
11 
12     @Override
13     protected ServletRequestDataBinder createBinderInstance(
14             @Nullable Object target, String objectName, NativeWebRequest request) throws Exception  {
15 
16         return new MyExtendedServletRequestDataBinder(target, objectName);
17     }
18 }
MyServletRequestDataBinderFactory

(3)建立一個ExtendedServletRequestDataBinder類的子類

 1 public class MyExtendedServletRequestDataBinder extends ExtendedServletRequestDataBinder {
 2 
 3 
 4     public MyExtendedServletRequestDataBinder(Object target, String objectName) {
 5         super(target, objectName);
 6     }
 7 
 8     public void bind(ServletRequest request) {
 9         MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
10         ((Consumer)request.getAttribute(WebConfig.DEFAULT_PARAM_NAME)).accept(mpvs);
11         MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
12         if (multipartRequest != null) {
13             bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
14         }
15         addBindValues(mpvs, request);
16         doBind(mpvs);
17     }
18 }
MyExtendedServletRequestDataBinder

(4)建立一個註冊攔截器的配置類WebMvcConfigurer的子類用來註冊攔截器

 1 @Configuration
 2 public class WebConfig implements WebMvcConfigurer {
 3 
 4 
 5     public final static String DEFAULT_PARAM_NAME = "paramDealFunction";
 6 
 7     @Autowired
 8     private PageInterceptor pageInterceptor;
 9 
10 
11     public void addInterceptors(InterceptorRegistry registry) {
12         //註冊自定義攔截器,新增攔截路徑和排除攔截路徑
13         registry.addInterceptor(pageInterceptor).addPathPatterns("/**/page");
14     }
15 
16     @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
17     public DispatcherServlet getDis(){
18         return new MyDispatcherServlet();
19     }
20 }
WebConfig

(5)建立一個攔截器HandlerInterceptor的子類

 1 @Component
 2 public class PageInterceptor implements HandlerInterceptor {
 3 
 4 
 5     @Override
 6     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 7         Consumer<MutablePropertyValues> function = (MutablePropertyValues mpvs) -> mpvs.addPropertyValue("pageStart",mpvs.get("pageNumber"));
 8         request.setAttribute(WebConfig.DEFAULT_PARAM_NAME, function);
 9         return true;
10     }
11 
12 }
View Code

總結一下所有的工作的目的:

(1)設計這個MyRequestMappingHandlerAdapter是為了重寫createDataBinderFactory,也就是重新注入DataBinderFactory;

(2)設計這個MyWebDataBinderFactory是為了重寫createBinderInstance方法,也就是重新注入一個ServletRequestDataBinder;

(3)設計這個MyExtendedServletRequestDataBinder是為了重寫bind方法,為了實現可以自定義處理引數;

其實這三個方法重寫已經可以實現了對引數的自定義處理,但是為了和springmvc攔截器整合,所以有新加了pageInterceptor和WebConfig兩個類;

好了,建立好這幾個類以後,你就可以在PageInterceptor中自行根據業務需求去處理引數啦!如果不關心是怎麼找到這個方法的讀者可以去改造上面的程式碼啦

下面上乾貨,記錄我是如何找到這個方法去處理引數的,也是整理一下我自己的思路;

3,整個分析的過程記錄

(1)畢竟springmvc還是特別好用的,我還是希望在不影響它任何功能的情況下實現這個處理引數的目的,我發現了實際處理引數的類,改造這個方法是最好的:

然後我找到了呼叫這個方法的方法,發現是直接呼叫的,別人的程式碼不能改,畢竟支援擴充套件,避免修改

於是找誰呼叫的這個方法,一定要直到找到可以注入的地方為止,發現下main這個方法就是調的本類的方法,沒有用,繼續找;

我發現下面這個方法是通過new出來的ServletRequestParameterPropertyValues物件進行獲取資料的,哎呀,如果這個地方是可以自定義注入的話,那問題就解決啦,注入一個自定義的子類,然後在這個子類中新增自定義引數的方法,就沒問題啦,?,但是不能,繼續

於是我發現了下面這個方法中這個binder物件是上個方法傳過來的,像這種傳過來的物件,是有可能是被適配的物件的,因此我覺得這個是有可能,因此繼續找這個物件是哪來的;

如果找到這個物件是可以注入的,那麼直接重寫這個類就可以啦(ps:我真的是這麼做的)

於是發現這個binder物件是在上個方法中通過一個叫binderFactory的物件建立的,現在去找這個物件是如何建立的;

然而我發現,這個方法是直接new出來的一個物件,這種還是沒有辦法更改的,因此只能考慮重寫這個binderFactory,於是,我們去找這個binderFactory是在哪建立的;

我們發現這個物件是從上個方法裡面傳過來的,哎,好像在哪遇到過?不管了,我們繼續找;

再往上找,發現這個binderFactory物件是InvocableHandlerMethod類中的一個屬性,哎,我們看看這個屬性是怎麼初始化的,千萬別是寫死的就好了;

然後我們發現,是通過set方法進行初始化的,那麼繼續debug,看看是誰呼叫的這個方法;

我們發現,是通過這樣一個方法來初始化的,並且這個binderFactory是從一個其他方法獲取到的,那麼繼續找它!吐血中。。。。

再找,發現了下面這個方法,發現還是new出來的,這種一律不管,這不是我們需要的方法,找!

於是找到這個方法,還是new出來的,這真是吐血了, 沒辦法了,這個方法是RequestMappingHandlerAdapter中的,看看這個物件能不能注入吧,繼續

於是我們繼續找,在DispatcherServlet類中發現了這段程式碼,這樣的話我們就看看這個HandlerAdapter是如何獲取的吧

這樣發現了,這個HandlerAdapter是DispatcherServlet類中的一個屬性handlerAdapters集合中的一個值,哎,看看這個值是怎麼初始化的,我的天,怎麼又感覺很熟悉呢?

於是找到了這個方法 ,這個方法中大概的意思是,在spring中獲取HandlerAdapter型別的所有子類,我的天啊,那麼有辦法了,自己寫一個HandlerAdapter吧;

4,總結一下

(1),如果想要改造別人的程式碼,首先確定這是一個好程式碼,不是任何程式碼都可以改造的;

(2),如果想要重寫某個方法,那麼就要找到這個方法所在的類是如何被呼叫的,只有可以注入的物件的方法,才能被改造,如果全部都是new出來的,那麼是無法改造的;

(3),能夠使用工廠,建造者,等設計模式建立的物件,不要使用new來建立,不然真的不好擴充套件;

5,這個方法其實還是有點問題的,比如預設處理requestMapping的HandlerAdapter也被註冊到了DispatcherServlet中,這樣的話有隱患,雖然,現在並沒有問題,嘿嘿;