一、框架预览

二、定义与对象
首先必须了解处理器即handler是什么:你在前期可以直接理解为controller方法
-
HttpServlet定义了规范, 但是如果不重写doGet方法的话就会报错
-
FrameworkServlet重写了doGet(),实现了HttpServletBean
-
DispatcherServlet:派发到处理器上
-
HanlderInterceptor,接口3个方法
-
preHandle//请求开始之前 postHandle//请求结束之后,画面渲染之前 afterCompletion//画面渲染之后。有的时候画面不存在,只有返回modelAndView时画面才存在,返回json时没有画面 //jdk8之后该接口有default,可以不重写
-
-
// 抽象类,都是空实现 // JDK8之前需要重写接口所有方法,如果直接使用这个抽象类省略重写方法 public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {//内部是HandlerInterceptor接口 // 为了方便jdk8之前直接使用 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; }
-
HandlerMapping:处理请求到处理器,即映射到java方法的封装
- RequestMappingHandlerMapping:映射器+处理器
-
HandlerMethod:java方法的封装
-
HandlerAdapter:为了重写老式重写Controller接口的类。从HandlerMapping中拿到HandlerMethod后ha去参数解析等操作
-
MethodParameter:对HandlerMethod的参数的封装,比如还有
@PathVariable
-
HandlerMethodArgumentResolver:对上面参数解析,比如请求
/getById/{1}
就可以把id注入到id参数上。他的实现比如是解析@PathVariable
,又比如是解析@RequestParam
的-
// HandlerMethodArgumentResolver接口的方法 //返回true代表是@PathVariable,可以进入resolveArgument boolean supportsParameter(MethodParameter parameter); // Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
-
-
HandlerMethodArgumentResolverComposite:组合模式,持有全部的参数解析器resolver
-
组合模式就是说 list的调用方法和一个元素怒的调用方法是一样的
-
class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>(); private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256); public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) { this.argumentResolvers.add(resolver); return this; } // 重点 // 是否支持解析该参数 public boolean supportsParameter(MethodParameter parameter) { return getArgumentResolver(parameter) != null; // 不为空 } // 重点 // 解析参数 public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { 报错无法解析参数类型 parameter.getParameterType() } return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { // 缓存 HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); // 缓存为空,遍历list if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; // 添加到缓存 this.argumentResolverCache.put(parameter, result); break; } } } return result; }
-
-
RequestResponseBodyMethodProcessor:实现了HandlerMethodArgumentResolver。处理两种body请求
@RequestBody
、@ResponseBody
-
public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class);//@RequestBody } public boolean supportsReturnType(MethodParameter returnType) { //@ResponseBody方法上或者类上有没有注解 return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); } // java对象转json public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest){ 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); }
-
private方法也能被请求到
HttpServlet
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);//405
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);//400
}
}
FramewordServlet
重写了doGet
public abstract class FrameworkServlet
extends HttpServletBean
implements ApplicationContextAware {
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
// get和post都会跳转到这里 // 这个方法是final,而doService需要被重写,这是模板方法模式
protected final void processRequest(HttpServletRequest request,
HttpServletResponse response) {
try {
doService(request, response);//要被子类DispatcherServlet重写
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
首先,Tomcat每次启动时都会加载并解析/WEB-INF/web.xml文件,所以可以先从web.xml找突破口,主要代码如下:
<servlet >
<servlet-name >spring-mvc</servlet-name>
<!-- servlet类 -->
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 初始化参数 -->
<init-param >
<param-name >contextConfigLocation</param-name>
<param-value >classpath:/spring-mvc.xml</param-value>
</init-param>
<!-- 启动时加载 -->
<load-on-startup >1</load-on-startup>
</servlet>
<servlet-mapping >
<servlet-name >spring-mvc</servlet-name>
<url-pattern >/</url-pattern>
</servlet-mapping>
我们可以从web.xml文件获得三个信息,分别是:servlet类为DispatcherServlet,它在启动时加载,加载时初始化参数contextConfigLocation为classpath下spring-mvc.xml的文件地址,接下来我们将目光移到DispatcherServlet类。
DispatcherServlet
很明显,它是一个Servlet的子类,其实不用说也知道,因为web.xml早已有配置。既然是Servlet,我们就要专注于它的service、doGet、doPost等相关方法,在它的父类FrameServlet,我们找到了service方法。
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// ====DispatcherServlet.doDispatch
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) {
String method = request.getMethod();
if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
processRequest(request, response);
}
else {
// 调用父类的service方法
super.service(request, response);
}
}
根据service方法,我们一步步找到一个方法链service –> processRequest –> doService –> doDispatch,我们最终将目光定位在doDispatch,因为从它的方法体就可以看出它是整个SpringMVC的核心方法。
三、流程预览
- FrameworkdServlet.doGet()–>
- FrameworkdServlet.processRequest()
- DispatcherServlet.doService(request, response);
- DispatcherServlet.doDispatch()
dis流程
①getHandler(processedRequest)
②getHandlerAdapter
③mappedHandler.applyPreHandle(
④mv = ha.handle
⑤mappedHandler.applyPostHandle
⑥processDispatchResult
四、DispatcherServlet.doDispatch
获取controller流程
- 扫描整个项目,spring已经做了,定义了一个集合
- 拿到所有加了 @Controller注解的类
- 遍历类里面所有的方法对象
- 判断方法是否加了@RequestMapping注解
- @RequestMapping注解的value作为map的key。put(value,method对象)
- 根据用户发送的请求 拿到请求中的URI,如test.do
- 使用请求的uri作为map的key,去map中get,看看是否有返回值
handlerMappings是比如RequestMapping的,welcome的
每个handlerMapping有一个mappingRegistry,里面有个registry-map,key为如{POST/user}、{PUT/user}等,value为对应的MappingRegistration
doDispatch
// DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 原生请求,coyote请求,跟tomcat有关
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;
// 哪个handler能处理当前请求
// 解析请求,获取HandlerExecutionChain对象。即获取controller。类#方法()
mappedHandler = getHandler(processedRequest);//processedRequest是对request的封装
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 知道了handler还不够,还要反射调用方法,还要设置参数,把这些操作封装到了HandlerAdapter。用ha.handler()
// 从HandlerExecutionChain对象获取HandlerAdapter对象,实际上是从HandlerMapping对象中获取
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;
}
}
// 在controller方法执行前,调用拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) { // 返回false的话直接返回
return;
}
try {
// 真正执行HandlerAdapter对象的handler方法,返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
// 在controller方法执行后,执行【拦截器】的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// 进行视图解析
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
说它是核心一点也不为过,从上述代码的中文注释可以看出,它包含了解析请求,执行相关拦截器,执行handle方法(到这里关于handle方法是什么,我们一脸懵逼。别急,接下来我们会讲述,总之它很重要就对了),执行视图解析方法。
五、ds.getHandler
①ds.getHandler:从handlerMapping过去methodHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历映射器。一直找,找到一个符合我们请求的映射器。从映射器中拿到处理器,即要拿到java方法,
// handlerMappings的类型是list,分别是
/*
RequestMappingHandlerMapping,//处理注解@RequestMapping,映射到handler。mapping.mappingRegistry.registry.mappingLookup()就能看到map,kv对映射RequestMappingInfo到HandlerMethod
BeanNameUrlHandlerMapping,
SimpleUrlHandlerMapping,
WelcomePageHandlerMapping
*/
for (HandlerMapping mapping : this.handlerMappings) {
// 调用映射器的getHandler //RequestMappingHandlerMapping // @RequestMapping
HandlerExecutionChain handler = mapping.getHandler(request); // @RequestMapping注解的
if (handler != null) {
return handler;
}
}
}
return null;
}
他会依次调用现有的handlerMappings集合,看看谁能处理当前的请求,这就是调用他们的getHandler()方法,要是处理不了他们就返回null了
RequestMapping中有个属性mappingRegistry,类型如下AbstractHandlerMethodMapping
private final MappingRegistry mappingRegistry = new MappingRegistry();//AbstractHandlerMethodMapping
mappingRegistry中有个属性mappingLookUp,是个map。第一个的key是"{GET/user/info/{firstName}}"->"...getUser()"
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
生成了handler之后,里面的属性就有HandlerMethod,interceptorList,
HandlerMethod的属性如
- bean(controller的类型),
- beanFactory,
- bridgeMethod(匹配到的getUser方法)
拿到的handler就是mappedHandler,他的类型是HandlerExecutionChain
AbstractHandlerMapping.getHandler
public abstract class AbstractHandlerMapping
extends WebApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {
// 查找hm中调用的方法
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
- AbstractHandlerMethodMapping:处理注解的controller
- AbstractUrlHandlerMapping:处理基础的controller
- 都继承了AbstractHandlerMapping
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
这两个类都重写了getHandlerInternal方法
Mapping.getHandlerInternal
获取HandlerMethod
// 注解的
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
// 里面先根据请求拿到list,先添加到匹配集合,然后找到最佳匹配。get (0)
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
// 查找处理器链 // 他map的key是bean名字
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 获得请求的uri
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
// 类的方式返回的是controller类对象,注解方式返回的是HandlerMethod对象。
Object handler = lookupHandler(lookupPath, request);
/*
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// 这个map的key是请求uri,valye是Controller对象
Object handler = this.handlerMap.get(urlPath);
*/
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
通过handlerMapping拿到了handlerMethod,别的hm可能拿到的是别的类型
解析参数前有pre
六、ha适配器包装参数和返回值解析器
②ds.getHandlerAdapter
回到dispatch
不同的控制器实现方式需要用不同的方式去调用
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
// handlerAdapters有3种:
/* 适配器有3个
RequestMappingHandlerAdaptor, // @RequestMapping
HttpRequestHandlerAdaptor, // 实现HttpRequestHandler的,这里不考虑
SimpleControllerHandlerAdaptor // 实现Controller接口的
// springboot里还有个HandlerrFunctionAdaptor // 支持函数向编程
为什么3种适配器2种映射器?23在前面合并了
*/
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {//先调用supports
return adapter;//ha
}
}
}
throw new ServletException("没有能解析该handler的adapter。DispatcherServlet配置需要包含这个handler的ha");
}
public abstract class AbstractHandlerMethodAdapter
extends WebContentGenerator implements HandlerAdapter, Ordered {
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && // 是HandlerMethod类型,@Controller的都是封装为HandlerMethod
supportsInternal((HandlerMethod) handler));
}
已经拿到了RequestMappingHandlerAdaptor
这个ha
拿ha是用support
④ha.handle
已经拿到了方法,但是方法参数还没有,把这个过程交给ha
1 ha接口
返回ModelAndView
/** MVC框架的SPI,允许MVC处理参数
Interface that must be implemented for each handler type to handle a request.
这个接口用于允许DispatcherServlet无线扩展
DispatcherServlet通过本类判断所有安装的handlers。
handler可以是Object类型。这使其他框架的handlers融合到框架里。
HandlerAdapter的实现类也可能实现了Ordered接口,DispatcherServlet就可以按序获取
*/
public interface HandlerAdapter {
/** 当前ha是否能解析该handler。。。HandlerAdapters一般只支持一个handler类型 */
boolean supports(Object handler);
/** 本ha是否能处理 使用给定的handlers处理本请求。通过了supports()后经过这个方法
返回一个ModelAndView,名字为视图名和必要的model数据,如果request没被正确处理,返回null */
@Nullable
ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;// 要使用的handler,必须依据经过了supports()
/** 如果没有支持的handler类,返回-1
* Same contract as for HttpServlet's {@code getLastModified} method.
* @return the lastModified value for the given handler
* @see HttpServlet#getLastModified
* @see LastModified#getLastModified
*/
long getLastModified(HttpServletRequest request, Object handler);
}
调用supports()分析适配器可用后,我们就调用它的handle()方法
2 ha实现类
- RequestMappingHandlerAdaptor, // @RequestMapping
- HttpRequestHandlerAdaptor, // 实现HttpRequestHandler的,这里不考虑
- SimpleControllerHandlerAdaptor // 实现Controller接口的
//SimpleControllerHandlerAdaptor //继承了Controller接口的自定义controller就是实现了该方法
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler){
return ((Controller) handler).handleRequest(request, response);
}
//AbstractHandlerMethodAdapter 注解方式的
public final ModelAndView handle(HttpServletRequest request,
HttpServletResponse response,
Object handler){
return handleInternal(request, response, (HandlerMethod) handler);
}
3 ha.handleInternal()
RequestMappingHandlerAdapter.handleInternal()
// 处理@RequestMapping注解的适配器
public class RequestMappingHandlerAdapter
extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}else { // 没有session
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No synchronization on session demanded at all...
// 执行目标方法,返回modelAndView
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
4 ha.invokeHandlerMethod()流程
RequestMappingHandlerAdapter.invokeHandlerMethod
- 包装ServletInvocableHandlerMethod
- invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);给目标方法里设置参数解析器
- invocableMethod.setHandlerMethodReturnValueHandlers给目标方法里设置返回值解析器
- invocableMethod.invokeAndHandle(webRequest, mavContainer);
- getModelAndView 获得视图
// RequestMappingHandlerAdapter
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 参数解析器,拿到全部
if (this.argumentResolvers != null) {
// 给目标方法里设置参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 给目标方法里设置返回值解析器,设置全部
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 创建ModelAndViewContainer,包含了请求中的视图和模型
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 去处理结果值【包括视图名、转发的map】,放入mavContainer
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 获得视图
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
4.1 createInvocableHandlerMethod
4.2 iM.setHandlerMethodArgumentResolvers(this.argumentResolvers);
给目标方法里设置参数解析器invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
有很多参数解析器,RequestParamMapMethodArgumentResolver
参数解析器接口:
/** 解析方法的参数 */
public interface HandlerMethodArgumentResolver {
/** 本参数解析器是否支持解析该 MethodParameter*/
boolean supportsParameter(MethodParameter parameter);
/** 用本参数解析器从请求中解析出来参数
ModelAndViewContainer给请求提供了访问model的通道
WebDataBinderFactory提供了创建WebDataBinder实例的方法,用于数据绑定和类型转换 */
@Nullable // 返回值为解析好的参数
Object resolveArgument(MethodParameter parameter, // 要解析的方法参数,要先经过supportsParameter()
@Nullable ModelAndViewContainer mavContainer, // 当前请求的ModelAndViewContainer
NativeWebRequest webRequest, // 当前请求的webRequest
@Nullable WebDataBinderFactory binderFactory) ;// 创建WebDataBinder实例的工厂
}
参数解析器和返回值解析器都放到了invocableMethod中
参数解析器实现类
解析参数
找到参数解析器后会缓存,方便下次获取
参数解析器被HandlerMethodArgumentResolverComposite持有着,他负责分派。
获得参数解析器
每个参数被封装为MethodParameter
参数解析器派发Composite
public class HandlerMethodArgumentResolverComposite
implements HandlerMethodArgumentResolver {
// 看是否支持解析该方法
public boolean supportsParameter(MethodParameter parameter) {
// for遍历现有实现类,调用supportsParameter
return getArgumentResolver(parameter) != null;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
// 缓存中获取参数解析器
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
// 缓存中没有
if (result == null) {
// 遍历参数解析器,看是否有注解
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
// 放入缓存
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
4.3 iM.setHandlerMethodReturnValueHandlers
给目标方法里设置返回值解析器invocableMethod.setHandlerMethodReturnValueHandlers
4.4 iM.invokeAndHandle(webRequest, mavContainer);
- invocableMethod
ServletInvocableHandlerMethod.invokeAndHandle
public class ServletInvocableHandlerMethod
extends InvocableHandlerMethod {
// 开始执行方法
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, // 里面存放model等信息
Object... providedArgs) throws Exception {
// 里面会调用我们写的controller方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// 设置响应状态
setResponseStatus(webRequest);
// 如果是空对象,直接返回
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}else if (StringUtils.hasText(getResponseStatusReason())) { // 返回值里有没有一些失败原因
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
// 处理返回结果handleReturnValue()
this.returnValueHandlers.handleReturnValue(
returnValue,
getReturnValueType(returnValue), // 返回值类型
mavContainer, webRequest);
}
4.4.1 invokeForRequest解析参数
// InvocableHandlerMethod获得参数
public Object invokeForRequest(NativeWebRequest request,
@Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取方法的参数所有值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// 利用反射调用目标方法
return doInvoke(args);
}
4.4.1.1 getMethodArgumentValues参数解析
序号太长了,该用 1)
(1) getMethodArgumentValues参数解析
遍历参数解析器,解析出来当前参数
public class InvocableHandlerMethod
extends HandlerMethod {
protected Object[] getMethodArgumentValues(NativeWebRequest request,
@Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) {
// 获取方法参数,里面有注解信息
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; }// 无参方法,直接返回
// new Object[]数组存放解析好的参数
Object[] args = new Object[parameters.length];
// 一次解析每个参数
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 是否有参数解析器支持解析当前参数(被封装为MethodParameter)
// 里面肯定是遍历解析器.supportsParameter()。再里面判断参数有没有指定的注解
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
// 有对应的参数解析器
// 调用参数解析器的resolveArgument方法
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}
(1.1) 派发解析
我们知道要用到参数解析器的派发器HandlerMethodArgumentResolverComposite处理
要经过support、resolveArgument操作,原理就是哪个每个之后遍历
(1.1.1)各种解析器解析流程
接口HandlerMethodArgumentResolver有两个方法,这个接口有很多实现类
-
boolean supportsParameter(MethodParameter parameter);
-
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
(1.1.2)解析参数resolveArgument
// HandlerMethodArgumentResolverComposite
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception {
// 获取单个参数的解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {抛异常,没找到参数的参数解析器}
// 转发,分派
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
public abstract class AbstractNamedValueMethodArgumentResolver
implements HandlerMethodArgumentResolver {
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception {
// 参数的名字,如username
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
// 解析参数名
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) { 抛异常,name不准为null }
// 确定参数的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) { // 默认值
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
。。。一些异常处理;
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
@Nullable // 仅仅解析参数的名字
private Object resolveStringValue(String value) {
if (this.configurableBeanFactory == null) {
return value;
}
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
// 解析器
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null || this.expressionContext == null) {
return value;
}
// 用解析器进行计算
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
}
(1.1.3)解析string参数
// PathVariable的
protected Object resolveName(String name,
MethodParameter parameter, // 参数信息封装
NativeWebRequest request) throws Exception {
//
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
// Header的
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
String[] headerValues = request.getHeaderValues(name);//调用原生的API
if (headerValues != null) {
return (headerValues.length == 1 ? headerValues[0] : headerValues);//只会返回一个
}
else { return null; }
}
// Param的
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null) {
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}
Object arg = null;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
(1.1.4)复杂类型参数解析
ServletModelAttributeMethodProcessor解析POJO类型参数
- 先创建一个空的person对象
- 创建一个webRequest和attribute【空person对象】、name对应的binder。数据绑定器
- 绑定器里有
- targer:person对象
- conversionService:转换器。把文本转为int等
// RequestFacade ResponseFacade
//================================================
// ServletModelAttributeMethodProcessor 这个处理复杂类型参数。在该父类ModelAttributeMethodProcessor中共有
// public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
public class ModelAttributeMethodProcessor
implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) || // 有注解@ModelAttribute
(this.annotationNotRequired && // 注解不是必须的
!BeanUtils.isSimpleProperty(parameter.getParameterType()))); // 非简单属性 POJO
// 判断是非简单类型,不是数组// 简单类型指的是枚举、字符串、number这些
}
public final Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, // 不能为空
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) { // 不能为空
// 获取参数名
String name = ModelFactory.getNameForParameter(parameter);
// 获得@ModelAttribute注解
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) { // 如果以前有person对象
attribute = mavContainer.getModel().get(name);
}
else {
try {
// 创建一个参数对应的空对象,如person对象,顺序为空。
attribute = createAttribute(name, parameter, binderFactory, webRequest);
} catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// 创建一个binder,绑定请求和空对象。binder对象的类型是ExtendedServletRequestDataBinder,
// binder属性有target是空对象,conversionService是转换器converter
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, // 请求
attribute, // 创建的空person对象
name);
// 如果绑定器的绑定对象不为空,即有person对象
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
// 绑定属性 // 先获得原生request,然后利用反射绑定bingder.bind(req) // 先获得请求中的kv对,然后遍历
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
// 赋值好的对象
return attribute;
}
如何创建参数对于的空对象
/**
* Extension point to create the model attribute if not found in the model,
* with subsequent parameter binding through bean properties (unless suppressed).
* <p>The default implementation typically uses the unique public no-arg constructor
* if available but also handles a "primary constructor" approach for data classes:
* It understands the JavaBeans {@link ConstructorProperties} annotation as well as
* runtime-retained parameter names in the bytecode, associating request parameters
* with constructor arguments by name. If no such constructor is found, the default
* constructor will be used (even if not public), assuming subsequent bean property
* bindings through setter methods.
* @param attributeName the name of the attribute (never {@code null})
* @param parameter the method parameter declaration
* @param binderFactory for creating WebDataBinder instance
* @return the created model attribute (never {@code null})
* @throws BindException in case of constructor argument binding failure
* @throws Exception in case of constructor invocation failure
* @see #constructAttribute(Constructor, String, MethodParameter, WebDataBinderFactory, NativeWebRequest)
* @see BeanUtils#findPrimaryConstructor(Class)
*/
protected Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
MethodParameter nestedParameter = parameter.nestedIfOptional();
Class<?> clazz = nestedParameter.getNestedParameterType();
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(clazz);
if (ctor == null) {
Constructor<?>[] ctors = clazz.getConstructors();
if (ctors.length == 1) {
ctor = ctors[0];
}
else {
try {
ctor = clazz.getDeclaredConstructor();
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("No primary or default constructor found for " + clazz, ex);
}
}
}
Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);
if (parameter != nestedParameter) {
attribute = Optional.of(attribute);
}
return attribute;
}
(1.1.5) 绑定器与转换器
绑定器:绑定器类型是ExtendedServletRequestDataBinder
。有属性target
、conversionService
【里面包含转换器】
转换器在数据绑定器中设置着
将请求参数的值绑定到指定的javaBean里


先创建绑定器
public class ClassPathBeanDefinitionScanner
extends ClassPathScanningCandidateComponentProvider {
public final WebDataBinder createBinder(
NativeWebRequest webRequest, // 请求
@Nullable Object target, //空对象
String objectName) throws Exception {
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (this.initializer != null) {
this.initializer.initBinder(dataBinder, webRequest);
}
initBinder(dataBinder, webRequest);
// 返回绑定器
return dataBinder;
}
1.1.1 bindRequestParameters绑定属性
进行属性绑定,绑定到person对象属性上
public class ServletModelAttributeMethodProcessor
extends ModelAttributeMethodProcessor {
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
// 获取原生请求
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
// 转换一下binder类型
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
// 绑定
servletBinder.bind(servletRequest);
}
public class ServletRequestDataBinder extends WebDataBinder {
public void bind(ServletRequest request) {
// 获取原生请求里的kv对
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
// 添加遍历的值
addBindValues(mpvs, request);
// 进行绑定
doBind(mpvs);
}
}
public class WebDataBinder extends DataBinder {
@Override
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
super.doBind(mpvs);
}
// DataBinder.java
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
// 绑定
applyPropertyValues(mpvs);
}
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// 绑定请求参数到目标对象
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// 用bind error processor创建FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
// 遍历kv对
for (PropertyValue pv : propertyValues) {
try {
// This method may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
setPropertyValue(pv);
}
}
...;
}
遍历kv对,利用反射复制到person对象
public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
public void setPropertyValue(PropertyValue pv) throws BeansException {
PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
if (tokens == null) {
String propertyName = pv.getName();
AbstractNestablePropertyAccessor nestedPa;
try {
// 利用反射进行赋值。
//先拿到属性访问器PropertyAccessor,先要访问age属性。
// 属性访问器的类型是BeanWrapperImpl,是target的包装,
nestedPa = getPropertyAccessorForPropertyPath(propertyName);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Nested property in path '" + propertyName + "不存在");
}
tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
if (nestedPa == this) {
pv.getOriginalPropertyValue().resolvedTokens = tokens;
}
// 用warrper进行set
nestedPa.setPropertyValue(tokens, pv);
}
else {
setPropertyValue(tokens, pv);
}
}
setPropertyValue里面拿到originalValue,比如"18",又名为valueToApply
然后调用convertForProperty()
转换属性类型。converIfNecessary()又是一个converterDelegate,他的基准是源类型和目的类型
拿到所有的转换器,判断conversionService.canConvert(sourceTypeDesc,typeDescriptor)
然后把valueToApply从"18"转为18
1.1.2 自定义 Converter
<input name="a" value="1,2,3">
string转A
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean // 不要加@Configuration
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override // 添加转换器
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override /// string转pet
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
model的参数解析
先看处理map的,再看model的
public class MapMethodProcessor
implements HandlerMethodArgumentResolver, // 是参数解析器
HandlerMethodReturnValueHandler { // 同时又是返回值处理器
@Override // MapMethodProcessor的
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType()) && // 是否也指派
parameter.getParameterAnnotations().length == 0; //
}
// MapMethodProcessor。map的
public Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
// mavContainer里面有个defaultModel属性=new BindingAwareModelMap(),继承了ModelMap。这是args数组里防止的是该map
// 每个mavContainer有一个BindingAwareModelMap属性,
return mavContainer.getModel();
}
// ModelMethodProcessor,和上面调用的是一样的方法。如果参数中有多个的话,我们会发现他们是同一个BindingAwareModelMap对象
public Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
上面我们知道了无论是参数map还是model,注入的都是同一个BindingAwareModelMap,他在mavcontainer中,那么如何放到转发后请求域中呢?去结果处理handleReturnValue(mavcontainer)那里看,那时候已经调用完了我们的controller,然后去处理他的返回值【】。我们在invokeAndHandle()里看,mvcContainer里已经有了model里设置的值,原来请求域中的是不转发的。然后去看返回结果吧
如果返回字符串,先设置到mavcontainer中viewName
getModelAndView里modelFactory.update(webrequest,mavcontainer)处理mavcontainer所有数据都在里面,包含要去页面和数据
getDefaultModel,updateBindingResult,拿到所有model的key。会绑定。把里面的数据再封装为new ModelAndView(model)
会判断是否是重定向数据,如果是重定向,再放到请求上下文里
至于转发,再processDispatchResult里,把model数据放到请求与中,在render里,解析试图前拿到model,,view.render里,createMergedOutputModel (request,res,model)先转移到hashmap里,在出来prepareResponse后,renderMergeOutputModel (mergedModel,request,res)。
exposeModelAsRequestAttributes(model,request)遍历设置
视图转发的时候,要把下一次请求的参数放到model中
(2)反射执行方法
// 都已经拿到正确的参数了,就是一个反射
protected Object doInvoke(Object... args) {
ReflectionUtils.makeAccessible(getBridgedMethod());
// getBridgedMethod拿到的是类.方法。然后用反射
return getBridgedMethod().invoke(getBean(), args);//getBean() {return this.bean;}返回的是controller对象
。。。一些异常处理;
}
4.4.2 处理返回结果handleReturnValue
返回值处理器
执行完方法后,设置完响应状态,然后处理返回值,即经过视图解析器等
内容协商
我们接下来看如何把
person
对象写成json
这个逻辑是在返回值处理器中处理的:HandlerMethodReturnValueHandlerComposite
返回值处理器如
RequestResponseBodyMethodProcessor
支持POJO
转json
,即返回值标准了@ResponseBody
注解的
this.returnValueHandlers.handleReturnValue(
returnValue,
getReturnValueType(returnValue), //执行方法的返回值。获取返回的结果类型
mavContainer, //里面有mav值,视图转发过来的
webRequest);
1 结果值处理器接口
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
/** 处理给定的返回值,添加属性到model中,设置视图, */
void handleReturnValue(@Nullable Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception;
}
2 返回值处理器派发器Composite
public class HandlerMethodReturnValueHandlerComposite
implements HandlerMethodReturnValueHandler {
// 集合
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
public void handleReturnValue(@Nullable Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("未知的返回值类型returnType.getParameterType().getName()");
}
// HandlerMethodReturnValueHandler
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
有哪些返回值处理器:
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

1) @ResponseBody返回值处理
先看对于返回json时如何进行返回值处理器:
- 先找到可以处理该返回值类型的 返回值处理器
ReturnValueHandler
,对于person(@ResponseBody注解
)找到了RequestResponseBodyMethodProcessor - 去返回体处理器中先进行内容协商,再进行消息转换MessageConverter
- 协商的结果和消息转换的结果叫媒体类型
- 内容协商的结果是application/json
- 消息转换器的结果是person对象转成json
- 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
- 2、返回值处理器调用 handleReturnValue 进行处理
@ResponseBody的注解的时候,发现返回值处理器交给了消息转换器,利用消息处理器 将数据写为json
1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
3、SpringMVC会挨个遍历所有容器底层的
HttpMessageConverter
,看谁能处理该媒体类型?
- 1、得到
MappingJackson2HttpMessageConverter
可以将对象
写为json
- 2、利用
MappingJackson2HttpMessageConverter
将对象
转为json
再写出去。
看看RequestResponseBodyMethodProcessor这个返回值处理器,他主要的是调用了writeWithMessageConverters
,这个方法进行了内容协商和消息转换
public class RequestResponseBodyMethodProcessor
extends AbstractMessageConverterMethodProcessor {// 他同时也是参数解析器
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||// 返回值的类,有注解@ResponseBody
returnType.hasMethodAnnotation(ResponseBody.class)); // 方法上有注解@ResponseBody
}
@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.
// 使用【消息转换器】进行写出操作,把对象转为json
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
1.1) writeWithMessageConverters
消息转换器里包含了内容协商,比如协商的结果是json
- 拿到浏览器请求头里的accept内容,进行内容协商
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
/* 把返回值类型 写成 输出消息 */
@SuppressWarnings({"rawtypes", "unchecked"})
protected <T> void writeWithMessageConverters(@Nullable T value, //person对象
MethodParameter returnType, // Person类型
ServletServerHttpRequest inputMessage, //输入消息inspect请求头里的Accept
ServletServerHttpResponse outputMessage){ // 写出的输出消息对象
Object body;
Class<?> valueType;
Type targetType;
// 值是否是字符串类型
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else { // 非字符串
body = value;
// 值的类型,如Person
valueType = getReturnValueType(body, returnType);
// 目标类型,也是Person
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
// 是否是资源类型,如inputStream
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
// 拿到请求和响应的媒体类型
// 媒体类型,牵扯到内容协商。内容协商是说浏览器要什么内容。请求头里已经写好了
// 比如Accept:text/html,application/xml
MediaType selectedMediaType = null;
// 看看响应头里有没有媒体类型ContentType。看看又被人处理过,比如拦截器已经写死了
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
// 媒体类型协商
// 看看有没有被确定过,比如前面处理了一半,那可能已经有响应类型了
if (isContentTypePreset) {
selectedMediaType = contentType;
} // 没被确定过媒体类型
else {
// 拿到原生request
HttpServletRequest request = inputMessage.getServletRequest();
// 获取客户端能接收的类型
/*
他是调用AbstractMessageConverterMethodProcessor类的方法
函数体为return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
ContentNegotiationManager类的解析媒体类型
public List<MediaType> resolveMediaTypes(NativeWebRequest request) {
// 遍历
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);//拿到请求头数据ACCEPT字段
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
}
*/
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 获取服务端生成的类型(针对于返回值类型的,如person)
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) { 找不到valueType对象的消息转换器 }
List<MediaType> mediaTypesToUse = new ArrayList<>();
// 已经有了浏览器和服务器各自的支持媒体类型。去协商一个最佳匹配的
// 内容协商,照顾浏览器的优先级,但也得服务器有
for (MediaType requestedType : acceptableTypes) {//浏览器能接收的
for (MediaType producibleType : producibleTypes) {//服务器能产的
// O(n^2)遍历
if (requestedType.isCompatibleWith(producibleType)) {
// 匹配搭配的媒体类型
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
// 没找到服务端和客户端匹配的媒体类型
if (mediaTypesToUse.isEmpty()) {
if (body != null) { // 有body,但是没有匹配的媒体类型,报错。
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
// 可能匹配到了多个,去选个最合适的
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
打印出双方的类型acceptableTypes、producibleTypes,然后打印最后选择的媒体类型selectedMediaType;
}
// 匹配的媒体类型,如 application/json。去进行消息类型转换
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// 看哪个消息转换器能处理该媒体类型
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = null;
if (converter instanceof GenericHttpMessageConverter) {
// 强转一下消息转换器
genericConverter = (GenericHttpMessageConverter<?>) converter;
}
// 消息转换器是否为空
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {//if体开始
//要转的数据,person对象
body = getAdvice().beforeBodyWrite(body, returnType,
selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
// 调用write()
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {打印null body }
return;
}//if结束
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
1.1.1) 内容协商
获取服务端能产的类型:
public abstract class AbstractMessageConverterMethodProcessor
extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
// 遍历所有消息转换器
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
// 如果哪种可以支持targetType。我们会发现json2Http这个消息转换器支持
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
// 放到支持的媒体集合里,可能有多个支持,这样我们才能拿这些去和浏览器要求的协商
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
json的内容协商
public abstract class AbstractJackson2HttpMessageConverter
extends AbstractGenericHttpMessageConverter<Object> {
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
if (!canWrite(mediaType)) {
return false;
}
if (mediaType != null && mediaType.getCharset() != null) {
Charset charset = mediaType.getCharset();
if (!ENCODINGS.containsKey(charset.name())) {
return false;
}
}
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// objectMapper是jackson底层的
if (this.objectMapper.canSerialize(clazz, causeRef)) {
return true;
}
logWarningIfNecessary(clazz, causeRef.get());
return false;
}
}
public abstract class AbstractHttpMessageConverter<T>
implements HttpMessageConverter<T> {
protected boolean canWrite(@Nullable MediaType mediaType) {
if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {// 如application/json application/*+json
// 浏览器能接收的和服务器能处理的,协商
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

1.1.2) 消息转换
1.1.2.1)、MessageConverter接口
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
/** 消息转换器接口,把HTTP请求和响应互相转换。T是媒体类型,如application/json */
public interface HttpMessageConverter<T> {
/* 能否以person以json方式读进来
判断是否能用消息转换器把class读进来。 */
boolean canRead(Class<?> clazz, //person对象
@Nullable MediaType mediaType);// 媒体类型
/** 能否写 ,能否以json方式写出去
给定的class是否可以用这个消息转换器转成对应的媒体类型写出去。
媒体类型一般是请求头里的Accept */
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/** 当前转换器支持的媒体类型 */
List<MediaType> getSupportedMediaTypes();
/* 从输入消息中读到指定class类型的object */
T read(Class<? extends T> clazz, // 转换后的类型
HttpInputMessage inputMessage);//HTTP里的信息
/* 把t对象以媒体类型写出去 */
void write(T t,// 要写的对象
@Nullable MediaType contentType, // 使用的媒体类型
HttpOutputMessage outputMessage);// 写后的消息
}
1.1.2.2) 常见 消息转换器
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的。
写
匹配好媒体类型后,然后找到能处理这种媒体类型的消息转换器,然后还是写
@Override // AbstractHttpMessageConverter
public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) {
// 响应头,如application/xml
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
//
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
@Override // AbstractJackson2HttpMessageConverter json的消息转换器
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
// objectMapper转json
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
// 写
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
// 通信的flush
generator.flush();
// 一些类型定义、转换json错误的异常捕获。。。
}
outputMessage
servletResponse
response
outputBuffer
1.1.3) xml消息转换案例
根据客户端接收能力不同,返回不同媒体类型的数据。
比如浏览器发送的返回json,安卓发送的返回xml
遍历消息转换器,找到能处理媒体数据的消息转换器
引入xml的pom 转换依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency
支持xml的依赖。类似于支持json的
用postman就可以写Accept的类型为json和xml,Http协议中规定的,告诉服务器本客户端可以接收的数据类型。而且有优先级
如application/json
、 applicatin/xml
4、xml内容协商原理
-
1、判断当前响应头中是否已经有确定的媒体类型。MediaType
-
2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
-
- contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
- HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
-
3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
-
4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
-
5、客户端需要【application/xml】。服务端能力【10种、json、xml】
-
-
6、进行内容协商的最佳匹配媒体类型
-
7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
导入了jackson处理xml的包,xml的converter就会自动进来
// WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
2) 视图名返回值处理器ViewNameMethodReturnValueHandler
执行完了目标方法,得到了modelAndView了,或者说得到了return的值了,那么接下来如何解析视图,是利用的返回值处理器(跟json的执行时间点差不多)
匹配到了ViewNameMethodReturnValueHandler这个返回值处理器可以处理,
可以里的的判断标准是前面的那些返回值处理器处理不了(比如json的那个返回值处理器因为没有@ResponseBody注解所以匹配不了),并且当前处理器可以处理null和字符串
那就去调用它的handler方法
处理返回值就是把该值设置到view中
public class ViewNameMethodReturnValueHandler
implements HandlerMethodReturnValueHandler {
// 处理视图的
public void handleReturnValue(@Nullable Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
// 如果是字符串
if (returnValue instanceof CharSequence) {
// 拿到字符串
String viewName = returnValue.toString();
// 设置视图地址到mavContainer,还没处理
// 目标方法处理过程中的数据也已经被放到了mavContainer中
mavContainer.setViewName(viewName);
// 判断这个字符串前缀是否"redirect:"
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
} else if (returnValue != null) {
抛异常("返回值为空,异常不该发生returnType.getParameterType().getName() in : returnType.getMethod());
}
}
// 然后去处理视图
把视图名设置到了mavContainer
我们发现比如我们在map里转发的数据在上面并没有处理,而是只是设置了个视图名,如"forward:/success"
然后我们再回到ha中:RequestMappingHandlerAdapter.invokeHandlerMethod
里面有一个getModelAndView
// RequestMappingHandlerAdapter
@Nullable 删去了很多内容;
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
// 给目标方法里设置参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
// 设置返回值解析器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
// 去处理结果值【包括视图名、转发的map】,放入mavContainer
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// 获得视图
return getModelAndView(mavContainer, modelFactory, webRequest);
finally {//不管发不发生异常,都会执行,这里不是拦截器的完成方法
webRequest.requestCompleted();
}
}
4.5 getModelAndView 获得视图
上面已经经历了:查找适配器、解析参数、执行方法、解析结果值。都被包装在了mavContainer中。
下面开始解析视图,或者转发
正常处理、转发和重定向都是返回ModelAndView
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,//里面有了model和字符串
ModelFactory modelFactory,
NativeWebRequest webRequest) throws Exception {
// 里面updateBindResult(),拿到所有kv,进行绑定策略
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
// 获得请求中的数据 //在转发中获取的是defaultModel// 在重定向中,获取的是redirectModel,所以没有获取到值
ModelMap model = mavContainer.getModel();
// new视图,同时设置了defaultModel/redirectModel 场景不同使用的不同
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
// 如果是重定向,从这里看出我们可以用RedirectAttributes这个model进行传输重定向数据
if (model instanceof RedirectAttributes) {
// 拿到model数据
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
// 再放到重定向的请求域中。这说明重定向在model里可以设置参数,起作用
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
// 返回modelAndView // 重定向也是返回了一个modelAndView
return mav;
}
重定向也是返回了一个ModelAndView
重定向model问题
注意刚才handler里的pojo对象,也会放到
mavContainer.defaultModel
中,但是没放到redirectModel
中那么在重定向的场景中,在controller中那个model参数使用的是
redirectModel
这个model,所以没有pojo可以在不同的场景中观察mavContainer中的哪个model与参数model是同一个对象
RedirectAttributes 问题:https://blog.youkuaiyun.com/z69183787/article/details/52596987
RedirectAttributes也是个model,只不过这个model可以重定向传输数据,会拼接到url之后(测试过了)
@Controller public class userController { @GetMapping("/user") public String getUser(Model model, RedirectAttributes attribute){ model.addAttribute("a",1); attribute.addAttribute("b",2); return "redirect:suc"; } @GetMapping("/suc") @ResponseBody public String suc(Model m){ System.out.println(m == null); return "123"; } }
发送/user请求时,model对象是
BindAwareModelMap$6152
,RedirectAttributes是RedirectAttributesModelMap$6153
类型在返回的过程中,a放到6152中,b放到6153中
分别对应default和redirect,POJO放到default中
model 6160 attr6161
在执行目标方法前new mavC,6581是model
上面处理完了mv,并且执行过了拦截器的preHandle
、postHandle
然后我们在doDispatch中看到下一步是:
⑥视图解析processDispatchResult
无论是json还是重定向,都走到了这里,重定向还没变呢
processDispatchResult
json是没有view的,在这里会区分
private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv, // 包含m和v,里面有两个属性view、model
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
// mv不为空 // 返回json时为空
if (mv != null && !mv.wasCleared()) {
// 渲染页面,视图解析
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else {//json走到了这里
if (logger.isTraceEnabled()) { logger.trace("无视图转染,没有mv返回"); }
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled.. 调用拦截器的AfterCompletion(
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
dis.render渲染
DispatcherServlet
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.国际化
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
// 获取视图的名字
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 根据方法的返回值得到view对象
view = resolveViewName(viewName,
mv.getModelInternal(), //拿出model
locale, request);
if (view == null) {
throw new ServletException("servlet XXX 解析不了视图viewName");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// 派发 Delegate to the View object for rendering.
if (logger.isTraceEnabled()) { logger.trace("开始渲染视图 view "); }
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
/// 渲染
view.render(mv.getModelInternal(), //传入model
request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) { logger.debug("解析视图错误"); }
throw ex;
}
}
视图解析器resolveViewName
视图解析器不是support规则了,而是合并了
protected View resolveViewName(String viewName,
@Nullable Map<String, Object> model,
Locale locale,
HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 遍历视图解析器
for (ViewResolver viewResolver : this.viewResolvers) {
// 看看目前的视图解析器能否解析viewName
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
// 内容协商的视图解析器,可以得到。重定向中得到的是RedirectView对象给浏览器,浏览器再重新请求
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
// 去里面遍历视图解析器。有BeanNameViewResolver、ThymeleafViewResolver、ViewResolverComposite、interResourceViewResolver
/*
BeanNameViewResolver 用容器中的bean
ThymeleafViewResolver : createView() 里面会判断是否是重定向
ViewResolverComposite
interResourceViewResolver
*/
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);//这里可能得到是一个转发视图
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
// 打印媒体类型mediaTypeInfo使用了 406 NOT_ACCEPTABLE
return NOT_ACCEPTABLE_VIEW;
}
else {
// 打印
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
createView
创建视图的时候,重定向和转发都创建他们的重定向/转发view提前return了。正常的thymeleaf最后才用loadView进行创建view
@Override // ThymeleafViewResolver
protected View createView(final String viewName, final Locale locale) throws Exception {
// First possible call to check "viewNames": before processing redirects and forwards
if (!this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
// 处理重定向
// Process redirects (HTTP redirects)
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
// 得到要转发的视图
final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}
// Process forwards (to JSP resources)
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
// The "forward:" prefix will actually create a Servlet/JSP view, and that's precisely its aim per the Spring
// documentation. See http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-forward-prefix
vrlogger.trace("[THYMELEAF] View \"{}\" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length(), viewName.length());
return new InternalResourceView(forwardUrl);
}
// Second possible call to check "viewNames": after processing redirects and forwards
if (this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
// 不是重定向和转发,开始解析视图。前面的if都return了
vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a " +
"{} instance will be created for it", viewName, getViewClass().getSimpleName());
return loadView(viewName, locale);
}
创建视图的3种场景
视图有3种,分为重定向,转发,还有正常的视图
1) new RedirectView
重定向的写法
public ModelAndView toPage(){
....
return new ModelAndView(new RedirectView("login.do"));
}
public ModelAndView toPage(){
....
Map<String,Object> reqParam = new HashMap<String,Object>(16);
reqParam.put("param","test");
return new ModelAndView(new RedirectView("login.do"),reqParam);
}
public class ModelAndView{
....
public ModelAndView(View view, Map<String, ?> model) {
this.view = view;
if (model != null) {
this.getModelMap().addAllAttributes(model);
}
}
}
2) new InternalResourceView(
3) new AbstractThymeleafView。
对于正常模板的渲染,是用loadView()
返回他的view的,返回的是new ThymeleafView
new ThymeleafView
@Override
protected View loadView(final String viewName, final Locale locale) throws Exception {
final AutowireCapableBeanFactory beanFactory = getApplicationContext().getAutowireCapableBeanFactory();
final boolean viewBeanExists = beanFactory.containsBean(viewName);
// 视图类型不是容器中的bean
final Class<?> viewBeanType = viewBeanExists? beanFactory.getType(viewName) : null;
// 准备一个视图对象
final AbstractThymeleafView view;
if (viewBeanExists && viewBeanType != null && AbstractThymeleafView.class.isAssignableFrom(viewBeanType)) {
// AppCtx has a bean with name == viewName, and it is a View bean. So let's use it as a prototype!
//
// This can mean two things: if the bean has been defined with scope "prototype", we will just use it.
// If it hasn't we will create a new instance of the view class and use its properties in order to
// configure this view instance (so that we don't end up using the same bean from several request threads).
//
// Note that, if Java-based configuration is used, using @Scope("prototype") would be the only viable
// possibility here.
final BeanDefinition viewBeanDefinition =
(beanFactory instanceof ConfigurableListableBeanFactory ?
((ConfigurableListableBeanFactory)beanFactory).getBeanDefinition(viewName) :
null);
// 创建一个ThymeleafView,就可以取解析
if (viewBeanDefinition == null || !viewBeanDefinition.isPrototype()) {
// No scope="prototype", so we will just apply its properties. This should only happen with XML config.
final AbstractThymeleafView viewInstance = BeanUtils.instantiateClass(getViewClass());
view = (AbstractThymeleafView) beanFactory.configureBean(viewInstance, viewName);
} else {
// This is a prototype bean. Use it as such.
view = (AbstractThymeleafView) beanFactory.getBean(viewName);
}
} else {
final AbstractThymeleafView viewInstance = BeanUtils.instantiateClass(getViewClass());
if (viewBeanExists && viewBeanType == null) {
// AppCtx has a bean with name == viewName, but it is an abstract bean. We still can use it as a prototype.
// The AUTOWIRE_NO mode applies autowiring only through annotations
beanFactory.autowireBeanProperties(viewInstance, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
// A bean with this name exists, so we apply its properties
beanFactory.applyBeanPropertyValues(viewInstance, viewName);
// Finally, we let Spring do the remaining initializations (incl. proxifying if needed)
view = (AbstractThymeleafView) beanFactory.initializeBean(viewInstance, viewName);
} else {
// Either AppCtx has no bean with name == viewName, or it is of an incompatible class. No prototyping done.
// The AUTOWIRE_NO mode applies autowiring only through annotations
beanFactory.autowireBeanProperties(viewInstance, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
// Finally, we let Spring do the remaining initializations (incl. proxifying if needed)
view = (AbstractThymeleafView) beanFactory.initializeBean(viewInstance, viewName);
}
}
view.setTemplateEngine(getTemplateEngine());
view.setStaticVariables(getStaticVariables());
// We give view beans the opportunity to specify the template name to be used
if (view.getTemplateName() == null) {
view.setTemplateName(viewName);
}
if (!view.isForceContentTypeSet()) {
view.setForceContentType(getForceContentType());
}
if (!view.isContentTypeSet() && getContentType() != null) {
view.setContentType(getContentType());
}
if (view.getLocale() == null && locale != null) {
view.setLocale(locale);
}
if (view.getCharacterEncoding() == null && getCharacterEncoding() != null) {
view.setCharacterEncoding(getCharacterEncoding());
}
if (!view.isProducePartialOutputWhileProcessingSet()) {
view.setProducePartialOutputWhileProcessing(getProducePartialOutputWhileProcessing());
}
return view;
}
渲染视图的3种场景
1/2)AbstractView.render
RedirectView、InternalResourceView
// AbstractView // view.render()
public void render(@Nullable Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
// 创建一个要合并的输出模型,将传入的model形成一个map
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
// 渲染合并输出的模型数据。
renderMergedOutputModel(mergedModel, // map
getRequestToExpose(request), //在里面先获取到原生request
response);
}
对于重定向和转发,调用的是父类中的render,因为他们没有自己重写,而对于正常的视图渲染,他自己写了render方法
3)ThymeleafView.render
从上一个dis.render来到了这一个render
public class ThymeleafView
extends AbstractThymeleafView {
public void render(final Map<String, ?> model, final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
renderFragment(this.markupSelectors, model, request, response);
}
protected void renderFragment(final Set<String> markupSelectorsToRender, final Map<String, ?> model, final HttpServletRequest request,
final HttpServletResponse response)
throws Exception {
final ServletContext servletContext = getServletContext() ;
final String viewTemplateName = getTemplateName();
// 模板引擎
final ISpringTemplateEngine viewTemplateEngine = getTemplateEngine();
if (viewTemplateName == null) {
throw new IllegalArgumentException("Property 'templateName' is required");
}
if (getLocale() == null) {
throw new IllegalArgumentException("Property 'locale' is required");
}
if (viewTemplateEngine == null) {
throw new IllegalArgumentException("Property 'templateEngine' is required");
}
// 数据
final Map<String, Object> mergedModel = new HashMap<String, Object>(30);
final Map<String, Object> templateStaticVariables = getStaticVariables();
if (templateStaticVariables != null) {
mergedModel.putAll(templateStaticVariables);
}
if (pathVariablesSelector != null) {
@SuppressWarnings("unchecked")
final Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(pathVariablesSelector);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
}
if (model != null) {
mergedModel.putAll(model);
}
final ApplicationContext applicationContext = getApplicationContext();
final RequestContext requestContext =
new RequestContext(request, response, getServletContext(), mergedModel);
final SpringWebMvcThymeleafRequestContext thymeleafRequestContext =
new SpringWebMvcThymeleafRequestContext(requestContext, request);
// For compatibility with ThymeleafView
addRequestContextAsVariable(mergedModel, SpringContextVariableNames.SPRING_REQUEST_CONTEXT, requestContext);
// For compatibility with AbstractTemplateView
addRequestContextAsVariable(mergedModel, AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, requestContext);
// Add the Thymeleaf RequestContext wrapper that we will be using in this dialect (the bare RequestContext
// stays in the context to for compatibility with other dialects)
mergedModel.put(SpringContextVariableNames.THYMELEAF_REQUEST_CONTEXT, thymeleafRequestContext);
// Expose Thymeleaf's own evaluation context as a model variable
//
// Note Spring's EvaluationContexts are NOT THREAD-SAFE (in exchange for SpelExpressions being thread-safe).
// That's why we need to create a new EvaluationContext for each request / template execution, even if it is
// quite expensive to create because of requiring the initialization of several ConcurrentHashMaps.
final ConversionService conversionService =
(ConversionService) request.getAttribute(ConversionService.class.getName()); // might be null!
final ThymeleafEvaluationContext evaluationContext =
new ThymeleafEvaluationContext(applicationContext, conversionService);
mergedModel.put(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME, evaluationContext);
final IEngineConfiguration configuration = viewTemplateEngine.getConfiguration();
final WebExpressionContext context =
new WebExpressionContext(configuration, request, response, servletContext, getLocale(), mergedModel);
final String templateName;
final Set<String> markupSelectors;
if (!viewTemplateName.contains("::")) {
// No fragment specified at the template name
templateName = viewTemplateName;
markupSelectors = null;
} else {
// Template name contains a fragment name, so we should parse it as such
final IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
final FragmentExpression fragmentExpression;
try {
// By parsing it as a standard expression, we might profit from the expression cache
fragmentExpression = (FragmentExpression) parser.parseExpression(context, "~{" + viewTemplateName + "}");
} catch (final TemplateProcessingException e) {
throw new IllegalArgumentException("Invalid template name specification: '" + viewTemplateName + "'");
}
final FragmentExpression.ExecutedFragmentExpression fragment =
FragmentExpression.createExecutedFragmentExpression(context, fragmentExpression);
templateName = FragmentExpression.resolveTemplateName(fragment);
markupSelectors = FragmentExpression.resolveFragments(fragment);
final Map<String,Object> nameFragmentParameters = fragment.getFragmentParameters();
if (nameFragmentParameters != null) {
if (fragment.hasSyntheticParameters()) {
// We cannot allow synthetic parameters because there is no way to specify them at the template
// engine execution!
throw new IllegalArgumentException(
"Parameters in a view specification must be named (non-synthetic): '" + viewTemplateName + "'");
}
context.setVariables(nameFragmentParameters);
}
}
正式转发与渲染
1)重定向RedirectView.renderMergedOutputModel
@Override// RedirectView
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 目标url
String targetUrl = createTargetUrl(model, request);
targetUrl = updateTargetUrl(targetUrl, model, request, response);
// Save flash attributes
RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
// 重定向
// Redirect
sendRedirect(request, response, targetUrl, this.http10Compatible);
}
protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request)
throws UnsupportedEncodingException {
// Prepare target URL.
StringBuilder targetUrl = new StringBuilder();
String url = getUrl();
Assert.state(url != null, "'url' not set");
if (this.contextRelative && getUrl().startsWith("/")) {
// Do not apply context path to relative URLs.
targetUrl.append(getContextPath(request));
}
targetUrl.append(getUrl());
String enc = this.encodingScheme;
if (enc == null) {
enc = request.getCharacterEncoding();
}
if (enc == null) {
enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
}
if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) {
Map<String, String> variables = getCurrentRequestUriVariables(request);
targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc);
}
if (isPropagateQueryProperties()) {
appendCurrentQueryParams(targetUrl, request);
}
if (this.exposeModelAttributes) {//如果有model,就拼接url
appendQueryProperties(targetUrl, model, enc);
}
return targetUrl.toString();
}
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
String targetUrl, boolean http10Compatible) throws IOException {
String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
if (http10Compatible) {
HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
if (this.statusCode != null) {
response.setStatus(this.statusCode.value());
response.setHeader("Location", encodedURL);
}
else if (attributeStatusCode != null) {
response.setStatus(attributeStatusCode.value());
response.setHeader("Location", encodedURL);
}
else {
// 调用了servlet的原始方法
// Send status code 302 by default.
response.sendRedirect(encodedURL);
}
}
else {
HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
response.setStatus(statusCode.value());
response.setHeader("Location", encodedURL);
}
}
2)转发InternalResourceView.renderMergedOutputModel
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
rd.forward(request, response);
public class InternalResourceView extends AbstractUrlBasedView {
// 渲染合并输出的model
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 暴露model作为请求域的属性。遍历model,然后调用request.setAttribute(name,value)
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
// 拿到转发器
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
// 转发
rd.forward(request, response);
}
}
createMergedOutputModel
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
HttpServletRequest request, HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
// 如果model不为空
if (model != null) {
// 转移到HashMap中
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
}
return mergedModel;//去设置到request中
}
3、至于HandlerAdapter是干嘛用的?它的handler方法有什么用?我们毫无概念,接下来我们从另一个角度切入(就像两个人相对而行,一个人筋疲力尽了,唯有靠另一个人努力前行才能相遇),所以我选择Controller,得先从配置文件入手,因为它采用了spring的IOC。
<bean id="controller" class="com.mvc.controller.MyController"></bean>
<bean id="interceptor" class="com.mvc.interceptor.MyInterceptor"></bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="controller">controller</prop>
</props>
</property>
<property name="interceptors">
<array>
<ref bean="interceptor"></ref>
</array>
</property>
</bean>
配置文件又给了我们一条重要的信息:controller和拦截器都是作为SimpleUrlHandlerMapping的参数传进去的,而SimpleUrlHandlerMapping是HandlerMapping的子类。从这里就可以猜测,controller的核心方法要么被HandlerMapping直接调用,要么被HandlerMapping的附属产品(类)进行调用,接下来我们查看一下controller核心方法的调用情况。
很幸运,看到SimpleControllerHandlerAdapter和DispatcherServlet.doDispatch(request, response),我好像发现了新大陆,这不正是我们想要的吗?HandlerAdapter类和doDispatch(request, response)方法完美地结合在了一起。再看SimpleControllerHandlerAdapter的handler方法:
这里也有一个方法的调用链,从上图就可以看出,handle方法最终是调用handleRequestInternal方法,也就是我们在controller中自定义的方法。总而言之,HandlerAdapter的handler方法是用来调用controller中的handleRequestInternal方法的,而handleRequestInternal的方法体正是我们用户自定义的业务逻辑。
返回值json解析
为了处理@ResponseBody
我们在pom中有starter-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
web场景自动引入了json场景,这个在上面的里面就有
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
而json-starter里有
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<scope>compile</scope>
</dependency>
给前端自动返回json数据;
自定义web组件
虽然实现了WebMvcConfigurer,你是里面并没有bean,只是重写方法。怎么注册到ioc中呢??原因在于@EnableWebMvc
@EnableWebMvc
在springboot中,不要写@EnableWebMvc
。会全面接管。写@Configuration
即可。
原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项
- 实现
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
-
- 把配置文件的值和ServletWebServerFactory 进行绑定
- 修改配置文件 server.xxx
- 直接自定义 ConfigurableServletWebServerFactory
Spring boot 使用嵌入式应用服务器时,如果希望对 WebServer 进行某些个性化配置,可以通过创建 WebServerFactoryCustomizer
子类的 实例并注册为 Bean 的方式实现。
WebServerFactory 对象创建完毕后, WebServerFactoryCustomizerBeanPostProcessor
会从 BeanFactory 中查询所有 WebServerFactoryCustomizer
的Bean生成列表、排序,然后逐一调用 WebServerFactoryCustomizer
的 customize()
方法。
@Component// web服务器工厂定制器
public class CustomizationBean
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {//这个参数类型和泛型一致。
server.setPort(9000);
}
}
SpringBoot从1.5.3升级到2.1.6有不少问题,比较典型的一个是WebServer的个性化设置问题。在1.5.3中可以直接通过实行EmbeddedServletContainerCustomizer就可以方便设置。
SpringBoot升级到2.0系列后不在支持EmbeddedServletContainerCustomizer。WebServerFactoryCustomizer也就是一种定制器类:需要通过新建自定义的WebServerFactoryCustomizer类来实现属性配置修改。
@Configuration
public class TomcatCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(
connector -> {
AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) connector.getProtocolHandler();
}
factory.addServerCustomizers(httpServer -> httpServer.wiretap(true));
super.customize(factory);
}
定制化的常见方式:
• 修改配置文件;
• xxxxxCustomizer;
• 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
• Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
• @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
原理
- 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
- 2、一旦使用
@EnableWebMvc
、。会 @Import(DelegatingWebMvcConfiguration
.class)导入mvc派发器配置 - 3、
DelegatingWebMvcConfiguration
的 作用,只保证SpringMVC最基本的使用- 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
- 自动配置了一些非常底层的组件。
RequestMappingHandlerMapping
这些组件依赖的组件都是从容器中获取 public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
- 4、
WebMvcAutoConfiguration
里面的配置要能生效 必须@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
- 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
2、原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项
@EnableWebMvc等价于<mvc:annotation-driven/>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)//启用了派发器 // 授权webmvc的配置
public @interface EnableWebMvc {
}
导入了DelegatingWebMvcConfiguration派发器配置这个组件,他继承了WebMvcConfigurationSupport
而WebMvcAutoConfiguration自动配置要求不能有@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
如下
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // 没有他mvc的自动配置才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
所以我们如果自己写了@EnableWebMvc,就会把mvc的自动配置失效,那么mvc的默认组件就都用不了了
所以我们尽量不要自己写@EnableWebMvc。
如果没有自己写@EnableWebMvc,那么自动配置mvc生效
mvc的自动配置类WebMvcAutoConfiguration
里有
-
@Configuration注入
WebMvcAutoConfigurationAdapter
,-
@Import(EnableWebMvcConfiguration.class),该类继承了DelegatingWebMvcConfiguration
-
DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport,维护WebMvcConfigurerComposite configurers
-
mvc自动配置要求在DispatcherServlet的自动配置之后,而DispatcherServlet的自动配置要求在ServlerWebServerFactory自动配置之后。也就是说,先tomcat,再servlet,再mvc
-
@AutoConfigureAfter(DispatcherServletAutoConfiguration.class) WebMvcAutoConfiguration @AutoConfigureAfter(ServlerWebServerFactoryAutoConfiguration.class)他里面会注册内嵌tomcat等 DispatcherServletAutoConfiguration
-
-
@Bean了很多默认组件
-
@Configuration(proxyBeanMethods = false) @Conditional(DispatcherServletRegistrationCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) @Import(DispatcherServletConfiguration.class) protected static class DispatcherServletRegistrationConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } } @Configuration(proxyBeanMethods = false) @Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) protected static class DispatcherServletConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { // DispatcherServlet DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; }
派发器
WebMvcConfigurer派发器
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration // 上面的@import给容器中注入了本类
extends WebMvcConfigurationSupport {
// 派发器
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false) // 自动注入webConfig,比如我们自己配置的也注入进去了
public void setConfigurers(List<WebMvcConfigurer> configurers) {//传入的是webConfig的CGLIB代理
if (!CollectionUtils.isEmpty(configurers)) {
// 调用派发器的add
this.configurers.addWebMvcConfigurers(configurers);//重写了add方法进行派发
}
}
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.configurers.addArgumentResolvers(argumentResolvers);
}
}
class WebMvcConfigurerComposite implements WebMvcConfigurer {
// 全局唯一的list
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
// 把WebMvcConfigurer对象都添加了。比如有10个webConfig,有2个里添加了拦截器,最终结果是会有2*10个拦截器
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addArgumentResolvers(argumentResolvers);
}
}
那么说明时候调用我们的addArgumentResolvers呢?WebMvcConfigurationSupport
里面有@Bean,里面有mapping.setInterceptors(getInterceptors()),而getInterceptors()里面有add的逻辑
springboot采用默认的,而mvc没有默认的
接口WebMvcConfigurer
/** 通过@EnableWebMvc自定义mvc配置,定义回调方法 */
public interface WebMvcConfigurer {
/**
* Helps with configuring HandlerMappings path matching options such as trailing slash match,
* suffix registration, path matcher and path helper.
* Configured path matcher and path helper instances are shared for:
* <ul>
* <li>RequestMappings</li>
* <li>ViewControllerMappings</li>
* <li>ResourcesMappings</li>
*/
default void configurePathMatch(PathMatchConfigurer configurer) {
}
/** 配置内容协商选项 */
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
/** 定义同步请求处理选项 */
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
/** 配置一个handler负责派发没有处理器 unhandled的请求,指向默认的servlet
通常的用处是DispatcherServlet被映射到 "/" ,因此重写默认的handling of static resources */
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/** 往默认的里添加Converters和Formatters */
default void addFormatters(FormatterRegistry registry) {
}
/** 添加拦截器。用于在controller method invocations 和 resource handler requests的前后pre- and post-processing */
default void addInterceptors(InterceptorRegistry registry) {
}
/** 添加处理images, js, css等静态资源的handlers处理器,可以是web项目路径、类路径或其他 */
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/** * Configure cross origin requests processing. */
default void addCorsMappings(CorsRegistry registry) {
}
/** 就是相当于配置controller,通常用于代替没有逻辑的controller,如渲染home网页、404、204等 */
default void addViewControllers(ViewControllerRegistry registry) {
}
/** 配置视图解析器。转换从controller返回的视图名,转成具体的servlet.View实现,去渲染 */
default void configureViewResolvers(ViewResolverRegistry registry) {
}
/**
* <p>This does not override the built-in support for resolving handler
* method arguments. To customize the built-in support for argument
* resolution, configure {@link RequestMappingHandlerAdapter} directly.
* @param resolvers initially an empty list
*/
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
/** 添加返回值解析器,用于处理controller方法的参数
* Add handlers to support custom controller method return value types.
* <p>Using this option does not override the built-in support for handling
* return values. To customize the built-in support for handling return
* values, configure RequestMappingHandlerAdapter directly.
* @param handlers initially an empty list
*/
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
/** HttpMessageConverters用于读写请求或响应的体。如果没有转换器添加,将注册默认的一些转换器
* 关闭了默认的converter registration,添加自己的converters
为了简单添加converter而不影响默认的registration,可以看下面的extendMessageConverters
* @param converters 初始为一个空list
*/
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/** 参数为已配置的转换器list
这是个钩子hook方法,用于在配置后扩展或修改转换器converters,用处是让默认的converters插入一个自定义的converter */
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/** 配置异常解析器
* <p>The given list starts out empty. If it is left empty, the framework
* configures a default set of resolvers, see
* {@link WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers(List, org.springframework.web.accept.ContentNegotiationManager)}.
* Or if any exception resolvers are added to the list, then the application
* effectively takes over and must provide, fully initialized, exception resolvers.
如果要扩展或者修改默认的 exception resolvers,应该看extendHandlerExceptionResolvers方法
* @param resolvers initially an empty list
* @see #extendHandlerExceptionResolvers(List)
* @see WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers(List, org.springframework.web.accept.ContentNegotiationManager)
*/
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
/** 扩展和修改默认的exception resolvers。
* @param resolvers 默认的异常解析器集合。
* @see WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers(List, ContentNegotiationManager)
*/
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
/** 自定义Validator。如果JSR-303在类路径下,默认的实现是OptionalValidatorFactoryBean */
@Nullable
default Validator getValidator() {return null;}
/** 提供自定义的消息码解析器MessageCodesResolver,用于从数据绑定和校验码处 building message codes
默认返回null */
@Nullable
default MessageCodesResolver getMessageCodesResolver() {return null;}
}
WebMvcConfigurationSupport
public class WebMvcConfigurationSupport
implements ApplicationContextAware, ServletContextAware {
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// 创建mapping
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
// 设置拦截器,在子类DelegatingWebMvcConfiguration复写
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
// 内容协商管理器
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
自定义formatter
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
3、开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
发请求:
http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml
看下面的媒体类型。因为是在内容协商管理器里多了个参数的协商策略,参数名字是format即可
确定客户端接收什么样的内容类型;
1、Parameter策略优先
确定是要返回json数据(获取请求头中的format的值)
return request.getParameter(getParameterName());
获取format对应的值
2、最终进行内容协商返回给客户端json即可。
5、自定义 MessageConverter
消息转换器也有顺序,比如都是json,可以用add(0)操作
消息转换器从http里转出来内容,比如转成json,然后用转换器把json转成java对象
消息转换器是如何注入的:自动配置类里有WebMvcConfigurationAdapter,适配类中有重写方法,会被调用,
在mvc自动配置类中有个requestMappingHandlerAdapter构造注入@Bean适配器RequestMappingHandlerAdapter(我们要看为什么适配器能拿到消息转换器),他第一句是调用父类的构造器,即WebMvcConfigurationSupport,里面也有个@Bean。
里面有个adpter.setMessageConverters(getMessageConverters());
getMessageConverters()里面调用了configureMessageConverters(this.messageConverters),而这个属性Support的属性,有webMvcConfigureComposite
而什么时候被扫描进去的是在HttpMessageConvertersAutoConfiguration这个类中
回顾流程:
实现多协议数据兼容。json、xml、x-guigu
0、@ResponseBody
响应数据出去 调用 RequestResponseBodyMethodProcessor
处理
1、Processor 处理方法返回值。通过 MessageConverter
处理
2、所有 MessageConverter
合起来可以支持各种媒体类型
数据的操作(读、写)
3、内容协商找到最终的 messageConverter
;
前面我们说的是基于请求头的内容协商策略,其实还可以基于路径、内容等。接口是
ContentNegotiationStrategy
为了自定义消息转换器,我们提出了需求
- 如果是浏览器发请求,返回xml。
application/xml
。先进行内容协商,然后把协商好的用消息转换器转换。 - 如果是ajax请求,返回json。
application/json
- 如果硅谷app发请求,返回自定义。比如我们自定义了一个协议
application/myapp
,为了能处理这个内容,我们需要自定义消息转换器- 添加自定义的消息转换器。系统里默认的消息转换器在WebMvcAutoConfiguration里有内嵌配置类,会加进去addDefaultHttpMessageConverters,只要判断存在,就会填进去
- 内容协商
- 消息转换器处理内容
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override /// 会添加。而configureMessageConverters那个方法会把原来的覆盖。
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}
}
}
/* 自定义的Converter */
public class GuiguMessageConverter
implements HttpMessageConverter<Person> {
@Override // 从controller的参数中读
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class);//只要是Person类型就能写
}
/**
* 服务器要统计所有MessageConverter都能写出哪些内容类型
* application/x-guigu
*/
@Override
public List<MediaType> getSupportedMediaTypes() { // 支持的媒体类型
return MediaType.parseMediaTypes("application/x-guigu");
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, // 要写的内容
MediaType contentType, // 媒体类型application/x-guigu
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
//写出去。输出流
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
上面的是用请求头的信息进行消息转换。现在看如何用url的参数www.baidu.com?format=json进行消息协商
自定义协商管理器
在writeWithMesageConverters()里有一个,
- 获取浏览器接收的媒体类型的时候getAcceptableMediaType(){里面有
- this.contentNegotiationManager.resolveMediaTypes(servletWebRequest)用协商管理器去获得
- 协商管理器中只有1个策略:header的
// 默认的协商策略不是参数的,而是请求头的
// ContentNegotiationManager.java
public ContentNegotiationManager() { // 无参构造器:
// 添加请求头的策略
this(new HeaderContentNegotiationStrategy());
}
public ContentNegotiationManager(ContentNegotiationStrategy... strategies) {//可以注入多个
this(Arrays.asList(strategies));
}
public ContentNegotiationManager(Collection<ContentNegotiationStrategy> strategies) {
this.strategies.addAll(strategies);
for (ContentNegotiationStrategy strategy : this.strategies) {
if (strategy instanceof MediaTypeFileExtensionResolver) {
this.resolvers.add((MediaTypeFileExtensionResolver) strategy);
}
}
}
默认只是一个header的。如果我们在yaml中指定contentnegotiation: favor-parameter: true
那么将自动注入一个参数的内容协商策略。现支持请求头和参数的
但是这个参数协商策略只支持只支持format=json这种形式,不支持format=gg

我们就可以通过重写协商管理器来添加/修改 新的策略方式
@Override // 重写
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//Map<String, MediaType> mediaTypes
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));// 添加自定义的
// 指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);// 传入媒体类型
// parameterStrategy.setParameterName("ff");
HeaderContentNegotiationStrategy headStrategy = new HeaderContentNegotiationStrategy();
// 添加参数和请求头的协商策略
configurer.strategies(Arrays.asList(parameterStrategy,headStrategy));
}
其他知识点
<mvc:annotation-driven>会自动注册
RequestMappingHandlerMapping
RequestMappingHandlerAdapter
ExceptionHandlerExceptionResolver 这3个bean
还将支持以下:
ConversionService类型扎UN哈UN
数据格式化
@Valid
@RequestBody @ ResponseBody
前端到后盾的数据格式化
在属性上可以加注解@DateTimeFormat(patterm="yyyy-MM-dd")
@NumberFormat(pattern="#,###,###.##")
状态码
public interface HttpServletResponse extends ServletResponse {
/** 添加一个Cookie给响应,可以设置多个Cookie */
public void addCookie(Cookie cookie);
/** 判断是否指定的响应头已经设置了 */
public boolean containsHeader(String name); // 参数为header name
/**
* Encodes the specified URL by including the session ID in it, or, if
* encoding is not needed, returns the URL unchanged. The implementation of
* this method includes the logic to determine whether the session ID needs
* to be encoded in the URL. For example, if the browser supports cookies,
* or session tracking is turned off, URL encoding is unnecessary.
* <p>
* For robust session tracking, all URLs emitted by a servlet should be run
* through this method. Otherwise, URL rewriting cannot be used with
* browsers which do not support cookies.
*/
public String encodeURL(String url);
/**
* Encodes the specified URL for use in the <code>sendRedirect</code> method
* or, if encoding is not needed, returns the URL unchanged. The
* implementation of this method includes the logic to determine whether the
* session ID needs to be encoded in the URL. Because the rules for making
* this determination can differ from those used to decide whether to encode
* a normal link, this method is separated from the <code>encodeURL</code>
* method.
* <p>
* All URLs sent to the <code>HttpServletResponse.sendRedirect</code> method
* should be run through this method. Otherwise, URL rewriting cannot be
* used with browsers which do not support cookies.
* @see #sendRedirect
* @see #encodeUrl
*/
public String encodeRedirectURL(String url);
/** 返回编码好的URL
* @deprecated As of version 2.1, use encodeURL(String url) instead
*/
@Deprecated
public String encodeUrl(String url); // 参数为要编码的url
@Deprecated
public String encodeRedirectUrl(String url); // 参数为要编码的url
/** 发送一个错误响应给客户端。指定响应状态码、清空输出buffer
服务器默认创建一个HTML编码格式的错误页面 响应,包含消息,设置 content type为 "text/html"
cookies和headers未修改
如果错误页面已经被web容器指定,将展示指定的消息
* If the response has already been committed, this method throws an
* IllegalStateException. After using this method, the response should be
* considered to be committed and should not be written to.
*
* @param sc: the error status code
* @param msg: the descriptive message
* @exception IOException: If an input or output exception occurs
* @exception IllegalStateException: If the response was committed
*/
public void sendError(int sc, String msg) throws IOException;
public void sendError(int sc) throws IOException;
/**
* Sends a temporary redirect response to the client using the specified
* redirect location URL. This method can accept relative URLs; the servlet
* container must convert the relative URL to an absolute URL before sending
* the response to the client. If the location is relative without a leading
* '/' the container interprets it as relative to the current request URI.
* If the location is relative with a leading '/' the container interprets
* it as relative to the servlet container root.
* <p>
* If the response has already been committed, this method throws an
* IllegalStateException. After using this method, the response should be
* considered to be committed and should not be written to.
*
* @param location
* the redirect location URL
* @exception IllegalStateException
* If the response was committed or if a partial URL is given
* and cannot be converted into a valid URL
*/
public void sendRedirect(String location) throws IOException;
/**
* Sets a response header with the given name and date-value. The date is
* specified in terms of milliseconds since the epoch. If the header had
* already been set, the new value overwrites the previous one. The
* <code>containsHeader</code> method can be used to test for the presence
* of a header before setting its value.
*
* @param name: the name of the header to set
* @param date: the assigned date value
* @see #containsHeader
* @see #addDateHeader
*/
public void setDateHeader(String name, long date);
/**
* Adds a response header with the given name and date-value. The date is
* specified in terms of milliseconds since the epoch. This method allows
* response headers to have multiple values.
*
* @param name: the name of the header to set
* @param date: the additional date value
* @see #setDateHeader
*/
public void addDateHeader(String name, long date);
/**
* Sets a response header with the given name and value. If the header had
* already been set, the new value overwrites the previous one. The
* <code>containsHeader</code> method can be used to test for the presence
* of a header before setting its value.
*
* @param name: the name of the header
* @param value:
* the header value If it contains octet string, it should be
* encoded according to RFC 2047
* (http://www.ietf.org/rfc/rfc2047.txt)
* @see #containsHeader
* @see #addHeader
*/
public void setHeader(String name, String value);
/**
* Adds a response header with the given name and value. This method allows
* response headers to have multiple values.
*
* @param name: the name of the header
* @param value: the additional header value If it contains octet string, it
* should be encoded according to RFC 2047
* (http://www.ietf.org/rfc/rfc2047.txt)
* @see #setHeader
*/
public void addHeader(String name, String value);
/**
* Sets a response header with the given name and integer value. If the
* header had already been set, the new value overwrites the previous one.
* The <code>containsHeader</code> method can be used to test for the
* presence of a header before setting its value.
*
* @param name: the name of the header
* @param value: the assigned integer value
* @see #containsHeader
* @see #addIntHeader
*/
public void setIntHeader(String name, int value);
/**
* Adds a response header with the given name and integer value. This method
* allows response headers to have multiple values.
*
* @param name: the name of the header
* @param value: the assigned integer value
* @see #setIntHeader
*/
public void addIntHeader(String name, int value);
/**
* Sets the status code for this response. This method is used to set the
* return status code when there is no error (for example, for the status
* codes SC_OK or SC_MOVED_TEMPORARILY). If there is an error, and the
* caller wishes to invoke an error-page defined in the web application, the
* <code>sendError</code> method should be used instead.
* <p>
* The container clears the buffer and sets the Location header, preserving
* cookies and other headers.
*
* @param sc: the status code
* @see #sendError
*/
public void setStatus(int sc);
/**
* Sets the status code and message for this response.
*
* @param sc: the status code
* @param sm: the status message
* @deprecated As of version 2.1, due to ambiguous meaning of the message
* parameter. To set a status code use
* <code>setStatus(int)</code>, to send an error with a
* description use <code>sendError(int, String)</code>.
*/
@Deprecated
public void setStatus(int sc, String sm);
/**
* Get the HTTP status code for this Response.
*
* @return The HTTP status code for this Response
*
* @since Servlet 3.0
*/
public int getStatus();
/**
* Return the value for the specified header, or <code>null</code> if this
* header has not been set. If more than one value was added for this
* name, only the first is returned; use {@link #getHeaders(String)} to
* retrieve all of them.
*
* @param name Header name to look up
*
* @return The first value for the specified header. This is the raw value
* so if multiple values are specified in the first header then they
* will be returned as a single header value .
*
* @since Servlet 3.0
*/
public String getHeader(String name);
/**
* Return a Collection of all the header values associated with the
* specified header name.
*
* @param name Header name to look up
*
* @return The values for the specified header. These are the raw values so
* if multiple values are specified in a single header that will be
* returned as a single header value.
*
* @since Servlet 3.0
*/
public Collection<String> getHeaders(String name);
/**
* Get the header names set for this HTTP response.
*
* @return The header names set for this HTTP response.
*
* @since Servlet 3.0
*/
public Collection<String> getHeaderNames();
/**
* Configure the supplier of the trailer headers. The supplier will be
* called in the scope of the thread that completes the response.
* <br>
* Trailers that don't meet the requirements of RFC 7230, section 4.1.2 will
* be ignored.
* <br>
* The default implementation is a NO-OP.
*
* @param supplier The supplier for the trailer headers
*
* @throws IllegalStateException if this method is called when the
* underlying protocol does not support trailer headers or if using
* HTTP/1.1 and the response has already been committed
*
* @since Servlet 4.0
*/
public default void setTrailerFields(Supplier<Map<String, String>> supplier) {
// NO-OP
}
/**
* Obtain the supplier of the trailer headers.
* <br>
* The default implementation returns null.
*
* @return The supplier for the trailer headers
*
* @since Servlet 4.0
*/
public default Supplier<Map<String, String>> getTrailerFields() {
return null;
}
/*
* Server status codes; see RFC 2068.
*/
/**
* Status code (100) indicating the client can continue.
*/
public static final int SC_CONTINUE = 100;
/**
* Status code (101) indicating the server is switching protocols according
* to Upgrade header.
*/
public static final int SC_SWITCHING_PROTOCOLS = 101;
/**
* Status code (200) indicating the request succeeded normally.
*/
public static final int SC_OK = 200;
/**
* Status code (201) indicating the request succeeded and created a new
* resource on the server.
*/
public static final int SC_CREATED = 201;
/**
* Status code (202) indicating that a request was accepted for processing,
* but was not completed.
*/
public static final int SC_ACCEPTED = 202;
/**
* Status code (203) indicating that the meta information presented by the
* client did not originate from the server.
*/
public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
/**
* Status code (204) indicating that the request succeeded but that there
* was no new information to return.
*/
public static final int SC_NO_CONTENT = 204;
/**
* Status code (205) indicating that the agent <em>SHOULD</em> reset the
* document view which caused the request to be sent.
*/
public static final int SC_RESET_CONTENT = 205;
/**
* Status code (206) indicating that the server has fulfilled the partial
* GET request for the resource.
*/
public static final int SC_PARTIAL_CONTENT = 206;
/**
* Status code (300) indicating that the requested resource corresponds to
* any one of a set of representations, each with its own specific location.
*/
public static final int SC_MULTIPLE_CHOICES = 300;
/**
* Status code (301) indicating that the resource has permanently moved to a
* new location, and that future references should use a new URI with their
* requests.
*/
public static final int SC_MOVED_PERMANENTLY = 301;
/**
* Status code (302) indicating that the resource has temporarily moved to
* another location, but that future references should still use the
* original URI to access the resource. This definition is being retained
* for backwards compatibility. SC_FOUND is now the preferred definition.
*/
public static final int SC_MOVED_TEMPORARILY = 302;
/**
* Status code (302) indicating that the resource reside temporarily under a
* different URI. Since the redirection might be altered on occasion, the
* client should continue to use the Request-URI for future
* requests.(HTTP/1.1) To represent the status code (302), it is recommended
* to use this variable.
*/
public static final int SC_FOUND = 302;
/**
* Status code (303) indicating that the response to the request can be
* found under a different URI.
*/
public static final int SC_SEE_OTHER = 303;
/**
* Status code (304) indicating that a conditional GET operation found that
* the resource was available and not modified.
*/
public static final int SC_NOT_MODIFIED = 304;
/**
* Status code (305) indicating that the requested resource <em>MUST</em> be
* accessed through the proxy given by the <code><em>Location</em></code>
* field.
*/
public static final int SC_USE_PROXY = 305;
/**
* Status code (307) indicating that the requested resource resides
* temporarily under a different URI. The temporary URI <em>SHOULD</em> be
* given by the <code><em>Location</em></code> field in the response.
*/
public static final int SC_TEMPORARY_REDIRECT = 307;
/**
* Status code (400) indicating the request sent by the client was
* syntactically incorrect.
*/
public static final int SC_BAD_REQUEST = 400;
/**
* Status code (401) indicating that the request requires HTTP
* authentication.
*/
public static final int SC_UNAUTHORIZED = 401;
/**
* Status code (402) reserved for future use.
*/
public static final int SC_PAYMENT_REQUIRED = 402;
/**
* Status code (403) indicating the server understood the request but
* refused to fulfill it.
*/
public static final int SC_FORBIDDEN = 403;
/**
* Status code (404) indicating that the requested resource is not
* available.
*/
public static final int SC_NOT_FOUND = 404;
/**
* Status code (405) indicating that the method specified in the
* <code><em>Request-Line</em></code> is not allowed for the resource
* identified by the <code><em>Request-URI</em></code>.
*/
public static final int SC_METHOD_NOT_ALLOWED = 405;
/**
* Status code (406) indicating that the resource identified by the request
* is only capable of generating response entities which have content
* characteristics not acceptable according to the accept headers sent in
* the request.
*/
public static final int SC_NOT_ACCEPTABLE = 406;
/**
* Status code (407) indicating that the client <em>MUST</em> first
* authenticate itself with the proxy.
*/
public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
/**
* Status code (408) indicating that the client did not produce a request
* within the time that the server was prepared to wait.
*/
public static final int SC_REQUEST_TIMEOUT = 408;
/**
* Status code (409) indicating that the request could not be completed due
* to a conflict with the current state of the resource.
*/
public static final int SC_CONFLICT = 409;
/**
* Status code (410) indicating that the resource is no longer available at
* the server and no forwarding address is known. This condition
* <em>SHOULD</em> be considered permanent.
*/
public static final int SC_GONE = 410;
/**
* Status code (411) indicating that the request cannot be handled without a
* defined <code><em>Content-Length</em></code>.
*/
public static final int SC_LENGTH_REQUIRED = 411;
/**
* Status code (412) indicating that the precondition given in one or more
* of the request-header fields evaluated to false when it was tested on the
* server.
*/
public static final int SC_PRECONDITION_FAILED = 412;
/**
* Status code (413) indicating that the server is refusing to process the
* request because the request entity is larger than the server is willing
* or able to process.
*/
public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
/**
* Status code (414) indicating that the server is refusing to service the
* request because the <code><em>Request-URI</em></code> is longer than the
* server is willing to interpret.
*/
public static final int SC_REQUEST_URI_TOO_LONG = 414;
/**
* Status code (415) indicating that the server is refusing to service the
* request because the entity of the request is in a format not supported by
* the requested resource for the requested method.
*/
public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
/**
* Status code (416) indicating that the server cannot serve the requested
* byte range.
*/
public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
/**
* Status code (417) indicating that the server could not meet the
* expectation given in the Expect request header.
*/
public static final int SC_EXPECTATION_FAILED = 417;
/**
* Status code (500) indicating an error inside the HTTP server which
* prevented it from fulfilling the request.
*/
public static final int SC_INTERNAL_SERVER_ERROR = 500;
/**
* Status code (501) indicating the HTTP server does not support the
* functionality needed to fulfill the request.
*/
public static final int SC_NOT_IMPLEMENTED = 501;
/**
* Status code (502) indicating that the HTTP server received an invalid
* response from a server it consulted when acting as a proxy or gateway.
*/
public static final int SC_BAD_GATEWAY = 502;
/**
* Status code (503) indicating that the HTTP server is temporarily
* overloaded, and unable to handle the request.
*/
public static final int SC_SERVICE_UNAVAILABLE = 503;
/**
* Status code (504) indicating that the server did not receive a timely
* response from the upstream server while acting as a gateway or proxy.
*/
public static final int SC_GATEWAY_TIMEOUT = 504;
/**
* Status code (505) indicating that the server does not support or refuses
* to support the HTTP protocol version that was used in the request
* message.
*/
public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
}