前言

  上篇:spring-boot-2.0.3不一样系列之shiro - 搭建篇,实现了spring-boot与shiro的整合,效果大家也看到了,工程确实集成了shiro的认证与授权功能。如果大家能正确搭建起来,并达到了认证和授权的效果,那说明我们会用了,说明我们知其然了;很好,能满足工作中的基本要求了。

  但是这样就够了吗?很显然还是不够的,知其然而不知其所以然是一道瓶颈,如果我们能跨过这道瓶颈,后面的路会越来越坦荡。就拿上篇博客来讲,我们仅仅只是在ShiroConfig类中加入了几个bean配置,怎么就让spring-boot集成了shiro,shiro又是如何做到认证和授权的,等等一些列问题,如果我们去细想的话,真的有很多疑点需要我们去探索。

  既然我们要去探索,势必就要读源码了。源码确实不好读,在我们工作当中,当我们读同事(或者前同事)写的代码的时候,总有那么一句话:草泥马,这是哪个sb写的,萦绕在我们的心头,甚至有时候会发现,这他么是我自己写的啊,哎,我操!有时候读自己写的代码都头疼,更别说看别人写的了。

  说了那么多,我们切入到正题,接下来会有一系列的文章来解析springboot的启动过程,而今天我们只看ShiroApplication类的构造方法。

ShiroApplication类

  入口还是那个熟悉的入口:main函数

  SpringApplication类注释

/*** Class that can be used to bootstrap and launch a Spring application from a Java main* method. By default class will perform the following steps to bootstrap your* application:** <ul>* <li>Create an appropriate {@link ApplicationContext} instance (depending on your* classpath)</li>* <li>Register a {@link CommandLinePropertySource} to expose command line arguments as* Spring properties</li>* <li>Refresh the application context, loading all singleton beans</li>* <li>Trigger any {@link CommandLineRunner} beans</li>* </ul>** In most circumstances the static {@link #run(Class, String[])} method can be called* directly from your {@literal main} method to bootstrap your application:** <pre class="code">* @Configuration* @EnableAutoConfiguration* public class MyApplication  {**   // ... Bean definitions**   public static void main(String[] args) throws Exception {*     SpringApplication.run(MyApplication.class, args);*   }* }* </pre>** <p>* For more advanced configuration a {@link SpringApplication} instance can be created and* customized before being run:** <pre class="code">* public static void main(String[] args) throws Exception {*   SpringApplication application = new SpringApplication(MyApplication.class);*   // ... customize application settings here*   application.run(args)* }* </pre>** {@link SpringApplication}s can read beans from a variety of different sources. It is* generally recommended that a single {@code @Configuration} class is used to bootstrap* your application, however, you may also set {@link #getSources() sources} from:* <ul>* <li>The fully qualified class name to be loaded by* {@link AnnotatedBeanDefinitionReader}</li>* <li>The location of an XML resource to be loaded by {@link XmlBeanDefinitionReader}, or* a groovy script to be loaded by {@link GroovyBeanDefinitionReader}</li>* <li>The name of a package to be scanned by {@link ClassPathBeanDefinitionScanner}</li>* </ul>** Configuration properties are also bound to the {@link SpringApplication}. This makes it* possible to set {@link SpringApplication} properties dynamically, like additional* sources ("spring.main.sources" - a CSV list) the flag to indicate a web environment* ("spring.main.web-application-type=none") or the flag to switch off the banner* ("spring.main.banner-mode=off").*/

View Code

    说的内容大概意思如下:

    SpringApplication用于从java main方法引导和启动Spring应用程序,默认情况下,将执行以下步骤来引导我们的应用程序:

      1、创建一个恰当的ApplicationContext实例(取决于类路径)

      2、注册CommandLinePropertySource,将命令行参数公开为Spring属性

      3、刷新应用程序上下文,加载所有单例bean

      4、触发全部CommandLineRunner bean

    大多数情况下,像SpringApplication.run(ShiroApplication.class, args);这样启动我们的应用,也可以在运行之前创建和自定义SpringApplication实例,具体可以参考注释中示例。

    SpringApplication可以从各种不同的源读取bean。 通常建议使用单个@Configuration类来引导,但是我们也可以通过以下方式来设置资源:

      1、通过AnnotatedBeanDefinitionReader加载完全限定类名

      2、通过XmlBeanDefinitionReader加载XML资源位置,或者是通过GroovyBeanDefinitionReader加载groovy脚本位置

      3、通过ClassPathBeanDefinitionScanner扫描包名称

    也就是说SpringApplication还是做了不少事的,具体实现后续会慢慢讲来,今天的主角只是SpringApplication构造方法。

  SpringApplication构造方法

    源代码如下

/*** 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;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));this.webApplicationType = deduceWebApplicationType();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}

    从注释上来看,就是说创建一个ShiroApplication实例,应用上下文从特定的资源文件中加载bean。可以在调用run之前自定义实例。

    从源码上来看,主要是deduceWebApplicationType();getSpringFactoriesInstances(xxx.class);deduceMainApplicationClass();这三个方法,我们一个一个来看。

    deduceWebApplicationType

      推断web应用类型

private WebApplicationType deduceWebApplicationType() {if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {return WebApplicationType.REACTIVE;}for (String className : WEB_ENVIRONMENT_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}return WebApplicationType.SERVLET;
}// 判断给定的类是否能够加载,就是说类路径下是否存在给定的类
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {try {forName(className, classLoader);return true;}catch (Throwable ex) {// Class or one of its dependencies is not present...return false;}
}

      如果org.springframework.web.reactive.DispatcherHandler能够被加载且org.springframework.web.servlet.DispatcherServlet不能够被加载,那么断定web应用类型是REACTIVE;如果javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext任意一个不能被加载,那么断定web应用类型是NONE;如果不能断定是REACTIVE和NONE,那么就是SERVLET类型;具体这三种类型代表什么含义,大家可以查看WebApplicationType中的说明。  

    getSpringFactoriesInstances

      从字面意思看就是获取spring工厂实例,至于从哪获取哪些工厂实例,我们往下看。

      getSpringFactoriesInstances源码

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));            // 获取指定类型的工厂名字List<T> instances = createSpringFactoriesInstances(type, parameterTypes,    // 根据名字、类型创建工厂实例
            classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}

View Code

      从源码我们看出主要做了三件事:

        1、loadFactoryNames,加载指定类型的工厂名称

          loadSpringFactories

          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));    // classLoader.getResources(FACTORIES_RESOURCE_LOCATION)获取类路径下全部的META-INF/spring.factories的URLresult = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {                                        // 遍历全部的URL,逐个读取META-INF/spring.factories中的属性URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));result.addAll((String) entry.getKey(), factoryClassNames);        // 属性全部放入MultiValueMap<String, String> result中,注意result的类型
            }}cache.put(classLoader, result);                                            // 结果放入缓存,方便下次查找return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}
}

View Code       

          loadSpringFactories做了以下这些事

            a、  查找类路径下全部的META-INF/spring.factories的URL

            b、 根据url加载全部的spring.factories中的属性,spring.factories内容如下

            c、  将所有spring.factories中的值缓存到SpringFactoriesLoader的cache中:

              private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();方便下次调用。

          加载完所有的工厂名称之后,然后从中获取指定工厂类型的工厂名称列表,也就是getOrDefault(factoryClassName, Collections.emptyList())做的事。

        2、createSpringFactoriesInstances,创建指定类型的工厂实例

          根据上面获取的指定类型的工厂名称列表来实例化工厂bean,我们可以简单的认为通过反射来实例化,但是具体的实现也没那么简单,感兴趣的小伙伴可以自己去跟。

        3、对工厂实例进行排序,然后返回排序后的实例列表

          排序规则:@Order从小到大排序,没有order则按没排序之前的顺序。

      deduceMainApplicationClass

        从当前堆栈跟踪列表中获取main方法所在的类名   

    构造方法总结

      1、 构造自身实例

      2、 推测web应用类型,并赋值到属性webApplicationType

      3、 设置属性List<ApplicationContextInitializer<?>> initializers和List<ApplicationListener<?>> listeners

        中途读取了类路径下所有META-INF/spring.factories的属性,并缓存到了SpringFactoriesLoader的cache缓存中

      4、 推断主类,并赋值到属性mainApplicationClass

      构造方法完成之后,实例的属性值如下

感想

  本来是想着springboot启动源码解析只用两篇来说明的,之后讲shiro的源码;可我写着写着发现好多实例莫名奇妙的就被实例化了,很多细节没有读到,所以决定细抠,将springboot的启动过程拆分成多篇来讲解,真真正正的明白springboot在启动的过程都做了些什么。

  补充一句:有时候,不是对手有多强大,只是我们不敢去尝试;勇敢踏出第一步,你会发现自己比想象中更优秀!诚如海因斯第一次跑进人类10s大关时所说:上帝啊,原来那扇门是虚掩着的!

参考

  spring boot 2.0 源码分析(一)

  springboot源码

spring-boot-2.0.3不一样系列之源码篇 - springboot源码一,绝对有值得你看的地方相关推荐

  1. Spring Boot 2.0正式发布,升还是不升呢?

    Spring帝国 Spring几乎是每一位Java开发人员都耳熟能详的开发框架,不论您是一名初出茅庐的程序员还是经验丰富的老司机,都会对其有一定的了解或使用经验.在现代企业级应用架构中,Spring技 ...

  2. 实战Spring Boot 2.0系列(一) - 使用Gradle构建Docker镜像

    前言 通常我们使用 Dockerfile 来构建项目的 Docker 镜像.但是也有使用 gradle 在编译项目的时候一起把镜像给 构建 并 上传 的需求.本文将会讲解如何使用 gradle 编写并 ...

  3. Spring Boot 2.0系列文章(四):Spring Boot 2.0 源码阅读环境搭建

    前提 前几天面试的时候,被问过 Spring Boot 的自动配置源码怎么实现的,没看过源码的我只能投降��了. 这不,赶紧来补补了,所以才有了这篇文章的出现,Spring Boot 2. 0 源码阅 ...

  4. Spring Boot 2.0选择HikariCP作为默认数据库连接池的五大理由

    转载自公众号:工匠小猪猪的技术世界 摘要: 本文非原创,是「工匠小猪猪的技术世界」搜集了一些HikariCP相关的资料整理给大家的介绍,主要讲解了为什么sb2选择了HikariCP以及HikariCP ...

  5. SpringBoot2.0(一):【重磅】Spring Boot 2.0权威发布

    就在昨天Spring Boot2.0.0.RELEASE正式发布,今天早上在发布Spring Boot2.0的时候还出现一个小插曲,将Spring Boot2.0同步到Maven仓库的时候出现了错误, ...

  6. 学习Spring Boot:(二十七)Spring Boot 2.0 中使用 Actuator

    前言 主要是完成微服务的监控,完成监控治理.可以查看微服务间的数据处理和调用,当它们之间出现了异常,就可以快速定位到出现问题的地方. springboot - version: 2.0 正文 依赖 m ...

  7. (转)Spring Boot 2(一):【重磅】Spring Boot 2.0权威发布

    http://www.ityouknow.com/springboot/2018/03/01/spring-boot-2.0.html 就在今天Spring Boot2.0.0.RELEASE正式发布 ...

  8. InfoQ就Spring Boot 2.0 GA版发布采访了项目牵头人Phil Webb

    \ 看新闻很累?看技术新闻更累?试试下载InfoQ手机客户端,每天上下班路上听新闻,有趣还有料! \ \\ 广受期待的Spring Boot 2.0近期由Spring的托管企业Pivotal发布.这是 ...

  9. Spring boot 2.0 with Oauth2 + Jwt

    2019独角兽企业重金招聘Python工程师标准>>> Spring boot 2.0 with Oauth2 + Jwt 在这篇文章中: Netkiller Spring Clou ...

最新文章

  1. python多线程的使用(导入线程模块、创建子线程任务、启动子线程任务、获取当前执行的线程号)
  2. linux 后台进程
  3. 红黑树 键值_查找(一)史上最简单清晰的红黑树讲解
  4. 实验 3 简单的分支与循环结构
  5. opencv+python机读卡识别(四)百度API进行数字识别
  6. 移动机器人建图与导航代码实现——3.Path Tracking
  7. 30人的产研团队如何高效协同?
  8. 蔚来辅助驾驶致死,副总裁沈斐或遇更大危机
  9. 从卓越工程的角度看微软中国开发团队的成长 (一)
  10. 华为路由器ws5200虚拟服务器,想处理垃圾路由吗?那就先入手这款华为WS5200路由器吧...
  11. Linux实战教学笔记50:Zabbix监控平台3.2.4(二)深入理解zabbix
  12. 查询相关股票十档行情的方法
  13. 2021最新最细致的IDEA集成SVN工具的使用 (入门到精通)
  14. 踩过坑的可控硅设计 经验总结
  15. SSM框架整合步骤思路及案例分析
  16. IP Multicast: MSDP RPF检测详细规则(转)
  17. 获取浏览器的地理位置信息
  18. java程序员常用的快捷键
  19. iOS 字符串转ascii码,字符串转NSData
  20. 罗斯柴尔德投资公司增加对GBTC的投资

热门文章

  1. 键盘摄影:今天老李是一名动物摄影师
  2. 【机器学习】集成学习:Boosting、Bagging 和 Stacking
  3. Mysql 安装问题一:无法启动此程序,因为计算机丢失MSVCP120.dll,MSVCR120.dll
  4. 时间序列预测之为何舍弃LSTM而选择Informer?(Informer模型解读)
  5. 让vue-cli初始化后的项目集成支持SSR
  6. Linux系统调用详解(实现机制分析)
  7. Linux | 关于putty出现access denied的问题解决方案
  8. 西门子plc,经典双泵变频恒压供水
  9. http://www.cnblogs.com/procoder/archive/2012/03/27/Windows-8-First-Try.html
  10. Java8 Function.identity() 的使用