spring boot 1.x完整學習指南(含各種常見問題servlet、web.xml、maven打包,spring mvc差別及解決方法)


spring boot 入門

  關於版本的選擇,spring boot 2.0開始依賴於  Spring Framework 5.1.0,而spring 5.x和之前的版本差距比較大,而且應該來說還沒有廣泛的使用,所以生產中,一般來說目前還是建議使用spring boot 1.x,目前最新版本是1.5.9,官方手冊 https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/pdf/spring-boot-reference.pdf
   spring boot相對於原來來說,有一個較大的區別就是極大的弱化了spring配置文件的重要性,幾乎所有的配置官方文檔中都使用注解,這對於一直以來使用配置文件為主的同學來說,需要去適應的地方(注:對於有過框架開發經驗來說,似乎特別偏好使用注解來進行各種設置,筆者也一樣,其實不是因為配置本身不好用,主要是為了盡可能避免大部分開發其實不care各種配置的精確性),spring框架的主要配置類注解可參考 https://docs.spring.io/spring/docs/4.3.18.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#beans-java,其中最常用的是@Configuration類注解,@Bean方法注解,和@ComponentScan類注解。
  spring boot應該來說一方面是對標tomcat外置容器的war啟動方式(當然它本身也支持外置容器),另一方面是通過約定俗成來簡化配置。對於不提供http服務的java應用來說,java service wrapper(https://wrapper.tanukisoftware.com/doc/english/download.jsp)也提供了使用java -jar啟動應用的方式,spring boot也推薦這種方式。
對於使用spring boot,官方推薦設置maven工程的parent為spring-boot-starter-parent,如下:
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>

  打開spring-boot-starter-parent的pom.xml文件,可以發現spring-boot-starter-parent提供了一些maven的默認設置,比如build中配置文件的路徑,在dependency-management節點中設置了很多spring自身庫以及外部三方庫的版本等,這樣我們引入依賴的時候就不需要設置版本信息了,spring-boot-starter-parent應該來說是整體spring-boot的骨架管理者,各具體的starter則是特定類型應用的骨架,比如spring-boot-starter-web是web應用的骨架。

  要開發web應用,還需要引入spring-boot-starter-web依賴即可。如下:
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
  查看maven依賴樹,可以發現spring-boot-starter-web實際上引入了很多我們在開發spring mvc應用時的依賴包,以及嵌入式的tomcat。
  注:如果我們想知道完整的spring-boot-starter-*,可以從https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#using-boot-starter和https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-starters查詢到。
 
=============================
  @EnableAutoConfiguration注解是spring boot引入的最主要注解,其完整類名是org.springframework.boot.autoconfigure.EnableAutoConfiguration。其含義是告訴Spring Boot,根據已知信息包括jar依賴判斷下用戶希望如何配置spring,它通常置於應用的啟動類(也就是帶main函數的類,所有的java應用都是由main類啟動,tomcat也一樣)上,如下所示:
package com.yidoo.springboot.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

   同時它也定義了默認的自動掃描根目錄。因為spring-boot-starter-web增加了tomcat和spring mvc,所以自然而然就認為是web應用了,其實現原理其實就是根據有沒有引入特定jar來判斷。

  不需要配置web.xml,也不需要配置spring-mvc.xml、spring-context.xml,就可以啟動運行了。
  運行 maven spring-boot:run就可以啟動spring boot應用了,在eclipse下,可以maven build ...輸入,spring-boot:run,如下:

   這樣maven就會開始打包,並啟動,如下:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.9.RELEASE)

2018-06-12 14:00:18.782  INFO 17268 --- [           main] Example                                  : Starting Example on TF017564 with PID 17268 (D:\eclipse\workspace\spring-boot-example\target\classes started by TF017564 in D:\eclipse\workspace\spring-boot-example)
2018-06-12 14:00:18.786  INFO 17268 --- [           main] Example                                  : No active profile set, falling back to default profiles: default
2018-06-12 14:00:19.052  INFO 17268 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5455de9: startup date [Tue Jun 12 14:00:19 CST 2018]; root of context hierarchy
2018-06-12 14:00:21.201  INFO 17268 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2018-06-12 14:00:21.220  INFO 17268 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-06-12 14:00:21.221  INFO 17268 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.23
2018-06-12 14:00:21.398  INFO 17268 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-06-12 14:00:21.399  INFO 17268 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2348 ms
2018-06-12 14:00:21.661  INFO 17268 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-06-12 14:00:21.686  INFO 17268 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-06-12 14:00:21.688  INFO 17268 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-06-12 14:00:21.690  INFO 17268 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-06-12 14:00:21.691  INFO 17268 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-06-12 14:00:22.331  INFO 17268 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5455de9: startup date [Tue Jun 12 14:00:19 CST 2018]; root of context hierarchy
2018-06-12 14:00:22.466  INFO 17268 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto java.lang.String Example.home()
2018-06-12 14:00:22.476  INFO 17268 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-06-12 14:00:22.478  INFO 17268 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-06-12 14:00:22.529  INFO 17268 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-12 14:00:22.529  INFO 17268 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-12 14:00:22.607  INFO 17268 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-12 14:00:22.829  INFO 17268 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-06-12 14:00:22.928  INFO 17268 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-06-12 14:00:22.936  INFO 17268 --- [           main] Example                                  : Started Example in 4.606 seconds (JVM running for 36.043)
2018-06-12 14:00:46.142  INFO 17268 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-06-12 14:00:46.142  INFO 17268 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-06-12 14:00:46.166  INFO 17268 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 24 ms
  學習spring boot原理,查看啟動日志是必要的,可以發現,總體和我們原來的日志類似,但也增加了一些額外的日志,后續筆者會從源碼講解spring boot的原理。
  既然@ EnableAutoConfiguration是spring boot的核心,我們就需要了解下,spring boot為那些應用提供了auto-config,https://docs.spring.io/spring-boot/docs/current/reference/html/auto-configuration-classes.html包含了完整的自動配置列表。自動配置的類是由spring-boot-autoconfigure模塊管理的,而spring-boot-autoconfigure模塊是由spring-boot-starter(它是Spring Boot的核心starter,包含了對自動配置、日志、spring框架核心以及yaml的依賴)引入的。技術所有的其他spring boot starter都依賴於spring-boot-starter,spring cloud也采用類似的組織方式。如下:

  相當於原來的各種繁瑣,spring-boot確實簡化了開發過程。

  雖然可以直接運行了,但是通常我們需要部署到其他環境,所以還是需要打個可執行的包出來。原來的做法通常是,我們只是打war,依賴於目標服務器已經安裝的tomcat等容器,使用spring boot,我們可以打出一個完全自我包含的可執行jar,只要目標環境安裝了JRE即可。可執行jar通常指的是包含了所有依賴的jar的jar包,比如dubbo就可以認為是自我包含的。要創建可執行的jar,需要在pom中增加spring-boot-maven-plugin插件,如下:
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  現在,我們就可以進行maven打包了。打出來的有兩個包:myproject-0.0.1-SNAPSHOT.jar和myproject-0.0.1-SNAPSHOT.jar.original。一個是合並了依賴的,一個沒有合並依賴。
  注意:打開合並了依賴的jar,我們可以發現它和dubbo打包不同,不是純粹的從依賴jar中取出class合並到一個jar,而是采用了自己的一套規則,具體可參考官方文檔11.5 Creating an executable jar一節。
  在cmd中執行java -jar myproject-0.0.1-SNAPSHOT.jar就可以啟動了,如下:
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.9.RELEASE)
2018-06-12 14:21:40.510 INFO 18208 --- [ main] Example : Starting Example on TF017564 with PID 18208 (D:\eclipse\workspace\spring-boot-example\target\myproject-0.0.1-SNAPSHOT.jar started by TF017564 in D:\eclipse\workspace\spring-boot-example\target)
2018-06-12 14:21:40.518 INFO 18208 --- [ main] Example : No active profile set, falling back to default profiles: default
2018-06-12 14:21:40.636 INFO 18208 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@69663380: startup date [Tue Jun 12 14:21:40 CST 2018]; root of context hierarchy
2018-06-12 14:21:43.156 INFO 18208 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2018-06-12 14:21:43.190 INFO 18208 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2018-06-12 14:21:43.195 INFO 18208 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.23
2018-06-12 14:21:43.407 INFO 18208 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2018-06-12 14:21:43.407 INFO 18208 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2776 ms
2018-06-12 14:21:43.659 INFO 18208 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2018-06-12 14:21:43.670 INFO 18208 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-06-12 14:21:43.673 INFO 18208 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-06-12 14:21:43.674 INFO 18208 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-06-12 14:21:43.674 INFO 18208 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2018-06-12 14:21:44.175 INFO 18208 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@69663380: startup date [Tue Jun 12 14:21:40 CST 2018]; root of context hierarchy
2018-06-12 14:21:44.343 INFO 18208 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto java.lang.String Example.home()
2018-06-12 14:21:44.350 INFO 18208 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-06-12 14:21:44.351 INFO 18208 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-06-12 14:21:44.412 INFO 18208 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-12 14:21:44.413 INFO 18208 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-12 14:21:44.479 INFO 18208 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-12 14:21:44.757 INFO 18208 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2018-06-12 14:21:44.887 INFO 18208 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-06-12 14:21:44.897 INFO 18208 --- [ main] Example : Started Example in 5.143 seconds (JVM running for 6.129)
  從上可知,基本相同。
  現在基於spring boot的應用跑起來了,接下去就要開始看我們原來開發中的那些配置、參數、tomcat端口等如何設置。
  spring boot本身對於代碼結構沒有要求,不過一般來說,應該將main應用類放在根package,比如com.yidoo.k3c。具體的業務代碼在下一級的package如下:

 

  然后在main應用類Application上放置@EnableAutoConfiguration注解,這樣,其實就定義了自動組件掃碼時的默認根目錄,@ComponentScan注解的時候就不需要聲明basePackage屬性,它會自動掃描main應用類所在的package以及子package,主應用類還應該聲明@Configuration,這是推薦的做法。
  不過由於可能會有很多配置,所以配置類很可能會有多個,可以通過@Import注解導入其他配置類(這不是spring boot的特性)。
package com.yidoo.springboot.example.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.yidoo.springboot.example.service.ExampleService;

@RestController
public class ExampleWeb {
    
    @Autowired
    private ExampleService exampleSerivce;
    
    @RequestMapping("/")
    String home() {
        return exampleSerivce.get();
    }
}
package com.yidoo.springboot.example.service;
import org.springframework.stereotype.Service;


@Service
public class ExampleService {

    public String get() {
        return "Hello World";
    }

}

  可以發現,從應用層面來說,和原來開發基本無異,基本上就是引導類由tomcat變成了我們定義的。啟動后,通過localhost:8080可以返回hello world。

  Spring Boot的自動配置特性是通過在主應用類上增加@EnableAutoConfiguration或@SpringBootApplication注解(它是個快捷方式)啟用的,需要注意的,只應該定義一個主應用類,並加上@EnableAutoConfiguration注解,其他輔助配置類不要加上@EnableAutoConfiguration注解。
  有些時候,我們並不希望使用starter定義,或者覺得spring自動配置的類不是我們想要的,可以通過啟動應用時帶上--debug標志查看原因(這和執行計划性質類似),也可以在@EnableAutoConfiguration注解上聲明exclude屬性排除某些配置類或者具體類,例如@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})(更多可參考16.2 Disabling specific auto-configuration)。
  我們幾乎總是在主應用類上同時聲明@Configuration, @EnableAutoConfiguration和@ComponentScan注解,因為他們基本上是一起使用的,所以spring提供了一個快捷方式@SpringBootApplication,它相當於同時聲明@Configuration, @EnableAutoConfiguration和@ComponentScan。
  SpringApplication是用來啟動應用的類,其構造器參數是spring bean的配置源,大多數情況下指向@Configuration類。默認情況下,啟動過程中會執行下列操作:
  • 創建合適的ApplicationContext實例;
  • 注冊CommandLinePropertySource實例,將命令行參數暴露為Spring屬性;
  • 刷新application context,加載所有單例;
  • 觸發所有 CommandLineRunner實例;
  同ApplicationContext中各種生命周期事件一樣,因為ApplicationContext運行在SpringApplication中,所以SpringApplication類似的提供了很多生命周期事件。SpringApplication可以說是Spring Boot運行時的核心主控類,應該好好看看其實現,javadoc地址為 https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/api/org/springframework/boot/SpringApplication.html
 

配置文件

  同war包一樣,由於各運行環境不同,我們會有很多不同的配置項需要自定義,比如jdbc連接、redis連接、注冊中心等等。當我們打出可執行的jar后,需要一種方式來設置某些參數值,Spring Boot提供了多種配置文件格式(這一點改進應該來說相當的不錯)來設置這些配置值,支持文本的properties配置文件、YAML(elastic出品的ELK使用的就是YAML配置文件)、JSON、環境變量、命令行參數。這些屬性可以通過@Value注解、Spring Environment( https://docs.spring.io/spring/docs/4.3.18.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#beans-environment)抽象、或@ConfigurationProperties綁定到對象的方式訪問(主要是為了確保配置值類型安全, https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#boot-features-external-config-typesafe-configuration-properties)。spring Boot加載配置的優先級從高到低(完整參考24. Externalized Configuration)常用的主要為:
  1. Devtools配置文件中的值
  2. 命令行(默認情況下,SpringApplication 會將命令行參數轉換為property ,同時添加到Environment)
  3. ServletConfig初始化參數
  4. ServletContext初始化參數
  5. Java系統屬性
  6. 環境變量
  7. RandomValuePropertySource(主要用來生成隨機值,random.*格式,適合用來生成隨機值,參考24.1 Configuring random values)
  8. jar包外的application-{profile}.properties
  9. jar包內的application-{profile}.properties
  10. jar包外的application.properties
  11. jar包內的application.properties
  12.  @Configuration 類上的@PropertySource注解
  13. SpringApplication.setDefaultProperties聲明的默認屬性
  對於jar包外的properties,其搜索順序為:
  1. 當前運行目錄的/config子目錄
  2. 當前目錄
  3. classpath的/config中
  4. classpath
  有些時候,我們會有多個配置文件,比如jdbc的、mq的、dubbo的,此時可以通過聲明spring.config.location明確指定配置文件,如下:
   java -jar myproject.jar --spring.config.location=classpath:/override.properties,file:/v.properties
  spring.config.location搜索的順序為從最后開始。
  profile相關的配置文件搜索順序同普通application.properties。
  配置文件中可以使用先前定義過的配置參數,因為Environment會進行回溯。所以可以像下面這樣使用:
  app.name=MyApp app.description=${app.name} is a Spring Boot application
 
  在以前,我們要為某些類比如Node/Plugin配置屬性的時候,需要使用@Value("${property}")注解一個個的注入,有時候配置屬性有幾十個,而且層次嵌套,在spring boot中,不需要這么做了,spring boot提供了@ConfigurationProperties(實際上這和spring boot毫無關系,純粹故意的)注解可以自動主動所有相關屬性,比如:
@ConfigurationProperties("foo")
public class FooProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();
 
    public static class Security {

        private String username;

        private String password;
    }
    ......
}
  可以自動匹配下列屬性:
  • foo.enabled, 默認false
  • foo.remote-address, 只要可從String轉換過來
  • foo.security.username
  • foo.security.password
  • foo.security.roles, String集合
  其實spring boot新增的很多特性跟spring boot本身沒有什么關系,但是都增加到org.springframework.boot包中了,其實應該放在core或者context或者context-support中更加合理的,只能說是有意為之。
 
  為了讓spring知道哪些類需要自動注入配置,需要在配置類上聲明@EnableConfigurationProperties注解,列出具體的類,如下:
@Configuration
@EnableConfigurationProperties(FooProperties.class)
    public class MyConfiguration {
}
這樣,FooProperties類就和常規的bean一樣,可以通過@Autowired注入使用了。
 
@ConfigurationProperties類還可以結合Spring的@Validated注解,如果配置類上標記了Validated注解,具體屬性上就可以使用JSR-303 中定義的注解,Spring boot會自動驗證(這個特性也一樣,跟spring boot毫無關系)。例如:
@ConfigurationProperties(prefix="foo")
@Validated
public class FooProperties {

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

}

對於嵌套屬性,要驗證的話,直接屬性值必須標記上@Valid注解以便觸發校驗,例如:

@ConfigurationProperties(prefix="connection")
@Validated
public class FooProperties {

    @NotNull
    private InetAddress remoteAddress;

    @Valid
    private final Security security = new Security();

    // ... getters and setters

    public static class Security {

        @NotEmpty
        public String username;

        // ... getters and setters

    }

}

日志

spring boot日志配置 參考https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html#howto-configure-logback-for-logging
Spring Boot有一個LoggingSystem抽象,他會根據classpath中可以找到的日志實現選擇可用的,如果Logback可用,它會優先選擇。
如果只是希望為各種logger設置級別,只要在application.properties中增加logging.level開頭的配置即可,如下:
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR
要設置文件的位置,增加logging.file開頭的配置
如果要更細粒度的配置,則需要使用LoggingSystem的原生配置格式,對於logback,Spring Boot會加載classpath:logback.xml,具體路徑搜索順序參考spring boot學習筆記。
原則上,不應該在application.properties中設置日志配置
spring boot提供了一些logback模板,可以參考或者適當修改,logback官方文檔參考https://logback.qos.ch/documentation.html。

如果Log4j 2在classpath上,Spring Boot也支持(注:spring boot不支持1.2.x),如果使用了各種starter組裝依賴,則需要排除掉Logback,否則啟動的時候會報沖突。如果沒有使用starter,則需要額外引入spring-jcl依賴。
配置log4j最簡單的方法就是使用spring-boot-starter-log4j2,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

  為了確保java.util.logging執行的debug會被Log4j 2記錄,需要配置系統屬性java.util.logging.manager為org.apache.logging.log4j.jul.LogManager。
log4j 2手冊可以參考http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf

  注:一般來說,使用logback或者log4j2其實關系並不大的,但實際上,對於高負載、復雜邏輯的系統,我們會發現一個業務服務上最終響應時間上rpc調用次數、日志、序列化占據了挺大的比例。

  對於spring boot下配置log4j 2,並支持MDC(我們都提到了跨界點日志上下文關聯的重要性,參考寫給大忙人的CentOS 7下最新版(6.2.4)ELK+Filebeat+Log4j日志集成環境搭建完整指南一文),官方並沒有文檔說明,網上也沒有直接提及,雖然如此,鑒於上一段所述原因,筆者還是研究了怎么樣才能讓spring boot使用log4j2又支持MDC(參考寫給大忙人的spring cloud 1.x學習指南一文)。

application.yml中增加如下:
logging:
  config: classpath:log4j2.xml
log4j2.xml配置如下:

    <Properties>
        <Property name="pattern">%d{yyyy-MM-dd HH:mm:ss,SSS} [%X{X-B3-TraceId},%X{X-B3-SpanId},%X{X-B3-ParentSpanId},%X{X-Span-Export}] %5p %c{1}:%L - %m%n</Property>
    </Properties>
輸出為[3bfdd6f72352ef7e,3bfdd6f72352ef7e,,false]

或者:

<Properties>
    <Property name="pattern">%d{yyyy-MM-dd HH:mm:ss,SSS} %X %5p %c{1}:%L - %m%n</Property>
</Properties>

輸出為{X-B3-SpanId=3bfdd6f72352ef7e, X-B3-TraceId=3bfdd6f72352ef7e, X-Span-Export=false}

除了這兩種自帶格式外,還可以自定義,例如在HandlerInterceptor接口的preHandle方法中設置上下文如下:

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getContextPath().length() > 1 ? request.getRequestURI().replace(request.getContextPath(), "") : request.getRequestURI();
        
        String sessionId = getSessionCookie(request);
        if (StringUtils.isEmpty(sessionId)) {
            logger.warn("請求" + path + "的sessionId為空!");
            response.sendRedirect(appWebHomeUrl + "/logout.html");
            return false;
        }
        try {
            String session = redisUtils.get(REDIS_SESSION_ID_PREFIX + sessionId).toString();
            String traceId = sessionId.substring(0, 8) + "_" + path + "_" + formatter.format(new Date());
            
            // 設置log4j2 mdc
            ThreadContext.push(traceId);
            return true;
        } catch (NullPointerException e) {
            logger.warn("請求" + path + "的sessionId不存在或已失效!");
            response.sendRedirect(appWebHomeUrl + "/logout.html");
            return false;
        }
    }

同理,在postHandle清除,如下:

    @Override
    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object handler, ModelAndView arg3)
            throws Exception {
        ThreadContext.clearAll();
    }

log4j2.xml Pattern配置如下:

<Pattern>%x %d{yyyy-MM-dd HH:mm:ss} [%r] [%c{1.}]-[%p] %t %l %m%n</Pattern>

輸出格式為:

[c327093e_/filesend_21:31:39] 2018-11-25 21:31:39 [138805] [c.h.t.a.d.c.CheckItemController]-[DEBUG] http-nio-8086-exec-8 {"operatorId":null,"memberId":null,"memberName":null,"branchId":null,"branchIds":null,"mobile":null,"operatorName":null,"realName":null,"email":null,"sessionKey":null,"sessionId":null,"traceId":"c327093e_/filesend_21:31:39"}

相比默認格式的可讀性要好得多,如果使用了rpc比如dubbo或者其它,可以通過filter進行透傳。

相關參考文檔

http://logging.apache.org/log4j/2.x/manual/thread-context.html
https://github.com/spring-cloud/spring-cloud-sleuth
https://zipkin.io/pages/instrumenting.html
https://github.com/openzipkin/b3-propagation
http://ryanjbaxter.com/cloud/spring%20cloud/spring/2016/07/07/spring-cloud-sleuth.html
https://github.com/spring-cloud/spring-cloud-sleuth/issues/162

如果繼承了ELK的話,logstash日志解析到字段配置如下:
filter {
  # pattern matching logback pattern
  grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
  }
}

 

MVC web應用

  如果希望使用Spring Boot的MVC特性,僅僅增加額外的配置,比如攔截器等,可以創建WebMvcConfigurerAdapter類型的@Configuration,但是去掉@EnableWebMvc(參見spring framework官方文檔22.16.1 Enabling the MVC Java Config or the MVC XML Namespace)注解。如果希望自定義RequestMappingHandlerMapping, RequestMappingHandlerAdapter,ExceptionHandlerExceptionResolver,可以定義一個WebMvcRegistrationsAdapter類提供上述組件。如果要完全控制Spring MVC,則在@Configuration類上加上@EnableWebMvc注解。
  Spring MVC使用HttpMessageConverter接口轉換HTTP請求和應答,spring boot包含了開箱即用的合理默認值,比如對象自動使用jackson轉換為JSON,字符串使用UTF-8編碼。
  有些時候我們需要自定義轉換器,比如對於JSON類型,通常忽略不存在的參數,可以如下配置:
@Configuration
public class MyConfiguration {

    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = ...
        HttpMessageConverter<?> another = ...
        return new HttpMessageConverters(additional, another);
    }

}

CORS(https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)支持

  spring 4.2開始,Spring MVC對CORS開箱即用的支持,可以通過在控制器方法或者類上標記  @CrossOrigin 注解即可,也可以通過全局性的在WebMvcConfigurer 中注冊,如下所示:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(false).maxAge(3600);
    }
}

嵌入式容器的配置

 
Spring Boot支持嵌入式的Tomcat, Jetty, 以及Undertow,默認情況下8080端口。
當使用嵌入式容器時,所有servlet規范中的組件Servlets, Filters、listeners等都可以通過spring bean的方式注冊,這樣就可以直接使用application.properties以及各種依賴bean,相對於原來方便了不少。
除了作為bean注冊外,還可以通過使用@ServletComponentScan注解自動注冊標記了@WebServlet, @WebFilter, @WebListener注解的類。
注:@ServletComponentScan不能用於標准容器。
 
嵌入式Web應用上下文EmbeddedWebApplicationContext
對於嵌入式容器,spring boot使用了一種新的ApplicationContext類型,EmbeddedWebApplicationContext 是一個特殊的WebApplicationContext ,它會在啟動時尋找一個EmbeddedServletContainerFactory bean,並使用它啟動自己。通常TomcatEmbeddedServletContainerFactory, JettyEmbeddedServletContainerFactory, 或UndertowEmbeddedServletContainerFactory會自動配置。

自定義容器配置

某些servlet容器配置可以通過Spring Environment屬性配置,用戶可以定義在application.properties中。主要包括:
  • server.port
  • server.address
  • server.session.timeout

JDBC等配置

 spring boot通過spring.datasource.*配置暴露DataSource屬性,如下:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
對於持久化部分,因為我們使用的是druid,所以不需要,即使是使用dbcp2和mysql,我們也在連接上設置了某些特殊的配置以使得系統更加穩定和高性能,如果自動包含了的話,可以排除掉相應的starter配置。redis、mq等雷同。
=======================
所以,總體來說,spring boot本身是很簡單的,它的目的主要應該是提供另外一種更加self-contain的部署方式、同時從第三方公正機構的角度規范了開發,這一點其實非常重要,至於說開發效率本身上,倒不見得會提高。
 
最后,很重要的是,前面我們看到application.yml有各種配置,那我們怎么知道到底有哪些配置呢?https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html列出了完整的配置屬性清單。
 

相關問題

SpringBoot 文件上傳臨時文件路徑被自動清空。
解決方法:
在啟動的額環境變量里面添加參數:-Djava.io.tmpdir = /xxx
或:
在代碼中增加系統默認目錄配置 ,如下:
@Bean
MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setLocation("/app/tmp");
    return factory.createMultipartConfig();
}

 在windows中,如果指向c:\目錄,可能會提示無權限,需要注意。

指定應用的context-path

在application.properties文件中添加如下內容:

# 如果無需修改默認端口,此配置可不要
server.port=8080
# 配置次路徑后,所有的資源訪問路徑都會加上/app前綴
server.context-path=/app
需要注意的的,配置了server.context-path路徑,所有的資源,請注意,包括靜態資源,訪問地址都會加上/app前綴。

在啟動JVM時,添加如下啟動參數:

-Dserver.context-path=/app

@Component
public class CustomContainer implements EmbeddedServletContainerCustomizer {
 
    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
         container.setContextPath("/app");
    }
}

Spring Boot默認只有一個Servlet,默認會映射到根路徑/,無法像配置DispatcherServlet的方式只將@Controller的路徑指向到上下文地址。

注意:在spring boot 2.x,參數發生了變更。

weblogic集成

參考:

https://blog.csdn.net/MT_xiaoshutong/article/details/54019993

https://segmentfault.com/a/1190000015721951

條件化注入

Springboot中提供了很多條件化配置的注解,只要輸入@ConditionalOn就能出現一大堆。不過比較常用的也就幾種:

/*******************
 *   Class包含Bean *
 ******************/

// 容器中有ThreadPoolTaskExecutor類型的bean時才注入
@ConditionalOnBean(ThreadPoolTaskExecutor.class)
@ConditionalOnMissingBean(ThreadPoolTaskExecutor.class)
// 類路徑中有ThreadPoolTaskExecutor類型的bean時才注入
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@ConditionalOnMissingClass

// 在配置文件中查找hello.name的值,如果能找到並且值等於yan,就注入,如果根本就沒配,也注入,這就是matchIfMissing = true的含義
@ConditionalOnProperty(prefix = "hello", name = "name", havingValue = "yan", matchIfMissing = true)

//只在web環境下注入
@ConditionalOnWebApplication

// java8或以上環境才注入
@ConditionalOnJava(ConditionalOnJava.JavaVersion.EIGHT)

 

問題描述:spring boot使用maven的package命令打出來的包,卻不包含依賴的jar包

問題原因:打包時使用了maven默認的maven-jar-plugin插件,而不是spring-boot-maven-plugin插件

解決方法:pom中必須配置spring-boot-maven-plugin插件,而且必須指定需要執行的目標構建

<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring.boot.version}</version>
<!-- 下面可選 -->
<executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>

問題: spring boot沒有打包本地第三方庫,如src/lib下的oracle jdbc。

解決方法:在build標簽下增加下列配置。

<resources>
        <resource>
          <directory>src/lib</directory>
          <targetPath>BOOT-INF/lib/</targetPath>
          <includes>
            <include>**/*.jar</include>
          </includes>
        </resource>
</resources>

 spring servlet、listener、context param、error-page、index-page、session-timeout配置:

 啟動到一半終止,沒有日志,如下:

2019-02-19 09:27:46,343 main DEBUG Reconfiguration complete for context[name=18b4aac2] at URI E:\恆生TA\TA-BASE\trunk\Sources\stage-source\Sources\ta-base\ta-base-webapp\target\classes\log4j2.xml (org.apache.logging.log4j.core.LoggerContext@3a80515c) with optional ClassLoader: null

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.8.RELEASE)

[] 2019-02-19 09:27:47 [2662] [o.j.logging]-[DEBUG] background-preinit org.hibernate.validator.internal.util.logging.LoggerFactory.make(LoggerFactory.java:19) Logging Provider: org.jboss.logging.Log4j2LoggerProvider
2019-02-19 09:27:47,107 background-preinit DEBUG AsyncLogger.ThreadNameStrategy=CACHED
[] 2019-02-19 09:27:47 [2670] [o.h.v.i.u.Version]-[INFO] background-preinit org.hibernate.validator.internal.util.Version.<clinit>(Version.java:30) HV000001: Hibernate Validator 5.3.5.Final
[] 2019-02-19 09:27:47 [2698] [o.h.v.i.e.r.DefaultTraversableResolver]-[DEBUG] background-preinit org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver.detectJPA(DefaultTraversableResolver.java:80) Found javax.persistence.Persistence on classpath, but no method 'getPersistenceUtil'. Assuming JPA 1 environment. All properties will per default be traversable.

日志框架配置不正確到時有些信息沒有顯示,改成debug啟動可能就報錯了。如:

log4j棧溢出:

java.lang.StackOverflowError: null
at org.slf4j.impl.JDK14LoggerAdapter.fillCallerData(JDK14LoggerAdapter.java:595) ~[slf4j-jdk14-1.7.25.jar:1.7.25]
at org.slf4j.impl.JDK14LoggerAdapter.log(JDK14LoggerAdapter.java:581) ~[slf4j-jdk14-1.7.25.jar:1.7.25]
at org.slf4j.impl.JDK14LoggerAdapter.log(JDK14LoggerAdapter.java:632) ~[slf4j-jdk14-1.7.25.jar:1.7.25]
at org.slf4j.bridge.SLF4JBridgeHandler.callLocationAwareLogger(SLF4JBridgeHandler.java:221) ~[jul-to-slf4j-1.7.25.jar:1.7.25]
at org.slf4j.bridge.SLF4JBridgeHandler.publish(SLF4JBridgeHandler.java:303) ~[jul-to-slf4j-1.7.25.jar:1.7.25]
at java.util.logging.Logger.log(Logger.java:738) ~[?:1.8.0_171]
at org.slf4j.impl.JDK14LoggerAdapter.log(JDK14LoggerAdapter.java:582) ~[slf4j-jdk14-1.7.25.jar:1.7.25]

解決方法,去掉jul依賴,如下:

 

 spring boot將外部路徑添加到classpath

默認情況下,spring boot不會將可執行jar之外的目錄作為classpath的一部分,通過-classpath指定也不起作用。要使用該功能,需要使用spring boot的PropertiesLauncher特性,也就是使用zip布局,如下所示:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <layout>ZIP</layout><!-- enables PropertiesLauncher -->
            </configuration>
        </plugin>
    </plugins>
</build>

然后通過-Dloader.path=/your/folder/containing/password/file/指定作為classpath的目錄。

spring boot maven plugin所有配置:https://docs.spring.io/spring-boot/docs/current/maven-plugin/repackage-mojo.html

注意其中的executable不能為true,否則無法修改。

為了滿足監控方便,社區開發了spring boot admin,相當於spring boot版的jvisualvm類似了,1.x和2.x版本都支持,參見https://github.com/codecentric/spring-boot-admin。

參考:

https://stackoverflow.com/questions/46728122/add-an-external-xml-file-containing-passwords-to-class-path-in-spring-boot

https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM