我猜你不知道FactoryBean是什么东西

  • 2019 年 12 月 20 日
  • 筆記

先说重点

BeanFactory

BeanFactory定义了IOC容器的最基本形式,并提供了IOC容器应遵守的的最基本的接口,也就是Spring IOC所遵守的最底层和最基本的编程规范。在Spring代码中,BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactoryXmlBeanFactoryApplicationContext等,都是附加了某种功能的实现。BeanFactory不是这文的重点。

FactoryBean

先看名字,根据经验猜测这是一个Bean。事实它的确是一个Bean,作用用一句话描述的话,就是一个生成Bean的工厂Bean。一般情况下,在Spring中可以使用注解xmlJavaConfig的方式配置产生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可以了解下