微服務專題01-Spring Application

前言

前面的章節我們講了源碼分析專題分佈式專題工程專題。從本節開始,進入微服務專題的講解,共計16小節,分別是:

  • 微服務專題01-Spring Application
  • 微服務專題02-Spring Web MVC 視圖技術
  • 微服務專題03-REST
  • 微服務專題04-Spring WebFlux 原理
  • 微服務專題05-Spring WebFlux 運用
  • 微服務專題06-雲原生應用(Cloud Native Applications)
  • 微服務專題07-Spring Cloud 配置管理
  • 微服務專題08-Spring Cloud 服務發現
  • 微服務專題09-Spring Cloud 負載均衡
  • 微服務專題10-Spring Cloud 服務熔斷
  • 微服務專題11-Spring Cloud 服務調用
  • 微服務專題12-Spring Cloud Gateway
  • 微服務專題13-Spring Cloud Stream (上)
  • 微服務專題14-Spring Cloud Bus
  • 微服務專題15-Spring Cloud Stream 實現
  • 微服務專題16-Spring Cloud 整體回顧

本節內容重點爲:

  • 自定義 SpringApplication:介紹 SpringApplicationSpringApplicationBuilder 的API調整
  • 配置 Spring Boot 源:理解 Spring Boot 配置源
  • SpringAppliation 類型推斷:Web 應用類型、Main Class 推斷
  • Spring Boot 事件:介紹 Spring Boot 事件與 Spring Framework 事件之間的差異和聯繫

自定義 SpringApplication

如圖所示,在https://start.spring.io/官網上快速搭建一個SpringBoot項目,通常把它稱爲SpringBoot的腳手架~
在這裏插入圖片描述
最簡單的SpringBoot項目也會包括SpringApplication啓動類,所以通常我們將SpringApplication作爲一切的起點,首先看看這個類是如何定義的

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
  • @EnableTransactionManagement
  • @EnableAspectJAutoProxy
  • @EnableAsync

@SpringBootConfiguration : 等價於 @Configuration -> Configuration Class 註解

@Component 的“派生性”

所謂派生性就是通俗意義上講的長得像~,你看@Component -> @ComponentScan

這裏將component稱之爲元註解,因爲componentScan是它派生出來的

關於對此註解的處理,以及掃描實現過程,可以參考以下內容:

Component 的處理類 -> ConfigurationClassParser

Component 的掃描類 ->

  • ClassPathBeanDefinitionScanner

    • ClassPathScanningCandidateComponentProvider

      	protected void registerDefaultFilters() {
      		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
          	...
      	}
      

    同理於 Dubbo @Service -> 2.5.7 ->

   new AnnotationTypeFilter(Service.class);

Spring 註解編程模型

https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model

這裏再看一下在SpringBoot常見的註解,諸如@Service@Repository@Controller@Configuration
在spring源碼裏,其實都是引入了@component,作爲這些註解的元註解。

  • @Component

    • @Service

      @Component
      public @interface Service {
          ...
      }
      
    • @Repository

      @Component
      public @interface Repository {
          ...
      }
      
    • @Controller

      @Component
      public @interface Controller {
          ...
      }
      
    • @Configuration

      @Component
      public @interface Configuration {
      	...
      }
      

Spring 模式註解:Stereotype Annotations

所謂的模式註解:@component邏輯上與@Service@Repository@Controller@Configuration都是一樣,只是物理層面上不同,但都是爲了找到BeanDefinition。

Spring 註解驅動示例

區別於ClassPathXmlApplicationContext以xml配置文件驅動,註解驅動的上下文是 AnnotationConfigApplicationContext ,這是在 Spring Framework 3.0 開始引入的

在springcloud的微服務架構中,會多次提到版本問題,因爲不同版本之間差異性可能很大,比如springcloud的1.0與2.0差異就很明顯,所以平時應注意版本問題!

@Configuration
public class SpringAnnotationDemo {

    public static void main(String[] args) {
        // @Bean @Configuration
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 註冊一個 Configuration Class = SpringAnnotationDemo
        context.register(SpringAnnotationDemo.class);
        // 上下文啓動
        context.refresh();
        System.out.println(context.getBean(SpringAnnotationDemo.class));
    }
}

無論是xml配置還是註解配置,其目的只有一個,就是找 BeanDefinition

現在我們來分析一下,@SpringBootApplication 這個註解,下面是通過源碼顯示的調用關係

  • @SpringBootApplication
    在這裏插入圖片描述
    • @SpringBootConfiguration
      在這裏插入圖片描述
      • @Configuration
        在這裏插入圖片描述
        • @Component

@SpringBootApplication 標註當前一些功能。我們知道,註解不能像Java類一樣繼承,就通過以上的這樣的方式層層調用,而註解的功能基本相似,我們就把註解的這種特性稱之爲派生性

SpringApplication Spring Boot 應用的引導

基本寫法

SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);
        Map<String,Object> 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);

Spring Boot 引導示例

@SpringBootApplication
public class MicroservicesProjectApplication {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);
        Map<String,Object> properties = new LinkedHashMap<>();
        properties.put("server.port",0);
        springApplication.setDefaultProperties(properties);
        ConfigurableApplicationContext context = springApplication.run(args);
        // 有異常?實際上沒有
        System.out.println(context.getBean(MicroservicesProjectApplication.class));
    }
}

調整示例爲 非 Web 程序

@SpringBootApplication
public class MicroservicesProjectApplication {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MicroservicesProjectApplication.class);
        Map<String, Object> 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
在這裏插入圖片描述

deduceWebApplicationType方法的邏輯爲:

  • 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 存在
      注意deduceWebApplicationType方法這裏的幾個private final String的常量,分別是:
      在這裏插入圖片描述

人工干預 Web 類型

設置 webApplicationType 屬性 爲 WebApplicationType.NONE
在這裏插入圖片描述

Spring Boot 事件

Spring 事件demo:

 public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // 添加事件監聽器
//        context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
//            @Override
//            public void onApplicationEvent(ApplicationEvent event) {
//                System.err.println("監聽事件:" + event);
//
//            }
//        });

        // 添加自義定監聽器
        context.addApplicationListener(new ClosedListener());
        context.addApplicationListener(new RefreshedListener());
        // 啓動 Spring 應用上下文
        context.refresh();

        // 一個是 ContextRefreshedEvent
        // 一個是 PayloadApplicationEvent
        // Spring 應用上下文發佈事件
        context.publishEvent("HelloWorld"); // 發佈一個 HelloWorld 內容的事件
        // 一個是 MyEvent
        context.publishEvent(new MyEvent("HelloWorld 2020"));

        // 一個是 ContextClosedEvent
        // 關閉應用上下文
        context.close();
    }

    private static class RefreshedListener implements ApplicationListener<ContextRefreshedEvent> {

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            System.out.println("上下文啓動:" + event);
        }
    }

    private static class ClosedListener implements ApplicationListener<ContextClosedEvent> {

        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            System.out.println("關閉上下文:" + event);
        }
    }

    private static class MyEvent extends ApplicationEvent {

        public MyEvent(Object source) {
            super(source);
        }
    }

Spring 內部發送事件
開啓上下文涉及的類的層級關係

  • ContextRefreshedEvent
    • ApplicationContextEvent
      • ApplicationEvent

開啓上下文調用鏈
refresh() -> finishRefresh() -> publishEvent(new ContextRefreshedEvent(this));

關閉上下文涉及的類的層級關係

  • ContextClosedEvent
    • ApplicationContextEvent
      • ApplicationEvent

關閉上下文調用鏈
close() -> doClose() -> publishEvent(new ContextClosedEvent(this));

自定義事件
PayloadApplicationEvent

*Spring 事件都是ApplicationEvent類型,Spring事件理解爲消息

發送 Spring 事件通過 ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)

Spring 事件的類型 ApplicationEvent
Spring 事件監聽器 ApplicationListener
Spring 事件廣播器 ApplicationEventMulticaster

實現類:SimpleApplicationEventMulticaster

ApplicationEvent 相當於消息內容
ApplicationListener 相當於消息消費者、訂閱者
ApplicationEventMulticaster 相當於消息生產者、發佈者

Spring Boot 事件監聽示例

demo1:

 public static void main(String[] args) {
        ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();

        // 添加監聽器
        multicaster.addApplicationListener(event -> {
            if (event instanceof PayloadApplicationEvent) {
                System.out.println("接受到 PayloadApplicationEvent :"
                        + PayloadApplicationEvent.class.cast(event).getPayload());
            }else {
                System.out.println("接收到事件:" + event);
            }
        });
        // 發佈/廣播事件
        multicaster.multicastEvent(new MyEvent("Hello,World"));
        multicaster.multicastEvent(new PayloadApplicationEvent<Object>("2", "Hello,World"));

    }

    private static class MyEvent extends ApplicationEvent {

        public MyEvent(Object source) {
            super(source);
        }
    }

運行結果:
在這裏插入圖片描述

demo2:

@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();
        ; // 運行
    }
}

運行結果:
按照時間順序打印的順序如下:
在這裏插入圖片描述
在這裏插入圖片描述

  1. ApplicationStartingEvent(1)
  2. ApplicationEnvironmentPreparedEvent(2)
  3. ApplicationPreparedEvent(3)
  4. ContextRefreshedEvent
  5. ServletWebServerInitializedEvent
  6. ApplicationStartedEvent(4)
  7. ApplicationReadyEvent(5)
  8. ContextClosedEvent
  9. ApplicationFailedEvent (特殊情況)(6)

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分爲三種都有什麼實用地方?

q2:框架底層的事件是單線程麼?業務實現是否可以使用事件去實現?如果使用事件實現會不會是不是會有性能問題?

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

	@Nullable
	private Executor taskExecutor;
    ...
}

更多架構知識,歡迎關注本套Java系列文章Java架構師成長之路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章