spring之我見--Controller註冊到DispatchServlet請求處理(上)
對應上一章 《spring之我見–從spring的啟動到ioc容器的建立》
今天我們探討一下Springmvc的工作原理,Springmvc的核心是Controller請求部分,所以我們的探討從Controller被註冊開始,到Controller如何被請求的。
1.Controller註冊前的準備工作
1.1 refresh()
上一章我們知道IOC容器是在ContextLoaderListener(ServletContextListener的實現類)下初始化的,而初始化的重要步驟是在ConfigurableApplicationContext的refresh ()方法下完成的,根據下面的註釋,refresh 方法負責載入配置檔案,比如常見的spring配置檔案applicationContext.xml
/**
* 載入或重新整理配置的持久表示,這可能是XML檔案、屬性檔案或關係資料庫模式。
* Load or refresh the persistent representation of the configuration,
* which might an XML file, properties file, or relational database schema.
* <p>As this is a startup method, it should destroy already created singletons
* if it fails, to avoid dangling resources. In other words, after invocation
* of that method, either all or no singletons at all should be instantiated.
* @throws BeansException if the bean factory could not be initialized
* @throws IllegalStateException if already initialized and multiple refresh
* attempts are not supported
*/
void refresh() throws BeansException, IllegalStateException;
這是refresh()具體實現,做了很多事,我們具體進obtainFreshBeanFactory()方法。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
當走到refreshBeanFactory()方法,我們看到loadBeanDefinitions()方法,官方解釋為 將bean定義載入到給定的bean工廠中,通常是通過委託給一個或多個bean定義閱讀器。話句話說 這裡就是生成BeanDefinition元資料的地方,BeanFactory不直接儲存例項,而是元資料,根據需要用反射等手段將BeanDefinition轉換成例項,我們的Controller當然也需要先轉換成元資料,
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
spring的呼叫棧比較深,我這裡只是挑重要的地方解釋,最主要還是自己debug走一遍。
當走到loadBeanDefinitions(XmlBeanDefinitionReader reader)方法(這跟上一個loadBeanDefinitions方法不是一樣,是過載方法),我們發現它開始讀配置檔案,這個configLocations陣列就一個String物件:“/WEB-INF/applicationContext.xml” .
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
而我們的applicationContext.xml檔案就寫了兩句,為了註冊一個controller。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- @Controller 掃描 -->
<context:component-scan base-package="controller" />
<!-- 註解驅動: 作用:替我們自動配置最新版的註解的處理器對映器和處理器介面卡 -->
<mvc:annotation-driven />
</beans>
順便我也把controller貼出來吧。
package controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author panqian
* @Description:
* @date 2018/1/26
*/
@RestController
@RequestMapping("controller")
public class TestController {
@GetMapping("test")
public void test(HttpServletRequest request) {
System.out.println("");
}
}
有人問為什麼系統知道“/WEB-INF/applicationContext.xml” 這個路徑,一開始我也納悶,但是既然能debug,我們就能知道程式執行的堆疊鏈,所以我在setConfigLocation打一個斷點,根據堆疊鏈往前找,直接就可以找到答案。
sc就是ServletContext,還記得上一章我們在web.xml寫的一個啟動的InitParameter?路徑就是從這裡取的。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
1.2XmlBeanDefinitionReader負責配置檔案解析
讓我們繼續往下看,剛剛拿到了檔案路徑,那我們的解析就交給了XmlBeanDefinitionReader負責,這裡面涉及解析節點,提取資訊,又是很深的呼叫棧,主要介紹一下MvcNamespaceHandler ,它針對不同的配置提供多種解析器,比如annotation-driven就用AnnotationDrivenBeanDefinitionParser。
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* {@link NamespaceHandler} for Spring MVC configuration namespace.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Sebastien Deleuze
* @since 3.0
*/
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
}
}
AnnotationDrivenBeanDefinitionParser又具體做了什麼事呢?看它的parse()方法,
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
XmlReaderContext readerContext = parserContext.getReaderContext();
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
// 生成 RequestMappingHandlerMapping,用於處理@Controller和@RequestMapping註解的
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
if (element.hasAttribute("enable-matrix-variables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
else if (element.hasAttribute("enableMatrixVariables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
configurePathMatchingProperties(handlerMappingDef, element, parserContext);
// 將該BeanDefinition加入BeanFactory工廠(ApplicationContext)
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);
。。。。。。
}
上面註冊的RequestMappingHandlerMapping 和RequestMappingHandlerAdapter是為後面controller註冊和使用準備的,只有配置了<mvc:annotation-driven />
才會註冊這些元件。
說了<mvc:annotation-driven />
是不是還有一個<context:component-scan base-package="controller" />
?這個標籤也有自己的parse–ComponentScanBeanDefinitionParser,它的parse方法更加簡單。
basePackage 拿到引數是我配的 “controller”,所以scanner.doScan方法直接到該包下拿到了testController的beanDefinitions,隨後註冊到 ApplicationContext中。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
2.生火燒飯! Controller註冊
通過前面的分析,我們已經有了用於 Controller註冊的RequestMappingHandlerMapping,TestController也被註冊進了IOC容器中,萬事俱備,我們重新返回refresh()方法中,剛剛都是走的obtainFreshBeanFactory();而接下來的finishBeanFactoryInitialization()很重要,首先這個方法用於例項化所有(非懶載入)單例物件。 然後RequestMappingHandlerMapping是單例的,非懶載入的,所以RequestMappingHandlerMapping也會在這個方法裡處理,再加上 RequestMappingHandlerMapping的afterPropertiesSet方法是具體註冊Controller的地方,所以我們需要重點跟蹤finishBeanFactoryInitialization()的程式碼。
這是我根據的doGetBean()的部分程式碼,當輪到RequestMappingHandlerMapping開始建立例項時,會走複寫的getObject()方法。
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
繼續往裡走,populateBean是在封裝例項欄位值,繼續進initializeBean方法
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
重點關注invokeInitMethods方法,這個方法給例項機會去 做一些初始化操作,而這個機會就是複寫afterPropertiesSet()方法。
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
。。。。
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
。。。。
}
invokeInitMethods方法會呼叫afterPropertiesSet(),那RequestMappingHandlerMapping怎麼複寫了這個方法呢?
((InitializingBean) bean).afterPropertiesSet();
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
super.afterPropertiesSet();
}
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
這裡真相大白,它會先通過isHandler(beanType)判斷是不是有@Controller或@RequestMapping註釋,是的話進入detectHandlerMethods(),最後將對映寫入mappingRegistry,這是一個MappingRegistry型別,所有的前端對映都會記錄在這裡,也就達到了註冊controller的目的。
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #isHandler(Class)
* @see #getMappingForMethod(Method, Class)
* @see #handlerMethodsInitialized(Map)
*/
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
3.參考文獻
https://www.cnblogs.com/kindevil-zx/p/6603154.html
http://blog.csdn.net/lovesomnus/article/details/49801593