java8 通过反射执行接口的default方法

作者: littlersmall | 来源:发表于2016-07-21 19:40 被阅读1713次

    最近更新light-dao遇到的。
    需要在一个接口中增加default方法,同时需要对非default方法重写。
    大概类似这样:

    public interface BaseDao {
        default String getName() {
            return "name";
        }
    
        @Select("select * from ad")
        List<Object> getAll(); 
    }
    

    我们重写的Handler类似这样:

    public class DaoInvocationHandler implements InvocationHandler {
        private Map<Method, SqlExecutor> sqlExecutorMap = new ConcurrentHashMap<Method, SqlExecutor>();
        private DataSourceHolder dataSourceHolder;
    
        public DaoInvocationHandler(DataSourceHolder dataSourceHolder) {
            this.dataSourceHolder = dataSourceHolder;
        }
    
        //对应于Dao接口里的函数
        //将每一个函数映射为一个SqlExecutor
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         if (!sqlExecutorMap.containsKey(method)) {
                synchronized (method) {
                    if (!sqlExecutorMap.containsKey(method)) {
                        SqlExecutor sqlExecutor = new SqlExecutor.Builder(method, dataSourceHolder.getLightTemplate()).build();
    
                        sqlExecutorMap.put(method, sqlExecutor);
                    }
                }
            }
    
            return sqlExecutorMap.get(method).execute(args);
        }
    }
    

    我们需要对default方法做特殊处理,搜索了一下stackoverflow

    http://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-refletively

    里面提供了一种解决方式:

    Object result = MethodHandles.lookup()
        .in(method.getDeclaringClass())
        .unreflectSpecial(method,method.getDeclaringClass())
        .bindTo(target)
        .invokeWithArguments();
    

    但是在使用时,报错如下:

    Caused by: java.lang.IllegalAccessException: no private access for invokespecial
    

    又尝试了几次,发现这种方式的改进版:

    Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        constructor.setAccessible(true);
    
        Class<?> declaringClass = method.getDeclaringClass();
        int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;
    
        return constructor.newInstance(declaringClass, allModes)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(args);
    

    通过设置constructor.setAccessible(true);来解决access权限问题。目前来看,问题基本解决。最终的invocationHandler代码如下:

    public class DaoInvocationHandler implements InvocationHandler {
        private Map<Method, SqlExecutor> sqlExecutorMap = new ConcurrentHashMap<Method, SqlExecutor>();
        private DataSourceHolder dataSourceHolder;
    
        public DaoInvocationHandler(DataSourceHolder dataSourceHolder) {
            this.dataSourceHolder = dataSourceHolder;
        }
    
        //对应于Dao接口里的函数
        //将每一个函数映射为一个SqlExecutor
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //default方法不重写
            if (method.isDefault()) {
                Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                        .getDeclaredConstructor(Class.class, int.class);
                constructor.setAccessible(true);
    
                Class<?> declaringClass = method.getDeclaringClass();
                int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;
    
                return constructor.newInstance(declaringClass, allModes)
                        .unreflectSpecial(method, declaringClass)
                        .bindTo(proxy)
                        .invokeWithArguments(args);
            } else if (!sqlExecutorMap.containsKey(method)) {
                synchronized (method) {
                    if (!sqlExecutorMap.containsKey(method)) {
                        SqlExecutor sqlExecutor = new SqlExecutor.Builder(method, dataSourceHolder.getLightTemplate()).build();
    
                        sqlExecutorMap.put(method, sqlExecutor);
                    }
                }
            }
    
            return sqlExecutorMap.get(method).execute(args);
        }
    }
    

    虽然解决了问题,但还是觉得java 8应该在method里添加针对interface default方法的直接调用方式,这样绕一大圈的解决方式显然不够优雅。
    只能期待reflect api下个版本的修改了。

    相关文章

      网友评论

        本文标题:java8 通过反射执行接口的default方法

        本文链接:https://www.haomeiwen.com/subject/ofgbjttx.html