Google Guava 反射机制工具类

作者: tuacy | 来源:发表于2019-07-08 17:57 被阅读204次

    一 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<String>、ArrayList<Integer>类型的对象。但是两者的打印都是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<String>里面的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里面找到(在单元测试代码里面)。

    相关文章

      网友评论

        本文标题:Google Guava 反射机制工具类

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