本文是“著”模式。但是本文寫完之後,我直接搜索了一下結論的核心,發現好些達人都已經實現了,無知好尷尬……
爲什麼要做這事兒?
自動化、可視化、授權……總之,誰用誰知道
如何在Controller類的方法內獲取到系統內已存的所有RequestMapping的路徑列表集?
簡單來說:在帶有@Controller註解的類中,添加以下的代碼:
@Autowired
ApplicationContext applicationContext;
public void getAllRequestMappingInfo() {
AbstractHandlerMethodMapping<RequestMappingInfo> objHandlerMethodMapping = (AbstractHandlerMethodMapping<RequestMappingInfo>)application.getBean(“requestMappingHandlerMapping”);
Map<RequestMappingInfo, HandlerMethod> mapRet = objHandlerMethodMapping.getHandlerMethods();
}
實現過程,沒仔細看書,只能蠻力調試了。
本文基於的主要庫版本:
spring-boot:1.5.2.RELEASE
*spring-xx:4.3.7.RELEASE
*:spring-xx中的xx包含core、context、beans等
以下是調試流程:
1、在項目的App.main方法(App類有@SpringBootApplication註解)中,使用SpringApplication.run方法,最終會創建一個SpringApplication類的對象,並調用其run(String…args)方法;
2、在run方法中,使用SpringApplication.createApplicationContext()方法,創建了ApplicationContext對象,其具體類型由SpringApplication對象屬性的webEnvironment布爾值所決定,由於後者的值爲true,因此ApplicationContext對象被初始化爲org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext類型;run方法內將創建的對象臨時賦值給了方法體內定義的context(ConfigurableApplicationContext類型)局部變量;再具體的細節這裏不表;
3、run方法內,繼續到this.refreshContext(context),在後者內調用refresh方法;
4、refresh最終會調用org.springframework.context.support.AbstractApplicationContext類的refresh方法;
5、在AbstractApplicationContext.refresh方法內,調用finishBeanFactoryInitialization方法對GenericApplicationContext.beanFactory成員變量進行初始化(GenericApplicationContext類是直接從AbstractApplicationContext類派生出來的,後者並沒有定義beanFactory成員變量,但是定義了一個抽象方法getBeanFactory,其實獲取的就是前者的成員變量,因爲前者不僅定義了成員變量,而且也實現了這個抽象方法),這個beanFactory成員變量在GenericApplicationContext類的構造函數裏被創建,其類型爲DefaultListableBeanFactory;
6、再回到finishBeanFactoryInitialization方法內,beanFactory會調用DefaultListableBeanFactory.preInstantiateSingletons方法,暫停一下,我們下面說一個beanFactory;
6.1、在第2步的run方法內使用createApplicationContext方法創建上下文時,會通過org.springframework.beans.BeanUtils類,最終在org.springframework.context.annotation.AnnotationConfigUtils類內的registerAnnotationsConfigProcessors方法中,對beanFactory進行一些初始化。beanFactory對象的beanDefinitionNames成員變量(ArrayList)保存着系統內的bean名稱(這裏,我覺得使用GenericApplicationContext.getDefaultListableBeanFactory()方法可以獲取到beanFactory,然後進行獲取到bean名稱集)
6.2、接上面,在AnnotationConfigUtils.registerAnnotationsConfigProcessors方法內,會對beanFactory對象實際添加幾個系統內最初的bean,將其名稱保存在DefaultListableBeanFactory的beanDefinitionNames成員變量內,將<bean名稱,對象>保存在beanDefinitionMap成員變量內。整個系統內的第一個bean名稱是org.springframework.context.annotation.internalConfigurationAnnotationProcessor,其值爲org.springframework.beans.factory.support.RootBeanDefinition類型的對象,且後者傳進去的參數爲org.springframework.context.annotation.ConfigurationClassPostProcessor類型。
6.3、whatever,最終在DefaultListableBeanFactory.registerBeanDefinition方法內,對bean進行添加。總之,在AnnotationConfigUtils.registerAnnotationsConfigProcessors方法內,會最終人爲的添加6個bean:
名稱 |
對象類型 |
org.springframework.context.annotation.internalConfigurationAnnotationProcessor |
org.springframework.beans.factory.support.RootBeanDefinition類型,參數爲org.springframework.context.annotation.ConfigurationClassPostProcessor |
org.springframework.context.annotation.internalAutowiredAnnotationProcessor |
org.springframework.beans.factory.support.RootBeanDefinition類型,參數爲org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor |
org.springframework.context.annotation.internalRequiredAnnotationProcessor |
org.springframework.beans.factory.support.RootBeanDefinition類型,參數爲org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor |
org.springframework.context.annotation.internalCommonAnnotationProcessor |
org.springframework.beans.factory.support.RootBeanDefinition類型,參數爲org.springframework.beans.factory.annotation.CommonAnnotationBeanPostProcessor |
org.springframework.context.annotation.internalPersistenceAnnotationProcessor |
org.springframework.beans.factory.support.RootBeanDefinition類型,參數爲org.springframework.beans.factory.annotation.PersistenceAnnotationBeanPostProcessor |
org.springframework.context.event.internalEventListenerProcessor |
org.springframework.beans.factory.support.RootBeanDefinition類型,參數爲org.springframework.context.event.EventListenterMethodProcessor |
org.springframework.context.event.internalEventListenerFactory |
org.springframework.beans.factory.support.RootBeanDefinition類型,參數爲org.springframework.context.event.DefaultEventListenerFactory |
7、回到AbstractApplicationContext.refresh方法內,在其中的this.invokeBeanFactoryPostProcessors步驟,會掃描獲取到系統內自定義的所有的bean名稱,並將其裝入到beanFactory對象內。
8、在AbstractApplicationContext.finishBeanFactoryInitialization方法內,beanFactory對象使用preInstantiateSingletons方法,對單件bean對象進行預初始化;
9、單件的bean對象很多,比如requestMappingHandlerMapping就是其中之一,還有error、beanNameHandlerMapping等等;
10、這裏我們主要看名稱爲requestMappingHandlerMapping的bean,這個名稱的bean最終通過一系列手段會對應org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping類型的對象;
11、RequestMappingHandlerMapping類對象在創建後,會調用bean的一個約定方法(它本身改寫了)afterPropertiesSet(),然後在其中又調用了它的父類AbstractHandlerMethodMapping的afterPropertiesSet()方法,後者內調用了initHandlerMethods()方法;
11.1、在AbstractHandlerMethodMapping.initHandlerMethods方法內,首先會獲取到系統內所有的bean names,然後一一枚舉這些bean,主要是爲了判斷bean對象的類上有沒有@Controller註解,或者方法上是不是有@RequestMapping註解。這個實現是通過這種方式達成的:RequestMappingHandlerMapping類實現了AbstractHandlerMethodMapping定義的抽象方法isHandler(),在前者的實體內,
12、在AbstractHandlerMethodMapping.initHandlerMethods方法內,會通過detectHandlerMethods()方法->registerHandlerMethods()方法,最終調用內部類AbstractHandlerMethodMapping.MappingRegistry的register方法,將@RequestMapping註解後的mapping字符串、Controller類對象、Mehod對象三者存放在MappingRegistry類對象的成員變量mappingLookup這個Map<T,HandlerMethod>類型的映身內,後者是一個LinkedHashMap類型的映射;
13、MappingRegistry類提供了getMapping()方法返回mappingLookup成員變量的引用;
14、AbstractHandlerMethodMapping類提供了getMappingRegistry()方法,返回MappingRegistry類對象,但是這個方法並不是一個public方法,因此只能在org.springframework.web.servlet.handler包內被使用了。
15、RequestMappingHandlerMapping類從AbstractHandlerMethodMapping類派生而來,通過getBean()方法,能夠獲取到其對象,但由於14步的影響,還是無法獲取到@RequestMapping的路徑;
16、另外一種方法,AbstractHandlerMethodMapping類內提供了一個公共的方法getHandlerMethods(),這個方法返回MappingRegistry類的mappingLookup成員變量的不可更改視圖;AbstractHandlerMethodMapping類還提供了其他幾個有用的方法,暫時略過;
17、ApplicationContext可使用ApplicationObjectSupport.getApplicationContext()方法獲取;所有的bean names可通過org.springframework.beans.factory.ListableBeanFactory.getBeansNamesForType()方法獲取到(此方法真正在org.springframework.beans.factory.support.DefaultListableBeanFactory類內被實現;
其他有用的方法:
1、如何通過bean名稱獲取bean的實際類型?
使用org.springframework.beans.factory.BeanFactory接口提供的getType方法(此方法在ApplicationContext被實現了)
最後,其實,在使用springboot調試時,控制檯的輸出信息裏,已經打印出了所有映射路徑及對應的處理方法信息,比本文最開始的方法打印的更全(本文僅做了RequestMappingHandlerMapping)
事後發現的一些達人的文章:
SpringMVC項目中獲取所有URL到Controller Method的映射:http://www.cnblogs.com/yuananyun/archive/2014/08/25/3934371.html
Spring 反射得到所有controller與method:http://183615215-qq-com.iteye.com/blog/1866281