Lettuce 同步操作源码分析

Lettuce 同步操作源码分析

通过Lettuce官网介绍,我们发现Lettuce通过如下方式,进行同步操作

RedisAdvancedClusterCommands<String, String> commands = connection.sync();
String name = commands.get("name");
System.out.println(name);

那么同步和异步操作,有什么不同呢?我们接下来,通过Lettuce源码,进行解答。

public StatefulRedisClusterConnectionImpl(RedisChannelWriter writer, RedisCodec<K, V> codec, Duration timeout) {

    super(writer, timeout);
    this.codec = codec;

    this.async = new RedisAdvancedClusterAsyncCommandsImpl<>(this, codec);
    this.sync = (RedisAdvancedClusterCommands) Proxy.newProxyInstance(AbstractRedisClient.class.getClassLoader(),
            new Class<?>[] { RedisAdvancedClusterCommands.class }, syncInvocationHandler());
    this.reactive = new RedisAdvancedClusterReactiveCommandsImpl<>(this, codec);
}

@Override
public RedisAdvancedClusterCommands<K, V> sync() {
    return sync;
}

我们发现,command的sync方法,返回的sync属性,而sync属性,则是通过动态代理创建的RedisAdvancedClusterCommands对象。那么,对于了解动态代理的朋友,相比已经知道了,其内部操作,是通过InvocationHandler进行的。
在这里,Lettuce通过syncInvocationHandler方法,创建InvocationHandler实现类,对应于ClusterFutureSyncInvocationHandler。现在,让给我们看一下其对应的handleInvocation方法。

@Override
    protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {

        try {

            if (method.isDefault()) {
                return methodHandleCache.computeIfAbsent(method, ClusterFutureSyncInvocationHandler::lookupDefaultMethod)
                        .bindTo(proxy).invokeWithArguments(args);
            }

            if (method.getName().equals("getConnection") && args.length > 0) {
                return getConnection(method, args);
            }

            if (method.getName().equals("readonly") && args.length == 1) {
                return nodes((Predicate<RedisClusterNode>) args[0], ClusterConnectionProvider.Intent.READ, false);
            }

            if (method.getName().equals("nodes") && args.length == 1) {
                return nodes((Predicate<RedisClusterNode>) args[0], ClusterConnectionProvider.Intent.WRITE, false);
            }

            if (method.getName().equals("nodes") && args.length == 2) {
                return nodes((Predicate<RedisClusterNode>) args[0], ClusterConnectionProvider.Intent.WRITE, (Boolean) args[1]);
            }

            Method targetMethod = apiMethodCache.computeIfAbsent(method, key -> {

                try {
                    return asyncApi.getClass().getMethod(key.getName(), key.getParameterTypes());
                } catch (NoSuchMethodException e) {
                    throw new IllegalStateException(e);
                }
            });

            Object result = targetMethod.invoke(asyncApi, args);

            if (result instanceof RedisFuture) {
                RedisFuture<?> command = (RedisFuture<?>) result;
                if (!method.getName().equals("exec") && !method.getName().equals("multi")) {
                    if (connection instanceof StatefulRedisConnection && ((StatefulRedisConnection) connection).isMulti()) {
                        return null;
                    }
                }
                return LettuceFutures.awaitOrCancel(command, getTimeoutNs(command), TimeUnit.NANOSECONDS);
            }

            return result;

        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }

在这里通过asyncApi属性,获取最终调用的方法。后续,通过Object result = targetMethod.invoke(asyncApi, args)得到处理结果。而asyncApi属性,在构造方法处,进行初始化。

this.async = new RedisAdvancedClusterAsyncCommandsImpl<>(this, codec);

看到这里,已经清楚了,同步操作,是通过异步方式,进行的处理。不同之处在于,异步方法返回值后,再通过

LettuceFutures.awaitOrCancel(command, getTimeoutNs(command), TimeUnit.NANOSECONDS)

获取其操作结果。
结论:

  1. 同步get操作,是通过异步get方法,进行的redis操作。
  2. 在获取异步结果后,通过LettuceFutures.awaitOrCancel,获取同步结果。

为了方便理解上面提到的态代理,在这里,进行简单的介绍。首先,让我们看一下Proxy注释。

Each proxy instance has an associated invocation handler
object, which implements the interface {@link InvocationHandler}.
A method invocation on a proxy instance through one of its proxy
interfaces will be dispatched to the {@link InvocationHandler#invoke
invoke} method of the instance’s invocation handler, passing the proxy
instance, a {@code java.lang.reflect.Method} object identifying
the method that was invoked, and an array of type {@code Object}
containing the arguments. The invocation handler processes the
encoded method invocation as appropriate and the result that it
returns will be returned as the result of the method invocation on
the proxy instance.

我们发现,调用代理的方法时,会自动转移到InvocationHandler实现类的invoke方法,从而得到处理结果。
针对于Proxy对象的创建,官方,提供如下两种方式

InvocationHandler handler = new MyInvocationHandler();
proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class&lt;?&gt;[] { Foo.class },handler);

现在让我们看一下Proxy.newInstance这种方式。

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

其中,第一个参数,用来指定用于加载Proxy,第二个参数,用于指定当前Proxy实现了那些接口,第三个参数,用于确定

  1. loader:类加载器,用于加载Proxy。
  2. interfaces:类实现的全部接口
  3. h:调用处理器

到这里,这部分源码分析已完成,不足之处,请各位大佬,不吝赐教。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要做个有钱人2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值