分布式微服务架构之SpringBoot定制篇

    科技2022-09-07  100

    一,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创建成功。

    Processed: 0.013, SQL: 9