zoukankan      html  css  js  c++  java
  • SpringBoot—项目启动时几种初始化操作及SpringApplication类详解

    技术公众号:后端技术解忧铺
    关注微信公众号:CodingTechWork,一起学习进步。

    引言

      在使用Spring Boot搭建项目时,启动项目工程,经常遇到一些需要启动初始化数据或者资源的需求,比如提前加载某个配置文件内容,初始化某个信息、做好安全认证等。这里一起学习总结了几种初始化数据的方式。

    @Bean注解配置

    使用方式

      编写配置类,使用@Configuration@Bean注解进行初始化。

    使用示例

    package com.example.andya.demo.conf;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author Andya
     * @create 2020-09-14 21:37
     */
    @Configuration
    public class InitConfigTest {
    
        @Value("${key}")
        private String key;
    
        @Bean
        public String testInit(){
            System.out.println("init key: " + key);
            return key;
        }
    }
    

    ApplicationRunner接口

    使用方式

      编写类去实现ApplicationRunner接口,实现run()方法,该方法在工程启动类的XXXApplicationSpringApplication.run(xxxApplication.class, args)方法之前,@Componet会在所有Spring的Beans初始化完成之后,执行完成。

    使用示例

    package com.example.andya.demo.service.initTest;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Andya
     * @date 2020-09-14 21:37
     */
    @Component
    public class ApplicationRunnerTest implements ApplicationRunner {
    
        @Value("${key}")
        private String key;
    
        @Override
        public void run(ApplicationArguments applicationArguments) throws Exception {
            System.out.println("ApplicationRunner test: init key : " + key);
        }
    }
    

    CommandLineRunner接口

    使用方式

      类似于ApplicationRunner接口,我们同样编写类去实现CommandLineRunner接口,实现run()方法,该方法在工程启动类的XXXApplicationSpringApplication.run(xxxApplication.class, args)方法之前,@Componet会在所有Spring的Beans初始化完成之后,执行完成。
      多个类实现接口后,可以通过@Order注解进行顺序的控制,@Order(n),n越小,启动执行的越早。

    使用示例

    示例1

    package com.example.andya.demo.service.initTest;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Andya
     * @create 2020-09-14 21:37
     */
    @Component
    @Order(1)
    public class CommandLineRunner1Test implements CommandLineRunner {
    
        @Value("${key}")
        private String key;
    
        @Override
        public void run(String... strings) throws Exception {
            System.out.println("CommandLineRunner first init: 1)init key : " + key);
        }
    }
    

    示例2

    package com.example.andya.demo.service.initTest;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Andya
     * @create 2020-09-14 21:37
     */
    @Component
    @Order(2)
    public class CommandLineRunner2Test implements CommandLineRunner {
    
        @Value("${key}")
        private String key;
    
        @Override
        public void run(String... strings) throws Exception {
            System.out.println("CommandLineRunner second init: 2)init key : " + key);
        }
    }
    
    

    两种接口分析

    接口对比

      CommandLineRunnerApplicationRunner都是接口,只是内部参数不一样,前者的参数是最原始的参数String类型,无任何处理;后者是ApplicationArguments类型,对原始参数进行了封装处理。

    执行结果

    上述三种方式的执行结果示例

      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    ( ( )\___ | '_ | '_| | '_ / _` |    
     \/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v1.5.6.RELEASE)
    
    2020-09-14 21:45:28.403  INFO 25408 --- [           main] com.example.andya.demo.DemoApplication   : Starting DemoApplication on DESKTOP-KC40970 with PID 25408 (F:selfcode	argetclasses started by Hugh in F:selfcode)
    2020-09-14 21:45:28.406  INFO 25408 --- [           main] com.example.andya.demo.DemoApplication   : No active profile set, falling back to default profiles: default
    2020-09-14 21:45:28.434  INFO 25408 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1542153: startup date [Tue Sep 15 11:28:28 CST 2020]; root of context hierarchy
    2020-09-14 21:45:29.322  INFO 25408 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 9000 (http)
    2020-09-14 21:45:29.327  INFO 25408 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    2020-09-14 21:45:29.327  INFO 25408 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.16
    2020-09-14 21:45:29.385  INFO 25408 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2020-09-14 21:45:29.385  INFO 25408 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 953 ms
    2020-09-14 21:45:29.504  INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
    2020-09-14 21:45:29.506  INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
    2020-09-14 21:45:29.506  INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
    2020-09-14 21:45:29.506  INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
    2020-09-14 21:45:29.506  INFO 25408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
    init key: value
    2020-09-14 21:45:29.818  INFO 25408 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1542153: startup date [Tue Sep 15 11:28:28 CST 2020]; root of context hierarchy
    2020-09-14 21:45:29.857  INFO 25408 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/aopTest/sayHi/{name}],methods=[GET]}" onto public java.lang.String com.example.andya.demo.controller.AopController.sayHi(java.lang.String)
    2020-09-14 21:45:29.857  INFO 25408 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.example.andya.demo.controller.HelloController.hello()
    2020-09-14 21:45:29.859  INFO 25408 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
    2020-09-14 21:45:29.859  INFO 25408 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
    2020-09-14 21:45:29.923  INFO 25408 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
    2020-09-14 21:45:29.923  INFO 25408 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
    2020-09-14 21:45:29.955  INFO 25408 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
    2020-09-14 21:45:30.064  INFO 25408 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
    2020-09-14 21:45:30.069  INFO 25408 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
    2020-09-14 21:45:30.095  INFO 25408 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 9000 (http)
    CommandLineRunner first init: 1)init key : value
    CommandLineRunner second init: 2)init key : value
    ApplicationRunner test: init key : value
    2020-09-14 21:45:30.097  INFO 25408 --- [           main] com.example.andya.demo.DemoApplication   : Started DemoApplication in 1.911 seconds (JVM running for 2.601)
    2020-09-14 21:46:00.004  INFO 25408 --- [pool-1-thread-1] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
    2020-09-14 21:46:59.781  INFO 25408 --- [nio-9000-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
    2020-09-14 21:46:59.781  INFO 25408 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
    2020-09-14 21:46:59.802  INFO 25408 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 21 ms
    

    源码分析

    XxxApplication启动类

    package com.example.andya.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    

    其中,在工程中,点击SpringApplication.run(DemoApplication.class, args);的run方法进行源码追踪。

    SpringApplication类源码

    	//构造函数1
        public SpringApplication(Object... sources) {
            this.bannerMode = Mode.CONSOLE;
            this.logStartupInfo = true;
            this.addCommandLineProperties = true;
            this.headless = true;
            this.registerShutdownHook = true;
            this.additionalProfiles = new HashSet();
            this.initialize(sources);
        }
    	//构造函数2
        public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
            this.bannerMode = Mode.CONSOLE;
            this.logStartupInfo = true;
            this.addCommandLineProperties = true;
            this.headless = true;
            this.registerShutdownHook = true;
            this.additionalProfiles = new HashSet();
            this.resourceLoader = resourceLoader;
            this.initialize(sources);
        }
    
    	//initialize方法
        private void initialize(Object[] sources) {
        	//source不为空时,保存配置类
            if (sources != null && sources.length > 0) {
                this.sources.addAll(Arrays.asList(sources));
            }
    		//判断该应用是否为web应用
            this.webEnvironment = this.deduceWebEnvironment();
            //获取并保存容器初始化ApplicationContextInitializer类,通过SpringFactoriesLoader.loadFactoryNames方法
            //从META-INF/spring.factories路径中获取ApplicationContextInitializer集合
            this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    		//获取并保存监听器 ApplicationListener类,同样的,通过SpringFactoriesLoader.loadFactoryNames方法
    		//从META-INF/spring.factories路径中获取ApplicationListener集合  
            this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
            //通过堆栈追踪名为main方法,从而获取包含main方法的类
            this.mainApplicationClass = this.deduceMainApplicationClass();
        }
        
        //run()方法1
        public static ConfigurableApplicationContext run(Object source, String... args) {
            return run(new Object[]{source}, args);
        }
    	//run()方法2
        public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
            return (new SpringApplication(sources)).run(args);
        }
        //run()方法3,最底层
        public ConfigurableApplicationContext run(String... args) {
        	//新建计时工具类StopWatch
            StopWatch stopWatch = new StopWatch();
            //启动计时工具类StopWatch
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            FailureAnalyzers analyzers = null;
            //设置java.awt.headless的系统属性,如服务器不需要显示器就需要如此设置
            this.configureHeadlessProperty();
            //获取监听器SpringApplicationRunListeners,调用了getSpringFactoriesInstances()方法
            //该方法又调用了loadFactoryNames()方法从META-INF/spring.factories路径中获取SpringApplicationRunListeners集合
            SpringApplicationRunListeners listeners = this.getRunListeners(args);
            //启动监听器
            listeners.starting();
    
            try {
            	//将args参数封装至ApplicationArguments中
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                //准备环境:内部调用了this.configureEnvironment()和listeners.environmentPrepared()等方法
                ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
                //从环境中获取Banner进行打印,可以自定义Banner
                Banner printedBanner = this.printBanner(environment);
                //创建Spring的容器
                context = this.createApplicationContext();
                //分析并诊断是项目启动否有问题
                new FailureAnalyzers(context);
                //准备容器上下文:
                //1)设置容器环境:context.setEnvironment(environment);
                //2)设置beanNameGenerator和resourceLoader:this.postProcessApplicationContext(context);
                //3)初始化context并检测是否接受该类型容器:this.applyInitializers(context);
                //4)触发监听事件:listeners.contextPrepared(context);是一个空函数;
                //5)注册bean:通过context.getBeanFactory().registerSingleton()方法向容器注入springApplicationArguments和springBootBanner
                //6)获取sources:Set<Object> sources = this.getSources();
                //7)加载启动类,注入到容器内:this.load(context, sources.toArray(new Object[sources.size()]));
                //给loader设置beanNameGenerator,resourceLoader和environment
                //然后调用load()方法,按照不同的source类型加载class,resource,package,charSequence,超过这些范围的就抛出异常“Invalid source type xxx”
                //8)触发监听事件:listeners.contextLoaded(context);进行
                this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                //刷新Spring容器
                this.refreshContext(context);
                //从容器中获取所有的ApplicationRunner和CommandLineRunner进行回调callRunner
                this.afterRefresh(context, applicationArguments);
                //触发监听事件,所有的SpringApplicationRunListener进行callFinishedListener回调
                listeners.finished(context, (Throwable)null);
                //计时器停止
                stopWatch.stop();
                if (this.logStartupInfo) {
                    (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
                }
    			//整个SpringBoot工程启动完毕,返回启动的Ioc容器上下文
                return context;
            } catch (Throwable var9) {
                this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
                throw new IllegalStateException(var9);
            }
        }
        protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
            this.callRunners(context, args);
        }
        //callRunners
        private void callRunners(ApplicationContext context, ApplicationArguments args) {
            List<Object> runners = new ArrayList();
            runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
            runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
            AnnotationAwareOrderComparator.sort(runners);
            Iterator var4 = (new LinkedHashSet(runners)).iterator();
    
            while(var4.hasNext()) {
                Object runner = var4.next();
                if (runner instanceof ApplicationRunner) {
                    this.callRunner((ApplicationRunner)runner, args);
                }
    
                if (runner instanceof CommandLineRunner) {
                    this.callRunner((CommandLineRunner)runner, args);
                }
            }
    
        }
    

      在上述源码中,我们分析了SpringBoot启动的一个流程,其中,我们可以从源码的run()方法一层一层点击追踪查看到,ApplicationRunnerCommandLineRunner是在callRunners方法中执行的。

    参考
    jdk1.8

  • 相关阅读:
    关于云原生应用的思考
    动手实现 LRU 算法,以及 Caffeine 和 Redis 中的缓存淘汰策略
    Spring5-Reactor函数式编程
    架构简洁之道:从阿里开源应用架构 COLA 说起
    如何优雅地运用位运算实现产品需求?
    如何优雅地运用位运算实现产品需求?
    图形处理:给 Canvas 文本填充线性渐变
    深入理解EnableAutoConfiguration原理
    pwnable.tw之3x17
    WebRTC之完整搭建Jitsi Meet指南
  • 原文地址:https://www.cnblogs.com/Andya/p/13673754.html
Copyright © 2011-2022 走看看