spring boot完整学习指南(含各种常见问题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

补充

spring boot面试题 https://blog.csdn.net/qq_30999361/article/details/124461725

posted @ 2018-06-14 20:10  zhjh256  阅读(1832)  评论(0编辑  收藏  举报