读者们应该知道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源码阅读过程中发现,有好多好多的它的实现类。可见它的地位不同一般。接下里以笔者实际自身项目的例子来演示。
需求背景
有个接口服务有一个稳定版本,经过了线上的历次磨炼。最近给这个服务增加了另外一个版本实现–增强版本。性能更猛,特性更强。对于普通接口服务,直接做接口兼容升级,经过测试就基本可以上线。但是这是一个非常核心的底层接口服务(基础架构部),不能随便来。我们得有这么个保护措施。就是一开始上增强版,当在线上环境出现不稳当时,能够不重启应用,马上使服务接口切换为稳定版继续提供服务。当然这个切换可以在这二者任意切换。
这个需求有几个点需要关注
- 接口兼容:这是基本常识,必须保证外层调用方感知不到。
- 不能重启应用:意味着可以借助配置中心的实时配置特性
- 遵守“开闭原则”,不要更改其他外部代码。
- 设置埋点:除了监控系统外,埋一些能够预警服务不稳定的埋点。
编码
我们先看一个发布出去的常规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开源系列组件作者