2020-3-25
环境
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.yang</groupId>
<artifactId>learnspringmvc</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>learnspringmvc Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.2.12.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!--文件上传依赖的两个jar包-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!-- log -->
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>learnspringmvc</finalName>
</build>
</project>
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>learn spring mvc Web Application</display-name>
<!-- 部署 DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<!-- 配置Spring MVC文件 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!-- 1 立即初始化servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>springmvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 注解扫描-->
<context:component-scan base-package="org.yang" />
<!-- 视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>IndexController.java
@RestController("/hi")
public class IndexController {
@GetMapping
public Map helloWord() {
Map<String, Object> data = new HashMap<String, Object>();
data.put("code", -1);
return data;
}
}文件结构
.
│ .classpath
│ .project
│ pom.xml
├─src
│ └─main
│ ├─java
│ │ └─org
│ │ └─yang
│ │ └─controller
│ │ IndexController.java
│ │
│ ├─resources
│ │ springmvc-servlet.xml
│ │
│ └─webapp
│ │ index.jsp
│ │
│ └─WEB-INF
│ web.xml
│
└─target
错误? No adapter for handler
http://localhost:8080/learnspringmvc/hi
javax.servlet.ServletException: No adapter for handler [org.yang.controller.IndexController@133e54fb]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
OK , 阅读源码解决吧 [见下 DispatcherServlet 处理流程]
Spring MVC 主要核心对象
Special Bean Types MVC 相关的一些特殊Bean
| Bean type | Explanation |
|---|---|
| HandlerMapping | Map a request to a handler along with a list of interceptors for pre- and post-processing. The mapping is based on some criteria, the details of which vary by HandlerMapping implementation. |
| The two main HandlerMapping implementations are RequestMappingHandlerMapping (which supports @RequestMapping annotated methods) and SimpleUrlHandlerMapping (which maintains explicit registrations of URI path patterns to handlers). | |
| HandlerAdapter | Help the DispatcherServlet to invoke a handler mapped to a request, regardless of how the handler is actually invoked. For example, invoking an annotated controller requires resolving annotations. The main purpose of a HandlerAdapter is to shield the DispatcherServlet from such details. |
| HandlerExceptionResolver | Strategy to resolve exceptions, possibly mapping them to handlers, to HTML error views, or other targets. See Exceptions. |
| ViewResolver | Resolve logical String-based view names returned from a handler to an actual View with which to render to the response. See View Resolution and View Technologies. |
| LocaleResolver, LocaleContextResolver | Resolve the Locale a client is using and possibly their time zone, in order to be able to offer internationalized views. See Locale. |
| ThemeResolver | Resolve themes your web application can use — for example, to offer personalized layouts. See Themes. |
| MultipartResolver | Abstraction for parsing a multi-part request (for example, browser form file upload) with the help of some multipart parsing library. See Multipart Resolver. |
| FlashMapManager | Store and retrieve the “input” and the “output” FlashMap that can be used to pass attributes from one request to another, usually across a redirect. See Flash Attributes. |
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
DispatcherServlet
DispatcherServlet (Spring MVC的Servlet 标准入口点) 它读取配置发现请求映射, 视图解析, 异常处理等所需的委托组件;
DispatcherServlet 对请求URL进行解析, 得到请求资源标识符(URI), 然后根据该URI, 调用HandlerMapping 获得该Handler配置的所有相关的对象(包括一个Handler处理器对象, 多个HandlerInterceptor拦截器对象), 最后以 HandlerExecutionChain 对象的形式返回;
org.springframework.web.servlet.DispatcherServlet#doDispatch //关键入口点
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest); // <!> 从 HandlerMapping 拿到 HandlerExecutionChain
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
......HandlerMapping
org.springframework.web.servlet.HandlerMapping
HandlerMapping 负责映射用户的URL和对应的处理类,HandlerMapping并没有规定这个URL与应用的处理类如何映射, 在 HandlerMapping 接口中只定义了 根据一个URL返回一个 HandlerExecutionChain 的处理链(见下), 我们可以在这个处理链中添加任意的HandlerAdapter 实例来处理这个URL对应的请求;
org.springframework.web.servlet.DispatcherServlet#getHandler
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);// 返回 HandlerExecutionChain
if (handler != null) {
return handler;
}
}
}
return null;
}HandlerMapping 的两个主要实现是
RequestMappingHandlerMapping(支持@RequestMapping带注解的方法)SimpleUrlHandlerMapping(维护对处理程序的URI路径模式的显式注册);
HandlerAdapter
org.springframework.web.servlet.HandlerAdapter
帮助 DispatcherServlet 调用映射到请求的处理程序, 而不管实际如何调用该处理程序; 例如, 调用带注解的控制器需要解析注解参数; HandlerAdapter的主要目的是使 DispatcherServlet 免受此类细节的影响;
-DispatcherServlte 会根据配置文件信息注册HandlerAdapter, 如果在配置文件中没有配置
那么DispatcherServlte会获取HandlerAdapter的默认配置, 会读取 DispatcherServlte.properties文件, 该文件中配置了三种HandlerAdapter:
-
HttpRequestHandlerAdapter适配处理实现org.springframework.web.HttpRequestHandler的Controller -
SimpleControllerHandlerAdapter适配处理实现org.springframework.web.servlet.mvc.Controller的Controller -
AnnotationMethodHandlerAdapter主要是适配注解类处理器, 注解类处理器就是我们经常使用的@Controller的这类Controller Spring Boot 注解类型的Controller就是这个
DispatcherServlte 会将这三个HandlerAdapter对象存储到它的handlerAdapters这个集合属性中, 这样就完成了HandlerAdapter的注册;
-DispatcherServlte 根据 HandlerMapping 传过来的 Controller 与已经注册好了的 HandlerAdapter一一匹配, 看哪一种HandlerAdapter是支持该controller类型的
如果找到了其中一种 HandlerAdapter 是支持传过来的 Controller类型, 那么该 HandlerAdapter 会调用自己的handle方法, handle方法运用java的反射机制执行controller的具体方法来获得ModelAndView,
例如SimpleControllerHandlerAdapter是支持实现了controller接口的控制器, 如果自己写的控制器实现了controller接口, 那么SimpleControllerHandlerAdapter 就会去执行自己写控制器中的具体方法来完成请求;
ModelAndView
org.springframework.web.servlet.ModelAndView
ModelAndView 顾名思义,Model代表模型, View代表视图, 这个名字就很好地解释了该类的作用;
业务处理器调用模型层处理完用户请求后, 把结果数据存储在该类的model属性中, 把要返回的视图信息存储在该类的view属性中
public class ModelAndView {
/** View instance or view name String. */
@Nullable
private Object view;
/** Model Map. */
@Nullable
private ModelMap model;
...
ViewResolver
org.springframework.web.servlet.ViewResolver
解析从处理程序返回的实际基于字符串的基于逻辑的视图名称, 以实际的视图呈现给响应
HandlerExceptionResolver
org.springframework.web.servlet.HandlerExceptionResolver
Strategy to resolve exceptions, possibly mapping them to handlers, to HTML error views, or other targets. 解决异常的策略, 可能将它们映射到处理程序, HTML错误视图或其他目标
DispatcherServlet 处理流程
简略
1. DispatcherServlet::doService
首先请求到达 DispatcherServlet::doService 初始化一些参数, 再到 DispatcherServlet::doDispatch
org.springframework.web.servlet.DispatcherServlet#doService
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());
2. 通过HandlerMapping 获得 HandlerExecutionChain
org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
...
}3. 再通过HandlerExecutionChain的(Object)handler 找到 HandlerAdapter
org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
}4. 然后调用 HandlerAdapter::handle(适配器模式) 返回ModelAndView(逻辑视图)
org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}5. 通过 ViewResolver 解析到具体的View org.springframework.web.servlet.View
org.springframework.web.servlet.DispatcherServlet#resolveViewName
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) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
6. 调用View.render渲染结果视图
void org.springframework.web.servlet.DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception#1057
void org.springframework.web.servlet.DispatcherServlet.processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception#1118
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. // 通过 `ViewResolver` 解析到 View
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
...
//调用`View.render`渲染结果视图
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
参数是在哪解析?
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();//
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
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;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}消息转换器
org.springframework.http.converter.HttpMessageConverter
MediaType 支持 org.springframework.http.MediaType
迷之 HttpMediaTypeNotSupportedException
前端的请求头中的文本类型为Content-Type:application/x-ww-form-urlencoded 后端接口用的是@PostMapping和使用@RequestBody来接收参数
某种情况下消息转换支持该 MediaType 但是处理过成发生异常; Spring 也会抛出类似不支持的迷之异常…
例如: jackson 序列化有问题 com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference
最后的异常. HttpMediaTypeNotSupportedException: Content type ‘application/json;charset=UTF-8’ not supported
问题解决
解决 No adapter for handler
回过来解决 No adapter for handler 的问题
HandlerAdapter org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(Object handler) throws ServletException
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//Handler 是 org.yang.controller.IndexController@60415e75
if (this.handlerAdapters != null) {
/**
有3个
handlerAdapters = [
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter@219397fa,
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter@70620678,
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter@217b35b6
]
**/
for (HandlerAdapter adapter: this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}原因很简单, 无法通过 Handler 找到对应的 HandlerAdapter适配
那么通过@Controller, (@RestController 继承@Controller) 注解的应该被谁适配? 其实正确的话是被RequestMappingHandlerAdapter适配的.
原因: 一个智障的问题! @RestController的value是表示Bean名称, 不是请求路径, 应使用@RequestMapping注解配置路径!
@RestController()
@RequestMapping("/hi")
public class IndexController {
注意这时候Object handler的值, 是在getHandler(processedRequest)封装 IndexController#helloWord() 为 org.springframework.web.method.HandlerMethod的实例
HandlerExecutionChain DispatcherServlet.getHandler(HttpServletRequest request) throws Exception
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping: this.handlerMappings) {
//最终是 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 封装
//注意 这里是套了一个 HandlerExecutionChain
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
另外: RequestMappingHandlerAdapter 适配的supports 逻辑, 在其父对象 AbstractHandlerMethodAdapter 中:
boolean org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.supports(Object handler)
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}HttpMediaTypeNotSupportedException
通常情况下, 是无法找到对应的Content-Type Mine转换器
源码 DispatcherServlet
void org.springframework.web.servlet.DispatcherServlet.doService(HttpServletRequest request, HttpServletResponse response) throws Exception
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));
}
}
}
// 在Request域中存一下, 框架对象
// Make framework objects available to handlers and view objects.
//WebApplicationContext 容器?
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? SpringMVC 中的 flashMap: 在重定向时, 如果需要传递参数, 但是又不想放在地址栏中, 我们就可以通过 flashMap 来传递参数
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 {
// 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);
}
}
}
}
//TODO 后续