分散式微服務架構之SpringBoot客製化篇

2020-10-07 16:03:57

一,springboot的啟動原理

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
    	//從主程式的入口進入
        SpringApplication.run(Application.class,args);
    }
}
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        //這裡的流程實際上分為了兩步:
        //第一步:建立SpringApplication
        //第二步:執行run()
        return new SpringApplication(primarySources).run(args);
    }

1.建立SpringApplication

進入new SpringApplication()

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //儲存主設定類
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //判斷當前是否是一個web應用
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //從類路徑下找到META-INF/spring.factory設定的所有ApplicationContextInitializer,然後儲存起來  TODO
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //從類路徑下找到META-INF/spring.factory設定的所有ApplicationListener,然後儲存起來
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //從多個設定類找到main方法的主設定類
        this.mainApplicationClass = deduceMainApplicationClass();
    }

再來看getSpringFactoriesInstances()

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        //SpringFactoriesLoader.loadFactoryNames(type, classLoader)
        //往工廠裡面載入bean
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

接著看loadFactoryNames()

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        //呼叫了loadSpringFactories
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            //從FACTORIES_RESOURCE_LOCATION下載入組態檔
            //FACTORIES_RESOURCE_LOCATION代表的哪裡?
            //public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

再回到SpringApplication()看setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        //實際呼叫的是getSpringFactoriesInstances
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        //呼叫了 loadFactoryNames
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

這裡就和上一步其實是一樣的,從META-INF/spring.factories載入bean。

2.執行run()

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

點選進入run()

    public ConfigurableApplicationContext run(String... args) {
        //監聽springboot應用的建立
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        //獲取SpringApplicationRunListeners;從類路徑下META‐INF/spring.factories
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //回撥所有的獲取SpringApplicationRunListener.starting()方法
        listeners.starting();
        try {
            //封裝命令列引數
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //準備環境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            //建立環境完成後回撥SpringApplicationRunListener.environmentPrepared();表示環境準備完成 
            Banner printedBanner = printBanner(environment);
            //建立ApplicationContext;決定建立web的ioc還是普通的ioc 利用反射建立
            context = createApplicationContext();
            //做異常列印報告的,沒啥用
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            //準備上下文環境;將environment儲存到ioc中;而且applyInitializers();
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //容器重新整理,ioc容器初始化(如果是web應用還會建立嵌入式的Tomcat)
            refreshContext(context);
            //從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner進行回撥
            //ApplicationRunner先回撥,CommandLineRunner再回撥
            afterRefresh(context, applicationArguments);
            //停止監聽
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            //所有的SpringApplicationRunListener回撥finished方法
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

prepareContext()

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		//給上下文設定環境
		context.setEnvironment(environment);
		//執行上下文的後置處理器,給容器中載入一些元件
		postProcessApplicationContext(context);
		//回撥之前儲存的所有的ApplicationContextInitializer的initialize方法
		applyInitializers(context);
		//回撥所有的SpringApplicationRunListener的contextPrepared();
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

refresh()

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 清空快取
			prepareRefresh();

			// cas的方式初始化bean工廠
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// 建立bean工廠,載入xxxAware 註冊單範例bean
			prepareBeanFactory(beanFactory);

			try {
				// 載入bean工廠的後置處理器
				postProcessBeanFactory(beanFactory);

				// 執行bean工廠的後置處理器
				invokeBeanFactoryPostProcessors(beanFactory);

				// 將bean的後置處理器註冊到容器中
				registerBeanPostProcessors(beanFactory);

				// 初始化容器資訊
				initMessageSource();

				// 初始化事件派發器
				initApplicationEventMulticaster();

				// 載入其他的單範例bean,並建立web容器
				onRefresh();

				// 將監聽器註冊到容器
				registerListeners();

				// 完成bean工廠的初始化,看看有沒有自定義的類載入器和xxxAware介面,都載入到容器中
				finishBeanFactoryInitialization(beanFactory);

				//清除快取,為此上下文初始化生命週期處理器,將容器重新整理派發到生命週期處理器,事件派發器釋出事件,啟動web容器,並行布事件
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

二,自定義starter

1.原理分析

1、這個場景需要使用到的依賴是什麼?

2、如何編寫自動設定

@Configuration  //指定這個類是一個設定類
@ConditionalOnXXX  //在指定條件成立的情況下自動設定類生效
@AutoConfigureAfter  //指定自動設定類的順序
@Bean  //給容器中新增元件
@ConfigurationPropertie結合相關xxxProperties類來繫結相關的設定
@EnableConfigurationProperties //讓xxxProperties生效加入到容器中
自動設定類要能載入
將需要啟動就載入的自動設定類,設定在META‐INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

3、模式:
啟動器只用來做依賴匯入;
專門來寫一個自動設定模組;
啟動器依賴自動設定;別人只需要引入啟動器(starter)
mybatis-spring-boot-starter;自定義啟動器名-spring-boot-starter

2.程式碼

1.首先建立兩個maven工程

yhd-spring-boot-starter
yhd-spring-boot-starter-autoconfigurer

2.引入依賴

在yhd-spring-boot-starter-autoconfigurer裡面引入

    <!--  引入springboot核心啟動器  -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
    </dependencies>

在yhd-spring-boot-starter裡面引入

        <!--  引入自動設定模組  -->
    <dependencies>
        <dependency>
            <groupId>com.yhd</groupId>
            <artifactId>yhd-spring-boot-starter-autoconfigurer</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

3.編寫yhd-spring-boot-starter-autoconfigurer

1.建立一個service

/**
 * @author yhd
 * @createtime 2020/10/6 1:28
 */
public class HelloService {

    @Autowired
    private HelloServiceProperties helloServiceProperties;


    public String sayHello(String name){
        return helloServiceProperties.getPrefix()+" "+name+" " +helloServiceProperties.getSuffix();
    }
}

2.建立一個HelloServiceProperties

/**
 * @author yhd
 * @createtime 2020/10/6 1:30
 * Not registered via @EnableConfigurationProperties, marked as Spring component, or scanned via @ConfigurationPropertiesScan
 */
@Component
@ConfigurationProperties(prefix = "yhd.hello")
public class HelloServiceProperties {

    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

3.建立一個HelloServiceAutoConfiguration

/**
 * @author yhd
 * @createtime 2020/10/6 1:35
 */
@SpringBootConfiguration
@ConditionalOnWebApplication
@EnableConfigurationProperties(HelloServiceProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    private HelloServiceProperties helloServiceProperties;

    @Bean
    public HelloService helloService(){
       return new HelloService();
    }
}

4.在resources目錄下建立META-INF目錄

5.在META-INF下建立spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yhd.config.HelloServiceAutoConfiguration

4.將這兩個專案安裝到倉庫

5.別的專案引入yhd-spring-boot-starter依賴

/**
 * @author yhd
 * @createtime 2020/10/6 1:45
 */
@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;
    @GetMapping("/hello")
    public String hello(){
        return helloService.sayHello("尹會東");
    }
}

組態檔:

yhd.hello.prefix=Hello
yhd.hello.suffix=!

存取localhost:8080,輸出:Hello 尹會東 !。
至此,starter建立成功。