前言:
小伙伴们,大家好,我是狂奔の蜗牛rz,当然你们可以叫我蜗牛君,我是一个学习Java半年多时间的小菜鸟,同时还有一个伟大的梦想,那就是有朝一日,成为一个优秀的Java架构师。 这个SpringBoot基础学习系列用来记录我学习SpringBoot框架基础知识的全过程 (这个系列是参照B站狂神的SpringBoot最新教程来写的,由于是之前整理的,但当时没有发布出来,所以有些地方可能有错误,希望大家能够及时指正!) 之后我将会以一天一更的速度更新这个系列,还没有学习SpringBoot的小伙伴可以参照我的博客学习一下;当然学习过的小伙伴,也可以顺便跟我一起复习一下基础。 最后,希望能够和大家一同进步吧!加油吧!少年们!
由于SpringBoot Web开发涉及的内容比较多,所以蜗牛君打算把这部分将会分成上中下三篇博客,上篇主要分析SpringBoot开发Web的优缺点以及静态资源的配置和使用;中篇主要介绍模板引擎和MVC配置原理,下篇是项目实战,基于SpringBoot构建一个简单的员工管理系统! SpringBoot Web开发(上篇)博客链接:https://blog.csdn.net/weixin_45301250/article/details/120694674
废话不多说,让我们开始今天的学习内容吧,由于今天我们来到了SpringBoot基础学习的第五站:SpringBoot Web开发(中篇)!
5.3 模板引擎
5.3.1 回忆jsp的使用
前端交给我们的页面,是html页面;如果以前开发,需要把它转换成jsp页面
1.jsp的好处
jsp页面的好处:
- 当我们查出一些数据转发到jsp页面以后,我们可以用jsp轻松实现数据的显示和交互等
- jsp还具有强大功能,包括能写Java代码等
2.jsp的弊端
但是现在使用SpringBoot开发
- 项目是以jar形式进行打包,而不是war包
- 使用的是内嵌的Tomcat,默认不支持jsp
3.解决静态页面开发问题
- SpringBoo默认不支持jsp,如果我们直接用纯静态页面的方式,那么会给开发带来非常大的麻烦
- 为了解决这个问题,SpringBoot推荐我们使用模板引擎
4.引入模板引擎
4-1 模板引擎种类
jsp其实就是一种模板引擎,还有使用较多的freemarker,包括SpringBoot推荐使用的thymeleaf等
4-2 模板引擎核心思想
- 模板引擎虽然各式各样,但其核心思想都是基本相同的:
4-2 模板引擎作用
模板引擎作用:
比如我们要写一个页面模板,比如有些值是动态的,那么我们可以使用一些表达式
那么这些值从哪里来的呢?
- 首先我们来组装一些数据,并且把这些数据找到
- 然后把这些数据交给模板引擎,模板引擎按照数据把表达式解析,填充到指定位置
- 最后把这个数据最终生成一个我们想要看到的内容渲染出去,这就是模板引擎
不管是jsp还是其他模板引擎,都是这个核心思想;它们之间的不同点,就是语法有些不同
5.3.2 Thymeleaf模板引擎的使用
1.Thymeleaf模板引擎
1-1 什么是Thymeleaf模板引擎?
我们主要学习SpringBoot推荐使用的Thymeleaf模板引擎
- 这个模板引擎是一个高级语言的模板引擎
- 并且它的语法很简单,功能也更加强大
1-2 学习方式和网站链接
建议去Thymeleaf官网或者Spring官网学习
网站链接:
- Thymeleaf官网:https://www.thymeleaf.org/
- Thymeleaf的Github主页:https://github.com/thymeleaf/thymeleaf-spring
- Spring官方文档:https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-template-engines
2.导入资源依赖和查看依赖版本
2-1 导入thymeleaf资源依赖
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
2-2 查看thymeleaf资源依赖版本
2-3 查看ThymeleafProperties类源码
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML";
......
}
3.Thymeleaf模板引擎的使用
3-1 项目结构
3-2 编写hello.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>test</h1>
</body>
</html>
3-3 编写HelloController控制器类
package com.kuang.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
3-4 测试结果
结果:访问/hello请求,页面跳转成功!
3-5 使用结论
- 只要需要使用Thymeleaf模板引擎,只需要的导入对应的资源依赖即可
- 要将html代码放在templates文件下
4.Thymeleaf的使用升级版
4-1 修改hello.index页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
</body>
</html>
4-2 修改HelloController控制器类
package com.kuang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("msg","Hello,SpringBoot!");
return "hello";
}
}
4-3 测试结果
结果:访问/hello请求跳转页面成功!
5.3.3 Thymeleaf的基本语法
1.常用属性和数字优先级
- 所有的Thymeleaf属性都定义了数字优先级,从而确定了它们在标记中的顺序
顺序 | 参数 | 特性 |
---|
1 | th:insert th:repalce | 碎片夹杂,相当于jsp中include | 2 | th:each | 片段迭代,for循环遍历 | 3 | th:if th:unless th:switch th:case | 条件评估 | 4 | th:object th:with | 局部变量定义 | 5 | th:attr th:attrprepend th:attrappend | 常规属性修改 | 6 | th:value th:href th:src … | 特定属性修改 | 7 | th:text th:utext | 转译文本和不转译文本 | 8 | th:fragement | 片段规格 | 9 | th:remove | 碎片清除 |
这种优先机制意味着:如果属性位置反转,则上述迭代片段将会给出完全相同的结果 (尽管可读性稍差)
2.th:untext的使用
2-1 编写HelloController控制器类
package com.kuang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("msg","<h1>Hello,SpringBoot!</h1>");
return "hello";
}
}
2-2 编写hello.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
</body>
</html>
2-3 测试结果
3.th:each的使用
3-1 编写HelloController控制器
package com.kuang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("users", Arrays.asList("周树人","周星驰","周杰伦"));
return "hello";
}
}
3-2 编写hello.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr>
<h3 th:each="user:${users}" th:text="${user}"></h3>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr>
<h3 th:each="user:${users}">[[ ${user} ]]</h3>
</body>
</html>
3-3 测试结果
? 与方式一结果相同
4.标准表达语法
4-1 简单表达式
- 变量表达式:${…}
- 选择变量表达式:*{…}
- 消息表达:#{…}
- 链接URL表达式:@{…}
- 片段表达式:~{…}
4-2 文字
- 文字文本:‘one text’,‘Another one!’,…
- 号码文字:0,34,3.0,12.3,…
- 布尔文字:true,false
- 空文字:null
- 文字标记:one,sometext,main,…
4-3 文字操作
- 字符串串联:+
- 文字替换:|The name is ${name}|
4-4 算术运算
- 二元运算符:+,-,*,/,%
- 减号:(一元运算符):-
4-5 布尔运算
- 二元运算符:and,or
- 布尔否定 (一元运算符):!,not
4-6 比较和等值
- 比较:>,<,>=,<=(gt,It,ge,le)
- 等号运算符:==,!= (eq,ne)
4-7 条件运算符
- 如果-则:(if) ? (then)
- 如果-则-否则:(if) ? (then) : (else)
- 默认:(value) ?: (defaultVaule)
4-8 特殊令牌
5.Strings工具类的使用
/*
* ======================================================================
* See javadoc API for class org.thymeleaf.expression.Strings
* ======================================================================
*/
/*
* Null-safe toString()
*/
${#strings.toString(obj)} // also array*, list* and set*
/*
* Check whether a String is empty (or null). Performs a trim() operation before check
* Also works with arrays, lists or sets
*/
// 判断字符串名字是否为空
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}
/*
* Perform an 'isEmpty()' check on a string and return it if false, defaulting to
* another specified string if true.
* Also works with arrays, lists or sets
*/
${#strings.defaultString(text,default)}
${#strings.arrayDefaultString(textArr,default)}
${#strings.listDefaultString(textList,default)}
${#strings.setDefaultString(textSet,default)}
/*
* Check whether a fragment is contained in a String
* Also works with arrays, lists or sets
*/
${#strings.contains(name,'ez')} // also array*, list* and set*
${#strings.containsIgnoreCase(name,'ez')} // also array*, list* and set*
/*
* Check whether a String starts or ends with a fragment
* Also works with arrays, lists or sets
*/
${#strings.startsWith(name,'Don')} // also array*, list* and set*
${#strings.endsWith(name,endingFragment)} // also array*, list* and set*
/*
* Substring-related operations
* Also works with arrays, lists or sets
*/
${#strings.indexOf(name,frag)} // also array*, list* and set*
${#strings.substring(name,3,5)} // also array*, list* and set*
${#strings.substringAfter(name,prefix)} // also array*, list* and set*
${#strings.substringBefore(name,suffix)} // also array*, list* and set*
${#strings.replace(name,'las','ler')} // also array*, list* and set*
/*
* Append and prepend
* Also works with arrays, lists or sets
*/
${#strings.prepend(str,prefix)} // also array*, list* and set*
${#strings.append(str,suffix)} // also array*, list* and set*
/*
* Change case
* Also works with arrays, lists or sets
*/
${#strings.toUpperCase(name)} // also array*, list* and set*
${#strings.toLowerCase(name)} // also array*, list* and set*
/*
* Split and join
*/
${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}
${#strings.arraySplit(namesStr,',')} // returns String[]
${#strings.listSplit(namesStr,',')} // returns List<String>
${#strings.setSplit(namesStr,',')} // returns Set<String>
/*
* Trim
* Also works with arrays, lists or sets
*/
${#strings.trim(str)} // also array*, list* and set*
/*
* Compute length
* Also works with arrays, lists or sets
*/
${#strings.length(str)} // also array*, list* and set*
/*
* Abbreviate text making it have a maximum size of n. If text is bigger, it
* will be clipped and finished in "..."
* Also works with arrays, lists or sets
*/
${#strings.abbreviate(str,10)} // also array*, list* and set*
/*
* Convert the first character to upper-case (and vice-versa)
*/
${#strings.capitalize(str)} // also array*, list* and set*
${#strings.unCapitalize(str)} // also array*, list* and set*
/*
* Convert the first character of every word to upper-case
*/
${#strings.capitalizeWords(str)} // also array*, list* and set*
${#strings.capitalizeWords(str,delimiters)} // also array*, list* and set*
/*
* Escape the string
*/
${#strings.escapeXml(str)} // also array*, list* and set*
${#strings.escapeJava(str)} // also array*, list* and set*
${#strings.escapeJavaScript(str)} // also array*, list* and set*
${#strings.unescapeJava(str)} // also array*, list* and set*
${#strings.unescapeJavaScript(str)} // also array*, list* and set*
/*
* Null-safe comparison and concatenation
*/
${#strings.equals(first, second)}
${#strings.equalsIgnoreCase(first, second)}
${#strings.concat(values...)}
${#strings.concatReplaceNulls(nullValue, values...)}
/*
* Random
*/
${#strings.randomAlphanumeric(count)}
5.4 MVC配置原理
5.4.1 MVC的拓展配置
1. 如何实现MVC拓展配置
如果你想在保持Spring Boot MVC特性的同时,又想添加MVC的拓展配置,例如 Intercepters (拦截器),formatters (格式转换器),view Controller (视图控制器)等,可以在你的自定义类前添加一个@Configuration注解,使你的类成为WebMvcConfigurer,并且不要使用@EnableWebMvc注解
2.编写自定义配置类
package com.kuang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
}
5.4.2 分析ContentNegotiatingViewResolver类和自定义视图解析器
1.查看ContentNegotiatingViewResolver类
- 查看ContentNegotiatingViewResolver视图解析器后,发现其实现了ViewResolver接口=
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
@Nullable
private ContentNegotiationManager contentNegotiationManager;
private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();
private boolean useNotAcceptableStatusCode = false;
@Nullable
private List<View> defaultViews;
@Nullable
private List<ViewResolver> viewResolvers;
private int order = -2147483648;
private static final View NOT_ACCEPTABLE_VIEW = new View() {
@Nullable
public String getContentType() {
return null;
}
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
response.setStatus(406);
}
};
}
2.查看resolveViewName方法源码
- 接着查看ViewResolver接口后,发现其有个resolveViewName方法来解析视图逻辑名,既然ContentNegotiatingViewResolver类实现了ViewResolver接口,那么它一定重写了该方法,我们去查看其是如何进行实现的
public interface ViewResolver {
@Nullable
View resolveViewName(String var1, Locale var2) throws Exception;
}
- 再继续回到ContentNegotiatingViewResolver类中,找到它重写的resolveViewName方法,查看其重写过程
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
......
@Nullable
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);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
} else {
this.logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
}
3.查看getCandidateViews方法源码
- 查看 getCandidateViews获取获选视图方法具体内容
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
......
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
List<View> candidateViews = new ArrayList();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
Iterator var5 = this.viewResolvers.iterator();
while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
Iterator var8 = requestedMediaTypes.iterator();
while(var8.hasNext()) {
MediaType requestedMediaType = (MediaType)var8.next();
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
Iterator var11 = extensions.iterator();
while(var11.hasNext()) {
String extension = (String)var11.next();
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
......
}
4.自定义视图解析器MyViewResolver
package com.kuang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver myViewResovler() {
return new MyViewResolver();
}
public static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
5.4.3 Debug测试观察视图解析器
1.在doService方法上设置断点
2.查看debug测试结果
- Debug测试后查看控制台发现分别有this,request和response三个属性
- 点击this属性,查看其下的viewResovlers,其中的第一个是默认的内容协商视图解析器,第三个是Thymeleaf的视图解析器,然后第四个就是自定义的MyConfig配置类,其中包含自定义的视图解析器
3.测试结论
- SpringBoot在自动配置很多组件时,首先会看容器中有没有用户自己配置的,如果用户使用@Bean注解将一些类注册成组件,那就使用用户配置的,如果没有就使用自动配置的
- 如果有些组件存在多个,比如我们自定义一个视图解析器,那么SpringBoot就会将用户配置的和默认的进行组合使用
5.4.4 分析DispatcherServlet类源码和FormattingConversionService方法
1.查看doService方法源码
public class DispatcherServlet extends FrameworkServlet {
......
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
this.logRequest(request);
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap();
Enumeration attrNames = request.getAttributeNames();
label104:
while(true) {
String attrName;
do {
if (!attrNames.hasMoreElements()) {
break label104;
}
attrName = (String)attrNames.nextElement();
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.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);
}
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
try {
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
......
}
2.查看doDispatch方法
public class DispatcherServlet extends FrameworkServlet {
......
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
......
}
3.查看FormattingConversionService方法源码
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
......
@Bean
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService((new DateTimeFormatters()).dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
this.addFormatters(conversionService);
return conversionService;
}
......
}
5.4.5 使用总结
- 在SpringBoot中如此多的自动装配,其实它们的原理都是一样的
- 在通过对WebMvc的自动配置原理进行分析后,也发现其是同样的设计思想
- 因此一定要养成这样的好习惯:通过查看官方文档,然后再结合源码,来得出相应的结论,这才是学习编程的最佳方式,也是进阶高级程序员的必经之路
5.5.6 自定义视图控制器
1.编写自定义类并且重写视图控制器方法
package com.kuang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("hello");
}
}
SpringBoot官网介绍说,如果你想要进行扩展你的Spring Boot MVC,那么你可以在你的自定义类前添加一个@Configuration注解,使你的类成为WebMvcConfigurer,并且不要使用@EnableWebMvc注解
但是为什么SpringBoot官网会强调不能使用@EnableWebMvc注解呢?如果我们使用了又会造成怎样的结果呢?让我们做个测试
2.使用@EnableWebMvc注解进行测试
- 在自定义的配置类前使用@EnableWebMvc注解
package com.kuang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("hello");
}
}
- 查看@EnableWebMvc注解的源码,我们发现其导入了一个DelegatingWebMvcConfiguration(委派/授权WebMvc配置类)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
- 接着查看DelegatingWebMvcConfiguration(委派的WebMvc配置类)源码,我们发现其继承了WebMvcConfigurationSupport(WebMvc配置支持类)
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
- 然后我们再次查看WebMvcAutoConfiguration自动配置类,发现其有个条件注解@ConditionalOnMissingBean,即当WebMvcConfigurationSupport类不存在时,下面的所有的自动配置都将会失效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
3.测试结论
这时我们才突然发现SpringBoot设计的精妙之处,如果我们在自定义的配置类中使用了@EnableWebMvc注解,相当于引入WebMvcConfigurationSupport类,那么就会触发@ConditionalOnMissingBean注解中条件,即WebMvcAutoConfiguration自动配置类失效
4.使用总结
在SpringBoot中,有非常多的xxxConfiguration类帮助我们进行扩展配置,只要看到了,我们就要关注它到底为我们配置了什么
好了,今天的有关 SpringBoot基础学习之SpringBoot Web开发(中篇) 的学习就到此结束啦,欢迎小伙伴们积极学习和讨论,喜欢的可以给蜗牛君点个关注,顺便来个一键三连,我们下期见,拜拜啦!
参考视频链接:https://www.bilibili.com/video/BV1PE411i7CV(【狂神说Java】SpringBoot最新教程IDEA版通俗易懂)
|