概述
前言
笔者的项目出现双返回值的问题,然而返回String的返回值时却十分正常,引起了笔者的好奇心。这章源码分析一下,主要从filter的过滤计数、双返回值的执行过程、一次性filter的用法分析。
1. filter执行过程
先简单说一下,每次执行请求,Tomcat等容器会创建责任链,filter的执行时,框架会计数,每个filter做一次dofilter操作,然后执行servlet的操作,是责任链模式的体现。
还是上一章的demo,每次请求执行需要创建
org.apache.catalina.core.StandardWrapperValve// Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
跟踪创建过程,非常简单
/**
* Construct a FilterChain implementation that will wrap the execution of
* the specified servlet instance.
*
* @param request The servlet request we are processing
* @param wrapper The wrapper managing the servlet instance
* @param servlet The servlet instance to be wrapped
*
* @return The configured FilterChain instance or null if none is to be
* executed.
*/
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
// If there is no servlet to execute, return null
if (servlet == null)
return null;
// Create and initialize a filter chain object
//创建责任链,ApplicationFilterChain
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
//content容器拿到filter map
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return filterChain;
// Acquire the information we will need to match filter mappings
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
//filter塞进ApplicationFilterChain中
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}
然后去dofilter
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
跟踪org.apache.catalina.core.ApplicationFilterChain
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
//就是这里,计数,表示设计之初每次filter只执行一次dofilter
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
//执行完filter就做servlet.service(request, response);
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
到此,filter的核心原理就明白了,创建责任链,塞filter,计数dofilter,执行servlet.service
2. 双返回值原理分析
虽然是我自己业务代码的问题,但是通过上面的源码可以看出,笔者示例demo
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
try {
LOGGER.info("=============secondFilter============");
//模拟异常
throw new RuntimeException("xxx");
} catch (Exception e) {
filterChain.doFilter(servletRequest, servletResponse);
//e.printStackTrace();
}
filterChain.doFilter(servletRequest, servletResponse);
}
这个filter执行2次dofilter,导致计数超标,然后执行2次servlet.service方法,执行本身也没什么事,关键在于servlet的response返回的时候,写操作的方式是不一样的,跟踪org.springframework.web.servlet.DispatcherServlet
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//本质在这里
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
继续跟踪,发现org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor在处理返回值的时候,HttpMessageConverter原因造成的。
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
Spring会在converter中选择一个converter处理返回值操作。
处理bean以及map时,springboot默认使用org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter
/**
* Method that can be used to serialize any Java value as
* JSON output, using provided {@link JsonGenerator}.
*/
public void writeValue(JsonGenerator g, Object value) throws IOException
{
_assertNotNull("g", g);
_configureGenerator(g);
if (_config.isEnabled(SerializationFeature.CLOSE_CLOSEABLE)
&& (value instanceof Closeable)) {
Closeable toClose = (Closeable) value;
try {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
g.flush();
}
} catch (Exception e) {
ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
return;
}
toClose.close();
} else {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
g.flush();
}
}
}
看到了,使用序列化方式输出写操作
_prefetch.serialize(g, value, _serializerProvider());
每执行一次servlet.service方法序列化输出一次
在看看String返回值
使用org.springframework.http.converter.StringHttpMessageConverter
@Override
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
HttpHeaders headers = outputMessage.getHeaders();
if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
headers.setAcceptCharset(getAcceptedCharsets());
}
Charset charset = getContentTypeCharset(headers.getContentType());
StreamUtils.copy(str, charset, outputMessage.getBody());
}
继续跟踪
/**
* Copy the contents of the given String to the given output OutputStream.
* Leaves the stream open when done.
* @param in the String to copy from
* @param charset the Charset
* @param out the OutputStream to copy to
* @throws IOException in case of I/O errors
*/
public static void copy(String in, Charset charset, OutputStream out) throws IOException {
Assert.notNull(in, "No input String specified");
Assert.notNull(charset, "No charset specified");
Assert.notNull(out, "No OutputStream specified");
Writer writer = new OutputStreamWriter(out, charset);
writer.write(in);
writer.flush();
}
难怪正常
Writer writer = new OutputStreamWriter(out, charset);
每次new,下一次输出会覆盖上一次输出,????
从容错角度,String返回处理正常;但从正确的角度,Jackson的返回时正确的
3. org.springframework.web.filter.OncePerRequestFilter
很多人说org.springframework.web.filter.OncePerRequestFilter可以规避多次dofilter的问题,然而这个只能让多个继承这个抽象类的filter,只执行一个filter,并不是规避多次dofilter的问题,原理
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("OncePerRequestFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else {
//设置属性标记
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
//这里要注意,责任链是链路,执行完成要移除设置的attribute,对业务无感知
//此处对try代码的入栈代码生效,此处出栈移除
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
就是set setAttribute,然后下次判断。attributeName就是filter的名称+后缀
protected String getAlreadyFilteredAttributeName() {
String name = getFilterName();
if (name == null) {
name = getClass().getName();
}
return name + ALREADY_FILTERED_SUFFIX;
}
总结
仅仅是简单的filter,不注意写的有问题,结合springboot,就会有各种原因,非常的诡异,需要分析源码才能根源上解决问题。最怕的是在特定情况出现,比如笔者模拟的异常时多返回值,但String返回正常,这时就需要更多的日志,或者Java请求栈分析了。
最后
以上就是时尚白羊为你收集整理的Spring Boot Filter 双返回值的问题源码分析的全部内容,希望文章能够帮你解决Spring Boot Filter 双返回值的问题源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复