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();
} }

为何这么做会生效(原理)

  1. WebMvcAutoConfiguration是SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
  2. 这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
  3. 我们点击 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一下,看看是否会自动装配。

  1. 我们可以在DelegatingWebMvcConfiguration类中去寻找一个我们刚才设置的 addViewControllers() 当做参考,发现它调用了WebMvcConfigurerCompositeaddViewControllers()方法
@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、定制首页、国际化的更多相关文章

  1. SpringBoot扩展SpringMVC自动配置

    SpringBoot中自动配置了 ViewResolver(视图解析器) ContentNegotiatingViewResolver(组合所有的视图解析器) 自动配置了静态资源文件夹.静态首页.fa ...

  2. SpringBoot中SpringMVC的自动配置以及扩展

    一.问题引入 我们在SSM中使用SpringMVC的时候,需要由我们自己写SpringMVC的配置文件,需要用到什么就要自己配什么,配置起来也特别的麻烦.我们使用SpringBoot的时候没有进行配置 ...

  3. SpringBoot日记——SpringMvc自动配置与扩展篇

    为了让SpringBoot保持对SpringMVC的全面支持和扩展,而且还要维持SpringBoot不写xml配置的优势,我们需要添加一些简单的配置类即可实现: 通常我们使用的最多的注解是: @Bea ...

  4. SpringBoot 之 扩展 SpringMVC

    增加自定义视图解析器: # src/main/java/com/wu/config/MyMvcConfig.java @Configuration // 标注这个类是一个配置类 public clas ...

  5. java框架之SpringBoot(5)-SpringMVC的自动配置

    本篇文章内容详细可参考官方文档第 29 节. SpringMVC介绍 SpringBoot 非常适合 Web 应用程序开发.可以使用嵌入式 Tomcat,Jetty,Undertow 或 Netty ...

  6. SpringBoot接管SpringMvc

    SpringBoot接管SpringMvc Spring Web MVC framework(通常简称为“Spring MVC”)是一个丰富的“model 视图控制器”web framework. S ...

  7. Spring Boot2.0+中,自定义配置类扩展springMVC的功能

    在spring boot1.0+,我们可以使用WebMvcConfigurerAdapter来扩展springMVC的功能,其中自定义的拦截器并不会拦截静态资源(js.css等). @Configur ...

  8. Springboot:员工管理之国际化(十(3))

    1:IDEA编码设置UTF-8 2:创建国际化文件 i18n\login.properties #默认语言 i18n\login_en_US.properties #英文语言 i18n\login_z ...

  9. 扩展SpringMVC以支持绑定JSON格式的请求参数

    此方案是把请求参数(JSON字符串)绑定到java对象,,@RequestBody是绑定内容体到java对象的. 问题描述: <span style="font-size: x-sma ...

随机推荐

  1. linux常用命令整理(一)

    1.sort(排序) 典型例题:sort -t: -k3n /etc/passwd 以冒号为分隔符根据第三个域的数字大小进行排序(默认分隔符是空格) 2.uniq(去除文件中的连续重复行) 典型例题: ...

  2. react性能优化最佳实践

    1.PureComponent 的使用场景 PureComponent 和 Component 的区别是,PureComponent 自带 shouldComponentUpdate 生命周期函数,会 ...

  3. codeforces Equalizing by Division (easy version)

    output standard output The only difference between easy and hard versions is the number of elements ...

  4. 如何将你的 Vue.js 项目部署在云开发静态托管之上

    云开发静态托管是云开发提供的静态网站托管的能力,静态资源(HTML.CSS.JavaScript.字体等)的分发由腾讯云对象存储 COS 和拥有多个边缘网点的腾讯云 CDN 提供支持. 在云开发静态托 ...

  5. 操作google_sheets

    起源:最近了使用flask和bootstrap写了测试小工具,数据全部使用excel存储,部署到测试环境. 问题:每次每个人在使用excel数据时都需要重新编辑好的excel通过upload按钮传到服 ...

  6. [linux][mysql] 命令更改表结构:添加、删除、修改字段、调整字段顺序

    原文出处:http://www.phpernote.com/MySQL/1120.html 查看表结构: desc tabl_name; show columns fromtable_name: 常用 ...

  7. python 工具链 多版本管理工具 pyenv

    理解Shims pyenv会在系统的PATH最前面插入一个shims目录: $(pyenv root)/shims:/usr/local/bin:/usr/bin:/bin 通过一个rehashing ...

  8. Java标识符中常见的命名规则

    标识符:就是给类,接口,方法,变量等起名字.组成规则:A:英文字母大小写B:数字字符C:$和_注意事项:A:不能以数字开头B:不能使Java中的关键字C:Java语言严格区分大小写常见的命名规则:见名 ...

  9. 用SQL查询分析实现类似金蝶K3的收发存明细表

    使用SQL查询分析实现类收发存的报表,原始需求在 另外一篇文章 的第四部分.下图是实现需求. 一.准备 删除临时表 [buy]判断是否存在临时表,存在则删除[/buy] if OBJECT_ID('t ...

  10. POJ1475 推箱子---模块化思想

    自古逢秋悲寂寥,我言秋日胜春朝. 晴空一鹤排云上,便引诗情到碧霄. --刘禹锡 题目:推箱子 网址:http://poj.org/problem?id=1475 推箱子游戏相信大家都不陌生,在本题中, ...