Springboot應(yīng)用中過濾器如何修改response的header和body內(nèi)容
Springboot過濾器修改response的header和body內(nèi)容
springboot添加過濾器,繼承Filter接口,實現(xiàn)doFilter方法
方法一,實現(xiàn)類增加注解@WebFilter,注解參數(shù)filterName表示過濾器名稱,urlPatterns表示要過濾的url路徑,在啟動類增加注解@ServletComponentScan,表示能掃描到該類。
當(dāng)有多個過濾器時,通過注解@Order,注解參數(shù)大小表示過濾器執(zhí)行的縣厚順序,越小越先執(zhí)行
@WebFilter(filterName = "responseFilter",urlPatterns = "/*") public class ResponseFilter implements Filter
方法二,創(chuàng)建新配置類,添加 @Configuration 注解,將自定義Filter加入過濾鏈。
@Configuration public class DefinedFilterConfig { ? ? @Bean ? ? public FilterRegistrationBean responseFilter() { ? ? ? ? FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); ? ? ? ? ResponseFilter responseFilter = new ResponseFilter(); ? ? ? ? filterRegistrationBean.setFilter(responseFilter); ? ? ? ? filterRegistrationBean.addUrlPatterns("/*");//配置過濾規(guī)則 ? ? ? ? filterRegistrationBean.setName("responseFilter");//設(shè)置過濾器名稱 ? ? ? ? filterRegistrationBean.setOrder(1);//執(zhí)行次序 ? ? ? ? return filterRegistrationBean; ? ? } }
springboot在response中添加或者修改header
分兩種情況
- 情況1,在chain.doFilter(servletRequest, servletResponse)代碼之前
servletResponse.addHeader("XXX", "xxxx");
或者
servletResponse.setHeader("XXX", "xxxx");
兩者的區(qū)別是addHeader不會覆蓋,只會追加,會照成headerName重名
setHeader會覆蓋重名的headerName
這樣是可以添加成功
- 情況2,在chain.doFilter(servletRequest, servletResponse)代碼之后
添加addHeader或者serHeader無效,是因為這和過濾器的處理流程以及對header的處理時機有關(guān)
首先過濾器鏈的處理流程是:進入到一個過濾器的doFitler方法中,處理一些邏輯,然后調(diào)用chain.doFilter(request, httpServletResponse);進入到過濾器鏈的下一個過濾器的doFilter方法中.當(dāng)在過濾器鏈上最后一個過濾器的doFilter方法中調(diào)用chain.doFilter(request, httpServletResponse);時,將會把請求轉(zhuǎn)發(fā)到Servlet中,再分配到對應(yīng)的Controller的方法中。當(dāng)從Controller的方法中退出,再回到最后一個過濾器的doFilter方法中之前,就將會把respone對象上的header寫入到headerBuffer中。
所以,在chain.doFilter()方法之后,一方面是給response對象設(shè)置header不會成功,因為發(fā)現(xiàn)response對象的狀態(tài)已經(jīng)是commited狀態(tài),就不會再寫入到headers里,另一方面,即便通過反射的方式寫入了,也不會輸出給客戶端,因為headers已經(jīng)處理過了。
springboot在response中修改body
spring提供了HttpServletResponse的包裝類HttpServletResponseWrapper
首先需要繼承該包裝類,生成自定義包裝類BodyCachingHttpServletResponseWrapper
public class BodyCachingHttpServletResponseWrapper extends HttpServletResponseWrapper { ? ? private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ? ? private HttpServletResponse response; ? ? private PrintWriter pwrite; ? ? public BodyCachingHttpServletResponseWrapper(HttpServletResponse response) { ? ? ? ? super(response); ? ? ? ? this.response = response; ? ? } ? ? public byte[] getBytes() { ? ? ? ? if(pwrite != null) { ? ? ? ? ? ? pwrite.close(); ? ? ? ? ? ? return byteArrayOutputStream.toByteArray(); ? ? ? ? } ? ? ? ? if(byteArrayOutputStream != null) { ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? byteArrayOutputStream.flush(); ? ? ? ? ? ? } catch (IOException e) { ? ? ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? return byteArrayOutputStream.toByteArray(); ? ? } ? ? @Override ? ? public ServletOutputStream getOutputStream() { ? ? ? ? return new ServletOutputStreamWrapper(this.byteArrayOutputStream , this.response); ? ? } ? ? @Override ? ? public PrintWriter getWriter() throws IOException { ? ? ? ? pwrite = new PrintWriter(new OutputStreamWriter(this.byteArrayOutputStream , this.response.getCharacterEncoding())); ? ? ? ? return pwrite; ? ? } ? ? private static class ServletOutputStreamWrapper extends ServletOutputStream { ? ? ? ? private ByteArrayOutputStream outputStream; ? ? ? ? private HttpServletResponse response; ? ? ? ? public ServletOutputStreamWrapper(ByteArrayOutputStream outputStream, HttpServletResponse response) { ? ? ? ? ? ? super(); ? ? ? ? ? ? this.outputStream = outputStream; ? ? ? ? ? ? this.response = response; ? ? ? ? } ? ? ? ? @Override ? ? ? ? public boolean isReady() { ? ? ? ? ? ? return true; ? ? ? ? } ? ? ? ? @Override ? ? ? ? public void setWriteListener(WriteListener listener) { ? ? ? ? } ? ? ? ? @Override ? ? ? ? public void write(int b) throws IOException { ? ? ? ? ? ? this.outputStream.write(b); ? ? ? ? } // ? ? ? ?@Override // ? ? ? ?public void flush() throws IOException { // ? ? ? ? ? ?if (! this.response.isCommitted()) { // ? ? ? ? ? ? ? ?byte[] body = this.outputStream.toByteArray(); // ? ? ? ? ? ? ? ?ServletOutputStream outputStream = this.response.getOutputStream(); // ? ? ? ? ? ? ? ?outputStream.write(body); // ? ? ? ? ? ? ? ?outputStream.flush(); // ? ? ? ? ? ?} // ? ? ? ?} ? ? }
然后通過過濾器,將自定義包裝類BodyCachingHttpServletResponseWrappe傳遞到chain.doFilter(servletRequest, bodyCachingHttpServletResponseWrapper ),通過bodyCachingHttpServletResponseWrapper .getBytes()方式獲得原有body的內(nèi)容。
最后自己根據(jù)自己的邏輯代碼,修改原有body的內(nèi)容,重新寫入輸出流即可
public class ResponseFilter implements Filter { ? ? @Override ? ? public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) ? ? ? ? ? ? throws IOException, ServletException { ? ? ? ? BodyCachingHttpServletResponseWrapper responseWrapper = new BodyCachingHttpServletResponseWrapper((HttpServletResponse) servletResponse); ? ? ? ? //獲取當(dāng)前accessToken ? ? ? ? Optional<String> currentUserAccessTokenOptional = SecurityUtils.getCurrentUserAccessToken(); ? ? ? ? String currentUserAccessToken = null; ? ? ? ? if(currentUserAccessTokenOptional.isPresent()) { ? ? ? ? ? ? currentUserAccessToken = currentUserAccessTokenOptional.get(); ? ? ? ? } ? ? ? ? responseWrapper.addHeader("xuncai_access_token", currentUserAccessToken); ? ? ? ? chain.doFilter(servletRequest, responseWrapper); ? ? ? ? Optional<String> xTotalCount = Optional.ofNullable(responseWrapper.getHeader("X-Total-Count")); ? ? ? ? byte[] writeValueByte = null; ? ? ? ? if(xTotalCount.isPresent() && responseWrapper.getBytes() != null) { ? ? ? ? ? ? String currentTotal = xTotalCount.get(); ? ? ? ? ? ? String currentData = new String(responseWrapper.getBytes()); ? ? ? ? ? ? String writeValue = "{\"total\":"+currentTotal+",\"data\":"+currentData+"}"; ? ? ? ? ? ? writeValueByte = writeValue.getBytes(); ? ? ? ? }else { ? ? ? ? ? ? writeValueByte = responseWrapper.getBytes(); ? ? ? ? } ? ? ? ? //重新寫入輸出流 ? ? ? ? ServletOutputStream outputStream = servletResponse.getOutputStream(); ? ? ? ? outputStream.write(writeValueByte); ? ? ? ? outputStream.flush(); ? ? ? ? outputStream.close(); ? ? } }
經(jīng)過測試,一切OK。
Springboot在絕大部分過濾器中修改請求頭信息
實現(xiàn)方式
反射。
代碼
package one.util; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.RequestFacade; import org.apache.tomcat.util.http.MimeHeaders; import org.springframework.util.ObjectUtils; import javax.servlet.ServletRequestWrapper; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class HeadersEdit { private final HttpServletRequest request; private Map<String, String> modifyHeaders; private MimeHeaders itsHeaders; private boolean isDeleteKnown=true; public HeadersEdit(HttpServletRequest request){ this.request=request; modifyHeaders= new HashMap<>(); itsHeaders=getMimeHeaders(getRequestFacade()); } private HttpServletRequest getRequestFacade(){ try{ Field field= ServletRequestWrapper.class.getDeclaredField("request"); HttpServletRequest result; field.setAccessible(true); result=(HttpServletRequest)field.get((request)); while(!(result instanceof RequestFacade)){ result=(HttpServletRequest)field.get((result)); } return result; }catch (Exception e){ e.printStackTrace(); throw new RuntimeException("HeadersEdit Error!"); } } private MimeHeaders getMimeHeaders(HttpServletRequest requestFacade){ try{ Field itsRequest= RequestFacade.class.getDeclaredField("request"); itsRequest.setAccessible(true); HttpServletRequest o5=(HttpServletRequest) itsRequest.get(requestFacade); Field coyoteField= Request.class.getDeclaredField("coyoteRequest"); coyoteField.setAccessible(true); org.apache.coyote.Request coyoteRequest=(org.apache.coyote.Request) coyoteField.get(o5); Field headers=org.apache.coyote.Request.class.getDeclaredField("headers"); headers.setAccessible(true); return (MimeHeaders)headers.get(coyoteRequest); }catch (Exception e){ e.printStackTrace(); throw new RuntimeException("HeadersEdit Error!"); } } private boolean setValueExecutor(Map<String,String>newHeaders){ String key; for(Map.Entry<String, String> entry : newHeaders.entrySet()){ key=entry.getKey(); if(itsHeaders.getHeader(key)!=null){ if(isDeleteKnown){ itsHeaders.removeHeader(key); }else{ return false; } } itsHeaders.addValue(key).setString(entry.getValue()); } return true; } public boolean setValue(String key,String value){ if(ObjectUtils.isEmpty(key)||ObjectUtils.isEmpty(value)){ return false; } Map<String,String>map=new HashMap<>(); map.put(key,value); return setValueExecutor(map); } public boolean setValue(Map<String,String>newHeaders){ if(ObjectUtils.isEmpty(newHeaders)){ return false; } return setValueExecutor(newHeaders); } public void changeCheck(){ isDeleteKnown=!isDeleteKnown; } }
測試環(huán)境
springsecurity中的過濾器、以及普通的過濾器。
原理簡述
許多過濾器都間接繼承了ServletRequestWrapper類,它有個屬性叫request。
springsecurity或者普通的過濾器類只是將其包裝了多層,通過反射一層一層剝開即可。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合Redis實現(xiàn)token緩存
于token通常會被多次使用,我們需要把它保存到緩存中,以減少頻繁地訪問數(shù)據(jù)庫,本文主要介紹了SpringBoot整合Redis實現(xiàn)token緩存,感興趣的可以了解一下2024-02-02Java使用Instant時輸出的時間比預(yù)期少了八個小時
在Java中,LocalDateTime表示沒有時區(qū)信息的日期和時間,而Instant表示基于UTC的時間點,本文主要介紹了Java使用Instant時輸出的時間比預(yù)期少了八個小時的問題解決,感興趣的可以了解一下2024-09-09Spring boot項目中異常攔截設(shè)計和處理詳解
這篇文章主要介給大家紹了關(guān)于Spring boot項目中異常攔截設(shè)計和處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起看看吧2018-12-12SpringBoot實現(xiàn)網(wǎng)頁消息推送的5種方法小結(jié)
項目開發(fā)中,實時消息推送已成為提升用戶體驗的關(guān)鍵技術(shù),本文將詳細介紹SpringBoot中實現(xiàn)網(wǎng)頁消息推送的幾種主流方案,希望對大家有所幫助2025-03-03SpringBoot實現(xiàn)websocket服務(wù)端及客戶端的詳細過程
文章介紹了WebSocket通信過程、服務(wù)端和客戶端的實現(xiàn),以及可能遇到的問題及解決方案,感興趣的朋友一起看看吧2024-12-12