Conditional note for spring-boot Underlying exploration

Keywords: Programming Spring REST

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);
	}

Posted by Dj_Lord on Thu, 30 Apr 2020 18:33:50 -0700