Read the SpringBoot source code with me (1)-Initialization of SpringApplication

Preface

I have never systematically studied the source code. Except for a separate Mybatis demo, I studied the source code of MyBatis. Basically, when you encounter a problem, you can look at the source code to solve the problem, and then nothing happens. At that time, I was still looking at MyBatis. I learned what Mybatis did when it was initialized. Then when we called the interface, he matched the logic of Mapper. But after so long, I still remember very few. So if you are free recently, just look at the SpringBoot source code and write a blog to record it. No matter how much you can watch, you don’t need money for blogging. See how much you write, and come back to the blog later to help you remember.
Note: In fact, what may be studied later is not only the source code of SpringBoot. As we all know, SpringBoot is just a box that integrates other frameworks and middleware. Also, my English is not very good, and I hope to correct any misunderstandings when reading the notes.

Build a simple SpringBoot demo

Directly build a very simple demo to help read the source code, build a Maven project, and introduce dependencies:

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

Here I added jdbc, so configure the data source in application.yml:

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong
    username: root
    password: 123456

Finally, write a startup class:

@RestController
@SpringBootApplication
public class MyApplication {
    @Autowired
    JdbcTemplate jdbcTemplate;

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

    @GetMapping("list")
    public String list(){
        String sql = "select * from user";
        List<User> userList = jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(User.class));
        return JSONUtil.toJsonStr(userList);
    }
}

In this way, a springBoot project has been written, and by the way, I wrote an interface to check user information. Visit http://localhost:8080/list to see the effect:
Insert picture description here

Start research

Next we began to study the source code of SpringBoot:

Program entry

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

As we all know, the entry point of java code is the main method, and there is a line of code in main. SpringApplication.run
Let's take a look at the run method first.

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified source using default settings.
	 * 	大意就是:这个一个静态方法,可以用来以默认的配置和指定的source来启动SpringApplication
	 * @param primarySource the primary source to load    加载的主要source
	 * @param args the application arguments (usually passed from a Java main method) 应用程序参数(通常从Java主方法传递)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

From the comments, we know that this method is used to start SpringApplication by default configuration, and then you can specify the source, enter some parameters, and return a ConfigurableApplicationContext, that is, after our project is started, you can get a ConfigurableApplicationContext. As for what this Context has, can What are you doing? I don’t know for the time being, and I haven’t used it before.
Then chase down:

/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return `new SpringApplication(primarySources).run(args)`;
	}

Hey, the difference from the previous run is that the parameter is an array, which means that when we start the project, we can specify multiple sources. As for what the sources are for, don’t worry about it, continue new SpringApplication(primarySources).run(args), slower here, first create an instance and then run With args, let's look at the process of new first:

Initialize the SpringApplication instance

/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * 创建一个新的SpringApplication实例,application contenxt将通过指定的sources来加载实例(beans);
	 *  SpringApplication class-level 文档有更详细描述,实例可以在调用run()之前定制化。
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #SpringApplication(ResourceLoader, Class...)
	 * @see #setSources(Set)
	 */
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

It is said that the class-level of SpringApplication has detailed instructions. If there are too many comments, I will not post it. You can read it by yourself. The large series of comments above are pretty big, but I have a general look.

  • In most cases, we start the application by calling run directly:
    in this waySpringApplication.run(MyApplication.class, args);
  • In order to configure a SpringApplication instance more, we can start it ourselves after instantiation:
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
  • There are other kinds, so I won't list them here, let's talk about them.

Then go:

/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;   //前面传过来是null
		Assert.notNull(primarySources, "PrimarySources must not be null"); //必须制定sources,用了断言,get
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 
		//WebApplicationType是一个枚举,从方法名上大致猜是通过classpath来设置应用的类型吧。
		this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

WebApplicationType

Still look at the WebApplicationType class:

/**
 * An enumeration of possible types of web application.
 *
 * @author Andy Wilkinson
 * @author Brian Clozel
 * @since 2.0.0
 */
public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 *  应用不是web应用也不是嵌入的web服务
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 *  该应用程序应作为基于Servlet的Web应用程序运行,将启动一个嵌入的servlet web服务
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 * 这里没懂,reactive是什么意思?反应性的,百度了下都感觉和spring cloud相关,云?微服务?
	 */
	REACTIVE;

	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

	static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
		if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.SERVLET;
		}
		if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.REACTIVE;
		}
		return WebApplicationType.NONE;
	}

	private static boolean isAssignable(String target, Class<?> type) {
		try {
			return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
		}
		catch (Throwable ex) {
			return false;
		}
	}

}

I don’t understand what REACTIVE means here. Let’s look at what type of configuration we are currently configuring. Under debug:
Debug first solves the ClassLoader problem. We see the null passing through the place where the ClassLoader passes, so we chased the code. Take a look at the getDefaultClassLoader method: In
Insert picture description here
other words, we can also talk about springBoot spring用的默认ClassLoader是AppClassLoader, and then look at the question just now.
Insert picture description here
At this point, we know that our current application is a servlet reference. Then look at:

ApplicationContextInitializer

/**
	 * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
	 * {@link ApplicationContext}.
	 * @param initializers the initializers to set
	 */
	public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>(initializers);
	}

Set up some initialization things (initializer?? translated), the parameter is a collection, and the collection element inherits the ApplicationContextInitializer. Let's first look at what ApplicationContextInitializer is, and look at the input parameters.

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
 * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
 * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
 *
 * <p>{@code ApplicationContextInitializer} processors are encouraged to detect
 * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
 * implemented or if the @{@link org.springframework.core.annotation.Order Order}
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @param <C> the application context type
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

English is not good, and I don’t know if I look at it to the effect. ApplicationContextInitializer is a callback interface used after ConfigurableApplicationContext.refresh() is executed. It should be used to dynamically change the configuration. Just like the cache we usually write, and then update the interface when there is a change.
Take a look at his implementation:
Insert picture description here
so many, don't mess with it, take a look at the entry just now: ((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)get a spring factory instance? ? Follow up,

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();  //应该就是AppClassLoader了
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

Debug looked at it and loaded 7 initializers.
Insert picture description here

loadFactoryNames

Load the factory name first? There are also notes that make sure the name is unique.

/**
	 * Load the fully qualified class names of factory implementations of the
	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
	 * class loader.
	 * 用给的ClassLoader从FACTORIES_RESOURCE_LOCATION中加载出工厂的实现类的全类名
	 * @param factoryType the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading resources; can be
	 * {@code null} to use the default
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

Take a look at loadSpringFactories:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

Insert picture description here
Insert picture description here

It turned out to be here spring-boot-2.2.5.RELEASE.jar!/META-INF/spring.factories,, go find and see:
Insert picture description here

After reading the following source code, I think there is a problem here. Obviously there are only 5 initializers, but 7 are loaded. Then I checked it. The original URL is two. The other loaded configuration file is in spring-boot-autoconfigure- 2.2.5. RELEASE.jar!/META-INF/spring.factories. This adds up to exactly 7. [Manually cover your face]
Insert picture description hereInsert picture description here
Insert picture description here

There are too many, don't care, anyway, now I know that now I have loaded this bunch of names into memory:
Insert picture description here
Insert picture description here

createSpringFactoriesInstances

Now that the names are set up, continue to createSpringFactoriesInstances:

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

It’s worth seeing here, the object is instantiated by classLoader+full class name:

  • Class<?> instanceClass = ClassUtils.forName(name, classLoader); 先拿到Class。
  • Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); Get the constructor object.
  • T instance = (T) BeanUtils.instantiateClass(constructor, args); Create an instance,

Directly call Niubi, this is not seen before.

Finally, the AnnotationAwareOrderComparator is sorted in order. I won't repeat the AnnotationAwareOrderComparator here. You can go and see for yourself. It is a sorting, annotating a bunch of Barabara, what is for better.

setListeners

The parameter (Collection) getSpringFactoriesInstances(ApplicationListener.class)logic is the same as above, so I won't look at it and let's see what is returned.
Insert picture description here
I don't understand, it's the implementation of some listeners anyway.
At this point, the initialization of a SpringApplication is complete, let's see how the application is up and running.

Portal

Read SpringBoot source code with me (two)-SpringApplication startup

Guess you like

Origin blog.csdn.net/qq1049545450/article/details/112957659