我猜你不知道FactoryBean是什么东西
- 2019 年 12 月 20 日
- 筆記
先说重点
BeanFactory
BeanFactory定义了IOC容器的最基本形式,并提供了IOC容器应遵守的的最基本的接口,也就是Spring IOC所遵守的最底层和最基本的编程规范。在Spring代码中,BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory
、XmlBeanFactory
、ApplicationContext
等,都是附加了某种功能的实现。BeanFactory不是这文的重点。
FactoryBean
先看名字,根据经验猜测这是一个Bean。事实它的确是一个Bean,作用用一句话描述的话,就是一个生成Bean的工厂Bean。一般情况下,在Spring中可以使用注解、xml、JavaConfig的方式配置产生bean加入到ioc的容器中,但是在某些情况下,实例化bean的过程复杂或者需要更加灵活的配置的时候,就可以考虑 FactoryBean
这个工厂接口来实例化bean。例如mybatis-spring中的SqlSessionFactoryBean
先看下接口
public interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); default boolean isSingleton() { return true; } }
getObject
返回由FactoryBean创建的Bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中
getObjectType
返回需创建Bean的类型,这方法在使用泛型的时候可以不重写,直接返回null,但是在创建prototype
的bean的时候这里最好返回Bean的类型。还有一点是,如果不是使用泛型,getObjectType也不返回对象类型,使用Autowired
将无法注入对象。
isSingleton
是否单例
实战
哈哈,这也是我写这笔记的原因。一直都知道FactoryBean的存在,但没有找到适合的应用场景,今天给我遇到了。需求是这样的
请求esb提供的接口获取数据,esb实现的方式是 webservice 。报文是又长又臭的xml,都写在类的话,对于强迫症的我是接受不了的,所有想到了下面这个方法
报文写在xml中,定义FactoryBean在实例化的过程中读取xml,通过动态代理映射到接口的方法中。仅文字有点抽象,我们看代码
1.定义接口
public interface EsbService { String func019(String phoneNumber); String func155(String customerId); }
上面这个类定义了两个接口,func019和func155对应esb系统中的两个接口,把对应的请求报文放在resources目录中,像下面这个样子,一一对应方法名称
|resources |---esb |-----func019.xml |-----fubc155.xml
2.定义FactoryBean
getObject中使用了JDK动态代理创建EsbService,目的就是像mybatis的Mapper一样,仅需定义接口和xml就可以实现方法。
@Component public class EsbServiceFactoryBean implements FactoryBean<EsbService> { @Override public EsbService getObject() throws Exception { return (EsbService) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{EsbService.class}, new EsbServiceProxy("esb")); } @Override public Class<?> getObjectType() { return EsbService.class; } }
3.动态代理的逻辑
public class EsbServiceProxy implements InvocationHandler { /** * xml文件后缀 */ private static final String XML_FILE_SUFFIX = ".xml"; /** * esb接口请求地址 */ private static final String ESB_URL = ""; /** * 缓存xml */ private Map<String, String> cacheXml = new ConcurrentHashMap<>(); public EsbServiceProxy(String path) throws Exception { // 读取报文到cacheXml中 File folder = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + path); File[] esbXmlFiles = folder.listFiles(); if (esbXmlFiles != null) { for (File esbXmlFile : esbXmlFiles) { String fileName = esbXmlFile.getName().replace(XML_FILE_SUFFIX, ""); String xml = FileUtil.readString(esbXmlFile, StandardCharsets.UTF_8); cacheXml.put(fileName, xml); } } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); String xml = cacheXml.get(methodName); xml = StrUtil.format(xml, args); return HttpUtil.createPost(ESB_URL) .header("Content-type", "text/xml") .header("SOAPAction", "call") .body(xml) .execute() .body(); } }
over,收工。以后需要添加其它api接口的只要把报文往文件夹一放,定义接口签名就可以了。
总结
大概的流程是:读取xml -> 缓存到map -> 方法签名与xml映射 -> 动态代理实现 -> 注入容器
有时候总觉得学的很多东西没有用处,大概就是对该知识点了解得不够深入,再需要的时候没有想出来。除了FactoryBean,还有Spring中的BeanDefinitionRegistryPostProcessor
可以了解下