spring boot 入门
<parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>1.5.9.RELEASEversion> 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应用的骨架。
<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> dependencies>
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 :: (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> 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
<build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build>
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.9.RELEASE)
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。
- 创建合适的ApplicationContext实例;
- 注册CommandLinePropertySource实例,将命令行参数暴露为Spring属性;
- 刷新application context,加载所有单例;
- 触发所有 CommandLineRunner实例;
配置文件
- Devtools配置文件中的值
- 命令行(默认情况下,SpringApplication 会将命令行参数转换为property ,同时添加到Environment)
- ServletConfig初始化参数
- ServletContext初始化参数
- Java系统属性
- 环境变量
- RandomValuePropertySource(主要用来生成随机值,random.*格式,适合用来生成随机值,参考24.1 Configuring random values)
- jar包外的application-{profile}.properties
- jar包内的application-{profile}.properties
- jar包外的application.properties
- jar包内的application.properties
- @Configuration 类上的@PropertySource注解
- SpringApplication.setDefaultProperties声明的默认属性
- 当前运行目录的/config子目录
- 当前目录
- classpath的/config中
- classpath
@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集合
@Configuration @EnableConfigurationProperties(FooProperties.class) public class MyConfiguration { }
@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.bootgroupId> <artifactId>spring-boot-starterartifactId> <exclusions> <exclusion> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-loggingartifactId> exclusion> exclusions> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-log4j2artifactId> 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%nProperty> Properties>
输出为[3bfdd6f72352ef7e,3bfdd6f72352ef7e,,false]
或者:
<Properties> <Property name="pattern">%d{yyyy-MM-dd HH:mm:ss,SSS} %X %5p %c{1}:%L - %m%nProperty> 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配置如下:
输出格式为:
[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
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应用
@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)支持
@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); } }
嵌入式容器的配置
自定义容器配置
- server.port
- server.address
- server.session.timeout