Google Guava 反射机制工具类

Posted tuacy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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里面找到(在单元测试代码里面)。

以上是关于Google Guava 反射机制工具类的主要内容,如果未能解决你的问题,请参考以下文章

Guava的一些总结

别再造轮子了,Google 开源的 Guava 工具库真心强大!

Google Guava Java编程工具类中文教程

别再重复造轮子了,推荐使用 Google Guava 开源工具类库,真心强大!

Atitit apache 和guava的反射工具

别再重复造轮子了,推荐使用 Google Guava 开源工具类库,真心强大!