封面画师:adsuger 封面ID:78096650
0. 前沿
创建一个SpringBoot Web项目的步骤:
创建一个SpringBoot应用,选择我们需要的模块,SpringBoot会自动把我们选择的模块配置好。
在配置文件(properties或yml)中手动进行配置
编写业务代码!完事!✌️
至于SpringBoot帮我们配置了什么?我们又该怎么在配置文件中编写自己的配置?
这些问题都可以在 SpringBoot原理 一文中查看!
注意:本文暂不涉及SpringBoot与数据库的使用!
1. 静态资源
在一个Web项目中,会有很多的静态资源,比如:.js文件、.css文件、或者一些图片,那么这些静态资源在SpringBoot 的Web项目中应该怎么处理呢?
要进行Web项目的编写,就需要用到Spring MVC。在SpringBoot中,Spring MVC的配置都在配置类 WebMvcAutoConfiguration 中,我们前往依赖中,找到SpringBoot自动配置的依赖(spring-boot-autoconfigure),打开META-INF目录下的spring.factories文件(这个文件已经在SpringBoot原理 一文中详细介绍了 )。
在spring.factories文件中找到自动配置类web.servlet.WebMvcAutoConfiguration,我们点击并打开它。在这个类中,我们找到WebMvcAutoConfigurationAdapter类,找到这个类后我们往下拉,可以看到方法—— addResourceHandlers :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void addResourceHandlers ( ResourceHandlerRegistry registry) {
// 禁用默认资源处理
if ( ! this . resourceProperties . isAddMappings () ) {
logger . debug ( "Default resource handling disabled" );
return ;
}
// 缓存控制
Duration cachePeriod =
this . resourceProperties . getCache (). getPeriod ();
CacheControl cacheControl = this . resourceProperties . getCache ().
getCachecontrol () . toHttpCacheControl ();
// webjars 配置
if ( ! registry . hasMappingForPattern ( "/webjars/**" ) ) {
customizeResourceHandlerRegistration ( registry . addResourceHandler ( "/webjars/**" )
. addResourceLocations ( "classpath:/META-INF/resources/webjars/" )
. setCachePeriod ( getSeconds (cachePeriod)). setCacheControl (cacheControl) ) ;
}
// 静态资源处理
String staticPathPattern = this . mvcProperties . getStaticPathPattern ();
if ( ! registry . hasMappingForPattern (staticPathPattern) ) {
customizeResourceHandlerRegistration ( registry . addResourceHandler (staticPathPattern)
. addResourceLocations ( getResourceLocations ( this . resourceProperties . getStaticLocations ()))
. setCachePeriod ( getSeconds (cachePeriod)). setCacheControl (cacheControl) ) ;
}
}
在 webjars 配置 的代码下,我们可以看到:所有的/webjars/**都需要去classpath:/META-INF/resources/webjars/目录下寻找资源。
1.1 WebJars配置
WebJars就是以jar包的方式导入静态资源,以前我们需要手动导入的静态资源(比如:jquery.js),现在我们可以在pom.xml导入依赖就可以了,比如:
1 2 3 4 5 6 <!--导入jQuery的依赖-->
< dependency >
< groupId >org.webjars</ groupId >
< artifactId >jquery</ artifactId >
< version >3.2.1</ version >
</ dependency >
PS:相关依赖可以在WebJars官网找到:WebJars
我们可以查看导入的jQuery的目录结构:
既然使用webjars就相当于导入了静态资源,那我们启动项目后可以访问到静态资源吗?当然可以,我们可以打开项目,在地址栏输入http://localhost:8080/webjars/jquery/3.2.1/jquery.js,即可看到如下界面:
1.2 静态资源映射
从源码中,我们可以看到,除了可以使用webjars导入依赖,还可以使用静态资源映射。
我们点击resourceProperties常量,可以在WebMvcAutoConfigurationAdapter类中找到ResourceProperties类:
点击并进入ResourceProperties类,在这个类的首行就可以看到一个数组常量:
1 2 3 4 5 private static final String [] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/" ,
"classpath:/resources/" ,
"classpath:/static/" ,
"classpath:/public/" } ;
ResourceProperties 可以设置与静态资源相关的参数,指向会去寻找资源的文件夹,即上面数组的内容。所以,我们不难得出结论,以下的四个目录存放的静态资源可以被我们识别到:
1 2 3 4 "classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
同样,我们可以在resources根目录下创建文件夹,然后我们就可以把我们的静态资源放到这些文件夹里了。比如,我们创建了一个myResource.js文件,然后我们启动项目,访问 http://localhost:8080/myResource.js,就可以访问到静态资源了。
我们还可以测试这三个文件夹下静态资源的访问顺序:我们在src/main/resources目录下创建文件夹resources和public,然后在resources、public、static三个文件夹 类编写一个.js文件,然后启动项目,在地址栏进行访问,访问后可得这三个文件夹下静态资源的访问顺序为:
resources > static > public
templates目录的使用在下文有所介绍!
1.3 自定义静态资源路径
除了前面两种方式设置静态资源路径,我们还可以自定义静态资源访问路径。操作也很简单,只需要在配置文件中编写配置就可以了,比如(application.properties):
1 spring.resources.static-locations = classpath:/yang/,classpath:/mofan/
但需要注意: 一旦自定义了静态资源的路径,SpringBoot自动配置的静态资源路径都将失效!
2. 首页和图标定制
首页为重点!
首页
我们在进行Web开发时,首先就需要解决首页的问题,那么SpringBoot中,应该怎么设置首页呢?
与静态资源处理一样,我们前往spring.factories文件中找到自动配置类web.servlet.WebMvcAutoConfiguration,点击并打开它。我们在WebMvcAutoConfiguration类中,直接搜索welcome关键字。然后可以找到如下的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @ Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping ( ApplicationContext applicationContext ,
FormattingConversionService mvcConversionService , ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping (
new TemplateAvailabilityProviders (applicationContext) , applicationContext , getWelcomePage () ,
this . mvcProperties . getStaticPathPattern () ) ;
welcomePageHandlerMapping . setInterceptors ( getInterceptors (mvcConversionService, mvcResourceUrlProvider));
return welcomePageHandlerMapping ;
}
private Optional < Resource > getWelcomePage () {
String [] locations = getResourceLocations ( this . resourceProperties . getStaticLocations () ) ;
return Arrays . stream (locations). map ( this :: getIndexHtml). filter ( this :: isReadable). findFirst ();
}
// 首页就是location下的index.html
private Resource getIndexHtml ( String location) {
return this . resourceLoader . getResource (location + "index.html" );
}
private boolean isReadable ( Resource resource) {
try {
return resource . exists () && ( resource . getURL () != null ) ;
}
catch ( Exception ex ) {
return false ;
}
}
这几个方法都是与首页相关的。我们将重点放在getIndexHtml方法,这个方法用来配置首页,表示location下的index.html就是首页,那么location又是什么呢?
我们在getIndexHtml()上面可以看到getWelcomePage(),这个方法调用了getIndexHtml()。然后,在getWelcomePage()中我们又发现如下代码:
1 String [] locations = getResourceLocations ( this . resourceProperties . getStaticLocations () ) ;
这一行代码就表示了location的含义。我们点击并进入getStaticLocations():
1 2 3 public String [] getStaticLocations () {
return this . staticLocations ;
}
点击staticLocations,找到这个变量:
1 private String [] staticLocations = CLASSPATH_RESOURCE_LOCATIONS ;
哦?这是个数组变量?😳
CLASSPATH_RESOURCE_LOCATIONS这个常量好像很熟悉?对,这个就是我们在上文看到的用于表示静态资源文件夹位置的常量:
1 2 3 4 5 private static final String [] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/" ,
"classpath:/resources/" ,
"classpath:/static/" ,
"classpath:/public/" } ;
这样一看,那我们的首页index.html就可以存放在这些位置了。👍
然后,我们可以创建一个index.html,把这个文件放在上述的静态资源文件夹下,然后启动项目,就可以用http://localhost:8080/访问到首页了。
但是真正开发中,我们不会直接在地址栏输入URL进行访问,我们会使用Controller来访问,就像Spring MVC中一样。
我们在此直接编写Controller是不能访问到index.html的,为什么?我们知道Spring MVC是需要配置视图解析器,但在这我们并没有配置,所以是不能够访问。除此之外,SpringBoot官方建议使用模板引擎Thymeleaf,而不是继续沿用.jsp文件,所以,在SpringBoot中, 如果我们想要访问页面,导入Thymeleaf的依赖就可以了导入Thymeleaf后会自动向Spring容器中添加一个视图解析器 。
图标定制
最新版的SpringBoot中,已经取消设置icon功能!(2.2.4版本已取消)
❓那么我们想要定制图标该怎么做呢?
在static目录下,创建images文件夹,将图标放在这个文件夹下(个人习惯,但也建议这么做!),然后直接在界面使用<link>应用:
1 < link rel = "icon" type = "image/x-icon" href = "/images/xx.icon" />
关闭默认图标(在配置文件中编写):
1 2 #关闭默认图标
spring.mvc.favicon.enabled = false
3. Thymeleaf模板引擎
3.1 模板引擎
❓什么是模板引擎?
模板引擎是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。
在以前,我们在.jsp文件中书写Java代码、HTML标签,甚至CSS、JS代码,.jsp文件似乎很强大。确实很强大,可代码的耦合度却很高。不仅如此,我们现在使用SpringBoot后,项目将以jar的方式进行打包,不再是war,同时我们还使用的是内嵌的tomcat,因此, SpringBoot现在默认是不支持jsp的 。
既然不支持,总的想个办法替代jsp吧,因此, SpringBoot推荐你使用模板引擎 。比如:Thymeleaf。
简单理解一下模板引擎的作用:将网页中动态的数据与静态的页面通过模板引擎生成HTML代码。
众多模板引擎中,SpringBoot推荐使用Thymeleaf,那么这玩意该咋用呢?
3.2 引入Thymeleaf
首先,我们得引入Thymeleaf,在SpringBoot中,就是一个starter的事,我们在pom.xml中引入它:
1 2 3 4 < dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-thymeleaf</ artifactId >
</ dependency >
这样,引入就完成了!简直不要太爽!👊
引入是完成了,那又该咋用呢?
3.3 使用Thymeleaf
先记住一句话: 使用了Thymeleaf模板引擎的.html文件都放在templates目录下 。
但是在实际使用的时候要记得: 我们无法直接访问templates目录下的文件,但可以使用Controller进行访问。
我们又来看源码,全局搜索 ThymeleafProperties 配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @ ConfigurationProperties ( prefix = "spring.thymeleaf" )
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets . UTF_8 ;
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 = DEFAULT_PREFIX ;
private String suffix = DEFAULT_SUFFIX ;
private String mode = "HTML" ;
private Charset encoding = DEFAULT_ENCODING ;
private boolean cache = true ;
// ...
}
我们可以看到默认设置的前缀和后缀,这就是前文说的:为什么没有配置视图解析器,但是导入Thymeleaf依赖就可以使用Controller访问界面了。
同时,我们还可以看到默认设置的文件路径,这就是为啥开头要说“使用了Thymeleaf模板引擎的.html文件都放在templates目录下”的原因了。
我们不妨测试一手?
1 2 3 4 5 6 7 8 9 @ Controller
public class HelloController {
@ RequestMapping ( "/aa" )
public String hello (){
return "hello" ;
}
}
编写测试界面hello.html,记得放在templates目录下:
1 2 3 4 5 6 7 8 9 10 <! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title >Title</ title >
</ head >
< body >
< h1 >你好</ h1 >
</ body >
</ html >
启动项目,在地址栏输入localhost:8080/aa试试?
界面是可以访问了,难道Thymeleaf就充当一个视图解析器的功能?当然不是!👇
3.4 Thymeleaf语法基础使用
不同的模板引擎有不同的语法,那Thymeleaf的语法是怎样的呢?
我们可以查看官方文档:Thymeleaf官网
在此,给出一个简单的使用:
1 2 < html lang = "en" xmlns:th = "https://www.thymeleaf.org/" >
<!-- xmlns:th="https://www.thymeleaf.org/" 就是约束 -->
1 2 3 4 5 @ RequestMapping ( "/test" )
public String test ( Model model){
model . addAttribute ( "msg" , "Hello,Thymeleaf" );
return "test" ;
}
编写测试界面test.html,记得放在templates目录下:
1 2 3 4 5 6 7 8 9 10 11 12 13 <! DOCTYPE html >
< html lang = "en" xmlns:th = "http://www.thymeleaf.org" >
< head >
< meta charset = "UTF-8" >
< title >Title</ title >
</ head >
< body >
< h1 >我是测试界面</ h1 >
<!-- Thymeleaf语法的使用 -->
< div th:text = "${msg}" ></ div >
</ body >
</ html >
启动项目,在地址栏输入localhost:8080/test试试?
我们已经会了最基本的语法了,那Thymeleaf还有其他的语法吗?当然是有的!💪
3.5 Thymeleaf高级语法
高级语法有哪些呢?官方文档走起!
前往文档第10点,查看属性优先级:
我们可以使用这些属性来替换HTML中原生的属性!
那么我们又可以写那些表达式呢?
依旧是官方文档!前往文档第4点,查看标准表达语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 Simple expressions:
Variable Expressions: ${...} (获取变量值)
{...}可以书写的内容有:
1. 获取对象的属性、调用方法,使用OGNL表达式
2. 内置的表达式基本对象
#ctx: the context object.
#vars: the context variables.
#locale: the context locale.
#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.
3. 内置的表达工具对象
#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).
4. 格式化日期
==============================================================================================================================================================
Selection Variable Expressions: *{...} (功能与取值表达式一样)
Message Expressions: #{...} (获取国际化内容)
Link URL Expressions: @{...}
Fragment Expressions: ~{...} (片段引用表达式)
Literals
Text literals: 'one text', 'Another one!',…
Number literals: 0, 34, 3.0, 12.3,…
Boolean literals: true, false
Null literal: null
Literal tokens: one, sometext, main,…
Text operations:
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:
Binary operators: +, -, *, /, %
Minus sign (unary operator): -
Boolean operations:
Binary operators: and, or
Boolean negation (unary operator): !, not
Comparisons and equality:
Comparators: >, <, >=, <= (gt, lt, ge, le)
Equality operators: ==, != (eq, ne)
Conditional operators:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _
PS:
转义与不转义:
1 2 3 < div th:text = "${msg}" ></ div >
<!--不转义-->
< div th:utext = "${msg}" ></ div >
行内写法与行内写法:
1 2 3 4 5 < h4 th:each = "user :${users}" th:text = "${user}" ></ h4 >
< h4 >
< span th:each = "user:${users}" >[[${user}]]</ span >
</ h4 >
4. MVC自动配置
我们要使用SpringBoot编写网页,那么使用MVC是必要的。SpringBoot的核心思想就是 约定大于配置 ,既然如此,想必SpringBoot也一定配置了MVC。
那么我们应该怎么使用呢?
遇事不决,读文档!👊
SpringBoot 2.2.4 MVC自动配置参考文档
为了避免链接挂了,我截了个图:
你会发现,我把截图中的某一处用红色矩形圈出,具体原因后面分析!
4.1 内容协商视图解析器
我们按照文档的顺序,先来看看ContentNegotiatingViewResolver (内容协商视图解析器)。
进入WebMvcAutoConfiguration类,然后搜索ContentNegotiatingViewResolver:
1 2 3 4 5 6 7 8 9 10 11 @ Bean
@ ConditionalOnBean ( ViewResolver . class )
@ ConditionalOnMissingBean ( name = "viewResolver" , value = ContentNegotiatingViewResolver . class )
public ContentNegotiatingViewResolver viewResolver ( BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver () ;
resolver . setContentNegotiationManager ( beanFactory . getBean ( ContentNegotiationManager . class ));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver . setOrder ( Ordered . HIGHEST_PRECEDENCE );
return resolver ;
}
在注释里说:内容协商视图解析器使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级。
我们再点击ContentNegotiatingViewResolver进入这个类,在这个类下有这样一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @ Override
@ 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 = getMediaTypes (((ServletRequestAttributes) attrs) . getRequest () ) ;
if (requestedMediaTypes != null ) {
// 获取候选的视图解析器
List < View > candidateViews = getCandidateViews (viewName , locale , requestedMediaTypes) ;
// 选择一个合适的视图解析器,然后把这个对象返回
View bestView = getBestView (candidateViews , requestedMediaTypes , attrs) ;
if (bestView != null ) {
return bestView ;
}
}
String mediaTypeInfo = logger . isDebugEnabled () && requestedMediaTypes != null ?
" given " + requestedMediaTypes . toString () : "" ;
if ( this . useNotAcceptableStatusCode ) {
if ( logger . isDebugEnabled () ) {
logger . debug ( "Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW ;
}
else {
logger . debug ( "View remains unresolved" + mediaTypeInfo);
return null ;
}
}
在上面的代码中,我们可以看到 获取了候选的试图解析器,那么是怎么获取的呢?我们点击并进入getCandidateViews:
在这个方法中,简单概括就是:遍历所有的视图解析器,然后将它们封装成一个对象,然后将对象添加到候选的视图,并返回候选的视图。
不难得出结论: ContentNegotiatingViewResolver 是用来组合所有的视图解析器的
我们再研究一下源码:在getCandidateViews会发现有一个viewResolvers,我们已经知道在getCandidateViews中会遍历视图解析器,那么视图解析器是在哪里赋值的呢?
在ContentNegotiatingViewResolver类中,有这样一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 protected void initServletContext ( ServletContext servletContext) {
Collection < ViewResolver > matchingBeans =
BeanFactoryUtils . beansOfTypeIncludingAncestors ( obtainApplicationContext (), ViewResolver . class ). values ();
if ( this . viewResolvers == null ) {
this . viewResolvers = new ArrayList <> ( matchingBeans . size () ) ;
for ( ViewResolver viewResolver : matchingBeans) {
if ( this != viewResolver) {
this . viewResolvers . add (viewResolver);
}
}
}
// ...
}
在这个方法中,我们可以看到使用了一个工具类获得视图解析器,那么我们是否可以自己编写一个视图解析器,然后把这个视图解析器添加到容器呢? 👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @ Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@ Bean //注册到容器中
public ViewResolver myViewResolver (){
return new MyViewResolver ();
}
//实现ViewResolver接口
// 实现了视图解析器接口的类,我们都可以把它当作视图解析器
private static class MyViewResolver implements ViewResolver {
@ Override
public View resolveViewName ( String s , Locale locale ) throws Exception {
return null ;
}
}
}
编写完成后,我们全局搜索DispatcherServlet 中的 doDispatch(),并给它打个断点:
然后使用Debug模式运行项目,在控制台可以看到:
我们可以看到,我们自己编写的视图解析器已经被添加到容器中了,这也验证了我们的猜想。
4.2 格式化器
在文档中,我们还看到有一个叫Formatter的东西,见名识意,这是一个格式化器。
那么这个东西该怎么用呢?我们又去找我们的老朋友WebMvcAutoConfiguration配置类。因为要使用格式化器,所以我们直接在MVC自动配置类中搜索Formatter,然后就可以看到下列代码:
1 2 3 4 5 6 7 @ Bean
@ Override
public FormattingConversionService mvcConversionService () {
WebConversionService conversionService = new WebConversionService ( this . mvcProperties . getDateFormat () ) ;
addFormatters (conversionService) ;
return conversionService ;
}
根据这段代码,我们可以看到日期格式化都是从mvcProperties中获取的,我们点击并进入getDateFormat():
1 2 3 public String getDateFormat () {
return this . dateFormat ;
}
点击dateFormat:
1 2 3 4 5 6 7 8 9 10 @ ConfigurationProperties ( prefix = "spring.mvc" )
public class WebMvcProperties {
// ....
/**
* Date format to use. For instance, `dd/MM/yyyy`.
*/
private String dateFormat ;
// ....
}
可以看到dateFormat是在WebMvcProperties类中,那么我们是否直接可以在SpringBoot配置文件中直接定义日期格式呢?答案是肯定的!😏
1 2 #配置日期格式化
spring.mvc.date-format = yyyy-MM-dd
除了视图解析器和格式化器,其他的配置我们可以按照相同的方法进行设置。
4.3 修改默认配置
SpringBoot在配置很多组件的时候,先看容器中有没有用户自己配置的(使用@Bean),如果有就用用户配置的,如果没有就用自动配置的。如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
我们又来看官方文档:
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
翻译一下:如果你想要保留SpringBoot MVC的功能,并且希望添加其他的MVC功能(比如:拦截器、格式化器、视图控制器等),你可以在你自己编写的类型为WebMvcConfigurer的类上添加@Configuration注解,但 不添加 @EnableWebMvc。
既然如此,那我们可以操作一波了!👊
1 2 3 4 5 6 7 8 9 @ Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@ Override
public void addViewControllers ( ViewControllerRegistry registry ) {
// 浏览器发送/test , 就会跳转到test页面;
registry . addViewController ( "/yang" ). setViewName ( "test" );
}
}
我们自己编写了一个视图控制器,在浏览器内访问localhost:8080/test可以跳转到test.html界面。进行测试后,确实可以成功跳转。
这表明了:想要拓展SpringMVC,且不破坏SpringBoot给我们的自动配置,就可以像这么做!
分析一下原理:
继续找我们的老朋友WebMvcAutoConfiguration 😆,在这个配置类中有一个适配器WebMvcAutoConfigurationAdapter:
1 2 3 4 5 6 7 @ Configuration ( proxyBeanMethods = false )
@ Import ( EnableWebMvcConfiguration . class )
@ EnableConfigurationProperties ({ WebMvcProperties . class , ResourceProperties . class })
@ Order ( 0 )
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
// ...
}
可以看到适配器上有个名为@Import(EnableWebMvcConfiguration.class)的注解。我们点击并进入这个注解中的类EnableWebMvcConfiguration:
1 2 3 4 @ Configuration ( proxyBeanMethods = false )
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
// ....
}
EnableWebMvcConfiguration类继承了DelegatingWebMvcConfiguration类。我们点击并进入父类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @ Configuration ( proxyBeanMethods = false )
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers
= new WebMvcConfigurerComposite () ;
// 获取容器中所有的WebMvcConfigurer
@ Autowired ( required = false )
public void setConfigurers ( List < WebMvcConfigurer > configurers ) {
if ( ! CollectionUtils . isEmpty (configurers)) {
this . configurers . addWebMvcConfigurers (configurers);
}
}
// ...
}
在这个类中,搜索刚刚设置的addViewController:
1 2 3 protected void addViewControllers ( ViewControllerRegistry registry) {
this . configurers . addViewControllers (registry);
}
点击并进入addViewControllers方法:
1 2 3 4 5 6 @ Override
public void addViewControllers ( ViewControllerRegistry registry) {
for ( WebMvcConfigurer delegate : this . delegates ) {
delegate . addViewControllers (registry);
}
}
在这个方法中,我们可以看到:将所有与WebMvcConfigurer相关的配置都一起调用,包括我们自己配置的和SpringBoot自动配置的。
不难得出结论: 所有的WebMvcConfiguration都会被调用,包括SpringBoot自动配置的和我们自己配置的。
说了半天,还记得开始挖的坑吗?我为什么要把 without @EnableWebMvc 括起来呢?这就要说到全面接管SpringMVC了。👇
4.4 全面接管SpringMVC
官方文档说不能加 @EnableWebMvc,我偏要加上去试试,加上去后,我们在前面代码的基础上在浏览器内访问localhost:8080/test。这个时候,我们发现无法正常显示网页了。
这是为啥呢?
主要是因为添加注解 @EnableWebMvc后,所有的配置将由我们自己接管,而SpringBoot自动配置的MVC全部失效!
我们在源码中分析一下:
点击并进入@EnableWebMvc注解:
1 2 3 4 5 6 @ Retention ( RetentionPolicy . RUNTIME )
@ Target ( ElementType . TYPE )
@ Documented
@ Import ( DelegatingWebMvcConfiguration . class )
public @ interface EnableWebMvc {
}
我们看到了刚交的朋友: DelegatingWebMvcConfiguration。点击并进入它:
1 2 3 4 @ Configuration ( proxyBeanMethods = false )
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ...
}
这个类继承了WebMvcConfigurationSupport ,看到这是不是觉得云里雾里,这和我们要讲的SpringBoot自动配置的MVC失效有啥关系呢?😵
别急,我们最后再拜访一下我们的老朋友WebMvcAutoConfiguration :
1 2 3 4 5 6 7 8 9 10 11 @ 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 {
// ...
}
WebMvcAutoConfiguration的注释表明,当存在WebMvcConfigurationSupport类时,自动配置的MVC将不会生效!!
😎