第一節(jié) SpringApplication
本文是在預習和學習后掘猿,想要盡自己所能將學到的知識以自己的理解記錄下來憾筏。當然件相,有些地方還是一知半解,所以是有很多內容是小馬哥所講所書骗污,權當引經據(jù)典,還望小馬哥見到不要怪罪沈条。
雖然今年項目上已經接觸過SpringBoot需忿,目前仍在使用,但了解到的只是常用的用法蜡歹。這篇文章主要講一下微服務的概念屋厘,以SpringApplication啟動類為切入點,講解Spring注解機制月而,SpringApplication啟動類的寫法和程序類型推斷汗洒,以及SpringBoot事件和事件監(jiān)聽。
一景鼠、微服務是什么仲翎?能做什么痹扇?
1铛漓、概念
微服務是一種細粒度(Fine-Grain)的SOA
SOA = Service-Oriented Architecture 面向服務架構
SOA的特征:
面向服務( Service-Oriented )
松耦合(Loose-Coupling)
模塊化(Modular)
分布式計算(Distributed Computing)
平臺無關性(Independent Platform)
集中管理(Center Government)
用到的技術:
Web Services
關于Web Services,小馬哥文章中講到下面一段話鲫构,因個人技術原因浓恶,暫時沒有特別讀懂,暫且放在這里供大家參考结笨,也歡迎各位大牛指點迷津包晰。
Web Services 技術演進的目的在于解決分布式計算中,統(tǒng)一異構系統(tǒng)的服務調用的通訊協(xié)議炕吸。前期的Web Services有XML-PRC伐憾、WSDL、SOAP等技術赫模,不但解決了Windows平臺COM+以及Java 平臺RMI無法跨平臺的問題树肃,而且使用了可讀性強的本文協(xié)議替代了復雜的二進制協(xié)議,如CORBA技術∑俾蓿現(xiàn)代的WebServices 技術主要代表有REST等胸嘴。
此外,還提到微服務并非等同于單體應用斩祭,我所理解的意思是單體應用只有一個核心劣像,是中心化的,任務是應用整體來完成的摧玫,有較高的耦合度耳奕。而微服務是去中心化的,多核的,將任務拆解屋群,由各個模塊分工協(xié)作完成的时迫,耦合度是較前者要低的。
2谓晌、微服務的優(yōu)勢和適用場景
優(yōu)勢:
(1)效率:應用微服務化后掠拳,變得更加輕量級,在編譯纸肉、打包溺欧、分發(fā)、部署等環(huán)節(jié)節(jié)約時間柏肪,提升效率姐刁;
(2)質量:面向持續(xù)集成友好,自動化編譯烦味、單元和集成測試用例執(zhí)行和回歸聂使,提高整體質量。
這里講的所謂“持續(xù)集成”谬俄,個人不是很清楚這個概念柏靶。
(3)穩(wěn)定:當應用大而全時,一個服務的問題可能會導致整體受到影響溃论。而微服務在服務A調用服務B出現(xiàn)問題時屎蜓,可以選擇降級或熔斷的方式進行處理,等待服務B正常運行后钥勋,恢復正常炬转。
(4)運維:微服務應用具備自動化編譯、打包算灸、分發(fā)扼劈、部署和運維的能力。
(5)成長:對新技術的適配能力菲驴,例如:Apache Kafka荐吵。在重要等級較低的微服務應用上,可以更好的做新技術的嘗試谢翎。
3捍靠、不適用微服務的應用場景
(1)場景單一:如果應用本身規(guī)模就不大,就沒必要使用微服務森逮,因為本身就是一種微服務榨婆。舉例如:一些靜態(tài)通告頁面。
(2)邏輯簡單:微服務是為了解決業(yè)務邏輯復雜性問題褒侧,因此此場景無需微服務良风。
(3)業(yè)務漸逝:如果業(yè)務將要消逝或停用谊迄,就不需要實施了。
(4)“老成持重”:老--服役三年以上的應用烟央;成--應用規(guī)模已經成型统诺;持--應用場景還需要長時間維持湾戳;重--應用屬于系統(tǒng)中較重要的模塊版述。
二、微服務用到的技術
技術上嫩痰,在阿里微服務的實踐過程中也不能免俗钞艇,基本上也是以下三個套路:
Docker
DDD
Middleware(Java)
此處雖然有所講解啄寡,但因主次問題,本文不多做介紹哩照。
大家可以做一下擴展閱讀:
《2016.11.19 微服務實踐之路(廈門) 演講稿》, 次凌均閣(小馬哥微信公眾號)
另外挺物,由于小馬哥提到Thymeleaf,所以網上查閱一些相關資料飘弧。在查閱時识藤,在知乎上發(fā)現(xiàn)了一個beetl和Thymeleaf互懟的帖子,感覺很有意思次伶。雖然只接觸過framemark和vue痴昧,以上兩種都沒有用到過,但是感覺這樣的討論能夠讓自己對一些技術有一些大致的印象学少。道理未必懂得剪个,但學習都是由淺入深,是有一個過程的版确。
三、SpringApplication是什么乎折?
SpringApplication 是 Spring Boot 驅動 Spring 應用上下文的引導類绒疗。
示例:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
? ? ? ? @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
? ? ? ? @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
? ? ...
}
涉及到的注解:
@ComponentScan: 它是版本引入的? Spring Framework 3.1骂澄。
這里強調一種版本意識吓蘑,版本差異所帶來的問題有時是很嚴重的。
@EnableAutoConfiguration: 激活自動裝配
這里擴展了一下坟冲,由@Enable磨镶,提到@Enable 開頭的:
@EnableWebMvc: 是使用Java 注解快捷配置Spring Webmvc的一個注解。
在使用該注解后配置一個繼承于WebMvcConfigurerAdapter的配置類即可配置好Spring Webmvc健提。
通過查看@EnableWebMvc的源碼琳猫,可以發(fā)現(xiàn)該注解就是為了引入一個DelegatingWebMvcConfiguration Java 配置類。并翻看DelegatingWebMvcConfiguration的源碼會發(fā)現(xiàn)該類似繼承于WebMvcConfigurationSupport的類私痹。
其實不使用@EnableWebMvc注解也是可以實現(xiàn)配置Webmvc脐嫂,只需要將配置類繼承于WebMvcConfigurationSupport類即可统刮。
@EnableTransactionManagement: 開啟注解事務管理,等同于xml配置文件中的
@EnableAspectJAutoProxy: 表示開啟AOP代理自動配置账千。
如果配@EnableAspectJAutoProxy表示使用cglib進行代理對象的生成侥蒙;設置@EnableAspectJAutoProxy(exposeProxy=true)表示通過aop框架暴露該代理對象,aopContext能夠訪問匀奏。
從@EnableAspectJAutoProxy的定義可以看得出鞭衩,它引入AspectJAutoProxyRegister.class對象,該對象是基于注解@EnableAspectJAutoProxy注冊一個AnnotationAwareAspectJAutoProxyCreator娃善,該對象通過調用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);注冊一個aop代理對象生成器醋旦。
@EnableAsync: 表示開啟異步調用支持,配合@Async使用会放。
@SpringBootConfiguration: 等價于 @Configuration -> Configuration Class 注解
此處提個小疑問:
以下關系饲齐,大家應該都了解。
@SpringBootApplication= (默認屬性)@Configuration+@EnableAutoConfiguration+@ComponentScan
但是咧最,我在實際項目使用中捂人,使用@SpringBootApplication注解后,無法掃描到包矢沿,還需要使用@ComponentScan注解滥搭,是因為沒有在SpringApplication后指定掃描路徑?啟動類在外層捣鲸,就算是掃描自身目錄和子包也應該可以找到瑟匆,不知道大家有沒有遇到過類似的問題。
四栽惶、Spring?注解編程模型@Component
@Component
示例:
@Service
@Component
public @interface Service {
? ? ...
}
@Repository
@Component
public @interface Repository {
? ? ...
}
@Controller
@Component
public @interface Controller {
? ? ...
}
@Configuration
@Component
public @interface Configuration {
? ? ...
}
因為注解是沒有繼承關系的愁溜,所以小馬哥講之稱為@Component的“派生性”。
五外厂、Spring?模式注解(Stereotype?Annotations)
Spring 注解驅動示例
注解驅動上下文AnnotationConfigApplicationContext冕象,Spring Framework 3.0開始引入的
示例:
@Configuration
public class SpringAnnotationDemo {
? ? public static void main(String[] args) {
? ? ? ? //? XML 配置文件驅動? ? ? ClassPathXmlApplicationContext
? ? ? ? // Annotation 驅動
? ? ? ? // 找 BeanDefinition
? ? ? ? // @Bean @Configuration
? ? ? ? AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
? ? ? ? // 注冊一個 Configuration Class = SpringAnnotationDemo
? ? ? ? context.register(SpringAnnotationDemo.class);
? ? ? ? // 上下文啟動
? ? ? ? context.refresh();
? ? ? ? System.out.println(context.getBean(SpringAnnotationDemo.class));
? ? }
}
這里講解了兩種Spring注解驅動方式,XML配置文件驅動和Annotation注解驅動汁蝶,現(xiàn)在用的比較多的是Annotation驅動渐扮,經常聽到XML配置方式被群里的大佬們詬病。感覺小項目使用XML配置還可以掖棉,如果項目規(guī)模較大墓律,一堆配置文件就會很頭疼了。
@SpringBootApplication 標注當前一些功能
下列列出的好像是層級關系幔亥,這里記的有些不清楚耻讽。
@SpringBootApplication
@SpringBootConfiguration
@Configuration
@Component
下面看一下 SpringApplication 也就是 Spring Boot 應用的引導方法。
基本寫法
SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);
? ? ? ? Map properties = new LinkedHashMap<>();
? ? ? ? properties.put("server.port",0);
? ? ? ? springApplication.setDefaultProperties(properties);
? ? ? ? springApplication.run(args);
SpringApplicationBuilder
new SpringApplicationBuilder(MicroservicesProjectApplication.class) // Fluent API
? ? ? ? ? ? ? ? // 單元測試是 PORT = RANDOM
? ? ? ? ? ? ? ? .properties("server.port=0")? // 隨機向 OS 要可用端口
? ? ? ? ? ? ? ? .run(args);
小馬哥提到了兩種書寫風格紫谷,其中一種叫做Fluent API風格齐饮,另外一種沒有聽清捐寥。有沒有大佬知道上面寫法的名稱?
六祖驱、Spring?Boot?引導過程
示例:
@SpringBootApplication
public class MicroservicesProjectApplication {
? ? public static void main(String[] args) {
? ? ? ? SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);
? ? ? ? Map properties = new LinkedHashMap<>();
? ? ? ? properties.put("server.port",0);
? ? ? ? springApplication.setDefaultProperties(properties);
? ? ? ? ConfigurableApplicationContext context = springApplication.run(args);
? ? ? ? // 有異常握恳?
? ? ? ? System.out.println(context.getBean(MicroservicesProjectApplication.class));
? ? }
}
這個示例運行結果是正常的,springApplication.run(args)和context.getBean(MicroservicesProjectApplication.class)并不會有沖突異常捺僻。
如果沒有記錯乡洼,通過控制臺可以看出,運行了兩次匕坯。實際結果束昵,有條件后運行一下代碼再下定論。
調整示例為 非 Web 程序
示例:
@SpringBootApplication
public class MicroservicesProjectApplication {
? ? public static void main(String[] args) {
? ? ? ? SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);
? ? ? ? Map properties = new LinkedHashMap<>();
? ? ? ? properties.put("server.port", 0);
? ? ? ? springApplication.setDefaultProperties(properties);
? ? ? ? // 設置為 非 web 應用
? ? ? ? springApplication.setWebApplicationType(WebApplicationType.NONE);
? ? ? ? ConfigurableApplicationContext context = springApplication.run(args);
? ? ? ? // 有異常葛峻?
? ? ? ? System.out.println(context.getBean(MicroservicesProjectApplication.class));
? ? ? ? // 輸出當前 Spring Boot 應用的 ApplicationContext 的類名
? ? ? ? System.out.println("當前 Spring 應用上下文的類:" + context.getClass().getName());
? ? }
}
輸出結果:
當前 Spring 應用上下文的類:org.springframework.context.annotation.AnnotationConfigApplicationContext
配置 Spring Boot 源
SpringAppliation 類型推斷
當不加以設置 Web 類型锹雏,那么它采用推斷:
->SpringAppliation()->deduceWebApplicationType() 第一次推斷為WebApplicationType.SERVLET
WebApplicationType.NONE: 非 Web 類型
Servlet 不存在
Spring Web 應用上下文ConfigurableWebApplicationContext不存在
spring-boot-starter-web不存在
spring-boot-starter-webflux不存在
WebApplicationType.REACTIVE: Spring WebFlux
DispatcherHandler
spring-boot-starter-webflux存在
Servlet 不存在
spring-boot-starter-web不存在
WebApplicationType.SERVLET: Spring MVC
spring-boot-starter-web存在
人工干預 Web 類型
設置 webApplicationType 屬性 為WebApplicationType.NONE
七、SpringBoot事件
Spring 事件
(1)Spring 內部發(fā)送事件
事件發(fā)布:
ContextRefreshedEvent
ApplicationContextEvent
ApplicationEvent
refresh() ->finishRefresh()->publishEvent(new ContextRefreshedEvent(this));
事件關閉:
ContextClosedEvent
ApplicationContextEvent
ApplicationEvent
close()->doClose()->publishEvent(new ContextClosedEvent(this));
(2)自定義事件
PayloadApplicationEvent
Spring 事件 都是ApplicationEvent類型
發(fā)送 Spring 事件通過ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)
Spring 事件的類型ApplicationEvent
Spring 事件監(jiān)聽器ApplicationListener
Spring 事件廣播器ApplicationEventMulticaster
實現(xiàn)類:SimpleApplicationEventMulticaster
Spring 事件理解為消息
ApplicationEvent相當于消息內容
ApplicationListener相當于消息消費者术奖、訂閱者
ApplicationEventMulticaster相當于消息生產者礁遵、發(fā)布者
注意:不能只發(fā)布事件,沒有監(jiān)聽采记。否則佣耐,啟動程序是獲取不到任何事件的。
這里提到了“監(jiān)聽者模式”唧龄,大家應該都會聯(lián)想到“觀察者模式”兼砖,兩者很相似,所以我去查閱了一些參考資料:
設計模式之監(jiān)聽模式(觀察者模式與監(jiān)聽模式區(qū)別)
個人理解:
監(jiān)聽者模式包含事件源既棺、事件和監(jiān)聽器讽挟,像是半自動化的監(jiān)聽,需要手動或者說是人工對事件源和事件做處理援制,然后提供給監(jiān)聽器戏挡;觀察者模式包含觀察者和被觀察者(也就是事件),像是自動化監(jiān)聽晨仑,只需要對事件負責即可。
監(jiān)聽者模式更加靈活拆檬,比較適用于需要自我把控的場景洪己,比如底層框架的實現(xiàn):spring框架的ApplicationEvent,ApplicationListener。
觀察者模式更加輕便竟贯,比較適用于約束比較固定的場景答捕,比如前臺按鈕的點擊事件,至于屑那,后臺用到觀察者模式的場景暫時沒有想到拱镐。
Spring Boot 事件監(jiān)聽示例
示例:
@EnableAutoConfiguration
public class SpringBootEventDemo {
? ? public static void main(String[] args) {
? ? ? ? new SpringApplicationBuilder(SpringBootEventDemo.class)
? ? ? ? ? ? ? ? .listeners(event -> { // 增加監(jiān)聽器
? ? ? ? ? ? ? ? ? ? System.err.println("監(jiān)聽到事件 : " + event.getClass().getSimpleName());
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? .run(args)
? ? ? ? ? ? ? ? .close();
? ? ? ? ; // 運行
? ? }
}
通過運行程序可知艘款,事件的運行順序(括號中是官方文檔中標注的順序,好像官方文檔中只提到了Application開頭的事件沃琅,沒有啟動過程中看到的Context和Servlet事件):
ApplicationStartingEvent(1) 啟動
ApplicationEnvironmentPreparedEvent(2) 環(huán)境配置
ApplicationPreparedEvent(3) 這里應該是
ContextRefreshedEvent 創(chuàng)建上下文context
ServletWebServerInitializedEvent
ApplicationStartedEvent(4)
ApplicationReadyEvent(5)
ContextClosedEvent
ApplicationFailedEvent (特殊情況)(6)
最后一種是啟動失敗的情況哗咆,所以做了特殊標注。
此外益眉,這里提到了lambda表達式:
event -> { // 增加監(jiān)聽器
? ? ? ? ? ? ? ? ? ? System.err.println("監(jiān)聽到事件 : " + event.getClass().getSimpleName());
? ? ? ? ? ? ? ? }
之前沒有接觸過晌柬,在此貼上兩篇文章:
Spring Boot 事件監(jiān)聽器
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
ConfigFileApplicationListener監(jiān)聽ApplicationEnvironmentPreparedEvent事件從而加載 application.properties 或者 application.yml 文件。
Spring Boot 很多組件依賴于 Spring Boot 事件監(jiān)聽器實現(xiàn)郭脂,本質是 Spring Framework 事件/監(jiān)聽機制年碘。
SpringApplication利用:
Spring 應用上下文(ApplicationContext)生命周期控制 注解驅動 Bean
Spring 事件/監(jiān)聽(ApplicationEventMulticaster)機制加載或者初始化組件
擴展問題:
q1:webApplicationType分為三種,都有什么實用地方
個人鄙見:
類型為SERVLET時展鸡,就是常用的web服務屿衅,受眼界所限,我了解的大部分項目都可以使用這種類型莹弊。是不是因為常用涤久,所以才會將SERVLET設置為默認類型?
類型為NONE時箱硕,意味著該應用啟動后拴竹,不會加載任何東西。是不是在啟動時需要做情況判斷的時候可以用到剧罩?
類型為REACTIVE時栓拜,支持響應式編程,響應式編程是基于異步和事件驅動的非阻塞程序惠昔。
響應式編程技術上有RxJava等幕与,可以提高代碼的抽象程度,讓開發(fā)者能夠更加專注于業(yè)務邏輯镇防。我覺得響應式編程可以將業(yè)務拆解成細粒度的事件啦鸣,一方面代碼維護性好(專注于業(yè)務邏輯,忽略具體實現(xiàn))来氧,另一方面能夠更好地應對業(yè)務邏輯的更改(有可能只需要改變部分環(huán)節(jié)的事件邏輯)诫给。
參考資料:
q2:框架底層的事件是單線程么中狂?業(yè)務實現(xiàn)是否可以使用事件去實現(xiàn)?如果使用事件實現(xiàn)是不是會有性能問題扑毡?
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
? ? @Nullable
? ? private Executor taskExecutor;
? ? ...
}
個人鄙見:
從SpringBoot事件運行示例來看胃榕,是單線程、順序執(zhí)行瞄摊。至于使用事件來實現(xiàn)業(yè)務勋又,這方面確實不甚清楚苦掘。
總結
本人野生碼農一枚,今日新手上路楔壤,不敢太過造次鹤啡,也不想因自己不知所謂的看法誤導大家。文中的問題挺邀,待我證實以后揉忘,再做修改。
謹此端铛,止筆泣矛。