Spring Cloud系列之Commons - 1. 背景與基礎知識準備

本文基於 Spring Cloud 2020.0 發佈版的依賴html

本系列會深刻分析 Spring Cloud 的每個組件,從Spring Cloud Commons這個 Spring Cloud 全部元素的抽象提及,深刻設計思路與源碼,並結合實際使用例子深刻理解。本系列適合有必定 Spring 或者 Spring Boot 使用經驗的人閱讀。java

什麼是Spring Cloud Commons

Spring Cloud框架包括以下功能:git

  • 分佈式多版本配置管理
  • 服務註冊與發現
  • 路由
  • 微服務調用
  • 負載均衡
  • 斷路器
  • 分佈式消息

Spring Cloud Commons包含實現這一切要加載的基礎組件的接口,以及Spring Cloud啓動如何加載,加載哪些東西。其中:github

  • spring cloud context:包括Spring Cloud應用須要加載的ApplicationContext的內容
  • spring cloud common: 包括以下幾個基本組件以及其加載配置:
    • 服務註冊接口:org.springframework.cloud.serviceregistry
    • 服務發現接口:org.springframework.cloud.discovery
    • 負載均衡接口:org.springframework.cloud.loadbalancer
    • 斷路器接口: org.springframework.cloud.circuitbreaker
  • spring cloud loadbalancer:相似於ribbon,而且是ribbon的替代品。實現了上述負載均衡接口的組件

這個系列咱們要講述的是 spring cloud common 這個模塊,spring cloud loadbalancer 還有 spring cloud context 將會在另外一個單獨的系列。web

Spring 與 Spring Boot 背景知識補充

咱們在看一個 Spring Cloud 模塊源代碼時,須要記住任何一個 Spring Cloud 模塊都是基於 Spring Boot 擴展而來的,這個擴展通常是經過 spring.factories SPI 機制。任何一個 Spring Cloud 模塊源代碼均可以以這個爲切入點進行理解spring

spring.factories SPI 機制

spring-core 項目中提供了 Spring 框架多種 SPI 機制,其中一種很是經常使用並靈活運用在了 Spring-boot 的機制就是基於 spring.factories 的 SPI 機制。編程

那麼什麼是 SPI(Service Provider)呢? 在系統設計中,爲了模塊間的協做,每每會設計統一的接口供模塊之間的調用。面向的對象的設計裏,咱們通常推薦模塊之間基於接口編程,模塊之間不對實現類進行硬編碼,而是將指定哪一個實現置於程序以外指定。Java 中默認的 SPI 機制就是經過 ServiceLoader 來實現,簡單來講就是經過在META-INF/services目錄下新建一個名稱爲接口全限定名的文件,內容爲接口實現類的全限定名,以後程序經過代碼:數組

//指定加載的接口類,以及用來加載類的類加載器,若是類加載器爲 null 則用根類加載器加載
ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class, someClassLoader);
Iterator<SpiService> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
    SpiService spiService = iterator.next();
}

獲取指定的實現類。app

在 Spring 框架中,這個類是SpringFactoriesLoader,須要在META-INF/spring.factories文件中指定接口以及對應的實現類,例如 Spring Cloud Commons 中的:負載均衡

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor

其中指定了EnvironmentPostProcessor的實現HostInfoEnvironmentPostProcessor

同時,Spring Boot 中會經過SpringFactoriesLoader.loadXXX相似的方法讀取全部的EnvironmentPostProcessor的實現類並生成 Bean 到 ApplicationContext 中:

EnvironmentPostProcessorApplicationListener

//這個類也是經過spring.factories中指定ApplicationListener的實現而實現加載的,這裏省略
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
    //建立這個Bean的時候,會調用
    public EnvironmentPostProcessorApplicationListener() {
        this(EnvironmentPostProcessorsFactory
                .fromSpringFactories(EnvironmentPostProcessorApplicationListener.class.getClassLoader()));
    }
}

EnvironmentPostProcessorsFactory

static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {
    return new ReflectionEnvironmentPostProcessorsFactory(
            //經過 SpringFactoriesLoader.loadFactoryNames 獲取文件中指定的實現類並初始化
            SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
}

spring.factories 的特殊使用 - EnableAutoConfiguration

META-INF/spring.factories 文件中不必定指定的是接口以及對應的實現類,例如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\

其中EnableAutoConfiguration是一個註解,LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration都是配置類並非EnableAutoConfiguration的實現。那麼這個是什麼意思呢?EnableAutoConfiguration是一個註解,LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration都是配置類。spring.factories這裏是另外一種特殊使用,記錄要載入的 Bean 類。EnableAutoConfiguration在註解被使用的時候,這些 Bean 會被加載。這就是spring.factories的另一種用法。

EnableAutoConfiguration是 Spring-boot 自動裝載的核心註解。有了這個註解,Spring-boot 就能夠自動加載各類@Configuration註解的類。那麼這個機制是如何實現的呢?

來看下EnableAutoConfiguration的源碼
EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    //排除的類
    Class<?>[] exclude() default {};
    //排除的Bean名稱
    String[] excludeName() default {};
}

咱們看到了有 @Import 這個註解。這個註解是 Spring 框架的一個很經常使用的註解,是 Spring 基於 Java 註解配置的主要組成部分。

@Import註解的做用

@Import註解提供了@Bean註解的功能,同時還有原來Spring基於 xml 配置文件裏的&lt;import&gt;標籤組織多個分散的xml文件的功能,固然在這裏是組織多個分散的@Configuration的類。這個註解的功能與用法包括

1. 引入其餘的@Configuration

假設有以下接口和兩個實現類:

package com.test
interface ServiceInterface {
    void test();
}

class ServiceA implements ServiceInterface {

    @Override
    public void test() {
        System.out.println("ServiceA");
    }
}

class ServiceB implements ServiceInterface {

    @Override
    public void test() {
        System.out.println("ServiceB");
    }
}

兩個@Configuration,其中ConfigA``@Import``ConfigB:

package com.test
@Import(ConfigB.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

@Configuration
class ConfigB {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceB() {
        return new ServiceB();
    }
}

經過ConfigA建立AnnotationConfigApplicationContext,獲取ServiceInterface,看是哪一種實現:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
    ServiceInterface bean = ctx.getBean(ServiceInterface.class);
    bean.test();
}

輸出爲:ServiceB.證實@Import的優先於自己的的類定義加載。

2. 直接初始化其餘類的Bean

Spring 4.2以後,@Import能夠直接指定實體類,加載這個類定義到context中。
例如把上面代碼中的ConfigA@Import修改成@Import(ServiceB.class),就會生成ServiceBBean到容器上下文中,以後運行main方法,輸出爲:ServiceB.證實@Import的優先於自己的的類定義加載.

3. 指定實現ImportSelector(以及DefferredServiceImportSelector)的類,用於個性化加載

指定實現ImportSelector的類,經過AnnotationMetadata裏面的屬性,動態加載類。AnnotationMetadataImport註解所在的類屬性(若是所在類是註解類,則延伸至應用這個註解類的非註解類爲止)。

須要實現selectImports方法,返回要加載的@Configuation或者具體Bean類的全限定名的String數組。

package com.test;
class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //能夠是@Configuration註解修飾的類,也能夠是具體的Bean類的全限定名稱
        return new String[]{"com.test.ConfigB"};
    }
}

@Import(ServiceImportSelector.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

再次運行main方法,輸出:ServiceB.證實@Import的優先於自己的的類定義加載。
通常的,框架中若是基於AnnotationMetadata的參數實現動態加載類,通常會寫一個額外的Enable註解,配合使用。例如:

package com.test;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportSelector.class)
@interface EnableService {
    String name();
}

class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //這裏的importingClassMetadata針對的是使用@EnableService的非註解類
        //由於`AnnotationMetadata`是`Import`註解所在的類屬性,若是所在類是註解類,則延伸至應用這個註解類的非註解類爲止
        Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}

以後,在ConfigA中增長註解@EnableService(name = "B")

package com.test;
@EnableService(name = "B")
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

再次運行main方法,輸出:ServiceB.

還能夠實現DeferredImportSelector接口,這樣selectImports返回的類就都是最後加載的,而不是像@Import註解那樣,先加載。
例如:

package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}

修改EnableService註解:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
    String name();
}

這樣ConfigA就優先於DefferredServiceImportSelector返回的ConfigB加載,執行main方法,輸出:ServiceA

4. 指定實現ImportBeanDefinitionRegistrar的類,用於個性化加載

ImportSelector用法與用途相似,可是若是咱們想重定義Bean,例如動態注入屬性,改變Bean的類型和Scope等等,就須要經過指定實現ImportBeanDefinitionRegistrar的類實現。例如:

定義ServiceC

package com.test;
class ServiceC implements ServiceInterface {

    private final String name;

    ServiceC(String name) {
        this.name = name;
    }

    @Override
    public void test() {
        System.out.println(name);
    }
}

定義ServiceImportBeanDefinitionRegistrar動態註冊ServiceC,修改EnableService

package com.test;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportBeanDefinitionRegistrar.class)
@interface EnableService {
    String name();
}

class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
                //增長構造參數
                .addConstructorArgValue(name);
        //註冊Bean
        registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
    }
}

ImportBeanDefinitionRegistrar@Bean 註解以後加載,因此要修改ConfigA去掉其中被@ConditionalOnMissingBean註解的Bean,不然必定會生成ConfigAServiceInterface

package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
//    @Bean
//    @ConditionalOnMissingBean
//    public ServiceInterface getServiceA() {
//        return new ServiceA();
//    }
}

以後運行main,輸出:TestServiceC

Spring Boot 核心自動裝載的實現原理

上面咱們提到了@EnableAutoConfiguration註解裏面的:

@Import(AutoConfigurationImportSelector.class)

屬於@Import註解的第三種用法,也就是經過具體的ImportSelector進行裝載,實現其中的selectImports接口返回須要自動裝載的類的全限定名稱。這裏的AutoConfigurationImportSelector實現是:
AutoConfigurationImportSelector

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    //`spring.boot.enableautoconfiguration`這個屬性沒有指定爲false那就是啓用了Spring Boot自動裝載,不然就是沒啓用。沒啓用的話,返回空數組
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    //獲取要加載的類,詳情見下面源代碼
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

//獲取要加載的類
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    //`spring.boot.enableautoconfiguration`這個屬性沒有指定爲false那就是啓用了Spring Boot自動裝載,不然就是沒啓用。沒啓用的話,返回空數組
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    //獲取註解
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //從spring.factories讀取全部key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的類
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //去重
    configurations = removeDuplicates(configurations);
    //根據EnableAutoConfiguration註解的屬性去掉要排除的類
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    //發佈AutoConfigurationImportEvent事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

Spring Boot中的 ApplicationContext 的層級是什麼

ApplicationContext 是 spring 用來容納管理 beans 以及其生命週期的容器。ApplicationContext 的分層規定了bean的界限以及能夠複用的 bean。關於 ApplicationContext 層級能夠參考官方文檔,這裏咱們經過一個簡單的例子來講明下 ApplicationContext 層級以及其中的bean界限,例如某些 bean 能夠被多個 ApplicationContext 共享,同時某些 bean 只在某個 ApplicationContext 生效,不一樣 ApplicationContext 能夠聲明同名或者同類型的bean這樣。咱們將實現一個下圖所示的 ApplicationContext 結構:

image

咱們會實現,一個 parent context 與三個對應 child context 的結構。

首先定義Parent context:

Bean類:

package com.test.spring.context.bean;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class RootBean {
    private Stirng name;
}

Context類:

import com.hopegaming.scaffold.spring.context.bean.RootBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@PropertySource(value = "classpath:/root.yaml", factory = YamlPropertyLoaderFactory.class)
public class RootContext {
    @Bean
    public RootBean getFatherBean() {
        RootBean rootBean = new RootBean();
        rootBean.setName("root");
        return rootBean;
    }
}

root.yml:

# 配置這些主要是將actuator相關接口暴露出來。
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    jmx:
      exposure:
        exclude: '*'
    web:
      exposure:
        include: '*'

因爲咱們使用了yml,這裏須要咱們自定義一個YamlPropertyLoaderFactory用於加載yml配置:

package com.test.spring.context.config;

import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;

import java.io.IOException;

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null){
            return super.createPropertySource(name, resource);
        }

        return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).get(0);
    }
}

定義child context的公共Bean類:

package com.test.spring.context.bean;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class ChildBean {
    private RootBean fatherBean;
    private String name;
}

定義ChildContext1:

package com.test.spring.context.config.child1;

import com.hopegaming.scaffold.spring.context.bean.ChildBean;
import com.hopegaming.scaffold.spring.context.bean.RootBean;
import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"})
@PropertySource(value = "classpath:/bean-config-1.yaml", factory = YamlPropertyLoaderFactory.class)
public class ChildContext1 {
    @Bean
    public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
        ChildBean childBean = new ChildBean();
        childBean.setFatherBean(fatherBean);
        childBean.setName(name);
        return childBean;
    }
}

bean-config-1.yaml

server:
  port: 8080
spring:
  application:
    name: child1

接下來分別是ChildContext2,ChildContext3的:

package com.test.spring.context.config.child2;

import com.hopegaming.scaffold.spring.context.bean.ChildBean;
import com.hopegaming.scaffold.spring.context.bean.RootBean;
import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"})
@PropertySource(value = "classpath:/bean-config-2.yaml", factory = YamlPropertyLoaderFactory.class)
public class ChildContext2 {
    @Bean
    public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
        ChildBean childBean = new ChildBean();
        childBean.setFatherBean(fatherBean);
        childBean.setName(name);
        return childBean;
    }
}
server:
  port: 8081
spring:
  application:
    name: child2

management:
  endpoint:
    health:
      show-details: always
  endpoints:
    jmx:
      exposure:
        exclude: '*'
    web:
      exposure:
        include: '*'
package com.test.spring.context.config.child3;

import com.hopegaming.scaffold.spring.context.bean.ChildBean;
import com.hopegaming.scaffold.spring.context.bean.RootBean;
import com.hopegaming.scaffold.spring.context.config.YamlPropertyLoaderFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication(scanBasePackages = {"com.test.spring.context.controller"})
@PropertySource(value = "classpath:/bean-config-3.yaml", factory = YamlPropertyLoaderFactory.class)
public class ChildContext3 {
    @Bean
    public ChildBean getChildBean(@Value("${spring.application.name}") String name, RootBean fatherBean) {
        ChildBean childBean = new ChildBean();
        childBean.setFatherBean(fatherBean);
        childBean.setName(name);
        return childBean;
    }
}
server:
  port: 8082
spring:
  application:
    name: child3

management:
  endpoint:
    health:
      show-details: always
  endpoints:
    jmx:
      exposure:
        exclude: '*'
    web:
      exposure:
        include: '*'

測試接口TestController

package com.test.spring.context.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Locale;

@RestController
public class TestController {
    @Autowired
    private ChildBean childBean;

    @RequestMapping("/test")
    public ChildBean getChildBean() {
        return childBean;
    }
}

啓動類:

package com.test.spring.context;

import com.hopegaming.scaffold.spring.context.config.child1.ChildContext1;
import com.hopegaming.scaffold.spring.context.config.child2.ChildContext2;
import com.hopegaming.scaffold.spring.context.config.child3.ChildContext3;
import com.hopegaming.scaffold.spring.context.config.root.RootContext;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

public class ContextMain {
    public static void main(String[] args) {
        SpringApplicationBuilder appBuilder =
                new SpringApplicationBuilder()
                        .sources(RootContext.class)
                        //第一個子context用child,剩下的都用sibling
                        .child(ChildContext1.class)
                        .sibling(ChildContext2.class)
                        .sibling(ChildContext3.class);
        ConfigurableApplicationContext applicationContext = appBuilder.run();
    }
}

啓動後,訪問http://127.0.0.1:8080/test返回:

{"fatherBean":{"name":"root"},"name":"child1"}

訪問http://127.0.0.1:8081/test返回:

{"fatherBean":{"name":"root"},"name":"child2"}

訪問http://127.0.0.1:8082/test返回:

{"fatherBean":{"name":"root"},"name":"child3"}

訪問http://127.0.0.1:8080/actuator/beans會有相似於下面的返回(省略了不關心的bean):

{
    "contexts": {
        "application-1": {
            "beans": {
                "getChildBean": {
                    "aliases": [],
                    "scope": "singleton",
                    "type": "com.hopegaming.scaffold.spring.context.bean.ChildBean",
                    "resource": "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2",
                    "dependencies": [
                        "getFatherBean"
                    ]
                },
                "childContext2": {
                    "aliases": [],
                    "scope": "singleton",
                    "type": "com.hopegaming.scaffold.spring.context.config.child2.ChildContext2$$EnhancerBySpringCGLIB$$26f80b15",
                    "resource": null,
                    "dependencies": []
                }
                .......
            },
            "parentId": "application"
        },
        "application": {
            "beans": {
                "getFatherBean": {
                    "aliases": [],
                    "scope": "singleton",
                    "type": "com.hopegaming.scaffold.spring.context.bean.RootBean",
                    "resource": "com.hopegaming.scaffold.spring.context.config.root.RootContext",
                    "dependencies": []
                },
                "rootContext": {
                    "aliases": [],
                    "scope": "singleton",
                    "type": "com.hopegaming.scaffold.spring.context.config.root.RootContext$$EnhancerBySpringCGLIB$$18d9c26f",
                    "resource": null,
                    "dependencies": []
                }
                .......
            },
            "parentId": null
        }
    }
}

經過這個例子,想必你們對於 ApplicationContext 層級有了必定的理解

Bean 加載條件

咱們會常常看到@Conditional相關的註解,例如@ConditionalOnBean還有@ConditionalOnClass等等,這些註解提供了自動裝載時候根據某些條件加載不一樣類的靈活性。@Conditional註解是 spring-context 提供的特性,Spring Boot 在這個註解的基礎上,提供了更多具體的條件配置註解,包括:

  • @ConditionalOnBean,若是當前 ApplicationContext 的 BeanFactory 已經包含這些 Bean,則知足條件。與之相反的是 @ConditionalOnMissingBean,若是當前 ApplicationContext 的 BeanFactory 不包含這些 Bean,則知足條件。
  • @ConditionalOnClass,若是當前 classpath 中有這些類,則知足條件。與之相反的是@ConditionalOnMissingClass,若是當前 classpath 中沒有這些類,則知足條件
  • @ConditionalOnProperty,指定屬性是否存在,而且值知足havingValue指定的值(沒設置就是不爲false就行),matchIfMissing表明若是屬性不存在表明條件知足仍是不知足。

以上幾個註解是比較經常使用的,剩下的例如ConditionalOnCloudPlatform這些不太經常使用,這裏先不提了。

若是有多個相似的@Conditional註解做用於同一個方法或者類,這些加載條件是「And」的關係

Configuration 加載順序

因爲 Bean 加載條件的複雜性,有時候咱們想某些 Configuration 類先加載,某些在特定的 Configuration 加載完以後再加載。例如:

@Configuration
public class FirstConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Service service1() {
        ......
    }
}
@Configuration
public class SecondConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Service service1() {
        ......
    }
}

假設這兩個類在不一樣 jar 包,咱們沒有辦法肯定最後建立的是哪個類的 Service,這時候咱們就須要用到一些決定 Configuration 加載順序的註解。注意這裏的 Configuration 加載順序僅僅是 Bean 定義加載順序,主要是爲了限制上面提到的 Bean 加載條件的判斷順序,而不是建立 Bean 的順序。Bean 建立的順序主要由 Bean 依賴決定以及@DependsOn註解限制。

相關的註解以下:

  • @AutoConfigureAfter 指定當前 Configuration 在 某個 Configuration 以後加載。
  • @AutoConfigureBefore 指定當前 Configuration 在 某個 Configuration 以前加載。
  • @AutoConfigureOrder 相似於@Order註解,指定當前 Configuration 的加載序號,默認是 0 ,越小越先加載。

Bean 排序

對於同一類型的 Bean(實現了同一接口的 Bean),咱們能夠用一個 List 進行自動裝載,例如:

public interface Service {
    void test();
}
@Componenet
public class ServiceA implements Service {
    @Override
    public void test() {
        System.out.println("ServiceA");
    }
}
@Componenet
public class ServiceB implements Service {
    @Override
    public void test() {
        System.out.println("ServiceB");
    }
}

@Componenet
public class Test {
    @Autowired
    private List<Service> services;
}

private List&lt;Service&gt; services 中就會有 serviceAserviceB 這兩個 Bean,可是誰在前誰在後呢?能夠經過@Order註解指定。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

    /**
     * The order value.
     * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
     * @see Ordered#getOrder()
     */
    int value() default Ordered.LOWEST_PRECEDENCE;

}

值越小,越靠前。

相關文章
相關標籤/搜索