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 = newTreeMap<>(); 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中,這樣的話有隱患,雖然,現在並沒有問題,嘿嘿;