Spring源码分析(一)Spring的初始化和XML解析

前言

Spring是什么?它是一个应用程序框架,为应用程序的开发提供强大的支持,例如对事务处理和持久化的支持等;它也是一个bean容器,管理bean对象的整个生命周期,维护bean的各种存在状态,例如bean对象的实例化、销毁、bean的单实例和多实例状态等。
Spring作为Java发展史上不可忽视的存在,说他重新定义了Java也不为过。它功能强大,着实为日常开发提供了大大的便利。表面越简单的东西,背后越复杂。
从本章节开始,我们一起分析Spring的源码,看它到底是怎么样来实现我们常说常用的诸如IOC、Annotation、AOP、事务等功能的。

1、Spring的入口

在我们的项目中,web.xml必不可少,其中就定义了Spring的监听器。

<listener>
      <listener-class>
            org.springframework.web.context.ContextLoaderListener
      </listener-class>
</listener>

我们来看ContextLoaderListener类,可以看到它实现了ServletContextListener接口,
contextInitialized就是Spring初始化的入口方法。
Spring还有一个入口,叫做org.springframework.web.servlet.DispatcherServlet,它们之间是父子容器的关系,最终都会调用到同一个方法org.springframework.context.support.AbstractApplicationContext.refresh()

2、初始化

Spring的初始化第一步就是要加载配置文件,然后解析里面的配置项。
ok,我们来到XmlWebApplicationContext类的loadBeanDefinitions方法。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

可以看到,configLocations是一个数组,它获取的就是配置文件。在笔者的项目中,只有一个配置文件,名字是applicationContext.xml。下一步就是通过loadBeanDefinitions这个方法解析这个配置文件。

3、解析XML配置

首先把一个配置文件封装成一个Resource对象,然后获取Resource对象的输入流,转换成InputSource对象,最后解析成Document对象。下面代码只保留了主要部分。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) 
                         throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                //这里的location就是配置文件-applicationContext.xml,转成Resource对象
                Resource[] resources=resourceLoader).getResources(location);
                //获取resources对象的输入流 再转成JDK的InputSource对象,最后解析成Document
                InputStream inputStream = resources.getInputStream();
                InputSource inputSource = new InputSource(inputStream);
                Document doc = doLoadDocument(inputSource, resource);
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
    }

applicationContext.xml配置文件解析成Document对象,它的Root节点信息如下:

[   
    [#text:], 
    [context:component-scan: null], 
    [#text:], 
    [bean: null], 
    [#text:], 
    [bean: null], 
    [#text:], 
    [bean: null],
    [#text:], 
    [bean: null], 
    [#text:], 
    [#comment:  指定了表现层资源的前缀和后缀
        viewClass:JstlView表示JSP模板页面需要使用JSTL标签库
        prefix 和suffix:查找视图页面的前缀和后缀,比如传进来的逻辑视图名为hello,则该该
                        jsp视图页面应该存放在“WEB-INF/jsp/hello.jsp”], 
    [#text:], 
    [bean: null], 
    [#text: ]
]

4、加载Bean信息

上一步我们看到Spring已经把applicationContext.xml这个配置文件解析成了Document对象,接下来就是关键的一步。先看源码

    //这里拿到的是Document对象的根节点,根节点信息参考上图
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    //这里有两个分支。
                    //一个是处理默认的节点(import、alias、bean、beans)
                    //一个是处理自定义的节点(context:component-scan)
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

4.1 component-scan的解析

首先定位到自定义解析方法delegate.parseCustomElement(ele);
最终调用了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext),不过它是怎么调用到这个类的呢?说起来就比较有意思了。
我们先来看Spring里面的一个配置文件,/META-INF/spring.handlers

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

这里面配置了不同标签的处理类,比如context标签处理类就是ContextNamespaceHandler,然后通过反射实例化这个处理类,调用它的init()方法。init()方法里面它又注册了一堆处理类,其中就有我们很感兴趣的component-scan。

    public NamespaceHandler resolve(String namespaceUri) {
        //handlerMappings里有个方法loadAllProperties(),获取Spring所有的配置项
        Map<String, Object> handlerMappings = getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            String className = (String) handlerOrClassName;
            try {
                //以context:component-scan举例
                //这里拿到的className就是org.springframework.context.config.ContextNamespaceHandler
                //通过反射,实例化这个ContextNamespaceHandler,然后调用init方法
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                NamespaceHandler namespaceHandler = BeanUtils.instantiateClass(handlerClass);
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        }
    }
    public void init() {
        registerBeanDefinitionParser("annotation-config",
             new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan",
             new ComponentScanBeanDefinitionParser());
        //...未完
    }

最终Spring就可以通过component-scan这个标签,拿到ComponentScanBeanDefinitionParser类,调用它的parse()方法。

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //获取包扫描路径,对应配置文件中的base-package="com.viewscenes.netsupervisor"
        String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
        basePackage = parserContext.getReaderContext().getEnvironment().
              resolvePlaceholders(basePackage);
        //这里可能有多个包路径,分割成数组
        String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

        /**
         * configureScanner 配置扫描器。
         * scanner.doScan 扫描执行
         * registerComponents 这里重点是对registerComponents的支持
         * 
         * @return
         */
        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
        Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
        registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

        return null;
    }
4.1.1 configureScanner 配置扫描器

这里面重点就是注册了默认的过滤器。use-default-filters,默认值是true,如果配置文件配置了此属性的值为false,有些注解就加不进来,到下一步扫描的时候就注册不了Bean。

protected void registerDefaultFilters() {
    //这个就是配置的use-default-filters,如果配置了false。那么下面的
    // Component、ManagedBean、Named注解都不会被扫描到
    if (useDefaultFilters) { 
        this.includeFilters.add(new AnnotationTypeFilter(Component.class));
        ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
        try {
            this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), 
                                                                          false));
            logger.debug("JSR-250 'javax.annotation.ManagedBean' 
                                              found and supported for component scanning");
        }
        catch (ClassNotFoundException ex) {
            // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
        }     
       //...未完
    }
}
4.1.2 doScan扫描

doScan分为三个步骤。

  • findCandidateComponents 扫描包路径下的所有class文件,过滤有Component注解的类,转换成BeanDefinition对象,加入一个LinkedHashSet中。
  • 循环上一步返回的LinkedHashSet,设置基本属性,比如setLazyInit、setScope。
  • 注册BeanDefinition对象,向Map容器中缓存beanName和BeanDefinition,向List中加入beanName。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        for (String basePackage : basePackages) {
            //findCandidateComponents方法扫描class文件,判断Component注解,转成BeanDefinition对象返回。
            //值得注意的是,Component不止是@Component,还有
            //@Controller、@Service、@Repository,因为在这三个注解上面还有个@Component。
          //这就相当于它们都是Component的子注解。
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.
                                                            resolveScopeMetadata(candidate);
                //设置属性,没有配置的都是默认值
                candidate.setScope(scopeMetadata.getScopeName());
                candidate.setxxx(scopeMetadata.getxxxName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                //registerBeanDefinition方法 注册BeanDefinition,等同于下面两句
                //this.beanDefinitionMap.put(beanName, beanDefinition);
                //this.beanDefinitionNames.add(beanName);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
        return beanDefinitions;
    }

最后整个方法返回的就是beanDefinition对象的Set集合,以两个Controller为例。

[   
    Generic bean: class [com.viewscenes.netsupervisor.controller.IndexController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\IndexController.class], 
    
    Generic bean: class [com.viewscenes.netsupervisor.controller.UserController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\UserController.class]
]
4.1.3 对annotation-config的支持

我们知道,在Spring配置文件有个配置是context:annotation-config 但如果配置了context:component-scan 就不必再配置config,这是因为在解析component-scan的时候已经默认添加了annotation-config的支持,除非你手动设置了annotation-config="false",不过这可不太妙,因为在IOC的时候就没办法支持@Autowired等注解了。

protected void registerComponents(XmlReaderContext readerContext, 
                      Set<BeanDefinitionHolder> beanDefinitions, Element element) {
    boolean annotationConfig = true;
    if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
        annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
    }
    if (annotationConfig) { //判断annotation-config属性的值
        Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
        if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
            beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
        if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
            beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
        if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
            beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
            ......未完
    }
}

4.2 bean标签的解析

bean标签的解析,就是默认的处理方法。

  • 获取bean标签的id,并且把beanName赋值为id,设置别名。新建AbstractBeanDefinition对象,通过反射设置beanClass,解析property属性名称和值。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
        //获取bean_id 
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String beanName = id;
        AbstractBeanDefinition beanDefinition = 
                              parseBeanDefinitionElement(ele, beanName, containingBean);
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        //最后返回已经包含了beanName、class对象和一系列方法的BeanDefinition对象
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, 
                                    String beanName, BeanDefinition containingBean) {
        String className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        try {
            //根据className反射设置setBeanClass和setBeanClassName
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);
            //设置默认方法 setScope、setLazyInit、setAutowireMode...
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            //设置property属性 <bean><property name="id" value="1001"></property></bean>
            parsePropertyElements(ele, bd);
            return bd;
        }
        return null;
}
  • 注册BeanDefinition对象,和component-scan扫描的bean注册一样。向容器中填充对象。
  • 不管是XML配置的Bean,还是通过component-scan扫描注册的Bean它们最后都是殊途同归的,会转换成一个BeanDefinition对象。记录着这个Bean对象的属性和方法,最后都注册到容器中,等待在实例化和IOC的时候遍历它们。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容