对应上一章 《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