文章摘要(AI生成)
7. 空指针安全尽管 Java 不允许您使用其类型系统来表达 null 安全性,但 Spring 框架现在在org.springframework.lang包中提供了以下注解,以便您声明 API 和字段的可空性:@Nullable: 表示特定参数、返回值或字段可以是 的注解null。@NonNull
7. 空指针安全
尽管 Java 不允许您使用其类型系统来表达 null 安全性,但 Spring 框架现在在org.springframework.lang
包中提供了以下注解,以便您声明 API 和字段的可空性:
@Nullable
: 表示特定参数、返回值或字段可以是null
的注解。@NonNull
: 用于指示特定参数、返回值或字段不能出现null
的注解(分别适用于@NonNullApi
和@NonNullFields
的参数、返回值和字段不需要)。@NonNullApi
: 包级别的注解,将非 null 声明为参数和返回值的默认语义。@NonNullFields
:包级别的注解,将非空声明为字段的默认语义。
Spring 框架本身利用了这些注解,但它们也可以在任何基于 Spring 的 Java 项目中用于声明空安全 API 和可选的空安全字段。尚不支持泛型类型参数、可变参数和数组元素可空性,但应在即将发布的版本中提供, 有关最新信息,请参阅SPR-15942 。可空性声明预计将在 Spring Framework 版本之间进行微调,包括次要版本。方法体内使用的类型的可空性超出了此功能的范围。
其他常见的库,如 Reactor 和 Spring Data 提供了使用类似可空性安排的空安全 API,为 Spring 应用程序开发人员提供一致的整体体验。
7.1. 用例
除了为 Spring Framework API 可空性提供显式声明之外,IDE(例如 IDEA 或 Eclipse)可以使用这些注解来提供与空安全性相关的有用警告,以避免在运行时报NullPointerException
。
它们还用于在 Kotlin 项目中使 Spring API 为空安全,因为 Kotlin 原生支持空安全。Kotlin 支持文档中提供了更多详细信息。
7.2. JSR-305 元注解
Spring 注解使用JSR 305 注解(一种休眠但广泛传播的 JSR)进行元注解。JSR-305 元注解让 IDEA 或 Kotlin 等工具供应商以通用方式提供空安全支持,而无需对 Spring 注解进行硬编码支持。
没有必要也不建议将 JSR-305 依赖项添加到项目类路径以利用 Spring 空安全 API。只有在代码库中使用空安全注解的基于 Spring 的库等项目才应添加 com.google.code.findbugs:jsr305:3.0.2
以及compileOnly
Gradle 配置或 Maven provided
的范围,以避免编译器警告。
8. 数据缓冲器和编解码器
Java NIO 提供了ByteBuffer
但许多库在其上构建自己的字节缓冲区 API,特别是对于重用缓冲区和/或使用直接缓冲区有利于性能的网络操作。例如,Netty 有ByteBuf
层次结构,Undertow 使用 XNIO,Jetty 使用池化字节缓冲区和要释放的回调,等等。spring-core
模块提供了一组抽象来处理各种字节缓冲区 API,如下所示:
DataBufferFactory
抽象数据缓冲区的创建。DataBuffer
表示一个字节缓冲区,它可以被 池化。DataBufferUtils
为数据缓冲区提供实用方法。- codes解码器将数据缓冲区流解码或编码为更高级别的对象。
8.1. DataBufferFactory
DataBufferFactory
用于通过以下两种方式之一创建数据缓冲区:
- 分配一个新的数据缓冲区,如果知道的话,可以选择预先指定容量,即使实现
DataBuffer
可以按需增长和缩小,这也会更有效。 - 包装现有的
byte[]
或java.nio.ByteBuffer
,它使用DataBuffer
实现装饰给定数据并且不涉及分配。
请注意,WebFlux 应用程序不会直接创建 DataBufferFactory
,而是通过客户端上的ServerHttpResponse
或ClientHttpRequest
访问它。工厂的类型取决于底层的客户端或服务器,例如对于 Reactor Netty是 NettyDataBufferFactory
,对于其他的是DefaultDataBufferFactory
。
8.2. DataBuffer
DataBuffer
接口提供与java.nio.ByteBuffer
类似的操作,但还带来了一些额外的好处,其中一些好处是受到 Netty ByteBuf
的启发。以下是部分功能列表:
- 以独立位置读取和写入,即不需要调用
flip()
来在读取和写入之间交替。 - 容量随需扩展
java.lang.StringBuilder
。 - 通过
PooledDataBuffer
池化缓冲区和引用计数 - 将缓冲区视为
java.nio.ByteBuffer
、InputStream
或OutputStream
。 - 确定给定字节的索引或最后一个索引。
8.3.PooledDataBuffer
正如 ByteBuffer的 Javadoc 中所解释的,字节缓冲区可以是直接的或非直接的。直接缓冲区可以驻留在 Java 堆之外,这消除了对本地 I/O 操作进行复制的需要。这使得直接缓冲区对于通过套接字接收和发送数据特别有用,但它们的创建和释放成本也更高,这导致了池化缓冲区的想法。
PooledDataBuffer
是它的扩展,DataBuffer
它有助于引用计数,这对于字节缓冲池至关重要。它是如何工作的?当 aPooledDataBuffer
被分配时,引用计数为 1。调用retain()
递增计数,调用release()
递减计数。只要计数大于0,就保证缓冲区不会被释放。当计数减少到 0 时,可以释放池化缓冲区,这实际上可能意味着为缓冲区保留的内存返回到内存池。
请注意,PooledDataBuffer
与其直接操作,在大多数情况下,最好使用DataBufferUtils
应用版本中的便捷方法或 DataBuffer
仅当它是PooledDataBuffer
.
8.4.DataBufferUtils
DataBufferUtils
提供了许多实用方法来操作数据缓冲区:
- 如果底层字节缓冲区 API 支持,则将数据缓冲区流加入可能具有零副本的单个缓冲区,例如通过复合缓冲区。
- 将
InputStream
或 NIOChannel
变为Flux<DataBuffer>
,反之亦然,Publisher<DataBuffer>
变为OutputStream
或 NIOChannel
。 - 如果缓冲区是
PooledDataBuffer
的实例,则释放或保留DataBuffer
的方法。 - 跳过或从字节流中获取,直到特定的字节数。
8.5.编解码器
org.springframework.core.codec
包提供以下策略接口:
Encoder
:编码Publisher<T>
成数据缓冲区流。Decoder
:解码Publisher<DataBuffer>
成更高级别的对象流。
spring-core
模块提供byte[]
、ByteBuffer
、DataBuffer
、Resource
和 String
编码器和解码器实现。spring-web
模块添加了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器。
8.6. 使用DataBuffer
使用数据缓冲区时,必须特别注意确保缓冲区被释放,因为它们可能被池化。我们将使用编解码器来说明它是如何工作的,但这些概念更普遍适用。让我们看看编解码器必须在内部做什么来管理数据缓冲区。
Decoder
是在创建更高级别对象之前最后读取输入数据缓冲区的,因此它必须按如下方式释放它们:
- 如果
Decoder
简单地读取每个输入缓冲区并准备立即释放它,它可以通过DataBufferUtils.release(dataBuffer)
方法. - 如果
Decoder
正在使用Flux
或Mono
操作符,例如flatMap
,reduce
,以及其他在内部预取和缓存数据项,或者正在使用操作符,例如filter
,skip
,以及其他遗漏项,则doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
方法必须被添加到组合链中以确保这些缓冲区在之前被释放被丢弃,也可能是由于错误或取消信号。 - 如果
Decoder
以任何其他方式保留一个或多个数据缓冲区,则必须确保在完全读取时释放它们,或者在缓存数据缓冲区被读取和释放之前发生错误或取消信号的情况下。
请注意,DataBufferUtils#join
提供了一种将数据缓冲区流聚合到单个数据缓冲区中的安全有效的方法。同样skipUntilByteCount
和 takeUntilByteCount
是解码器使用的其他安全方法。
Encoder
分配了其他必须读取(和释放)的数据缓冲区。所以 Encoder
没有什么可做的。但是,如果在用数据填充缓冲区时发生序列化错误,则Encoder
必须注意释放数据缓冲区。例如:
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
// serialize and populate buffer..
release = false;
}
finally {
if (release) {
DataBufferUtils.release(buffer);
}
}
return buffer;
Encoder
的消费者负责释放它接收到的数据缓冲区。在 WebFlux 应用程序中,Encoder
的输出用于写入 HTTP 服务器响应或客户端 HTTP 请求,在这种情况下,释放数据缓冲区是写入服务器响应或客户端请求的代码的责任.
请注意,在 Netty 上运行时,有用于 解决缓冲区泄漏问题的调试选项。
9. 日志
从 Spring Framework 5.0 开始,Spring 在spring-jcl
模块中实现了自己的 Commons Logging 桥接器。该实现检查类路径中是否存在 Log4j 2.x API 和 SLF4J 1.7 API,并使用找到的第一个作为日志实现,回退到 Java 平台的核心日志工具(也称为JUL或java.util.logging
)如果 Log4j 2.x 和 SLF4J 都不可用。
将 Log4j 2.x 或 Logback(或其他 SLF4J 提供程序)放入您的类路径中,无需任何额外的桥梁,并让框架自动适应您的选择。有关详细信息,请参阅 Spring Boot 日志记录参考文档。
Spring 的 Commons Logging 变体仅用于核心框架和扩展中的基础设施日志记录目的。对于应用程序代码中的日志记录需求,更喜欢直接使用 Log4j 2.x、SLF4J 或 JUL。
可以通过 org.apache.commons.logging.LogFactory
检索 Log
实现,如下例所示。
public class MyBean {
private final Log log = LogFactory.getLog(getClass());
// ...
}
10. 附录
10.1. XML 模式
附录的这一部分列出了与核心容器相关的 XML 模式。
10.1.1. util
库
顾名思义,util
标签处理常见的实用程序配置问题,例如配置集合、引用常量等。要使用util
中的标签,您需要在 Spring XML 配置文件的顶部有以下前导码(片段中的文本引用正确的模式,以便您可以使用util
命名空间中的标签):
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!-- bean definitions here -->
</beans>
使用<util:constant/>
考虑以下 bean 定义:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
前面的配置使用 SpringFactoryBean
实现 ( FieldRetrievingFactoryBean
) 将bean 上的属性值isolation
设置为java.sql.Connection.TRANSACTION_SERIALIZABLE
常量的值。这一切都很好,但它很冗长并且(不必要地)将 Spring 的内部管道暴露给最终用户。
以下基于 XML Schema 的版本更简洁,清楚地表达了开发者的意图(“注入这个常量值”),并且读起来更好:
<bean id="..." class="...">
<property name="isolation">
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</property>
</bean>
从字段值设置 Bean 属性或构造函数参数
FieldRetrievingFactoryBean
是一个FactoryBean
用来检索一个static
或非静态字段值的。它通常用于检索public
static
final
常量,然后可用于为另一个 bean 设置属性值或构造函数参数。
以下示例显示了如何使用 staticField
属性公开static
字段:
<bean id="myField"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>
还有一种方便的使用形式,其中该static
字段被指定为 bean 名称,如以下示例所示:
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
这确实意味着 beanid
是什么不再有任何选择(因此引用它的任何其他 bean 也必须使用这个更长的名称),但是这种形式定义非常简洁,用作内部 bean 非常方便因为id
不必为 bean 引用指定 ,如以下示例所示:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
您还可以访问另一个 bean 的非静态(实例)字段,如 FieldRetrievingFactoryBean
类的 API 文档中所述。
在 Spring 中很容易将枚举值作为属性或构造函数参数注入到 bean 中。您实际上不必对 Spring 内部结构(甚至诸如FieldRetrievingFactoryBean
. 以下示例枚举显示了注入枚举值是多么容易:
package javax.persistence;
public enum PersistenceContextType {
TRANSACTION,
EXTENDED
}
现在考虑以下PersistenceContextType
类型的 setter和相应的 bean 定义:
package example;
public class Client {
private PersistenceContextType persistenceContextType;
public void setPersistenceContextType(PersistenceContextType type) {
this.persistenceContextType = type;
}
}
<bean class="example.Client">
<property name="persistenceContextType" value="TRANSACTION"/>
</bean>
使用<util:property-path/>
考虑以下示例:
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
前面的配置使用 SpringFactoryBean
实现 ( PropertyPathFactoryBean
) 创建一个名为testBean.age
的 bean(类型为int
) ,该 bean的值等于testBean
bean 的age
属性。
现在考虑以下示例,它添加了一个<util:property-path/>
元素:
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<util:property-path id="name" path="testBean.age"/>
path
元素的属性值<property-path/>
遵循beanName.beanProperty
的形式 。在这种情况下,它获取名为 testBean
的 bean 的age
属性。该age
属性的值为10
。
<util:property-path/>
用于设置 Bean 属性或构造函数参数
PropertyPathFactoryBean
是FactoryBean
用于评估给定目标对象上的属性路径的。目标对象可以直接指定,也可以通过 bean 名称指定。然后,您可以在另一个 bean 定义中将此值用作属性值或构造函数参数。
下面的示例显示了一个用于另一个 bean 的路径,按名称:
<!-- target bean to be referenced by name -->
<bean id="person" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 11, which is the value of property 'spouse.age' of bean 'person' -->
<bean id="theAge"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetBeanName" value="person"/>
<property name="propertyPath" value="spouse.age"/>
</bean>
在以下示例中,针对内部 bean 评估路径:
<!-- results in 12, which is the value of property 'age' of the inner bean -->
<bean id="theAge"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetObject">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="12"/>
</bean>
</property>
<property name="propertyPath" value="age"/>
</bean>
还有一种快捷方式,其中 bean 名称是属性路径。以下示例显示了快捷方式:
<!-- results in 10, which is the value of property 'age' of bean 'person' -->
<bean id="person.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
这种形式确实意味着 bean 的名称没有选择。对它的任何引用也必须使用相同的id
,即路径。如果用作内部 bean,则根本不需要引用它,如以下示例所示:
<bean id="..." class="...">
<property name="age">
<bean id="person.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
</property>
</bean>
您可以在实际定义中具体设置结果类型。对于大多数用例来说,这不是必需的,但有时它可能很有用。有关此功能的更多信息,请参阅 javadoc。
使用<util:properties/>
考虑以下示例:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>
前面的配置使用 SpringFactoryBean
的实现 ( PropertiesFactoryBean
) 来实例化java.util.Properties
包含从提供的Resource
位置加载的值。
下面的示例使用一个util:properties
元素进行更简洁的表示:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
使用<util:list/>
考虑以下示例:
<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</list>
</property>
</bean>
前面的配置使用 SpringFactoryBean
实现 ( ListFactoryBean
) 创建一个java.util.List
实例并使用从提供的sourceList
中获取的值对其进行初始化。
下面的示例使用一个<util:list/>
元素进行更简洁的表示:
<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:list>
您还可以使用<util:list/>
元素上的 list-class
属性显式控制实例化和填充的 List
的确切类型。例如,如果我们确实需要实例化 java.util.LinkedList
,我们可以使用以下配置:
<util:list id="emails" list-class="java.util.LinkedList">
<value>jackshaftoe@vagabond.org</value>
<value>eliza@thinkingmanscrumpet.org</value>
<value>vanhoek@pirate.org</value>
<value>d'Arcachon@nemesis.org</value>
</util:list>
如果未提供list-class
属性,则容器选择一个List
实现。
使用<util:map/>
考虑以下示例:
<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' -->
<bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="sourceMap">
<map>
<entry key="pechorin" value="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</map>
</property>
</bean>
前面的配置使用 SpringFactoryBean
的实现 ( MapFactoryBean
) 创建一个java.util.Map
实例,该实例使用从提供的'sourceMap'
.
下面的示例使用一个<util:map/>
元素进行更简洁的表示:
<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
<entry key="pechorin" value="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</util:map>
您还可以使用<util:map/>
元素上的'map-class'
属性显式控制实例化和填充Map
的确切类型。例如,如果我们真的需要实例化java.util.TreeMap
,我们可以使用以下配置:
<util:map id="emails" map-class="java.util.TreeMap">
<entry key="pechorin" value="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</util:map>
如果未提供'map-class'
属性,则容器选择一个Map
实现。
使用<util:set/>
考虑以下示例:
<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' -->
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
<property name="sourceSet">
<set>
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</set>
</property>
</bean>
前面的配置使用 SpringFactoryBean
的实现 (SetFactoryBean
) 创建一个java.util.Set
实例,该实例使用取自sourceSet
所提供的值进行初始化。
下面的示例使用一个<util:set/>
元素进行更简洁的表示:
<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:set>
您还可以使用<util:set/>
元素上的set-class
属性显式控制实例化和填充确切的Set
类型。例如,如果我们真的需要实例化 java.util.TreeSet
,我们可以使用以下配置:
<util:set id="emails" set-class="java.util.TreeSet">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:set>
如果未提供set-class
属性,则容器选择一个Set
实现。
10.1.2. aop
Schema
aop
标签处理在 Spring 中配置所有 AOP,包括 Spring 自己的基于代理的 AOP 框架和 Spring 与 AspectJ AOP 框架的集成。这些标签在标题为Aspect Oriented Programming with Spring的章节中全面介绍。
为了完整起见,要使用aop
库中的标签,您需要在 Spring XML 配置文件的顶部有以下序言(片段中的文本引用正确的模式,以便aop
命名空间中的标签可用于你):
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
</beans>
10.1.3. context
Schema
context
标签处理与管道相关的ApplicationContext
配置——也就是说,通常不是对最终用户很重要的 bean,而是在 Spring 中完成大量“咕噜”工作的 bean,例如BeanfactoryPostProcessors
. 以下代码段引用了正确的架构,以便您可以使用context
命名空间中的元素:
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- bean definitions here -->
</beans>
使用<property-placeholder/>
此元素激活${…}
占位符的替换,这些占位符根据指定的属性文件(作为Spring 资源位置)进行解析。此元素是一种便利机制,可为您设置一个PropertySourcesPlaceholderConfigurer
。如果您需要对特定PropertySourcesPlaceholderConfigurer
设置进行更多控制 ,您可以自己将其显式定义为 bean。
使用<annotation-config/>
此元素激活 Spring 基础结构以检测 bean 类中的注解:
- spring
@Configuration
模型 @Autowired
/@Inject
,@Value
, 和@Lookup
- JSR-250 的
@Resource
、@PostConstruct
和@PreDestroy
(如果可用) - JAX-WS
@WebServiceRef
和 EJB 3@EJB
(如果可用) - JPA
@PersistenceContext
和@PersistenceUnit
(如果有) - spring的
@EventListener
或者,您可以选择为这些注解显式激活自定义的BeanPostProcessors
。
该元素不会激活 Spring 的 @Transactional
注解处理;您可以为此目的使用<tx:annotation-driven>
元素。同样,Spring 的 缓存注解也需要显式 启用。
使用<component-scan/>
这个元素在基于注解的容器配置一节中有详细说明。
使用<load-time-weaver/>
此元素在 Spring Framework 中使用 AspectJ 进行加载时编织的部分中有详细说明。
使用<spring-configured/>
这个元素在使用 AspectJ 通过 Spring 依赖注入域对象的部分中有详细说明。
使用<mbean-export/>
此元素在有关配置基于注解的 MBean 导出的部分中有详细说明。
10.1.4. Bean Schema
最后但同样重要的是,我们在beans
Schema中有元素。这些元素自框架诞生之初就存在于 Spring 中。这里没有显示beans
模式中各种元素的示例,因为它们在依赖项和详细配置中被非常全面地介绍 (实际上,在整个章节中)。
请注意,您可以向<bean/>
XML 定义添加零个或多个键值对。如果有的话,如何使用这些额外的元数据完全取决于您自己的自定义逻辑(因此通常仅在您编写自己的自定义元素时使用,如题为XML Schema Authoring的附录中所述)。
以下示例显示了<bean/>
上下文中的<meta/>
元素(请注意,如果没有任何逻辑来解释它,元数据实际上是无用的)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="x.y.Foo">
<meta key="cacheName" value="foo"/>
<property name="name" value="Rick"/>
</bean>
</beans>
这是示例meta
元素
在前面的示例中,您可以假设有一些逻辑使用 bean 定义并设置一些使用提供的元数据的缓存基础设施。
10.2. 自定义XML Schema
从 2.0 版开始,Spring 提供了一种机制,可以将基于模式的扩展添加到基本的 Spring XML 格式中,用于定义和配置 bean。本节介绍如何编写自己的自定义 XML bean 定义解析器并将此类解析器集成到 Spring IoC 容器中。
为了便于编写使用模式感知 XML 编辑器的配置文件,Spring 的可扩展 XML 配置机制基于 XML Schema。如果您不熟悉标准 Spring 发行版附带的 Spring 当前 XML 配置扩展,您应该首先阅读XML Schemas的上一节。
要创建新的 XML 配置扩展:
- 创作一个 XML 模式来描述您的自定义元素。
- 编写自定义
NamespaceHandler
的实现代码。 - 编写一个或多个
BeanDefinitionParser
的实现代码(这是完成实际工作的地方)。 - 使用 Spring注册您的新工件。
对于一个统一的示例,我们创建一个 XML 扩展(自定义 XML 元素),它允许我们配置 SimpleDateFormat
类型的对象(来自java.text
包)。完成后,我们将能够定义SimpleDateFormat
类型的 bean 定义如下:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
(我们将在本附录后面包含更详细的示例。第一个简单示例的目的是引导您完成制作自定义扩展的基本步骤。)
10.2.1. 创作 Schema
创建与 Spring 的 IoC 容器一起使用的 XML 配置扩展首先要创建一个 XML Schema 来描述扩展。对于我们的示例,我们使用以下模式来配置SimpleDateFormat
对象:
<!-- myns.xsd (inside package org/springframework/samples/xml) -->
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.mycompany.example/schema/myns"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="dateformat">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="lenient" type="xsd:boolean"/>
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
指示的行包含所有可识别标签的扩展库(意味着它们具有id
我们可以用作容器中的 bean 标识符的属性)。我们可以使用这个属性,因为我们导入了 Spring 提供的 beans
命名空间。
上述模式允许我们使用<myns:dateformat/>
元素直接在 XML 应用程序上下文文件中配置SimpleDateFormat
对象,如以下示例所示:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
请注意,在我们创建了基础设施类之后,前面的 XML 片段本质上与以下 XML 片段相同:
<bean id="dateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm"/>
<property name="lenient" value="true"/>
</bean>
前面两个片段中的第二个在容器中创建了一个带有几个属性集的 bean(由SimpleDateFormat
类型的名称 dateFormat
标识)。
创建配置格式的基于模式的方法允许与具有模式感知 XML 编辑器的 IDE 紧密集成。通过使用正确编写的模式,您可以使用自动完成功能让用户在枚举中定义的多个配置选项之间进行选择。
10.2.2. 编码一个NamespaceHandler
除了模式之外,我们还需要NamespaceHandler
解析 Spring 在解析配置文件时遇到的这个特定命名空间的所有元素。对于这个例子, NamespaceHandler
应该负责myns:dateformat
元素的解析。
NamespaceHandler
接口具有三种方法:
init()
: 允许初始化NamespaceHandler
并在使用处理程序之前由 Spring 调用。BeanDefinition parse(Element, ParserContext)
:当 Spring 遇到顶级元素(未嵌套在 bean 定义或不同的命名空间内)时调用。此方法本身可以注册 bean 定义、返回 bean 定义或两者兼而有之。BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)
:当 Spring 遇到不同命名空间的属性或嵌套元素时调用。一个或多个 bean 定义的装饰(例如)与 Spring 支持的范围一起使用。我们首先突出一个简单的例子,不使用装饰,然后我们在一个更高级的例子中展示装饰。
尽管您可以为整个命名空间编写自己的NamespaceHandler
代码(并因此提供解析命名空间中每个元素的代码),但通常情况下,Spring XML 配置文件中的每个顶级 XML 元素都会导致单个 bean 定义(在我们的例子中,单个<myns:dateformat/>
元素导致单个SimpleDateFormat
bean 定义)。Spring 提供了许多支持这种场景的便利类。在以下示例中,我们使用NamespaceHandlerSupport
该类:
package org.springframework.samples.xml;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
}
}
您可能会注意到,此类中实际上并没有很多解析逻辑。事实上,NamespaceHandlerSupport
类有一个内置的委托概念。它支持注册任意数量的BeanDefinitionParser
实例,当需要解析其名称空间中的元素时,它会委托给这些实例。这种干净的关注点分离让 NamespaceHandler
能够处理其命名空间中所有自定义元素的解析编排,同时委托 BeanDefinitionParsers
来完成 XML 解析的繁重工作。这意味着每个BeanDefinitionParser
仅包含解析单个自定义元素的逻辑,正如我们在下一步中看到的那样。
10.2.3. 使用BeanDefinitionParser
如果NamespaceHandler
遇到已映射到特定 bean 定义解析器(在本例中为日期格式)的类型的 XML 元素,则使用 BeanDefinitionParser
。换句话说,BeanDefinitionParser
负责解析模式中定义的一个不同的顶级 XML 元素。在解析器中,我们可以访问 XML 元素(因此也可以访问其子元素),以便可以解析自定义 XML 内容,如以下示例所示:
package org.springframework.samples.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import java.text.SimpleDateFormat;
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class getBeanClass(Element element) {
return SimpleDateFormat.class;
}
protected void doParse(Element element, BeanDefinitionBuilder bean) {
// this will never be null since the schema explicitly requires that a value be supplied
String pattern = element.getAttribute("pattern");
bean.addConstructorArgValue(pattern);
// this however is an optional property
String lenient = element.getAttribute("lenient");
if (StringUtils.hasText(lenient)) {
bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
}
}
}
10.2.4. 注册处理程序和模式
编码完成。剩下要做的就是让 Spring XML 解析基础架构知道我们的自定义元素。我们通过在两个特殊用途的属性文件中注册我们的自定义 namespaceHandler
和自定义 XSD 文件来做到这一点。这些属性文件都放置在应用程序的META-INF
目录中,例如,可以与 JAR 文件中的二进制类一起分发。Spring XML 解析基础结构通过使用这些特殊属性文件自动选择您的新扩展,其格式将在接下来的两节中详细介绍。
创建META-INF/spring.handlers
调用的spring.handlers
属性文件包含 XML Schema URI 到命名空间处理程序类的映射。对于我们的示例,我们需要编写以下内容:
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(:
字符是 Java 属性格式中的有效分隔符,因此 URI 中的:
字符需要使用反斜杠进行转义。)
键值对的第一部分(键)是与您的自定义命名空间扩展关联的 URI,并且需要与自定义 XSD 架构中指定的targetNamespace
属性值完全匹配。
编写’META-INF/spring.schemas’
调用的属性文件spring.schemas
包含 XML 模式位置(在使用模式作为xsi:schemaLocation
属性的一部分的 XML 文件中与模式声明一起引用)到类路径资源的映射。需要此文件来防止 Spring 绝对必须使用需要 Internet 访问来检索模式文件的默认 EntityResolver
。如果您在此属性文件中指定映射,Spring 会在类路径上搜索模式(在本例中, myns.xsd
在org.springframework.samples.xml
包中)。以下代码段显示了我们需要为自定义模式添加的行:
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(记住:
字符必须被转义。)
鼓励您将 XSD 文件(或多个文件)部署在类路径上的NamespaceHandler
和BeanDefinitionParser
类旁边。
10.2.5. 在 Spring XML 配置中使用自定义扩展
使用您自己实现的自定义扩展与使用 Spring 提供的“自定义”扩展之一没有什么不同。以下示例在 Spring XML 配置文件中使用前面步骤中开发的自定义<dateformat/>
元素:
<?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:myns="http://www.mycompany.example/schema/myns"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
<!-- as a top-level bean -->
<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
<bean id="jobDetailTemplate" abstract="true">
<property name="dateFormat">
<!-- as an inner bean -->
<myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
</property>
</bean>
</beans>
10.2.6. 更详细的例子
本节介绍一些更详细的自定义 XML 扩展示例。
在自定义元素中嵌套自定义元素
本节中的示例展示了如何编写满足以下配置目标所需的各种工件:
<?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:foo="http://www.foo.example/schema/component"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">
<foo:component id="bionic-family" name="Bionic-1">
<foo:component name="Mother-1">
<foo:component name="Karate-1"/>
<foo:component name="Sport-1"/>
</foo:component>
<foo:component name="Rock-1"/>
</foo:component>
</beans>
前面的配置将自定义扩展相互嵌套。 元素实际配置的类是 Component 类(如下一个示例所示)。请注意 Component 类如何不公开 Components 属性的 setter 方法。这使得通过使用 setter 注入来配置 Component 类的 bean 定义变得困难(或者说不可能)。以下清单显示了 Component 类::
package com.foo;
import java.util.ArrayList;
import java.util.List;
public class Component {
private String name;
private List<Component> components = new ArrayList<Component> ();
// mmm, there is no setter method for the 'components'
public void addComponent(Component component) {
this.components.add(component);
}
public List<Component> getComponents() {
return components;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
此问题的典型解决方案是创建一个自定义FactoryBean
来公开components
属性的setter属性。以下清单显示了这样的自定义 FactoryBean
:
package com.foo;
import org.springframework.beans.factory.FactoryBean;
import java.util.List;
public class ComponentFactoryBean implements FactoryBean<Component> {
private Component parent;
private List<Component> children;
public void setParent(Component parent) {
this.parent = parent;
}
public void setChildren(List<Component> children) {
this.children = children;
}
public Component getObject() throws Exception {
if (this.children != null && this.children.size() > 0) {
for (Component child : children) {
this.parent.addComponent(child);
}
}
return this.parent;
}
public Class<Component> getObjectType() {
return Component.class;
}
public boolean isSingleton() {
return true;
}
}
这很好用,但它向最终用户暴露了很多 Spring 管道。我们要做的是编写一个自定义扩展来隐藏所有这些 Spring 管道。如果我们坚持前面描述的步骤,我们首先创建 XSD 模式来定义我们的自定义标签的结构,如以下清单所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/component"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/component"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="component">
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="component"/>
</xsd:choice>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="name" use="required" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
再次按照前面描述的过程,我们然后创建一个自定义NamespaceHandler
:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
}
}
接下来是自定义BeanDefinitionParser
。请记住,我们正在创建描述 ComponentFactoryBean
的 BeanDefinition
。以下清单显示了我们的自定义BeanDefinitionParser
实现:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.util.List;
public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
return parseComponentElement(element);
}
private static AbstractBeanDefinition parseComponentElement(Element element) {
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
factory.addPropertyValue("parent", parseComponent(element));
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
if (childElements != null && childElements.size() > 0) {
parseChildComponents(childElements, factory);
}
return factory.getBeanDefinition();
}
private static BeanDefinition parseComponent(Element element) {
BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
component.addPropertyValue("name", element.getAttribute("name"));
return component.getBeanDefinition();
}
private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
for (Element element : childElements) {
children.add(parseComponentElement(element));
}
factory.addPropertyValue("children", children);
}
}
最后,需要通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件将各种工件注册到 Spring XML 基础架构,如下所示:
# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
“普通”元素的自定义属性
编写您自己的自定义解析器和相关的工件并不难。但是,有时这不是正确的做法。考虑一个场景,您需要将元数据添加到已经存在的 bean 定义中。在这种情况下,您当然不想编写自己的整个自定义扩展。相反,您只想向现有的 bean 定义元素添加一个附加属性。
再举一个例子,假设您为(它不知道的)访问集群 JCache的服务对象定义了一个 bean 定义,并且您希望确保命名的 JCache 实例在周围的集群中急切地启动。以下清单显示了这样的定义:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
jcache:cache-name="checking.account">
<!-- other dependencies here... -->
</bean>
然后,当解析“jcache:cache-name”属性时,我们可以创建另一个 BeanDefinition
。然后这个BeanDefinition
为我们初始化命名的 JCache
。我们还可以修改“checkingAccountService”的现有 BeanDefinition
,使其依赖于这个新的 JCache
初始化 BeanDefinition
。以下清单显示了我们的` JCacheInitializer:
package com.foo;
public class JCacheInitializer {
private String name;
public JCacheInitializer(String name) {
this.name = name;
}
public void initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
现在我们可以转到自定义扩展。首先,我们需要编写描述自定义属性的 XSD 架构,如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/jcache"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/jcache"
elementFormDefault="qualified">
<xsd:attribute name="cache-name" type="xsd:string"/>
</xsd:schema>
接下来,我们需要创建关联的NamespaceHandler
,如下:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
new JCacheInitializingBeanDefinitionDecorator());
}
}
接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析 XML 属性,所以我们写的是 BeanDefinitionDecorator
而不是BeanDefinitionParser
。以下清单显示了我们的BeanDefinitionDecorator
实现:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
ParserContext ctx) {
String initializerBeanName = registerJCacheInitializer(source, ctx);
createDependencyOnJCacheInitializer(holder, initializerBeanName);
return holder;
}
private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
String initializerBeanName) {
AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
String[] dependsOn = definition.getDependsOn();
if (dependsOn == null) {
dependsOn = new String[]{initializerBeanName};
} else {
List dependencies = new ArrayList(Arrays.asList(dependsOn));
dependencies.add(initializerBeanName);
dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
}
definition.setDependsOn(dependsOn);
}
private String registerJCacheInitializer(Node source, ParserContext ctx) {
String cacheName = ((Attr) source).getValue();
String beanName = cacheName + "-initializer";
if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
initializer.addConstructorArg(cacheName);
ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
}
return beanName;
}
}
最后,我们需要通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件将各种工件注册到 Spring XML 基础架构中,如下所示:
# 在 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# 在 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
10.3. 应用程序启动步骤
附录的这一部分列出了StartupSteps
核心容器被检测的现有内容。
每个启动步骤的名称和详细信息不是公共合同的一部分,可能会发生变化;这被认为是核心容器的实现细节,并将跟随其行为变化。
名称 | 描述 | 标签 |
---|---|---|
spring.beans.instantiate |
bean 及其依赖项的实例化。 | beanName bean 的名称,beanType 注入点所需的类型。 |
spring.beans.smart-initialize |
SmartInitializingSingleton bean的初始化。 |
beanName bean 的名称。 |
spring.context.annotated-bean-reader.create |
AnnotatedBeanDefinitionReader 的创建。 |
|
spring.context.base-packages.scan |
扫描基础包。 | packages 用于扫描的基本包数组。 |
spring.context.beans.post-process |
bean 后处理阶段。 | |
spring.context.bean-factory.post-process |
调用BeanFactoryPostProcessor bean。 |
postProcessor 当前的后处理器。 |
spring.context.beandef-registry.post-process |
调用BeanDefinitionRegistryPostProcessor bean。 |
postProcessor 当前的后处理器。 |
spring.context.component-classes.register |
通过 注册组件类AnnotationConfigApplicationContext#register 。 |
classes 用于注册的给定类的数组。 |
spring.context.config-classes.enhance |
使用 CGLIB 代理增强配置类。 | classCount 增强类的计数。 |
spring.context.config-classes.parse |
配置类解析阶段使用ConfigurationClassPostProcessor . |
classCount 已处理类的计数。 |
spring.context.refresh |
应用程序上下文刷新阶段。 |
评论区