不管是平时,还是面试,现在对于spring的讨论少了很多,不再像刚进入人们视野时,那么抢眼;spring现在更像空气一样,只要是java构建的项目,十之八九都是建立在spring之上,因此不可轻视,温故而知新
继《BeanFactory与FactoryBean》之后,再看FactoryBean的应用
在上面的文章中指定FactoryBean可以更加灵活的创建Bean,那就从Spring管理Bean方式说起
XML配置Bean
这种方式,是最早的,借助XML,spring创建bean,来分离调用方与被调用方
id="helloWorld" class="me.spring.beans.HelloWorld"> name="name" value="Spring">
但是xml不够简洁,且没有编译时检查等功能
@Component @Service
注解开始流行,spring陆续引入了很多注解,关于Bean的有很多
@Component @Service @Repositiory
Java注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的
这些都是把Bean托管给容器
@Bean
从spring3.0开始,可以使用@Configuration与@Bean组合来创建Bean
此注解相对于@Service更灵活了些,如果一个第三方jar中的类需要托管给spring,是没法在类注明@Service注解的,
当然你可以使用XML来实现,但已经约定大于配置,xml不再推荐
除了突破第三方jar限制外,还有
- 关注点分离,使用@Bean,可以把Bean的创建全部放到了一个地方,接口及其实现不再与具体框架性代码有任何耦合
FactoryBean
虽然@Bean已经相对@Service很灵活了,但还是需要使用@Bean声明,有一些场景无法应对
比如包装RPC,之前写过《RPC的套路》,其实这儿有点AOP的味道
远程service数量是不定的,不能会每一个service去创建并声明上@Bean
一、创建一个FactoryBean
FactroyBean创建的Bean是getObjectType()返回的类型,怎么动态指定此remoteServer,需要使用BeanDefinitionBuilder
@Slf4j public class RemoteProxyFactoryBean<T> implements FactoryBean<T> , MethodInterceptor { @Setter @Getter private Class<T> remoteService; @Setter @Getter private String url; @Override public T getObject() throws Exception { T service = ProxyFactory.getProxy(remoteService, this); return service; } @Override public Class<T> getObjectType() { return remoteService; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { log.info("RemoteProxyFactoryBean invoke"); Method method = invocation.getMethod(); log.info("method:{} invoked",method.getName()); if (method.getDeclaringClass() == Object.class && method.getName().equals("toString")) { return remoteService.getName() + "@" + System.identityHashCode(remoteService); } //远程调用service,可以使用HTTP,也可以是TCP StringBuilder sb = new StringBuilder(url); sb.append(method.getName()); sb.append("?var="); sb.append(Arrays.toString(invocation.getArguments())); log.info("get url:{}",sb.toString()); return null; } }
二、BeanDefinitionBuilder构建Bean注册spring
使用BeanDefinitionBuilder来构建RemoteProxyFactoryBean的属性,并注册到spring; 注意到上面的RemoteProxyFactoryBean并没有使用@Component托管给spring
private void registeRemoteService() throws IOException { SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(this.applicationContext); //读取此包下的所有类,进行包装 Resource[] resources = applicationContext.getResources(resolvePackageToScan("com.jack.remote.service")); List<String> classNames = Arrays.stream(resources).map(resource -> { MetadataReader metadataReader = null; try { metadataReader = metadataReaderFactory.getMetadataReader(resource); } catch (IOException e) { e.printStackTrace(); } return metadataReader.getClassMetadata().getClassName(); }).collect(Collectors.toList()); //托管给容器 classNames.forEach(clazz -> { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(RemoteProxyFactoryBean.class); beanDefinitionBuilder.addPropertyValue("remoteService",clazz).addPropertyValue("url","http://remotehost/"); defaultListableBeanFactory.registerBeanDefinition(clazz+"-proxy",beanDefinitionBuilder.getBeanDefinition()); }); }
三、装配
对于远程service包下的类,就可以直接使用@Autowired来装配
总结
至此FactoryBean已经完结,对于它的作用已经了解:它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式