作者簡介
陳喆劣纲,現(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ù)的功能了。