Spring Boot 的 Web 開發

ETFOX發表於2018-05-13

web 開發是開發中至關重要的一環,web 開發的核心內容包括內嵌 servlet 容器和 spring MVC。

1.springboot 的 web 開發支援

    springboot 提供了 spring-boot-starter-web 為 web 開發予以支援,spring-boot-starter-web 為我們提供了嵌入的 Tomcat 以及 Spring MVC 的依賴。而 web 相關的自動配置儲存在 spring-boot-autoconfigure.jar 和 org.springframework.boot.web 下。

2.Thymeleaf 模板引擎

    儘可能的少用 JSP,因為在內嵌 Servlet 容器上 JSP 執行有點問題(內嵌 Tomcat、jetty 不支援以 jar 形式執行的 JSP,Undertow 不支援 JSP)。

    springboot 提供了大量的模板引擎,包括 FreeMark、Groovy、Thymeleaf、Velocity 和 Mustache,Spring Boot  中推薦使用 Thymeleaf 作為模板引擎,因為 Thymeleaf 提供了完美的 Spring MVC  的支援。

2.1 Thymeleaf 基礎知識

    Thymeleaf 是一個 Java 類庫,它是一個 xml/xhtml/html5 的模板引擎,可以作為 MVC 的 Web 應用的 VIEW 層。

    Thymeleaf 還提供了額外的模組與 Spring MVC  整合,所以我們可以使用 Thymeleaf 完全的替代 JSP。

    詳見:http://www.thymeleaf.org

2.2 與 Spring MVC 整合

    在 springmvc 中,若我們需要整合一個模板引擎的話,需要定義 ViewResolver,而 ViewResolver 需要定義一個 View ,定義字首字尾。Thymeleaf 為我們提供的相關的驅動。

    

以上為預設提供的配置。

下面示例一個簡單的例子:

package com.pangu.demo;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import com.pangu.demo.web.entity.Person;

@Controller
@SpringBootApplication
public class WebApplication {
	public static void main(String[] args) {
		SpringApplication.run(WebApplication.class, args);
	}
	@RequestMapping("/thy")
	public String thymeleaf(Model model){
		Person person = new Person("etfox", 22);
		
		List<Person> people = new ArrayList<Person>();
		Person person1 = new Person("etfox1", 22);
		Person person2 = new Person("etfox2", 22);
		Person person3 = new Person("etfox3", 22);
		
		people.add(person1);
		people.add(person2);
		people.add(person3);
		
		model.addAttribute("singlePerson", person);
		model.addAttribute("people", people);
		
		return "index";
	}
	
	@RequestMapping(value = "/json",produces={MediaType.APPLICATION_JSON_VALUE})
	public String json(Model model) {
		Person single = new Person("aa",11);
		model.addAttribute("single", single);
		return "jsonView";
	}
	
	@Bean
	public MappingJackson2JsonView jsonView() {
		MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
		return jsonView;
	}
	
}

index.jsp:

<html xmlns:th="http://www.thymeleaf.org">
  <head>
  	 <meta content="text/html;charset=UTF-8"/>
  	 <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet"/> 
    <link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet"/>
  </head>
  <body>
  
  <div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">訪問model</h3>
    </div>
    <div class="panel-body">
        	<span th:text="${singlePerson.name}"></span> 
    </div>
  </div>
  
  <div th:if="${not #lists.isEmpty(people)}">
	  <div class="panel panel-primary">
	    <div class="panel-heading">
	        <h3 class="panel-title">列表</h3>
	    </div>
	    <div class="panel-body">
	        <ul class="list-group">
				<li class="list-group-item" th:each="person:${people}">
				    <span th:text="${person.name}"></span>
				   	<span th:text="${person.age}"></span>
				   	<button class="btn" th:onclick="'getName(\'' + ${person.name} + '\');'">獲得名字</button>
				</li>
	        </ul>
	    </div>
	 </div>
 </div>
  
  <script th:src="@{jquery.min.js}" type="text/javascript"></script><!-- 2 -->
  <script th:src="@{bootstrap/js/bootstrap.min.js}"></script><!-- 2 -->
  
  <script th:inline="javascript">
  	var single = [[${singlePerson}]];
  	console.log(single.name+"/"+single.age)
  	
  	function getName(name){
  		console.log(name);
  	}
  </script>
  
  </body>
 </html>

專案結構:



run。。。。。。。。。。。。。。。。



後記:SB 在自定義攔截器之後,之前的預設配置將不再生效,也就是說你配置的預設的靜態路徑啥的,剛碰到的坑!!!!!


3.Web 相關配置

3.1:Spring Boot 提供的自動配置

    通過檢視 WebMvcAutoConfiguration  及 WebMvcProperties 的原始碼,可以發現 Spring Boot  為我們提供瞭如下的自動配置。

3.1.1 自動配置的 ViewResolver

    (1).ContentNegotiating ViewResolver

    這是 spring MVC 提供的一個特殊的 ViewResolver,ContentNegotiatingViewResolver 不是自己處理 view,而是代理給不同的 ViewResolver 來處理不同的 View,所以它有最高的優先順序。

    (2).BeanNameViewResolver

    在控制器(@Controller)中的一個方法的返回值的字串(檢視名)會根據 BeanNameViewResolver 去查詢 Bean 的名稱為返回字串的 View  來渲染檢視。是不是不好理解,下面舉個栗子。

    定義 BeanNameViewResolver 的 Bean: 

@Bean
	public BeanNameViewResolver beanNameViewResolver(){
		BeanNameViewResolver resolver = new BeanNameViewResolver();
		return resolver;
	}

定義一個 View 的 Bean ,名稱為 jsonView:

@Bean
	public MappingJackson2JsonView jsonView() {
		MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
		return jsonView;
	}
在控制器中,返回值為字串 jsonView, 它會找 Bean 的名稱為 jsonView 的檢視來渲染:
@RequestMapping(value = "/json",produces={MediaType.APPLICATION_JSON_VALUE})
	public String json(Model model) {
		Person single = new Person("aa",11);
		model.addAttribute("single", single);
		return "jsonView";
	}

(3).InternalResourceViewResolver

這個是一個極為常用的 ViewResolver,主要通過設定字首字尾,以及控制器中方法來返回檢視名的字串,以的到實際的頁面,springboot 的原始碼如下:

@Bean
@ConditionalOnMissingBean(InternalResourceViewResolver.class)
public InternalResourceViewResolver defaultViewResolver(){
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(this.prefix);//字首
    resolver.setSuffix(this.suffix);//字尾
    return resolver;
}
3.1.2 自動配置的靜態資源

在自動配置類的 addResourceHandlers 方法中定義了以下靜態資源自動配置。

(1)類路徑檔案

把類路徑下的/static、/public、/resources 和 /META-INF/resources 資料夾下的靜態檔案直接對映為 /**,可以通過 http://localhost:xxxx/** 直接訪問。

(2)webjar

何謂 webjar,webjar 就是我們常用的指令碼框架封裝的 jar 包中的 jar 包

把 webjar 的 META-INF/resources/webjar/ 下的靜態檔案對映為 /webjar/**,可以通過 http://localhost:8080/webjar/** 來訪問。

3.1.3 自動配置的 Formatter 和 Converter

關於自動配置 Formatter 和 Converter,我們可以看一下 WebMvcAutoconfiguration 類中的定義:

@Override
		public void addFormatters(FormatterRegistry registry) {
			for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
				registry.addConverter(converter);
			}
			for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
				registry.addConverter(converter);
			}
			for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
				registry.addFormatter(formatter);
			}
		}

		private <T> Collection<T> getBeansOfType(Class<T> type) {
			return this.beanFactory.getBeansOfType(type).values();
		}
從程式碼中可以看出,只要我們定義了 Converter GenericConverter Formatter 介面類的實現 bean ,這些 bean 就會自動註冊到 spring MVC 中。
3.1.4 自動配置的 HttpMessageConverters

在 WebMvcAutoConfiguration 中,我們註冊了 messageConverters,程式碼如下:

public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
				WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
				@Lazy HttpMessageConverters messageConverters,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConverters = messageConverters;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider
					.getIfAvailable();
		}


private final HttpMessageConverters messageConverters;
@Override
		public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
			converters.addAll(this.messageConverters.getConverters());
		}
在 springboot 中如果要新增自定義的 HttpMessageConverter , 則只需定義一個你自己的 HttpMessageConverters  的 Bean,然後在此 bean 中註冊自定義 HttpMessageConverter  即可。
3.1.5 靜態首頁的支援

把靜態 index.html 檔案放置在如下目錄。

classpath:/META-INF/resources/index.html

classpath:/resources/index.html

classpath:/static/index.html

classpath:/public/index.html

 當我們訪問應用根目錄 http://localhost:xxxx/ 時,會直接對映。

4.接管 Spring Boot 的 Web 配置

如果 Spring Boot 提供的 Spring MVC 預設配置不符合你的需求,則可以通過一個配置類(註解有@Configuration 的類)加上 @EnableWebMvc 註解來實現完全自己控制的 MVC 配置。

當然通常情況下可以滿足絕大多數的需求。在既需要保留 Spring Boot 提供的便利,又需要增加自己的額外配置的時候,可以定義一個配置類繼承 WebMvcConfigurerAdapter,無需使用 @EnableWebMvc 註解:

package com.pangu.config;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**

 * @ClassName: WebMvcConfig

 * @Description: TODO 接管 SpringBoot 的 Web 配置,並不會覆蓋 WebMvcAutoConfiguration 中的 addViewControllers

 * @author etfox

 * @date 2018年7月1日 下午3:06:16

 *

 * @Copyright: 2018 www.etfox.com Inc. All rights reserved.

 */

@Configuration

public class WebMvcConfig implements WebMvcConfigurer {

    

    /**

     * <p>Title: addViewControllers</p>   

     * <p>Description:http://127.0.0.1:8080/xx </p>   

     * @param registry   

     * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addViewControllers(org.springframework.web.servlet.config.annotation.ViewControllerRegistry)

     */

    @Override

    public void addViewControllers(ViewControllerRegistry registry) {

        registry.addViewController("/xx").setViewName("test");

    }

    

}

值得注意的是,在這裡重寫 addViewControllers 方法,並不會覆蓋 WebMvcAutoConfiguration 中的 addViewControllers( 此方法中,SpringBoot 將”/“對映至 index.html),這也就意味著我們自己的配置和 springboot 的配置同時生效,這也是我們推薦的新增自己的 MVC 配置的方式。


5. 註冊 servlet、filter、listener

    當使用嵌入式的 servlet 容器時,我們通過將 servlet、filter 和 listener 宣告為 spring bean 而達到註冊的效果:或者註冊 ServletRegistrationBean、FilterRegistrationBean  和 ServletListenerRegistrationBean  的 Bean.




6.Tomcat 配置

    servlet 容器的配置,Spring Boot 預設內嵌的 Tomcat 為 servlet 容器。

    6.1 配置 Tomcat

        關於 Tomcat 的所有屬性都在 org.springframework.boot.autoconfigure.web.ServerProperties 配置類中做了定義,我們只需在 application.properties 配置屬性做配置即可。通用的 servlet 容器配置都以 server 作為字首,而Tomcat 特有配置都以 server 作為字首,而 Tomcat 特有配置都以 server.tomcat 作為字首。示例:




7.Favicon 配置

    將 ico 檔案配置在靜態路徑下即可,通常根目錄。

8.WebSocket

    8.1 什麼是 WebSocket

        WebScoket 為瀏覽器和服務端提供了雙向非同步通訊的功能,即瀏覽器可以向服務端傳送訊息,服務端也可以向瀏覽器端傳送訊息。WebSocket 需瀏覽器的支援,如 IE 10+、Chrome 13+, Firefox 6+, 這對我們現在的瀏覽器來說都不是問題。

        WebScoket 是通過一個 socket 來實現雙工非同步通訊能力。但是直接使用 WebSocket( 或 SockJS:WebSocket  協議的模擬,增加了當瀏覽器不支援的時候相容支援)協議開發程式顯得特別繁瑣,我們使用它的子協議 STOMP ,它是一個更高階別的協議, STOMP 協議使用一個基於幀的格式來定義訊息,與 HTTP 的 request 和 response 類似(具有類似於 @RequestMapping 的 @MessageMapping),我們會在後面實戰內容中觀察 STOMP 的幀。

    8.2 Spring Boot  提供的自動配置

        Spring Boot 對內嵌的 Tomcat(7/8)、Jetty9 和 Undertow 使用 websocket 提供了支援,配置原始碼存在 autoconfigure.websocket 下。


Spring Boot 為 WebSocket 提供的 stater pom 是 spring-boot-starter-websocket。







相關文章