spring-data-redis BoundOperationsProxyFactory 源码
spring-data-redis BoundOperationsProxyFactory 代码
文件路径:/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.core;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
/**
* Utility to create implementation objects for {@code Bound…Operations} so that bound key interfaces can be implemented
* automatically by translating interface calls to actual {@code …Operations} interfaces.
*
* @author Mark Paluch
* @since 3.0
*/
class BoundOperationsProxyFactory {
private final Map<Method, Method> targetMethodCache = new ConcurrentHashMap<>();
/**
* Create a proxy object that implements {@link Class boundOperationsInterface} using the given {@code key} and
* {@link DataType}. Calls to {@code Bound…Operations} methods are bridged by forwarding these either to the
* {@code operationsTarget} or a default implementation.
*
* @param boundOperationsInterface the {@code Bound…Operations} interface.
* @param key the bound key.
* @param type the {@link DataType} for which to create a proxy object.
* @param operations the {@link RedisOperations} instance.
* @param operationsTargetFunction function to extract the actual delegate for method calls.
* @param <T>
* @return the proxy object.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> T createProxy(Class<T> boundOperationsInterface, Object key, DataType type,
RedisOperations<?, ?> operations, Function<RedisOperations<?, ?>, Object> operationsTargetFunction) {
DefaultBoundKeyOperations delegate = new DefaultBoundKeyOperations(type, key, (RedisOperations) operations);
Object operationsTarget = operationsTargetFunction.apply(operations);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addInterface(boundOperationsInterface);
proxyFactory.addAdvice(new DefaultMethodInvokingMethodInterceptor());
proxyFactory.addAdvice(
new BoundOperationsMethodInterceptor(key, operations, boundOperationsInterface, operationsTarget, delegate));
return (T) proxyFactory.getProxy();
}
Method lookupRequiredMethod(Method method, Class<?> targetClass, boolean considerKeyArgument) {
Method target = lookupMethod(method, targetClass, considerKeyArgument);
if (target == null) {
throw new IllegalArgumentException("Cannot lookup target method for %s in class %s; This appears to be a bug"
.formatted(method, targetClass.getName()));
}
return target;
}
@Nullable
Method lookupMethod(Method method, Class<?> targetClass, boolean considerKeyArgument) {
return targetMethodCache.computeIfAbsent(method, it -> {
Class[] paramTypes;
if (isStreamRead(method)) {
paramTypes = new Class[it.getParameterCount()];
System.arraycopy(it.getParameterTypes(), 0, paramTypes, 0, paramTypes.length - 1);
paramTypes[paramTypes.length - 1] = StreamOffset[].class;
} else if (considerKeyArgument) {
paramTypes = new Class[it.getParameterCount() + 1];
paramTypes[0] = Object.class;
System.arraycopy(it.getParameterTypes(), 0, paramTypes, 1, paramTypes.length - 1);
} else {
paramTypes = it.getParameterTypes();
}
return ReflectionUtils.findMethod(targetClass, method.getName(), paramTypes);
});
}
private boolean isStreamRead(Method method) {
return method.getName().equals("read")
&& method.getParameterTypes()[method.getParameterCount() - 1].equals(ReadOffset.class);
}
/**
* {@link MethodInterceptor} to delegate proxy calls to either {@link RedisOperations}, {@code key},
* {@link DefaultBoundKeyOperations} or the {@code operationsTarget} such as {@link ValueOperations}.
*/
class BoundOperationsMethodInterceptor implements MethodInterceptor {
private final Class<?> boundOperationsInterface;
private final Object operationsTarget;
private final DefaultBoundKeyOperations delegate;
public BoundOperationsMethodInterceptor(Object key, RedisOperations<?, ?> operations,
Class<?> boundOperationsInterface, Object operationsTarget, DefaultBoundKeyOperations delegate) {
this.boundOperationsInterface = boundOperationsInterface;
this.operationsTarget = operationsTarget;
this.delegate = delegate;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
switch (method.getName()) {
case "getKey":
return delegate.getKey();
case "rename":
delegate.rename(invocation.getArguments()[0]);
return null;
case "getOperations":
return delegate.getOps();
}
if (method.getDeclaringClass() == boundOperationsInterface) {
return doInvoke(invocation, method, operationsTarget, true);
}
return doInvoke(invocation, method, delegate, false);
}
@Nullable
private Object doInvoke(MethodInvocation invocation, Method method, Object target, boolean considerKeyArgument) {
Method backingMethod = lookupRequiredMethod(method, target.getClass(), considerKeyArgument);
Object[] args;
Object[] invocationArguments = invocation.getArguments();
if (isStreamRead(method)) {
// stream.read requires translation to StreamOffset using the bound key.
args = new Object[backingMethod.getParameterCount()];
System.arraycopy(invocationArguments, 0, args, 0, args.length - 1);
args[args.length - 1] = new StreamOffset[] {
StreamOffset.create(delegate.getKey(), (ReadOffset) invocationArguments[invocationArguments.length - 1]) };
} else if (backingMethod.getParameterCount() > 0 && backingMethod.getParameterTypes()[0].equals(Object.class)) {
args = new Object[backingMethod.getParameterCount()];
args[0] = delegate.getKey();
System.arraycopy(invocationArguments, 0, args, 1, args.length - 1);
} else {
args = invocationArguments;
}
try {
return backingMethod.invoke(target, args);
} catch (ReflectiveOperationException e) {
ReflectionUtils.handleReflectionException(e);
throw new UnsupportedOperationException("Should not happen", e);
}
}
}
/**
* Default {@link BoundKeyOperations} implementation. Meant for internal usage.
*
* @author Costin Leau
* @author Christoph Strobl
*/
static class DefaultBoundKeyOperations implements BoundKeyOperations<Object> {
private final DataType type;
private Object key;
private final RedisOperations<Object, ?> ops;
DefaultBoundKeyOperations(DataType type, Object key, RedisOperations<Object, ?> operations) {
this.type = type;
this.key = key;
this.ops = operations;
}
@Override
public Object getKey() {
return key;
}
@Override
public Boolean expire(long timeout, TimeUnit unit) {
return ops.expire(key, timeout, unit);
}
@Override
public Boolean expireAt(Date date) {
return ops.expireAt(key, date);
}
@Override
public Long getExpire() {
return ops.getExpire(key);
}
@Override
public Boolean persist() {
return ops.persist(key);
}
@Override
public void rename(Object newKey) {
if (ops.hasKey(key)) {
ops.rename(key, newKey);
}
key = newKey;
}
public DataType getType() {
return type;
}
public RedisOperations<Object, ?> getOps() {
return ops;
}
}
}
相关信息
相关文章
spring-data-redis AbstractOperations 源码
spring-data-redis BoundGeoOperations 源码
spring-data-redis BoundHashOperations 源码
spring-data-redis BoundKeyOperations 源码
spring-data-redis BoundListOperations 源码
spring-data-redis BoundSetOperations 源码
spring-data-redis BoundStreamOperations 源码
spring-data-redis BoundValueOperations 源码
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦