stay Previous We talked about the implementation of conditional annotations.Finally, there's the question, "Why doesn't spring inject beans into classes that use conditional annotations when conditions don't work?"With this in mind, today we'll look at the spring-boot startup process in which our beans are injected with conditional annotations.
Let's look at the critical steps to start spring-boot
1. Start point of the program
//1. Entrance SpringApplication.run(SampleTomcatJspApplication.class, args); //2. Follow the entrance and we're here public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
When we get here, we see that one is actually creating a SpringApplication object and executing the run method.Let's first look at what happened when we created the corresponding
2. Construction of SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //Resource Loader Assignment this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //Get the corresponding web application type this.webApplicationType = WebApplicationType.deduceFromClasspath(); //Set the ApplicationContextInitializer, which also becomes a callback function setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //Set up listeners (refer to observer mode) setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //Gets the class name corresponding to the main function this.mainApplicationClass = deduceMainApplicationClass(); }
There are three main ways to construct a function. The first is to get the type of web application, which determines the creation of containers behind us.Settings for the ApplicationContextInitializer, which is an extension point, allow us to do some environment settings mainly before the container refreshes.Finally, the registration of listeners is also an extension point
3. run method for SpringApplication
Now that SpringApplication was created, we can tell which type of web application we are using.So let's move on to the run method in the SpringApplication object
StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); //1. Create Containers context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //2. Refresh Container refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } 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;
The rest is not unimportant, but what we are now focusing on is where conditional annotations are used during container creation.So let's start by focusing on the process of creating and refreshing containers and see what's going on in both processes.
3.1 Create Configurable Application Context
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
Create containers based on the type of web application we get.The tracking code knows that the web type here is SERVLET, so the corresponding container created is the AnnotationConfigServletWebServerApplicationContext.Let's see what happens inside the parameterless constructor of the AnnotationConfigServletWebServerApplicationContext class
3.2 Instantiate AnnotationConfigServletWebServerApplicationContext
public AnnotationConfigServletWebServerApplicationContext() { // AnnotatedBeanDefinitionReader initializes BeanDefinition read class this.reader = new AnnotatedBeanDefinitionReader(this); //ClassPathBeanDefinitionScanner Initial BeanDefinition Scan Class this.scanner = new ClassPathBeanDefinitionScanner(this); }
Students who have seen the spring source before are familiar with these two classes.We don't care about scan classes for the first time. Let's see what AnnotatedBeanDefinitionReader does.
3.2.1 AnnotatedBeanDefinitionReader Creation
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); this.registry = registry; //See if this place has something to do with the conditional notes? this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); //Register Comment Post Processor (this is the key point) AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }
The code here has something to do with our topic today.The ConditionEvaluator class is used to evaluate the condition annotation labels.In other words, it is the result of his behavior that determines whether a class should be injected.
AnnotationConfigUtils.registerAnnotationConfigProcessors() is so important that we have to look inside to see what's inside
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) { DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8); // Look at what this is, to handle @Configuration annotations related, and the answer to our article is in this class if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } //Similarly, this is used to handle @Autowired, etc. if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor. if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor. if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, AnnotationConfigUtils.class.getClassLoader())); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME)); } return beanDefs; }
This method is important, everyday notes can be used.This is where they get processed. Let's go directly to ConfigurationClassPostProcessor#postProcessBeanFactory(), track this method and get into processConfigBeanDefinitions(), which has a piece of code in processConfigBeanDefinitions()
do { parser.parse(candidates);//This is where you parse your notes and keep track of them parser.validate(); } while (!candidates.isEmpty());
Track this method and you'll end up here
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { // Use conditionEvaluator to determine if a class can be injected.That's why our conditional notes work if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass, filter); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); }