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,表示能掃描到該類。
當有多個過濾器時,通過注解@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方法中.當在過濾器鏈上最后一個過濾器的doFilter方法中調(diào)用chain.doFilter(request, httpServletResponse);時,將會把請求轉(zhuǎn)發(fā)到Servlet中,再分配到對應(yīng)的Controller的方法中。當從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);
? ? ? ? //獲取當前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-02
Java使用Instant時輸出的時間比預(yù)期少了八個小時
在Java中,LocalDateTime表示沒有時區(qū)信息的日期和時間,而Instant表示基于UTC的時間點,本文主要介紹了Java使用Instant時輸出的時間比預(yù)期少了八個小時的問題解決,感興趣的可以了解一下2024-09-09
Spring boot項目中異常攔截設(shè)計和處理詳解
這篇文章主要介給大家紹了關(guān)于Spring boot項目中異常攔截設(shè)計和處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習或者使用spring boot具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起看看吧2018-12-12
SpringBoot實現(xiàn)網(wǎng)頁消息推送的5種方法小結(jié)
項目開發(fā)中,實時消息推送已成為提升用戶體驗的關(guān)鍵技術(shù),本文將詳細介紹SpringBoot中實現(xiàn)網(wǎng)頁消息推送的幾種主流方案,希望對大家有所幫助2025-03-03
SpringBoot實現(xiàn)websocket服務(wù)端及客戶端的詳細過程
文章介紹了WebSocket通信過程、服務(wù)端和客戶端的實現(xiàn),以及可能遇到的問題及解決方案,感興趣的朋友一起看看吧2024-12-12

