Google Guava 反射机制工具类

一 TypeToken

       Guava TypeToken类是用来帮我们解决java运行时泛型类型被擦除的问题的。

       这里用一个具体的实例来解释下什么是类型檫除,特别是使用泛型的时候容易出现类型檫除。

        ArrayList<String> stringList = Lists.newArrayList();
        ArrayList<Integer> intList = Lists.newArrayList();
        System.out.println("intList type is " + intList.getClass());
        System.out.println("stringList type is " + stringList.getClass());

       上述代码虽然我们定义的是ArrayList、ArrayList类型的对象。但是两者的打印都是java.util.ArrayList。没办法去获取到String、Integer了。他们的类型被檫除了,在想通过类型来判断是哪个对象就做不到了。

       鉴于类似这样的情况发生Guava给我们提供了TypeToken类。怎么用,

        // 认为stringList和intList的类型是一样的。这就是所谓的泛型类型擦除, 泛型String和Integer被檫除了。
        System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));

        // 定义了一个空的匿名类
        TypeToken<ArrayList<String>> typeToken = new TypeToken<ArrayList<String>>() {
        };
        // TypeToken解析出了泛型参数的具体类型
        TypeToken<?> genericTypeToken = typeToken.resolveType(ArrayList.class.getTypeParameters()[0]);
        System.out.println(genericTypeToken.getType());

       通过TypeToken我们就可以获取到ArrayList里面的class java.lang.String。

       TypeToken常用方法如下:

方法描述
getType()获得包装的java.lang.reflect.Type.
getRawType()返回大家熟知的运行时类
getSubtype(Class<?>)返回那些有特定原始类的子类型。举个例子,如果这有一个Iterable并且参数是List.class,那么返回将是List。
getSupertype(Class<?>)产生这个类型的超类,这个超类是指定的原始类型。举个例子,如果这是一个Set并且参数是Iterable.class,结果将会是Iterable。
isAssignableFrom(type)如果这个类型是 assignable from 指定的类型,并且考虑泛型参数,返回true。List<? extends Number>是assignable from List,但List没有.
getTypes()返回一个Set,包含了这个所有接口,子类和类是这个类型的类。返回的Set同样提供了classes()和interfaces()方法允许你只浏览超类和接口类。
isArray()检查某个类型是不是数组,甚至是<? extends A[]>。
getComponentType()返回组件类型数组。

二 Invokable

       Guava的Invokable是对java.lang.reflect.Method和java.lang.reflect.Constructor的流式包装。它简化了常见的反射代码的使用。

2.1 Invokable常用方法

    /**
     * 根据指定的Method返回Invokable对象(MethodInvokable)
     */
    public static Invokable<?, Object> from(Method method);

    /**
     * 根据指定的Constructor返回Invokable对象(ConstructorInvokable)
     */
    public static <T> Invokable<T, T> from(Constructor<T> constructor) {
        return new ConstructorInvokable<T>(constructor);
    }

    /**
     * 判断当前方法是否可以重写的
     * Constructors, private, static or final methods, or methods declared by final classes
     * 都是不能被重写的
     */
    public abstract boolean isOverridable();

    /**
     * 当前方法对应的参数是否是可变的参数
     */
    public abstract boolean isVarArgs();

    /**
     * 执行对应的方法
     */
    // All subclasses are owned by us and we'll make sure to get the R type right.
    @SuppressWarnings("unchecked")
    @CanIgnoreReturnValue
    public final R invoke(@Nullable T receiver, Object... args)
            throws InvocationTargetException, IllegalAccessException {
        return (R) invokeInternal(receiver, checkNotNull(args));
    }

    /**
     * 获取当前方法的返回值
     */
    // All subclasses are owned by us and we'll make sure to get the R type right.
    @SuppressWarnings("unchecked")
    public final TypeToken<? extends R> getReturnType();

    /**
     * 获取当前方法的参数
     */
    public final ImmutableList<Parameter> getParameters();

    /**
     * 获取当前方法的异常类型
     */
    public final ImmutableList<TypeToken<? extends Throwable>> getExceptionTypes();

    /**
     * 显式指定此Invokable的返回类型.可以作为一个过滤作用处理
     *
     * 例如:
     *
     * Method factoryMethod = Person.class.getMethod("create");
     * Invokable<?, Person> factory = Invokable.of(getNameMethod).returning(Person.class);
     *
     */
    public final <R1 extends R> Invokable<T, R1> returning(Class<R1> returnType);

    /**
     * 显式指定此Invokable的返回类型
     */
    public final <R1 extends R> Invokable<T, R1> returning(TypeToken<R1> returnType);

    /**
     * 返回表示声明由此Invokable对象表示的方法的类的Class对象,父类中申明的就会返回父类class
     */
    @SuppressWarnings("unchecked") // The declaring class is T's raw class, or one of its supertypes.
    @Override
    public final Class<? super T> getDeclaringClass();

    /**
     * 方法返回表示声明由此Method对象表示的方法的类的Class对象,Class包装成了TypeToken而已
     */
    // Overridden in TypeToken#method() and TypeToken#constructor()
    @SuppressWarnings("unchecked") // The declaring class is T.
    @Override
    public TypeToken<T> getOwnerType();

    /**
     *  返回一个AnnotatedType对象,把该方法的返回类型包装成了一个AnnotatedType类型
     */
    public abstract AnnotatedType getAnnotatedReturnType();

2.2 Invokable简单使用

public class InvokableTest {

    @Test
    public void invokableTest() {
        // 对象
        InvokableInstance object = new InvokableInstance(10);
        // 获去对象对应的类
        Class clazz = object.getClass();
        Method[] invokableSourceList = clazz.getMethods();
//        Constructor[] invokableSourceList =  clazz.getConstructors();
        if (invokableSourceList.length > 0) {
            for (Method item : invokableSourceList) {
                System.out.println("========================================");
                // 方法名字
                System.out.println("方法名字:" + item.getName());
                // 把Method包装成Invokable
                Invokable methodInvokable = Invokable.from(item);
                // getDeclaringClass() 获取定义该方法的类
                System.out.println("定义该方法的类:" + methodInvokable.getDeclaringClass());
                // getOwnerType() 获取定义该方法的class的包装对象Type
                System.out.println("定义该方法的类:" + methodInvokable.getOwnerType().getType());
                // isOverridable() 该方法是否可以重写
                System.out.println("是否可以重写:" + methodInvokable.isOverridable());
                // isVarArgs() 该方法是否可变参数
                System.out.println("是否可变参数:" + methodInvokable.isVarArgs());
                // getReturnType() 该方法返回值类型
                System.out.println("返回值类型:" + methodInvokable.getReturnType().getType());
                // getParameters() 获取参数
                ImmutableList<Parameter> parameterList = methodInvokable.getParameters();
                for (int index = 0; index < parameterList.size(); index++) {
                    System.out.println("方法参数" + index + ": " + parameterList.get(index).getType());
                }
                // getExceptionTypes() 获取异常类
                ImmutableList<TypeToken> exceptionList = methodInvokable.getExceptionTypes();
                for (int index = 0; index < exceptionList.size(); index++) {
                    System.out.println("异常类" + index + ": " + exceptionList.get(index).getType());
                }
                // getAnnotatedReturnType()
                AnnotatedType annotatedType = methodInvokable.getAnnotatedReturnType();
                System.out.println("annotatedType: " + annotatedType.getType());
            }
        }
    }

    /**
     * 一个测试用类
     */
    class InvokableInstance {

        /**
         * 构造函数
         */
        public InvokableInstance(int a) {

        }

        /**
         * 仅仅是用来测试的一个方法
         */
        @CanIgnoreReturnValue
        public void sum(int a, int b) throws NullPointerException {
//            return a + b;
        }

    }

}

三 动态代理(Dynamic Proxies)

       Guava为了方便大家很好的处理动态代理,给大家做了两件事:简化生成代理对象的生成、提供了AbstractInvocationHandler了。

3.1 动态代理对象的生成

       对于单一的接口类型需要被代理的时候。Guava简化了代理对象的生成。我们用一个具体的实例来说明

定义一个很简单的接口AddService

public interface AddService {

    int add(int a, int b);

}

实现AddService

public class AddServiceImpl implements AddService {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

       原来JDK生成代理对象是通过Proxy.newProxyInstance()方法生成,这个方法需要三个参数:第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器、第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口、第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法。我们还是直接用代码来说明问题。

实现InvocationHandler接口(代理方法处理接口)。通过实现类里面的getProxy()
拿到代理对象。

public class JdkInvocationHandlerImpl<T> implements InvocationHandler {

    /**
     * 目标对象
     */
    private T targetObject;

    public JdkInvocationHandlerImpl(T target) {
        this.targetObject = target;
    }

    /**
     * 绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
     */
    public AddService getProxy() {
        //该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
        //第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
        //第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
        //第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
        //根据传入的目标返回一个代理对象
        return (AddService) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 打印参数信息列表
        for (Object arg : args) {
            System.out.println(arg);
        }
        Object ret;
        try {
            /*原对象方法调用前处理日志信息*/
            System.out.println("方法开始执行-->>");
            //调用目标方法
            ret = method.invoke(targetObject, args);
            /*原对象方法调用后处理日志信息*/
            System.out.println("方法执行成功-->>");
        } catch (Exception e) {
            // 执行方法过程中出现异常
            e.printStackTrace();
            System.out.println("方法执行失败-->>");
            throw e;
        }
        return ret;
    }
}

使用

    @Test
    public void jdkNewProxyTest() {
        AddServiceImpl service = new AddServiceImpl();
        JdkInvocationHandlerImpl<AddService> addServiceHandler = new JdkInvocationHandlerImpl<>(service);
        AddService proxy = addServiceHandler.getProxy();
        Assert.assertEquals(3, proxy.add(1, 2));
    }

       Guava生成代理对象。直接调用Reflection.newProxy()函数,这个函数也是需要两个参数:第一个参数接口,要实现和目标对象的某一样的接口(那个接口里面的方法需要代理)、第二个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法。具体使用实例如下:

实现InvocationHandler接口(代理方法处理接口)。通过实现类里面的Reflection.newProxy()拿到代理对象。

public class GuavaInvocationHandlerImpl<T> implements InvocationHandler {

    /**
     * 目标对象对应的接口(因为一个对象可以实现多个接口,我们不知道是那个接口,所以传递进来)
     */
    private Class<T> targetInterface;
    /**
     * 目标对象
     */
    private T targetObject;


    public GuavaInvocationHandlerImpl(Class<T> targetInterface, T targetObject) {
        /*参数判断*/
        // targetInterfaced一定要是一个接口
        checkArgument(targetInterface.isInterface(), "%s 不是一个接口类", targetInterface);
        // targetObject一定是targetInterface接口的实现类。
        boolean valid = false;
        Class<?>[] targetInterfaceList = targetObject.getClass().getInterfaces();
        if (targetInterfaceList != null && targetInterfaceList.length > 0) {
            for (Class<?> item : targetInterfaceList) {
                if (targetInterface.getName().equals(item.getName())) {
                    valid = true;
                    break;
                }
            }
        }
        checkArgument(valid, "%s 必须实现 %s", targetObject.getClass().getName(), targetInterface.getName());
        this.targetInterface = targetInterface;
        this.targetObject = targetObject;
    }

    /**
     * 获取都代理对象
     */
    public T getProxy() {
        // guava里面Reflection帮助类提供的 Reflection.newProxy() 实现
        // 第一个参数接口,要实现和目标对象的某一样的接口(那个接口里面的方法需要代理)
        // 第二个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
        return Reflection.newProxy(targetInterface, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 打印参数信息列表
        for (Object arg : args) {
            System.out.println(arg);
        }
        Object ret;
        try {
            /*原对象方法调用前处理日志信息*/
            System.out.println("方法开始执行-->>");
            //调用目标方法
            ret = method.invoke(targetObject, args);
            /*原对象方法调用后处理日志信息*/
            System.out.println("方法执行成功-->>");
        } catch (Exception e) {
            // 执行方法过程中出现异常
            e.printStackTrace();
            System.out.println("方法执行失败-->>");
            throw e;
        }
        return ret;
    }
}

使用

    @Test
    public void guavaNewProxyTest() {
        AddServiceImpl service = new AddServiceImpl();
        GuavaInvocationHandlerImpl<AddService> addServiceHandler = new GuavaInvocationHandlerImpl<>(AddService.class, service);
        AddService proxy = addServiceHandler.getProxy();
        Assert.assertEquals(3, proxy.add(1, 2));
    }

3.2 AbstractInvocationHandler

       有时候你可能想动态代理能够更直观的支持equals(),hashCode()和toString()。AbstractInvocationHandler能帮我们做到三件事:

  • 一个代理实例equal另外一个代理实例,只要他们有同样的接口类型和equal的invocation handlers。
  • 一个代理实例的toString()会被代理到invocation handler的toString(),这样更容易自定义toString()。
  • AbstractInvocationHandler确保传递给handleInvocation(Object, Method, Object[]))的参数数组永远不会空,从而减少了空指针异常的机会。

如果对上面讲的几点不是很明白的话,强烈建议大家看下AbstractInvocationHandler的源码部分。

public class GuavaAbstractInvocationHandlerImpl<T> extends AbstractInvocationHandler {

    /**
     * 目标对象对应的接口(因为一个对象可以实现多个接口,我们不知道是那个接口,所以传递进来)
     */
    private Class<T> targetInterface;
    /**
     * 目标对象
     */
    private T targetObject;


    public GuavaAbstractInvocationHandlerImpl(Class<T> targetInterface, T targetObject) {
        /*参数判断*/
        // targetInterfaced一定要是一个接口
        checkArgument(targetInterface.isInterface(), "%s 不是一个接口类", targetInterface);
        // targetObject一定是targetInterface接口的实现类。
        boolean valid = false;
        Class<?>[] targetInterfaceList = targetObject.getClass().getInterfaces();
        if (targetInterfaceList != null && targetInterfaceList.length > 0) {
            for (Class<?> item : targetInterfaceList) {
                if (targetInterface.getName().equals(item.getName())) {
                    valid = true;
                    break;
                }
            }
        }
        checkArgument(valid, "%s 必须实现 %s", targetObject.getClass().getName(), targetInterface.getName());
        this.targetInterface = targetInterface;
        this.targetObject = targetObject;
    }

    /**
     * 获取都代理对象
     */
    public T getProxy() {
        // guava里面Reflection帮助类提供的 Reflection.newProxy() 实现
        return Reflection.newProxy(targetInterface, this);
    }

    @Override
    protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {
        // 打印参数信息列表
        for (Object arg : args) {
            System.out.println(arg);
        }
        Object ret;
        try {
            /*原对象方法调用前处理日志信息*/
            System.out.println("方法开始执行-->>");
            //调用目标方法
            ret = method.invoke(targetObject, args);
            /*原对象方法调用后处理日志信息*/
            System.out.println("方法执行成功-->>");
        } catch (Exception e) {
            // 执行方法过程中出现异常
            e.printStackTrace();
            System.out.println("方法执行失败-->>");
            throw e;
        }
        return ret;
    }
}

四 ClassPath

       严格来讲,Java没有平台无关的方式来浏览类和类资源。不过一定的包或者工程下,还是能够实现的,比方说,去检查某个特定的工程的惯例或者某种一直遵从的约束。

       ClassPath是一种实用工具,它提供尽最大努力的类路径扫描。

       值得注意的是,ClassPath是一个尽力而为的工具。它只扫描jar文件中或者某个文件目录下的class文件。也不能扫描非URLClassLoader的自定义class loader管理的class,所以不要将它用于关键任务生产任务。

4.1 ClassPath常用方法

    /**
     * 创建一个ClassPath对象
     */
    public static ClassPath from(ClassLoader classloader) throws IOException;

    /**
     * 返回从当前类路径可加载的所有资源,包括所有可加载类的类文件,但不包括“META-INF / MANIFEST.MF”文件
     */
    public ImmutableSet<ResourceInfo> getResources();

    /**
     * 返回可加载的所有类
     */
    public ImmutableSet<ClassInfo> getAllClasses();

    /**
     * 返回所有的顶级类(不包括内部类)
     */
    public ImmutableSet<ClassInfo> getTopLevelClasses();
    
    /**
     * 返回指定package下的所有顶级类
     */
    public ImmutableSet<ClassInfo> getTopLevelClasses(String packageName);

    /**
     * 返回所有指定package已经子package下所有的顶级类
     */
    public ImmutableSet<ClassInfo> getTopLevelClassesRecursive(String packageName);

4.2 ClassPath简单使用

    @Test
    public void classPathTest() {
        try {
            ClassPath classpath = ClassPath.from(ClassPathBase.class.getClassLoader());
            // getTopLevelClassesRecursive
            ImmutableSet<ClassPath.ClassInfo> topClassList =  classpath.getTopLevelClassesRecursive("com.tuacy.guava.study");
            if (topClassList != null && !topClassList.isEmpty()) {
                for (ClassPath.ClassInfo classInfoItem : topClassList) {
                    System.out.println(classInfoItem.toString());
                }
            }
            // getResources
            ImmutableSet<ClassPath.ResourceInfo> resourceList =  classpath.getResources();
            if (resourceList != null && !resourceList.isEmpty()) {
                for (ClassPath.ResourceInfo resourceItem : resourceList) {
                    System.out.println(resourceItem.url().toString());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class ClassPathBase {

    }

       关于Guava反射机制工具类我们就暂时先提这些,上文中涉及到的代码可以在https://github.com/tuacy/google-guava-study里面找到(在单元测试代码里面)。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值