0.9、Spring 源码学习 FactoryBean

前言

Spring 允许我们直接将一个 Java 类注册为 SpringBean ,同时还提供了另外一种方式,即 FactoryBean 方式,通过直接或者间接实现 FactoryBean 接口,我们同样可以将一个 Java 类注册为 SpringBean。

FactoryBean 接口介绍

功能简介

FactoryBean 支持泛型,通过实现 FactoryBean 接口,子类可以成为 具体某一个 Java 类的工厂类,这样我们在 Spring context 中获取这个Java 类时,就可以直接通过 FactoryBean 来进行了。

具体来说,无须再将这个 Java 类声明为 SpringBean,而是将这个类对应的 FactoryBean 注册为 SpringBean。
这样有什么好处呢?原本你能将这个 Java 类注册为 普通的 SpringBean,实例化过程你是参与不了的,FactoryBean 则将这个 Java 类的实例化工作交给了你,就是可以进行自定义了。

当然,这里还隐含一个问题,我们在日常使用中并没有注意到 FactoryBean 的存在,即在调用过程中是无感的,Spring 是怎么做到的呢?

下面我们从应用层面来看看 FactoryBean 的使用。

源码路径

org.springframework.beans.factory.FactoryBean

FactoryBean 源码内容

FactoryBean 是一个支持泛形的接口,其内部有三个方法。
泛型的作用是指定该 FactoryBean 子类要具体实例化的 Java 类。
三个内部方法中,最主要的是 getObject(),子类将在这个方法中完成 泛型中指定的 Java 类 的实例化工作。

package org.springframework.beans.factory;
import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

	/**
	* 子类在内部手动实例化 T 类型的对象
	**/
    @Nullable
    T getObject() throws Exception;
	
	/**
	* 返回 T 的类型,比如 String.class
	**/
    @Nullable
    Class<?> getObjectType();
	
	/**
	*  是否是单例模式,默认为 true
	**/
    default boolean isSingleton() {
        return true;
    }
}

FactoryBean 的使用

事实上,Spring 中还是存在一些“约定俗成”的东西的,FactoryBean 就算是其中一个。这主要体现在通过 FactoryBean 获取 SpringBean 的过程中。
FactoryBean 是一个编程式规范,其具体实现不应该依赖于注解驱动的注入或者反射功能.。
FactoryBean 子类本身也是 SpringBean ,但是我们却要通过它获取另外一个 Java 对象,Spring 使用了一个命名区分的逻辑,当你使用FactoryBean的beanName来获取时,Spring会调用这个名字对应Bean的 getObject() ,此时它是一个工厂类,当你需要获取这个工厂类本身时,你可以在 beanName 前增加 “&” 符号,此时你获取的就是 这个 FactoryBean子类本身的 SpringBean 了。

自定义普通类 SimpleClass

这个类将成为 FactoryBean 子类的泛型限定类,也是 FactoryBean 子类 工厂负责产生对象的类型。

package com.bestcxx.test;

/**
 * @Title: 普通 bean
 * @Description: 自定义的一个普通 bean,正常而言我们会将该类注册为 SpringBean,但是这里我们不会这样做,而是测试 FactoryBean 的效果
 */
public class SimpleBean {
    /**自定义参数:时间戳*/
    private Long timestamp;

    public void printTimestamp(){
        System.out.println("timestamp="+this.timestamp);
    }
    public Long getTimestamp() {
        return timestamp;
    }
    public void setTimestamp(Long timestamp) {
        this.timestamp = timestamp;
    }

}

自定义 FactoryBean 子类 MyFactoryBean

本类将完成三件事:

  • 实现 FactoryBean 接口,这里子类我命名为 MyFactoryBean,并指定泛型为 SimpleBean
  • 覆盖重写 FactoryBean 接口吧三个方法 ,完成 SimpleBean 实例化逻辑
  • 使用注解方式将 MyFactoryBean 注册为 SpringBean
package com.bestcxx.test;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Title: ClassA
 * @Description: ClassA
 */
@Component
public class MyFactoryBean implements FactoryBean<SimpleBean> {

    /**
     * 通过 MyFactoryBean.getObject() 方法给 SimpleBean 增加了一些额外的操作
     * @return
     * @throws Exception
     */
    @Override
    public SimpleBean getObject() throws Exception {
        SimpleBean simpleBean=new SimpleBean();
        simpleBean.setTimestamp(new Date().getTime());
        return simpleBean;
    }

    @Override
    public Class<?> getObjectType() {
        return SimpleBean.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

xml 配置文件

提供下 xml 配置文件,路径为 src/main/resources/test/applicationContext-test.xml
关键代码就一句 < context:component-scan base-package=“com.bestcxx.test”/>

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.bestcxx.test"/>
</beans>

调用时 beanName 中 “&” 是区分 MyFactoryBean 和 SimpleBean 的关键

为了保证调用过程的无感,FactoryBean 的 beanName 默认表示 SimpleBean 对象,如果你想获取 MyFactoryBean ,则需要在 beanName 前加“&”。
比如本文,获取 SimpleBean对象 就用 “myFactoryBean”,如果获取 MyFactoryBean 这个 SpringBean 本身,则用 “&myFactoryBean”。
这一块的逻辑由Spring 负责,在 bean 初始化和获取阶段有体现。简单来说就是 如果 SpringBean 为 FactoryBean 类型,则判断获取 beanName 是否有 “&” 前缀,如果没有则返回 getObject() 的结果值,否则则返回 MyFactoryBean 本身。
可能有人会问,“simpleBean” 还有用吗?由于本文代码没有特别指定 beanName,所以从 Spring 角度来看, 只有 "myFactoryBean" 被注册为了 springBean的 beanName,“simpleBean”是不能用的。

在这里插入图片描述

测试方法

&myFactoryBean 用于测试 MyFactoryBean 这个 SpringBean的获取
myFactoryBean 用于测试 SimpleBean 的获取

测试代码
package com.bestcxx.test;

import junit.framework.TestCase;
import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Title: FactoryBean 测试
 * @Description: MyFactoryBean 的测试类
 */
public class MyFactoryBeanTest {

    /**
     * 测试 使用 FactoryBean的名字 和 &FactoryBean的名字 加载bean 的效果
     */
    @Test
    public void testMyBeanFactoryBean(){

        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:test/applicationContext-test.xml");
        TestCase.assertEquals("出错了",applicationContext!=null,true);
		
		// &myFactoryBean 获取 FactoryBean 类型对象
        Object object=applicationContext.getBean("&myFactoryBean");
        TestCase.assertEquals("出错了2",object!=null,true);
        if(object instanceof SimpleBean){
            ((SimpleBean) object).printTimestamp();
        }else if(object instanceof FactoryBean){
            System.out.println("获取到了 myFactoryBean 的实例 ");
        }

		// myFactoryBean 获取 SimpleBean 类型对象
        Object object2=applicationContext.getBean("myFactoryBean");
        TestCase.assertEquals("出错了3",object2!=null,true);
        if(object2 instanceof SimpleBean){
            ((SimpleBean) object2).printTimestamp();
        }else if(object2 instanceof FactoryBean){
            System.out.println("获取到了 myFactoryBean 的实例 ");
        }
    }
}

测试结果
获取到了 myFactoryBean 的实例 
timestamp=1576383881254
发布了459 篇原创文章 · 获赞 229 · 访问量 187万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章