02|扩展Bean:如何配置constructor、property和init-method?
你好,我是郭屹。
上节课,我们初步实现了一个MiniSpring框架,它很原始也很简单。我们实现了一个BeanFactory,作为一个容器对Bean进行管理,我们还定义了数据源接口Resource,可以将多种数据源注入Bean。
这节课,我们继续增强IoC容器,我们要做的主要有3点。
- 增加单例Bean的接口定义,然后把所有的Bean默认为单例模式。
- 预留事件监听的接口,方便后续进一步解耦代码逻辑。
- 扩展BeanDefinition,添加一些属性,现在它只有id和class两个属性,我们要进一步地丰富它。
构建单例的Bean
首先我们来看看如何构建单例的Bean,并对该Bean进行管理。
单例(Singleton)是指某个类在整个系统内只有唯一的对象实例。只要能达到这个目的,采用什么技术手段都是可以的。常用的实现单例的方式有不下五种,因为我们构建单例的目的是深入理解Spring框架,所以我们会按照Spring的实现方式来做。
为了和Spring框架内的方法名保持一致,我们把BeanFactory接口中定义的registryBeanDefinition方法修改为registryBean,参数修改为beanName与obj。其中,obj为Object类,指代与beanName对应的Bean的信息。你可以看下修改后的BeanFactory。
1
2
3
4
5
|
public interface BeanFactory {
Object getBean(String beanName) throws BeansException;
Boolean containsBean(String name);
void registerBean(String beanName, Object obj);
}
|
既然要管理单例Bean,接下来我们就定义一下SingletonBeanRegistry,将管理单例Bean的方法规范好。
1
2
3
4
5
6
|
public interface SingletonBeanRegistry {
void registerSingleton(String beanName, Object singletonObject);
Object getSingleton(String beanName);
boolean containsSingleton(String beanName);
String[] getSingletonNames();
}
|
这个类的名称上带有Registry字样,所以让人一眼就能知道这里面存储的就是Bean。从代码可以看到里面的方法名称简单直接,分别对应单例的注册、获取、判断是否存在,以及获取所有的单例Bean等操作。接口已经定义好了,接下来我们定义一个默认的实现类。这也是从Spring里学的方法,它作为一个框架并不会把代码写死,所以这里面的很多实现类都是默认的,默认是什么意思呢?就是我们可以去替换,不用这些默认的类也是可以的。我们就按照同样的方法,来为我们的默认实现类取个名字DefaultSingletonBeanRegistry。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
//容器中存放所有bean的名称的列表
protected List<String> beanNames = new ArrayList<>();
//容器中存放所有bean实例的map
protected Map<String, Object> singletons = new ConcurrentHashMap<>(256);
public void registerSingleton(String beanName, Object singletonObject) {
synchronized (this.singletons) {
this.singletons.put(beanName, singletonObject);
this.beanNames.add(beanName);
}
}
public Object getSingleton(String beanName) {
return this.singletons.get(beanName);
}
public boolean containsSingleton(String beanName) {
return this.singletons.containsKey(beanName);
}
public String[] getSingletonNames() {
return (String[]) this.beanNames.toArray();
}
protected void removeSingleton(String beanName) {
synchronized (this.singletons) {
this.beanNames.remove(beanName);
this.singletons.remove(beanName);
}
}
}
|
DefaultSingletonBeanRegistry 类的实现
在这个类中,我们定义了两个关键的数据结构:beanNames
列表和singletons
映射关系。
线程安全考虑
为了保证在多线程并发环境下的线程安全,我们采取了以下措施:
SimpleBeanFactory 的修改
为了确保通过SimpleBeanFactory
创建的Bean默认是单例的,我们将其修改为继承DefaultSingletonBeanRegistry
。这样,任何通过SimpleBeanFactory
创建的Bean都将自动注册为单例,这与Spring框架的处理方式保持一致。
结论
通过上述设计,我们确保了在单线程和多线程环境下,Bean的单例性都能得到保证,从而为应用的稳定性和性能提供了基础。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class SimpleBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory{
private Map<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>(256);
public SimpleBeanFactory() {
}
//getBean,容器的核心方法
public Object getBean(String beanName) throws BeansException {
//先尝试直接拿bean实例
Object singleton = this.getSingleton(beanName);
//如果此时还没有这个bean的实例,则获取它的定义来创建实例
if (singleton == null) {
//获取bean的定义
BeanDefinition beanDefinition = beanDefinitions.get(beanName);
if (beanDefinition == null) {
throw new BeansException("No bean.");
}
try {
singleton = Class.forName(beanDefinition.getClassName()).newInstance();
}
//新注册这个bean实例
this.registerSingleton(beanName, singleton);
}
return singleton;
}
public void registerBeanDefinition(BeanDefinition beanDefinition) {
this.beanDefinitions.put(beanDefinition.getId(), beanDefinition);
}
public Boolean containsBean(String name) {
return containsSingleton(name);
}
public void registerBean(String beanName, Object obj) {
this.registerSingleton(beanName, obj);
}
}
|
SimpleBeanFactory的改进
我们对SimpleBeanFactory进行了重要的改进,主要增加了对containsBean
和registerBean
方法的实现。通过代码分析,我们可以发现这两处实现都是针对单例Bean的操作。
ClassPathXmlApplicationContext的调整
在ClassPathXmlApplicationContext类中,我们也增加了对containsBean
和registerBean
的实现。这些改动使得Bean的管理和查询更为灵活和高效。
XmlBeanDefinitionReader的调整
除了SimpleBeanFactory和ClassPathXmlApplicationContext之外,XmlBeanDefinitionReader类也需要进行相应的调整,以确保整个Bean生命周期管理的一致性和完整性。
总结
通过这些改动,SimpleBeanFactory及其相关类的功能得到了增强,提高了Spring框架中Bean管理的效率和可用性。
1
2
3
4
5
6
|
public Boolean containsBean(String name) {
return this.beanFactory.containsBean(name);
}
public void registerBean(String beanName, Object obj) {
this.beanFactory.registerBean(beanName, obj);
}
|
XmlBeanDefinitionReader调整后如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class XmlBeanDefinitionReader {
SimpleBeanFactory simpleBeanFactory;
public XmlBeanDefinitionReader(SimpleBeanFactory simpleBeanFactory) {
this.simpleBeanFactory = simpleBeanFactory;
}
public void loadBeanDefinitions(Resource resource) {
while (resource.hasNext()) {
Element element = (Element) resource.next();
String beanID = element.attributeValue("id");
String beanClassName = element.attributeValue("class");
BeanDefinition beanDefinition = new BeanDefinition(beanID, beanClassName);
this.simpleBeanFactory.registerBeanDefinition(beanDefinition);
}
}
}
|
增加事件监听
构建好单例Bean之后,为了监控容器的启动状态,我们要增加事件监听。
我们先定义一下ApplicationEvent和ApplicationEventPublisher。通过名字可以看出,一个是用于监听应用的事件,另一个则是发布事件。
- ApplicationEventPublisher的实现
1
2
3
|
public interface ApplicationEventPublisher {
void publishEvent(ApplicationEvent event);
}
|
1
2
3
4
5
6
|
public class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 1L;
public ApplicationEvent(Object arg0) {
super(arg0);
}
}
|
Java事件监听与IoC容器增强
事件监听与ApplicationEvent
ApplicationEvent继承了Java工具包内的EventObject,实现了Java事件监听机制的简单封装。尽管目前还没有具体的实现,但为后续使用观察者模式解耦代码提供了基础。
IoC容器增强
引入了两个新概念:单例Bean和事件监听。事件监听部分目前只预留了入口,便于未来扩展。单例Bean是Spring框架的默认实现,我们提供了实现方法,并考虑到多线程高并发场景,使用了ConcurrentHashMap存储Bean信息。
单例Bean容器
至此,容器转变为管理单例Bean的容器。接下来,我们将为Bean注入属性值做准备。
属性注入
Spring支持三种属性注入方式:Field注入、Setter注入和构造器注入。
Setter注入配置
探讨如何在XML文件中声明使用Setter注入方式。
1
2
3
4
5
|
<beans>
<bean id="aservice" class="com.minis.test.AServiceImpl">
<property type="String" name="property1" value="Hello World!"/>
</bean>
</beans>
|
由上面的示例可以看出,我们在 <bean>
标签下引入了 <property>
标签,它又包含了type、name和value,分别对应属性类型、属性名称以及赋值。你可以看一下这个Bean的代码。
1
2
3
4
5
6
7
|
public class AServiceImpl {
private String property1;
public void setProperty1(String property1) {
this.property1 = property1;
}
}
|
配置构造器注入
接下来我们再看看怎么声明构造器注入,同样是在XML里配置。
1
2
3
4
5
6
|
<beans>
<bean id="aservice" class="com.minis.test.AServiceImpl">
<constructor-arg type="String" name="name" value="abc"/>
<constructor-arg type="int" name="level" value="3"/>
</bean>
</beans>
|
可以看到,与Setter注入类似,我们只是把 <property>
标签换成了 <constructor-args>
标签。
1
2
3
4
5
6
7
8
9
10
|
public class AServiceImpl {
private String name;
private int level;
public AServiceImpl(String name, int level) {
this.name = name;
this.level = level;
}
}
|
注入操作的核心目的是为Bean的各个属性赋值。选择注入方式应基于具体情境,以便捷性为标准。以下是两种主要的注入方式及其适用场景:
-
构造器注入:适用于必须在对象创建时立即提供所有必需属性的情况。这种方式确保了对象一旦被创建,就处于一个有效且一致的状态。
-
Setter注入:适用于对象创建后,属性值可以稍后设置或更改的情况。这种方式提供了更大的灵活性,允许对象在不同时间点拥有不同的状态。
如果构造器注入无法满足所有属性的赋值需求,可以将其与Setter注入结合使用,以实现更灵活的对象配置。
总之,注入操作的选择应基于实际需求和便捷性,以确保Bean对象能够以最高效和最合理的方式被初始化和配置。
1
2
3
4
5
6
7
8
|
<beans>
<bean id="aservice" class="com.minis.test.AServiceImpl">
<constructor-arg type="String" name="name" value="abc"/>
<constructor-arg type="int" name="level" value="3"/>
<property type="String" name="property1" value="Someone says"/>
<property type="String" name="property2" value="Hello World!"/>
</bean>
</beans>
|
现在我们已经明确了 <property>
和 <constructor-args>
标签的定义,但是只有外部的XML文件配置定义肯定是不行的,还要去实现。这就是我们接下来需要完成的工作。
实现属性类
与这个定义相关,我们要配置对应的属性类,分别命名为ArgumentValue和PropertyValue。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class ArgumentValue {
private Object value;
private String type;
private String name;
public ArgumentValue(Object value, String type) {
this.value = value;
this.type = type;
}
public ArgumentValue(Object value, String type, String name) {
this.value = value;
this.type = type;
this.name = name;
}
//省略getter和setter
}
|
1
2
3
4
5
6
7
8
9
|
public class PropertyValue {
private final String name;
private final Object value;
public PropertyValue(String name, Object value) {
this.name = name;
this.value = value;
}
//省略getter
}
|
在编程中,当我们提到Value
这个词且不带’s’时,通常指的是针对单个属性或参数的值。然而,在实际的应用场景下,比如在一个Bean对象中,我们经常会遇到需要处理多个属性或参数的情况。为了有效地管理这些多属性或多参数的情形,在Spring框架中采用了集合类来封装这些值,以提供更加灵活的操作方式。
受到Spring框架设计理念的启发,我们也开发了两个类似的类——ArgumentValues
和PropertyValues
,它们分别用于封装方法参数以及Bean属性的集合,并提供了丰富的功能,如添加、获取、判断等操作,旨在简化外部调用的同时支持对单个元素与整个集合的操作。
- ArgumentValues类:此aaaaaaa是专门用来封装方法调用时传递给方法的所有参数的一个容器。它不仅能够单独存储每个参数的信息,还允许作为整体进行一系列操作,从而提高了参数处理的灵活性与效率。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class ArgumentValues {
private final Map<Integer, ArgumentValue> indexedArgumentValues = new HashMap<>(0);
private final List<ArgumentValue> genericArgumentValues = new LinkedList<>();
public ArgumentValues() {
}
private void addArgumentValue(Integer key, ArgumentValue newValue) {
this.indexedArgumentValues.put(key, newValue);
}
public boolean hasIndexedArgumentValue(int index) {
return this.indexedArgumentValues.containsKey(index);
}
public ArgumentValue getIndexedArgumentValue(int index) {
return this.indexedArgumentValues.get(index);
}
public void addGenericArgumentValue(Object value, String type) {
this.genericArgumentValues.add(new ArgumentValue(value, type));
}
private void addGenericArgumentValue(ArgumentValue newValue) {
if (newValue.getName() != null) {
for (Iterator<ArgumentValue> it =
this.genericArgumentValues.iterator(); it.hasNext(); ) {
ArgumentValue currentValue = it.next();
if (newValue.getName().equals(currentValue.getName())) {
it.remove();
}
}
}
this.genericArgumentValues.add(newValue);
}
public ArgumentValue getGenericArgumentValue(String requiredName) {
for (ArgumentValue valueHolder : this.genericArgumentValues) {
if (valueHolder.getName() != null && (requiredName == null || !valueHolder.getName().equals(requiredName))) {
continue;
}
return valueHolder;
}
return null;
}
public int getArgumentCount() {
return this.genericArgumentValues.size();
}
public boolean isEmpty() {
return this.genericArgumentValues.isEmpty();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class PropertyValues {
private final List<PropertyValue> propertyValueList;
public PropertyValues() {
this.propertyValueList = new ArrayList<>(0);
}
public List<PropertyValue> getPropertyValueList() {
return this.propertyValueList;
}
public int size() {
return this.propertyValueList.size();
}
public void addPropertyValue(PropertyValue pv) {
this.propertyValueList.add(pv);
}
public void addPropertyValue(String propertyName, Object propertyValue) {
addPropertyValue(new PropertyValue(propertyName, propertyValue));
}
public void removePropertyValue(PropertyValue pv) {
this.propertyValueList.remove(pv);
}
public void removePropertyValue(String propertyName) {
this.propertyValueList.remove(getPropertyValue(propertyName));
}
public PropertyValue[] getPropertyValues() {
return this.propertyValueList.toArray(new PropertyValue[this.propertyValueList.size()]);
}
public PropertyValue getPropertyValue(String propertyName) {
for (PropertyValue pv : this.propertyValueList) {
if (pv.getName().equals(propertyName)) {
return pv;
}
}
return null;
}
public Object get(String propertyName) {
PropertyValue pv = getPropertyValue(propertyName);
return pv != null ? pv.getValue() : null;
}
public boolean contains(String propertyName) {
return getPropertyValue(propertyName) != null;
}
public boolean isEmpty() {
return this.propertyValueList.isEmpty();
}
}
|
首先,我们扩展了BeanDefinition类,新增了几个属性:lazyInit、dependsOn和initMethodName。这些属性分别用于控制Bean的延迟初始化、定义Bean依赖的其他Bean以及指定Bean的初始化方法。接下来,我们继续扩展BeanFactory接口,增强对Bean的处理能力。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class BeanDefinition {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
private boolean lazyInit = false;
private String[] dependsOn;
private ArgumentValues constructorArgumentValues;
private PropertyValues propertyValues;
private String initMethodName;
private volatile Object beanClass;
private String id;
private String className;
private String scope = SCOPE_SINGLETON;
public BeanDefinition(String id, String className) {
this.id = id;
this.className = className;
}
//省略getter和setter
}
|
BeanDefinition 属性扩展
在原有的Bean定义中,我们增加了以下属性以支持更灵活的Bean管理:
-
scope:标识Bean的作用域,区分单例模式(Singleton)和原型模式(Prototype)。
-
lazyInit:指示Bean是否延迟初始化,即在应用启动时不立即创建Bean实例。
-
initMethodName:指定一个初始化方法的名称,该方法将在Bean创建后被调用。
-
dependsOn:记录Bean之间的依赖关系,确保在创建Bean之前先创建其依赖的Bean。
-
构造器参数:定义Bean构造函数所需的参数。
-
property列表:定义Bean属性的值,用于设置Bean的属性。
BeanDefinitionRegistry 接口
为了集中管理BeanDefinition,我们引入了BeanDefinitionRegistry接口,它充当BeanDefinition的仓库,提供以下功能:
-
register:注册一个新的BeanDefinition。
-
remove:移除一个已存在的BeanDefinition。
-
get:获取一个BeanDefinition。
-
contains:检查是否存在某个BeanDefinition。
这些接口使得Bean的管理更加集中和有序。
1
2
3
4
5
6
|
public interface BeanDefinitionRegistry {
void registerBeanDefinition(String name, BeanDefinition bd);
void removeBeanDefinition(String name);
BeanDefinition getBeanDefinition(String name);
boolean containsBeanDefinition(String name);
}
|
随后调整BeanFactory,新增Singleton、Prototype的判断,获取Bean的类型。
1
2
3
4
5
6
7
|
public interface BeanFactory {
Object getBean(String name) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name);
boolean isPrototype(String name);
Class<?> getType(String name);
}
|
通过代码可以看到,我们让SimpleBeanFactory实现了BeanDefinitionRegistry,这样SimpleBeanFactory既是一个工厂同时也是一个仓库,你可以看下调整后的部分代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public class SimpleBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory, BeanDefinitionRegistry{
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
private List<String> beanDefinitionNames = new ArrayList<>();
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
this.beanDefinitionMap.put(name, beanDefinition);
this.beanDefinitionNames.add(name);
if (!beanDefinition.isLazyInit()) {
try {
getBean(name);
} catch (BeansException e) {
}
}
}
public void removeBeanDefinition(String name) {
this.beanDefinitionMap.remove(name);
this.beanDefinitionNames.remove(name);
this.removeSingleton(name);
}
public BeanDefinition getBeanDefinition(String name) {
return this.beanDefinitionMap.get(name);
}
public boolean containsBeanDefinition(String name) {
return this.beanDefinitionMap.containsKey(name);
}
public boolean isSingleton(String name) {
return this.beanDefinitionMap.get(name).isSingleton();
}
public boolean isPrototype(String name) {
return this.beanDefinitionMap.get(name).isPrototype();
}
public Class<?> getType(String name) {
return this.beanDefinitionMap.get(name).getClass();
}
}
|
修改完BeanFactory这个核心之后,上层对应的 ClassPathXmlApplicationContext部分作为外部集成包装也需要修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class ClassPathXmlApplicationContext implements BeanFactory,
ApplicationEventPublisher{
public void publishEvent(ApplicationEvent event) {
}
public boolean isSingleton(String name) {
return false;
}
public boolean isPrototype(String name) {
return false;
}
public Class<?> getType(String name) {
return null;
}
}
|
小结
在这节课中,我们对IoC容器进行了扩展和丰富,使其功能更接近于Spring框架。具体来说,我们模仿了Spring创建单例Bean的方式,并且增加了容器事件监听处理机制。此外,我们还增强了BeanDefinition
的属性,比如引入了lazyInit
(延迟初始化)和initMethodName
(初始化方法名)等特性,同时对BeanFactory
也做了相应的调整来支持这些新特性。
除此之外,我们也为构造器注入和Setter注入这两种依赖注入方式准备了基本的实例类,这将有助于后续实现它们。
随着这些改进,我们的IoC容器已经逐渐具备了更多Spring框架的特点。
构造器注入与Setter注入比较
相同点
不同点
-
时机不同:构造器注入发生在对象创建时;而Setter注入则可以在对象创建之后的任何时候进行。
-
强制性:构造器注入是必须的,在对象创建之前就必须提供所有必需的依赖;而Setter注入不是强制性的,可以只设置部分属性。
-
不变性:构造器注入使得对象一旦创建后其依赖就不可变,有利于创建不可变的对象;而Setter注入允许后期更改依赖关系。
优缺点
构造器注入
Setter注入