今天有个比较好玩的问题记录下。首先说下问题:
在 Spring Data Redis 官方文档中,可以看到这样一个常规用法。
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="defaultSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
</bean>
<bean id="redisService" class="com.xxxx.xxx.redisService">
<property name="template" ref="redisTemplate"/>
<property name="listOps" ref="redisTemplate"/>
<property name="hashOps" ref="redisTemplate"/>
<property name="valueOps" ref="redisTemplate"/>
<property name="setOps" ref="redisTemplate"/>
<property name="zsetOps" ref="redisTemplate"/>
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
这里注入 XxxOps,为什么指定的都是 redisTemplate,原因是?
首先第一想法肯定是redistemplate 是子类实现,xxoperations是父类。那么真的是这样吗?
那么先从继承关系看。
RedisTemplate继承关系是
ListOperations继承关系
可以看到RedisTemplate 和 ListOperations 并没有继承关系,这里是怎么把 RedisTemplate 注入到 ListOperations 类型上去的呢?而且不但可以将 RedisTemplate 注入到 ListOperations ,也可以注入到 ValueOperations、SetOperations、ZSetOperations、HashOperations 等类型上。
这时候就看看源码吧。
所有Ops的类都继承了AbstractOperations这个类。这个类的构造方法是
final RedisTemplate<K, V> template;
AbstractOperations(RedisTemplate<K, V> template) {
this.template = template;
}
然后所有的Ops的构造方法是,例如ListOperations的
DefaultValueOperations(RedisTemplate<K, V> template) {
super(template);
}
然后RedisTemplate的实现是
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
private final ValueOperations<K, V> valueOps = new DefaultValueOperations<>(this);
private final ListOperations<K, V> listOps = new DefaultListOperations<>(this);
private final SetOperations<K, V> setOps = new DefaultSetOperations<>(this);
private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations<>(this, new ObjectHashMapper());
private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations<>(this);
private final GeoOperations<K, V> geoOps = new DefaultGeoOperations<>(this);
private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations<>(this);
private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations<>(this);
这时是不是就有个想法是,通过构造方法注入的形式实现的呢?
于是我模拟写了个Test Demo
public class Test1 {
final Test test;
public Test1(Test test) {
this.test = test;
}
public void a(){
System.out.println(11);
test.bb();
}
public void bb(){
System.out.println(test.test1());
}
}
@Component("test")
public class Test {
private Test1 test1 = new Test1(this);
public Test1 test1(){
return test1;
}
public void bb(){
System.out.println(22);
}
}
@RestController
public class TestController{
@Resource(name="test")
private Test1 test1;
@RequestMapping("/test")
public String test(){
test1.a();
test1.bb();
return "json";
}
}
可以看下运行结果
这时候就认为确实是通过构造方法注入的形式实现。但是这时候有人说不是,它是通过PropertyEditor实现的。那么PropertyEditor是怎么做的呢?
Spring 框架可以通过 java.beans.PropertyEditor 接口的实现类来实现类型转换。Spring Data Redis 提供了 ListOperationsEditor 可以将 RedisTemplate 转为 ListOperations。具体如下:
class ListOperationsEditor extends PropertyEditorSupport {
public void setValue(Object value) {
if (value instanceof RedisOperations) {
super.setValue(((RedisOperations) value).opsForList());
} else {
throw new IllegalArgumentException("Editor supports only conversion of type " + RedisOperations.class);
}
}
}
如果 PropertyEditor 类与它们处理的类在同一个包中,并且类名再加上 Editor 后缀,则无需显式注册,该 PropertyEditor 可以被自动发现。
可以看到,ListOperations 类和 ListOperationsEditor 都在 org.springframework.data.redis.core 包下,且 ListOperationsEditor 符合命名规则,即在 ListOperations 类名上加上 Editor 后缀,所以可以自动发现并生效。
那么redis这个疑问是得到回答了,但是刚才尝试的构造方法注入其实也可以,那么具体spring用的哪个逻辑呢?
于是debug源码吧,看看具体逻辑:
之前的寻找的就忽略不提了,直接上重点。
可以看到是请求到ConversionUtils 是转换工具类,之后执行converter.convert方法。
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
Class<?> sourceClass = sourceType.getType();
Class<?> targetClass = targetType.getType();
Member member = getValidatedMember(targetClass, sourceClass);
try {
if (member instanceof Method) {
Method method = (Method) member;
ReflectionUtils.makeAccessible(method);
if (!Modifier.isStatic(method.getModifiers())) {
return method.invoke(source);
}
else {
return method.invoke(null, source);
}
}
else if (member instanceof Constructor) {
Constructor<?> ctor = (Constructor<?>) member;
ReflectionUtils.makeAccessible(ctor);
return ctor.newInstance(source);
}
}
catch (InvocationTargetException ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException());
}
catch (Throwable ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex);
}
// If sourceClass is Number and targetClass is Integer, the following message should expand to:
// No toInteger() method exists on java.lang.Number, and no static valueOf/of/from(java.lang.Number)
// method or Integer(java.lang.Number) constructor exists on java.lang.Integer.
throw new IllegalStateException(String.format("No to%3$s() method exists on %1$s, " +
"and no static valueOf/of/from(%1$s) method or %3$s(%1$s) constructor exists on %2$s.",
sourceClass.getName(), targetClass.getName(), targetClass.getSimpleName()));
}
可以看到转换类会通过构造方法Constructor去创建目标class的对象,也就是说这时候Test1的对象新创建的,这时候就看Resource注解对应获取对象单例如何实现了,这次就不说了,直接说结果,是会删除本地cache,以最后生成的为主。
那么知道构造如何处理的,PropertyEditor怎么使用的呢,那就是在ConversionUtils调用的上层。
也就是说如果配置了PropertyEditor,那么会执行PropertyEditor的逻辑,只有没有配置PropertyEditor才会走构造方法注入的逻辑。
今天分享下整体查具体疑问的流程,bye!