SpringMVC源码分析(1)标签解析

本文主要内容是根据一个常见的springmvc 配置文件,剖析分解每个标签的工作内容。

一个非常熟悉的springmvc配置样例

<?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:context="http://www.springframework.org/schema/context"
   xmlns:mvc="http://www.springframework.org/schema/mvc"
   xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <!-- Scans the classpath of this application for @Components to deploy as beans -->
   <context:component-scan base-package="org.springframework.samples.mvc.basic" />

   <!-- Configures the @Controller programming model -->
   <mvc:annotation-driven />

   <!-- Forwards requests to the "/" resource to the "welcome" view -->
   <mvc:view-controller path="/" view-name="welcome"/>

   <!-- Configures Handler Interceptors -->   
   <mvc:interceptors>
      <!-- Changes the locale when a 'locale' request parameter is sent; e.g. /?locale=de -->
      <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
   </mvc:interceptors>

   <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
   <mvc:resources mapping="/resources/**" location="/resources/" />

   <!-- Saves a locale change using a cookie -->
   <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />

   <!-- Application Message Bundle -->
   <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
      <property name="basename" value="/WEB-INF/messages/messages" />
      <property name="cacheSeconds" value="0" />
   </bean>

   <!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/views/"/>
      <property name="suffix" value=".jsp"/>
   </bean>
</beans>

接下来,逐个标签进行分析

2.<context:component-scan>标签

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

   public void init() {
       ...
      registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
      ...  
   }

ComponentScanBeanDefinitionParser负责具体解析工作。

2.1 属性值

public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {

   private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";

   private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";

   private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";

   private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
   
   private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
   
   private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
   
   private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";

   private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";

   private static final String INCLUDE_FILTER_ELEMENT = "include-filter";

   private static final String FILTER_TYPE_ATTRIBUTE = "type";

   private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
   ...
   }

首先在ComponentScanBeanDefinitionParser类中定义了</context:component-scan>元素的所有属性值

base-package:为必须配置属性,指定了spring需要扫描的跟目录名称,可以使用”,” “;” “\t\n(回车符)”来分割多个包名

resource-pattern:配置扫描资源格式.默认”*/.class”

use-default-filters:是否使用默认扫描策略,默认为”true”,会自动扫描指定包下的添加了如下注解的类,@Component, @Repository, @Service,or @Controller

annotation-config:是否启用默认配置,默认为”true”,该配置会在BeanDefinition注册到容器后自动注册一些BeanPostProcessors对象到容器中.这些处理器用来处理类中Spring’s @Required and
@Autowired, JSR 250’s @PostConstruct, @PreDestroy and @Resource (如果可用),
JAX-WS’s @WebServiceRef (如果可用), EJB 3’s @EJB (如果可用), and JPA’s
@PersistenceContext and @PersistenceUnit (如果可用),但是该属性不会处理Spring’s @Transactional 和 EJB 3中的@TransactionAttribute注解对象,这两个注解是通过<tx:annotation-driven>元素处理过程中对应的BeanPostProcessor来处理的.

include-filter:如果有自定义元素可以在该处配置

exclude-filter:配置哪些类型的类不需要扫描,如上面指定了类中添加了”Controller”元素的类不扫描.
注意:</context:component-scan>元素中默认配置了annotation-config,所以不需要再单独配置</annotation-config>元素.

2.2 解析

public BeanDefinition parse(Element element, ParserContext parserContext) {
   String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
         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;
}

ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner主要用来完成类路径下符合条件的bean扫描功能,并将扫描出来的bean注册到指定的BeanFactory中。

  • 默认过滤器主要扫描@Component @Repository @Service @Controller注解的类,同样可以通过配置类扫描过滤器来扫描自定义注解的类。
  • 当类路径下有javax.annotation.ManagedBean和javax.inject.Named类库时支持这2个注解扫描。

3 <mvc:annotation-driven />标签

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

   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());
   }

}

AnnotationDrivenBeanDefinitionParser具体负责解析工作。

3.2 具体解析方法

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
...
public BeanDefinition parse(Element element, ParserContext parserContext) {
   Object source = parserContext.extractSource(element);

   CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
   parserContext.pushContainingComponent(compDefinition);
   
   RootBeanDefinition annMappingDef = new RootBeanDefinition(DefaultAnnotationHandlerMapping.class);
   annMappingDef.setSource(source);
   annMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   annMappingDef.getPropertyValues().add("order", 0);
   String annMappingName = parserContext.getReaderContext().registerWithGeneratedName(annMappingDef);

   RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
   RuntimeBeanReference validator = getValidator(element, source, parserContext);
   
   RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
   bindingDef.setSource(source);
   bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   bindingDef.getPropertyValues().add("conversionService", conversionService);
   bindingDef.getPropertyValues().add("validator", validator);

   RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
   annAdapterDef.setSource(source);
   annAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   annAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
   annAdapterDef.getPropertyValues().add("messageConverters", getMessageConverters(source));
   String annAdapterName = parserContext.getReaderContext().registerWithGeneratedName(annAdapterDef);

   RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
   csInterceptorDef.setSource(source);
   csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);    
   RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
   mappedCsInterceptorDef.setSource(source);
   mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
   mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
   String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
   
   parserContext.registerComponent(new BeanComponentDefinition(annMappingDef, annMappingName));
   parserContext.registerComponent(new BeanComponentDefinition(annAdapterDef, annAdapterName));
   parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
   parserContext.popAndRegisterContainingComponent();
   
   return null;
}
...
}

(1)注册DefaultAnnotationHandlerMapping bean,具体负责请求与@Controller的mapping.

(2)注册AnnotationMethodHandlerAdapter bean,调用@Controller的方法,并注入webBindingInitializer属性和messageConverters。

(3)其中webBindingInitializer指定conversionService,validator属性。

3.1

Configures the conversionService if specified, otherwise defaults to a fresh ConversionService instance created by the default FormattingConversionServiceFactoryBean.

3.2

Configures the validator if specified, otherwise defaults to a fresh Validator instance created by the default LocalValidatorFactoryBean if the JSR-303 API is present on the classpath.

3.3

Configures standard HttpMessageConverters, including the Jaxb2RootElementHttpMessageConverter if JAXB2 is present on the classpath, and the MappingJacksonHttpMessageConverter if Jackson is present on the classpath.

4.<mvc:view-controller>标签

ViewControllerBeanDefinitionParser 负责具体解析。

主要完成 注册一个ParameterizableViewController.和SimpleUrlHandlerMapping ,SimpleControllerHandlerAdapter

SimpleUrlHandlerMapping注册的名称为"org.springframework.web.servlet.config.viewControllerHandlerMapping";

5.<mvc:interceptors>标签

注册一组MappedInterceptor 。有公共和私有之分。

<bean>标签对应公共interceptor;

<mvc:interceptor和<mvc:mapping>对应Mapping私有。

6.<mvc:resources>标签

ResourcesBeanDefinitionParser负责解析。

完成工作

(6.1) 注册一个 ResourceHttpRequestHandler

(6.2)注册一个 SimpleUrlHandlerMapping for mapping resource

(6.3) 可能会注册一个HttpRequestHandlerAdapter.

(由AbstractHttpRequestHandlerBeanDefinitionParser.registerHandlerAdapterIfNecessary实现)

@Override
public void doParse(Element element, ParserContext parserContext) {
   Object source = parserContext.extractSource(element);
   registerResourceMappings(parserContext, element, source);
}

欢迎工作一到五年的Java工程师朋友们加入Java高并发: 957734884,群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容