作者简介
陈喆,现就职于中科院某研究所担任副研究员,专注于工业云平台、MES系统的设计与研发。
spring-music项目是一个用于演示在Cloud Foundry上实现绑定数据库服务的spring项目。项目地址:https://github.com/scottfrederick/spring-music
本文重点讲解spring-music实现数据库绑定功能的相关代码实现,帮助你理解如何创建一个spring应用实现同时支持按照地址访问数据库和按service binding访问数据库。
1 初始化项目
如果要从零创建spring-music项目可以使用spring initializr:http://start.spring.io/。
创建Gradle Project,选择Spring Boot 1.
添加项目参数和依赖
初始只添加四个基础依赖:
1、Web:添加spring-boot-starter-web依赖,引入spring mvc支持
2、Actuator:添加spring-boot-starter-actuator依赖,可以用于监控系统健康状况
3、JPA:添加spring-boot-starter-data-jpa依赖,支持对数据库访问。Spring Data JPA封装了一套规范的API,通过统一的接口实现对不同厂商的ORM组件的调用
4、H2:添加h2依赖,H2是一个用Java开发的嵌入式数据库,可以同应用程序打包在一起发布,这样可以非常方便地存储少量结构化数据。也可以用于测试和缓存。
项目创建成功后,生成的代码结构是这样的:
工程中关键文件/文件夹有如下几项:
src文件夹:源文件
main:功能代码
java: java类文件夹
SpringMusicApplication:工程启动类
resources: 资源文件夹
static:静态资源文件
templates:模板文件
application.properties:项目配置文件
test:测试代码
build.gralde:Gradle配置文件
gradlew:Gradle Wrapper的可执行脚本。Gradle Wrapper是对Gradle的一层包装,便于在团队开发过程中统一Gradle构建的版本
gradlew.bat:Gradle Wrappter在Windows下的可执行脚本
settings.gradle:多模块项目中根模块项目的模块描述文件,但模块项目可以不管它
进行项目根目录下,执行命令
# gradlew bootrun
可以运行程序,但由于还没有开发页面,所以无法访问到页面。
2 解读build.gralde文件
由于在初始化项目是选择的项目类型是Gradle Project,所以在工程目录中可以看到build.gradle文件。Gralde基于Groovy语言提供了一个构建项目的框架,通过集成众多plugin来实现各种自动化构建功能。build.gradle是默认生成的gradle脚本文件,内容如下:
buildscript {
ext {
springBootVersion = '1.5.14.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-web')
runtime('com.h2database:h2')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
build.gradle由如下几个代码块组成。
buildscript 代码块
buildscript {
ext {
springBootVersion = '1.5.14.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
上文的buildscript代码块用于设置脚本的运行环境,gradle在执行脚本时,会优先执行buildscript代码块中的内容,然后才会执行剩余的build脚本。buildscript代码块包括如下子代码块:
1 ext{}:定义了gradle的额外属性,此处定义了本项目使用的spring boot版本(springBootVersion)。
2 repositories {}:java依赖库管理,用于指定下载依赖包的maven库。可以指定官方的依赖库,也可以指定自己配置的私有依赖库。默认使用 mavenCentral(),即maven中心仓库。
3 dependencies{}:定义依赖路径。支持maven/ivy,远程,本地库,也支持单文件。如果前面定义了repositories{}maven 库,则使用maven的依赖库,gradle 就会自动的往远程库下载相应的依赖。
3.1 本项目添加了spring-boot-gradle-plugin插件,它主要提供一下几个功能:
3.1.1 简化执行和发布:它可以把所有classpath的类库构建成一个单独的可执行jar文件,这样可以简化你的执行和发布等操作。
3.1.2 自动搜索入口文件:它会扫描 public static void main() 函数并且标记这个函数的宿主类为可执行入口。
3.1.3 简化依赖:一个典型的Spring应用还是需要很多依赖类库的,想要配置正确这些依赖挺麻烦的,所以这个插件提供了内建的依赖解析器会自动匹配和当前Spring Boot版本匹配的依赖库版本。
应用插件代码块
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin:声明引用插件的类型。什么是插件(plugin)呢?Gradle能够发挥作用,是依靠执行脚本中的任务(task),一个任务是一个原子操作,即不可分割的。项目开发过程中,我们往往需要按照一定顺序执行多个任务以完成某个特定功能,我们将这些任务及其属性、配置封装在一起形成插件(plugin)。插件分为脚本插件和二进制插件。脚本插件是一个Gradle文件,二进制插件则经过编译形成可执行文件,本项目引用的是二进制插件。
初始项目引用了3个插件:
1、java plugin[https://docs.gradle.org/current/userguide/java_plugin.html]:Java plugin插件是Gradle内置插件,指定项目为java项目,提供了一系列的任务支持构建、编译、测试Java项目,项目编译(在项目提示符下执行:gradle build)时生成项目的jar包。
2、eclipse plugin[https://docs.gradle.org/current/userguide/eclipse_plugin.html]:是Gradle内置插件,用于生成Eclipse IDE所需的.project,.classpath等文件。
3、org.springframework.boot plugin[https://plugins.gradle.org/plugin/org.springframework.boot]:支持spring boot
版本信息
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
1、version :指定当前工程的版本
2、sourceCompatibility: 指定编译.java文件的jdk版本
项目依赖
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-web')
runtime('com.h2database:h2')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
与buildscript代码块中的repositories和dependencies的使用方式几乎完全一样,唯一不同之处是在buildscript代码块中你可以对dependencies使用classpath声明。该classpath声明说明了在执行其余的build脚本时,class loader可以使用这些你提供的依赖项。这也正是我们使用buildscript代码块的目的。而如果你的项目中需要使用该类库的话,就需要定义在buildscript代码块之外的dependencies代码块中。
在dependencies {}代码块中,有几种不同的依赖配置方式,如果不正确配置的话,就会遇到依赖包无法导入或者runtime、providedCompile无法使用的情况:
1、compile:如果你的jar包/依赖代码在编译的时候需要依赖,在运行的时候也需要,那么就用compile。前提:apply plugin: 'war'或者apply plugin: 'java'。
2、providedCompile: 如果你的jar包/依赖代码仅在编译的时候需要,但是在运行时不需要依赖,就用providedCompile。前提:apply plugin: 'war'。
3、runtime: 如果你的jar包/依赖代码仅在运行的时候需要,但是在编译时不需要依赖,就用runtime 。前提:apply plugin: 'java'。
修改配置内容
spring-music项目的build.gradle文件在初始化项目的build.gradle文件的基础上新增了一些内容。
buildscript代码块中repositories增加
jcenter()
maven { url "https://repo.spring.io/plugins-release" }
1、jcenter():新的中央远程仓库,兼容maven中心仓库,而且性能更优
2、maven { url "https://repo.spring.io/plugins-release" }:引用spring提供的release版插件库
应用插件代码块增加
apply plugin: 'eclipse-wtp'
apply plugin: 'idea'
1、eclipse-wtp:插件将构建web项目的开发环境,生成所需要的.project,.classpath等文件。因为我web开发使用的是eclipse-j2ee版本,所以指定为wtp环境。
2、idea:是Gradle内置插件,用于生成Eclipse IDE所需的.project,.classpath等文件。
注:spring-music使用了“apply plugin: 'spring-boot'”,这是“apply plugin: 'org.springframework.boot'”的早期版本,现在不再使用。
targetCompatibility = 1.8
确保class文件与targetCompatibility指定版本,或者更新的java虚拟机兼容,
jar {
baseName = "spring-music"
version = "" // omit the version from the war file name
}
jar任务是“apply plugin: 'java'”提供的任务,可以自定义jar包,此处自定义了jar包的名称和版本。
task wrapper(type: Wrapper) {
gradleVersion = '2.14'
}
此处指定了gradle wrapper的版本信息。
3 创建实体类
spring-music项目是一个唱片管理网站,按照常规套路,编写Java程序首先设计并创建实体类。Album类展示了声明一个实体类最常用的结构和要素。
Album类
Album作为唱片的实体类,描述了唱片所必须的属性字段,同时通过ORM属性将该实体类与数据库表建立映射关系。
@Entity // 对实体注释,说明此java类是实体类,任何Hibernate映射对象都要有这个注释
public class Album {
@Id //声明此属性为主键。该属性值可以通过应该自身创建
@Column(length=40)
@GeneratedValue(generator="randomId") //指定主键的生成策略,自处指定使用名为"randomId"的自定义主键生成策略
@GenericGenerator(name="randomId", strategy="org.cloudfoundry.samples.music.domain.RandomIdGenerator") //自定义主键生成策略指定到类org.cloudfoundry.samples.music.domain.RandomIdGenerator上
private String id;
private String title;
private String artist;
private String releaseYear;
private String genre;
private int trackCount;
private String albumId;
public Album() {
}
public Album(String title, String artist, String releaseYear, String genre) {
this.title = title;
this.artist = artist;
this.releaseYear = releaseYear;
this.genre = genre;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getReleaseYear() {
return releaseYear;
}
public void setReleaseYear(String releaseYear) {
this.releaseYear = releaseYear;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
public int getTrackCount() {
return trackCount;
}
public void setTrackCount(int trackCount) {
this.trackCount = trackCount;
}
public String getAlbumId() {
return albumId;
}
public void setAlbumId(String albumId) {
this.albumId = albumId;
}
}
RandomIdGenerator类
RandomIdGenerator是自定义的主键生成器,用于Album生成唯一主键。
public class RandomIdGenerator implements IdentifierGenerator {
@Override
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
return generateId();
}
public String generateId() {
return UUID.randomUUID().toString();
}
}
该类实现了IdentifierGenerator 接口,通过重写generate()方法自定义主键生成机制。
4 创建关系数据库Repository
创建完实体类,通过定义Repository实现数据持久化。本文重点讲关系数据库的相关内容,mongodb和redis与关系数据库类似。
JpaAlbumRepository
本工程使用spring data jpa,通过继承JpaRepository就实现了对实体对象的增删改查,相当简单。
@Repository //@Repository用于标注数据访问组件,即DAO组件
@Profile({"in-memory", "mysql", "postgres", "oracle", "sqlserver"}) //标明当前运行环境,在spring使用DI来依赖注入的时候,能够根据当前制定的运行环境来注入相应的bean
public interface JpaAlbumRepository extends JpaRepository {
}
AlbumRepositoryPopulator
AlbumRepositoryPopulator的主要功能是做数据初始化。
@Component
public class AlbumRepositoryPopulator implements ApplicationListener, ApplicationContextAware {
private final Jackson2ResourceReader resourceReader;
private final Resource sourceData;
private ApplicationContext applicationContext;
public AlbumRepositoryPopulator() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
resourceReader = new Jackson2ResourceReader(mapper);
sourceData = new ClassPathResource("albums.json");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().equals(applicationContext)) {
CrudRepository albumRepository =
BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, CrudRepository.class);
if (albumRepository != null && albumRepository.count() == 0) {
populate(albumRepository);
}
}
}
@SuppressWarnings("unchecked")
public void populate(CrudRepository repository) {
Object entity = getEntityFromResource(sourceData);
if (entity instanceof Collection) {
for (Album album : (Collection) entity) {
if (album != null) {
repository.save(album);
}
}
} else {
repository.save(entity);
}
}
private Object getEntityFromResource(Resource resource) {
try {
return resourceReader.readFrom(resource, this.getClass().getClassLoader());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
上文的AlbumRepositoryPopulator实现了ApplicationListener接口和ApplicationContextAware接口。它的逻辑是这样的,通过实现ApplicationContextAware接口获取当前上下文环境,然后通过实现ApplicationListener接口在上下文初始化或刷新的时候从resources\albums.json文件中读取数据并保存到当前数据库中。
ApplicationContextAware
通过实现ApplicationContextAware接口,可以使Bean获取它所在的容器。
例如通过setApplicationContext方法设置Bean实例的当前上下文环境。
ApplicationListener
AlbumRepositoryPopulator通过实现ApplicationListener接口实现了Spring的事件机制,为Bean与Bean之间的消息通信提供了支持。当一个Bean处理完一个任务之后,希望另外一个Bean知道并能够做相应的处理,这时就需要让另外一个Bean监听当前Bean所发送的事件。
如果要实现Spring的事件机制,需要做3件事情:
1、自定义继承ApplicationEvent的事件
2、定义实现ApplicationListener的事件监听器
3、使用容器发布事件
上文的AlbumRepositoryPopulator就是一个实现了ApplicationListener接口的事件监听器,事件监听逻辑写在onApplicationEvent函数里。
从代码可以看到,AlbumRepositoryPopulator监听的是ContextRefreshedEvent事件。Spring提供了一些默认事件,常用的有:
1、ContextRefreshedEvent:当ApplicationContext初始化或者刷新时触发该事件
2、ContextClosedEvent:ApplicationContext被关闭时触发该事件.容器被关闭时,其管理的所有单例Bean都被销毁
3、RequestHandleEvent:在Web应用中,当一个Http请求结束时触发该事件
4、ContextStartedEvent:当容器调用start()方法时触发
5、ContextStopEvent:当容器调用stop()方法时触发
onApplicationEvent
事件函数onApplicationEvent实现了数据初始化的功能,核心功能是在当前应用上下文下找到CrudRepository类型的Bean(可能是JpaAlbumRepository,可能是MongoAlbumRepository,也可能是RedisAlbumRepository,这个根据profile确定),然后从resources\albums.json文件读取数据并初始化到数据库中。
AlbumRepositoryPopulator类定义了一个私有类变量
private ApplicationContext applicationContext;
代表当前应用的上下文环境。
CrudRepository albumRepository =
BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, CrudRepository.class);
BeanFactoryUtils.beanOfTypeIncludingAncestors方法从上下文环境中获取CrudRepository类型的Bean。如果获取到CrudRepository类型的Bean,则执行populate函数。通过populate函数实现将工程resources\albums.json文件下的数据初始化到数据库中。
populate
populate函数实现将资源文件albums.json中的数据初始化到当前数据库中。
@SuppressWarnings("unchecked")
该批注的作用是给编译器一条指令,告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
Object entity = getEntityFromResource(sourceData);
将工程resources\albums.json中的数据加载成一个对象。其中sourceData是Resource类型,表示资源。通过sourceData = new ClassPathResource("albums.json")初始化,获取资源目录下的albums.json文件资源。
在getEntityFromResource方法中,使用resourceReader.readFrom(resource, this.getClass().getClassLoader())将sourceData资源转换成对象。
resourceReader是Jackson2ResourceReader类型,该对象基于Jackson库将结构化的资源文件转化为Object对象。初始化该对象时,需要传参ObjectMapper,用于定义Java对象与Json的映射结构。
5 AlbumController
创建实体和Repository后就完成了对Album对象的增删改查的功能实现。但此时前端页面还无法调用,需要暴露成REST服务接口才可以被前端页面访问调用,因此定义AlbumController暴露对Album的增删改查接口。
AlbumController实现将之前定义的repository函数封装成外界可以访问的REST API。
@RestController //构建一个Restful Web Service,支持返回xml或json数据
@RequestMapping(value = "/albums") //指定请求的实际地址
public class AlbumController {
private static final Logger logger = LoggerFactory.getLogger(AlbumController.class); //使用AlbumController类初始化日志对象
private CrudRepository repository; //引用AlbumRepository,为了便于根据profile重载Repository,此处使用的是父类CrudRepository
@Autowired
public AlbumController(CrudRepository repository) {
this.repository = repository;
}
@RequestMapping(method = RequestMethod.GET) //指定请求的实际地址,method指定请求的method类型
public Iterable albums() {
return repository.findAll();
}
@RequestMapping(method = RequestMethod.PUT)
public Album add(@RequestBody @Valid Album album) { //@RequestBody读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上;@Valid用于验证信息是否符合要求
logger.info("Adding album " + album.getId());
return repository.save(album);
}
@RequestMapping(method = RequestMethod.POST)
public Album update(@RequestBody @Valid Album album) {
logger.info("Updating album " + album.getId());
return repository.save(album);
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Album getById(@PathVariable String id) {
logger.info("Getting album " + id);
return repository.findOne(id);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public void deleteById(@PathVariable String id) {
logger.info("Deleting album " + id);
repository.delete(id);
}
}
6 InfoController
InfoController定义了运行环境的查询服务接口,用于查询当前运行的应用信息和服务信息。
核心功能基于两个类实现:
1、org.springframework.cloud.Cloud:Spring Cloud Connectors中的类,可以获取当前运行的云平台的环境信息。
2、org.springframework.core.env.Environment:获取当前激活的Profile中的属性数据
@RestController
public class InfoController {
@Autowired(required = false) //@Autowired(required = false)告诉 Spring在找不到匹配 Bean 时也不报错
private Cloud cloud; //Spring Cloud Connectors中的类,当部署在云环境下时,通过Cloud对象可以获取当前运行环境
private Environment springEnvironment; //获取当前激活的Profile的属性数据
@Autowired
public InfoController(Environment springEnvironment) {
this.springEnvironment = springEnvironment;
}
@RequestMapping(value = "/appinfo")
public ApplicationInfo info() {
return new ApplicationInfo(springEnvironment.getActiveProfiles(), getServiceNames());
}
@RequestMapping(value = "/service")
public List showServiceInfo() {
if (cloud != null) {
return cloud.getServiceInfos(); //查询当前全部服务信息
} else {
return new ArrayList<>();
}
}
private String[] getServiceNames() {
if (cloud != null) {
final List serviceInfos = cloud.getServiceInfos();
List names = new ArrayList<>();
for (ServiceInfo serviceInfo : serviceInfos) {
names.add(serviceInfo.getId());
}
return names.toArray(new String[names.size()]);
} else {
return new String[]{};
}
}
}
7 配置连接本地关系库
在创建完Model,Repository和Controller之后,实际就完成了数据存取服务的核心功能开发。但spring music项目主要目的是演示通过配置支持不同种类的数据库,因此配置文件是本项目的核心内容。在src\main\java\org.cloudfoudnry.samples.music\config\data文件夹下可以看到很多Config文件,这些都是配置文件。早期的Spring项目是基于XML配置的,后来出现了基于java的配置,配置方式更加简单。比如MySqlLocalDataSourceConfig:
@Configuration
@Profile("mysql-local")
public class MySqlLocalDataSourceConfig extends AbstractLocalDataSourceConfig {
@Bean
public DataSource dataSource() {
return createDataSource("jdbc:mysql://localhost/music", "com.mysql.jdbc.Driver", "", "");
}
}
@Configuration表示这是一个配置类,被注解的类内部包含一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,用于构建bean定义并初始化Spring容器。比如dataSource就是在配置文件中定义的一个Bean。
@Profile是spring提供的用来标明当前运行环境的注解,根据不同的环境选择实例化不同的Bean。Spring通过设定Enviroment的ActiveProfiles来设定当前context需要使用的配置环境。
MySqlLocalDataSourceConfig配置文件需要实例化的bean只有一个,就是dataSource。datasource是javax.sql.DataSource类型,用于表示提供到此 DataSource 对象表示的物理数据源的连接。DataSource 接口由驱动程序供应商实现。共有三种类型的实现:
1、基本实现 :生成标准 Connection 对象
2、连接池实现 :生成自动参与连接池的 Connection 对象。此实现与中间层连接池管理器一起使用。
3、分布式事务实现 :生成一个 Connection 对象,该对象可用于分布式事务,并且几乎始终参与连接池。此实现与中间层事务管理器一起使用,并且几乎始终与连接池管理器一起使用。
MySqlLocalDataSourceConfig 通过父类AbstractLocalDataSourceConfig的方法createDataSource创建了一个DataSource的基本实现,创建数据源时赋值数据源的四个属性:
1、jdbcUrl:数据库地址
2、driverClass:驱动名称
3、userName:数据库用户名
4、password:数据库密码
PostgresLocalDataSourceConfig与MySqlLocalDataSourceConfig类似。
创建了不同Profile的配置文件,如何运行的时候指定使用哪个profile呢?可以在启动时赋值参数。
$ ./gradlew tomcatRun -Dspring.profiles.active=<profile>
上面的命令,如果赋值mysql-local则使用配置文件MySqlLocalDataSourceConfig ,如果profile赋值postgres-local则使用配置文件PostgresLocalDataSourceConfig。
8 配置绑定Paas云关系数据库
spring应用绑定paas云上的数据库服务,依赖于Spring Cloud Spring Service Connector。RelationalCloudDataSourceConfig用于实现获取从paas云绑定的数据库服务获取得到的数据库连接。
@Configuration
@Profile({"mysql-cloud", "postgres-cloud", "oracle-cloud", "sqlserver-cloud"})
public class RelationalCloudDataSourceConfig extends AbstractCloudConfig {
@Bean
public DataSource dataSource() {
return connectionFactory().dataSource();
}
}
上面的代码演示了Spring Cloud Spring Service Connector的基本用法,通过继承AbstractCloudConfig 创建Java配置文件,然后创建数据源Bean。当只有唯一的关系数据库服务绑定到该应用时,DataSource Bean可以创建与该服务的数据库连接,如果未绑定数据库服务则Bean创建失败。
可能你会疑问,这里没有配置数据库地址等连接参数,如何建立连接的呢?这是因为通过service binding绑定关系数据库服务的时候,paas云平台会自动生成数据库地址、用户名、密码等连接参数,并将这些连接参数存储在应用运行容器的环境变量中。Spring Cloud Spring Service Connector的DataSource Bean可以自动从环境变量中读取连接参数并建立数据库连接。
扩展阅读:http://cloud.spring.io/spring-cloud-connectors/spring-cloud-spring-service-connector.html
9 自动识别激活Profile
如果只是绑定一种类型的数据库,比如Mysql,前文的配置文件就够用了。当然,此时@Profile可以指定为cloud,因为在cloud foundry上部署spring应用,将自动启用“cloud” profile。
但spring-music演示了一个比较通用的功能,就是自动识别绑定的数据库服务类型,然后激活对应的profile。这个功能是通过SpringApplicationContextInitializer实现的。
SpringApplicationContextInitializer继承ApplicationContextInitializer接口,在spring容器刷新之前执行回调函数initialize。initialize函数是这样的:
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
Cloud cloud = getCloud(); //获取运行应用程序的环境的Cloud对象
ConfigurableEnvironment appEnvironment = applicationContext.getEnvironment(); //获取运行应用程序的配置环境
String[] persistenceProfiles = getCloudProfile(cloud); //如果运行在云环境中,根据绑定服务的类型设置激活profile,比如绑定的是mysql服务,则生成mysql-cloud profile;绑定的是oracle服务,则生成的是oracle-cloud profile.
if (persistenceProfiles == null) {
persistenceProfiles = getActiveProfile(appEnvironment); //如果不是在云环境运行,则判断是不是通过命令$ ./gradlew tomcatRun -Dspring.profiles.active=在本地运行。比如在本地运行绑定Mysql服务,则设置profile为mysql。getActiveProfile函数就会设置当前激活的profile为mysql-lcoal。
}
if (persistenceProfiles == null) {
persistenceProfiles = new String[] { IN_MEMORY_PROFILE }; //如果运行应用时未设置profile,就使用默认的内存数据库。
}
for (String persistenceProfile : persistenceProfiles) {
appEnvironment.addActiveProfile(persistenceProfile); //将激活的profile绑定到当前运行应用上。
}
}
通过以上代码就可以实现了在本地或cloud foundry上运行spring应用并绑定数据库服务的功能了。