Spring 类型转化

今天有个比较好玩的问题记录下。首先说下问题:

在 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!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值