我们说Spring Boot简化了Spring的开发,可以根据导入的starter包自动向Spring容器中注册Bean。在Spring Boot之前,我们要向Spring容器中注册Bean,首先需要配置xml,如果是Web容器,则将spring.xml位置配置到Spring 提供的监听器中,由Spring解析注册Bean,否则则使用new ClassPathXmlApplicationContext("/spring.xml")或者new AnnotationConfigApplicationContext()等容器读取配置解析注册Bean。在Spring Boot中,只需要一个包含main方法的启动类即可,在运行该main方法时,会读取Bean,并且向Spring 容器中注册Bean。如下为Spring Boot最简单的启动类:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
如上代码所示,仅仅的四行代码,我们就开发了一个Spring MVC的应用,我们在运行该类的时候,会自动加载和注册对应的Bean,下面我们讲述,Spring Boot是如何加载和注册Bean的。首先我们从@SpringBootApplication说起,查看源码如下:
@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)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
我们先不管注解中的属性,但是可以看出来这是一个复合注解,由@SpringBootConfiguration,@EnableAutoConfiguration和@ComponentScan三个注解构成。@ComponentScan注解我们都熟悉,是Spring中的一个元注解,用于配置Spring要扫描的包。因此我们需要继续查看另外两个注解的源码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
是不是又看到了我们熟悉的Spring的注解@Configuration和@Import,我们查看@AutoConfigurationPackage注解源码可以发现其元注解依然是@Import注解:@Import(AutoConfigurationPackages.Registrar.class)。只要熟悉Spring中这些注解的使用,很容易就了解@SpringBootApplication注解的功能。其实Spring Boot中的注解基本上都是对Spring中注解的增强。熟悉完注解之后我们开始查看run方法:SpringApplication.run(Application.class, args);
//启动调用run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
//调用重载的run方法
return run(new Class<?>[] { primarySource }, args);
}
//重载的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//创建SpringApplication对象并且调用run方法
return new SpringApplication(primarySources).run(args);
}
通过上面的方法,我们发现run方法返回一个ConfigurableApplicationContext实例,如果对Spring熟悉,则很容易理解这个实例,也就是说,我们通过运行run方法创建了一个Spring 容器,并且可以返回,代码如下所示:
public static void main(String[] args) {
//返回ApplicationContext 容器
ApplicationContext context = SpringApplication.run(Application.class, args);
//从容器中获取实例
Application application = context.getBean(Application.class);
//遍历容器中的实例,并且打印Bean的名称和类型
for(String beanName : context.getBeanDefinitionNames()) {
System.out.println("注册到Spring容器中的Bean名称为 "+beanName +",类型为 " +context.getBean(beanName));
}
}
上面的代码最后一部分没什么用处,只是演示了Spring Boot可以通SpringApplication类的run方法创建一个Spring容器,而真正执行逻辑的方法时在run(args)方法中,该方法用于运行一个Spring应用并且创建和刷新一个新的ApplicationContext实例。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
//获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//SpringBoot运行开始监听
listeners.starting();
try {
//准备Spring Boot运行环境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
Banner printedBanner = printBanner(environment);
//创建ApplicationContext容器
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
//准备容器
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
//刷新容器
refreshContext(context);
//刷新容器后处理
afterRefresh(context, applicationArguments);
//SpringBoot运行成功监听
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);
}
}
Spring Boot运行经历了一系列的步骤,包括监听器,环境的准备,容器的创建,容器的准备与刷新等,本章主要关注的是容器的创建于容器的刷新,这两步是向Spring中注册Bean的核心步骤。首先看createApplicationContext()方法,该方法用于创建Spring容器:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
//如果Context为空
if (contextClass == null) {
try {
//获取容器Class实例
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);
}
我们需要关注的代码为try块中的代码,里面有两个常量DEFAULT_WEB_CONTEXT_CLASS 和 DEFAULT_CONTEXT_CLASS)他们是在SpringApplication类中声明的如下:
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
好了 现在比较清晰了,该方法做的就是通过反射创建一个ApplicationContext实例,而平时我们是直接通过new创建的ApplicationContext实例,使用的方式是以注解容器。
ApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext();
ApplicationContext context = new AnnotationConfigApplicationContext();
创建容器之后,就要刷新容器,向容器中注册Bean,也就是refreshContext(context)方法:
private void refreshContext(ConfigurableApplicationContext context) {
//在此调用refresh方法
refresh(context);
//如果注册勾子执行勾子方法
if (this.registerShutdownHook) {
try {
//执行勾子方法
context.registerShutdownHook();
}catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
//真正refresh的方法
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//调用AbstractApplicationContext的refresh()方法
((AbstractApplicationContext) applicationContext).refresh();
}
看到上面我们又看到重点了,最终调用AbstractApplicationContext的refresh()方法,是不是有点熟悉了,因为我们使用AnnotationConfigApplicationContext的构造方法实例化ApplicationContext的时候,构造方法中正是调用了这个方法。
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}
通过上面代码我们看到了,最终主要的代码逻辑全部都又走到了Spring框架中,这里我们只讲述Spring Boot入口Spring容器创建和注册的流程,后续我们会继续讲解Spring Boot是如何完成自动注册的。