Spring5源码(17)-Spring解析xml默认命名空间

简介: Spring5源码(17)-Spring解析xml默认命名空间


上一节分析了Spring将xml文件转换为Document对象的过程,接下来Spring会对已转换的Document对象进行解析。打开XmlBeanDefinitionReader类的registerBeanDefinitions方法。打开DefaultBeanDefinitionDocumentReader类的doRegisterBeanDefinitions方法。

1. 引言

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 1、创建documentReader对象
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 2、读取已经注册的bean个数
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 3、解析Document并注册BeanDefinition
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 4、返回本次注册的bean个数
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

该方法总共分为了四步,第三步是执行对Document对象解析和注册BeanDefinition。先来看对Document对象的解析过程。

2.doRegisterBeanDefinitions方法分析

protected void doRegisterBeanDefinitions(Element root) {
    // 1、创建BeanDefinitionParserDelegate对象,用来解析Element元素
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    // 2、解析并验证profile节点,如果配置了profile属性,则验证当前环境是否激活了对应的profile节点,
    // 用于多开发环境配置,该方式在开发中已不多见。
    // 例如:System.setProperty("spring.profiles.active", "dev");
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec,
                    BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }
    // 3、解析前置处理,空的模板方法
    preProcessXml(root);
    // 4、解析并注册BeanDefinition
    parseBeanDefinitions(root, this.delegate);
    // 5、解析后置处理,空的模板方法
    postProcessXml(root);
    this.delegate = parent;
}

doRegisterBeanDefinitions方法并没有自己去执行对xml节点的解析而是委托给了BeanDefinitionParserDelegate,通过代理去解析xml节点。Spring的xml文件又可能包含默认命名空间和自定义命名空间,我们来看Spring是如何进行处理的。

3.parseBeanDefinitions方法简析

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 1、解析默认命名空间
    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;
                // 默认命名空间
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                // 自定义命名空间
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    // 2、解析自定义命名空间
    else {
        delegate.parseCustomElement(root);
    }
}

这与上一节的xml解析分析大致相同,只是增加了对命名空间的判断,针对不同的命名空间使用不同的方法进行解析。先来看对默认命名空间中的标签的解析。

4.parseDefaultElement解析默认标签

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 1、解析import标签,并注册beanDefinition
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    // 2、解析alias标签,并注册别名
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    // 3、解析bean标签,并注册beanDefinition
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    // 4、解析beans标签
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        doRegisterBeanDefinitions(ele);
    }
}

代码到这里,相信大家已经有所了然了,日常开发中的常见标签已经展现出来了(虽然现在提倡无配置文件,但是还是要对其原理有所了解)。parseDefaultElement方法对import、alias、bean、beans四种标签分别做了单独处理,bean标签的解析最复杂也是一个比较基础的操作,先来看对bean标签的解析,如果大家能够了解bean的解析,那么其他的解析过程相信大家都能看懂,因为相对而言要比解析bean标签简单的多。

5.解析Bean标签

/**
 * 解析bean标签将其转换为definition并注册到BeanDefinitionRegistry
 * Process the given bean element, parsing the bean definition and registering it with the registry.
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 1、将解析的节点信息封装至BeanDefinitionHolder对象
    // BeanDefinitionHolder-->封装了BeanDefinition,beanName以及aliases
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        // 2、装饰BeanDefinition
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            // 3、执行注册
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        // 4、发送注册事件
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

可以看到Spring将解析之后的内容封装至了BeanDefinitionHolder类,该类封装了BeanDefinition,beanName以及aliases,BeanDefinition封装了bean标签的各种信息。

该方法还执行了对BeanDefinition的注册,留在下节分析。来看bean标签解析的具体过程。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 1、解析bean的id和bean的别名
    // 获取id标签属性
    String id = ele.getAttribute(ID_ATTRIBUTE);
    // 获取name(别名)标签属性,缓存至List集合
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    List<String> aliases = new ArrayList<>();
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }
    // 如果bean的id为空,但是别名不为空的话,那么默认采用第一个别名作为beanName
    // 例如:<bean class="com.lyc.cn.v2.day01.Dog" name="myDog1,myDog2"/>,使用myDog1作为beanName
    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
    }
    // 2、containingBean不为空,则检查beanName和别名是否被使用
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }
    // 3、解析bean标签,将其转换为BeanDefinition对象
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    // 4、如果beanDefinition且未配置bean的id属性,name属性,则为当前bean生成id和别名
    // 例如:<bean class="com.lyc.cn.v2.day01.Dog"/>,<bean factory-bean="dog4"/>,<bean parent="outer"/>等
    if (beanDefinition != null) {
        if (!StringUtils.hasText(beanName)) {
            try {
                // containingBean不为null,则当前bean是内部bean,使用BeanDefinitionReaderUtils生成beanName
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                } else {
                    // 否则,使用XmlReaderContext对象生成beanName
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    // Register an alias for the plain bean class name, if still possible,
                    // if the generator returned the class name plus a suffix.
                    // This is expected for Spring 1.2/2.0 backwards compatibility.
                    // 如果生成器返回的是类名加上后缀,则为普通bean类名注册一个别名。这在Spring 1.2/2.0的向后兼容性中是可以实现的。
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null
                            && beanName.startsWith(beanClassName)
                            && beanName.length() > beanClassName.length()
                            && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
            } catch (Exception ex) {
                error(ex.getMessage(), ele);
                return null;
            }
        }
        // 5、创建并返回BeanDefinitionHolder对象
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }
    return null;
}

首先对id,name属性进行解析,该过程比较简单,接下来就是对bean中其他属性的解析。来看parseBeanDefinitionElement方法。

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    this.parseState.push(new BeanEntry(beanName));
    // 1、解析class属性
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    // 2、解析parent属性
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }
    try {
        // 3、创建AbstractBeanDefinition对象
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        // 4、解析bean标签属性
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
        // 5、解析meta标签
        parseMetaElements(ele, bd);
        // 6、解析lookup-method属性
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        // 7、解析replace-method属性
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        // 8、解析构造函数参数
        parseConstructorArgElements(ele, bd);
        // 9、解析property属性
        parsePropertyElements(ele, bd);
        // 10、解析qualifier属性
        parseQualifierElements(ele, bd);
        // 11、设置bean定义来源和元数据的来源
        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));
        return bd;
    } catch (ClassNotFoundException ex) {
        error("Bean class [" + className + "] not found", ele, ex);
    } catch (NoClassDefFoundError err) {
        error("Class that bean class [" + className + "] depends on not found", ele, err);
    } catch (Throwable ex) {
        error("Unexpected failure during bean definition parsing", ele, ex);
    } finally {
        this.parseState.pop();
    }
    return null;
}

该方法涉及的细节较多,篇幅限制,只对其中比较重要比较常见的方法进行分析。分析第四步解析bean标签属性。

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
                                                                @Nullable BeanDefinition containingBean,
                                                                AbstractBeanDefinition bd) {
    // 1、设置bean作用域
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
        error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    } else if (containingBean != null) {
        // Take default from containing bean in case of an inner bean definition.
        // 未明确指定bean的作用域,且当前被解析bean是内部bean的话,
        // 则默认使用outer bean的的作用域作为当前bean的作用域
        // 例如:下面的配置,解析到inner属性时,inner未指定作用域,则使用outer的作用域,也就是prototype
        /**
        <bean id="outer" class="com.lyc.cn.v2.day01.inner.Outer" scope="prototype">
            <property name="inner">
                <bean id="inner" class="com.lyc.cn.v2.day01.inner.Inner"/>
            </property>
         </bean>
         **/
        bd.setScope(containingBean.getScope());
    }
    // 2、设置abstract属性
    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }
    // 3、设置lazy-init(延迟加载)属性;
    // 如果该属性为true的话,ApplicationContext容器在初始化时不会加载该bean;
    // 而是在第一次向容器索取该bean时才会被初始化
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (DEFAULT_VALUE.equals(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
    // 4、设置autowire属性,此属性默认不开启
    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
    bd.setAutowireMode(getAutowireMode(autowire));
    // 5、设置depends-on属性,如果BeanA依赖于BeanB,可通过depends-on属性使BeanB在BeanA之前完初始化
    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }
    // 6、设置autowire-candidate属性,当一个接口有多个实现类时,
    // 配置autowire-candidate属性可以明确指定实现类是否参与自动注入
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
        String candidatePattern = this.defaults.getAutowireCandidates();
        if (candidatePattern != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
            bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    } else {
        bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }
    // 7、设置primary属性,当byType注入有多个类型时,
    // 可以指定primary="true",提高注入的优先级,避免抛出异常
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }
    // 8、设置init-method,bean初始化完成后回调该方法
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        bd.setInitMethodName(initMethodName);
    }
    else if (this.defaults.getInitMethod() != null) {
        // 尝试从DocumentDefaultsDefinition对象中获取init-method属性
        // DocumentDefaultsDefinition对象保存了Spring bean的一些简单设置,
        // 我们可以通过该类设定通用的bean属性模板
        bd.setInitMethodName(this.defaults.getInitMethod());
        bd.setEnforceInitMethod(false);
    }
    // 9、设置destroy-method属性,bean销毁后回调该方法
    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
        String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
        bd.setDestroyMethodName(destroyMethodName);
    }
    else if (this.defaults.getDestroyMethod() != null) {
        // 尝试从DocumentDefaultsDefinition对象中获取destroy-method属性
        bd.setDestroyMethodName(this.defaults.getDestroyMethod());
        bd.setEnforceDestroyMethod(false);
    }
    // 10、设置factory-method属性,该属性可指定静态工厂或实例工厂方法实例化bean
    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    // 11、设置factory-bean属性,一般和factory-method属性一起使用,
    // 指定工厂bean和工厂bean的工厂方法
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }
    return bd;
}

其他的方法大家可以通过在配置文件中配置属性,debug跟踪调试,不在一一赘述。到这里Spring已经将xml中的配置信息封装至BeanDefinitionHolder对象,接下来就可以执行对bean的注册了。

另外对bean name的生成策略简单分析一下:

上面parseBeanDefinitionElement方法已经有所简介,来看一下具体过程:

public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws
            BeanDefinitionStoreException {
    // 1、获取bean的className
    String generatedBeanName = definition.getBeanClassName();
    // 2、如果generatedBeanName为null,
    // 则判断其是否有parent属性或者factory-bean并生成generatedBeanName
    if (generatedBeanName == null) {
        // parent属性不为空
        if (definition.getParentName() != null) {
            generatedBeanName = definition.getParentName() + "$child";
        }
        // factory-bean属性不为空
        else if (definition.getFactoryBeanName() != null) {
            generatedBeanName = definition.getFactoryBeanName() + "$created";
        }
    }
    // 3、如果上述方法均无法生成generatedBeanName,则抛出异常
    if (!StringUtils.hasText(generatedBeanName)) {
        throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither "
                + "'class' nor 'parent' nor 'factory-bean' - can't "
                + "generate bean name");
    }
    // 4、判断是否内部bean,并根据生成的generatedBeanName,拼接最终的beanName返回
    String id = generatedBeanName;
    if (isInnerBean) {
        // Inner bean: generate identity hashcode suffix.
        // GENERATED_BEAN_NAME_SEPARATOR-->#
        id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
    } else {
        // Top-level bean: use plain class name.
        // Increase counter until the id is unique.
        int counter = -1;
        while (counter == -1 || registry.containsBeanDefinition(id)) {
            counter++;
            // GENERATED_BEAN_NAME_SEPARATOR-->#
            id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
        }
    }
    return id;
}

这一部分的代码比较多,也比较细,一时间找不到应该如何写才能让读者更清晰的了解解析过程,感兴趣的多多调试跟踪代码吧。





目录
相关文章
|
1月前
|
数据采集 人工智能 Java
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
DevDocs是一款基于智能爬虫技术的开源工具,支持1-5层深度网站结构解析,能将技术文档处理时间从数周缩短至几小时,并提供Markdown/JSON格式输出与AI工具无缝集成。
105 1
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
|
1月前
|
安全 Java API
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
119 2
|
2月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
194 1
|
1月前
|
前端开发 安全 Java
Spring Boot 便利店销售系统项目分包设计解析
本文深入解析了基于Spring Boot的便利店销售系统分包设计,通过清晰的分层架构(表现层、业务逻辑层、数据访问层等)和模块化设计,提升了代码的可维护性、复用性和扩展性。具体分包结构包括`controller`、`service`、`repository`、`entity`、`dto`、`config`和`util`等模块,职责分明,便于团队协作与功能迭代。该设计为复杂企业级应用开发提供了实践参考。
68 0
|
1月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
226 70
|
1月前
|
Java 关系型数据库 MySQL
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
180 11
|
1月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
141 5
|
1月前
|
安全 Java 数据安全/隐私保护
Spring Security: 深入解析 AuthenticationSuccessHandler
本文深入解析了 Spring Security 中的 `AuthenticationSuccessHandler` 接口,它用于处理用户认证成功后的逻辑。通过实现该接口,开发者可自定义页面跳转、日志记录等功能。文章详细讲解了接口方法参数及使用场景,并提供了一个根据用户角色动态跳转页面的示例。结合 Spring Security 配置,展示了如何注册自定义的成功处理器,帮助开发者灵活应对认证后的多样化需求。
62 2
|
1月前
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
73 0
|
5月前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
126 6

推荐镜像

更多