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. transaction 用tx事务 测试时 报错:Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/mvc]

    Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/sc ...

  2. File类心得

    File类心得 在程序中设置路径时会有系统依赖的问题,java.io.File类提供一个抽象的.与系统独立的路径表示.给它一个路径字符串,它会将其转换为与系统无关的抽象路径表示,这个路径可以指向一个文 ...

  3. Pytest系列(21)- allure的特性,@allure.description()、@allure.title()的详细使用

    如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 前言 前面介绍了两种allure的 ...

  4. Nikto使用方法

    Introduction Nikto是一款开源的(GPL)网站服务器扫描器,使用Perl基于LibWhisker开发.它可以对网站服务器进行全面的多种扫描,包括6400多个潜在危险的文件/CGI,检查 ...

  5. 基于canvas的画板

    最近重新在看Html5&CSS3的知识,看到canvas的时候,想到了以前在学校学计算机图形学时做过的画图实验,于是想,可以基于html5和css3来做一款画板,经过1天的努力,完成了画板的一 ...

  6. php7.2.1+redis3.2.1 安装redis扩展(windows系统)

    前提:已成功安装PHP环境和Redis服务 下面进入正题: 第一步,下载redis驱动扩展文件,注意:需要根据上面信息下载对应版本 https://windows.php.net/downloads/ ...

  7. 调用sleep后,我做了一个噩梦

    sleep系统调用 我是一个线程,生活在Linux帝国.一直以来辛勤工作,日子过得平平淡淡,可今天早上发生了一件事让我回想起来都后怕. 早上,我还是如往常一样执行着人类编写的代码指令,不多时走到了一个 ...

  8. shift后门

    shift快捷 Windows的粘滞键------C:\windows\system32\sethc.exe,它本是为不方便按组合键的人设计的 Windows系统按5下shift后,Windows就执 ...

  9. phpstudy之访问loaclhost显示目录

    phpstudy版本:phpstudy2018 具体操作: 当前版本的默认设置访问网站根目录是不会显示目录的,需要我们设置,其实也很简单,只需两步就可以搞定 1.找到phpstudy目录下的www文件 ...

  10. Springboot:异步业务处理(十二)

    说明 当正常业务处理调用一个复杂业务或者耗时较长的请求时,客户等待时间会比较长,造成不好的用户体验,所以这时候需要用的异步处理 构建一个群发邮件的service接口及实现(模拟) 接口:com\spr ...