public static Type[] getReturnTypes(Method method) {
        Class<?> returnType = method.getReturnType();
        Type genericReturnType = method.getGenericReturnType();
        if (Future.class.isAssignableFrom(returnType)) {
            if (genericReturnType instanceof ParameterizedType) {
                Type actualArgType = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
                if (actualArgType instanceof ParameterizedType) {
                    returnType = (Class<?>) ((ParameterizedType) actualArgType).getRawType();
                    genericReturnType = actualArgType;
                } else if (actualArgType instanceof TypeVariable) {
                    returnType = (Class<?>) ((TypeVariable<?>) actualArgType).getBounds()[0];
                    genericReturnType = actualArgType;
                } else {
                    returnType = (Class<?>) actualArgType;
                    genericReturnType = returnType;
                }
            } else {
                returnType = null;
                genericReturnType = null;
            }
        }
        return new Type[] {returnType, genericReturnType};
    }
@Test
    void testGetReturnTypes() throws Exception {
        Class<TypeClass> clazz = TypeClass.class;

        Type[] types = ReflectUtils.getReturnTypes(clazz.getMethod("getFuture"));
        Assertions.assertEquals("java.lang.String", types[0].getTypeName());
        Assertions.assertEquals("java.lang.String", types[1].getTypeName());

        Type[] types1 = ReflectUtils.getReturnTypes(clazz.getMethod("getString"));
        Assertions.assertEquals("java.lang.String", types1[0].getTypeName());
        Assertions.assertEquals("java.lang.String", types1[1].getTypeName());

        Type[] types2 = ReflectUtils.getReturnTypes(clazz.getMethod("getT"));
        Assertions.assertEquals("java.lang.String", types2[0].getTypeName());
        Assertions.assertEquals("T", types2[1].getTypeName());

        Type[] types3 = ReflectUtils.getReturnTypes(clazz.getMethod("getS"));
        Assertions.assertEquals("java.lang.Object", types3[0].getTypeName());
        Assertions.assertEquals("S", types3[1].getTypeName());

        Type[] types4 = ReflectUtils.getReturnTypes(clazz.getMethod("getListFuture"));
        Assertions.assertEquals("java.util.List", types4[0].getTypeName());
        Assertions.assertEquals("java.util.List<java.lang.String>", types4[1].getTypeName());

        Type[] types5 = ReflectUtils.getReturnTypes(clazz.getMethod("getGenericWithUpperFuture"));
        // T extends String, the first arg should be the upper bound of param
        Assertions.assertEquals("java.lang.String", types5[0].getTypeName());
        Assertions.assertEquals("T", types5[1].getTypeName());

        Type[] types6 = ReflectUtils.getReturnTypes(clazz.getMethod("getGenericFuture"));
        // default upper bound is Object
        Assertions.assertEquals("java.lang.Object", types6[0].getTypeName());
        Assertions.assertEquals("S", types6[1].getTypeName());
    }