背景
RedisFactoryBean作为一个FactoryBean,用来生成访问redis用的client,可以是主从模式的,也可以是集群模式的。
疑问
如果一个项目里同时配置了redisClient 和redisClusterClient, 那么它们的工厂类RedisFactoryBean 实例会一样吗? bean的xml配置如下:
<!-- 测试主从 -->
<bean id="redisClient" class="com.common.redis.RedisFactoryBean">
<property name="hostAndPort" value="0.0.0.2:6602"/>
<property name="password" value="password"/>
<property name="isCluster" value="false"/>
<property name="testOnBorrow" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="poolMinEvictableIdleTimeMillis" value="60000"/>
<property name="poolTimeBetweenEvictionRunsMillis" value="30000"/>
<property name="poolMinIdle" value="1"/>
<property name="poolMaxIdle" value="20"/>
<property name="poolMaxTotal" value="100"/>
<property name="soTimeout" value="30000" />
</bean>
<!-- 测试集群 -->
<bean id="redisClusterClient" class="com.common.redis.RedisFactoryBean">
<property name="hostAndPort" value="0.0.0.1:6602"/>
<property name="password" value="password"/>
<property name="poolMinIdle" value="5"/>
<property name="poolMaxIdle" value="20"/>
<property name="poolMaxTotal" value="100"/>
<property name="soTimeout" value="1000" />
<property name="maxRedirects" value="5" />
<property name="testWhileIdle" value="true"/>
<property name="poolMinEvictableIdleTimeMillis" value="60000"/>
<property name="poolTimeBetweenEvictionRunsMillis" value="30000"/>
<property name="isCluster" value="true" />
</bean>
源码解析
Bean配置信息中, BeanDefinitionParserDelegate类负责解析<bean>元素
// 处理<bean>元素的id、name 和别名属性
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// id属性值
String id = ele.getAttribute(ID_ATTRIBUTE);
// name属性值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// alias属性值
List<String> aliases = new ArrayList<String>();
// 将所有name属性值放到别名中
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
// 检查<bean> 元素所配置的id或者name的唯一性
// containingBean 标识<bean>元素中是否包含子<bean>元素
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 配置的bean定义进行解析
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
// 如果<bean>元素中没有配置id、别名或者name, 且没有包含子元素
// <bean>元素,则为解析的bean生成一个唯一beanName并注册
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
// 如果<bean>元素中没有配置id、别名或者name, 且包含子元素
// <bean>元素,则为解析的bean使用别名向IOC容器注册
beanName = this.readerContext.generateBeanName(beanDefinition);
// 为了解析的Bean使用别名注册时,为了向后兼容
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
BeanDefinitionReaderUtils 向spring IoC容器注册解析的bean
// 将解析的BeanDefinitionHold注册到Spring IOC容器中
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
// 向IOC容器注册beanDefinition
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
// 向IOC容器注册别名
registry.registerAlias(beanName, alias);
}
}
}
结论
所以,因为它们设置的id 不同, beanName也不同,在ioc容器存储的BeanDefinition也是不同的。依赖注入时,根据beanName从ioc容器获取到不同的BeanDefinition, 那么实例化时redisClient的RedisFactoryBean 和redisClusterClient的也会是不同的实例。
延伸
那同一个<bean>配置时,RedisFactoryBean什么情况实例会相同,什么情况实例会不同呢?
bean的scope配置默认为单例模式, 其中两种模式区别如下:
单例模式: 提供了具有特定名称的全局共享实例对象,可以在查询时对其进行检索;
原型模式:确定每次检索都会创建单独的实例对象。
那么我们可以分别配置scope为singleton 和 prototype, 测试RedisFactoryBean的生成情况。
当scope="prototype" 时,测试代码如下:
<bean id="redisClient" scope="prototype" class="com.common.redis.RedisFactoryBean">
<property name="hostAndPort" value="0.0.0.1:6602"/>
......
</bean>
@Test
public void testRedisBeanFactory() throws Exception{
ApplicationContext ac = new FileSystemXmlApplicationContext("classpath:applicationContext_test.xml");
RedisFactoryBean redisFactoryBean = (RedisFactoryBean) ac.getBean("&redisClient");
RedisFactoryBean redisFactoryBean2 = (RedisFactoryBean) ac.getBean("&redisClient");
Assert.assertTrue(redisFactoryBean == redisFactoryBean2);
}
测试结果:
|
因此,当scope="prototype" 时, 同个beanName, 从ioc容器检索得到的两个实例是不同的。
同样的,当scope="singleton" 时,同个beanName, 从ioc容器检索得到的两个实例是相同的。