第一節 SpringApplication
本文是在預習和學習后,想要盡自己所能將學到的知識以自己的理解記錄下來。當然,有些地方還是一知半解,所以是有很多內容是小馬哥所講所書,權當引經據典,還望小馬哥見到不要怪罪。
雖然今年項目上已經接觸過SpringBoot,目前仍在使用,但了解到的只是常用的用法。這篇文章主要講一下微服務的概念,以SpringApplication啟動類為切入點,講解Spring注解機制,SpringApplication啟動類的寫法和程序類型推斷,以及SpringBoot事件和事件監聽。
一、微服務是什么?能做什么?
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 技術演進的目的在于解決分布式計算中,統一異構系統的服務調用的通訊協議。前期的Web Services有XML-PRC、WSDL、SOAP等技術,不但解決了Windows平臺COM+以及Java 平臺RMI無法跨平臺的問題,而且使用了可讀性強的本文協議替代了復雜的二進制協議,如CORBA技術?,F代的WebServices 技術主要代表有REST等。
此外,還提到微服務并非等同于單體應用,我所理解的意思是單體應用只有一個核心,是中心化的,任務是應用整體來完成的,有較高的耦合度。而微服務是去中心化的,多核的,將任務拆解,由各個模塊分工協作完成的,耦合度是較前者要低的。
2、微服務的優勢和適用場景
優勢:
(1)效率:應用微服務化后,變得更加輕量級,在編譯、打包、分發、部署等環節節約時間,提升效率;
(2)質量:面向持續集成友好,自動化編譯、單元和集成測試用例執行和回歸,提高整體質量。
這里講的所謂“持續集成”,個人不是很清楚這個概念。
(3)穩定:當應用大而全時,一個服務的問題可能會導致整體受到影響。而微服務在服務A調用服務B出現問題時,可以選擇降級或熔斷的方式進行處理,等待服務B正常運行后,恢復正常。
(4)運維:微服務應用具備自動化編譯、打包、分發、部署和運維的能力。
(5)成長:對新技術的適配能力,例如:Apache Kafka。在重要等級較低的微服務應用上,可以更好的做新技術的嘗試。
3、不適用微服務的應用場景
(1)場景單一:如果應用本身規模就不大,就沒必要使用微服務,因為本身就是一種微服務。舉例如:一些靜態通告頁面。
(2)邏輯簡單:微服務是為了解決業務邏輯復雜性問題,因此此場景無需微服務。
(3)業務漸逝:如果業務將要消逝或停用,就不需要實施了。
(4)“老成持重”:老--服役三年以上的應用;成--應用規模已經成型;持--應用場景還需要長時間維持;重--應用屬于系統中較重要的模塊。
二、微服務用到的技術
技術上,在阿里微服務的實踐過程中也不能免俗,基本上也是以下三個套路:
Docker
DDD
Middleware(Java)
此處雖然有所講解,但因主次問題,本文不多做介紹。
大家可以做一下擴展閱讀:
《2016.11.19 微服務實踐之路(廈門) 演講稿》, 次凌均閣(小馬哥微信公眾號)
另外,由于小馬哥提到Thymeleaf,所以網上查閱一些相關資料。在查閱時,在知乎上發現了一個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的源碼,可以發現該注解就是為了引入一個DelegatingWebMvcConfiguration Java 配置類。并翻看DelegatingWebMvcConfiguration的源碼會發現該類似繼承于WebMvcConfigurationSupport的類。
其實不使用@EnableWebMvc注解也是可以實現配置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注解驅動,現在用的比較多的是Annotation驅動,經常聽到XML配置方式被群里的大佬們詬病。感覺小項目使用XML配置還可以,如果項目規模較大,一堆配置文件就會很頭疼了。
@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 內部發送事件
事件發布:
ContextRefreshedEvent
ApplicationContextEvent
ApplicationEvent
refresh() ->finishRefresh()->publishEvent(new ContextRefreshedEvent(this));
事件關閉:
ContextClosedEvent
ApplicationContextEvent
ApplicationEvent
close()->doClose()->publishEvent(new ContextClosedEvent(this));
(2)自定義事件
PayloadApplicationEvent
Spring 事件 都是ApplicationEvent類型
發送 Spring 事件通過ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)
Spring 事件的類型ApplicationEvent
Spring 事件監聽器ApplicationListener
Spring 事件廣播器ApplicationEventMulticaster
實現類:SimpleApplicationEventMulticaster
Spring 事件理解為消息
ApplicationEvent相當于消息內容
ApplicationListener相當于消息消費者、訂閱者
ApplicationEventMulticaster相當于消息生產者、發布者
注意:不能只發布事件,沒有監聽。否則,啟動程序是獲取不到任何事件的。
這里提到了“監聽者模式”,大家應該都會聯想到“觀察者模式”,兩者很相似,所以我去查閱了一些參考資料:
個人理解:
監聽者模式包含事件源、事件和監聽器,像是半自動化的監聽,需要手動或者說是人工對事件源和事件做處理,然后提供給監聽器;觀察者模式包含觀察者和被觀察者(也就是事件),像是自動化監聽,只需要對事件負責即可。
監聽者模式更加靈活,比較適用于需要自我把控的場景,比如底層框架的實現:spring框架的ApplicationEvent,ApplicationListener。
觀察者模式更加輕便,比較適用于約束比較固定的場景,比如前臺按鈕的點擊事件,至于,后臺用到觀察者模式的場景暫時沒有想到。
Spring Boot 事件監聽示例
示例:
@EnableAutoConfiguration
public class SpringBootEventDemo {
? ? public static void main(String[] args) {
? ? ? ? new SpringApplicationBuilder(SpringBootEventDemo.class)
? ? ? ? ? ? ? ? .listeners(event -> { // 增加監聽器
? ? ? ? ? ? ? ? ? ? System.err.println("監聽到事件 : " + event.getClass().getSimpleName());
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? .run(args)
? ? ? ? ? ? ? ? .close();
? ? ? ? ; // 運行
? ? }
}
通過運行程序可知,事件的運行順序(括號中是官方文檔中標注的順序,好像官方文檔中只提到了Application開頭的事件,沒有啟動過程中看到的Context和Servlet事件):
ApplicationStartingEvent(1) 啟動
ApplicationEnvironmentPreparedEvent(2) 環境配置
ApplicationPreparedEvent(3) 這里應該是
ContextRefreshedEvent 創建上下文context
ServletWebServerInitializedEvent
ApplicationStartedEvent(4)
ApplicationReadyEvent(5)
ContextClosedEvent
ApplicationFailedEvent (特殊情況)(6)
最后一種是啟動失敗的情況,所以做了特殊標注。
此外,這里提到了lambda表達式:
event -> { // 增加監聽器
? ? ? ? ? ? ? ? ? ? System.err.println("監聽到事件 : " + event.getClass().getSimpleName());
? ? ? ? ? ? ? ? }
之前沒有接觸過,在此貼上兩篇文章:
Spring Boot 事件監聽器
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監聽ApplicationEnvironmentPreparedEvent事件從而加載 application.properties 或者 application.yml 文件。
Spring Boot 很多組件依賴于 Spring Boot 事件監聽器實現,本質是 Spring Framework 事件/監聽機制。
SpringApplication利用:
Spring 應用上下文(ApplicationContext)生命周期控制 注解驅動 Bean
Spring 事件/監聽(ApplicationEventMulticaster)機制加載或者初始化組件
擴展問題:
q1:webApplicationType分為三種,都有什么實用地方
個人鄙見:
類型為SERVLET時,就是常用的web服務,受眼界所限,我了解的大部分項目都可以使用這種類型。是不是因為常用,所以才會將SERVLET設置為默認類型?
類型為NONE時,意味著該應用啟動后,不會加載任何東西。是不是在啟動時需要做情況判斷的時候可以用到?
類型為REACTIVE時,支持響應式編程,響應式編程是基于異步和事件驅動的非阻塞程序。
響應式編程技術上有RxJava等,可以提高代碼的抽象程度,讓開發者能夠更加專注于業務邏輯。我覺得響應式編程可以將業務拆解成細粒度的事件,一方面代碼維護性好(專注于業務邏輯,忽略具體實現),另一方面能夠更好地應對業務邏輯的更改(有可能只需要改變部分環節的事件邏輯)。
參考資料:
q2:框架底層的事件是單線程么?業務實現是否可以使用事件去實現?如果使用事件實現是不是會有性能問題?
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
? ? @Nullable
? ? private Executor taskExecutor;
? ? ...
}
個人鄙見:
從SpringBoot事件運行示例來看,是單線程、順序執行。至于使用事件來實現業務,這方面確實不甚清楚。
總結
本人野生碼農一枚,今日新手上路,不敢太過造次,也不想因自己不知所謂的看法誤導大家。文中的問題,待我證實以后,再做修改。
謹此,止筆。