通过spring-music项目讲解spring数据库服务绑定

作者简介

陈喆,现就职于中科院某研究所担任副研究员,专注于工业云平台、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应用并绑定数据库服务的功能了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容