Spring Boot 学习之基本概念及使用
Contents
官方建议的代码目录
com
+- example
+- myproject
+- Application.java
|
+- domain
| +- Customer.java
| +- CustomerRepository.java
|
+- service
| +- CustomerService.java
|
+- web
+- CustomerController.java
Application.java 典型的代码
package com.example.myproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@ComponentScan
它会扫描(@Component, @Service, @Repository, @Controller)等这些注解,并自动注册为Spring Bean
@SpringBootApplication
等同于同时使用(@Configuration, @EnableAutoConfiguration, @Componentscan)这些注解.
运行Spring Boot
java -jar target/myproject-0.0.1-SNAPSHOT.jar
或者
java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n -jar target/myproject-0.0.1-SNAPSHOT.jar
或者
export MAVEN_OPTS=-Xmx1024m -XX:MaxPermSize=128M -Djava.security.egd=file:/dev/./urandom
mvn spring-boot:run
或者
默认的配置文件名
application.properties
父,子容器设置
new SpringApplicationBuilder()
.bannerMode(Banner.Mode.OFF)
.sources(Parent.class)
.child(Application.class)
.run(args);
Spring boot 中事件监听
SpringApplication.addListeners(…)
通过注册监听器,来监听相应的事件
访问应用参数
@Component
public class MyBean {
@Autowired
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
// if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
}
}
Spring Boot 监控
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加这个依赖,然后就可以看到启动时加载的信息了:
troller.error(javax.servlet.http.HttpServletRequest)
2016-02-14 15:38:21.371 INFO 30028 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-02-14 15:38:21.371 INFO 30028 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-02-14 15:38:21.405 INFO 30028 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-02-14 15:38:21.884 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.884 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.885 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2016-02-14 15:38:21.885 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/env || /env.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.886 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/health || /health.json],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
2016-02-14 15:38:21.888 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2016-02-14 15:38:21.888 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.890 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.890 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.890 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.891 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/info || /info.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-02-14 15:38:21.891 INFO 30028 --- [ main] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
修改前缀
management.contextPath=/my/manager
这样子,就要通过/my/manager/health
这样子来访问了.
Spring Admin
maven的依赖
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
<!-- 以下配置是客户端 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>1.5.4</version>
</dependency>
在配置文件里添加
spring.application.name=Spring Boot Admin Web
spring.boot.admin.url=http://localhost:${server.port}/admin
spring.boot.admin.context-path=/admin
management.security.enabled=false
info.version=@project.version@
然后访问: Go
spring boot admin 开启认证
application.properties
# spring boot admin
spring.application.name=Spring Boot Admin Web
spring.boot.admin.context-path=/admin
spring.boot.admin.username=admin
spring.boot.admin.password=unidsp
management.security.enabled=false
security.basic.enabled=true
security.basic.path=/admin
security.user.name=admin
security.user.password=unidsp
spring.boot.admin.client.metadata.user.name=${security.user.name}
spring.boot.admin.client.metadata.user.password=${security.user.password}
pom.xml
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-login</artifactId>
<version>1.5.4</version>
</dependency>
根据不同环境使用 properties
application.properties
application-dev.properties
application-pro.properties
然后设置spring.profiles.active=xxx
,就可以使用相应的配置文件了.(-Dspring.profiles.active=production)
注意,这里是有继承关系的.即application.properties
是公共的,即无论在哪种环境,它都会加载的.
如果spring.profiles.active=default
,就只会加载application.properties
.
如果spring.profiles.active=dev
,就会加载application.properties
,然后再加载application-dev.properties
.而且application-dev.properties
的属性,会覆盖application.properties
的.
而且,还可以向前引用变量。比如这样子:
hello.world=def
hello.world.parent=${hello.world} from parent
使用 Bean 来映射properties文件
@Component
@ConfigurationProperties(prefix="person")
public class ConnectionSettings {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
然后 properties
文件 里可以这样子写
person.firstName=xxx
或者
person.first-name=xxx
或者
PERSON_FIRST_NAME=xxx
利用 Spring Annotation 自动在 application.properties
中生成配置信息
在 pom.xml
中加入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
然后重新编译一下项目, 这时在IDE里编辑 applicatin.properties
时, IDE 就会自动提示 @ConfigurationProperties
注解的前缀的选项了.
在 Java 代码里根据不同的环境来加载不同的 Bean
@Configuration
@Import({Development.class, Production.class})
public class MyApplicationConfiguration {}
@Configuration
@Profile("development")
public class Development {}
@Configuration
@Profile // The default
public class Production {}
@Configuration
public class MyApplicationConfiguration {
@Bean
@Profile("development")
public static PropertySourcesPlaceholderConfigurer developmentPropertyPlaceholderConfigurer() {
// instantiate and return configurer...
}
@Bean
@Profile // The default
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
// instantiate and return configurer...
}
}
在 XML 文件里根据不同的环境加载不同的 Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- ======================================= -->
<!-- use varibles in bean definition -->
<!-- ======================================= -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean class="com.shengwang.demo.HelloWorldBean">
<constructor-arg value="${hellow.world.name}" />
</bean>
<!-- ====================================================== -->
<!-- import different variables according to active profile -->
<!-- ====================================================== -->
<beans profile="development">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/development.properties" />
</beans>
<beans profile="test">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/test.properties" />
</beans>
<beans profile="production">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/production.properties" />
</beans>
</beans>
配置自定义Bean
@Configuration
public class MyConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = ...
HttpMessageConverter<?> another = ...
return new HttpMessageConverters(additional, another);
}
}
关于静态资源的处理
这是Spring boot 默认情况下配置.
默认情况下,在classpath路径下的/static (or /public or /resources or /META-INF/resources)
这些目录,是被看作是静态资源的存放路径的.
也可以通过spring.resources.staticLocations
参数来指定.
一般情况下, 公司都是使用 Nginx 作为代理的. 所以一般不需要在 Java 项目中处理静态文件. 确实需要的话, 参考:
打包为普通的 war 包形式
HelloWorldSpringBoot.java
package org.emacsist;
import de.codecentric.boot.admin.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* Created by sky on 16-2-14.
*/
@SpringBootApplication
@EnableAdminServer
@EnableDiscoveryClient
public class HelloWorldSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(HelloWorldSpringBoot.class);
}
public static void main(String[] args) throws Exception {
SpringApplication app = new SpringApplication(HelloWorldSpringBoot.class);
app.run(args);
}
}
修改pom
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>org.emacsist.HelloWorldSpringBoot</start-class>
<java.version>1.8</java.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
项目的目录结构改为
src |
|-main
|-webapp
|-WEB-INF
其他的按普通的项目目录处理即可.
优雅关闭 Spring Boot
在application.properties
里添加以下配置
endpoints.shutdown.enabled=true
启动时就可以看到以下加载信息了
Mapped "{[/shutdown || /shutdown.json],methods=[POST]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint.invoke()
然后可以这样子关闭:
╭─sky@sky-linux ~
╰─➤ curl -d "" http://localhost:8080/shutdown
{"message":"Shutting down, bye..."}%
自定义监控
health
@Component
public class ExecutorHeal implements HealthIndicator {
@Override
public Health health() {
Object dataSource = null;
if (dataSource == null) {
return Health.down().status("DOWN").outOfService().withDetail("msg", "datasource is null").build();
} else {
return Health.up().status("UP").withDetail("max connections", "100").build();
}
}
}
Metrics
package org.emacsist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.stereotype.Component;
/**
* Created by sky on 16-2-16.
*/
@Component
public class MyMetric {
private final CounterService counterService;
private final GaugeService gaugeService;
@Autowired
public MyMetric(CounterService counterService, GaugeService gaugeService) {
this.counterService = counterService;
this.gaugeService = gaugeService;
}
public void loginCounter() {
this.counterService.increment("login.count");
// reset each minute
}
public void cacheGauge() {
this.gaugeService.submit("cache.hit", 80.0);
}
}
spring 中使用 logback根据不同的环境,配置不同的日志方式
文件名是logback-spring.xml
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>
<springProfile name="dev, staging">
<!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>
<springProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>
spring 非 web 以守护进程方式启动
package com.compay.wxsdk.listener;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
@SpringBootApplication
@ComponentScan("com.compay.wxsdk")
@EnableAutoConfiguration(exclude = {WebMvcAutoConfiguration.class, VelocityAutoConfiguration.class})
@Configuration
public class ListenerApplication implements CommandLineRunner {
public static void main(String[] args) throws IOException, InterruptedException {
// Enumeration<URL> roots = WebApplication.class.getClassLoader().getResources("");
// while (roots.hasMoreElements()) {
// URL url = roots.nextElement();
// File root = new File(url.getPath());
// for (File file : root.listFiles()) {
// System.out.println(file.getAbsolutePath() + " ==> " + file.getName());
// }
// }
System.out.println("start ....listener app");
ConfigurableApplicationContext configurableApplicationContext = new SpringApplicationBuilder(ListenerApplication.class).web(false).run(args);
configurableApplicationContext.registerShutdownHook();
configurableApplicationContext.start();
final CountDownLatch closeLatch = configurableApplicationContext.getBean(CountDownLatch.class);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
closeLatch.countDown();
}
});
closeLatch.await();
System.out.println("shutdown....");
}
@Override
public void run(String... strings) throws Exception {
}
@Bean
public CountDownLatch closeLatch() {
return new CountDownLatch(1);
}
}
Spring boot自定义Error Page 以及记录异常日志
自定义ErrorPgae
package com;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
/**
* Created by emacsist on 2017/3/7.
*/
@Configuration
public class MvcConfiguration {
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500"));
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404"));
}
};
}
}
记录异常日志
package com;
import com.utils.Loggers;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
/**
* Controller 统一异常、错误处理
* Created by emacsist on 2017/3/7.
*/
@ControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(value = Exception.class)
public String defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception {
ByteArrayOutputStream bao = new ByteArrayOutputStream(1024);
PrintWriter printWriter = new PrintWriter(bao);
printWriter.append(getRequestInfo(request));
e.printStackTrace(printWriter);
printWriter.flush();
String stack = bao.toString(StandardCharsets.UTF_8.name());
Loggers.ERROR_LOGGER.error("{}", stack);
return "forward:/500";
}
private String getRequestInfo(HttpServletRequest request){
String lineSepartor = System.getProperty("line.separator");
StringBuilder sb = new StringBuilder(1024);
sb.append("Request URL : ").append(request.getRequestURL()).append(lineSepartor);
sb.append("Request Method: ").append(request.getMethod()).append(lineSepartor);
sb.append("Request Paramter: ").append(lineSepartor);
Enumeration params = request.getParameterNames();
while(params.hasMoreElements()){
String paramName = (String)params.nextElement();
sb.append(paramName).append("=").append(request.getParameter(paramName)).append(lineSepartor);
}
return sb.toString();
}
}
包含多个 properties 文件
在 application.properties
添加
spring.profiles.include=p1,p2,p3
Spring Boot 中使用日志
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<!-- 这个可以结合 spring admin 动态修改日志级别 -->
<jmxConfigurator/>
</configuration>