首页 > 编程语言 > 详细

SpringBoot:扩展SpringMVC、定制首页、国际化

时间:2020-04-19 11:34:14      阅读:166      评论:0      收藏:0      [点我收藏+]

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、定制首页、国际化

原文:https://www.cnblogs.com/rainszj/p/12730699.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!