欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[Spring] 实例化Bean的三种方法

程序员文章站 2022-05-22 20:26:24
...

Spring框架的核心是IOC容器,它是个管理对象的工厂,常说的两个专业名词“控制反转”和“依赖注入”,就是它最重要的两大特征:

  1. 控制反转:创建对象的权利交给IOC容器,程序员负责声明要创建的对象
  2. 依赖注入:对象之间的依赖关系由IOC容器负责实现,程序员负责声明这种依赖关系,依赖关系就是指对象内部有哪些属性

Spring容器中的对象叫做Bean,本文要讲的实例化Bean方法,说的就是“控制反转”这个特性,程序员怎么声明Bean,容器怎么创建Bean

现在Spring已经发展到Spring Boot阶段了,大量使用注解配置,应用起来更加高阶,程序也更加“智能”。

但是我个人感觉,可能还是XML配置更容器理解原理特性,因此,以下程序都采用XML配置书写。

方法一:构造器

有一个对象类定义,该对象和其它对象没有依赖关系:

package com.comeheart.spring.entity;

public class User {
}

Spring标准XML配置文件中,程序员需要用<bean/>节点声明要创建的对象:

<?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="user" class="com.comeheart.entity.User"></bean>

</beans>

其中,id属性声明对象在容器中的唯一标识符,class属性声明对象的全路径类名。

Spring容器读取到XML声明后,利用java反射机制调用User类的构造器,实例化该对象。以下源码有省略:

package org.springframework.beans;

public abstract class BeanUtils {

    public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
        Assert.notNull(ctor, "Constructor must not be null");
        // 省略代码
        return ctor.newInstance(argsWithDefaultValues);
    }
    
}

方法二:静态工厂方法

改造一下User类定义,首先禁用构造器实例化,然后提供静态工厂方法返回对象实例:

package com.comeheart.spring.entity;

public class User {

    private static User user = new User();

    private User() {
    }

    public static User createInstance() {
        return user;
    }

}

SpringXML声明如下:

<?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="user" class="com.comeheart.spring.entity.User" factory-method="createInstance"></bean>

</beans>

class属性和factory-method属性结合,表示使用User类的静态工厂方法createInstance()创建该对象。有两点需要注意:

  1. factory-method方法返回值必须是class属性声明的类型
  2. factory-method方法本身必须属于class属性声明的类型

为什么factory-method方法一定要属于class类型?

因为Spring<bean/>定义里面,没有提供类似factory-class这样的声明,也就是不能指定工厂类,所以,默认的,factory-method属性声明的方法就属于class属性声明的类了。

咋一看,静态工厂方法不如构造器简洁,但是它有好处,程序员可以在IOC容器实例化对象的过程中,更*的操作对象。

Spring容器读取XML配置后,判断对象声明中有factory-method属性和class属性,就会调用该类的工厂方法,获取对象实例:

package org.springframework.beans.factory.support;

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
        // 省略前后代码
        // mbd.getFactoryMethodName() 就对应着 factory-method 声明
        if (mbd.getFactoryMethodName() != null) {
    	    return instantiateUsingFactoryMethod(beanName, mbd, args);
        }
    }    

}

方法三:对象工厂方法

User类定义回归到原来的样子:

package com.comeheart.spring.entity;

public class User {
}

新增一个Locator类定义,表示工厂对象,其中的对象工厂方法创建User类实例:

public class Locator {

    private static User user = new User();

    public User createUserInstance() {
        return user;
    }

}

此时,Locator内部需要有一个实实在在的User对象,这种“需要”就叫做“依赖”,Locator类依赖User类。

这种依赖关系如果交给Spring容器去处理,就是“依赖注入”的概念。

在这里,我并没有这么做,依赖关系由Locator自己管理,自己去new User()对象。

说回来,SpringXML配置文件声明如下:

<?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="locator" class="com.comeheart.spring.entity.Locator"></bean>

    <bean id="user" factory-bean="locator" factory-method="createUserInstance"></bean>

</beans>

IOC容器读取XML配置后,会先使用构造器方法创建Locator对象,然后调用Locator对象的createUserInstance()工厂方法获取User对象。

静态工厂方法、对象工厂方法,Spring实现的两者代码逻辑都是一样的。

都会先判断factory-method逻辑,这和上一节代码相同:

package org.springframework.beans.factory.support;

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
        // 省略前后代码
        // mbd.getFactoryMethodName() 就对应着 factory-method 声明
        if (mbd.getFactoryMethodName() != null) {
    	    return instantiateUsingFactoryMethod(beanName, mbd, args);
        }
    }    

}

追踪其中的instantiateUsingFactoryMethod(...)逻辑,可以得到:

package org.springframework.beans.factory.support;

class ConstructorResolver {

    public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    
        Object factoryBean;
        Class<?> factoryClass;
        boolean isStatic;

        String factoryBeanName = mbd.getFactoryBeanName();
        if (factoryBeanName != null) {
            if (factoryBeanName.equals(beanName)) {
                throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, "factory-bean reference points back to the same bean definition");
            }
            factoryBean = this.beanFactory.getBean(factoryBeanName);
            if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
                throw new ImplicitlyAppearedSingletonException();
            }
            factoryClass = factoryBean.getClass();
            isStatic = false;
        } else {
            // It's a static factory method on the bean class.
            if (!mbd.hasBeanClass()) {
                throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, "bean definition declares neither a bean class nor a factory-bean reference");
            }
            factoryBean = null;
            factoryClass = mbd.getBeanClass();
            isStatic = true;
		}
    }

}

这段代码中,有两个方法域变量factoryBeanfactoryClass

  1. factoryBean是对象工厂方法需要的
  2. factoryClass是静态工厂方法需要的

通过factoryBeanName做一个判断,就能确定该对象的实例化方式,是静态工厂方法,还是对象工厂方法。

容器启动与应用

容器加载配置启动成功以后,就可以使用容器提供的接口,获取各种实例化对象信息:

public class App {

    public static void main(String[] args) {
        // Spring容器启动
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 获取对象等使用方法
        User user = context.getBean("user", User.class);
        Class<?> clazz = context.getType("user");
    }
    
}