springboot starter启动流程

2021-09-30  本文已影响0人  凌晨的咸鱼
  1. 初始化SpringApplication:确定webApplicatuonType类型,扫描所有jar包下META-INF/spring.factories文件,生成ApplicationContextInitializer和ApplicationListener接口的实现类的对象。
- 确定webApplicationType类型:
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  源码解析:获取类加载器,去加载路径下的类,如果找不到就抛异常返回false,最后通过判 
  断返回类型为SERVLET
  参考:https://blog.csdn.net/z69183787/article/details/105834513/

- 确定applicationContext的实现类:
  run方法中有这一段:
  ConfigurableApplicationContext context = null;
  context = this.createApplicationContext();
  打开createApplicationContext方法:
  switch(this.webApplicationType) {
      case SERVLET:
          contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
          break;
      case REACTIVE:
          contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
          break;
      default:
          contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
  * 最终得知applicationcontext的实现类为;AnnotationConfigServletWebServerApplicationContext
    然后通过return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass) 方法实例化AnnotationConfigServletWebServerApplicationContext类,生成对象
  
  * 所以,通过添加不同的jar包,就能得到不同的webApplicationType类型,通过不同的webApplicationType类型就能加载不同的applicationContext的实现类。

- 假如我们想在springboot项目启动完成之后,做点什么,我们应该怎么办呢?
  实现ApplicationRunner,重写run方法。
  this.callRunners(context, applicationArguments); 这段是执行程序,大致流程如下:
  找到ApplicationRunner的实现类,反射生成对象,调用run()方法。前提是实现了ApplicationRunner的类标注@Component注解交给spring管理。
  思维发散:要用到spring某个接口的功能时,先要创建一个类实现接口并加上@Component注解交给Spring管理,然后实现这个接口的某个固定方法,
  spring会在执行过程中去找到接口的实现类,然后反射创建对象,调用这个固定方法执行其中的程序。
  参考:https://zhuanlan.zhihu.com/p/343357078

- Runner,它是在项目加载完成之后执行的,有后就有前,有没有在项目加载之前执行的呢,ApplicationContextInitializer就是在spring的bean加载之前执行的
  ApplicationContextInitializer用于在spring容器刷新之前初始化Spring ConfigurableApplicationContext的回调接口。
  this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); - 就是这个方法中的this.applyInitializers(context)方法找到调用initialize()方法
  this.refreshContext(context);
  剪短说就是在容器刷新之前调用该类的 initialize 方法。并将 ConfigurableApplicationContext 类的实例传递给该方法
  通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
  * 过程:在spring的bean加载之前,通过扫描所有jar包META-INF文件夹下的spring.factories文件,找到所有实现了ConfigurableApplicationContext接口的类,
    进行实例化,然后调用每个实例化对象的initialize()方法进行初始化等工作。这就是ApplicationContextInitializer接口的作用,请记住,他是用来初始化的。
  * 自己实现:创建一个类实现ApplicationContextInitializer接口,重写initialize()方法,在resource目录下新建META-INF文件夹,
    新建spring.factories文件,按照格式接口=配置类,即可以实现spring的bean加载前调用的初始化程序
  参考: https://zhuanlan.zhihu.com/p/343704665

- ApplicationContextAware
  如果我们需要在工具类中获取bean怎么办,又不能用@Autowired引入
  此时,我们可以创建一个类交给Spring管理,然后实现ApplicationContextAware接口,重写其中的set方法。然后在这个类中写个方法去get全局变量ApplicationContext,获取写个方法获取某个bean就可以用了
  源码逻辑:启动过程中会去找到ApplicationContextAware接口的实现类,然后通过回调其中的set方法,把全局使用的ApplicationContext给set进去,所以我们就可以使用了。

- springboot项目有很多自动配置注解,比如@EnableScheduling定时任务,@EnableAsync异步编程。
  其实实现很简单,点开这个注解,你会发现里面有一个@Import({SchedulingConfiguration.class})注解。
  进入导入的注解类,返回一个return new ScheduledAnnotationBeanPostProcessor();
  进入这个创建的类,有一个核心方法:
  public Object postProcessAfterInitialization(Object bean, String beanName) {
      if (!(bean instanceof AopInfrastructureBean) && !(bean instanceof TaskScheduler) && !(bean instanceof ScheduledExecutorService)) {
          Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
          if (!this.nonAnnotatedClasses.contains(targetClass)) {
              Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (method) -> {
                  Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);
                  return !scheduledMethods.isEmpty() ? scheduledMethods : null;
              });
              if (annotatedMethods.isEmpty()) {
                  this.nonAnnotatedClasses.add(targetClass);
                  if (this.logger.isTraceEnabled()) {
                      this.logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
                  }
              } else {
                  // 重点就是这里,因为我们是定时任务自动配置,所以这里面会找到加上@Scheduled注解的所有方法,然后去执行他们
                  annotatedMethods.forEach((method, scheduledMethods) -> {
                      scheduledMethods.forEach((scheduled) -> {
                          this.processScheduled(scheduled, method, bean);
                      });
                  });
                  if (this.logger.isTraceEnabled()) {
                      this.logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods);
                  }
              }
          }

          return bean;
      } else {
          return bean;
      }
  }
  所以其实很简单,我们在启动类上配置@EnableXXX注解,就会自动导入对应的实现类,实现类中有某个方法去执行对应的逻辑,
  比如找到添加了定时器@Scheduled的注解方法或者添加了异步的注解方法,去执行其中的逻辑。


Spring如何把bean对象放入applicationContext的实现类AnnotationConfigServletWebServerApplicationContext中的源码在哪里研究下

listeners研究下

重要方法:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        // 获取类加载器
        ClassLoader classLoader = this.getClassLoader();
        // 获取所有实现类的名称集合
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 生成实现类的对象集合
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
  1. 通过@EnableAutoConfiguration注解中的@Import({AutoConfigurationImportSelector.class})注解,同样扫描所有jar包下META-INF/spring.factories文件,生成EnableAutoConfiguration接口的实现类的对象,比如mybatis-spring-boot-starter就是这种方式加载的。
  2. 通过@ComponentScan注解,扫描启动类所在包及其子包中所有包含@Component的类并实例化,主要包括我们自定义的@Controller,@Service等注解。
上一篇下一篇

猜你喜欢

热点阅读