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

8 篇文章 1 订阅

一、引言

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

二、方案选型

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

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

1、传入Function

public static <T, R> R invoke(Function<T, R> f, T request) {
        return f.apply(request);
    }

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


    @Resource
    private ExecuteProcess executeProcess;

    executeProcess::execute.request

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

2、反射

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

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

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对象

cbac50e4e970426fa9b11c3b71c30e99.png

3ca2604904f04bb1a0061cae86a31da6.png

2、自优化 

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

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

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

/**
      * 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);
         }
     }
 }

3、review 

        在大家进行讨论分析的时候,提出了新的看法,主要是加一些校验和使用ConcurrentHashMap提供的api,这样代码更加简洁严谨

 /**
     * map的大小
     */
    private static final int INIT_SIZE = 2;

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

    /**
     * 拼接请求方法
     */
    private static final String REQUEST_METHOD = "_";

    /**
     * 不是静态方法,也不是合成方法,也不是桥接方法 才可以反射调用
     * 
     * @param method
     * @return
     */
    private static boolean isIncludedMethod(Method method) {
        if (Modifier.isStatic(method.getModifiers()) || method.isSynthetic() || method.isBridge()) {
            return false;
        }
        return true;
    }

    /**
     * 检查入参
     * 
     * @param instance
     * @param <I>
     * @throws IllegalArgumentException
     */
    private static <I> void check(I instance) throws IllegalArgumentException {
        if (instance == null) {
            throw new IllegalArgumentException("obj is null");
        }
    }

    /**
     * 反射获取方法
     * 
     * @param clazz
     * @param request
     * @param methodName
     * @param <T>
     * @return
     * @throws NoSuchMethodException
     */
    private static <T> Method collectMethod(Class<?> clazz, T request, String methodName) throws NoSuchMethodException {
        Method method = clazz.getMethod(methodName, request.getClass());
        if (isIncludedMethod(method)) {
            return method;
        }
        throw new NoSuchMethodException("No such method: " + methodName);
    }

    /**
     * 缓存获取执行方法
     * 
     * @param instance 类
     * @param request 请求参数
     * @param methodName 接口名称
     * @param <T>
     * @return
     */
    private static <I, T> Method getMethod(I instance, T request, String methodName) {
        // 校验
        check(instance);
        Class<?> clazz = instance.getClass();
        String meKey = request.getClass().getName() + REQUEST_METHOD + methodName;
        return methodCache.computeIfAbsent(clazz, clz -> new ConcurrentHashMap<>(INIT_SIZE)).computeIfAbsent(meKey,
            me -> {
                try {
                    return collectMethod(clazz, request, methodName);
                } catch (NoSuchMethodException e) {
                    throw new SoaInvokeException(e);
                }
            });
    }

    /**
     * 
     * @param instance 类的实例
     * @param request 请求参数
     * @param methodName 接口名称
     * @param <I>
     * @param <T>
     * @param <R>
     * @return
     */
    public static <I, T, R> R invoke(I instance, T request, String methodName) {
        try {
            Method method = getMethod(instance, request, methodName);
            // 执行方法
            R result = (R)method.invoke(instance, request);
            return result;
        } catch (IllegalAccessException | InvocationTargetException e) {
            // 异常处理
            throw new SoaInvokeException(e);
        }
    }

四、拓展

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

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

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

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


/**
 * 缓存类实例、方法名对应的方法
 */
 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);
 }
}

五、总结

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胖当当技术

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

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

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

打赏作者

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

抵扣说明:

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

余额充值