不管是平时,还是面试,现在对于spring的讨论少了很多,不再像刚进入人们视野时,那么抢眼;spring现在更像空气一样,只要是java构建的项目,十之八九都是建立在spring之上,因此不可轻视,温故而知新
继《BeanFactory与FactoryBean》之后,再看FactoryBean的应用
在上面的文章中指定FactoryBean可以更加灵活的创建Bean,那就从Spring管理Bean方式说起
XML配置Bean
这种方式,是最早的,借助XML,spring创建bean,来分离调用方与被调用方
代码语言:javascript复制<bean id="helloWorld" class="me.spring.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
但是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
代码语言:javascript复制@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
代码语言:javascript复制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,类似于设计模式中的工厂模式和装饰器模式