SpringBoot原理分析

导读:本篇文章讲解 SpringBoot原理分析,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、SpringBootApplication注解学习

 1、SpringBoot启动方法

    在启动类的入口函数上,添加了@SpringBootApplication注解。

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
    	SpringApplication app = new SpringApplication(Application.class);
    	app.setDefaultProperties(PropertiesUtils.getInstance().getProperties());
    	app.run(args);
    }
}

 2、SpringBootApplication注解

    该注解是一个组合注解,即@SpringBootApplication = @SpringBootConfiguration(@Configuration) + @EnableAutoConfiguration + @ComponentScan。代码如下:

@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, attribute = "exclude")
	Class<?>[] exclude() default {};
	
	@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
	String[] excludeName() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

 3、SpringBootConfiguration注解

    该注解本质上是一个@Configuration注解,除了具有@Configuration注解的功能外,还表明该注解的类是一个SpringBoot应用。
    @Configuration注解注解的作用,主要是定义配置类,可以替换XML配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。其他具体用法请参考相关内容学习,此处不再展开。
    注解代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

 4、ComponentScan注解

    @ComponentScan注解会自动扫描指定包(如果不指定,那么默认扫描当前类路径以及子路径下的所有类)下的全部标有@Component(包括@Service、@Repository、@Controller等子注解)的类,并注册成bean。代码不再贴出。

 5、EnableAutoConfiguration注解

    @EnableAutoConfiguration注解用于自动载入应用程序所需要的所有Bean,这依赖于SpringBoot在类路径中的查找。主要依靠了@Import注解实现该功能。

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

    通过上面代码可以发现包括了两个关键注解@AutoConfigurationPackage和@Import。其中@AutoConfigurationPackage用于把包下所有被注解的类注册到AutoConfigurationPackages类中。@Import注解是实现自动注入的核心代码,下面专门介绍。

 5、Import注解

    @Import注解的作用是提供了注入类的几种实现方法,主要有以下几种:
    1. 声明一个bean
    2. 导入@Configuration注解的配置类
    3. 导入ImportSelector的实现类
    4. 导入ImportBeanDefinitionRegistrar的实现类
    在@EnableAutoConfiguration注解中使用的是@Import的第三种方法,即ImportSelector实现方法。该方法的详细使用方法请自行查阅相关文档或博客。该方法的核心代码是ImportSelector接口的selectImports方法。在@EnableAutoConfiguration注解中该方法的具体实现是在AutoConfigurationImportSelector类中完成,即在@EnableAutoConfiguration注解中通过@import注解引入EnableAutoConfigurationImportSelector类,EnableAutoConfigurationImportSelector类又继承了AutoConfigurationImportSelector类,并在该类中实现了selectImports方法,该方法中实现了相关类的自动加。代码如下:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			configurations = removeDuplicates(configurations);
			configurations = sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return configurations.toArray(new String[configurations.size()]);
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}

    上述代码中selectImports方法实现了相关类的自动加载,具体加载实现逻辑后续模块单独介绍,该方法的调用时机请参考
在这里插入图片描述

二、方法执行过程

 1、入口

public static void main(String[] args) {
	SpringApplication app = new SpringApplication(Application.class);
	app.setDefaultProperties(PropertiesUtils.getInstance().getProperties());
	app.run(args);
}

 2、对象构建过程

    public SpringApplication(Object... sources) {
		initialize(sources);
	}
  2.1、构造函数-初始化方法

    该方法主要做了四件事,分别是:

  1. 判断是否是web环境
  2. 初始化ApplicationContextInitializer相关实例
  3. 初始化ApplicationListener相关实例
  4. 判断是否是main方法应用(启动)
    @SuppressWarnings({ "unchecked", "rawtypes" })
	private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
		this.webEnvironment = deduceWebEnvironment();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
 2.1.1 判断是否是web环境
    private boolean deduceWebEnvironment() {
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return false;
			}
		}
		return true;
	}
 2.1.2 判断是否是main方法应用(启动)
    private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}
 2.1.3 初始化实例对象

    主要是初始化了SpringApplication类的下列属性:

  1. private List<ApplicationContextInitializer<?>> initializers;
  2. private List<ApplicationListener<?>> listeners;
    初始化initializers和listeners对象过程中,核心方法的代码如下:
    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<String>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

    后续,根据反射创建实例对象不再继续分析。

 3、run方法运行过程

 3.1、 run方法代码如下:
    /**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			analyzers = new FailureAnalyzers(context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			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);
		}
	}

    其中,StopWatch对象用于执行代码的耗时检测。configureHeadlessProperty方法,是设置系统属性java.awt.headless,这里设置为true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能。这些代码与主流程关系不大,且代码比较简单,需要详细了解,可以自行查看和学习。

 3.2、 getRunListeners(args)方法

    该方法主要是获取运行时监听器EventPublishingRunListener(SpringApplicationRunListener的实现类)的封装对象。实际上,就是获取SpringApplicationRunListeners对象,该对象是一个SpringApplicationRunListener的集合,该类改造了SpringApplicationRunListener接口中的一些方法(实现批处理功能,本质上就是添加for循环处理,可以理解成包装类)。其中,SpringApplicationRunListener接口主要用于监听SpringApplication的run方法。通过SpringFactoriesLoader加载SpringApplicationRunListener(一个或多个),SpringApplicationRunListener的实现类必须声明一个接收SpringApplication实例和String[]数组的公有构造方法,每次SpringApplication的run方法执行,都会创建一个新的SpringApplicationRunListener实例。该方法的代码如下:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}

    在该方法中,通过getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)方法实现了SpringApplicationRunListener类型的对象加载,内部还是通过读取spring-boot.jar中META-INF/spring.factories文件中的配置,并筛选出符合要求的对象集合(EventPublishingRunListener对象),底层是通过SimpleApplicationEventMulticaster实现了事件广播等功能,具体用法,另外章节专门分析。

 3.3、 应用参数封装
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);

    该代码主要是实现参数的封装,把启动时的系统参数封装成了ApplicationArguments对象(真正的对象DefaultApplicationArguments),即它持有着args参数,就是main函数传进来的参数。具体内容,可以查看源码继续深入学习。

 3.4、 prepareEnvironment方法

    该方法准备运行的环境,比如开发环境dev,测试环境test,还是生产环境prd,然后根据环境解析不同的配置文件。具体代码如下:

    private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		listeners.environmentPrepared(environment);
		if (!this.webEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		return environment;
	}

    其中,getOrCreateEnvironment方法会创建ConfigurableEnvironment对象,如果是web程序,那么创建一个StandardServletEnvironment对象并返回,如果不是web程序,那么创建一个StandardEnvironment对象并返回。

configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);

    上述是环境配置代码,第一行是配置环境参数、Profile等。第二行是把该环境配置到上述得到的监听器中,即发布一个ApplicationEnvironmentPreparedEvent事件,其实底层最终是通过事件广播器,依次调用每个ApplicationListener对象的onApplicationEvent方法,该功能将来单独分析学习。

    if (!this.webEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
	.convertToStandardEnvironmentIfNecessary(environment);
		}
		return environment;

    上述是检查当前环境是否是Web环境,如果不是就返回StandardEnvironment对象,如果是就原样返回。

 3.5、 printBanner方法

    该方法主要是默认或者自定义系统启动时的打印字样,不属于主要流程,本处不做深入学习,将来单独分析(里面涉及了一些设计模式的应用,值得学习)。

 3.6、 createApplicationContext方法

代码如下:

    protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				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);
	}

    该方法主要是创建ConfigurableApplicationContext对象。当applicationContextClass属性为null时,会根据是否是web环境分别创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext或者org.springframework.context.annotation.AnnotationConfigApplicationContext对象。如果applicationContextClass属性不为null时,根据该Class创建对应的对象,并强转成ConfigurableApplicationContext对象。

 3.7、 FailureAnalyzers对象

    该对象用于分析故障并提供可以显示给用户的诊断信息。并在handleRunFailure方法中进行异常分析处理。

 3.8、 prepareContext方法

    代码如下:

    private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

		// Add boot specific singleton beans
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}

		// Load the sources
		Set<Object> sources = getSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[sources.size()]));
		listeners.contextLoaded(context);
	}

    其中,context.setEnvironment(environment)是为上下午设置环境;postProcessApplicationContext(context);是为设置上下文的beanNameGenerator和resourceLoader(如果SpringApplication有的话);applyInitializers(context)是为了对上下文进行初始化,底层其实是调用ApplicationContextInitializer对象的initialize方法;listeners.contextPrepared(context),该方法和之前的environmentPrepared方法时类似的,只不过EventPublishingRunListener的contextPrepared方法是个空实现而已。

    if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}

    上述代码,是实现日志打印功能。

context.getBeanFactory().registerSingleton("springApplicationArguments",applicationArguments);

    上述代码,是向上下文的beanFactory中注册一个singleton的实例bean,即ApplicationArguments对象。

    if (printedBanner != null) {
		context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
	}

    上述代码,是向上下文的beanFactory中注册一个singleton的实例bean,即PrintedBanner对象。

    Set<Object> sources = getSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[sources.size()]));

    上述代码,作用是注册启动类的bean定义,即调用SpringApplication.run(Application.class, args);的类。SpringApplication的load方法内会创建BeanDefinitionLoader的对象,并调用它的load()方法,最终通过BeanDefinitionLoader类的load方法实现类的注册,该方法中又通过this.annotatedReader.register(source);实现。其中,sources参数代表的即为调用SpringApplication.run(Application.class, args);的类。

  listeners.contextLoaded(context);
    @Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		this.initialMulticaster.multicastEvent(
				new ApplicationPreparedEvent(this.application, this.args, context));
	}

    上述代码中,调用contextLoaded方法,底层是调用了EventPublishingRunListener类的contextLoaded方法,该方法中通过创建ApplicationPreparedEvent事件对象,并广播出去,也就是调用ApplicationListener的onApplicationEvent方法。

 3.9、 refreshContext(context)方法

    代码如下:

    private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
    /**
	 * Refresh the underlying {@link ApplicationContext}.
	 * @param applicationContext the application context to refresh
	 */
	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}
@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

    通过上述代码,可以看出来,最终是调用了AbstractApplicationContext类(真正的实现类是EmbeddedWebApplicationContext)的refresh方法。该方法其实就是spring项目启动的一个核心方法,这里暂时不展开分析。其中,refresh方法中的onRefresh方法,最终调用了EmbeddedWebApplicationContext类的onRefresh方法,代码如下:

    @Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createEmbeddedServletContainer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start embedded container",
					ex);
		}
	}

    其中,createEmbeddedServletContainer方法实现了内嵌服务器的创建。

 3.9、 listeners.finished(context, null)方法

    该方法底层是调用了EventPublishingRunListener类的finished方法。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68923.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!