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方法

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

  • 1.8中的default方法

    在java8以后,接口中可以添加使用default或者static修饰的方法。default修饰方法只能在接口中使...

  • java8接口中的默认方法

    java8接口中的默认方法 概述默认方法 java8中可以在接口中定义一个default修饰的方法,例如: 这样任...

  • java8特性简单介绍

    接口默认方法支持 通过使用default关键字,java8可以在接口中增加非抽象的方法实现,这个功能也被称之为扩展...

  • Java8中的default方法

    在java8以后,接口中可以添加使用default或者static修饰的方法,在这里我们只讨论default方法,...

  • java8 新特性总结

    java8新特性 1.接口方法默认实现:java8中接口方法可以有默认实现,需要加入default关键字。 2.L...

  • Java新特性之Java8的那些事儿-接口默认方法

    什么是接口默认方法 ​ 从Java8开始,程序允许在接口中包含带有具体实现的方法,使用default修饰,这类...

  • Koltin之类的定义

    类的定义 接口 interface 类似 Java8 接口中的方法可以有默认实现,但是不需要 default 关键...

  • Java8函数式编程(一): Lambda 表达式

    Java编程的精髓是面向接口编程,Java8最大变化正在于接口。 默认方法(default method) 函数式...

  • JAVA8_新特性_接口

    JAVA8的接口里的静态方法,可以有方法体,方法不是抽象的。 通过接口名直接调用 JAVA8的接口里的非静态方法,...

网友评论

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

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