Spring IoC自定义标签解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring IoC自定义标签解析

概述


本文接着 Spring IoC之存储对象BeanDefinition 一文继续学习,在学习自定义标签的知识时,首先我们先了解一下自定义标签的实现,欢迎阅读:Spring自定义标签的实现


自定义标签


parseBeanDefinitions()方法中有这么一段代码:


if (delegate.isDefaultNamespace(ele)) {
    this.parseDefaultElement(ele, delegate);
} else {
    delegate.parseCustomElement(ele);
}
复制代码


如果传入的标签不是默认标签,则调用 parseCustomElement(ele)方法,该方法定义如下:


public BeanDefinition parseCustomElement(Element ele) {
    return this.parseCustomElement(ele, (BeanDefinition)null);
}
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 获取 namespaceUri
    String namespaceUri = this.getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    } else {
        // 根据 namespaceUri 获取相应的 Handler
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            // 调用自定义的 Handler 处理
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }
}
复制代码


处理过程分为三步:

  1. 获取标签的命名空间
  2. 读取自定义标签处理器
  3. 标签解析


获取标签的命名空间


标签的解析是从命名空间的提起开始的,无论是区分 Spring 中默认标签和自定义标签 还是 区分自定义标签中不同标签的处理器都是以标签所提供的命名空间为基础的,而至于如何提取对应元素的命名空间其实并不需要我们亲内去实现,在 org.w3c.dom.Node 中已经提供了方法供我们直接调用:


String namespaceUri = getNamespaceURI(ele);
@Nullable
public String getNamespaceURI(Node node) {
    return node.getNamespaceURI();
}
复制代码


在代码调试过程中可以看到此处的数据,如下图所示:


image.png


读取自定义标签处理器


根据 namespaceUri 获取 Handler,这个映射关系我们在 Spring.handlers 中已经定义了,所以只需要找到该类,然后初始化返回,最后调用该 Handler 对象的 parse() 方法处理,该方法我们也提供了实现。所以上面的核心就在于怎么找到该 Handler 类。调用方法为


this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)
复制代码


首先 this.readerContext.getNamespaceHandlerResolver()返回了一个 NamespaceHandlerResolver 对象,该对象在 registerBeanDefinitions 中的 createReaderContext()方法设置的,其中涉及到以下方法:


public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.getNamespaceHandlerResolver());
}
复制代码


XmlReaderContext 构造函数中最后一个参数就是 NamespaceHandlerResolver 对象,该对象由 getNamespaceHandlerResolver() 提供,如下:


public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
    return new DefaultNamespaceHandlerResolver(cl);
}
复制代码


所以 getNamespaceHandlerResolver().resolve(namespaceUri) 调用的就是 DefaultNamespaceHandlerResolver 的 resolve()。如下:


public NamespaceHandler resolve(String namespaceUri) {
    // 获取handlerMapping对象,其键为当前的命名空间url,
    // 值为当前命名空间的处理逻辑类对象,或者为处理逻辑类的包含全路径的类名
    Map<String, Object> handlerMappings = this.getHandlerMappings();
    // 查看是否存在当前url的处理类逻辑,没有则返回null
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    } else if (handlerOrClassName instanceof NamespaceHandler) {
        // 如果存在当前url对应的处理类对象,则直接返回该处理对象
        return (NamespaceHandler)handlerOrClassName;
    } else {
        // 如果当前url对应的处理逻辑还是一个没初始化的全路径类名,则通过反射对其进行初始化
        String className = (String)handlerOrClassName;
        try {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            } else {
                // 初始化类
                NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
                // 调用 init() 方法
                namespaceHandler.init();
                // 记录在缓存
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        } catch (ClassNotFoundException var7) {
            throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var7);
        } catch (LinkageError var8) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var8);
        }
    }
}
复制代码


首先调用 getHandlerMappings() 获取所有配置文件中的映射关系 handlerMappings ,该关系为 <命名空间,类路径>,然后根据命名空间 namespaceUri 从映射关系中获取相应的信息,如果为空或者已经初始化了就直接返回,否则根据反射对其进行初始化,同时调用其 init() 方法,最后将该 Handler 对象缓存。 init() 方法主要是将自定义标签解析器进行注册,如我测试代码中的 init()


public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
//        registerBeanDefinitionParser("car",new CarBeanDefinitionParser());
        registerBeanDefinitionParser("xxx",new CarParser(Car.class));
    }
}
复制代码


当得到自定义命名空间处理后会马上执行 namespaceHandler.init() 来进行自定义 BeanDefinitionParser的注册,在这里,你可以注册多个标签解析器。init()中的 registerBeanDefinitionParser 方法 其实就是将映射关系放在一个 Map 结构的 parsers 对象中:private final Map parsers


标签解析


得到了解析器和分析的元素后,Spring 就可以将解析工作委托给自定义解析器去解析了,对于标签的解析使用的是:NamespaceHandler.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法,进入到方法体内:


public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = this.findParserForElement(element, parserContext);
    return parser != null ? parser.parse(element, parserContext) : null;
}
复制代码


调用 findParserForElement() 方法获取 BeanDefinitionParser 实例,其实就是获取在 init() 方法里面注册的实例对象。如下:


private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    //获取元素名称,也就是<myTag:xxx中的 xxx
    String localName = parserContext.getDelegate().getLocalName(element);
    //根据 user 找到对应的解析器,也就是在
    //registerBeanDefinitionParser("xxx",new CarParser(Car.class));
    //中注册的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}
复制代码


获取 localName,在上面的例子中就是 : xxx,然后从 Map 实例 parsers 中获取 CarParser 实例对象。返回 BeanDefinitionParser 对象后,调用其 parse(),该方法在 CarParser 中实现:


image.png


我们在 CarParser 类中定义了两个方法如下:


public class CarParser implements BeanDefinitionParser {
    private Class<?> beanclass;
    public CarParser(Class<?> beanclass) {
        this.beanclass = beanclass;
    }
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanclass);
        beanDefinition.setLazyInit(false);
        String brand = element.getAttribute("brand");
        String color = element.getAttribute("color");
        double price = Double.valueOf(element.getAttribute("price"));
        int maxSpeed = Integer.valueOf(element.getAttribute("speed"));
        beanDefinition.getPropertyValues().add("brand",brand);
        beanDefinition.getPropertyValues().add("color",color);
        beanDefinition.getPropertyValues().add("price",price);
        beanDefinition.getPropertyValues().add("maxSpeed",maxSpeed);
        BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
//        beanDefinitionRegistry.registerBeanDefinition(beanclass.getName(),beanDefinition);//注册bean到BeanDefinitionRegistry中
        String id = element.getAttribute("id");
        beanDefinitionRegistry.registerBeanDefinition(id,beanDefinition);
        return beanDefinition;
    }
}
复制代码


自定义的 parse()方法首先定义创建一个 RootBeanDefinition,setBeanClass()方法相当于标签中的 class 属性,通过 element 获取到各个属性填写的值,然后填充到 beanDefinition 的 propertyValues 属性中,用于之后 bean 实例的创建。id 属性还是用来标识标签,最后将 id 和 beanDefinition 一起注册到BeanDefinitionRegistry 中。


至此,自定义标签的解析过程已经分析完成了。其实整个过程还是较为简单:首先会加载 handlers 文件,将其中内容进行一个解析,形成  这样的一个映射,然后根据获取的 namespaceUri 就可以得到相应的类路径,对其进行初始化等到相应的 Handler 对象,调用 parse() 方法,在该方法中根据标签的 localName 得到相应的 BeanDefinitionParser 实例对象,调用 parse() ,该方法定义在 BeanDefinitionParser 的实现类中。对于自定义的 Parser 类,首先需要与 bean 类相关联,这里我们采用的是设置个 beanclass 属性, 最为重要的是 doParse()方法,该方法将标签中填写的属性值,填充到一个新的 BeanDefinition 中,最后将其注册到 BeanDefinitionRegistry 中 。



目录
相关文章
|
4天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
3天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
24天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
44 2
|
29天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
112 2
|
29天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
52 2
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
83 0
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
67 0
|
4天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
4天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多