Spring Boot 原理解析—从入口SpringApplication说起

    技术2022-07-11  125

    我们说Spring Boot简化了Spring的开发,可以根据导入的starter包自动向Spring容器中注册Bean。在Spring Boot之前,我们要向Spring容器中注册Bean,首先需要配置xml,如果是Web容器,则将spring.xml位置配置到Spring 提供的监听器中,由Spring解析注册Bean,否则则使用new ClassPathXmlApplicationContext("/spring.xml")或者new AnnotationConfigApplicationContext()等容器读取配置解析注册Bean。在Spring Boot中,只需要一个包含main方法的启动类即可,在运行该main方法时,会读取Bean,并且向Spring 容器中注册Bean。如下为Spring Boot最简单的启动类:

    @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

    如上代码所示,仅仅的四行代码,我们就开发了一个Spring MVC的应用,我们在运行该类的时候,会自动加载和注册对应的Bean,下面我们讲述,Spring Boot是如何加载和注册Bean的。首先我们从@SpringBootApplication说起,查看源码如下:

    @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }

    我们先不管注解中的属性,但是可以看出来这是一个复合注解,由@SpringBootConfiguration,@EnableAutoConfiguration和@ComponentScan三个注解构成。@ComponentScan注解我们都熟悉,是Spring中的一个元注解,用于配置Spring要扫描的包。因此我们需要继续查看另外两个注解的源码。

    @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { } @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 {}; String[] excludeName() default {}; }

    是不是又看到了我们熟悉的Spring的注解@Configuration和@Import,我们查看@AutoConfigurationPackage注解源码可以发现其元注解依然是@Import注解:@Import(AutoConfigurationPackages.Registrar.class)。只要熟悉Spring中这些注解的使用,很容易就了解@SpringBootApplication注解的功能。其实Spring Boot中的注解基本上都是对Spring中注解的增强。熟悉完注解之后我们开始查看run方法:SpringApplication.run(Application.class, args);

    //启动调用run方法 public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { //调用重载的run方法 return run(new Class<?>[] { primarySource }, args); } //重载的run方法 public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { //创建SpringApplication对象并且调用run方法 return new SpringApplication(primarySources).run(args); }

    通过上面的方法,我们发现run方法返回一个ConfigurableApplicationContext实例,如果对Spring熟悉,则很容易理解这个实例,也就是说,我们通过运行run方法创建了一个Spring 容器,并且可以返回,代码如下所示:

    public static void main(String[] args) { //返回ApplicationContext 容器 ApplicationContext context = SpringApplication.run(Application.class, args); //从容器中获取实例 Application application = context.getBean(Application.class); //遍历容器中的实例,并且打印Bean的名称和类型 for(String beanName : context.getBeanDefinitionNames()) { System.out.println("注册到Spring容器中的Bean名称为 "+beanName +",类型为 " +context.getBean(beanName)); } }

    上面的代码最后一部分没什么用处,只是演示了Spring Boot可以通SpringApplication类的run方法创建一个Spring容器,而真正执行逻辑的方法时在run(args)方法中,该方法用于运行一个Spring应用并且创建和刷新一个新的ApplicationContext实例。

    public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); //获取监听器 SpringApplicationRunListeners listeners = getRunListeners(args); //SpringBoot运行开始监听 listeners.starting(); try { //准备Spring Boot运行环境 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); Banner printedBanner = printBanner(environment); //创建ApplicationContext容器 context = createApplicationContext(); analyzers = new FailureAnalyzers(context); //准备容器 prepareContext(context, environment, listeners, applicationArguments,printedBanner); //刷新容器 refreshContext(context); //刷新容器后处理 afterRefresh(context, applicationArguments); //SpringBoot运行成功监听 listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } return context; }catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } }

    Spring Boot运行经历了一系列的步骤,包括监听器,环境的准备,容器的创建,容器的准备与刷新等,本章主要关注的是容器的创建于容器的刷新,这两步是向Spring中注册Bean的核心步骤。首先看createApplicationContext()方法,该方法用于创建Spring容器:

    protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; //如果Context为空 if (contextClass == null) { try { //获取容器Class实例 contextClass = Class.forName(this.webEnvironment? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); }catch (ClassNotFoundException ex) { throw new IllegalStateException("Unable create a default ApplicationContext, "+ "please specify an ApplicationContextClass",ex); } } //实例化并返回容器 return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); }

    我们需要关注的代码为try块中的代码,里面有两个常量DEFAULT_WEB_CONTEXT_CLASS 和 DEFAULT_CONTEXT_CLASS)他们是在SpringApplication类中声明的如下:

    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext"; public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

    好了 现在比较清晰了,该方法做的就是通过反射创建一个ApplicationContext实例,而平时我们是直接通过new创建的ApplicationContext实例,使用的方式是以注解容器。

    ApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext(); ApplicationContext context = new AnnotationConfigApplicationContext();

    创建容器之后,就要刷新容器,向容器中注册Bean,也就是refreshContext(context)方法:

    private void refreshContext(ConfigurableApplicationContext context) { //在此调用refresh方法 refresh(context); //如果注册勾子执行勾子方法 if (this.registerShutdownHook) { try { //执行勾子方法 context.registerShutdownHook(); }catch (AccessControlException ex) { // Not allowed in some environments. } } } //真正refresh的方法 protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); //调用AbstractApplicationContext的refresh()方法 ((AbstractApplicationContext) applicationContext).refresh(); }

    看到上面我们又看到重点了,最终调用AbstractApplicationContext的refresh()方法,是不是有点熟悉了,因为我们使用AnnotationConfigApplicationContext的构造方法实例化ApplicationContext的时候,构造方法中正是调用了这个方法。

    public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { this(); register(annotatedClasses); refresh(); }

    通过上面代码我们看到了,最终主要的代码逻辑全部都又走到了Spring框架中,这里我们只讲述Spring Boot入口Spring容器创建和注册的流程,后续我们会继续讲解Spring Boot是如何完成自动注册的。

    Processed: 0.008, SQL: 9