Spring?WebMVC初始化Controller流程詳解
Spring WebMVC初始化Controller流程
此篇文章開始之前先向大家介紹一個(gè)接口 InitializingBean
這個(gè)接口的作用如果了解spring生命周期的應(yīng)該知道 ,這個(gè)接口的作用就是在bean初始化之后會(huì)執(zhí)行的init方法
public interface InitializingBean {
? ? void afterPropertiesSet() throws Exception;
}當(dāng)然能實(shí)現(xiàn)這種方式的方法spring還介紹了關(guān)于注解的@PostConstruct 和xml 的 init-method = "" 的兩種方式。但是springmvc使用的是接口的方式。
這里要介紹的初始化Controller是指填充完HandlerMapping map<String,Method>即代表初始化Controller流程
我們?cè)偬嵋稽c(diǎn)小知識(shí)。聲明一個(gè)controller類有哪些方式。(現(xiàn)在應(yīng)該沒有人用第二/三種方式吧)
- 1.使用注解@Controller 和 請(qǐng)求路徑@RequestMapping
- 2.實(shí)現(xiàn) Controller 接口 并將該類交給spring容器管理beanName為請(qǐng)求路徑
- 3.實(shí)現(xiàn) HttpRequestHandler 接口并將該類交給spring容器管理beanName為請(qǐng)求路徑
那么我們的map填充就從實(shí)現(xiàn)了InitializingBean 接口 的afterPropertiesSet這個(gè)方法開始。
源碼中是這個(gè)類 AbstractHandlerMethodMapping(下面的代碼有刪減)
public void afterPropertiesSet() {
? ? this.initHandlerMethods();
}
protected void initHandlerMethods() {
? ? String[] var1 = this.getCandidateBeanNames();//1.獲取容器初始化的所有beanName
? ? int var2 = var1.length;
? ? for(int var3 = 0; var3 < var2; ++var3) {
? ? ? ? String beanName = var1[var3];
? ? ? ? if (!beanName.startsWith("scopedTarget.")) {
? ? ? ? ? ? this.processCandidateBean(beanName);//2.獲取所有聲明為Controller類的beanName
? ? ? ? }
? ? }
? ? this.handlerMethodsInitialized(this.getHandlerMethods());
}獲取容器初始化的所有beanName(父子容器概念)
protected String[] getCandidateBeanNames() {
? ? return this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.obtainApplicationContext(), Object.class) : this.obtainApplicationContext().getBeanNamesForType(Object.class);
}獲取所有聲明為Controller類的beanName
protected void processCandidateBean(String beanName) {
? ? Class beanType = null;
? ? try {
? ? ? ? beanType = this.obtainApplicationContext().getType(beanName);//獲取bean的類型
? ? } catch (Throwable var4) {
? ? ? ? if (this.logger.isTraceEnabled()) {
? ? ? ? ? ? this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);
? ? ? ? }
? ? }
? ? if (beanType != null && this.isHandler(beanType)) {//獲取所有聲明為Controller類的beanName
? ? ? ? this.detectHandlerMethods(beanName);//1.開始處理這種類型的beanName
? ? }
}開始處理這種類型的beanName
protected void detectHandlerMethods(Object handler) {
? ? Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();
? ? if (handlerType != null) {
? ? ? ? Class<?> userType = ClassUtils.getUserClass(handlerType);
? ? ? ? Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? return this.getMappingForMethod(method, userType);//1.獲取到類類型下所有的方法
? ? ? ? ? ? } catch (Throwable var4) {
? ? ? ? ? ? ? ? throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? if (this.logger.isTraceEnabled()) {
? ? ? ? ? ? this.logger.trace(this.formatMappings(userType, methods));
? ? ? ? }
? ? ? ? methods.forEach((method, mapping) -> {
? ? ? ? ? ? Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
? ? ? ? ? ? this.registerHandlerMethod(handler, invocableMethod, mapping);//注冊(cè)并填充map
? ? ? ? });
? ? }
}獲取到類類型下所有的方法和注冊(cè)并填充map
第一個(gè) MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap();
urlLookup.add(url, mapping); eg:url = '/test/test.do'?
mapping是一個(gè)RequestMappingInfo 對(duì)象 RequestMappingInfo.patternsCondition = T --> /test/test.do
第二個(gè) Map<T, AbstractHandlerMethodMapping.MappingRegistration<T>> registry = new HashMap();
eg:key = 'url' ?value = 'method'
而我們的第二種和第三種的方式基本沒有用了,因?yàn)闀?huì)出現(xiàn)類爆炸,就像原始的servlet一樣每一個(gè)方法都需要寫一個(gè)類。
這兩種方式是通過beanName為路徑來(lái)實(shí)例化對(duì)象并執(zhí)行通過該對(duì)象來(lái)執(zhí)行里面的方法的。
源碼中這兩種map的填充方式是在bean的生命周期中通過實(shí)現(xiàn)beanFactory的applyBeanPostProcessorsBeforeInitialization方法來(lái)填充的。
@Controller 類中初始化問題
在Controller類中常常遇到有些參數(shù)需要初始化,甚至有些只允許初始化一次,而Controller類不像servelet類可以調(diào)用init()函數(shù)進(jìn)行初始化,這里想到的辦法是設(shè)置標(biāo)記值,讓初始化部分只調(diào)用一次。
第一種方法
設(shè)置isStart值。
private static Boolean isStart = false;
if(!isStart){
//進(jìn)行初始化
isStart=true;
}第二種方法
使用注釋@PostConstruct,該注釋的類會(huì)在類初始化時(shí)進(jìn)行調(diào)用。
@PostConstruct
private void init(){
//進(jìn)行初始化
}以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring?@EventListener?異步中使用condition的問題及處理
這篇文章主要介紹了Spring?@EventListener?異步中使用condition的問題及處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java實(shí)現(xiàn)的mysql事務(wù)處理操作示例
這篇文章主要介紹了Java實(shí)現(xiàn)的mysql事務(wù)處理操作,結(jié)合實(shí)例形式較為詳細(xì)的分析了Java基于JDBC操作mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)事務(wù)處理的相關(guān)概念、操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-08-08
Java?中向?Arraylist?添加對(duì)象的示例代碼
本文介紹了如何在 Java 中向 ArrayList 添加對(duì)象,并提供了示例和注意事項(xiàng),通過掌握這些知識(shí),讀者可以在自己的 Java 項(xiàng)目中有效地使用 ArrayList 來(lái)存儲(chǔ)和操作對(duì)象,需要的朋友可以參考下2023-11-11
關(guān)于mybatis遇到Integer類型的參數(shù)時(shí)動(dòng)態(tài)sql需要注意條件
這篇文章主要介紹了關(guān)于mybatis遇到Integer類型的參數(shù)時(shí)動(dòng)態(tài)sql需要注意條件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
詳解IDEA 中使用Maven創(chuàng)建項(xiàng)目常見錯(cuò)誤和使用技巧(推薦)
這篇文章主要介紹了詳解IDEA 中使用Maven創(chuàng)建項(xiàng)目常見錯(cuò)誤和使用技巧(推薦),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
詳解MyBatis的Dao層實(shí)現(xiàn)和配置文件深入
這篇文章主要為大家詳細(xì)介紹了MyBatis的Dao層實(shí)現(xiàn)和配置文件深入,文中的示例代碼講解詳細(xì),感興趣的小伙伴快來(lái)跟隨小編一起學(xué)習(xí)一下2022-07-07

