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源碼