全套SpringBoot2.x入门到项目实战课程系列(第5章 Spring Boot 的Web开发)

第5章 Spring Boot 的Web开发

5.1 Web开发支持

  • Spring Boot 为 Web 开发提供了 spring-boot-starter-web 启动器作为基本支持,为我们提供了嵌入的Tomcat 以及 Spring MVC 的依赖支持。(参考:pom.xml)
  • 也提供了很多不同场景的自动配置类,让我们只需要在配置文件中指定少量的配置即可启动项目。自动配置类存储在 spring-boot-autoconfigure.jarorg.springframework.boot.autoconfigure 包下。
    在这里插入图片描述
  • 思考自动配置原理: 自动配置场景 SpringBoot 帮我们配置了什么?是否修改?能修改哪些配置?是否可以
    扩展?……
  • 自动配置类举例:
    • 文件名可以看出
      xxxxAutoConfiguration :向容器中添加自动配置组件
      xxxxProperties :使用自动配置类 来封装 配置文件的内容
    • SpringMVC配置 : WebMvcAutoConfiguration 和 WebMvcProperties内
      在这里插入图片描述
    • 内嵌 Servlet 容器 : ServletWebServerFactoryAutoConfiguration 和 ServerProperties
      在这里插入图片描述
    • 上传文件的属性 :MultipartAutoConfiguration 和 MultipartProperties
      在这里插入图片描述
    • JDBC : DataSourceAutoConfiguration 和 DataSourceProperties
      在这里插入图片描述

5.2 静态资源的映射规则

  • 对静态资源的映射规则, 可通过分析 WebMvcAutoConfiguration 自动配置类得到

5.2.1 webjars 资源映射

  • 在 WebMvcAuotConfiguration.addResourceHandlers() 分析webjars 资源映射
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    	if (!this.resourceProperties.isAddMappings()) {
    		logger.debug("Default resource handling disabled");
    	} else {
    		Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    		CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    	if (!registry.hasMappingForPattern("/webjars/**")) {
    		//收到 /webjars/**请求后 ,会去classpath:/META-INF/resources/webjars/ 查找资源文件
    		this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/METAINF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
    	}
    	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    	if (!registry.hasMappingForPattern(staticPathPattern)) {
    		this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
    	}
    }
    
    1. 所有 /webjars/** 请求,都去 classpath:/META-INF/resources/webjars/ 目录找对应资源文件
    2. webjars:以jar包的方式引入静态资源
      webjars官网: https://www.webjars.org/
    3. 在官网打开资源文件的依赖配置信息,然后粘贴到 pom.xml 中
      <!--引入 jquery webjars-->
      <dependency>
      	<groupId>org.webjars</groupId>
      	<artifactId>jquery</artifactId>
      	<version>3.3.1</version>
      </dependency>
      
    4. 访问 localhost:8080/webjars/jquery/3.3.1/jquery.js 会在下面路径 中查找
      在这里插入图片描述

5.2.2 其他静态资源映射

  • 在 WebMvcAuotConfiguration.addResourceHandlers() 分析 访问其他资源映射
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    	if (!this.resourceProperties.isAddMappings()) {
    		logger.debug("Default resource handling disabled");
    	} else {
    		Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    		CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    	if (!registry.hasMappingForPattern("/webjars/**")) {
    		this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/METAINF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
    	}
    	// 接收/**
    	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    	if (!registry.hasMappingForPattern(staticPathPattern)) {
    		this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
    	}
    }
    
  • staticPathPattern 处理其他访问的静态路径, 从 WebMVCProperties 构造器中获取到 /**
    public WebMvcProperties() {
    	this.localeResolver = WebMvcProperties.LocaleResolver.ACCEPT_HEADER;
    	this.dispatchTraceRequest = false;
    	this.dispatchOptionsRequest = true;
    	this.ignoreDefaultModelOnRedirect = true;
    	this.throwExceptionIfNoHandlerFound = false;
    	this.logResolvedException = false;
    	=======接收 /**请求
    	this.staticPathPattern = "/**";
    	this.async = new WebMvcProperties.Async();
    	this.servlet = new WebMvcProperties.Servlet();
    	this.view = new WebMvcProperties.View();
    	this.contentnegotiation = new WebMvcProperties.Contentnegotiation();
    	this.pathmatch = new WebMvcProperties.Pathmatch();
    }
    
  • ResourceProperties 根据请求查找资源文件, 从以下 四个路径 中 查找( 静态资源目录 )
    @ConfigurationProperties(prefix = "spring.resources",ignoreUnknownFields = false)
    public class ResourceProperties {
    	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/",
    	"classpath:/public/"};
    	private String[] staticLocations;
    	private boolean addMappings;
    	private final ResourceProperties.Chain chain;
    	private final ResourceProperties.Cache cache;
    
    "classpath:/META-INF/resources/",
    "classpath:/resources/",
    "classpath:/static/",
    "classpath:/public/"
    
  • 总结:
    • 当接受到 /** 请求访问资源时, 会被映射到下面4个 类路径下的静态资源目录中 查找
      classpath:/META-INF/resources/
      classpath:/resources/
      classpath:/static/
      classpath:/public/
      
    • 访问 localhost:8080/style.css 会在上面四个静态资源路径 中查找文件

5.2.3 欢迎页映射

  • 在 WebMvcAuotConfiguration.welcomePageHandlerMapping() 分析 欢迎页映射
    @Bean
    public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
    	return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(
    		applicationContext), applicationContext
    		=======查找欢迎页========
    		, this.getWelcomePage()
    		, this.mvcProperties.getStaticPathPattern());
    }
    
  • getWelcomePage() 方法获取 欢迎页面 可存储路径
    private Optional<Resource> getWelcomePage() {
    	String[] locations =
    	===2. 上面说的4个静态资源路径加上 "/" 路径
    	getResourceLocations(
    	=====1. 获取上面说的4个静态资源路径
    	this.resourceProperties.getStaticLocations());
    	==================================在上面路径下查找 index.html 页面
    	return Arrays.stream(locations).map(this::getIndexHtml).
    	filter(this::isReadable).findFirst();
    }
    // 上面获取的路径中查找 index.html 页面
    private Resource getIndexHtml(String location) {
    	return this.resourceLoader.getResource(location + "index.html");
    }
    
  • 分析后, 会从 4个静态资源目录 + 根路径 / 中 查找 index.html 页面
    classpath:/META-INF/resources/
    classpath:/resources/
    classpath:/static/
    classpath:/public/
    /: 当前项目根路径下
    
  • 会在 静态资源目录下 与 根路径查找 (按该顺序) index.html页面; 收到 “/**” 请求映射
  • 访问 localhost:8080/ 会在上面5个目录中查找 index.html 页面(因为/也属于 /** )

5.2.4 图标映射

  • Spring Boot 会在静态资源目录下 与 根路径(按该顺序) 查找 faicon.ico 页面;
    如果存在这样的文件,Spring Boot 会自动将其设置为应用图标。
    classpath:/META-INF/resources/
    classpath:/resources/
    classpath:/static/
    classpath:/public/
    /: 当前项目根路径下
    

5.3 Thymeleaf 模板引擎

Spring Boot 官方不推荐使用JSP,因为内嵌的 Tomcat 、Jetty 容器不支持以 jar 形式运行 JSP。Spring Boot中提供了大量模板引擎,包含 Freemarker、Mastache、Thymeleaf 等。 而 Spring Boot 官方推荐使用Thymeleaf 作为模板引擎, 因为 Thymeleaf 提供了完美的 SpringMVC 的支持。
在这里插入图片描述

5.3.1 引入 Thymeleaf

  • pom.xml 加入 Thymeleaf 启动器
    <!-- thymeleaf 模板启动器 -->
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    

5.3.2 使用 Thymeleaf

  • 模板文件放在哪里 ?
    @ConfigurationProperties( prefix = "spring.thymeleaf" )
    public class ThymeleafProperties {
    	private static final Charset DEFAULT_ENCODING;
    	public static final String DEFAULT_PREFIX = "classpath:/classpath:/templates/";
    	public static final String DEFAULT_SUFFIX = ".html";
    
    • 通过上面分析发现, 将 HTML 页面放到 classpath:/templates/ 目录下, Thymeleaf 就能自动渲染
      @RequestMapping("/execute")
      public String execute(Map<String, Object> map) {
      	map.put("name", "梦学谷");
      	// classpath:/templates/success.html
      	return "success";
      }
      
    • 发送 http://localhost:8080/execute 后, 通过上面代码转到 classpath:/templates/success.html
  • 导入 Thymeleaf 的名称空间
    在 html 页面加上以下名称空间, 使用 Thymeleaf 时就有语法提示。
    <html xmlns:th="http://www.thymeleaf.org">
    
  • 演示 Thymeleaf 语法
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    	<meta charset="UTF-8">
    	<title>hello</title>
    </head>
    <body>
    	<h2 >成功</h2>
    	<!--th:text 设置p标签的标签体内容-->
    	<p th:text="${name}">这里显示名字</p>
    </body>
    </html>
    

5.3.3 Thymeleaf 语法

5.3.3.1 常用属性
  • 参考 Thymeleaf 官方文档 10 Attribute Precedence
    在这里插入图片描述
5.3.3.2 标准表达式语法
  • 参考 Thymeleaf 官方文档 4 Standard Expression Syntax
    一、Simple expressions(表达式语法)
    	1. Variable Expressions(变量表达式): ${...} (参考: 4.2 Variables)
    		1)、获取变量值;使用OGNL表达式;
    		2)、获取对象的属性, 调用方法
    		3)、使用内置的基本对象:
    			#ctx : the context object.(当前上下文对象)
    			#vars: the context variables.(当前上下文里的变量)
    			#locale : the context locale. (当前上下文里的区域信息)
    			下面是Web环境下的隐式对象
    			#request : (only in Web Contexts) the HttpServletRequest object.
    			#response : (only in Web Contexts) the HttpServletResponse object.
    			#session : (only in Web Contexts) the HttpSession object.
    			#servletContext : (only in Web Contexts) the ServletContext object.
    			示例: ${session.foo} (用法参考: 18 Appendix A: Expression Basic Objects)
    		4)、使用内置的工具对象:(用法参考: 19 Appendix B: Expression Utility Objects)
    			#execInfo : information about the template being processed.
    			#messages : methods for obtaining externalized messages inside variables
    			expressions, in the same way as they would be obtained using #{} syntax.
    			#uris : methods for escaping parts of URLs/URIs
    			#conversions : methods for executing the configured conversion service
    			(if any).
    			#dates : methods for java.util.Date objects: formatting, component
    			extraction, etc.
    			#calendars : analogous to #dates , but for java.util.Calendar
    			objects.
    			#numbers : methods for formatting numeric objects.
    			#strings : methods for String objects: contains, startsWith,
    			prepending/appending, etc.
    			#objects : methods for objects in general.
    			#bools : methods for boolean evaluation.
    			#arrays : methods for arrays.
    			#lists : methods for lists.
    			#sets : methods for sets.
    			#maps : methods for maps.
    			#aggregates : methods for creating aggregates on arrays or collections.
    			#ids : methods for dealing with id attributes that might be repeated
    			(for example, as a result of an iteration).
    	2. Selection Variable Expressions(选择表达式): *{...}
    	(参考:4.3 Expressions on selections)
    		1)、和${}在功能上是一样, 额外新增:配合 th:object 使用
    			<div th:object="${session.user}">
    				省得每次写${session.user.firstName}, 直接取出对象,然后写对象名即可
    				<p>Name: <span th:text="*{firstName}">Sebastian</span> </p>
    				<p>Email: <span th:text="*{email}">Saturn</span> </p>
    			</div>
    	3. Message Expressions(获取国际化内容): #{...} (参考:4.1 Messages)
    	4. Link URL Expressions(定义URL): @{...} (参考:4.4 Link URLs)
    	5. Fragment Expressions(片段引用表达式): ~{...} (参考:4.5 Fragments)
    		<div th:insert="~{commons :: main}">...</div>
    二、Literals(字面量) (参考: 4.6 Literals)
    	1. Text literals: 'one text' , 'Another one!' ,2. Number literals: 0 , 34 , 3.0 , 12.3 ,3. Boolean literals: true , false
    	4. Null literal: null
    	5. Literal tokens: one , sometext , main ,…
    三、Text operations(文本操作) (参考: 4.7 Appending texts)
    	1. String concatenation: +
    	2. Literal substitutions: |The name is ${name}|
    四、Arithmetic operations(数学运算) (参考: 4.9 Arithmetic operations)
    	1. Binary operators: + , - , * , / , %
    	2. Minus sign (unary operator): -
    五、Boolean operations(布尔运算)
    	1. Binary operators: and , or
    	2. Boolean negation (unary operator): ! , not
    六、Comparisons and equality(比较运算) (参考: 4.10 Comparators and Equality)
    	1. Comparators: > , < , >= , <= ( gt , lt , ge , le )
    	2. Equality operators: == , != ( eq , ne )
    七、Conditional operators(条件表达式;三元运算符) (参考: 4.11 Conditional expressions)
    	1. If-then: (if) ? (then)
    	2. If-then-else: (if) ? (then) : (else)
    	3. Default: (value) ?: (defaultvalue)
    八、Special tokens(特殊操作) (参考: 4.13 The No-Operation token)
    	1. No-Operation: _
    

5.3.4 实例代码演示

5.3.4.1 声明与引入公共片段
<!--header.html-->
<body>
	<!--声明公共片段-->
	<!-- 方式1:-->
	<div th:fragment="header_common">
	这是th:fragment声明公共片段
	</div>
	<!-- 方式2:选择器写法-->
	<div id="header_common_id">
	这是id选择器声明公共片段
	</div>
</body>

<!-- success.html 引入头部公共片段 -->
<!--方式1:
header : 公共片段所在模板的文件名
header_common :声明代码片段名 -->
<div th:replace="header :: header_common"></div>
<!--方式2:选择器写法
header : 公共片段所在模板的文件名
#header_common_id: 声明代码片的id值
-->
<div th:replace="header :: #header_common_id"></div>
<!--
th:insert 和 th:replace的区别
th:insert和th:replace都可以引入片段,两者的区别在于
th:insert: 保留引入时使用的标签
th:replace:不保留引入时使用的标签, 将声明片段直接覆盖当前引用标签
-->
<h2 th:insert="header :: #header_common_id"></h2>

练习:将项目中的 公共模块抽取出来到 public.html 中

5.3.4.2 迭代th:each
  • 常用迭代方式
    • HelloController
      @RequestMapping("/study")
      public String study(Map<String, Object> map, HttpServletRequest request) {
      	List<User> userList = new ArrayList<>();
      	userList.add(new User("小梦", 1));
      	userList.add(new User("小李", 2));
      	userList.add(new User("小张", 1));
      	map.put("userList", userList);
      	return "study";
      }
      
    • study.html
      <table border="1px">
      	<tr>
      		<th>姓名</th>
      	</tr>
      	<!--方式1: -->
      	<tr th:each="user : ${userList}">
      		<!--每次迭代都会生成一个当前标签-->
      		<td th:text="${user}">mengxuegu</td>
      	</tr>
      </table>
      <hr/>
      <ul>
      	<!--方式2:-->
      	<!--作用在同一个标签上, 每次迭代生成一个当前标签-->
      	<li th:each="user : ${userList}" th:text="${user}"></li>
      </ul>
      
    • 获取迭代状态
      <table border="1px">
      	<tr>
      		<th>编号</th>
      		<th>姓名</th>
      		<th>总数</th>
      		<th>偶数/奇数</th>
      		<th>第一个元素</th>
      		<th>最后一个元素</th>
      	</tr>
      	<!--
      		user : 第1个值,代表每次迭代出对象,名字任意取
      		iterStat : 第2个值,代表每次迭代器内置对象, 名字任意取, 并有如下属性:
      		index : 当前迭代下标 0 开始
      		count : 当前迭代下标 1 开始
      		size : 获取总记录数
      		current : 当前迭代出的对象
      		even/odd : 当前迭代是偶数还是奇数 (1开始算,返回布尔值)
      		first : 当前是否为第一个元素
      		last : 当前是否为最后一个元素
      	-->
      	<tr th:each="user, iterStat : ${userList}">
      		<td th:text="${iterStat.count}">0</td>
      		<td th:text="${user.username}">mengxuegu</td>
      		<td th:text="${user.gender == 1 ? '' : ''}">未知</td>
      		<td th:text="${iterStat.size}">0</td>
      		<td th:text="${iterStat.even}? '偶数' : '奇数'"></td>
      		<td th:text="${iterStat.first}"></td>
      		<td th:text="${iterStat.last}"></td>
      	</tr>
      </table>
      
    • 练习 : 供应商管理 查询页面
5.3.4.3 条件判断
  • th:if 不仅判断返回为 true 的表达式,还判断一些特殊的表达式。
    • 如果值不是Null, 以下情况均返回 true:
      • 如果值是boolean类型并且值为true.
      • 如果值是数值类型并且值不为0.
      • 如果值是字符类型并且值不为空.
      • 如果值是字符串并且内容不为 “false” , “off” 或者 “no” .
      • 如果值不是上述类型也返回true.
    • 如果值是NULL, 则返回false
      <hr/>
      下面加not
      <h3 th:if="not ${#lists.isEmpty(userList)}">th:if判断,如果此文字显示说明有值</h3>
      <h3 th:unless="${#lists.isEmpty(userList)}">th:unless判断,如果此文字显示说明有值</h3>
      
  • th:unless 与 th:if 作用正好相反。
  • th:swith th:case
    @RequestMapping("/study")
    public String study(Map<String, Object> map, HttpServletRequest request) {
    	List<User> userList = new ArrayList<>();
    	userList.add(new User("小梦", 1));
    	userList.add(new User("小李", 2));
    	userList.add(new User("小张", 1));
    	map.put("userList", userList);
    	// 1女, 2男
    	map.put("sex", 1);
    	map.put("man", 2);
    	return "study";
    }
    
    <div th:switch="${sex}">
    	<!--1女, 2男-->
    	<p th:case="1"></p>
    	<!--判断sex的值和下面取出man的值是否相等,相等则显示-->
    	<p th:case="${man}"></p>
    	<!--如果值都不在上述case里,则th:case="*"语句生效。-->
    	<p th:case="*">未知</p>
    </div>
    
5.3.4.4 显示标签体内容
  • th:text 转义特殊字符, 即 h1标签以文本显示出来
  • th:utext 不转义特殊字符, 即 h1 标签展现出本来效果
    @RequestMapping("/study")
    public String study(Map<String, Object> map, HttpServletRequest request) {
    	List<User> userList = new ArrayList<>();
    	userList.add(new User("小梦", 1));
    	userList.add(new User("小李", 2));
    	userList.add(new User("小张", 1));
    	map.put("userList", userList);
    	// 1女, 2男
    	map.put("sex", 1);
    	map.put("man", 2);
    	// th:text th:utext
    	map.put("desc", "欢迎来到<h1>梦学谷<h1>");
    	return "study";
    }
    
    <hr/>
    <div th:text="${desc}"> </div>
    <div th:utext="${desc}"> </div>
    
  • 补充:Thymeleaf 行内表达式双中括号: [[表达式]] (就是不在标签上使用属性,参考12 Inlining)
    <input type="checkbox" /> [[${desc}]]
    <p>Hello, [[${desc}]] 。。。</p>
    
5.3.4.5 th:object 直接取出对象
  • 使用th:object 直接取出对象,然后写对象里的属性名即可获取属性值
    @RequestMapping("/study")
    public String study(Map<String, Object> map, HttpServletRequest request) {
    	List<User> userList = new ArrayList<>();
    	userList.add(new User("小梦", 1));
    	userList.add(new User("小李", 2));
    	userList.add(new User("小张", 1));
    	map.put("userList", userList);
    	// 1女, 2男
    	map.put("sex", 1);
    	map.put("man", 2);
    	// th:text th:utext
    	map.put("desc", "欢迎来到<h1>梦学谷<h1>");
    	request.getSession().setAttribute("user", new User("小不点", 2));
    	return "study";
    }
    
    <!--使用th:object 直接取出对象,然后写对象里的属性名即可获取属性值-->
    <div th:object="${session.user}">
    	<p>
    		姓名:<span th:text="*{username}">xxxx</span>
    	</p>
    	<p>
    		性别:<span th:text="*{gender == 1 ? '' : ''}">xxxx</span>
    	</p>
    </div>
    

5.4 SpringBoot 热部署

  • 默认情况下, 在开发中我们修改一个项目文件后,想看到效果不得不重启应用,这会导致浪费大量时间 ,我们希望不重启应用的情况下,程序可以自动部署(热部署)。
  • 如何能实现热部署?
    1. 关于模板引擎
      在 Spring Boot 开发环境下禁用模板缓存
      #开发环境下关闭thymeleaf模板缓存,thymeleaf默认是开启状态
      spring.thymeleaf.cache=false
      
    2. 添加 Spring Boot Devtools 热部署依赖
      <!--热部署-->
      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-devtools</artifactId>
      </dependency>
      
    3. Intellij IEDA和Eclipse不同,Intellij IDEA必须做一些小调整:
      • 在 Eclipse 中,修改文件后要手动进行保存,它就会自动编译,就触发热部署现象。
      • 在Intellij IEDA 中,修改文件后都是自动保存,默认不会自动编译文件,需要手动编译按 Ctrl + F9 (推荐使用)或 Build -> Build Project ;或者进行以下设置才会自动编译(效果不明显)(File -> Settings -> Build, Execution, Deployment -> Compiler -> 勾选 Build project automatically)
        在这里插入图片描述

5.5 分析 SpringMVC 自动配置

Spring Boot 为 Spring MVC 提供了适用于多数应用的自动配置功能( WebMvcAutoConfiguration)。
在Spring默认基础上,自动配置添加了以下特性:

  • 引入 ContentNegotiatingViewResolver 和 BeanNameViewResolver beans.
    • 自动配置了视图解析器ViewResolver(根据方法返回值获取视图对象View,视图对象决定如何渲染?重定向Or 转发)
    • ContentNegotiatingViewResolver : 组合所有的视图解析器的(通过源码可分析出)
      public class ContentNegotiatingViewResolver
      	//146
      	public View resolveViewName(String viewName, Locale locale) throws Exception {
      		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
      		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
      		List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
      		if (requestedMediaTypes != null) {
      			//选择所有候选的视图对象
      			List<View> candidateViews = this.getCandidateViews(viewName, locale,requestedMediaTypes);
      			//从候选中选择最合适的视图对象
      			View bestView = this.getBestView(candidateViews, requestedMediaTypes,attrs);
      	
      	//存入所有视图解析器
      	private List<ViewResolver> viewResolvers;
      	107
      	protected void initServletContext(ServletContext servletContext) {
      		Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
      		//从容器中获取所有的视图解析器
      		this.obtainApplicationContext(), ViewResolver.class).values();
      
    • 自定义视图解析器:可以@Bean向容器中添加一个我们自定义的视图解析器,即可被容器管理使用
      @Bean
      public ViewResolver myViewResolver () {
      	return new MyViewResolver();
      }
      private class MyViewResolver implements ViewResolver {
      	@Override
      	public View resolveViewName(String s, Locale locale) throws Exception {
      		return null;
      	}
      }
      // DispatcherServlet.doDispatch 断点后,发送任意请求,可查看已被容器自动管理了
      
    • 自动注册 Converter , GenericConverter , and Formatter beans。
      • Converter :转换器; 如: 文本类型转换目标类型, true 转 boolean类型
      • GenericConverter :转换器,Spring内部在注册时,会将Converter先转换为GenericConverter之后,再统一对GenericConverter注册。
      • Formatter : 格式化器; 如: 2017/12/17 格式化 Date类型
        @Bean
        public FormattingConversionService mvcConversionService() {
        	//传入日期格式, spring.mvc.date-format配置日期格式
        	WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
        	this.addFormatters(conversionService);
        	return conversionService;
        }
        //将格式化器添加容器中
        protected void addFormatters(FormatterRegistry registry) {
        	this.configurers.addFormatters(registry);
        }
        
    • 对 HttpMessageConverters 的支持。
      • SpringMVC 用它来转换Http请求和响应的;User _json User _xml
      • 可以通过@Bean向容器中添加一个我们自定义HttpMessageConverters ,即可被容器管理使用
    • 自动注册 MessageCodesResolver 。
      • 定义错误代码生成规则
    • 自动注册 ConfigurableWebBindingInitializer 。
      • 初始化所有 Web数据绑定器 对象, 比如 请求数据 ——》JavaBean
    • 对静态资源的支持,包括对 Webjars 的支持。
    • 对静态首页 index.html 的支持。
    • 对自定义Favicon 图标的支持。

如果想保留 Spring Boot MVC的特性,而且还想扩展新的功能(拦截器, 格式化器, 视图控制器等),你可以在你自
定义的 WebMvcConfigurer 类上增加 @Configuration 注解。
如果你想全面控制SpringMVC(也就是不使用默认配置功能), 你在自定义的Web配置类上添加
@Configuration 和 @EnableWebMvc 注解。

5.6 扩展 SpringMVC 功能

  • 扩展一个视图解析器功能
    <mvc:view-controller path="/mengxuegu" view-name="success"/>
    <mvc:interceptors>
    	<mvc:interceptor>
    		<mvc:mapping path="/hello"/>
    		<bean></bean>
    	</mvc:interceptor>
    </mvc:interceptors>
    
  • 如果想保留 Spring Boot MVC的特性,而且还想扩展新的功能(拦截器, 格式化器, 视图控制器等),你可以在你自定义的 WebMvcConfigurer 类上增加 @Configuration 注解。
    自定义配置类保留了所有的自动配置, 也能用我们扩展的功能
    package com.mengxuegu.springboot.config;
    ......
    /**
    * @Auther: www.mengxuegu.com
    */
    @Configuration
    public class MySpringMvcConfigurer implements WebMvcConfigurer{
    	@Override
    	public void addViewControllers(ViewControllerRegistry registry) {
    		// super.addViewControllers(registry);
    		//发送 /mengxuegu 请求来到 success.html
    		registry.addViewController("/mengxuegu").setViewName("success");
    	}
    }
    

原理:

  1. 自定义WebMvcConfigurer自动配置时会导入;
    @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    导入EnableWebMvcConfiguration.class
    @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer,
    ResourceLoaderAware {
    
  2. EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration
    @Configuration
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
    
  3. 分析 DelegatingWebMvcConfiguration , 会将所有web配置组件加到WebMvcConfigurerComposite中,
    @Configuration
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    	//存储所有的mvc配置类组件
    	private final WebMvcConfigurerComposite configurers =
    	new WebMvcConfigurerComposite();
    	@Autowired( required = false )
    	public void setConfigurers(List<WebMvcConfigurer> configurers) {
    		if (!CollectionUtils.isEmpty(configurers)) {
    			this.configurers.addWebMvcConfigurers(configurers);
    			/*
    			一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
    			public void addViewControllers(ViewControllerRegistry registry) {
    				Iterator var2 = this.delegates.iterator();
    				while(var2.hasNext()) {
    					WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
    					delegate.addViewControllers(registry);
    				}
    			}
    			*/
    		}
    	}
    
  4. 保留原来的配置类,也添加了新的配置类,所有的WebMvcConfigurer都会一起起作用
  5. 效果:SpringMVC的自动配置和我们的扩展配置都会起作用;

5.7 全面控制 SpringMVC

如果你想全面控制SpringMVC(SpringBoot对SpringMVC的自动配置都废弃), 在自定义的Web配置类上添加@Configuration 和 @EnableWebMvc 注解。

/**
* @Auther: www.mengxuegu.com
*/
@EnableWebMvc
@Configuration
public class MySpringMvcConfigurer implements WebMvcConfigurer{
	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		// super.addViewControllers(registry);
		//发送 /mengxuegu 请求来到 success.html
		registry.addViewController("/mengxuegu").setViewName("success");
	}
}

原理: 为什么添加 @EnableWebMvc 自动配置就失效了?

  1. @EnableWebMvc 的核心
    @Import(DelegatingWebMvcConfiguration.class)
    public @interface EnableWebMvc {
    
  2. 先记住继承了WebMvcConfigurationSupport类
    @Configuration
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
  3. 而在 WebMvcAutoConfiguration 上使用了
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    容器中没有这个组件的时候,这个自动配置类才生效
    @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
    @AutoConfigureOrder(-2147483638)
    @AutoConfigureAfter({DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class})
    public class WebMvcAutoConfiguration {
    
    **@ConditionalOnMissingBean 表示的是没有WebMvcConfigurationSupport这个组件,**
    **WebMvcAutoConfiguration自动配置类才会生效.**
    
  4. 相反 @EnableWebMvc 将 WebMvcConfigurationSupport 组件导入进来, 使得
    WebMvcAutoConfiguration就失效了
  5. WebMvcConfigurationSupport 只是SpringMVC最基本的功能;

5.8 总结 SpringMVC 配置

  • 在Spring Boot中自已配置组件的时候,先看容器中有没有公司自已配置的(@Bean、@Component),如果有就用公司自已配置的; 如果没有,才自动配置.
  • 在Spring Boot中会有非常多的xxxConfigurer帮助我们进行扩展配置.
  • 在Spring Boot中会有很多的xxxCustomizer帮助我们进行定制配置.

My GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值