SpringBoot

437 阅读3分钟

starter原理是什么?怎么实现的?

starter其实就是将需要依赖的jar包集合在里面,然后自动配置。这里主要用到的是springboot的自动配置功能。自己完全可以实现一个starter。
www.jianshu.com/p/bbf439c8a…

springboot的自动配置是怎么实现的?

  1. 启动类上都会加@SpringBootApplication,这个注解其实可以拆分为三个注解 SpringBootConfiguration,EnableAutoConfiguration,ComponentScan,其中EnableAutoConfiguration就是用于启动自动配置的;
  2. @Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。这一步将所有需要自动配置的类注入到spring容器。也就拿到了所有需要自动配置的类。
  3. 具体的XxxxAutoConfiguration是怎么拿到application.properties中我们的配置呢,例如:DataSourceAutoConfiguration
Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration 

@EnableConfigurationProperties({DataSourceProperties.class})将DataSourceProperties注入到spring容器,下面看下DataSourceProperties

@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean 

@ConfigurationProperties将prefix = "spring.datasource"的配置参数都加载到对应的属性中,然后spring就拿到了一个配置好了的DataSourceProperties,再通过DataSourceAutoConfiguration把这些属性构造成DataSource所需要的所有bean注入到spring容器中。

Springboot 是如何省去web.xml?

当实现了Servlet3.0规范的容器(比如tomcat7及以上版本)启动时,通过SPI扩展机制自动扫描所有已添加的jar包下的META-INF/services/javax.servlet.ServletContainerInitializer中指定的全路径的类,并实例化该类,然后回调META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的ServletContainerInitializer的实现类的onStartup方法。 如果该类存在@HandlesTypes注解,并且在**@HandlesTypes**注解中指定了我们感兴趣的类,所有实现了这个类的onStartup方法将会被调用。 下面看SpringServletContainerInitializer的代码,主要是调用WebApplicationInitializer的实现类的onStartup方法

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer 

WebApplicationInitializer的Springboot实现类SpringBootServletInitializer

public void onStartup(ServletContext servletContext) throws ServletException {
		// Logger initialization is deferred in case an ordered
		// LogServletContextInitializer is being used
		this.logger = LogFactory.getLog(getClass());
		WebApplicationContext rootAppContext = createRootApplicationContext(
				servletContext);
		if (rootAppContext != null) {
			servletContext.addListener(new ContextLoaderListener(rootAppContext) {
				@Override
				public void contextInitialized(ServletContextEvent event) {
					// no-op because the application context is already initialized
				}
			});
		}
		else {
			this.logger.debug("No ContextLoaderListener registered, as "
					+ "createRootApplicationContext() did not "
					+ "return an application context");
		}
	}

	protected WebApplicationContext createRootApplicationContext(
			ServletContext servletContext) {
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		builder.main(getClass());
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(
				new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
		builder = configure(builder);
		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty() && AnnotationUtils
				.findAnnotation(getClass(), Configuration.class) != null) {
			application.addPrimarySources(Collections.singleton(getClass()));
		}
		Assert.state(!application.getAllSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");
		// Ensure error pages are registered
		if (this.registerErrorPageFilter) {
			application.addPrimarySources(
					Collections.singleton(ErrorPageFilterConfiguration.class));
		}
		return run(application);
	}

run方法就是我们@springapplication的main方法的run一样,一般我们用外置tomcat启动需要自定义一个ServletInitializer,这样就能找到主类了。

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Bootstrap.class);
    }

}

下面我们看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;
		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);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//这个地方启动spring容器,并且启动了webSevlet容器内置tomcat,并将dispatchserverlet注册到容器中了,具体怎么注入的看DispatcherServletAutoConfiguration
			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;
	}

refreshContext(context)启动spring容器,并且启动了webSevlet容器内置tomcat,并将dispatchsevlet注册到容器中了,具体怎么注入的看DispatcherServletAutoConfiguration。dispatchsevlet绑定到tomcat是在创建tomcat实例的时候,也是在onRefresh()方法中。

总结一下:

  1. 外部tomcat启动
  2. 回调ServletContainerInitializer.onStartup(ServletContext)
  3. 调用所有WebApplicationInitializer的实现(SpringBootServletInitializer)
  4. 调用SpringApplication.run启动spring容器
  5. 将DispatcherServletRegistrationBean注册到spring容器,注意该类是ServletContextInitializer子类,onStartup方法中将servlet注册到ServletContext
  6. spring容器启动后,onRefresh方法中createWebServer(),该方法会调用所有的ServletContextInitializer的回调方法onStartup,从而将dispatchServlet注册到ServletContext。具体调用链:
    onRefresh()-->createWebServer()-->getSelfInitializer().onStartup(servletContext)-->selfInitialize()-->getServletContextInitializerBeans()-->new ServletContextInitializerBeans(getBeanFactory()) --> addServletContextInitializerBeans

内部tomcat启动就是直接从第4步开始。

blog.csdn.net/choubayan43… www.cnblogs.com/hello-shf/p…
blog.csdn.net/mnicsm/arti…

SpringBoot怎么将application.properties加载进内存的?

在容器创建之前会发布的ApplicationEnvironmentPreparedEvent,ConfigFileApplicationListener处理事件的时候会把application.yml配置信息加载进来