前言:
小伙伴们,大家好,我是狂奔の蜗牛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开发(中篇)!
前端交给我们的页面,是html页面;如果以前开发,需要把它转换成jsp页面
jsp页面的好处:
但是现在使用SpringBoot开发
jsp其实就是一种模板引擎,还有使用较多的freemarker,包括SpringBoot推荐使用的thymeleaf等
模板引擎作用:
比如我们要写一个页面模板,比如有些值是动态的,那么我们可以使用一些表达式
那么这些值从哪里来的呢?
不管是jsp还是其他模板引擎,都是这个核心思想;它们之间的不同点,就是语法有些不同
我们主要学习SpringBoot推荐使用的Thymeleaf模板引擎
建议去Thymeleaf官网或者Spring官网学习
网站链接:
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
//使用配置属性注解:绑定指定配置文件中的所有属性值
@ConfigurationProperties(prefix = "spring.thymeleaf")
//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"; //格式
......
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>testh1>
body>
html>
package com.kuang.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//在templates目录下的所有页面,只能通过controller来跳转
//这个需要模板应请支持:thymeleaf
//使用@RestController注解来实现Controller接口
@RestController
public class HelloController {
//真实访问路径为:http://loclahost:8080/hello
//使用@RequestMapping注解,设置请求映射路径
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
结果:访问/hello请求,页面跳转成功!
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div th:text="${msg}">div>
body>
html>
package com.kuang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
//在templates目录下的所有页面,只能通过controller来跳转
//这个需要模板应请支持:thymeleaf
//使用@Controller注解来实现Controller接口
@Controller
public class HelloController {
//真实访问路径为:http://loclahost:8080/hello
//使用@RequestMapping注解,设置请求映射路径
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("msg","Hello,SpringBoot!");
return "hello";
}
}
结果:访问/hello请求跳转页面成功!
顺序 | 参数 | 特性 |
---|---|---|
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 | 碎片清除 |
这种优先机制意味着:如果属性位置反转,则上述迭代片段将会给出完全相同的结果 (尽管可读性稍差)
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;
//在templates目录下的所有页面,只能通过controller来跳转
//使用@Controller注解来实现Controller接口
@Controller
public class HelloController {
//真实访问路径为:http://loclahost:8080/hello
//使用@RequestMapping注解,设置请求映射路径
@RequestMapping("/hello")
public String hello(Model model) {
//设置模型:设置msg属性和值,进行数据渲染
model.addAttribute("msg","Hello,SpringBoot!
");
//返回视图逻辑名给视图解析器
return "hello";
}
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div th:text="${msg}">div>
<div th:utext="${msg}">div>
body>
html>
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;
//在templates目录下的所有页面,只能通过controller来跳转
//使用@Controller注解来实现Controller接口
@Controller
public class HelloController {
//真实访问路径为:http://loclahost:8080/hello
//使用@RequestMapping注解,设置请求映射路径
@RequestMapping("/hello")
public String hello(Model model) {
//设置模型:设置msg属性和值,进行数据渲染
model.addAttribute("users", Arrays.asList("周树人","周星驰","周杰伦"));
//返回视图逻辑名给视图解析器
return "hello";
}
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
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>Titletitle>
head>
<body>
<hr>
<h3 th:each="user:${users}">[[ ${user} ]]h3>
body>
html>
与方式一结果相同
/*
* ======================================================================
* 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
${#strings.setSplit(namesStr,',')} // returns Set
/*
* 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)}
如果你想在保持Spring Boot MVC特性的同时,又想添加MVC的拓展配置,例如 Intercepters (拦截器),formatters (格式转换器),view Controller (视图控制器)等,可以在你的自定义类前添加一个@Configuration注解,使你的类成为WebMvcConfigurer,并且不要使用@EnableWebMvc注解
package com.kuang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//扩展SpringMVC
//使MyMvcConfig成为配置类
@Configuration
//实现WebMvcConfigurer配置接口
public class MyMvcConfig implements WebMvcConfigurer {
}
//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;
//NOT_ACCEPTABLE_VIEW(不可接收的视图)方法:创建新的视图对象
private static final View NOT_ACCEPTABLE_VIEW = new View() {
@Nullable
//获取内容状态
public String getContentType() {
//返回为空
return null;
}
//提交信息方法:其第一个为Map集合对象
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
//响应设置的406状态码
response.setStatus(406);
}
};
}
//视图解析器接口
public interface ViewResolver {
@Nullable
//解析视图逻辑名方法:包含两个参数,第一个是视图逻辑名,第二个是事发地点
View resolveViewName(String var1, Locale var2) throws Exception;
}
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
......
@Nullable
//解析视图逻辑名方法:有两个参数:viewName是视图逻辑名;locale:地区
public View resolveViewName(String viewName, Locale locale) throws Exception {
//获取请求参数对象(RequestAttributes):通过请求上下文容器的获取请求参数方法获取
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
//断言状态是否为attrs(请求参数),如果是就替换ServletRequestAttributes(Servlet请求参数)
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//获取请求媒介类型数组集合:通过调用getMediaTypes(获取媒介类型)的getRequest(获取请求)方法获取
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
//判断请求媒介类型是否为空
if (requestedMediaTypes != null) {
//如果不为空,调用getCandidateViews(获取候选视图),将candidateViews(候选视图)对象存入List(视图集合)中
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
//获取bestView(最佳视图)对象:通过调用getBestView(获取最佳视图)方法获取
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
//判断最佳视图(bestView)对象是否为空
if (bestView != null) {
//如果不为空,就返回最佳视图(bestView)对象
return bestView;
}
}
//获取媒介类型信息:通过判断日志是否能够Debug,并且请求媒介类型对象是否为空,如果能够Debug并且其不为空,则将请求媒介类型对象转换成字符串形式
String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
//判断它是否使用不可接收的状态码
if (this.useNotAcceptableStatusCode) {
//如果使用不可接收状态码,判断日志是否能够Debug
if (this.logger.isDebugEnabled()) {
//如果满足条件,打印出406不可接收的提示和媒介类型信息
this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
//如果不能进行Debug,返则回到不可接收视图方法
return NOT_ACCEPTABLE_VIEW;
} else {//如果没有使用不可接收的状态码
//日志打印debug信息"View remains unresolved"(视图仍然不能被解析)和媒介状态信息
this.logger.debug("View remains unresolved" + mediaTypeInfo);
//然后返回空值
return null;
}
}
}
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
......
//获取候选视图的方法:有三个参数:viewName(视图逻辑名);locale(地区);requestedMediaTypes(请求媒介类型集合),返回值类型是一个封装View视图对象的List集合
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
//获取候选视图集合对象
List<View> candidateViews = new ArrayList();
//判断viewResolver(视图解析器)是否为空
if (this.viewResolvers != null) {
//断言状态:contentNegotiationManager(内容协商管理器)对象是否为空
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
//如果不为空,则获取迭代器对象(var5):通过调用视图解析器的iterator方法获取
Iterator var5 = this.viewResolvers.iterator();
//判断迭代器中是否存在下一个值
while(var5.hasNext()) {
//如果存在,则通过强制转换获取视图解析器对象
ViewResolver viewResolver = (ViewResolver)var5.next();
//获取视图对象:通过调用视图解析器的resolveViewName(解析视图逻辑名)方法获取
View view = viewResolver.resolveViewName(viewName, locale);
//判断视图对象是否为空
if (view != null) {
//如果不为空,添加视图对象到候选视图集合中去
candidateViews.add(view);
}
//获取请求媒介类型的迭代器var8
Iterator var8 = requestedMediaTypes.iterator();
//判断请求媒介类型迭代器是否存在下一个值
while(var8.hasNext()) {
//如果存在,获取其next值,即MediaType对象
MediaType requestedMediaType = (MediaType)var8.next();
//获取扩展名(extensions)集合:通过调用contentNegotiationManager(内容协商管理器)的resolveFileExtensions(解析文件拓展)方法获取
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType); //获取拓展名集合的迭代器
Iterator var11 = extensions.iterator();
//判断拓展名集合的迭代器是否存在下一个值
while(var11.hasNext()) {
//如果存在next值,通过强制转换获取扩展名
String extension = (String)var11.next();
//获取带有拓展名的视图逻辑名
String viewNameWithExtension = viewName + '.' + extension;
//调用视图解析器(viewResolver)对象的解析视图逻辑名(resolveViewName)方法来获取视图对象(view):两个参数,第一参数值为带有拓展名的视图逻辑名,第二参数为是事发地点
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
//判断视图是否空
if (view != null) {
//如果不为空,将其添加到候选视图集合中去
candidateViews.add(view);
}
}
}
}
}
//判断默认的视图集合是否为空
if (!CollectionUtils.isEmpty(this.defaultViews)) {
//如果默认视图集合不为空,就将它们加入到候选视图集合中去
candidateViews.addAll(this.defaultViews);
}
//如果默认视图集合为空,直接返回候选视图集合即可
return candidateViews;
}
......
}
package com.kuang.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//扩展SpringMVC
//如果你想DIY一些定制化功能,只要使用@Configuration组件,然后将它交给SpringBoot,SpringBoot就会帮我们自动装配
//使MyMvcConfig成为配置类
@Configuration
//实现WebMvcConfigurer配置接口
public class MyMvcConfig implements WebMvcConfigurer {
//将myViewResovler方法当做组件装入到IOC容器中
@Bean
public ViewResolver myViewResovler() {
return new MyViewResolver();
}
//ContentNegotiatingViewResolver实现了视图解析器接口的类,我们就可以把它看做是一个视图解析器
//自定义一个实现ViewResolver接口的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
//DispatcherServlet:前置控制器/请求分发器
public class DispatcherServlet extends FrameworkServlet {
......
//doService执行服务方法
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 {
//调用doDispatch方法
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
......
}
//DispatcherServlet:前置控制器/请求分发器
public class DispatcherServlet extends FrameworkServlet {
......
//未响应都会经过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 {
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);
}
}
}
......
}
@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 })
//WebMVC自动配置类
public class WebMvcAutoConfiguration {
......
@Bean
//mvc格式转换服务方法:返回值为格式转换服务(FormattingConversionService)
public FormattingConversionService mvcConversionService() {
//获取Format(格式)对象:通过调用mvc属性类(从配置文件中获取)的获取格式方法
Format format = this.mvcProperties.getFormat();
//创建WebConversionService(Web转换服务)类实例化对象
WebConversionService conversionService = new WebConversionService((new DateTimeFormatters()).dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
//将WebConversionService(Web转换服务)类实例化对象添加到格式中去
this.addFormatters(conversionService);
//返回web转换服务实例化对象
return conversionService;
}
......
}
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;
//扩展SpringMVC
//使用@Configuration注解让MyMvcConfig成为配置类
@Configuration
//实现WebMvcConfigurer配置接口
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
//重写添加视图控制器(addViewControllers)方法
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("hello");
}
}
SpringBoot官网介绍说,如果你想要进行扩展你的Spring Boot MVC,那么你可以在你的自定义类前添加一个@Configuration注解,使你的类成为WebMvcConfigurer,并且不要使用@EnableWebMvc注解
但是为什么SpringBoot官网会强调不能使用@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;
//扩展SpringMVC
//使用@Configuration注解让MyMvcConfig成为配置类
@Configuration
//使用@EnableWebMvc注解:作用是导入DelegatingWebMvcConfiguration(委派WebMvc配置类),从IOC容器中获取所有的WebMvc配置类
@EnableWebMvc
//实现WebMvcConfigurer配置接口
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
//重写添加视图控制器(addViewControllers)方法
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("hello");
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
//导入DelegatingWebMvcConfiguration(委派/授权WebMvc配置类)
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
@Configuration(proxyBeanMethods = false)
//DelegatingWebMvcConfiguration(委派的WebMvc配置类)继承了WebMvcConfigurationSupport(WebMvc配置支持类)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//获取WebMvcConfigurerComposite(WebMvc配置组合)对象
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//DelegatingWebMvcConfiguration类的无参构造
public DelegatingWebMvcConfiguration() {
}
//自动装配setConfigurers(设置配置类)方法
@Autowired(required = false)
//设置配置类方法有一个参数:一个WebMvc配置类数组集合
public void setConfigurers(List<WebMvcConfigurer> configurers) {
//判断配置类是否为空
if (!CollectionUtils.isEmpty(configurers)) {
//如果不为空,将配置类添加到WebMvc配置组合对象中去
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//当WebMvcConfigurationSupport类不存在时,下面的所有的自动配置都将会失效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
//WebMVC自动配置类
public class WebMvcAutoConfiguration {
}
这时我们才突然发现SpringBoot设计的精妙之处,如果我们在自定义的配置类中使用了@EnableWebMvc注解,相当于引入WebMvcConfigurationSupport类,那么就会触发@ConditionalOnMissingBean注解中条件,即WebMvcAutoConfiguration自动配置类失效
在SpringBoot中,有非常多的xxxConfiguration类帮助我们进行扩展配置,只要看到了,我们就要关注它到底为我们配置了什么
好了,今天的有关 SpringBoot基础学习之SpringBoot Web开发(中篇) 的学习就到此结束啦,欢迎小伙伴们积极学习和讨论,喜欢的可以给蜗牛君点个关注,顺便来个一键三连,我们下期见,拜拜啦!
参考视频链接:https://www.bilibili.com/video/BV1PE411i7CV(【狂神说Java】SpringBoot最新教程IDEA版通俗易懂)