官方建议的代码目录

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 项目中处理静态文件. 确实需要的话, 参考:

CSDN-catoop

打包为普通的 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>

参考资料