Spring源码学习四——FactoryBean的作用

一、FactoryBean

先看FactoryBean的注释
在这里插入图片描述
第一段翻译:BeanFactory管理的对象有些会实现这个接口,实现了这个接口的对象用来生产单个对象的工厂。如果一个bean实现了这个接口,他将作为一个工厂来暴露一个新对象,而不是直接作为工厂暴露自己本身。
第二段翻译:实现了这个接口的bean不能当作普通bean使用。一个FactoryBean以普通bean的形式定义,但是总是通过getObject()方法来创建它所包装的bean,并暴露出去。

1.1 getObject

在这里插入图片描述
注释翻译:返回被这个工厂管理的实例。如同BeanFactory一样,支持单例和多例模式。调用此方法时如果对象尚未被完整初始化,将会抛出异常

光看它的方法和注释可能还是不明白FactoryBean到底有啥用,咱们来看看源码是怎么对他进行处理的

二、源码中的特殊处理

在这里插入图片描述
这块是创建bean的实例(具体创建过程可以看本专栏前几篇文章),调用完getSingleton之后,产生的实例已经加入到容器中。继续进入beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
在这里插入图片描述
第一个if判断是否要获取factory本身,第二个判断bean实例是否实现了FactoryBean,进入if就直接返回了。
在这里插入图片描述
能走进红框代码说明,当前实例是实现了FactoryBean的,并且getObject产生的对象未被缓存。继续进入
在这里插入图片描述
此时外层if的条件是满足的,因为我们的bean都是单例的,而且factory本身也是已经进入了缓存。factoryBeanObjectCache存的是getObject返回的对象,此时肯定是空的,因此内层if也会进入,进去object = doGetObjectFromFactoryBean(factory, beanName);
在这里插入图片描述
就在这里调用了getObject(),返回之后在getObjectFromFactoryBean中将返回的对象加入到factoryBeanObjectCache。因此getBean的过程中产生了两个对象,一个factory本身,一个getObject返回的对象,它们存在不同的地方,前者是singletonObjects,后者是factoryBeanObjectCache。

三、使用示例

Spring对FactoryBean做了特殊处理,到底这样做了之后有什么用呢?在另一个MyBatis的专栏中有使用到。在这里模拟手写一下Spring如何从Mybatis缓存中拿到Mapper代理对象,这个过程中就会用到FactoryBean

3.1 MapperScan

自定义一个MapperScan注解,依赖自定义的注册器YexMapperScannerRegistrar

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(YexMapperScannerRegistrar.class)
public @interface MapperScan {
    String value() default "";
}

3.2 YexMapperScannerRegistrar

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class YexMapperScannerRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String path = (String) importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()).get("value");
        YexClassPathMapperScanner scanner = new YexClassPathMapperScanner(registry);
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        scanner.scan(path);
    }
}

ImportBeanDefinitionRegistrar被回调,这块new了一个自定义的BeanDefinition扫描器YexClassPathMapperScanner。它将扫描指定的路径,并将得到的BeanDefinition放入容器。

3.3 YexClassPathMapperScanner

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;

import java.util.Set;


public class YexClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    public YexClassPathMapperScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //使用父类的方法扫描指定的包路径,返回的beanDefinitions 已经在容器当中
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
		//处理beanDefinition
        processBeanDefinitions(beanDefinitions);

        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        AbstractBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (AbstractBeanDefinition) holder.getBeanDefinition();

            String beanClassName = definition.getBeanClassName();
            //设置bean实例化时使用的构造方法
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
			//将definition的类型改为YexMapperFactoryBean,自定义的FactoryBean
            definition.setBeanClass(YexMapperFactoryBean.class);
        }
    }

    @Override
    public boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }
}

super.doScan(basePackages);扫描得到的BeanDefinition是接口,无法实例化,因此在后面将beanClass设置为YexMapperFactoryBean。那么在实例化的时候就会进行特殊处理(前面有讲到),先产生一个YexMapperFactoryBean对象(名称是“&+beanName”),再产生一个getObject返回的对象(名称就是beanName)。因此,在依赖注入时获得的是getObject返回的对象

3.4 YexMapperFactoryBean

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;

public class YexMapperFactoryBean implements FactoryBean {

    private SqlSession sqlSession;

    private Class mapperInterface;

	//实例化时设置属性
    public YexMapperFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    //注入一个Mybatis的SqlSessionFactory,添加mapper接口,该过程会产生代理 
    @Autowired
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
        this.sqlSession = sqlSessionFactory.openSession();
    }

	//得到Mybatis产生的Mapper代理对象
    @Override
    public Object getObject() throws Exception {
        return sqlSession.getMapper(mapperInterface);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    public void setMapperInterface(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

}

实例化YexMapperFactoryBean
### 3.4.1
添加进Mybatis的缓存
在这里插入图片描述
得到Mapper代理对象
在这里插入图片描述

四、总结

  1. FactoryBean包装一个对象,使用getObject()方法来获取真正的实例化对象
  2. FactoryBean包装的对象一般来自第三方,无法添加@Component等注解,就无法直接被Spring管理
  3. 这些对象是已经被实例化了的,有统一的来源,例如来自Mybatis
  4. 当然也可以自定义一个bean,使用FactoryBean来包装,但这好像是多余的。@Bean注解可以很好的解决需要自定义实例化、初始化过程的bean
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值