SpringMVC(八):使用Servlet原生API作为Spring MVC hanlder方法的参数

时间:2022-08-04 14:43:00

在SpringMVC开发中,是有场景需要在Handler方法中直接使用ServletAPI。

在Spring MVC Handler的方法中都支持哪些Servlet API作为参数呢?

--Response

* <li>{@link ServletResponse}
* <li>{@link OutputStream}
* <li>{@link Writer}

--Request

* <li>{@link WebRequest}
* <li>{@link ServletRequest}
* <li>{@link MultipartRequest}
* <li>{@link HttpSession}
* <li>{@link PushBuilder} (as of Spring 5.0 on Servlet 4.0)
* <li>{@link Principal}
* <li>{@link InputStream}
* <li>{@link Reader}
* <li>{@link HttpMethod} (as of Spring 4.0)
* <li>{@link Locale}
* <li>{@link TimeZone} (as of Spring 4.0)
* <li>{@link java.time.ZoneId} (as of Spring 4.0 and Java 8)

备注: 不同的SpringMVC版本,可能这里的标准不一致,我这里的SpringMVC版本是5.02。

为什么Spring MVC handler方法中支持上边的ServletAPI呢?

下边先看一个Spring MVC handler方法中使用servlet API作为参数的例子,之后调试来解析为什么。

新建一个handler类:TestServletAPI.java

 package com.dx.springlearn.hanlders;

 import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; @Controller
public class TestServletAPI {
private final String SUCCESS = "success"; @RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request, HttpServletResponse response) {
System.out.println("testServletAPI,request:" + request + ",response:" + response);
return SUCCESS;
}
}

备注:在第15行打上断掉,后边debug调试时需要这么做。

在index.jsp上添加链接:

    <a href="testServletAPI">test ServletAPI</a>

测试打印结果为:

testServletAPI,request:org.apache.catalina.connector.RequestFacade@534683c2,response:org.apache.catalina.connector.ResponseFacade@77dbf19f

断点分析:

SpringMVC(八):使用Servlet原生API作为Spring MVC hanlder方法的参数

下边截图中是handler参数类型解析映射的位置:

SpringMVC(八):使用Servlet原生API作为Spring MVC hanlder方法的参数

下边截图的函数中针对不同的参数解析映射方式不同:

SpringMVC(八):使用Servlet原生API作为Spring MVC hanlder方法的参数

下图可以说明两个问题:

1)除了上图中providedArgs方式处理handler方法参数映射解析外,其他解析器都是实现了HandlerMethodArgumentResolver接口类;

2)其中跟ServletAPI接口类有关的handler方法参数映射解析器包含(该结论是调试时确定,但从图中不能完全确定):

org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver,
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver

SpringMVC(八):使用Servlet原生API作为Spring MVC hanlder方法的参数

通过查看org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver,
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver源代码,就可以明白为什么Spring MVC handler方法只支持那些Servlet API:

org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver.java

package org.springframework.web.servlet.mvc.method.annotation;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import javax.servlet.ServletResponse; import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer; /**
* Resolves response-related method argument values of types:
* <ul>
* <li>{@link ServletResponse}
* <li>{@link OutputStream}
* <li>{@link Writer}
* </ul>
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1
*/
public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (ServletResponse.class.isAssignableFrom(paramType) ||
OutputStream.class.isAssignableFrom(paramType) ||
Writer.class.isAssignableFrom(paramType));
} /**
* Set {@link ModelAndViewContainer#setRequestHandled(boolean)} to
* {@code false} to indicate that the method signature provides access
* to the response. If subsequently the underlying method returns
* {@code null}, the request is considered directly handled.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { if (mavContainer != null) {
mavContainer.setRequestHandled(true);
} Class<?> paramType = parameter.getParameterType(); // ServletResponse, HttpServletResponse
if (ServletResponse.class.isAssignableFrom(paramType)) {
return resolveNativeResponse(webRequest, paramType);
} // ServletResponse required for all further argument types
return resolveArgument(paramType, resolveNativeResponse(webRequest, ServletResponse.class));
} private <T> T resolveNativeResponse(NativeWebRequest webRequest, Class<T> requiredType) {
T nativeResponse = webRequest.getNativeResponse(requiredType);
if (nativeResponse == null) {
throw new IllegalStateException(
"Current response is not of type [" + requiredType.getName() + "]: " + webRequest);
}
return nativeResponse;
} private Object resolveArgument(Class<?> paramType, ServletResponse response) throws IOException {
if (OutputStream.class.isAssignableFrom(paramType)) {
return response.getOutputStream();
}
else if (Writer.class.isAssignableFrom(paramType)) {
return response.getWriter();
} // Should never happen...
throw new UnsupportedOperationException("Unknown parameter type: " + paramType);
} }

org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver.java

package org.springframework.web.servlet.mvc.method.annotation;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.security.Principal;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.PushBuilder; import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.servlet.support.RequestContextUtils; /**
* Resolves request-related method argument values of the following types:
* <ul>
* <li>{@link WebRequest}
* <li>{@link ServletRequest}
* <li>{@link MultipartRequest}
* <li>{@link HttpSession}
* <li>{@link PushBuilder} (as of Spring 5.0 on Servlet 4.0)
* <li>{@link Principal}
* <li>{@link InputStream}
* <li>{@link Reader}
* <li>{@link HttpMethod} (as of Spring 4.0)
* <li>{@link Locale}
* <li>{@link TimeZone} (as of Spring 4.0)
* <li>{@link java.time.ZoneId} (as of Spring 4.0 and Java 8)
* </ul>
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1
*/
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @Nullable
private static Class<?> pushBuilder; static {
try {
pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder",
ServletRequestMethodArgumentResolver.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Servlet 4.0 PushBuilder not found - not supported for injection
pushBuilder = null;
}
} @Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
} @Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); // WebRequest / NativeWebRequest / ServletWebRequest
if (WebRequest.class.isAssignableFrom(paramType)) {
if (!paramType.isInstance(webRequest)) {
throw new IllegalStateException(
"Current request is not of type [" + paramType.getName() + "]: " + webRequest);
}
return webRequest;
} // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
return resolveNativeRequest(webRequest, paramType);
} // HttpServletRequest required for all further argument types
return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
} private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
T nativeRequest = webRequest.getNativeRequest(requiredType);
if (nativeRequest == null) {
throw new IllegalStateException(
"Current request is not of type [" + requiredType.getName() + "]: " + webRequest);
}
return nativeRequest;
} @Nullable
private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException {
if (HttpSession.class.isAssignableFrom(paramType)) {
HttpSession session = request.getSession();
if (session != null && !paramType.isInstance(session)) {
throw new IllegalStateException(
"Current session is not of type [" + paramType.getName() + "]: " + session);
}
return session;
}
else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) {
return PushBuilderDelegate.resolvePushBuilder(request, paramType);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
InputStream inputStream = request.getInputStream();
if (inputStream != null && !paramType.isInstance(inputStream)) {
throw new IllegalStateException(
"Request input stream is not of type [" + paramType.getName() + "]: " + inputStream);
}
return inputStream;
}
else if (Reader.class.isAssignableFrom(paramType)) {
Reader reader = request.getReader();
if (reader != null && !paramType.isInstance(reader)) {
throw new IllegalStateException(
"Request body reader is not of type [" + paramType.getName() + "]: " + reader);
}
return reader;
}
else if (Principal.class.isAssignableFrom(paramType)) {
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
throw new IllegalStateException(
"Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
}
return userPrincipal;
}
else if (HttpMethod.class == paramType) {
return HttpMethod.resolve(request.getMethod());
}
else if (Locale.class == paramType) {
return RequestContextUtils.getLocale(request);
}
else if (TimeZone.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone : TimeZone.getDefault());
}
else if (ZoneId.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault());
} // Should never happen...
throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName());
} /**
* Inner class to avoid a hard dependency on Servlet API 4.0 at runtime.
*/
private static class PushBuilderDelegate { @Nullable
public static Object resolvePushBuilder(HttpServletRequest request, Class<?> paramType) {
PushBuilder pushBuilder = request.newPushBuilder();
if (pushBuilder != null && !paramType.isInstance(pushBuilder)) {
throw new IllegalStateException(
"Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder);
}
return pushBuilder; }
} }