SpringBoot:扩展SpringMVC、定制首页、国际化
SpringBoot扩展使用SpringMVC、使用模板引擎定制首页及静态资源绑定、页面国际化
扩展使用SpringMVC
如何扩展SpringMVC
How to do!
如果你希望保留SpringBoot 中MVC的功能,并希望添加其他的配置(拦截器、格式化、视图控制器和其他功能),只需要添加自己的@Configuration配置类,并让该类实现 WebMvcConfigurer接口,但是不要在该类上添加 @EnableWebMvc注解,一旦添加了,该配置类就会全面接管SpringMVC中配置,不会再帮我们自动装配了!WebMvcAutoConfiguration这个自动装配类也就失效了!
Action!
新建一个包叫config,写一个类MyMvcConfig
/**
* 该类类型应为:webMvcConfigurer,所以我们实现其接口
* 通过覆盖重写其中的方法实现扩展MVC的功能
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 添加视图控制器
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器访问:localhost:8080/index.html或者localhost:8080/,都跳转到 classpath:/templates/index.html
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
// 浏览器访问:localhost:8080/main.html 跳转到 classpath:/templates/dashborad.html
registry.addViewController("/main.html").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
为何这么做会生效(原理)
WebMvcAutoConfiguration是SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter- 这个类上有一个注解,在做其他自动配置时会导入:
@Import(EnableWebMvcConfiguration.class) - 我们点击
EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration,这个父类中有这样一段代码:
/**
* DelegatingWebMvcConfiguration 是 WebMvcConfigurationSupport 的子类,
* 可以检测和委托给所有类型为:WebMvcConfigurer 的bean,
* 允许它们自定义 WebMvcConfigurationSupport 提供的配置
* 它是由 注解@EnableWebMvc 实际导入的类
* @since 3.1
*/
@Configuration
// 委派webMvc配置类
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// webMvc配置综合类
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 会从容器中获取所有的 webMvcConfigurer,自动装配
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
// 调用了WebMvcConfigurerComposite的addWebMvcConfigurers方法
this.configurers.addWebMvcConfigurers方法(configurers);
}
}
}
我们可以在 WebMvcConfigurerComposite 里Debug一下,看看是否会自动装配。


- 我们可以在
DelegatingWebMvcConfiguration类中去寻找一个我们刚才设置的 addViewControllers() 当做参考,发现它调用了WebMvcConfigurerComposite的addViewControllers()方法
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
点进去:addViewControllers()
public void addViewControllers(ViewControllerRegistry registry) {
/*
for循环,将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
*/
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
5. 所以得出结论:所有的 WebMvcConfigurer 都会起作用,不止Spring的配置类,我们自己的配置类也会被调用。
小结:
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来
全面接管SpringMVC
当然,我们在实际开发中,不推荐使用全面接管SpringMVC
但我们要明白这一点:为什么一旦添加了@EnableWebMvc注解,我们就会全面接管SpringMVC,它不会帮我自动装配了呢?
先演示一下效果:
首先创建一个配置类,添加@Configuration注解、实现WebMmvConfigurer接口,先不添加 @EnableWebMvc注解
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
}
访问在public目录下的 index.html

然后再添加@EnableWebMvc
// 标记这个注解的类是一个配置类,本质也是一个 Component:组件
@Configuration
// 标记这个注解的类会全面接管SpringMVC,不会再自动装配 WebMvc配置
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
}
再次访问首页

可以看到自动配置失效了,回归到了最初的样子!
说说为什么:
我们先点击去这个:@EnableWebMvc注解看看
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
它导入了一个类:DelegatingWebMvcConfiguration
再点进入看看
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
DelegatingWebMvcConfiguration它又继承了一个父类:WebMvcConfigurationSupport
现在我们再回到:WebMvcAtuoConfiguration这个自动配置类
// 代表这是一个配置类:Java Config
@Configuration
// 判断容器是否是 web容器
@ConditionalOnWebApplication(type = Type.SERVLET)
// 判断容器中有没有这些类
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
/*
@ConditionalOnMissingBean:判断容器中是否【没有】WebMvcConfigurationSupport 这个类,如果没有才会生效。
如果容器中没有这个组件的时候,这个自动配置类才会生效!
而我们的@EnableWebMvc注解导入的类,它最终继承了这个WebMvcConfigurationSupport配置类,所以一旦加上了@EnableWebMvc这个注解,SpringBoot对SpirngMVC的自动装配才会失效!
*/
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
在SpringBoot中会有非常多的 XXConfigurer帮助我们进行扩展配置,只要看见了这个,我们就应该多留心注意
首页实现
实现目的:默认访问首页
方式一:通过Controller实现
// 会解析到templates目录下的index.html页面
@RequestMapping({"/","/index.html"})
public String index(){
return "index";
}
方式二:编写MVC扩展配置
package com.rainszj.config;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
为了保证资源导入稳定,我们建议在所有资源导入时候使用 th:去替换原有的资源路径!
// 修改前
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<link href="css/bootstrap.min.css" rel="stylesheet">
// 修改后 使用 @{/...},其中 / 不能忘写,它代表当前项目本身
// @{}它会自动帮我们到存放静态资源的文件夹下寻找相关资源
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
修改项目名:在application.properties或者yaml
server.servlet.context-path=/项目名
修改完项目名后,访问地址变成:localhost:8080/项目名/
使用 th:后,无论我们的项目名称如何变化,它都可以自动寻找到!
页面国际化
国际化:可以切换不同的语言显示
首先在IDEA中,统一设置properties的编码问题,防止乱码

在resources目录下新建一个i18n(Internationalization)目录,新建一个login.properties 文件,还有一个 login_zh_CN.properties,到这一步IDEA会自动识别我们要做国际化的操作;文件夹变了!



第一步:编写页面对应的国际化配置文件


login.properties:默认
login.password=密码
login.remeber=记住我
login.sign=登录
login.tip=请登录
login.username=用户名
login_zh_CN.properties:中文
login.password=密码
login.remeber=记住我
login.sign=登录
login.tip=请登录
login.username=用户名
login_en_US.properties:英文
login.password=Password
login.remeber=Remember me
login.sign=Sign in
login.tip=Please sign in
login.username=Username
第二步:我们去看一下SpringBoot对国际化的自动配置!
这里又涉及到一个类: MessageSourceAutoConfiguration ,里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;
@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = {};
@Bean
// 绑定application.yaml中的spring.meeages
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(StringUtils
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
protected static class ResourceBundleCondition extends SpringBootCondition {
private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();
// 获取匹配结果
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
ConditionOutcome outcome = cache.get(basename);
if (outcome == null) {
outcome = getMatchOutcomeForBasename(context, basename);
cache.put(basename, outcome);
}
return outcome;
}
// 获取basename的匹配结果
private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
for (Resource resource : getResources(context.getClassLoader(), name)) {
if (resource.exists()) {
return ConditionOutcome.match(message.found("bundle").items(resource));
}
}
}
return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
}
// 获取资源
private Resource[] getResources(ClassLoader classLoader, String name) {
// 这就是为什么我们要写:i18n.login ,它会自动帮我们替换
String target = name.replace('.', '/');
try {
return new PathMatchingResourcePatternResolver(classLoader)
.getResources("classpath*:" + target + ".properties");
}
catch (Exception ex) {
return NO_RESOURCES;
}
}
}
}
在applicaiont.properties中配置国际化的的路径:
spring.messages.basename=i18n.login
第三步:去页面获取管国际化的值
thymeleaf中,取message的表达式为:#{}
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
行内写法:
<div class="checkbox mb-3">
<label>
<input type="checkbox"> [[#{login.remeber}]]
</label>
</div>
index.html
注意:引入thymeleaf的头文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" th:action="#">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<p style="color: red;" th:text="${msg}"></p>
<input type="text" class="form-control" name="username" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control" name="password" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox"> [[#{login.remeber}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.sign}]]</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" href="">中文</a>
<a class="btn btn-sm" href="">English</a>
</form>
</body>
</html>
但是我们想要更好!可以根据按钮自动切换中文英文!
在Spring中有一个国际化的 Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器
我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置了
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
// 用户配置了就用优先用用户配置的,否则容器会基于 accept-language 配置
// accept-language 通常是由客户端浏览器决定,更进一步是由操作系统的语言决定
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
AcceptHeaderLocaleResolver 这个类中有一个方法
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 默认的就是根据请求头带来的区域信息获取Locale进行国际化
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的locale生效!
我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!
修改一下前端页面的跳转连接;
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
<!--这里携带参数不用 ?,使用(key=value)-->
去写一个处理区域信息的类,实现LocaleResolver 接口
// 可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {
// 解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l");
Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
// 如果请求链接不为空
if (!StringUtils.isEmpty(language)){
// 分割请求参数
String[] split = language.split("_");
// 语言、国家
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
为了让我们自己的区域化信息对象生效,我们需要在 MyMvcConfig 中注册它的Bean,把它交给Spring容器托管
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
我们重启项目,来访问一下,发现点击按钮可以实现成功切换!
SpringBoot:扩展SpringMVC、定制首页、国际化的更多相关文章
- SpringBoot扩展SpringMVC自动配置
SpringBoot中自动配置了 ViewResolver(视图解析器) ContentNegotiatingViewResolver(组合所有的视图解析器) 自动配置了静态资源文件夹.静态首页.fa ...
- SpringBoot中SpringMVC的自动配置以及扩展
一.问题引入 我们在SSM中使用SpringMVC的时候,需要由我们自己写SpringMVC的配置文件,需要用到什么就要自己配什么,配置起来也特别的麻烦.我们使用SpringBoot的时候没有进行配置 ...
- SpringBoot日记——SpringMvc自动配置与扩展篇
为了让SpringBoot保持对SpringMVC的全面支持和扩展,而且还要维持SpringBoot不写xml配置的优势,我们需要添加一些简单的配置类即可实现: 通常我们使用的最多的注解是: @Bea ...
- SpringBoot 之 扩展 SpringMVC
增加自定义视图解析器: # src/main/java/com/wu/config/MyMvcConfig.java @Configuration // 标注这个类是一个配置类 public clas ...
- java框架之SpringBoot(5)-SpringMVC的自动配置
本篇文章内容详细可参考官方文档第 29 节. SpringMVC介绍 SpringBoot 非常适合 Web 应用程序开发.可以使用嵌入式 Tomcat,Jetty,Undertow 或 Netty ...
- SpringBoot接管SpringMvc
SpringBoot接管SpringMvc Spring Web MVC framework(通常简称为“Spring MVC”)是一个丰富的“model 视图控制器”web framework. S ...
- Spring Boot2.0+中,自定义配置类扩展springMVC的功能
在spring boot1.0+,我们可以使用WebMvcConfigurerAdapter来扩展springMVC的功能,其中自定义的拦截器并不会拦截静态资源(js.css等). @Configur ...
- Springboot:员工管理之国际化(十(3))
1:IDEA编码设置UTF-8 2:创建国际化文件 i18n\login.properties #默认语言 i18n\login_en_US.properties #英文语言 i18n\login_z ...
- 扩展SpringMVC以支持绑定JSON格式的请求参数
此方案是把请求参数(JSON字符串)绑定到java对象,,@RequestBody是绑定内容体到java对象的. 问题描述: <span style="font-size: x-sma ...
随机推荐
- intellJ svn控制错误
电脑突然蓝屏了,重启打开intellj 后原本好好的项目是可以用intellj更新或者提交的,现在却都不能了,如图: 如上图:svn地址里是空白的,应该显示: 那到底是什么情况呢,就因为电脑崩溃了in ...
- 对象中属性 加锁 用:volatile 关键词修饰 而 不用 synchronized 加锁
一个对象中有一个状态 属性,现在业务需求 存在多线程来修改 和 拿去 这个状态 的值,这种情况如果加锁怎么加? 一种是 在 set 和get 这个状态的 方法那加 synchronized . 还有一 ...
- 多线程高并发编程(5) -- CountDownLatch、CyclicBarrier源码分析
一.CountDownLatch 1.概念 public CountDownLatch(int count) {//初始化 if (count < 0) throw new IllegalArg ...
- CAS原理解析
CAS底层原理 概念 CAS的全称是Compare-And-Swap,它是CPU并发原语 它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的 CAS并发原语体现在Jav ...
- 详解 stream流
在本人之前的博文中,我们学习了 I/O流.NIO流的相关概念. 那么,在JDK8的更新内容中,提出了一个新的流 -- stream流 那么,现在,本人就来讲解下这个流: 目录 stream流 常用AP ...
- [护网杯2018] easy_laravel
前言 题目环境 buuoj 上的复现,和原版的题目不是完全一样.原题使用的是 nginx + mysql 而 buuoj 上的是 apache + sqlite composer 这是在 PHP5.3 ...
- selemiun 问题总结
1.如果打开一个网页定位一个元素时发现不能够定位某一个元素,并且定位的方法没问题,则需要看下该网页是否有frame框架 解决办法: 如果有frame框架则需要先切换到frame框架下: driver. ...
- js事件冒泡于事件捕获
事件冒泡 事件捕获指的是从document到触发事件的那个节点,即自上而下的去触发事件. 事件冒泡是自下而上(从最深节点开始,向上传播事件)的触发事件 //例子 <div id="pa ...
- 2019-2020-1 20199325《Linux内核原理与分析》第三周作业
在实验楼当中进行实验3的实践:主要是针对cpu占用分配,使用时间片轮转算法进行分配 在Shell命令当中输入如下代码: $ cd ~/LinuxKernel/linux-3.9.4 $ rm -rf ...
- 联想在S规则债券市场完成了里程碑式的新债券发行
腾讯科技讯,香港,2020 年 4 月 24 日-联想集团(HKSE:992)(ADR:LNVGY)今日宣布,在S规则债券市场上成功发行了里程碑式的 6.5 亿美元债券. 这些债券吸引了全球大量固定收 ...