FactoryBean能怎么用呢

本文探讨了Spring中的FactoryBean和BeanFactory的区别,FactoryBean作为一个特殊的Bean,用于定制实例化逻辑。通过一个实际项目场景,展示了如何使用FactoryBean实现动态切换服务版本,满足不重启应用的服务切换需求,并遵循开闭原则。文章还提供了测试代码和解决方案,解释了如何正确注入和使用FactoryBean。
摘要由CSDN通过智能技术生成

读者们应该知道BeanFactory。它是Spring Ioc容器的底层定义。FactoryBean这家伙跟它长得很像,二者的区别也是面试中面试官比较喜欢问的。下面我贴一下我的关于这二者区别的学习笔记。

Spring 5 学习笔记


  • FactoryBean和BeanFactory的区别
    • BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖.
    • FactoryBean是个Bean.在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上&。

从概念上理解不难,但是这个FactoryBean该怎么用呢?笔者在spring源码阅读过程中发现,有好多好多的它的实现类。可见它的地位不同一般。接下里以笔者实际自身项目的例子来演示。

需求背景

有个接口服务有一个稳定版本,经过了线上的历次磨炼。最近给这个服务增加了另外一个版本实现–增强版本。性能更猛,特性更强。对于普通接口服务,直接做接口兼容升级,经过测试就基本可以上线。但是这是一个非常核心的底层接口服务(基础架构部),不能随便来。我们得有这么个保护措施。就是一开始上增强版,当在线上环境出现不稳当时,能够不重启应用,马上使服务接口切换为稳定版继续提供服务。当然这个切换可以在这二者任意切换。

这个需求有几个点需要关注

  1. 接口兼容:这是基本常识,必须保证外层调用方感知不到。
  2. 不能重启应用:意味着可以借助配置中心的实时配置特性
  3. 遵守“开闭原则”,不要更改其他外部代码。
  4. 设置埋点:除了监控系统外,埋一些能够预警服务不稳定的埋点。

编码

我们先看一个发布出去的常规RPC接口服务。
接口定义

public interface Service {

    void doService();
}

稳定版本实现

@org.springframework.stereotype.Service
public class StableServiceImpl implements Service {

    private final static Logger log = LoggerFactory
            .getLogger(StableServiceImpl.class);

    @Override
    public void doService() {
        log.info("我是稳定版  纵天崩地裂 我依旧稳如狗....");
    }
}

RPC暴露服务。里面注入了我们稳定版本实现来委派实现。

public class ServiceRpcImpl implements Service {

    @Autowired
    private Service service;
   
    @Override
    public void doService() {
        service.doService();
    }
}

现在我们增加一个增强版本的实现

@org.springframework.stereotype.Service
public class EnhanceServiceImpl implements Service {

    private final static Logger log = LoggerFactory.getLogger(EnhanceServiceImpl.class);
    
    @Override
    public void doService() {
    	// todo 埋点
        log.info("我是增强版 但稳定性需要在线上锤一锤...");
    }
}

铺垫好了,现在主角来了。我们的FactoryBean,具有工厂和装饰器两种模式,非常适合我们的场景。
定义一个我们的FactoryBean–ServiceFactory

@Component
@RefreshScope
public class ServiceFactory implements FactoryBean<Service>, ApplicationContextAware {

    private final static Logger log = LoggerFactory.getLogger(ServiceFactory.class);
    /**
     * 稳定版的bean name
     */
    private final String STABLE = "stableServiceImpl";
    /**
     * 增强版的bean name
     */
    private final String ENHANCE = "enhanceServiceImpl";
    /**
     * 应用程序上线文
     */
    private ApplicationContext applicationContext;
    /**
     * Nacos配置中心的开关配置
     */
    @Value("${switch:false}")
    private boolean serviceSwitchOn;

    @Override
    public Service getObject() {
        return (Service) (serviceSwitchOn ? applicationContext.getBean(STABLE) : applicationContext.getBean(ENHANCE));
    }
    
    @Override
    public Class<?> getObjectType() {
        return Service.class;
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

提一个问题 :这个ServiceFactory 能直接用吗?可以直接用,但是它实际是一个动态代理对象,这里是实现了接口,所以它具体为一个JDK代理对象,由于这个特殊性,我们不能像平时的spring bean直接注入。
例如这样就是不行的。因为类型不一样,无法注入。

    @Autowired
    private ServiceFactory serviceFactory;

该怎么用呢?还记得开头那里笔者的笔记内容吗?现在来演示一下

@Component
@Primary
public class SmartService implements Service, ApplicationContextAware {

    private ApplicationContext applicationContext;
	
    private Service autoSelect() {
        return (Service) applicationContext.getBean("serviceFactory");
    }
   
    @Override
    public void doService() {
        autoSelect().doService();
    }
     @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

serviceFactory是我们的FactoryBean的bean name。这样就可以动态根据开关实时拿到我们想要的实现对象。

由于开闭原则,我们不能去修改外部调用层的代码,哪怕是注解也不要动。比如ServiceRpcImpl
在这里插入图片描述
所以怎么办呢?不改@Autowired就注入失败了呀(多个候选者)。所以上@Primary。让SmartService 作为明面,内部进行委派转发,这样一来外部调用可以保持不变。

测试

我们写个controller模仿我们RPC暴露层来测试一下。先上增强版。

@RestController
public class DiscoveryController {

    @Autowired
    private Service service;

    @GetMapping("/do")
    public void doService() {
        service.doService();
    }
}

在这里插入图片描述
Nacos切换开关
在这里插入图片描述
再请求一次。
在这里插入图片描述

本文就到此结束了,我们下文见,谢谢
github: honey开源系列组件作者

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊杰eboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值