03 SpringBoot對Web的支援
文章目錄
- 支援
- Thymealeaf模板引擎
- 自動配置的Formatter和Converter
- 接管Spring Boot 的Web配置
- 國際化
- SpringBoot使用Undertow
- @ControllerAdvice
- Tomcat配置
- 檔案上傳
- SSL與HTTPS
- Favicon配置
- WebSocket
- 基於BootStrap、AngularJS的現代Web應用
- war還是jar?
支援
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>
然後引入undertow
的starter
即可
配置
# 設定IO執行緒數, 它主要執行非阻塞的任務,它們會負責多個連線, 預設設定每個CPU核心一個執行緒
# 不要設定過大,如果過大,啟動專案會報錯:開啟檔案數過多
server.undertow.io-threads=16
# 阻塞任務執行緒池, 當執行類似servlet請求