spring-boot-2.0.3应用篇 - shiro集成,实现了spring-boot与shiro的整合,效果你们也看到了,工程确实集成了shiro的认证与受权功能。若是你们能正确搭建起来,并达到了认证和受权的效果,那说明咱们会用了,说明咱们知其然了;很好,能知足工做中的基本要求了。html
可是这样就够了吗?很显然仍是不够的,知其然而不知其因此然是一道瓶颈,若是咱们能跨过这道瓶颈,后面的路会愈来愈坦荡。就拿上篇博客来说,咱们仅仅只是在ShiroConfig类中加入了几个bean配置,怎么就让spring-boot集成了shiro,shiro又是如何作到认证和受权的,等等一些列问题,若是咱们去细想的话,真的有不少疑点须要咱们去探索。java
既然咱们要去探索,势必就要读源码了。源码确实很差读,在咱们工做当中,当咱们读同事(或者前同事)写的代码的时候,总有那么一句话:草泥马,这是哪一个sb写的,萦绕在咱们的心头,甚至有时候会发现,这他么是我本身写的啊,哎,我操!有时候读本身写的代码都头疼,更别说看别人写的了。react
说了那么多,咱们切入到正题,接下来会有一系列的文章来解析springboot的启动过程,而今天咱们只看SpringApplication类的构造方法。web
入口仍是那个熟悉的入口:main函数spring
/** * 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"). */
说的内容大概意思以下:bootstrap
SpringApplication用于从java main方法引导和启动Spring应用程序,默认状况下,将执行如下步骤来引导咱们的应用程序:缓存
一、建立一个恰当的ApplicationContext实例(取决于类路径)springboot
二、注册CommandLinePropertySource,将命令行参数公开为Spring属性app
三、刷新应用程序上下文,加载全部单例beanide
四、触发所有CommandLineRunner bean
大多数状况下,像SpringApplication.run(ShiroApplication.class, args);这样启动咱们的应用,也能够在运行以前建立和自定义SpringApplication实例,具体能够参考注释中示例。
SpringApplication能够从各类不一样的源读取bean。 一般建议使用单个@Configuration类来引导,可是咱们也能够经过如下方式来设置资源:
一、经过AnnotatedBeanDefinitionReader加载彻底限定类名
二、经过XmlBeanDefinitionReader加载XML资源位置,或者是经过GroovyBeanDefinitionReader加载groovy脚本位置
三、经过ClassPathBeanDefinitionScanner扫描包名称
也就是说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();这三个方法,咱们一个一个来看。
推断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中的说明。
从字面意思看就是获取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 duplicates Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 获取指定类型的工厂名字 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, // 根据名字、类型建立工厂实例 classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
从源码咱们看出主要作了三件事:
一、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的URL result = 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); } }
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())作的事。
二、createSpringFactoriesInstances,建立指定类型的工厂实例
根据上面获取的指定类型的工厂名称列表来实例化工厂bean,咱们能够简单的认为经过反射来实例化,可是具体的实现也没那么简单,感兴趣的小伙伴能够本身去跟。
三、对工厂实例进行排序,而后返回排序后的实例列表
排序规则:@Order从小到大排序,没有order则按没排序以前的顺序。
从当前堆栈跟踪列表中获取main方法所在的类名
一、 构造自身实例
二、 推测web应用类型,并赋值到属性webApplicationType
三、 设置属性List<ApplicationContextInitializer<?>> initializers和List<ApplicationListener<?>> listeners
中途读取了类路径下全部META-INF/spring.factories的属性,并缓存到了SpringFactoriesLoader的cache缓存中
四、 推断主类,并赋值到属性mainApplicationClass
构造方法完成以后,实例的属性值以下
原本是想着springboot启动源码解析只用两篇来讲明的,以后讲shiro的源码;可我写着写着发现好多实例莫名奇妙的就被实例化了,不少细节没有读到,因此决定细抠,将springboot的启动过程拆分红多篇来说解,真真正正的明白springboot在启动的过程都作了些什么。
补充一句:有时候,不是对手有多强大,只是咱们不敢去尝试;勇敢踏出第一步,你会发现本身比想象中更优秀!诚如海因斯第一次跑进人类10s大关时所说:上帝啊,原来那扇门是虚掩着的!
springboot源码