架构(十六)本地方法缓存

一、引言

作者需要在底层公共包里面加一个方法反射的工具类,看起来很简单的事,问题也不少,这里讲讲过程。在结合同事的思维误区聊聊本地加锁块的问题。

二、方案选型

其实一开始有两种方案,一种是传入Function和入参,一种是传入实例、方法名、入参

两种都可以,但是一开始想着尽量让使用方少操作,而且反射有性能损耗,所以还是先研究了传入Function和入参

1、传入Function

java 复制代码
public static <T, R> R invoke(Function<T, R> f, T request) {
        return f.apply(request);
    }

这样就很简单,用的时候还是从spring里面拿出来

java 复制代码
    @Resource
    private ExecuteProcess executeProcess;

    executeProcess::execute.request

这其实是可以的,但是对于作者的需求来说不行,因为作者需要通过传入类的实例拿到他注解上的一些属性,但是通过Function是拿不到的

2、反射

反射需要传入类的实例,参数和方法名,也就多了一个参数

这里要说一下request就可以了,随着目前的规范化代码,重载方法在业务系统里面基本是会被骂的,大家都是放一个对象,入参有增加了,对象加一个参数就可以,而不是把重载方法都搞一遍

java 复制代码
public static <T, R> R invoke(T instance, T request, String methodName) {
         try {
             Class<?> clazz = instance.getClass();
             Method method = clazz.getMethod(methodName, request.getClass());
             // 执行方法
             R result = (R)method.invoke(instance, request);
             return result;
         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
             // 异常处理
             throw new SoaInvokeException(e);
         }
     }

三、优化

选型之后就要对这个实现进行优化了,那么反射可以在哪里进行优化呢,method.invoke肯定是少不了的,但是执行方法可以被缓存,而不是每次都要反射遍历那就先看看chatGpt有没有什么建议

1、chatGpt建议

他先是让用方法名字作为key进行缓存,这有问题,但是不同的类之间没有同名方法吗

然后再问了一下,又说用pair.of把类和方法作为有序对,但是这也有问题,这相当于每次调用这个方法都在创建pair对象

2、自优化

上面chatGpt的两种方法不予采纳,那就自己进行优化吧

这里其实有一个疑问点,那就是第一次加锁的时候,到底是锁methodCache还是clazz呢,锁methodCache有点大,锁clazz又怕别人也有在锁的,这时候就要根据实际情况了

我们使用的时候基本不会加这个clazz,框架倒是有可能,但是分析了这一类clazz,至少我们传入的应该是不会被锁的,所以最终选了clazz

java 复制代码
/**
      * map的大小
      */
     private static final int INIT_SIZE = 16;
 
     /**
      * 缓存类实例、方法名对应的方法
      */
     protected static ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Method>> methodCache =
         new ConcurrentHashMap<>(INIT_SIZE);
 
     /**
      * 获取执行方法
      * 
      * @param instance 类
      * @param request 请求参数
      * @param methodName 接口名称
      * @param <T>
      * @return
      * @throws NoSuchMethodException
      */
     private static <T> Method getMethod(T instance, T request, String methodName) throws NoSuchMethodException {
         Class<?> clazz = instance.getClass();
         Method method;
         ConcurrentHashMap<String, Method> meMap = methodCache.get(clazz);
         String meKey = methodName + request.getClass().getName();
         if (meMap == null) {
             synchronized (clazz) {
                 meMap = methodCache.get(clazz);
                 if (meMap == null) {
                     method = clazz.getMethod(methodName, request.getClass());
                     meMap = new ConcurrentHashMap(INIT_SIZE);
                     meMap.put(meKey, method);
                     // 将方法对象存入缓存
                     methodCache.put(clazz, meMap);
                     return method;
                 }
             }
         }
         method = meMap.get(meKey);
         if (method == null) {
             synchronized (meMap) {
                 method = meMap.get(meKey);
                 if (method == null) {
                     method = clazz.getMethod(methodName, request.getClass());
                     meMap.put(meKey, method);
                 }
             }
         }
         return method;
     }
 
     public static <T, R> R invoke(T instance, T request, String methodName) {
         try {
             Method method = getMethod(instance, request, methodName);
             // 执行方法
             R result = (R)method.invoke(instance, request);
             return result;
         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
             // 异常处理
             throw new SoaInvokeException(e);
         }
     }
 }

四、拓展

在这个过程中,同事和作者产生过一个技术的分歧,他觉得锁本地静态变量methodCache会导致整个工具类被锁,其他线程会卡在invoke(T instance, T request, String methodName)外面进不来

作者认为他锁的是局部代码块,也就是invoke进得去,但是其他线程会卡在if (meMap == null) {,有争论就做个实验好了,虽然说也没有准备锁methodCache,但是技术理念不能错,不然一定会在别的地方踩坑

作者在加锁的方法做了延迟,然后开了两个线程,第一个抢到的线程会等第二个线程进来,如果第二个线程的日志被挡在invoke外面就是同事说的对,不然就是作者对的

事实证明作者是对的,这里作者也不把截图贴上去了,有兴趣的可以自己试试,多动手,少动嘴

java 复制代码
/**
 * 缓存类实例、方法名对应的方法
 */
 protected static ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Method>> methodCache =
new ConcurrentHashMap<>();

 public static void main(String[] args) {
ServiceServerExtensionUtil util = new ServiceServerExtensionUtil();
 ServiceServerExtensionUtilTwo utilTwo = new ServiceServerExtensionUtilTwo();
 // 反射方法执行成功, 方法被缓存
 Thread a = new Thread(() -> {
System.out.println("thread:" + Thread.currentThread().getName());
 ServiceServerExtension.invoke(util, "test", "invokeUtil");
 });
 Thread b = new Thread(() -> {
try {
Thread.sleep(1000);
 } catch (InterruptedException e) {
e.printStackTrace();
 }
System.out.println("thread:" + Thread.currentThread().getName());
 ServiceServerExtension.invoke(utilTwo, "test", "invokeUtil");
 });
 a.start();
 b.start();
 }

/**
 * 获取执行方法
 * 
 * @param clazz 类
 * @param request 请求参数
 * @param methodName 接口名称
 * @param <T>
 * @return
 * @throws NoSuchMethodException
 */
 private static <T> Method getMethod(Class<?> clazz, T request, String methodName) throws NoSuchMethodException {

System.out.println("thread:" + Thread.currentThread().getName() + "getMethod start");
 Method method;
 ConcurrentHashMap<String, Method> meMap = methodCache.get(clazz);
 if (meMap == null) {
synchronized (methodCache) {
try {
Thread.sleep(10000);
 } catch (InterruptedException e) {
e.printStackTrace();
 }
meMap = methodCache.get(clazz);
 if (meMap == null) {
method = clazz.getMethod(methodName, request.getClass());
 meMap = new ConcurrentHashMap(16);
 meMap.put(methodName, method);
 // 将方法对象存入缓存
 methodCache.put(clazz, meMap);
 System.out.println("thread:" + Thread.currentThread().getName() + "getMethod end");
 return method;
 }
}
}
method = meMap.get(methodName);
 if (method == null) {
synchronized (meMap) {
method = meMap.get(methodName);
 if (method == null) {
method = clazz.getMethod(methodName, request.getClass());
 meMap.put(methodName, method);
 }
}
}
System.out.println("thread:" + Thread.currentThread().getName() + "getMethod end");
 return method;
 }

 public static <T, R> R invoke(T instance, T request, String methodName) {
try {
Class<?> clazz = instance.getClass();
 Method method = getMethod(clazz, request, methodName);
 // 执行方法
 R result = (R)method.invoke(instance, request);
 return result;
 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// 异常处理
 throw new SoaInvokeException(e);
 }
}

五、总结

看起来很简单的东西做起来其实有很多细节要考虑,很多技术细节大家也都不是那么确定的,千万不要在自己有疑惑的时候听别人的技术理念,有疑惑就要自己去尝试验证。

相关推荐
我命由我123453 小时前
Android Studio - Android Studio 自定义预览尺寸
android·java·ide·java-ee·android studio·android-studio·android runtime
mCell4 小时前
你以为短链接只是 Hash + 301/302?
后端·算法·架构
咖啡八杯4 小时前
GoF设计模式——迭代器模式
java·后端·设计模式·迭代器模式
AIGS0014 小时前
企业AI落地的关键认知:向量空间JBoltAI的本体语义平台
java·人工智能·人工智能ai大模型应用
记忆停留w5 小时前
从单体到微服务:Redis 协同 MySQL、Milvus、MinIO 搭建企业级RAG/AI Agent脚手架架构
大数据·人工智能·redis·微服务·ai·架构·milvus
财财源源5 小时前
.NET 程序保护实战系列01-流水线架构与保护引擎总览
架构·.net
KaMeidebaby5 小时前
卡梅德生物技术快报|抗体亲和力成熟工业化调控新机制:差异性浆细胞增殖工艺优化思路
java·开发语言·人工智能·算法·机器学习·架构·spark
深圳恒讯6 小时前
菲律宾云服务器与传统VPS的架构差异
运维·服务器·架构
醉城夜风~6 小时前
Java详解经典算法题:接雨水(三种实现方案+原理剖析)
java·开发语言·算法
龙亘川7 小时前
开源本地 AI 智能体网关 OpenClaw 深度实践:架构解析、全场景部署与自动化落地指南
人工智能·架构·开源·openclaw