2020-3-25

Java N_DesignPatterns 设计模式

最新文档-

spring

环境

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 typeExplanation
HandlerMappingMap 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).
HandlerAdapterHelp 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.
HandlerExceptionResolverStrategy to resolve exceptions, possibly mapping them to handlers, to HTML error views, or other targets. See Exceptions.
ViewResolverResolve 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, LocaleContextResolverResolve the Locale a client is using and possibly their time zone, in order to be able to offer internationalized views. See Locale.
ThemeResolverResolve themes your web application can use — for example, to offer personalized layouts. See Themes.
MultipartResolverAbstraction for parsing a multi-part request (for example, browser form file upload) with the help of some multipart parsing library. See Multipart Resolver.
FlashMapManagerStore 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 的两个主要实现是

  1. RequestMappingHandlerMapping(支持@RequestMapping带注解的方法)
  2. SimpleUrlHandlerMapping(维护对处理程序的URI路径模式的显式注册);

HandlerAdapter

org.springframework.web.servlet.HandlerAdapter

帮助 DispatcherServlet 调用映射到请求的处理程序, 而不管实际如何调用该处理程序; 例如, 调用带注解的控制器需要解析注解参数; HandlerAdapter的主要目的是使 DispatcherServlet 免受此类细节的影响;

-DispatcherServlte 会根据配置文件信息注册HandlerAdapter, 如果在配置文件中没有配置

那么DispatcherServlte会获取HandlerAdapter的默认配置, 会读取 DispatcherServlte.properties文件, 该文件中配置了三种HandlerAdapter:

  1. HttpRequestHandlerAdapter 适配处理实现org.springframework.web.HttpRequestHandler的Controller

  2. SimpleControllerHandlerAdapter 适配处理实现org.springframework.web.servlet.mvc.Controller的Controller

  3. 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 后续

资料

https://mp.weixin.qq.com/s/0x7_OXPDFX5BqF0jGxN2Vg