1. 程式人生 > >03 SpringBoot對Web的支援

03 SpringBoot對Web的支援

文章目錄

支援

SpringBoot提供了

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</
artifactId
>
</dependency>

為我們提供了嵌入式的Tomcat以及SpringMVC的依賴

和Web相關的自動配置儲存在spring-boot-autoconfigure-1.5.3.RELEASE.jar的org.springframework.boot.autoconfigure.web下

開發過程中有什麼配置有疑惑了,就去找個包下找相關的自動配置類進行檢視即可

Thymealeaf模板引擎

Spring Boot中推薦使用Thymeleaf作為模板引擎

內嵌的Tomcat、Jetty是不支援以jar的形式執行 JSP 的

Ubdertow不支援jsp

Thymealeaf是一個Java類庫,是一個xml、xhtml、html5的模板引擎,可以作為Web應用的View層

配置Thymeleaf

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
<groupId>org.zln.learning.spboot</groupId>
	<artifactId>spboot-demo02</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
<name>spboot-demo02</name>
	<description>Demo project for Spring Boot</description>
<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

當前匯入的Thymealeaf版本仍舊是2,

如果想要使用Thymealeaf3,就需要自己配置

<properties>
    <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
    <thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version>
</properties>

僅新增這兩個屬性配置即可,starter仍舊用的是thymeleaf

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templatemode.*;
@Configuration
public class ThymeleafConfig implements ApplicationContextAware{
    private static final String UTF8 = "UTF-8";
    private ApplicationContext applicationContext;
private String[] array(String ...args) {
        return args;
    }
@Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
private TemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver);
        return engine;
    }
private ITemplateResolver htmlTemplateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setApplicationContext(applicationContext);
        resolver.setPrefix("classpath:/templates/");
        resolver.setTemplateMode(TemplateMode.HTML);
        return resolver;
    }
@Bean
    public ViewResolver htmlViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine(htmlTemplateResolver()));
        resolver.setContentType("text/html");
        resolver.setCharacterEncoding(UTF8);
        resolver.setViewNames(array("*.html"));
        resolver.setCache(false);
        return resolver;
    }
private ITemplateResolver cssTemplateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setApplicationContext(applicationContext);
        resolver.setPrefix("classpath:/templates/");
        resolver.setTemplateMode(TemplateMode.CSS);
        return resolver;
    }
@Bean
    public ViewResolver cssViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine(cssTemplateResolver()));
        resolver.setContentType("text/css");
        resolver.setCharacterEncoding(UTF8);
        resolver.setViewNames(array("*.css"));
        resolver.setCache(false);
        return resolver;
    }
private ITemplateResolver javascriptTemplateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setApplicationContext(applicationContext);
        resolver.setPrefix("classpath:/templates/");//Thymeleaf的HTML檔案放在此
        resolver.setTemplateMode(TemplateMode.JAVASCRIPT);
        return resolver;
    }
@Bean
    public ViewResolver javascriptViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine(javascriptTemplateResolver()));
        resolver.setContentType("application/javascript");
        resolver.setCharacterEncoding(UTF8);
        resolver.setViewNames(array("*.js"));
        resolver.setCache(false);
        return resolver;
    }
}

  • Thymealeaf預設配置

見:ThymeleafProperties 類

首頁

預設靜態首頁

  • classpath:/META-INF/resources/index.html
  • classpath:/resources/index.html
  • classpath:/static/index.html
  • classpath:/public/index.html

小結

如果不想要使用Thymealeaf作為View層,仍舊使用JSP的話,

那麼建立好Spring Boot 的 Web 工程後,再匯入

<dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
   <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp.jstl</groupId>
    <artifactId>jstl-api</artifactId>
    <version>1.2</version>
</dependency>

在src/main/resources/application.properties檔案中配置JSP和傳統Spring MVC中和view的關聯

  • MVC

spring.view.prefix=/WEB-INF/views/
spring.view.suffix=.jsp

建立src/main/webapp/WEB-INF/views目錄,JSP檔案就放這裡

注意

以上程式碼pom.xml中的javax.servlet.jsp.jstl是用於支援JSP標籤庫的,

在Web2.5的容器中沒有問題,但當你的容器是Web3.0或以上版本時,就會出問題。這是個非常坑爹的問題。

javax.servlet.jsp.jstl會自動載入依賴servlet-api-2.5.jar,

而且會在實際執行時把支援Web3.0的3.1版本的javax.servlet-api覆蓋掉。

即使你在pom.xml顯示的在加入3.1版本的javax.servlet-api也沒用。導致SpringBoot應用丟擲Runtimeexception執行錯誤。

這是一個不可調和的矛盾,要麼不用javax.servlet.jsp.jstl,要麼不用Web3.0。

但絕大多數情況下,jstl標籤庫不是必須的,而Web3.0是必須的。

替代方式就是不用JSP,改用Themeleaf吧

思考: 在前端框架如此興盛的當下,還有必要使用HTML模板嗎?

Web相關配置

詳見程式碼:WebMvcAutoConfiguration、WebMvcProperties

自動配置的ViewResolver

  • ContentNegotiatingViewResolver
    ​ 一個特殊的ViewResolver,自己不處理view,
    ​ 通過代理給不同的viewresolver處理不同的view
    ​ 擁有最高優先順序
  • BeanNameViewResolver
    ​ 根據控制器的返回字串,查詢對應的view來渲染檢視
  • InternalResourceViewResolver

自動配置的靜態資源

類路徑下的/static、/public、/resources、/META-INF/resources ,

這幾個資料夾下的靜態檔案直接對映為/**,

一般放置js、css檔案

可以通過http://localhost:8080/**來訪問

webjar的 /META-INF/resources/webjars/ 下的的靜態檔案對映為 /webjar/**

Thymeleaf具體怎麼使用,見Thymeleaf部分的筆記

自動配置的Formatter和Converter

Formatter和Converters是用於型別轉化的

只要我們定義了Converter、GenericConverter、Formatter介面的實現類,

這些Bean就會自動註冊到Spring MVC中

  • 自動配置HttpMessageConverters

編寫好自定義的HttpMessageConverters後,註冊為Bean,Spring Boot會自動註冊

HttpMessageConverters是幹嘛的?

答:請求的json字串被@RequestBody轉化為物件,就是HttpMessageConverters乾的

@Bean
public HttpMessageConverters customerConverters() {
    HttpMessageConverter<?> h1 = ....
    HttpMessageConverter<?> h2 = ....
    return new HttpMessageConverters(h1,h2);
}

Formatter是對錶單請求進行資料格式轉換,

如果是JSON資料,可以直接在物件上使用@JsonFormatter、@JsonSerialize等註解

接管Spring Boot 的Web配置

如果Spring Boot的預設配置不符合要求,

則可以通過一個配置類加上@EnableWebMvc註解實現完全自己控制的MVC配置

如何既保留SpringBoot自動配置的便利,又增加自己額外的配置呢?

這個配置類需要繼承WebMVCConfigurerAdapter,無需@EnableWebMvc

當然,配置類上@Configuration是肯定要新增的

註冊Servlet、Filter、Listener

有兩種方法:

  • 將Servlet、Filter、Listener註冊為Spring Bean
  • 註冊 ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean
@Bean
public ServletRegistrationBean servletRegistrationBean(){
    return new ServletRegistrationBean(new MyServlet(),"/xx/*");
}
@Bean
public FilterRegistrationBean filterRegistrationBean(){
    FilterRegistrationBean reg = new FilterRegistrationBean();
    reg.setFilter(new MyFilter());
    return reg;
}
@Bean
ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean(){
    return new ServletListenerRegistrationBean(MyListener)(new MyListener());
}


國際化

  • application.yml


spring:
  messages:
#  國際化檔案
    basename: i18n/messages/messages

messages.properties
messages_en_US.properties
messages_zh_CN.properties

package com.zhidianfan.pig.yd.config;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

/**
 * 自定義國際化語言解析器
 */
public class MyLocaleResolver implements LocaleResolver {

    private static final String I18N_LANGUAGE = "i18n_language";
    private static final String I18N_LANGUAGE_SESSION = "i18n_language_session";

    @Override
    public Locale resolveLocale(HttpServletRequest req) {
        String i18n_language = req.getParameter(I18N_LANGUAGE);
        Locale locale = Locale.getDefault();
        if (!StringUtils.isEmpty(i18n_language)) {
            String[] language = i18n_language.split("_");
            locale = new Locale(language[0], language[1]);

            //將國際化語言儲存到session  
            HttpSession session = req.getSession();
            session.setAttribute(I18N_LANGUAGE_SESSION, locale);
        } else {
            //如果沒有帶國際化引數,則判斷session有沒有儲存,有儲存,則使用儲存的,也就是之前設定的,避免之後的請求不帶國際化引數造成語言顯示不對
//            在分散式環境中,使用Redis代替Session,Key:賬戶+language
            HttpSession session = req.getSession();
            Locale localeInSession = (Locale) session.getAttribute(I18N_LANGUAGE_SESSION);
            if (localeInSession != null) {
                locale = localeInSession;
            }
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest req, HttpServletResponse res, Locale locale) {

    }

}  








package com.zhidianfan.pig.yd.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * web端配置資訊
 *
 * @author danda
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    /**
     * 配置自己的國際化語言解析器
     *
     * @return
     */
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }

    /**
     * 配置自己的攔截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //super.addInterceptors(registry);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

請求中,帶上 &i18n_language=zh_CN 用於設定當前語言環境

使用國際化

    @Autowired
    private MessageSource messageSource;

    String res = messageSource.getMessage("hello", null, locale);
    

將session儲存改為Redis儲存

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.zhidianfan.pig.yd.utils.UserUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.LocaleResolver;

/**
 * 自定義國際化語言解析器
 */
public class MyLocaleResolver implements LocaleResolver {

    private static final String I18N_LANGUAGE = "i18n_language";
    private Logger log = LoggerFactory.getLogger(getClass());

    private RedisTemplate redisTemplate;
    private String local = "zh_CN";//預設環境 en_US 英文環境

    private UserUtils userUtils;

    public MyLocaleResolver(RedisTemplate redisTemplate, UserUtils userUtils) {
        this.redisTemplate = redisTemplate;
        this.userUtils = userUtils;
    }

    public MyLocaleResolver(RedisTemplate redisTemplate, String local) {
        this.redisTemplate = redisTemplate;
        this.local = local;
    }

    @Override
    public Locale resolveLocale(HttpServletRequest req) {
        String i18n_language = req.getParameter(I18N_LANGUAGE);
        Locale locale = null;
        String username = userUtils.getUserName();//使用者名稱為唯一鍵
        String k = "LANGUAGE:" + username;
        if (!StringUtils.isEmpty(i18n_language)) {//客戶請求中設定了值,以設定的國際化資訊為準
            String[] language = i18n_language.split("_");
            locale = new Locale(language[0], language[1]);
            redisTemplate.opsForValue().set(k, locale);
            log.info("設定當前使用者的語言環境:{}", i18n_language);

        } else {//客戶未在請求中設定國際化引數

            Locale v = (Locale) redisTemplate.opsForValue().get(k);
            if (v != null) {
                locale = v;
                log.info("從快取中獲取語言環境:{}", v.getLanguage());
            } else {
                //設定預設值
                String[] language = local.split("_");
                locale = new Locale(language[0], language[1]);
                redisTemplate.opsForValue().set(k, locale);
                log.info("設定當前使用者的預設語言環境:{}", local);

            }
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest req, HttpServletResponse res, Locale locale) {

    }

}      

SpringBoot使用Undertow

pom

首先要把tomcat依賴排除掉。

不過有時候,tomcat的依賴不一定是由web的starter引入進來的,這個需要自己看清楚仔細咯

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--高效能web伺服器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

然後引入undertowstarter即可

配置

# 設定IO執行緒數, 它主要執行非阻塞的任務,它們會負責多個連線, 預設設定每個CPU核心一個執行緒
# 不要設定過大,如果過大,啟動專案會報錯:開啟檔案數過多

server.undertow.io-threads=16

# 阻塞任務執行緒池, 當執行類似servlet請求