Spring Boot特性入门篇

全文共 3264 个字

初始化Spring-boot

最佳的文档结构。

com
 +- example
     +- myproject
         +- Application.java
         |
         +- domain
         |   +- Customer.java
         |   +- CustomerRepository.java
         |
         +- service
         |   +- CustomerService.java
         |
         +- web
             +- CustomerController.java

spring-boot还是建议按照标准的controller-service-dao结构分层。有一个独立的Application.java作为系统启动入口。

引入

这里仅仅以Maven为例:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.springframework</groupId>
    <artifactId>gs-spring-boot-demo</artifactId>
    <version>0.1.0</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
	    	<version>1.5.9.RELEASE</version>
        </dependency>
    </dependencies>
    <properties>
        <java.version>1.8</java.version>
    </properties>
</project>

引入了 spring-boot-starter-web,基本上开发一个web应用所需的包都会引入其中。如果需要使用JPA等等功能需要另外引入对应的starter。spring-boot用pom的方式整合了许多开箱即用的工具,官方称之为starter特性,后面会介绍什么是starter。

启动

Spring boot提供了多种启动方式,最简单的方式是在main方法中调用 SpringApplication.run 方法即可启动Spring Boot。当然,run方法必须要配合相关的注解才能实现Spring Boot目标功能。关于spring boot打包以及jara -jar或者CLI启动,后续的博文会介绍。

DEBUG模式

通常情况下,启动Spting Boot时日志输出都是生产模式(关闭DEBUG级别的日志),在启动参数中增加--debug参数即可开启调试模式的日志输出。

Eclipse的设置:工程右键->Debug As->Debug Configurations->打开Arguments选项->在Program arguments中增加 --debug 参数。

Spring Boot特性入门篇

纯Java配置——@Configuration

@Configuration是一个用于类的注解,他可以替换原来定义在xml文件中的spring配置。当为某一个类增加这个注解后,会将其视作一个源自配置文件的Bean
其实springioc容器一直以来都没多大变化,延续基于单例的IOC的机制一直向下衍生功能线,不管使用什么注解,基本上所有用到的实例都是一个Bean,所有的Bean都放在同一个的IOC容器中(当然也可以创建多个容器,但是似乎并没什么应用需要这么特殊的实现)。Spring Xml配置是根据xml的描述生成多个Bean,而引入@Configuration注解使得配置可以彻底基于Java代码。

自动配置注入——@EnableAutoConfiguration 

这个注解用于在SpringIOC容器中启用自动推导配置功能(使用boot中定义的默认配置)。其执行过程实际就是根据classpath中的包来决定是否需要注入某个用于资源配置的Bean来支持其工作。比如在classpath中发现了tomcat-embedded.jar 这个包,那么可以推定需要启用tomcat的嵌入工具,那么boot会帮助我们创建一个 TomcatEmbeddedServletContainerFactory 的实例作为Bean放置到容器中以供其使用。我们可以通过注解的  exclude() 和 excludeName() 方法告知不需要自动生成某些配置。也可以通过声明  spring.autoconfigure.exclude JVM参数。 

实质上Spring-Boot-Web就是一个更加自动化的Spring-Webmvc——不用整合servlet容器并且分分钟启动。而Spring-Boot最大的亮点之一就是根据引入的包自动注入配置。如果打开--debug模式会看到很多匹配相关的内容输出。下面是自动匹配输出的一些内容,为了便于说明只选取了很小一部分,实际输出的内容比这个多得多。

=========================
AUTO-CONFIGURATION REPORT
=========================

Positive matches:
-----------------

   DispatcherServletAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
      - @ConditionalOnWebApplication (required) found StandardServletEnvironment (OnWebApplicationCondition)

   DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched:
      - @ConditionalOnClass found required class 'javax.servlet.ServletRegistration'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
      - Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition)


Negative matches:
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition)

AUTO-CONFIGURATION REPORT 开始就是匹配日志,Positive matche 之后的表示匹配上的配置,Negative matches之后表示未匹配上的配置。每一项的内容都详细说明了匹配上的依赖关系和未匹配的原因。

包扫描——@ComponentScan

@ComponentScan注解用于设定IOC容器加载Bean的扫描路径,等价于xml配置中的<context:component-scan>元素(@ComponentScan属于Spring Framework的Context模块)。在指定的路径中会将@Component及其子类限定的类(如@Service@Repository@Controller)作为一个Bean添加到IOC容器中。

@ComponentScan中包含多个参数,例如basePackagesbasePackageClassesexcludeFilters等,都是用于定义扫描的包路径或限定名。如果没有为@ComponentScan注解设定任何参数,则会扫描当前包以及所有子孙包。

Spring-boot整合——@SpringBootApplication

@SpringBootApplication注解整合了@Configuration@EnableAutoConfiguration@ComponentScan的效果。当为一个入口类(包含启动的main方法)定义一个@SpringBootApplication注解后,意味着增加了上述三个注解的功能——1)当前类是一个资源Bean,2)启用spring boot的自动推导配置(开箱即用)、3)自动扫描入口类之后的所有子包。

所以下面2种写法实现的效果是几乎一致的(在@SpringBootApplication中对@ComponentScan做了参数限定,所以只能说几乎一致。):

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

开箱即用——Starter

Spring Boot通过Maven的方式提供了一系列开箱即用(一站式服务)的工具,包括MQ、AOP、JPA等,文档上将这个特性命名为Starter。前面 引入 部分使用的 spring-boot-starter-web 就是一个Starter 。Starter 特性并没有什么新的技术,仅仅是通过pom文件的方式引用了一些必要的包,然后在引入之后通过Spring Boot的自动推导配置为引入的jar包注入必要的配置Bean。官网的表13.1 列举了所有Sprint Boot官方提供的Starter

当然除了官方提供的Starter我们还可以自定义。不过需要注意的是命名规则——由官方提供的Starter命名规则为spring-boot-starter-*,而自定义(第三方提供)的规则为 acme-spring-boot-starter-*。自定义的Starter在某些使用需要额外指定自动配置功能,详情请看 关于自定义Starter的说明

逐渐替换默认配置

这也是Spring Boot的最佳实践之一。虽然它提供了相当丰富的默认配置,但是并不是所有的东西用默认配置就可以解决。Spring Boot建议根据需要逐渐替换工程所需的配置。例如默认情况下工程引入了 HSQLDB ,并且没有配置DataSource,那么我们所有的数据库操作(例如JPA)都会直接使用HSQLDB内存数据库。如果我们向容器注入了DataSource实例,那么我们定义的配置将会替换默认配置。

开发Spring-boot

全局定义开发环境——spring-boot-devtools

spring-boot-devtools(以下简称Devtools)为开发环境提供了许多快速便捷的设置,仅需要增加一个依赖即可实现开发所需的配置,以Maven为例:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

在引入他之后当前环境自动变为开发环境。需要注意的是如果运行完整打包的工程Spring Boot不启用任何Devtools相关的功能(实际上打包工具spring-boot-maven-plugin默认情况下不会去打包Devtools),为了防止Devtools的作用域污染子项目,我们最好增加 Maven optional 标记。

下面介绍Devtools具体提供了什么功能。

1.代码修改与热部署

Devtools 的一项功能就是能够监控代码的变更,并在发现变更时“热部署”最新的代码。不过这里的热部署不是Jvm bytecode级别的热部属,也和OGSI没任何关系。

根据官方的介绍是实现了两个ClassLoader——BaseClassLoader和RestartClassLoader(推断这2个ClassLoader应该破坏了双亲委派模型)。第一次启动JVM时所有的.class文件和.jar文件中的类都用BaseClassLoader加载,然后在开发的过程中凡是变更过的.class 文件都会被标记,这些被标记的.class之后都会使用RestartClassLoader加载。在初始化一个类时,被标记了用RestartClassLoader加载的Class<?>实例,没有被标则委派给BaseClassLoader加载,每次发起“热部署”时都会新建一个RestartClassLoader重新加载类,这样可以保证变更过的代码都是重新加载的。

Devtools进行“热部署”时会调用spring的上下文挂钩(spring context hook)来重新部署IOC容器。如果你关闭了它——SpringApplication.setRegisterShutdownHook(false),“热部署”无法将新加载的类实例部署到IOC容器中导致代码替换失败。

Spring Boot特性入门篇

上面是开发过程中Jconsole的输出,每一次修改代码保存都会新增一些非堆(方法区)的空间,这说明重新加载了新的字节码数据并解析到非堆中。

jvm环境中classPath路径下的任何文件修改都会触发Devtools 的热部署,某些时候并不需要都监控所有的路径,例如/resources、/static、/template等,我们可以通过设定spring.devtools.restart.exclude属性来排除热部署监控的位置。例如:

spring.devtools.restart.exclude=static/**

此外,使用“热部署”时还需注意以下几点(个个都有可能是引发问题的坑啊):

  1. 属性spring.devtools.restart.additional-paths属性可以用来增加监控classpath之外的路径。
  2. Devtools内嵌了LiveReload,如果不想启用它可以将spring.devtools.livereload.enabled属性设置为fasles
  3. Devtools会自动忽略某些包的扫描,例如spring-boot、spring-boot-devtools、spring-boot-autoconfigure、spring-boot-actuator、spring-boot-starter
  4. Devtools会修改SpringContext指定的ResourceLoader,如果自定义了一个新的ResourceLoader,修改后的getResource方法将无法生效。
  5. spring.devtools.restart.enabled属性设置为false可以关闭Devtools的“热部署”功能。
  6. 某些IDE整合了代码监控功能,可以通过spring.devtools.restart.trigger-file属性指定要监控的文件,只有这个文件发生变更时才会触发Devtools进行全局的文件变更检查。
  7. 前面介绍了Devtools的“热部署”是通过2个ClassLoaderBaseClassLoader、RestartClassLoader)实现的,默认情况下.jar包中的类只会使用BaseClassLoader加载。我们可以通过在根目录新建一个META-INF/spring-devtools.properties文件,然后在其中设置restart.exclude. 和 restart.include. 属性来指定被 RestartClassLoader 加载的 .jar 类。详情见官网例子

2.缓存启用和停用

很多框架、工具都提供了缓存功能,在生产环境中对某些热数据进行适当的缓存能够有效的提高性能。但是在开发环境这些缓存反而会影响我们验证功能。所以Devtools全局提供了缓存管理,并默认关闭大部分工具或框架的缓存。开发人员可用通过设置运行环境properties的方式来指定缓存功能,例如:

System.setProperty("spring.thymeleaf.cache", "true");

就可以指定启用thymeleaf模板引擎的缓存。缓存管理相关的配置请看 github上spring-boot-devtools环境设置相关的代码

3.文件配置

除了使用参数,我们可以把Devtools的所有配置写到$HOME目录下一个".spring-boot-devtools.properties"的文件中。例如:

spring.devtools.reload.trigger-file=.reloadtrigger

4.远程开发

Devtools除了提供本机开发的增强功能之外,还增加了强大的远程开发与调试功能。

首先,我们需要在打包的时候连同spring-boot-devtools一起打包并发布,而spring-boot-maven-plugin默认不是打包Devtools的,所以我们需要将Pom文件的plugins配置简单修改一下:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludeDevtools>false</excludeDevtools>
            </configuration>
        </plugin>
    </plugins>
</build>

发布之前需要设置一个属性:

spring.devtools.remote.secret=mysecret

 特别需要注意:这个属性会带来安全风险,所以仅仅用于测试和开发,切记不要用于生产运行。

将打好的包部署到远程服务器即可,我们称之为服务端。

然后,要在本地开发环境配置一个客户端

客户端需要配合IDE一起使用。假设你的工程名字为my-app在Eclipse下进行下面的配置:

  1. Run 菜单栏目里选择Run Configurations...
  2. 创建一个新的 Java Application(在Java Application处右键,然后选择new)。
  3. Project一栏里选择my-app工程。
  4. Main Class一栏里使用org.springframework.boot.devtools.RemoteSpringApplication作为main方法类。
  5. Arguments选项卡中,在Program arguments中添加服务端的地址(类似https://myapp.cfapps.io的格式)

最后,启用了spring.devtools.remote.secret之后,客户端会监控本地classpath下文件变更。一旦触发“热部署”它会先在本地完成,然后将变更的内容推送到远程服务端触发“热部署”。就像你在本地开发一样,这对开发一些回调应用和不同环境的调试带来了极大的便利。

还有,Devtools在基于jdwp远程调式的基础上进行了扩展,提供支持HTTP传输远程调试信息。绝大部分情况下都能使用Java的远程调试能解问题,如有特殊需求(如用到docker等),可以看 这里