Spring Boot是由Pivotal团队提供的快速开发框架,基于SpringMVC经过注解+内置Http服务器如:tomcat-embed-core,简化了XML配置,快速将一些经常使用的第三方依赖整合(经过Maven继承依赖关系),最终实现以Java应用程序的方式进行执行。java
starter包含了搭建项目快速运行所需的依赖。它是一个依赖关系描述符的集合。当应用须要一种spring的其它服务时,不须要粘贴拷贝大量的依赖关系描述符。例如想在spring中使用redis,只须要在项目中包含 spring-boot-starter-redis 依赖就可使用了,全部的starters遵循一个类似的命名模式:spring-boot-starter-,在这里是一种特殊类型的应用程序。该命名结构能够帮你找到须要的starter。不少IDEs集成的Maven容许你经过名称搜索依赖。react
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
这是一个简单的SpringBoot的启动类实现,下文章将围绕这个demo进行扩展分析。web
如下是注解@SpringBootApplication的源码实现redis
@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 {
能够发现它是由众多注解组合而成的,下面具体分析下这里每一个注解所起到的做用。spring
@Retention Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:tomcat
<context:component-scan>
,可以使用basePackages属性指定要扫描的包,及扫描的条件。若是不设置则默认扫描@ComponentScan注解所在类的同级类和同级目录下的全部类,因此咱们的Spring Boot项目,通常会把入口类放在顶层目录中,这样就可以保证源码目录下的全部类都可以被扫描到。@EnableAutoConfiguration 这个注解是让Spring Boot的配置可以如此简化的关键性注解。我把EnableAutoConfiguration的实现端上来了,你们来鉴赏一下!服务器
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
关于注解的话题就先谈到这里,下面开启撸代码环节。并发
查看SpringApplication的源代码能够发现SpringApplication的启动由两部分组成:app
源码以下:框架
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader;//一、初始化资源加载器 Assert.notNull(primarySources, "PrimarySources must not be null");//二、断言资源加载类不能为 null this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//三、初始化加载资源类集合并去重 this.webApplicationType = deduceWebApplicationType();//四、 推断应用类型是Standard仍是Web setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));//五、设置应用上下文初始化器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//六、设置监听器 this.mainApplicationClass = deduceMainApplicationClass();//七、推断应用入口类 }
下面将针对源码中的重要实现进行详细的分析。
ResourceLoader接口,在 Spring 中用于加载资源,经过它能够获取一个Resouce 对象。使用spring的朋友都知道它加载资源的方式由多种,下面就挑两个经常使用的继承ResourceLoader的接口与实现类提及。
上面介绍过该类经过实现 ResourceLoader 接口实现了加载单个资源的功能。它的子类经过继承它来实现具体的资源访问策略。下面来探究下该类如何加载单个资源:
public Resource getResource(String location) {//这里是三种识别location加载出Resource的方式。 Assert.notNull(location, "Location must not be null"); //1.先看有没有自定义的ProtocolResolver,若是有则先根据自定义的ProtocolResolver解析location获得Resource for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } //2.根据路径是否匹配"/"或"classpath:"来解析获得ClassPathResource if (location.startsWith("/")) { return getResourceByPath(location); }else if (location.startsWith(CLASSPATH_URL_PREFIX)) {//classpath return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); }else { try { //默认传入的location是一个URL路径,加载获得一个UrlResource URL url = new URL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { // 若是以上三种状况都不知足,则按照“/”来处理 return getResourceByPath(location); } } }
扫盲:ProtocolResolver是解析location的自定义拓展类,有了它咱们才能随意传入不一样格式的location,而后根据对应的格式去解析并得到咱们的Resource便可。
扩展:在Spring容器初始化过程当中,咱们能够自定义一个类实现ProtocolResolver接口,而后实现该resolve方法,就能够解析特定的location获得Resoure。
该接口继承了ResourceLoader接口,在其基础上增长了同时对多个资源的访问功能。
public interface ResourcePatternResolver extends ResourceLoader { String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; // 例如使用 ant 风格的路径,匹配路径下的多个资源 Resource[] getResources(String locationPattern) throws IOException; }
PathMatchingResourcePatternResolver是 ResourcePatternResolver 接口的直接实现类,它是基于模式匹配的,默认使用AntPathMatcher 进行路径匹配,它除了支持 ResourceLoader 支持的前缀外,还额外支持 “classpath*” ,下面查看源码看看它如何实现对多个资源的访问。
public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); //首先判断资源路径是不是类路径下的资源(以 “classpath*:” 开头) if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // 经过 getPathMatcher 方法取得 PathMatcher ,默认只有 AntPathMatcher 一个实现类 // 经过 isPattern 方法判断路径是否容许存在多个匹配的资源(路径中包含 “*” 或 “?”) if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { //找到全部匹配路径(ant 风格)的资源 return findPathMatchingResources(locationPattern); } else { //经过类加载器查找 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } else { int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(':') + 1); // 判断资源路径 ":" 以后的部分是否包含 "*" 或 "?" if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // a file pattern return findPathMatchingResources(locationPattern); } else { // 若不存在表示是单个资源,则经过从构造函数传入的 ResourceLoader 取得 return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } }
扩展:查看源码发现ApplicationContext接口也继承了 ResourcePatternResolver 接口,说明它也集成了对对单个或多个资源的访问功能。
Resource res = ctx.getResource("some/resource/path/myTemplate.txt);
Spring采用和 ApplicationContext 相同的策略来访问资源。也就是说:
也就是说 ApplicationContext 将会肯定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来,这就体现了策略模式的优点。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext>{ void initialize(C applicationContext); }
}方法得到ApplicationContextInitializer接口所有实现类的完整名称,最后经过反射的机制得到ApplicationContextInitializer实现类。
ApplicationContextInitializer.class)方法得到实现类
listeners成员变量,是一个ApplicationListener<?>类型对象的集合。能够看到获取该成员变量内容使用的是跟成员变量initializers同样的方法,只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { void onApplicationEvent(E event); }
这个接口基于JDK中的EventListener接口,实践了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型须要是ApplicationEvent类型的子类,而这个类一样是继承自JDK中的EventObject类。
该方法经过构造一个运行时异常,经过异常栈中方法名为main的栈帧来获得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; }
源码以下:
public ConfigurableApplicationContext run(String... args) { //一、计时监控类 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 二、初始化应用上下文和异常报告集合 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 三、设置系统属性java.awt.headless的值,默认值为:true(没有图形化界面) configureHeadlessProperty(); // 四、建立全部 Spring 运行监听器并发出开始执行的事件 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { // 五、初始化默认应用参数类 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // 六、根据SpringApplicationRunListeners和应用参数来准备 Spring 环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); // 七、准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体 Banner printedBanner = printBanner(environment); // 八、建立Spring上下文 context = createApplicationContext(); // 九、准备异常报告器 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 十、Spring上下文前置处理 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 十一、刷新Spring上下文 refreshContext(context); // 十二、Spring上下文后置处理 afterRefresh(context, applicationArguments); // 1三、中止计时监控类 stopWatch.stop(); // 1四、输出日志记录执行主类名、时间信息 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } // 1五、发布应用上下文启动完成事件 listeners.started(context); // 1六、执行全部 Runner 运行器 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { // 1七、发布应用上下文就绪事件 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } // 1八、返回应用上下文 return context; }
下面针对run方法中提到的一些重要步骤进行阐述:
启动方法以下:
public void start() throws IllegalStateException { start(""); } public void start(String taskName) throws IllegalStateException { if (this.currentTaskName != null) { throw new IllegalStateException("Can't start StopWatch: it's already running"); } this.currentTaskName = taskName; this.startTimeMillis = System.currentTimeMillis(); }
你能够看到它传入了一个空字符串给当前任务做为任务名称,而后记录当前Spring Boot应用启动的开始时间。而且它会判断当前任务名是否存在,用于保证Spring Boot应用不重复启动。
这里仅初始化一个应用上下文对象context和一个空的异常报告集合,具体用途看下文。
configureHeadlessProperty的方法实现以下:
private void configureHeadlessProperty() { System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty( SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); } private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
该方法设置了一个名为java.awt.headless的系统属性,观其源码能够发现它给属性设值System.setProperty(),而它的值来源于System.getProperty(),这并不奇怪,由于System中getProperty()有2个重载方法,其中getProperty()有单参和双参两个方法,这里调用的是双参数方法,该方法在没有的时候会返回一个调用者指定的默认值,因此这里先获取后设置。这个设置的目的是保证即便没有检测到显示器(服务器不须要显示器),也容许程序启动。
实现以下:
//根据args获取全部SpringApplicationRunListeners监听器 SpringApplicationRunListeners listeners = getRunListeners(args); //这段代码你们应该已经熟悉了,获取SpringApplicationRunListeners扩展 private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class}; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
经过Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};类型加载对应的监听器,并建立SpringApplicationRunListener实例
下面的内容和以前实例化初始化器的流程是同样,经过getSpringFactoriesInstances方法从spring.factories中获取与SpringApplicationRunListener.class相关的实例类名列表。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
args是启动Spring应用的命令行参数,该参数能够在Spring应用中被访问。
如:--server.port=9000
建立并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile(其做用就是指定激活的配置文件,能够区分环境来加载不一样的配置)),并遍历调用全部的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
源码以下:
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { //获取或建立环境(存在就直接返回,不存在建立一个再返回) ConfigurableEnvironment environment = getOrCreateEnvironment(); //配置环境:配置PropertySources和activeProfiles configureEnvironment(environment, applicationArguments.getSourceArgs()); //listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。 listeners.environmentPrepared(environment); //将环境绑定到SpringApplication bindToSpringApplication(environment); //若是非web环境,将环境转换成StandardEnvironment if (this.webApplicationType == WebApplicationType.NONE) { environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); } //配置PropertySources对它本身的递归依赖 ConfigurationPropertySources.attach(environment); return environment; }
prepareEnvironment的做用:加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties)等;初始化日志系统。
该功能仅供自娱自乐,不作过多解读,看源码即是。
private Banner printBanner(ConfigurableEnvironment environment) { if (this.bannerMode == Banner.Mode.OFF) { return null; } ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(getClassLoader())); SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter( resourceLoader, this.banner); if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger); } return bannerPrinter.print(environment, this.mainApplicationClass, System.out); }
源码以下:
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"; public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"; public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext"; protected ConfigurableApplicationContext createApplicationContext() { // 先判断有没有指定的实现类 Class<?> contextClass = this.applicationContextClass; // 若是没有,则根据应用类型选择 if (contextClass == null) { try { //根据webApplicationType的类型去反射建立ConfigurableApplicationContext的具体实例。 switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_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); }
createApplicationContext()该方法的逻辑较为简单,共两个分支:
这一步的逻辑和实例化初始化器和监听器的同样,都是经过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化全部的异常处理类。
源码以下:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //设置容器环境,包括各类变量 context.setEnvironment(environment); //设置上下文的 bean 生成器和资源加载器 postProcessApplicationContext(context); //执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例) applyInitializers(context); //触发全部 SpringApplicationRunListener 监听器的 contextPrepared 事件方法 listeners.contextPrepared(context); //记录启动日志 if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // 添加特定于引导的单例bean context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } // 加载全部资源 Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); //加载咱们的启动类,将启动类注入容器 load(context, sources.toArray(new Object[0])); //触发全部 SpringApplicationRunListener 监听器的 contextLoaded 事件方法 listeners.contextLoaded(context); }
这块会对整个上下文进行一个预处理,好比触发监听器的响应事件、加载资源、设置上下文环境等等。
源码以下:
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { //向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文 context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
这块主要作了两件事:
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { }
该方法没有实现,能够根据须要作一些定制化的操做。
源码以下:
public void stop() throws IllegalStateException { if (this.currentTaskName == null) { throw new IllegalStateException("Can't stop StopWatch: it's not running"); } long lastTime = System.currentTimeMillis() - this.startTimeMillis; this.totalTimeMillis += lastTime; this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime); if (this.keepTaskList) { this.taskList.add(this.lastTaskInfo); } ++this.taskCount; this.currentTaskName = null; }
该方法主要是作计时监听器中止操做,并统计一些任务执行信息。
源码以下:
if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); }
用于打印主类信息和时间信息。
源码以下:
public void started(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.started(context); } }
执行全部SpringApplicationRunListener实现的started方法。
Runner 运行器用于在服务启动时进行一些业务初始化操做,这些操做只在服务启动后执行一次。
Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口CommandLineRunner、ApplicationRunner
对比:
相同点
不一样点
源码以下:
private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); // 从Spring容器中获取ApplicationRunner实现类 runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); // 从Spring容器中获取CommandLineRunner实现类 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); //排序 AnnotationAwareOrderComparator.sort(runners); //回调 for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }
ApplicationRunner仍是CommandLineRunner,都是在应用启动完成后执行一次业务初始化代码,达到的效果也相似,因为ApplicationRunner的方法参数是ApplicationArguments对象,使用起来更加方便,因此更推荐使用。
源码以下
public void running(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.running(context); } }
触发全部 SpringApplicationRunListener 监听器的 running 事件方法。
SpirngBoot启动流程到这里就分析完了、接下来的文章会针对SpringBoot的特定场景进行分析,但愿每一个看到这篇文章的朋友都能有所收获,同时也欢迎你们多提宝贵意见共同成长。