BeanFactory定义了IOC容器的最基本形式,并提供了IOC容器应遵守的的最基本的接口,也就是Spring IOC所遵守的最底层和最基本的编程规范。在Spring代码中,BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory
、XmlBeanFactory
、ApplicationContext
等,都是附加了某种功能的实现。BeanFactory不是这文的重点。
先看名字,根据经验猜测这是一个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;
}
}
返回由FactoryBean创建的Bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中
返回需创建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
可以了解下