通過spring-music項(xiàng)目講解spring數(shù)據(jù)庫服務(wù)綁定

作者簡介

陳喆劣纲,現(xiàn)就職于中科院某研究所擔(dān)任副研究員挎塌,專注于工業(yè)云平臺采缚、MES系統(tǒng)的設(shè)計(jì)與研發(fā)嘴脾。

spring-music項(xiàng)目是一個(gè)用于演示在Cloud Foundry上實(shí)現(xiàn)綁定數(shù)據(jù)庫服務(wù)的spring項(xiàng)目视乐。項(xiàng)目地址:https://github.com/scottfrederick/spring-music

本文重點(diǎn)講解spring-music實(shí)現(xiàn)數(shù)據(jù)庫綁定功能的相關(guān)代碼實(shí)現(xiàn)洛搀,幫助你理解如何創(chuàng)建一個(gè)spring應(yīng)用實(shí)現(xiàn)同時(shí)支持按照地址訪問數(shù)據(jù)庫和按service binding訪問數(shù)據(jù)庫。

1 初始化項(xiàng)目

如果要從零創(chuàng)建spring-music項(xiàng)目可以使用spring initializr:http://start.spring.io/佑淀。

創(chuàng)建Gradle Project留美,選擇Spring Boot 1.

添加項(xiàng)目參數(shù)和依賴

初始只添加四個(gè)基礎(chǔ)依賴:

1、Web:添加spring-boot-starter-web依賴伸刃,引入spring mvc支持

2谎砾、Actuator:添加spring-boot-starter-actuator依賴,可以用于監(jiān)控系統(tǒng)健康狀況

3捧颅、JPA:添加spring-boot-starter-data-jpa依賴景图,支持對數(shù)據(jù)庫訪問。Spring Data JPA封裝了一套規(guī)范的API碉哑,通過統(tǒng)一的接口實(shí)現(xiàn)對不同廠商的ORM組件的調(diào)用

4挚币、H2:添加h2依賴,H2是一個(gè)用Java開發(fā)的嵌入式數(shù)據(jù)庫扣典,可以同應(yīng)用程序打包在一起發(fā)布妆毕,這樣可以非常方便地存儲少量結(jié)構(gòu)化數(shù)據(jù)。也可以用于測試和緩存贮尖。

項(xiàng)目創(chuàng)建成功后笛粘,生成的代碼結(jié)構(gòu)是這樣的:

工程中關(guān)鍵文件/文件夾有如下幾項(xiàng):

src文件夾:源文件

? main:功能代碼

??? java: java類文件夾

????? SpringMusicApplication:工程啟動類

? resources: 資源文件夾

??? static:靜態(tài)資源文件

??? templates:模板文件

??? application.properties:項(xiàng)目配置文件

? test:測試代碼

build.gralde:Gradle配置文件

gradlew:Gradle Wrapper的可執(zhí)行腳本。Gradle Wrapper是對Gradle的一層包裝,便于在團(tuán)隊(duì)開發(fā)過程中統(tǒng)一Gradle構(gòu)建的版本

gradlew.bat:Gradle Wrappter在Windows下的可執(zhí)行腳本

settings.gradle:多模塊項(xiàng)目中根模塊項(xiàng)目的模塊描述文件薪前,但模塊項(xiàng)目可以不管它

進(jìn)行項(xiàng)目根目錄下润努,執(zhí)行命令

# gradlew bootrun

可以運(yùn)行程序,但由于還沒有開發(fā)頁面序六,所以無法訪問到頁面任连。


2 解讀build.gralde文件

由于在初始化項(xiàng)目是選擇的項(xiàng)目類型是Gradle Project,所以在工程目錄中可以看到build.gradle文件例诀。Gralde基于Groovy語言提供了一個(gè)構(gòu)建項(xiàng)目的框架随抠,通過集成眾多plugin來實(shí)現(xiàn)各種自動化構(gòu)建功能。build.gradle是默認(rèn)生成的gradle腳本文件繁涂,內(nèi)容如下:

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由如下幾個(gè)代碼塊組成拱她。

buildscript 代碼塊

buildscript {

?? ext {

? ? ? springBootVersion = '1.5.14.RELEASE'

?? }

?? repositories {

? ? ? mavenCentral()

?? }

?? dependencies {

? ? ? classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")

?? }

}

上文的buildscript代碼塊用于設(shè)置腳本的運(yùn)行環(huán)境,gradle在執(zhí)行腳本時(shí)扔罪,會優(yōu)先執(zhí)行buildscript代碼塊中的內(nèi)容秉沼,然后才會執(zhí)行剩余的build腳本。buildscript代碼塊包括如下子代碼塊:

1 ext{}:定義了gradle的額外屬性矿酵,此處定義了本項(xiàng)目使用的spring boot版本(springBootVersion)唬复。

2 repositories {}:java依賴庫管理,用于指定下載依賴包的maven庫全肮〕ㄟ郑可以指定官方的依賴庫,也可以指定自己配置的私有依賴庫辜腺。默認(rèn)使用?mavenCentral()休建,即maven中心倉庫。

3 dependencies{}:定義依賴路徑评疗。支持maven/ivy测砂,遠(yuǎn)程,本地庫百匆,也支持單文件砌些。如果前面定義了repositories{}maven 庫,則使用maven的依賴庫加匈,gradle 就會自動的往遠(yuǎn)程庫下載相應(yīng)的依賴寄症。

? 3.1 本項(xiàng)目添加了spring-boot-gradle-plugin插件,它主要提供一下幾個(gè)功能:

????? 3.1.1 簡化執(zhí)行和發(fā)布:它可以把所有classpath的類庫構(gòu)建成一個(gè)單獨(dú)的可執(zhí)行jar文件矩动,這樣可以簡化你的執(zhí)行和發(fā)布等操作。

????? 3.1.2 自動搜索入口文件:它會掃描 public static void main() 函數(shù)并且標(biāo)記這個(gè)函數(shù)的宿主類為可執(zhí)行入口释漆。

????? 3.1.3 簡化依賴:一個(gè)典型的Spring應(yīng)用還是需要很多依賴類庫的悲没,想要配置正確這些依賴挺麻煩的,所以這個(gè)插件提供了內(nèi)建的依賴解析器會自動匹配和當(dāng)前Spring Boot版本匹配的依賴庫版本。

應(yīng)用插件代碼塊

apply plugin: 'java'

apply plugin: 'eclipse'

apply plugin: 'org.springframework.boot'

apply plugin:聲明引用插件的類型示姿。什么是插件(plugin)呢甜橱?Gradle能夠發(fā)揮作用,是依靠執(zhí)行腳本中的任務(wù)(task)栈戳,一個(gè)任務(wù)是一個(gè)原子操作岂傲,即不可分割的。項(xiàng)目開發(fā)過程中子檀,我們往往需要按照一定順序執(zhí)行多個(gè)任務(wù)以完成某個(gè)特定功能镊掖,我們將這些任務(wù)及其屬性、配置封裝在一起形成插件(plugin)褂痰。插件分為腳本插件和二進(jìn)制插件亩进。腳本插件是一個(gè)Gradle文件,二進(jìn)制插件則經(jīng)過編譯形成可執(zhí)行文件缩歪,本項(xiàng)目引用的是二進(jìn)制插件归薛。

初始項(xiàng)目引用了3個(gè)插件:

1、java plugin[https://docs.gradle.org/current/userguide/java_plugin.html]:Java plugin插件是Gradle內(nèi)置插件匪蝙,指定項(xiàng)目為java項(xiàng)目主籍,提供了一系列的任務(wù)支持構(gòu)建、編譯逛球、測試Java項(xiàng)目千元,項(xiàng)目編譯(在項(xiàng)目提示符下執(zhí)行:gradle build)時(shí)生成項(xiàng)目的jar包。

2需忿、eclipse plugin[https://docs.gradle.org/current/userguide/eclipse_plugin.html]:是Gradle內(nèi)置插件诅炉,用于生成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 :指定當(dāng)前工程的版本

2、sourceCompatibility: 指定編譯.java文件的jdk版本

項(xiàng)目依賴

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聲明說明了在執(zhí)行其余的build腳本時(shí),class loader可以使用這些你提供的依賴項(xiàng)溢谤。這也正是我們使用buildscript代碼塊的目的瞻凤。而如果你的項(xiàng)目中需要使用該類庫的話,就需要定義在buildscript代碼塊之外的dependencies代碼塊中世杀。

在dependencies {}代碼塊中阀参,有幾種不同的依賴配置方式,如果不正確配置的話瞻坝,就會遇到依賴包無法導(dǎo)入或者runtime蛛壳、providedCompile無法使用的情況:

1、compile:如果你的jar包/依賴代碼在編譯的時(shí)候需要依賴,在運(yùn)行的時(shí)候也需要衙荐,那么就用compile捞挥。前提:apply plugin: 'war'或者apply plugin: 'java'。

2忧吟、providedCompile: 如果你的jar包/依賴代碼僅在編譯的時(shí)候需要砌函,但是在運(yùn)行時(shí)不需要依賴,就用providedCompile。前提:apply plugin: 'war'溜族。

3讹俊、runtime: 如果你的jar包/依賴代碼僅在運(yùn)行的時(shí)候需要,但是在編譯時(shí)不需要依賴,就用runtime 斩祭。前提:apply plugin: 'java'劣像。

修改配置內(nèi)容

spring-music項(xiàng)目的build.gradle文件在初始化項(xiàng)目的build.gradle文件的基礎(chǔ)上新增了一些內(nèi)容。

buildscript代碼塊中repositories增加

jcenter()

maven { url "https://repo.spring.io/plugins-release" }

1摧玫、jcenter():新的中央遠(yuǎn)程倉庫耳奕,兼容maven中心倉庫,而且性能更優(yōu)

2诬像、maven { url "https://repo.spring.io/plugins-release" }:引用spring提供的release版插件庫

應(yīng)用插件代碼塊增加

apply plugin: 'eclipse-wtp'

apply plugin: 'idea'

1屋群、eclipse-wtp:插件將構(gòu)建web項(xiàng)目的開發(fā)環(huán)境,生成所需要的.project,.classpath等文件坏挠。因?yàn)槲襴eb開發(fā)使用的是eclipse-j2ee版本芍躏,所以指定為wtp環(huán)境。

2降狠、idea:是Gradle內(nèi)置插件对竣,用于生成Eclipse IDE所需的.project,.classpath等文件。

注:spring-music使用了“apply plugin: 'spring-boot'”榜配,這是“apply plugin: 'org.springframework.boot'”的早期版本否纬,現(xiàn)在不再使用。

targetCompatibility = 1.8

確保class文件與targetCompatibility指定版本蛋褥,或者更新的java虛擬機(jī)兼容临燃,

jar {

? ? baseName = "spring-music"

? ? version = "" // omit the version from the war file name

}

jar任務(wù)是“apply plugin: 'java'”提供的任務(wù),可以自定義jar包烙心,此處自定義了jar包的名稱和版本膜廊。

task wrapper(type: Wrapper) {

? ? gradleVersion = '2.14'

}

此處指定了gradle wrapper的版本信息。


3 創(chuàng)建實(shí)體類

spring-music項(xiàng)目是一個(gè)唱片管理網(wǎng)站淫茵,按照常規(guī)套路爪瓜,編寫Java程序首先設(shè)計(jì)并創(chuàng)建實(shí)體類。Album類展示了聲明一個(gè)實(shí)體類最常用的結(jié)構(gòu)和要素匙瘪。

Album類

Album作為唱片的實(shí)體類钥勋,描述了唱片所必須的屬性字段炬转,同時(shí)通過ORM屬性將該實(shí)體類與數(shù)據(jù)庫表建立映射關(guān)系。

@Entity //? 對實(shí)體注釋算灸,說明此java類是實(shí)體類,任何Hibernate映射對象都要有這個(gè)注釋

public class Album {

? ? @Id? //聲明此屬性為主鍵驻啤。該屬性值可以通過應(yīng)該自身創(chuàng)建

? ? @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();

? ? }

}

該類實(shí)現(xiàn)了IdentifierGenerator 接口赊瞬,通過重寫generate()方法自定義主鍵生成機(jī)制。


4 創(chuàng)建關(guān)系數(shù)據(jù)庫Repository

創(chuàng)建完實(shí)體類贼涩,通過定義Repository實(shí)現(xiàn)數(shù)據(jù)持久化巧涧。本文重點(diǎn)講關(guān)系數(shù)據(jù)庫的相關(guān)內(nèi)容,mongodb和redis與關(guān)系數(shù)據(jù)庫類似遥倦。

JpaAlbumRepository

本工程使用spring data jpa谤绳,通過繼承JpaRepository就實(shí)現(xiàn)了對實(shí)體對象的增刪改查,相當(dāng)簡單袒哥。

@Repository? //@Repository用于標(biāo)注數(shù)據(jù)訪問組件缩筛,即DAO組件

@Profile({"in-memory", "mysql", "postgres", "oracle", "sqlserver"})? //標(biāo)明當(dāng)前運(yùn)行環(huán)境,在spring使用DI來依賴注入的時(shí)候堡称,能夠根據(jù)當(dāng)前制定的運(yùn)行環(huán)境來注入相應(yīng)的bean

public interface JpaAlbumRepository extends JpaRepository {

}

AlbumRepositoryPopulator

AlbumRepositoryPopulator的主要功能是做數(shù)據(jù)初始化瞎抛。

@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實(shí)現(xiàn)了ApplicationListener接口和ApplicationContextAware接口。它的邏輯是這樣的却紧,通過實(shí)現(xiàn)ApplicationContextAware接口獲取當(dāng)前上下文環(huán)境桐臊,然后通過實(shí)現(xiàn)ApplicationListener接口在上下文初始化或刷新的時(shí)候從resources\albums.json文件中讀取數(shù)據(jù)并保存到當(dāng)前數(shù)據(jù)庫中。

ApplicationContextAware

通過實(shí)現(xiàn)ApplicationContextAware接口晓殊,可以使Bean獲取它所在的容器断凶。

例如通過setApplicationContext方法設(shè)置Bean實(shí)例的當(dāng)前上下文環(huán)境。

ApplicationListener

AlbumRepositoryPopulator通過實(shí)現(xiàn)ApplicationListener接口實(shí)現(xiàn)了Spring的事件機(jī)制挺物,為Bean與Bean之間的消息通信提供了支持懒浮。當(dāng)一個(gè)Bean處理完一個(gè)任務(wù)之后,希望另外一個(gè)Bean知道并能夠做相應(yīng)的處理识藤,這時(shí)就需要讓另外一個(gè)Bean監(jiān)聽當(dāng)前Bean所發(fā)送的事件砚著。

如果要實(shí)現(xiàn)Spring的事件機(jī)制,需要做3件事情:

1痴昧、自定義繼承ApplicationEvent的事件

2稽穆、定義實(shí)現(xiàn)ApplicationListener的事件監(jiān)聽器

3、使用容器發(fā)布事件

上文的AlbumRepositoryPopulator就是一個(gè)實(shí)現(xiàn)了ApplicationListener接口的事件監(jiān)聽器赶撰,事件監(jiān)聽邏輯寫在onApplicationEvent函數(shù)里舌镶。

從代碼可以看到柱彻,AlbumRepositoryPopulator監(jiān)聽的是ContextRefreshedEvent事件。Spring提供了一些默認(rèn)事件餐胀,常用的有:

1哟楷、ContextRefreshedEvent:當(dāng)ApplicationContext初始化或者刷新時(shí)觸發(fā)該事件

2、ContextClosedEvent:ApplicationContext被關(guān)閉時(shí)觸發(fā)該事件.容器被關(guān)閉時(shí),其管理的所有單例Bean都被銷毀

3否灾、RequestHandleEvent:在Web應(yīng)用中,當(dāng)一個(gè)Http請求結(jié)束時(shí)觸發(fā)該事件

4卖擅、ContextStartedEvent:當(dāng)容器調(diào)用start()方法時(shí)觸發(fā)

5、ContextStopEvent:當(dāng)容器調(diào)用stop()方法時(shí)觸發(fā)

onApplicationEvent

事件函數(shù)onApplicationEvent實(shí)現(xiàn)了數(shù)據(jù)初始化的功能墨技,核心功能是在當(dāng)前應(yīng)用上下文下找到CrudRepository類型的Bean(可能是JpaAlbumRepository,可能是MongoAlbumRepository惩阶,也可能是RedisAlbumRepository,這個(gè)根據(jù)profile確定)扣汪,然后從resources\albums.json文件讀取數(shù)據(jù)并初始化到數(shù)據(jù)庫中断楷。

AlbumRepositoryPopulator類定義了一個(gè)私有類變量

private ApplicationContext applicationContext;

代表當(dāng)前應(yīng)用的上下文環(huán)境。

CrudRepository albumRepository =

? ? ? ? BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, CrudRepository.class);

BeanFactoryUtils.beanOfTypeIncludingAncestors方法從上下文環(huán)境中獲取CrudRepository類型的Bean崭别。如果獲取到CrudRepository類型的Bean冬筒,則執(zhí)行populate函數(shù)。通過populate函數(shù)實(shí)現(xiàn)將工程resources\albums.json文件下的數(shù)據(jù)初始化到數(shù)據(jù)庫中紊遵。

populate

populate函數(shù)實(shí)現(xiàn)將資源文件albums.json中的數(shù)據(jù)初始化到當(dāng)前數(shù)據(jù)庫中账千。

@SuppressWarnings("unchecked")

該批注的作用是給編譯器一條指令,告訴編譯器忽略 unchecked 警告信息暗膜,如使用List匀奏,ArrayList等未進(jìn)行參數(shù)化產(chǎn)生的警告信息。

Object entity = getEntityFromResource(sourceData);

將工程resources\albums.json中的數(shù)據(jù)加載成一個(gè)對象学搜。其中sourceData是Resource類型娃善,表示資源。通過sourceData = new ClassPathResource("albums.json")初始化瑞佩,獲取資源目錄下的albums.json文件資源聚磺。

在getEntityFromResource方法中,使用resourceReader.readFrom(resource, this.getClass().getClassLoader())將sourceData資源轉(zhuǎn)換成對象炬丸。

resourceReader是Jackson2ResourceReader類型瘫寝,該對象基于Jackson庫將結(jié)構(gòu)化的資源文件轉(zhuǎn)化為Object對象。初始化該對象時(shí)稠炬,需要傳參ObjectMapper焕阿,用于定義Java對象與Json的映射結(jié)構(gòu)。


5 AlbumController

創(chuàng)建實(shí)體和Repository后就完成了對Album對象的增刪改查的功能實(shí)現(xiàn)首启。但此時(shí)前端頁面還無法調(diào)用暮屡,需要暴露成REST服務(wù)接口才可以被前端頁面訪問調(diào)用,因此定義AlbumController暴露對Album的增刪改查接口毅桃。

AlbumController實(shí)現(xiàn)將之前定義的repository函數(shù)封裝成外界可以訪問的REST API褒纲。

@RestController //構(gòu)建一個(gè)Restful Web Service准夷,支持返回xml或json數(shù)據(jù)

@RequestMapping(value = "/albums")? //指定請求的實(shí)際地址

public class AlbumController {

? ? private static final Logger logger = LoggerFactory.getLogger(AlbumController.class);? ? ? ? //使用AlbumController類初始化日志對象

? ? private CrudRepository repository;?? //引用AlbumRepository,為了便于根據(jù)profile重載Repository莺掠,此處使用的是父類CrudRepository

? ? @Autowired

? ? public AlbumController(CrudRepository repository) {

? ? ? ? this.repository = repository;

? ? }

? ? @RequestMapping(method = RequestMethod.GET)? ?? //指定請求的實(shí)際地址衫嵌,method指定請求的method類型

? ? public Iterable albums() {

? ? ? ? return repository.findAll();

? ? }

? ? @RequestMapping(method = RequestMethod.PUT)

? public Album add(@RequestBody @Valid Album album) {? ?? //@RequestBody讀取Request請求的body部分?jǐn)?shù)據(jù),使用系統(tǒng)默認(rèn)配置的HttpMessageConverter進(jìn)行解析汁蝶,然后把相應(yīng)的數(shù)據(jù)綁定到要返回的對象上渐扮;@Valid用于驗(yàn)證信息是否符合要求

? ? ? ? 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定義了運(yùn)行環(huán)境的查詢服務(wù)接口,用于查詢當(dāng)前運(yùn)行的應(yīng)用信息和服務(wù)信息掖棉。

核心功能基于兩個(gè)類實(shí)現(xiàn):

1、org.springframework.cloud.Cloud:Spring Cloud Connectors中的類膀估,可以獲取當(dāng)前運(yùn)行的云平臺的環(huán)境信息幔亥。

2、org.springframework.core.env.Environment:獲取當(dāng)前激活的Profile中的屬性數(shù)據(jù)

@RestController

public class InfoController {

? ? @Autowired(required = false)? ? ? ? //@Autowired(required = false)告訴 Spring在找不到匹配 Bean 時(shí)也不報(bào)錯(cuò)

? ? private Cloud cloud;? ? //Spring Cloud Connectors中的類察纯,當(dāng)部署在云環(huán)境下時(shí)帕棉,通過Cloud對象可以獲取當(dāng)前運(yùn)行環(huán)境

? ? private Environment springEnvironment;? ? ? //獲取當(dāng)前激活的Profile的屬性數(shù)據(jù)

? ? @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();? ?? //查詢當(dāng)前全部服務(wù)信息

? ? ? ? } 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 配置連接本地關(guān)系庫

在創(chuàng)建完Model,Repository和Controller之后,實(shí)際就完成了數(shù)據(jù)存取服務(wù)的核心功能開發(fā)饼记。但spring music項(xiàng)目主要目的是演示通過配置支持不同種類的數(shù)據(jù)庫香伴,因此配置文件是本項(xiàng)目的核心內(nèi)容。在src\main\java\org.cloudfoudnry.samples.music\config\data文件夾下可以看到很多Config文件具则,這些都是配置文件即纲。早期的Spring項(xiàng)目是基于XML配置的,后來出現(xiàn)了基于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表示這是一個(gè)配置類,被注解的類內(nèi)部包含一個(gè)或多個(gè)被@Bean注解的方法匪凡,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進(jìn)行掃描膊畴,用于構(gòu)建bean定義并初始化Spring容器。比如dataSource就是在配置文件中定義的一個(gè)Bean病游。

@Profile是spring提供的用來標(biāo)明當(dāng)前運(yùn)行環(huán)境的注解唇跨,根據(jù)不同的環(huán)境選擇實(shí)例化不同的Bean。Spring通過設(shè)定Enviroment的ActiveProfiles來設(shè)定當(dāng)前context需要使用的配置環(huán)境衬衬。

MySqlLocalDataSourceConfig配置文件需要實(shí)例化的bean只有一個(gè)买猖,就是dataSource。datasource是javax.sql.DataSource類型佣耐,用于表示提供到此 DataSource 對象表示的物理數(shù)據(jù)源的連接政勃。DataSource 接口由驅(qū)動程序供應(yīng)商實(shí)現(xiàn)。共有三種類型的實(shí)現(xiàn):

1兼砖、基本實(shí)現(xiàn) :生成標(biāo)準(zhǔn) Connection 對象

2奸远、連接池實(shí)現(xiàn) :生成自動參與連接池的 Connection 對象既棺。此實(shí)現(xiàn)與中間層連接池管理器一起使用。

3懒叛、分布式事務(wù)實(shí)現(xiàn) :生成一個(gè) Connection 對象丸冕,該對象可用于分布式事務(wù),并且?guī)缀跏冀K參與連接池薛窥。此實(shí)現(xiàn)與中間層事務(wù)管理器一起使用胖烛,并且?guī)缀跏冀K與連接池管理器一起使用。

MySqlLocalDataSourceConfig 通過父類AbstractLocalDataSourceConfig的方法createDataSource創(chuàng)建了一個(gè)DataSource的基本實(shí)現(xiàn)诅迷,創(chuàng)建數(shù)據(jù)源時(shí)賦值數(shù)據(jù)源的四個(gè)屬性:

1佩番、jdbcUrl:數(shù)據(jù)庫地址

2、driverClass:驅(qū)動名稱

3罢杉、userName:數(shù)據(jù)庫用戶名

4嘴纺、password:數(shù)據(jù)庫密碼

PostgresLocalDataSourceConfig與MySqlLocalDataSourceConfig類似赴蝇。

創(chuàng)建了不同Profile的配置文件整胃,如何運(yùn)行的時(shí)候指定使用哪個(gè)profile呢杠愧?可以在啟動時(shí)賦值參數(shù)。

$ ./gradlew tomcatRun -Dspring.profiles.active=<profile>

上面的命令律想,如果賦值mysql-local則使用配置文件MySqlLocalDataSourceConfig 猎莲,如果profile賦值postgres-local則使用配置文件PostgresLocalDataSourceConfig。


8 配置綁定Paas云關(guān)系數(shù)據(jù)庫

spring應(yīng)用綁定paas云上的數(shù)據(jù)庫服務(wù)技即,依賴于Spring Cloud Spring Service Connector著洼。RelationalCloudDataSourceConfig用于實(shí)現(xiàn)獲取從paas云綁定的數(shù)據(jù)庫服務(wù)獲取得到的數(shù)據(jù)庫連接。

@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 創(chuàng)建Java配置文件郭脂,然后創(chuàng)建數(shù)據(jù)源Bean。當(dāng)只有唯一的關(guān)系數(shù)據(jù)庫服務(wù)綁定到該應(yīng)用時(shí)澈歉,DataSource Bean可以創(chuàng)建與該服務(wù)的數(shù)據(jù)庫連接展鸡,如果未綁定數(shù)據(jù)庫服務(wù)則Bean創(chuàng)建失敗。

可能你會疑問埃难,這里沒有配置數(shù)據(jù)庫地址等連接參數(shù)莹弊,如何建立連接的呢?這是因?yàn)橥ㄟ^service binding綁定關(guān)系數(shù)據(jù)庫服務(wù)的時(shí)候涡尘,paas云平臺會自動生成數(shù)據(jù)庫地址忍弛、用戶名、密碼等連接參數(shù)考抄,并將這些連接參數(shù)存儲在應(yīng)用運(yùn)行容器的環(huán)境變量中细疚。Spring Cloud Spring Service Connector的DataSource Bean可以自動從環(huán)境變量中讀取連接參數(shù)并建立數(shù)據(jù)庫連接。

擴(kuò)展閱讀:http://cloud.spring.io/spring-cloud-connectors/spring-cloud-spring-service-connector.html


9 自動識別激活Profile

如果只是綁定一種類型的數(shù)據(jù)庫川梅,比如Mysql疯兼,前文的配置文件就夠用了然遏。當(dāng)然,此時(shí)@Profile可以指定為cloud吧彪,因?yàn)樵赾loud foundry上部署spring應(yīng)用待侵,將自動啟用“cloud” profile。

但spring-music演示了一個(gè)比較通用的功能姨裸,就是自動識別綁定的數(shù)據(jù)庫服務(wù)類型秧倾,然后激活對應(yīng)的profile。這個(gè)功能是通過SpringApplicationContextInitializer實(shí)現(xiàn)的傀缩。

SpringApplicationContextInitializer繼承ApplicationContextInitializer接口那先,在spring容器刷新之前執(zhí)行回調(diào)函數(shù)initialize。initialize函數(shù)是這樣的:

@Override

public void initialize(ConfigurableApplicationContext applicationContext) {

? ? Cloud cloud = getCloud(); //獲取運(yùn)行應(yīng)用程序的環(huán)境的Cloud對象

? ? ConfigurableEnvironment appEnvironment = applicationContext.getEnvironment(); //獲取運(yùn)行應(yīng)用程序的配置環(huán)境

? ? String[] persistenceProfiles = getCloudProfile(cloud); //如果運(yùn)行在云環(huán)境中赡艰,根據(jù)綁定服務(wù)的類型設(shè)置激活profile胃榕,比如綁定的是mysql服務(wù),則生成mysql-cloud profile瞄摊;綁定的是oracle服務(wù),則生成的是oracle-cloud profile.

? ? if (persistenceProfiles == null) {

? ? ? ? persistenceProfiles = getActiveProfile(appEnvironment); //如果不是在云環(huán)境運(yùn)行苦掘,則判斷是不是通過命令$ ./gradlew tomcatRun -Dspring.profiles.active=在本地運(yùn)行换帜。比如在本地運(yùn)行綁定Mysql服務(wù),則設(shè)置profile為mysql鹤啡。getActiveProfile函數(shù)就會設(shè)置當(dāng)前激活的profile為mysql-lcoal惯驼。

? ? }

? ? if (persistenceProfiles == null) {

? ? ? ? persistenceProfiles = new String[] { IN_MEMORY_PROFILE }; //如果運(yùn)行應(yīng)用時(shí)未設(shè)置profile,就使用默認(rèn)的內(nèi)存數(shù)據(jù)庫递瑰。

? ? }

? ? for (String persistenceProfile : persistenceProfiles) {

? ? ? ? appEnvironment.addActiveProfile(persistenceProfile); //將激活的profile綁定到當(dāng)前運(yùn)行應(yīng)用上祟牲。

? ? }

}

通過以上代碼就可以實(shí)現(xiàn)了在本地或cloud foundry上運(yùn)行spring應(yīng)用并綁定數(shù)據(jù)庫服務(wù)的功能了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抖部,一起剝皮案震驚了整個(gè)濱河市说贝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慎颗,老刑警劉巖乡恕,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俯萎,居然都是意外死亡傲宜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門夫啊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來函卒,“玉大人,你說我怎么就攤上這事撇眯”ㄇ叮” “怎么了虱咧?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沪蓬。 經(jīng)常有香客問我彤钟,道長,這世上最難降的妖魔是什么跷叉? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任逸雹,我火速辦了婚禮,結(jié)果婚禮上云挟,老公的妹妹穿的比我還像新娘梆砸。我一直安慰自己,他們只是感情好园欣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布帖世。 她就那樣靜靜地躺著,像睡著了一般沸枯。 火紅的嫁衣襯著肌膚如雪日矫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天绑榴,我揣著相機(jī)與錄音哪轿,去河邊找鬼。 笑死翔怎,一個(gè)胖子當(dāng)著我的面吹牛窃诉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赤套,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼飘痛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了容握?” 一聲冷哼從身側(cè)響起宣脉,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唯沮,沒想到半個(gè)月后脖旱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡介蛉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年萌庆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片币旧。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡践险,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巍虫,我是刑警寧澤彭则,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站占遥,受9級特大地震影響俯抖,放射性物質(zhì)發(fā)生泄漏瓦胎。R本人自食惡果不足惜柬祠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一莽龟、第九天 我趴在偏房一處隱蔽的房頂上張望绍绘。 院中可真熱鬧陪拘,春花似錦欠痴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工所意, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秧耗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓瘫辩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子衔瓮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容