《系列二》-- 3、FactoryBean 的使用

发布时间 2023-06-24 10:12:42作者: bokerr

FactoryBean 解决的问题

我们已知,spring 创建bean 利用的是反射机制, 当我们想要从容器中获取某个Bean,只需要使用配置的Class信息,通过反射即可得到Bean的实例对象。

上边所述的是一般场景下的bean 创建,只有一个动作,反射拿到对象完事。但是考虑下,如果在真正的业务场景下,Bean 的创建可能还需要包括一些属性的初始化,下边举个栗子:

假如我们现在需要对代码进行安全整改,数据库口令、静态token等信息禁止通过配置文件配置,必须在运行时动态从配置中心获取,我们有一个管理所有配置的单例bean, 那么当应用启动过程中,打开数据库链接前,就必须完成这个配置管理bean的初始化。


@Setter
@Getter
public class AppConfigManager {
    private Object mysqlPassword;
    private Object redisPassword;
    private Object other;
}

/**
 * 通过自定义方法,配置初始化,这里可能涉及一些无法通过配置 bean.xml 来达成的事情。 
 */
public class ConfigManagerUtil {
    private static AppConfigManager initConfig(Object anyInfo) {
        AppConfigManager appConfigManager = new AppConfigManager();
        appConfigManager.mysqlPassword = ConfigManagerUtil.getRemoteMysqlConfig();
        appConfigManager.redisPassword = ConfigManagerUtil.getRemoteRedisConfig();
        appConfigManager.other = ConfigManagerUtil.getRemoteOther();
    }
    private static Object getRemoteMysqlConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }
    private static Object getRemoteRedisConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }
    private static Object getRemoteOther() {
        // do something ,that maybe can`t configuration by bean.xml
    }
}


上文定义了两个伪代码类:一个时配置管理对象类,另一个是一个工具类,它负责配置管理类的初始化

  • AppConfigManager
  • ConfigManagerUtil

如果我们想要简单的按照如下方式,向容器注入配置管理类肯定是不可能的,AppConfigManager 必须经过额外的操作来初始化。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
	https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
	<bean id="action" class="com.bokerr.AppConfigManager"/>
</beans>

FactoryBean 接口初识

Spring 官方引入了一个接口: FactoryBean 没错它是个泛型。

就像上边我们需要通过 ConfigManagerUtil 去管理 AppConfigManager那样,我们可以认为:FactoryBean 管理的泛型对象就是它维护的 Bean。

factoryBean_struct

我们可以看下边的接口代码:

public interface FactoryBean<T> {
    /***
     * 返回 FactoryBean 管理的泛型对象- Class 对象
     */
    @Nullable
	T getObject() throws Exception;

    /***
     * 返回 FactoryBean 管理的泛型对象- Class 对象
     */
	@Nullable
	Class<?> getObjectType();

	default boolean isSingleton() {
	    // 默认单例
		return true;
	}
}

那么,接下来我们就只需要在, ConfigManagerUtil 工具上实现 FactoryBean 接口。

然后就可以继续通过 bean.xml 向容器注入 AppConfigManager对象了?

改造结果

接下来,结合: FactoryBean 我们改写下之前的代码。

ConfigManagerUtil 重命名为: ConfigManagerFactoryBean


/**
 * 通过自定义方法,配置初始化,这里可能涉及一些无法通过配置 bean.xml 来达成的事情。 
 */
public class ConfigManagerFactoryBean implements FactoryBean<AppConfigManager> {

    private Boolean isInit = new Boolean("false"); // sys
    
    private AppConfigManager singleTone;

    @Nullable
    T getObject() throws Exception {
        // 双重锁保证单例, 这里我没仔细深究 getObject() 有没有从外部实现过单例控制,疑罪从无原则,为了保证可靠性,我自己又实现了一次。
        // 【根据 FactoryBean 的默认方法 isSingleton() 来看大概率spring 容器已经帮我们完成了这里的单例控制。】 
        if (!isInit){
            isInit = new Boolean("true");
            synchronized (isInit) {
                Object params = defaultInfo();
                return initConfig(params);
            }
        }
        ConfigManagerFactoryBean.updateConif();
        return singleTone;
    }

    @Nullable
    Class<AppConfigManager> getObjectType() {
        AppConfigManager.class;
    }

    /**
     * 初始化配置
     */
    private static AppConfigManager initConfig(Object anyInfo) {
        AppConfigManager appConfigManager = new AppConfigManager();
        appConfigManager.mysqlPassword = ConfigManagerFactoryBean.getRemoteMysqlConfig();
        appConfigManager.redisPassword = ConfigManagerFactoryBean.getRemoteRedisConfig();
        appConfigManager.other = ConfigManagerFactoryBean.getRemoteOther();
    }

    /**
     * 配置更新
     */
    private static void updateConif() {
        singleTone.mysqlPassword = ConfigManagerUtil.getRemoteMysqlConfig();
        singleTone.redisPassword = ConfigManagerUtil.getRemoteRedisConfig();
        singleTone.other = ConfigManagerUtil.getRemoteOther();
    }

    private static Object getRemoteMysqlConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }

    private static Object getRemoteRedisConfig() {
        // do something ,that maybe can`t configuration by bean.xml
    }

    private static Object getRemoteOther() {
        // do something ,that maybe can`t configuration by bean.xml
    }
}

完成上述的改造后,我们只需要配置如下的 bean.xml 即可:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
	https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <!-- 虽然这里配置的Class是ConfigManagerFactoryBean, 但是通过beanName = "configBean" 获取的bean 对象实例会是:AppConfigManager -->
	<bean id="configBean" class="com.bokerr.ConfigManagerFactoryBean" scope="singleton"/>
</beans>

在容器初始化的过程中会判断 [ beans>.bean>.class ] 配置的类的类型:

  • 如果没有实现接口:FactoryBean,那么容器直接根据 [class] 配置值,反射出对象实例后,直接返回即可

  • 如果发现它实现了 FactoryBean 接口,那么获取bean 的时候就不是直接返回反射得到的对象,而是会去调用: [ 根据 Class 反射出来的 FactoryBean 对象的 getObject() 方法 ]

我想通过本文你已经简单的认识了 FactoryBean 接口了。




这里遗留了一个问题:FactoryBean 的 getObject()方法的调用过程中,spring 容器自身是否提供了该方法返回值的单例控制实现,不再需要我们从 getObject() 方法内部来实现单例。