02|扩展Bean:如何配置constructor、property和init-method?

你好,我是郭屹。 上节课,我们初步实现了一个MiniSpring框架,它很原始也很简单。我们实现了一个BeanFactory,作为一个容器对Bean进行管理,我们还定义了数据源接口Resource,可以将多种数据源注入Bean。 这节课,我们继续增强IoC容器,我们要做的主要有3点。

  1. 增加单例Bean的接口定义,然后把所有的Bean默认为单例模式。
  2. 预留事件监听的接口,方便后续进一步解耦代码逻辑。
  3. 扩展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映射关系。

  • beanNames:用于存储所有单例Bean的别名。

  • singletons:存储Bean名称和实现类的映射关系。

线程安全考虑

为了保证在多线程并发环境下的线程安全,我们采取了以下措施:

  • singletons定义为ConcurrentHashMap,这是一种线程安全的哈希表,能够在多线程环境下提供高效的并发访问。

  • 在实现registrySingleton方法时,我们使用了synchronized关键字来确保方法的线程安全。这确保了在任何时候,只有一个线程能够修改Bean的注册信息,从而保证了单例Bean的唯一性。

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进行了重要的改进,主要增加了对containsBeanregisterBean方法的实现。通过代码分析,我们可以发现这两处实现都是针对单例Bean的操作。

ClassPathXmlApplicationContext的调整

在ClassPathXmlApplicationContext类中,我们也增加了对containsBeanregisterBean的实现。这些改动使得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);
}
  • ApplicationEvent的实现
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注入和构造器注入

  • Field注入:直接给Bean中的变量赋值。

  • Setter注入:通过调用setXXX()方法注入值。

  • 构造器注入:在构造器中传入参数进行注入。 本节课,我们将实现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的各个属性赋值。选择注入方式应基于具体情境,以便捷性为标准。以下是两种主要的注入方式及其适用场景:

  1. 构造器注入:适用于必须在对象创建时立即提供所有必需属性的情况。这种方式确保了对象一旦被创建,就处于一个有效且一致的状态。

  2. 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框架设计理念的启发,我们也开发了两个类似的类——ArgumentValuesPropertyValues,它们分别用于封装方法参数以及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();
    }
}
  • PropertyValues类
 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注入比较

相同点

  • 目的相同:两种注入方式都是用来解决对象之间的依赖关系,实现控制反转(IoC)。

  • 使用场景:在需要注入依赖的情况下都可以使用。

  • 配置方式:在XML配置或注解配置中都可以指定。

不同点

  • 时机不同:构造器注入发生在对象创建时;而Setter注入则可以在对象创建之后的任何时候进行。

  • 强制性:构造器注入是必须的,在对象创建之前就必须提供所有必需的依赖;而Setter注入不是强制性的,可以只设置部分属性。

  • 不变性:构造器注入使得对象一旦创建后其依赖就不可变,有利于创建不可变的对象;而Setter注入允许后期更改依赖关系。

优缺点

构造器注入

  • 优点

  • 提供了明确的依赖关系,易于理解和维护。

  • 通过构造函数确保了每个依赖项都已初始化,避免了null指针异常。

  • 支持不可变对象的创建。

  • 缺点

  • 当依赖过多时,构造函数参数列表会变得很长。

  • 对于可选依赖不太友好。

Setter注入

  • 优点

  • 更加灵活,可以在运行时改变依赖。

  • 可以很容易地添加新的依赖而不影响现有代码。

  • 对于可选依赖更加方便。

  • 缺点

  • 如果不正确地管理依赖,则可能导致null指针异常。

  • 不利于创建不可变对象。 如果你有任何想法或者想要深入探讨这个话题,请随时留言交流!别忘了分享给可能感兴趣的朋友哦!期待下节课再见!