第3章 零XML配置的Spring Boot
Spring Boot 提供了一种统一的方式来管理应用的配置,允许开发人员使用属性properties文件、YAML 文件、环境变量和命令行参数来定义优先级不同的配置值。
本章我们就来体验一下零XML配置的Spring Boot 应用的开发。
2.1 Spring Boot 简介
2.1.1 Spring 去 XML配置简史
Spring IOC有一个非常核心的概念——Bean。由Spring容器来负责对Bean的实例化,装配和管理。
最早XML是用来描述Bean最为流行的配置方式。Spring可以从XML配置文件中读取任何类型的元数据并自动转换成相应的Java代码。
随着Spring的日益发展,越来越多的人对Spring提出了批评。“Spring项目大量的烂用XML”就是最为严励的一个批评。
由于Spring会把几乎所有的业务类都以Bean的形式配置在XML文件中,造成了大量的XML文件。使用XML来配置Bean失去了编译时的类型安全检查。大量的XML配置使得整个项目变得更加复杂。
我们使用XML配置的问题之一是要等到运行时的时候来发现Bean里面的错误或者其他愚蠢的问题。当然,在使用Spring的IDE插件(或者其他整合了Spring Tools Suite的工具)能在一定程度上发现这方面问题。
- Spring 2.5中我们开始有了面向注解的依赖注入编程(@Component和 @Autowired)。
- Spring 3.0中我们有了 JavaConfig , 它能够取代 XML。Spring 3.1中又提供了@Enable* 系列的注解,使得基于注解的配置更加自动化。
- 在 Spring 4.0中@Conditional注解(org.springframework.context.annotation.Conditional)实现的基于条件的配置,根据应用的类路径中的类、环境信息以及其他一些相关信息,在运行时自动化地完成配置,大量省去了程序员们大量样板化的手工配置工作。
2.1.2 要进一步解决的问题
在有了上面的这些支持后,使用Spring或者SpringMVC的过程中,仍然有很多配置以及繁琐的操作需要我们手工去完成。例如:
- 单独部署配置一个 tomcat(tomcat 的相关配置需要单独去tomcat安装目录下配)
- 使用大量依赖库可能导致的版本冲突
- Spring 集成使用使用模板引擎 Freemarker、velocity、thymeleaf 等需要单独配置
- Spring 集成使用 MyBatis、JPA 等 ORM 框架需要单独配置
- Spring 集成使用安全框架 Security 、日志框架等等都需要单独一套配置
- 配置 SpringMVC 中的 DispatcherServlet 需要在 web.xml 或者在 Servlet 初始化代码里进行显式配置
- 静态资源的处理
- 应用的运维监控工作等
由于这些已经存在的问题,Spring Boot应运而生,使用Spring Boot可以让我们快速创建一个基于Spring的项目,而让这个Spring项目跑起来我们只需要很少的配置就可以了。
2.1.3 SpringBoot 的核心功能
Spring Boot主要有如下核心功能:
直接创建一个可以独立运行的Spring项目
Spring Boot可以以jar包的形式来运行,我们可以直接通过 java -jar xx.jar 命令来运行一个Spring Boot项目。内嵌Servlet容器
Spring Boot可以内嵌Tomcat直接一键运行 Web 应用。我们不再需要打 war 包,丢到 Tomcat 下面,再启动 Tomcat 等等一系列操作了。
- 提供starter简化 Maven/Gradle 配置
使用Spring或者SpringMVC我们需要添加大量的依赖,而这些依赖很多都是固定的样板化配置。Spring Boot 通过starter 帮助我们简化 Maven/Gradle 配置。
自动配置Spring
SpringBoot 提供了大量超级实用的 starter,大大简化了我们使用 Spring 开发过程中的配置。生产环境的应用监控
使用SpringBoot 提供的 Actuator ,我们可以方便地进行应用程序的监控。无代码生成和xml配置
SpringBoot 没有引入任何形式的代码生成,它是使用的 Spring 4.0的条件注解以实现根据条件进行配置;同时使用了 Maven/Gradle 的依赖传递解析机制来实现 Spring 应用里面的自动配置。
2.2 回顾 Spring
Spring能够进行自动化的装配,它使用两种方式来进行自动化装配:
1、组件扫描:
Spring会自动发现应用上下文中所创建的bean
2、自动装配:
Spring会自动构建bean之间的依赖关系
Spring的自动化装配使用了零xml配置,也就是使用了全代码配置(注解配置),其中代码配置类使用@Configuration注解进行标注。
2.2.1 组件扫描:
@Component能给一个类自动生成对象并注入到Spring容器中,比如下面的CDPlayer,会自动new一个CDPlayer的对象并放置到Spring容器中。
public interface Player {
void play();
}
@Component
public class CDPlayer implements Player {
public void play() {
System.out.print("播放CD");
}
}
我们知道,Spring容器中每个bean都有自己唯一的一个id,自动注入的bean的id的规则如下:
1、如果@Component中有值,类似
@Component("xxx"),那么此bean的id即为xxx 。
2、如果类名为第一个字母大写,第二个字母小写,即满足首字母大写的驼峰命令规则,比如CdPlayer, 那么其bean的id第一个字母要小写,其余不变,所以最终为cdplayer。
3、如果不满足规则1,比如类名是CDPlayer, 那么其bean的id跟类名相同,所以最终为CDPlayer。
2.2.2 自动装配
需要注意的是,配置对象需要 @Configuration和@ComponentScan注解,而@ComponentScan注解是标注Spring扫描的基础包名的。如果没有值,默认会扫描自己所在的包的所有类。
其中的Configuration配置对象:
@Configuration
@ComponentScan
public class PlayerConfig {
}
那么如何配置扫描包呢?比如我想扫描 com.easy.springboot 包下的所有类,可以这样:
@ComponentScan(basePackages = "com.easy.springboot")
我们使用junit4来进行测试,注意在想获取Spring中bean上加上@Autowired,Spring会自动注入。
另外,下面的Player可以换成CDPlayer,因为CDPlayer的对象既是CDPlayer的对象,也是Player的对象。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PlayerConfig.class)
public class TestCDPlayer {
@Autowired
private Player cdPlayer;
@Test
public void testNotNull(){
cdPlayer.play();
}
}
另外,我们也可以将一个对象注入到另一个对象中。比如有一个People类,要为其注入一个Player类,然后其跑步的时候会调用Player类的play方法,这样实现即可:
@Component
public class People {
@Autowired
private Player player;
public void run(){
player.play();
System.out.println("正在跑步");
}
}
这里的
@Autowired
private Player player
Spring会自动把生成的Player对象注入到People类中。
注入的规则分为两步:
1、Spring会先查找id为player的Bean对象,如果找到,注入进来
2、如果没有找到id为player的Player对象,那么就去寻找Spring容器中查找Player的对象,如果一个都没有,或者有两个或者多个,那么就会报错。
2.2.3 使用用JavaConfig
一般来说,自动装配bean已经能为我们解决很多问题。但是,有的时候可能我们需要更自动的配置,这个时候我们就可以使用 JavaConfig 来完成。
Spring Boot的零XML配置也是基于 JavaConfig来实现的。
JavaConfig就是使用注释来描述Bean配置的组件,也就是注解驱动配置( Annotation-Driven Configuration)。它是从Spring 3.0后嵌入到Spring里的一个以前的独立项目。JavaConfig能够等价看成是XML文件,不过它只是用Java编写的。
提示:JavaConfig 是Spring的一个子项目(详细了解可参考:http://docs.spring.io/spring-javaconfig/docs/).
比如我想获取同一个类的多个bean对象,就可以在JavaConfig中声明方法来装配bean:
@Bean
public Player player(){
return new CDPlayer();
}
此时Spring容器就会生成一个Player的对象,其id为player(注意id与方法名是一样的)。
我们也可以为Player对象指定Bean id:
@Bean(name="firstPlayer")
public Player player(){
return new CDPlayer();
}
那么我们在代码中就可以根据这个id名称来装配这个 Bean
@Autowired
private Player firstPlayer
不过,要注意的是@Autowired默认按类型(byType)装配。如果我们想使用名称装配,可以结合@Qualifier注解进行使用,如下:
@Autowired
@Qualifier("firstPlayer")
private Player firstPlayer;
另外,我们还可以使用@Resource(这个注解属于J2EE的)
@Resource(name="firstPlayer") //在字段上的注入,先按照名字装配
private Player firstPlayer;
默认按照名称(byName)进行装配,名称可以通过name属性进行指定, 装配规则是:
如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。
当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
Spring的XML配置方式是使用被Spring命名空间的所支持的一系列的XML标签来实现的。Spring有以下主要的命名空间:context、beans、jdbc、tx、aop、mvc等。
使用XML来配置Bean所能实现的功能,通过JavaConfig同样可以很好的实现。之前我们都是在xml文件中定义bean的,比如:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloBean" class="com.hello.impl.HelloWorldImpl">
</beans>
其实我们可以使用注解来完成这些事情,例如下面的代码,完成的功能和上面的xml配置的功能是一样的:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.hello.HelloWorld;
import com.hello.impl.HelloWorldImpl;
@Configuration
public class AppConfig {
@Bean(name="helloBean")
public HelloWorld helloWorld() {
return new HelloWorldImpl();
}
}
使用@Bean注解,来标识此方法构造出一个由Spring容器管理的bean。@Bean声明所起到的作用与<bean/> 元素类似。
Spring对Java配置的支持是由@Configuration注解和@Bean注解来实现的。由@Bean注解的方法将会实例化、配置和初始化一个新对象,这个对象将由Spring的IoC容器来管理。
2.2.4 导入子配置类
其实,Spring 中的 XML 配置文件本质上说是一种编程元数据。在早期Java版本中,应用中的元数据一般使用属性文件、XML。但是用配置文件不够灵活而且比较繁琐。从Spring 3起,JavaConfig功能已经包含在Spring核心模块,它允许开发者将bean定义和在Spring配置XML文件到Java类中。与此同时仍然允许使用经典的XML方式来定义bean和配置。
一般在一个大型工程项目中,如果将所有的bean都配置在一个xml文件中,那么这个文件就会非常的大。所以一般会将一个大的xml配置文件分割为好几份。这样方便管理,最后在总的那个xml文件中导入。比如:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<import resource="config/customer.xml"/>
<import resource="config/scheduler.xml"/>
</beans>
但是现在我们也可以使用JavaConfig来完成同样的工作了:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({ CustomerConfig.class, SchedulerConfig.class })
public class AppConfig {
}
@Configuration可以被认为是相当于XML的< bean / >元素。
2.2.5 导入XML 配置
在 Spring XML中, 启动注解注入bean,通过如下标签实现:
<context:annotation-config/>
在 JavaConfig中, 等同于 @AnnotationDrivenConfig注解。
代码示例:
@Configuration
@AnnotationDrivenConfig
public class Config {
}
使用@ComponentScan注解,等同于在 Spring XML中的
<context:component-scan/>
代码示例:
package com.company.foo;
@Service
public class FooServiceImpl implements FooService {
private final FooRepository fooRepository;
@Autowired
public FooService(FooRepository fooRepository) {
this.fooRepository = fooRepository;
}
// ...
}
package com.company.foo;
@Repository
public class JdbcFooRepository implements FooRepository {
private final DataSource dataSource;
@Autowired
public FooRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
// ...
}
@Configuration
@ComponentScan("com.company") // search the com.company package for @Component classes
@ImportXml("classpath:com/company/data-access-config.xml") // XML with DataSource bean
public class Config {
}
在配置类中使用上述的配置,我们就可以在代码里调用service方法了:
public class Main {
public static void main(String[] args) {
JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(Config.class);
FooService fooService = ctx.getBean(FooService.class);
fooService.doStuff();
}
}
2.3 SpringBoot的应用级配置文件
SpringBoot应用中有个应用级的配置文件application.properties 。
例如,我们在application.properties中配置一个使用 JPA 的数据源
# datasource: unicode编码的支持,设定为utf-8
spring.datasource.url=jdbc:mysql://localhost:3306/blog?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&characterSetResults=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
spring.jpa.database=MYSQL
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto=update
# Naming strategy
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
2.3.1 配置文件所在目录
Spring Boot 提供的 SpringApplication 类会搜索并加载 application.properties 文件来获取配置属性值。SpringApplication 类会在下面位置搜索该文件:
1.当前目录的/config子目录
2.当前目录
3.classpath 中的/config包
4.classpath
我们在开发的时候,application.properties经常使用它来配置一些可以手动修改而且不用编译的变量。这样我们在打成war包或者jar包用于生产环境时,我们可以直接通过application.properties配置文件来修改环境变量,而不用再次重新编译。
Spring Boot几乎所有的配置项都可以在这个文件中配置,如果不配置,则使用默认项。Spring Boot会检测配置项的key,启动相应的自动配置模块。
提示:这个application.properties里面到底有哪些key是可配置的呢?在SpringBoot官网文档给出了详尽的配置以及说明。
参考 Appendix A. Common application properties中: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#common-application-properties
这个完整的application properties文件,有1000多行,属性key有900多个。当你看到如此庞大的配置,你一定会被吓到。不过,在实际项目中,我们如果遵循Spring Boot的约定,通常不需要我们单独指定太多配置。这里SpringBoot的实现方案,充分体现了Spring框架的“约定优于配置”理念。通过约定俗成的规范,很多问题的解决讲得到大大的简化。
虽然是零XML配置,但是“有些配置的事情”,还是必须要做的。
2.3.2 yml 格式的配置文件
相对于属性文件来说,application.yml 是一个更好的配置文件格式。
当有同一前缀大量使用的情况下,使用.yml格式的配置文件会更加简单。例如上面的使用 JPA 的数据源的配置,我们使用 yml 格式配置如下
# datasource: unicode编码的支持,设定为utf-8
spring:
datasource:
url: jdbc:mysql://localhost:3306/blog?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&characterSetResults=utf8
username: root
password: root
testWhileIdle: true
validationQuery: SELECT 1
spring:
jpa:
database: MYSQL
show-sql: true
# Hibernate ddl auto (create, create-drop, update)
hibernate:
ddl-auto: update
# Naming strategy
naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
注意:使用.yml时,属性名的值和冒号中间必须有空格。它的基本语法规则如下:
- 区分大小写
- 使用缩进表示层级关系
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
-
#
表示注释,从这个字符一直到行尾,都会被解析器忽略。
只要将SnakeYAML 库放到classpath下,SpringApplication就会自动支持YAML,以作为.properties的替换。当然,我们通常不需要这么做,因为我们一般都会使用Starters,在spring-boot-starter里已经集成了SnakeYAML,所以不需要我们单独去添加该依赖。
2.4 配置服务端口号
Spring Boot默认已经配置了很多环境变量,例如,tomcat的默认端口是8080,项目的contextpath是“/”等等。
我们在上一章中,使用的是默认的服务端口8080。如果我们想自定义端口号,在SpringBoot中怎么搞?
很简单,在application.properties中只需要一行配置即可。
server.port=5678
如果是在application.yml中
server:
port: 8080
注意:冒号之后有空格。
启动应用,你将看到:
2017-04-04 23:31:21.673 INFO 90250 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-04-04 23:31:21.774 INFO 90250 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 5678 (http)
2017-04-04 23:31:21.780 INFO 90250 --- [ main] com.example.DemoApplication : Started DemoApplication in 4.712 seconds (JVM running for 5.511)
TomcatEmbeddedServletContainer已经在5678端口监听请求了。
浏览器访问:http://localhost:5678/hello
你会看到成功输出:
Hello World! Tue Apr 04 23:32:38 CST 2017
2.5 配置 Profile
一般在一个项目中,总是会有好多个环境。通常的应用部署会包含开发、测试和生产等若干个环境。比如:
开发环境 -> 测试环境 -> 预发布环境 -> 生产环境
在开发中, 通常针对不同的环境我们都会有不同的配置。例如日志打印、数据库连接等,开发、测试、生产等每个环境的配置可能都不一样。
Spring Boot支持通过不同的profile来配置不同环境的配置。
---
#开发环境配置
spring:
profiles: dev
server:
port: 8080
---
#测试环境配置
spring:
profiles: test
server:
port: 8081
---
#生产环境配置
spring:
profiles: prod
server:
port: 8082
application.yml配置文件中,我们使用一组(---)来作为分隔符。
然后,我们通过spring.profiles.active来指定采用哪个 profiles
spring:
profiles:
active: dev
这样我们可以控制本地启动调用 dev 环境的配置文件。
如果是部署到服务器的话,我们正常打成jar包,发布是时候,采用:
--spring.profiles.active=dev (test、pro) 来控制加载哪个环境的配置,完整命令如下:
java -jar xxxxx.jar --spring.profiles.active=test 表示加载测试环境test的配置
java -jar xxxxx.jar --spring.profiles.active=prod 表示加载生产环境prod的配置
2.6 使用 JavaConfig 配置 Profile
Spring 框架本身提供了多种的方式来管理配置属性文件。Spring 3.1 之前可以使用 PropertyPlaceholderConfigurer。Spring 3.1 引入了新的环境(Environment)和概要信息(Profile)API,是一种更加灵活的处理不同环境和配置文件的方式。
Spring Profiles提供了一种隔离应用程序配置的方式,并让这些配置只在特定的环境下生效。任何@Component或@Configuration都能注解@Profile,从而限制加载它的环境:
@Configuration
@Profile("production")
public class ProductionConfiguration {
// ...
}
指定的属性方式常用的有2种:
1.配置在application.properties中:
spring.profiles.active=production
2.或使用命令行开关:
--spring.profiles.active=production
如果应用中包含多个 profile,可以为每个 profile 定义各自的属性文件,按照application-{profile}.properties(.yml)来命名。
2.7 自定义配置项
我们可以将自己自定义的属性配置在application.properties中(注意不要和Spring Boot的默认配置的key重复),然后在java类中通过@Value("${属性名}")注解来加载对应的配置属性,例如:application.properties文件中有如下自定义配置项:
com.easy.springboot.h5perf.app.name = H5性能测试平台
则在java中有如下应用:
@Component
public class TestProperties {
@Value("${com.easy.springboot.h5perf.app.name }")
private String appName;
}
Spring Boot使用application.properties默认了很多配置。但需要自己添加一些配置的时候,我们应该怎么做呢。
例如,下面这段yml格式的配置
environments:
dev:
url: http://dev.easy.springboot.com
name: Easy Spring Boot
会转化为:
environments.dev.url=http://dev.easy.springboot.com
environments.dev.name=Easy Spring Boot
如果我们自定义了属性key,在代码中怎样使用上面的配置信息呢?利用Spring DataBinder工具集,Spring Boot通过注解@ConfigurationProperties 和@EnableConfigurationProperties 来完成这件事情。
在代码中对应的Bean对象:
@ConfigurationProperties(prefix="environments")
public class EnvironmentsConfig {
private String url;
private String name;
public String getUrl(){ return this.url; }
public void setUrl(String url){ this.url = url; }
public String getName(){ return this.name; }
public void setName(){ this.name = name; }
}
有些时候,我们还会配置类似下面这样的列表的属性
my:
servers:
- dev1.easy.com
- dev2.easy.com
会转化为:
my.servers[0]=dev1.easy.com
my.servers[1]=dev2.easy.com
使用@ConfigurationProperties这个注解来实现属性Bean的绑定,需要在Bean里面添加一个java.util.List(或者Set)类型的属性,然后写好setter(setter也可以换成初始化一个对象),getter:
@ConfigurationProperties(prefix="my")
public class ServersConfig {
private List<String> servers = new ArrayList<String>();//initialize it with a mutable value, equals a setter
public List<String> getServers() {
return this.servers;
}
}
YamlPropertySourceLoader类能够将YAML作为PropertySource导出到Spring Environment。所以我们可以使用常用的@Value注解配合占位符语法来访问yml 中的属性。
另外,当我们使用@ConfigurationProperties注解定义配置的Bean时,需要在SpringBoot Application启动类上标注@EnableConfigurationProperties。
2.8 配置的优先级
常规情况下,我们都知道Spring Boot的配置会从application.properties中读取。实际上,从resource目录下的application.properties文件读取是Spring Boot配置链中的一环而已。
Spring Boot提供了一种优先级配置读取的机制来帮助我们从这种困境中走出来。
Spring Boot 所提供的配置优先级顺序比较复杂。按照优先级从高到低的顺序,具体的列表(从高到低)如下所示。
- 命令行参数(优先级最高)。
- 通过 System.getProperties() 获取的 Java 系统参数。
- 操作系统环境变量。
- 从 java:comp/env 得到的 JNDI 属性。
- 通过 RandomValuePropertySource 生成的random.*属性。
- jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件,通过spring.config.location参数指定
- jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
- jar包外部的application.properties或application.yml(不带spring.profile)配置文件
- jar包内部的application.properties或application.yml(不带spring.profile)配置文件
- 应用 Java配置类,包含@Configuration注解的 Java 类,通过@PropertySource注解声明的属性文件。
- 通过SpringApplication.setDefaultProperties声明的默认属性。
如果Spring Boot在优先级更高的位置找到了配置,那么它就会覆盖优先级低的配置。
Spring Boot 的这个配置优先级看似复杂,其实是很合理的。命令行参数的优先级之所以被设置为最高,是因为可以方便我们在测试或生产环境中快速地修改配置参数值,而不需要重新打包和部署应用。
SpringApplication 类默认会把以“--”开头的命令行参数转化成应用中可以使用的配置参数,如 “--name=Alex” 会设置配置参数 “name” 的值为 “Alex”。
如果不需要这个功能,可以通过
SpringApplication.setAddCommandLineProperties(false)
禁用解析命令行参数。
2.9 本章小结
Spring Boot 它抛弃了Spring 中繁琐的xml配置文件的方式,声明式注解的方法为服务开发提供快速简洁的配置方式。在Spring Boot 中,我们会发现,我们其实不用做一些基本的配置也能直接运行刚创建好的工程项目,因为它内嵌了很多基本的通用的配置组件而不需要我们自己来做一些重复的配置工作。
Spring Boot让环境配置变得轻松很多。
参考资料:
1.https://www.ibm.com/developerworks/cn/java/j-lo-spring-boot/index.html
2.http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#common-application-properties