IOC 容器:BeanDefinition 篇
相关文档:
- 参考文档
- Spring 官方文档
- 《Spring 技术内幕:深入解析 Spring 架构与设计原理》
注: 笔者经常喜欢引用一些官方文档以及其他文献中的内容,一来,这些内容确实存在着其自身的价值与意义。二来,也是为了使得自己的文章尽量言出有据,不瞎叨叨。但由于个人能力以及理解问题,错误仍然无法保证完全避免。如有问题,欢迎及时指正。
涉及问题
- 什么是配置元数据(
ConfigurationMetadata
)? - 什么是
BeanDefinition
? BeanDefinition
与配置元数据的关系?包含了哪些配置元数据?- 配置元数据有哪些表现形式?以及相应的载入方式。
配置元信息(ConfigurationMetadata
)
- 什么是配置元数据?
A Spring IoC container manages one or more beans. These beans are created with the configuration metadata that you supply to the container (for example, in the form of XML
<bean/>
definitions).
This configuration metadata represents how you, as an application developer, tell the Spring container to instantiate, configure, and assemble the objects in your application.
由此可知,配置元数据是指我们提供给 Spring 容器的一些配置信息,其中包含了 Bean
的定义信息,通过这些配置信息,我们可以告诉 Spring 容器如何去实例化、配置以及组装 Bean。
Within the container itself, these bean definitions are represented as
BeanDefinition
objects.
在容器内部,这些 Bean
的定义信息经过封装后表现为一个或多个 BeanDefinition
对象。spring
就是通过解析这些 BeanDefinition
实例来生成所定义的 Bean
实例。
Bean 的定义类(BeanDefinition
)
-
BeanDefinition
设计意图:
Java Doc 注释:
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
This is just a minimal interface: The main intention is to allow a {@link
BeanFactoryPostProcessor
} to introspect and modify property values and other bean metadata.
BeanDefinition
是 spring
设计的用来描述一个 Bean
实例的接口。包含了许多关于 Bean
实例的信息。并且 spring
还设计了一个 BeanFactoryPostProcessor
容器后置处理器接口,可以在容器初始化阶段对 BeanDefinition
元数据或属性值进行修改。
-
BeanDefinition
继承关系:
BeanDefinition
是spring
定义的一个用来描述bean
实例的接口。它继承了AttributeAccessor
和BeanMetadataElement
接口。
-
AttributeAccessor
要求BeanDefinition
需要具备属性存储的能力。此处的属性并非指对象的Field
,而是近似于一个标签的作用,例如,ConfigurationClass
对应的BeanDefinition
会在容器启动过程中标记一个org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass
的属性,来标记为是配置类。 -
BeanMetadataElement
允许BeanDefinition
配置一个类型为Object
的配置源。
-
BeanDefinition
接口中声明的方法:
/**
* Set the name of the parent definition of this bean definition, if any.
*/
void setParentName(@Nullable String parentName);
/**
* Return the name of the parent definition of this bean definition, if any.
*/
@Nullable
String getParentName();
说明 BeanDefinition
具备父子层级关系,子 BeanDefinition
可以复用父 BeanDefinition
中配置的元数据信息,从而减少不必要的重复配置。
/**
* Specify the bean class name of this bean definition.
* <p>The class name can be modified during bean factory post-processing,
* typically replacing the original class name with a parsed variant of it.
* @see #setParentName
* @see #setFactoryBeanName
* @see #setFactoryMethodName
*/
void setBeanClassName(@Nullable String beanClassName);
@Nullable
String getBeanClassName();
指定 BeanDefinition
对应的 Bean
实例化时所对应的 class
类。
/**
* Override the target scope of this bean, specifying a new scope name.
* @see #SCOPE_SINGLETON
* @see #SCOPE_PROTOTYPE
*/
void setScope(@Nullable String scope);
@Nullable
String getScope();
scope
为范围的意思,这里我们通常可以理解为作用域。不同作用域的 Bean
获取方式的内部实现策略可能会存在差异。 spring
提供了 singleton
和 prototype
两种常用的作用域,分别对应设计模式中的单例模式和原型模式。
/**
* Set whether this bean should be lazily initialized.
* <p>If {@code false}, the bean will get instantiated on startup by bean
* factories that perform eager initialization of singletons.
*/
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
设置 Bean
是否延迟初始化,如果设置为 true
,在容器对单例对象进行预先初始化时,会跳过该 BeanDefinition
所对应 Bean
的实例化,而是在向容器第一次获取该 Bean
的时候才进行实例化。但如果该 Bean
是别的非延迟加载单例 Bean
的依赖对象时,则在容器对单例对象进行预先初始化是,也会被实例化。
/**
* Set the names of the beans that this bean depends on being initialized.
* The bean factory will guarantee that these beans get initialized first.
*/
void setDependsOn(@Nullable String... dependsOn);
@Nullable
String[] getDependsOn();
设置该 Bean
实例所依赖的其他 Bean
, spring
容器会先去初始化所依赖的其他 Bean
。
/**
* Set whether this bean is a candidate for getting autowired into some other bean.
* <p>Note that this flag is designed to only affect type-based autowiring.
* It does not affect explicit references by name, which will get resolved even
* if the specified bean is not marked as an autowire candidate. As a consequence,
* autowiring by name will nevertheless inject a bean if the name matches.
*/
void setAutowireCandidate(boolean autowireCandidate);
boolean isAutowireCandidate();
设置该 Bean
是否为依赖注入候选者(把当前 Bean
注入到其他 Bean
中)。该属性仅影响基于类型的依赖注入。如果是通过 beanName
来进行依赖注入,则该属性不生效。
/**
* Set whether this bean is a primary autowire candidate.
* <p>If this value is {@code true} for exactly one bean among multiple
* matching candidates, it will serve as a tie-breaker.
*/
void setPrimary(boolean primary);
boolean isPrimary();
设置该 Bean
是否为优先候选者,该属性同样是仅影响基于类型的依赖注入,当存在多个候选者时,会优先选用 primary
为 true
的候选者(只能有一个)。否则系统将会因为无法选定候选者而抛出异常。
/**
* Specify the factory bean to use, if any.
* This the name of the bean to call the specified factory method on.
* @see #setFactoryMethodName
*/
void setFactoryBeanName(@Nullable String factoryBeanName);
@Nullable
String getFactoryBeanName();
/**
* Specify a factory method, if any. This method will be invoked with
* constructor arguments, or with no arguments if none are specified.
* The method will be invoked on the specified factory bean, if any,
* or otherwise as a static method on the local bean class.
* @see #setFactoryBeanName
* @see #setBeanClassName
*/
void setFactoryMethodName(@Nullable String factoryMethodName);
@Nullable
String getFactoryMethodName();
setFactoryBeanName
和 setFactoryMethodName
对应设计模式中的工厂模式。 setFactoryBeanName(String factoryBeanName)
指定生产 Bean
的工厂实例,setFactoryMethodName
指定对应的工厂方法名称。
/**
* Return the constructor argument values for this bean.
* <p>The returned instance can be modified during bean factory post-processing.
* @return the ConstructorArgumentValues object (never {@code null})
*/
ConstructorArgumentValues getConstructorArgumentValues();
default boolean hasConstructorArgumentValues() {
return !getConstructorArgumentValues().isEmpty();
}
获取构造参数值,对应类实例化时调用构造函数的参数值。如果是通过工厂方法来生成实例的话,则对应工厂方法的参数值。
/**
* Return the property values to be applied to a new instance of the bean.
* <p>The returned instance can be modified during bean factory post-processing.
* @return the MutablePropertyValues object (never {@code null})
*/
MutablePropertyValues getPropertyValues();
default boolean hasPropertyValues() {
return !getPropertyValues().isEmpty();
}
获取所描述Bean
实例的属性值,Mutable
为可修改的意思,获取 MutablePropertyValues
对象后可以对里面的 PropertyValue
进行编辑修改。
void setInitMethodName(@Nullable String initMethodName);
@Nullable
String getInitMethodName();
void setDestroyMethodName(@Nullable String destroyMethodName);
@Nullable
String getDestroyMethodName();
以上方法分别为设置 Bean
实例的初始化方法,和销毁方法。
void setRole(int role);
int getRole();
设置 Bean
的角色,该方法没有什么功能上的意义。仅用来对 Bean
做一个功能上的标识。
void setDescription(@Nullable String description);
@Nullable
String getDescription();
设置类的描述信息。
/**
* Return a resolvable type for this bean definition,
* based on the bean class or other specific metadata.
* <p>This is typically fully resolved on a runtime-merged bean definition
* but not necessarily on a configuration-time definition instance.
* @return the resolvable type (potentially {@link ResolvableType#NONE})
* @since 5.2
* @see ConfigurableBeanFactory#getMergedBeanDefinition
*/
ResolvableType getResolvableType();
获取该 BeanDefinition
所描述 Bean
的 ResolvableType
。ResolvableType
是 spring
设计的一个支持获取泛型信息的接口,方便泛型操作。
boolean isSingleton();
boolean isPrototype();
/**
* Return whether this bean is "abstract", that is, not meant to be instantiated.
*/
boolean isAbstract();
是否单例、是否原型、是否抽象类。
/**
* Return a description of the resource that this bean definition
* came from (for the purpose of showing context in case of errors).
*/
@Nullable
String getResourceDescription();
返回该 Bean
所来自资源的描述。
@Nullable
BeanDefinition getOriginatingBeanDefinition();
获取原始的 BeanDefinition
信息,如果存在的话。
-
BeanDefinition
包含了哪些配置元数据信息?
在了解了 BeanDefinition
接口后,我们则可以简单地总结一个 BeanDefinition
中都包含了哪些 Bean
定义信息。不过,Spring
官方文档已经对此做了总结,这里再做一个简单的阐述:
配置项 | 作用 |
---|---|
Class |
实例化 Bean 所对应的类信息 |
Name |
Bean 的名称 |
Constructor arguments |
Bean 的构造参数 |
Properties |
Bean 的属性值 |
Scope |
Bean 的作用域 |
Autowiring mode |
Bean 的自动装配模式, 默认为 NONE
|
Lazy initialization mode |
Bean 的延迟初始化模式 |
Initialization method |
Bean 的初始化方法 |
Destruction method |
Bean 的销毁方法 |
BeanDefinition 的具体实现
BeanDefinition
的具体实现类有许多,这里仅挑选以下几个做出简略说明:
RootBeanDefinition
ChildBeanDefinition
GenericBeanDefinition
AnnotatedBeanDefinition
上文提到了 BeanDefinition
是可以支持父子层级关系的。RootBeanDefinition
和 ChildBeanDefinition
这两个实现类,则直接从名字上表明了这一问题。Root
代表根部的意思,则 RootBeanDefinition
代表了最上层的父级 BeanDefinition
,它不能设置 parentName
,否则会抛出异常。而 ChildBeanDefinition
则代表了子 BeanDefinition
,可以指定父级 BeanDefinition
, 并复用 parentBeanDefinition
的配置元数据信息。
GenericBeanDefinition
是一个相对比较灵活的实现类,相比于以上两种去掉了一些层级关系上的约束。可以随意指定 parentName
。如果我们使用 Java
编码的形式来生成 BeanDefinition
,通常可以使用此类。
AnnotatedBeanDefinition
相对于其他的 BeanDefinition
实现类,提供了一些对类上注解以及方法注解的支持。
以上内容比较简单,可以自行熟悉了解。也可以参考下面提供一些简单代码,对 BeanDefinition
有一个基本的理解。
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition();
userBeanDefinition.setBeanClass(User.class);
MutablePropertyValues tomPropertyValues = new MutablePropertyValues();
tomPropertyValues.addPropertyValue("id", 1);
tomPropertyValues.addPropertyValue("name", "tom");
tomPropertyValues.addPropertyValue("age", 18);
userBeanDefinition.setPropertyValues(tomPropertyValues);
beanFactory.registerBeanDefinition("tom", userBeanDefinition);
GenericBeanDefinition idGeneratorBeanDefinition = new GenericBeanDefinition();
idGeneratorBeanDefinition.setBeanClass(UserIdGenerator.class);
beanFactory.registerBeanDefinition("userIdGenerator", idGeneratorBeanDefinition);
GenericBeanDefinition userRepositoryBeanDefinition = new GenericBeanDefinition();
userRepositoryBeanDefinition.setBeanClass(UserRepository.class);
MutablePropertyValues userRepositoryPropertyValues = new MutablePropertyValues();
userRepositoryPropertyValues.addPropertyValue("idGenerator",
// 引用容器中命名为 idGenerator 的 Bean
new RuntimeBeanReference("userIdGenerator"));
userRepositoryBeanDefinition.setPropertyValues(userRepositoryPropertyValues);
beanFactory.registerBeanDefinition("userRepository", userRepositoryBeanDefinition);
GenericBeanDefinition userServiceBeanDefinition = new GenericBeanDefinition();
userServiceBeanDefinition.setBeanClass(UserService.class);
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, new RuntimeBeanReference("userRepository"));
userServiceBeanDefinition.setConstructorArgumentValues(constructorArgumentValues);
beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
UserService userService = beanFactory.getBean(UserService.class);
User tom = beanFactory.getBean("tom", User.class);
userService.addUser(tom);
userService.listUser().forEach(System.out::println);
BeanDefinition 的载入
以上,我们已经了解到,配置元信息(ConfigurationMetadata
) 是我们提供给 spring
的用来定义描述 Bean
的一些配置信息。不过,在能够让 spring
容器真正使用之前,我们需要先将这些配置元信息(ConfigurationMetadata
) 转化成 BeanDefinition
对象。而这一过程呢,在 《Spring
技术内幕...》一书中称为 BeanDefinition
的载入。这里沿用此称法。
上面我们已经演示了如何通过 Java
编码的方式去生成 BeanDefinition
。但是在实际项目应用中,我们往往不会采用这样的方式,操作复杂,且不便于维护。
那么,我们通常都是如何向 spring
提供这些元数据信息呢?
Spring 官方文档:
The configuration metadata is represented in XML, Java annotations, or Java code.
通常呢,我们会采用以下三种方式来向容器提供 Bean
的配置元数据信息。
-
XML Configuration
. 传统的以XML
文件为载体的元数据配置方式。 -
Annotation-based Configuration
. 将元数据信息由XML
文件中转移到实例Bean
所对应的类源码文件中,转由注解的方式来表达。此方式需要开启注解驱动,并配置包扫描来完成实现。可以简化XML
文件中繁琐的Bean
元数据配置信息。典型用法,就是在XML
文件中,引入context
命名空间,并开启注解配置,以及包扫描。此方法涉及的一些关键注解是:@Component
,@Autowired
,@Qualifier
等。 -
Java-based Configuration
. 此方式是基于注解配置方式的再一次演进。核心注解为@Bean
和@Configuration
。通过声明一种ConfigurationClass
来实现完全代替XML
文件配置。
下面呢,我们会对这三种方式进行一一说明。
XML Configuration
XML
文件配置方式是传统的配置方式。如果做过传统项目,以下会是我们很熟悉的配置方式。
user-beans.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: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 id="tom" class="com.grasswort.beans.model.User">
<property name="id" value="${tom.id}"/>
<property name="name" value="${tom.name}"/>
<property name="age" value="${tom.age}"/>
</bean>
<bean id="jerry" class="com.grasswort.beans.model.User">
<property name="id" value="${jerry.id}"/>
<property name="name" value="${jerry.name}"/>
<property name="age" value="${jerry.age}"/>
</bean>
<!-- 引入属性文件 -->
<context:property-placeholder
location="classpath:com/grasswort/beans/beandefinition/configurationmeta/user-beans.properties"/>
</beans>
user-service.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 引入另一个 XML 配置文件 -->
<import resource="user-beans.xml"/>
<bean id="idGenerator" class="com.grasswort.beans.model.UserIdGenerator"></bean>
<bean id="userRepository" class="com.grasswort.beans.model.UserRepository">
<property name="idGenerator" ref="idGenerator"/>
</bean>
<bean id="userService" class="com.grasswort.beans.model.UserService">
<constructor-arg ref="userRepository"/>
</bean>
</beans>
user-beans.properties
tom.id=1
tom.name=tom
tom.age=18
jerry.id=2
jerry.name=jerry
jerry.age=8
XML
文件配置,通常使用 XmlBeanDefinitionReader
工具类来完成 BeanDefinition
的载入。
public class XmlBeanDefinitionConfigurationTest {
private static Logger logger = LoggerFactory.getLogger(XmlBeanDefinitionConfigurationTest.class);
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
int loadCount = xmlBeanDefinitionReader.loadBeanDefinitions(
"com/grasswort/beans/beandefinition/configurationmeta/user-service.xml");
logger.info("已解析 BeanDefinition 数量 : {}", loadCount);
Stream.of(beanFactory.getBeanDefinitionNames())
.forEach(System.out::println);
}
}
运行结果如下:
2020-08-26 19:19:09 [INFO] 已解析 BeanDefinition 数量 : 6
tom
jerry
org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0
idGenerator
userRepository
userService
XML
文件方式将配置信息与应用程序代码分离了开来,对实体类无侵入,便于维护。
缺点呢,主要是当 Bean
比较多,依赖关系复杂的情况下,配置过于繁琐,又显得不那么便于维护。
Annotation-based Container Configuration
Instead of using XML to describe a bean wiring, the developer moves the configuration into the component class itself by using annotations on the relevant class, method, or field declaration.
Annotation injection is performed before XML injection. Thus, the XML configuration overrides the annotations for properties wired through both approaches.
上面一段话的核心在于 moves the configuration into the component class itself by using annotations on the relevant class, method, or field declaration.
正是由于 XML
文件配置过于繁琐,所以基于注解的配置方式,则尝试将 Bean
的配置元数据信息转移到该 Bean
实例所对应的类上。通过在类、方法、以及属性上声明注解来实现 Bean
依赖元数据信息的配置。
例如:通过在类上声明 @Component
以及它的派生注解来声明一个类的实例为 spring
所管理的 Bean
。在方法或属性上声明 @Autowired
注解来表明该类实例的依赖关系。
@Repository
public class UserRepository {
private final List<User> userList = new ArrayList<>(5);
private IdGenerator idGenerator;
/**
* setter dependency injection
* @param idGenerator
*/
@Autowired
public void setIdGenerator(IdGenerator idGenerator) {
this.idGenerator = idGenerator;
}
}
支持的注解:
- spring 2.0
@Required
- spring 2.5
@Component
@Autowired
@Qualifier
@Value
- JSR-250(
@PostConstruct
、@PreDestroy
、@Resource
)
- spring 3.0
- JSR-330(
@Inject
、@Named
) @Primary
- JSR-330(
通过将元数据信息转移到 class
上后,我们便可以在 XML
文件中省去一些 Bean
的配置信息。但是为了寻找这些 class
文件并解析元数据信息,我们需要配合包的扫描以及开启注解支持来配合实现。所以,XML
文件应为:
user-service-annotation-based.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: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">
<context:annotation-config/>
<context:component-scan base-package="com.grasswort.beans.model"/>
<import resource="user-beans.xml"/>
</beans>
由于依然使用的是 XML
文件,所以,同样可以使用 XmlBeanDefinitionReader
来进行载入:
public class AnnotationBasedConfigurationTest {
private static Logger logger = LoggerFactory.getLogger(AnnotationBasedConfigurationTest.class);
public static void main(String[] args) {
GenericApplicationContext beanFactory = new GenericApplicationContext();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
int loadCount = xmlBeanDefinitionReader.loadBeanDefinitions("com/grasswort/beans/beandefinition/configurationmeta/user-service-annotation-based.xml");
logger.info("已解析 BeanDefinition 数量 : {}", loadCount);
Stream.of(beanFactory.getBeanDefinitionNames())
.forEach(System.out::println);
beanFactory.refresh();
UserService userService = beanFactory.getBean(UserService.class);
beanFactory.getBeansOfType(User.class).values().forEach(userService::addUser);
userService.listUser().forEach(System.out::println);
}
}
运行结果如下:
2020-08-26 20:07:02 [INFO] 已解析 BeanDefinition 数量 : 10
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
userIdGenerator
userRepository
userService
tom
jerry
org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0
User{id=1, name='tom', age=18}
User{id=2, name='jerry', age=8}
我们看到,添加了 <context:annotation-config/>
之后,容器中多了 4 个 BeanDefinition
。它们就是用来处理注解的,这些 BeanDefinition
是从哪里来的呢?
在 spring-context
模块下存在这样一个文件 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
当我们在 XML
文件中开启注解配置之后,XmlBeanDefinitionReader
在载入 BeanDefinition
过程中,会激活
ContextNamespaceHandler
来处理,ContextNamespaceHandler
会注册一个 AnnotationConfigBeanDefinitionParser
的解析器。它会调用 AnnotationConfigUtils
来注册以上 4 个 BeanDefinition
。代码如下:(这个 AnnotationConfigUtils
需要关注一下,以后会经常遇到。)
public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
// Obtain bean definitions for all relevant BeanPostProcessors.
Set<BeanDefinitionHolder> processorDefinitions =
AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);
// 省略部分代码
...
return null;
}
}
这 4 个BeanDefinition
分别对应的是(隐式注册的相关 post-processors
):
ConfigurationClassPostProcessor
(这个暂时可以忽略,对应的是下面提到的Java-based Configuration
)AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
这也映照了 Java
官方文档中的一段话:
(The implicitly registered post-processors include
AutowiredAnnotationBeanPostProcessor
,CommonAnnotationBeanPostProcessor
,PersistenceAnnotationBeanPostProcessor
, and the aforementionedRequiredAnnotationBeanPostProcessor
.)
Java-based Container Configuration
以上,在开启了注解配置之后呢,我们仍然还余留了一个配置 XML
文件和一个我们未处理的 user-beans.xml
。
user-service-annotation-based.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: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">
<context:annotation-config/>
<context:component-scan base-package="com.grasswort.beans.model"/>
<import resource="user-beans.xml"/>
</beans>
user-beans.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: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 id="tom" class="com.grasswort.beans.model.User">
<property name="id" value="${tom.id}"/>
<property name="name" value="${tom.name}"/>
<property name="age" value="${tom.age}"/>
</bean>
<bean id="jerry" class="com.grasswort.beans.model.User">
<property name="id" value="${jerry.id}"/>
<property name="name" value="${jerry.name}"/>
<property name="age" value="${jerry.age}"/>
</bean>
<!-- 引入属性文件 -->
<context:property-placeholder
location="classpath:com/grasswort/beans/beandefinition/configurationmeta/user-beans.properties"/>
</beans>
那么,我们能否通过 Java Code
来替代它呢?这就是我们接下来要提到的 Java-based Container Configuration
。
Java-based Configuration
的核心注解是 @Bean
和 @Configuration
。标注了 @Configuration
注解的类,会被 spring
容器视为 ConfigurationClass
,即配置类。而该类中标注了 @Bean
注解的方法,会在容器启动过程中,以工厂方法模式的 BeanDefinition
注册到容器里。
注意是容器启动过程中,假如没有调用容器的 refresh
方法,配置类中声明的 BeanDefinition
是不会注册到容器中的。这利用的是 spring
容器提供的 BeanFactoryPostProcessor
容器后置处理机制,而在这里起作用的则是上文提到的由 AnnotationConfigUtils
注册的 ConfigurationClassPostProcessor
。
接下来,直接上代码:
UsersConfiguration.java
@Configuration
@PropertySource("classpath:com/grasswort/beans/beandefinition/configurationmeta/user-beans.properties")
public class UsersConfiguration {
@Bean
public User tom(@Value("${tom.id}") Long id,
@Value("${tom.name}") String name,
@Value("${tom.age}") Integer age) {
User tom = new User();
tom.setId(id);
tom.setAge(age);
tom.setName(name);
return tom;
}
@Bean
public User jerry(@Value("${jerry.id}") Long id,
@Value("${jerry.name}") String name,
@Value("${jerry.age}") Integer age) {
User jerry = new User();
jerry.setId(id);
jerry.setAge(age);
jerry.setName(name);
return jerry;
}
}
UserServiceConfiguration.java
@Configuration
@Import(UsersConfiguration.class)
public class UserServiceConfiguration {
@Bean
public IdGenerator idGenerator() {
return new UserIdGenerator();
}
@Bean
public UserRepository userRepository(IdGenerator idGenerator) {
UserRepository userRepository = new UserRepository();
userRepository.setIdGenerator(idGenerator);
return userRepository;
}
@Bean
public UserService userService(UserRepository userRepository, Collection<User> users) {
UserService userService = new UserService(userRepository);
users.forEach(userService::addUser);
return userService;
}
}
然后,我们发现,这个ConfigurationClass
和 XML
配置文件其实是很相似的。
@Bean
对应了<bean/>
@Import(UserConfiguration.class)
对应了<import resource="user-beans.xml"/>
@PropertySource("xx.properties")
对应了<context:property-placeholder location="xx.properties"/>
而 <context:property-placeholder.../>
其实还有另一种写法:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>xx.properties</value>
</property>
<property name="fileEncoding">
<value>UTF-8</value>
</property>
</bean>
然后,我们发现,ConfigurationClass
其实和 XML
配置文件在概念上非常的类似。
然后,我们使用 AnnotationConfigApplicationContext
容器来进行载入并启动:
/**
* @author xuliangliang
* @Description
* @Date 2020/8/13
* @see org.springframework.context.annotation.ConfigurationClassPostProcessor
* @see org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
*/
public class JavaBasedConfigurationTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(UserServiceConfiguration.class);
context.refresh(); // can't get the BeanDefinition named tom if not refresh
// this step will register a {@link ConfigurationClassPostProcessor} bean ,
// it will invoke the {@link ConfigurationClassBeanDefinitionReader} to resolve inner bean.
Stream.of(context.getBeanDefinitionNames())
.forEach(System.out::println);
BeanDefinition userConfigurationBd = context.getBeanDefinition("userServiceConfiguration");
System.out.println(userConfigurationBd.getAttribute("org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass"));
BeanDefinition beanDefinition = context.getBeanDefinition("tom");
System.out.println(beanDefinition);
UserService userService = context.getBean(UserService.class);
userService.listUser().forEach(System.out::println);
}
}
运行结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
userServiceConfiguration
com.grasswort.beans.beandefinition.configurationmeta.UsersConfiguration
tom
jerry
idGenerator
userRepository
userService
full
Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.grasswort.beans.beandefinition.configurationmeta.UsersConfiguration; factoryMethodName=tom; initMethodName=null; destroyMethodName=(inferred); defined in com.grasswort.beans.beandefinition.configurationmeta.UsersConfiguration
User{id=1, name='tom', age=18}
User{id=2, name='jerry', age=8}
最后呢,我们已经没有再依赖 XML
配置文件。