Spring源码-FactoryBean-应用拓展附源码解析

46次阅读

共计 7095 个字符,预计需要花费 18 分钟才能阅读完成。

前言

在学习 Spring Core 中 IOC 容器时,你肯定会接触到 BeanFactory 这个 Spring 中最基础的 IOC 容器。这个应该是大家学习 Spring 源码时最先接触到的类了。Spring 中还存在这一个 FactoryBean 类,两者拼写上十分相似,并且使用频率都十分得高。在一些 Spring 面试题,也会问你这两者有什么区别。

这里先说结论:

  • BeanFactory:Spring 中的 IoC 容器,所有 Spring Bean 的 Factory
  • FactoryBean:一个 Bean,一个不简单的 Bean,一个能产生对象或者修饰对象生成的工厂 Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

在学习 Spring 源码和其他开源项目的源码的过程当中,发现 FactoryBean 是一些框架在做集成 Spring 时经常会使用到的类,本文具体讲述的也是 FactoryBean 的简单实用和具体应用拓展。

What is FactoryBean

Spring 中有两种类型的 Bean,一种是普通 Bean,另一种是工厂 Bean 即 FactoryBean。

一般情况下,Spring 通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean。在某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,则需要在 <bean> 中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 org.Springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑。(看后面的一些例子会理解更深刻)

所以说,当配置一个 <bean> 的过程非常复杂,创建过程中涉及到很多其他的 bean 和复杂的逻辑,用 xml 配置比较困难,这时可以考虑用 FactoryBean

接口定义

package org.springframework.beans.factory;

public interface FactoryBean<T> {T getObject() throws Exception;
    
    Class<?> getObjectType();
    
    boolean isSingleton();}

在一些开源框架上的使用

MyBatis-Spring # SqlSessionFactoryBean

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="***" />
        <property name="configLocation" value="***"/>
        <property name="mapperLocations" value="***"/>
    </bean>
public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {...}

阿里开源的分布式服务框架 Dubbo # ReferenceBean<T>

<?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:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd        http://dubbo.apache.org/schema/dubbo        http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
 
    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
    <dubbo:application name="consumer-of-helloworld-app"  />
 
    <!-- 使用 multicast 广播注册中心暴露发现服务地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234" />
 
    <!-- 生成远程服务代理,可以和本地 bean 一样使用 demoService -->
    <dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" />
</beans>

<dubbo:reference 对应的 Bean 是com.alibaba.dubbo.config.spring.ReferenceBean

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {...}

拓展实践

涉及

  • ProduceLocation 生产地区
  • Material 材料
  • ProductFactoryBean 产品工厂 bean
  • Product 产品
  • Boostrap 启动类
  • test-config.xml 测试配置文件

ProduceLocation

@Data
public class ProduceLocation {

    private String locationName;

    private double distanceKm;

    private double pricePerPerKm;
}

Material

@Data
public class Material {

    private String name;

    private double pricePerGram;

    private double weight;
}

Product

@Data
@Builder
public class Product {

    private Material material;

    private ProduceLocation location;

    private double price;
}

ProductFactoryBean

@Setter
@Getter
public class ProductFactoryBean implements FactoryBean<Product> {

    private Material material;

    private ProduceLocation produceLocation;

    @Override
    public Product getObject() throws Exception {return Product.builder()
                .location(produceLocation)
                .material(material)
                .price(cal(material, produceLocation))
                .build();}

    private double cal(Material material, ProduceLocation produceLocation) {return material.getPricePerGram() * material.getWeight()
                + produceLocation.getDistanceKm() * produceLocation.getPricePerPerKm();
    }

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

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

test-config.xml

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

    <bean id="produceLocation" class="base.ioc.FactoryBeanDemoSet.ProduceLocation">
        <property name="locationName" value="杭州"/>
        <property name="pricePerPerKm" value="151.01"/>
        <property name="distanceKm" value="3.1"/>
    </bean>
    <bean id="material" class="base.ioc.FactoryBeanDemoSet.Material">
        <property name="name" value="巧克力豆"/>
        <property name="pricePerGram" value="100"/>
        <property name="weight" value="50"/>
    </bean>
    <bean id="product" class="base.ioc.FactoryBeanDemoSet.ProductFactoryBean">
        <property name="material" ref="material"/>
        <property name="produceLocation" ref="produceLocation"/>
    </bean>
</beans>

Boostrap

/**
 * @author Richard_yyf
 * @version 1.0 2019/9/21
 */
public class Bootstrap {public static void main(String[] args) throws Exception {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-config.xml");
        Product product = (Product) context.getBean("product");
        System.out.println(product.toString());
    }
}

输出

Product(material=Material(name= 巧克力豆, pricePerGram=100.0, weight=50.0), location=ProduceLocation(locationName= 杭州, distanceKm=3.1, pricePerPerKm=151.01), price=5468.131)

上述的配置当然也可以改用 java config 的方式来做。

上述是一个简单业务的示例,你还可以通过 FactoryBean 来对一些开源工具 API 使用进行一些封装,比如对 httpClient 创建过程做了一些封装,例如超时时间、连接池大小、http 代理等。

特性

给定一个 id=mybean 的 FactoryBean,getBean("mybean")得到的就是这个 FactoryBean 创建的对象实例,而 getBean("&mybean") 得到的确实 FactoryBean 自身对象。

根据上述的 demo,运行如下代码

/**
 * @author Richard_yyf
 * @version 1.0 2019/9/21
 */
public class Bootstrap {public static void main(String[] args) throws Exception {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-config.xml");
        // Product product = (Product) context.getBean("product");
        // System.out.println(product.toString());
        
        FactoryBean<Product> factoryBean = (ProductFactoryBean) context.getBean("&product");
        System.out.println(factoryBean.getObject().toString());
    }
}

Output

Product(material=Material(name= 巧克力豆, pricePerGram=100.0, weight=50.0), location=ProduceLocation(locationName= 杭州, distanceKm=3.1, pricePerPerKm=151.01), price=5468.131)

对应源码

先直接锁定对应逻辑源码,

    protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

        // Don't let calling code try to dereference the factory if the bean isn't a factory.
        // BeanFactoryUtils.isFactoryDereference(name)方法判断 name 是否以 & 前缀
        if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
        }
        
       // Now we have the bean instance, which may be a normal bean or a FactoryBean.
        // If it's a FactoryBean, we use it to create a bean instance, unless the
        // caller actually wants a reference to the factory.
        if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {return beanInstance;}

        Object object = null;
        if (mbd == null) {object = getCachedObjectForFactoryBean(beanName);
        }
        if (object == null) {
            // Return bean instance from factory.
            FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
            // Caches object obtained from FactoryBean if it is a singleton.
            if (mbd == null && containsBeanDefinition(beanName)) {mbd = getMergedLocalBeanDefinition(beanName);
            }
            boolean synthetic = (mbd != null && mbd.isSynthetic());
            object = getObjectFromFactoryBean(factory, beanName, !synthetic);
        }
        return object;
    }
  1. BeanFactoryUtils.isFactoryDereference(name) 判断是否在获取 FactoryBean 的引用

        // 不为空且以”&“开头
        public static boolean isFactoryDereference(String name) {return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
        }
        String FACTORY_BEAN_PREFIX = "&";
  2. 如果进来的 beanInstance 不是 FactoryBean,但调用者是用获取FactoryBean 的引用时,抛出 BeanIsNotAFactoryException 异常
  3. 如果调用者是要获取 FactoryBean 的引用,且beanInstanceFactoryBean(前面有判断),则直接返回beanInstance
  4. 如果进来的 beanInstance 是普通 bean,直接返回beanInstance
  5. 通过进来的FactoryBean 来创建一个对应的 bean

正文完
 0