• Java反射详解


    Java反射详解

    分类:java基础日期:2012-07-20作者:ticmy

     
     

    反射,是Java中非常重要的一个功能,如果没有反射,可以说很多框架都难以实现。

    什么是反射?说白了就是可以通过Java代码获取装载到方法区的类信息的手段。

    当装载一个类时,会在方法区产生一个数据结构,该结构中包含着装载的类的相关信息。字节码可以看成是数据流,那么方法区的这种数据结构可以说是字节码数据流的结构化表现。装载的最终产物就是java.lang.Class类的一个对象,它是Java程序与方法区内部数据结构之间的接口。
    那么,我们能通过这个接口访问内部数据结构的哪些信息呢?接下来介绍关于反射常用的一些内容。反射的大部分方法大都与安全管理器有关,本文忽略此部分。

    假设有以下代码(一个简单的文章管理代码)

    package com.ticmy.reflect;
     
    /**
     * 文章管理接口
     * @author Administrator
     */
    public interface ArticleInterface {
        public void del(long id) throws Exception;
        public void add(String content) throws Exception;
        public void modify(long id, String content) throws Exception;
    }

    一个简单实现

    package com.ticmy.reflect;
     
    import java.util.Map;
    import java.util.Random;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicLong;
     
    public class ArticleManage implements ArticleInterface {
        private final Map<Long, String> articles;
        private boolean needCheck;
        private final AtomicLong idIncr = new AtomicLong(1);
        public ArticleManage(boolean needCheck) {
            System.out.println("带参数的构造方法");
            this.needCheck = needCheck;
            articles = new ConcurrentHashMap<Long, String>();
        }
     
        public ArticleManage() {
            System.out.println("默认构造方法");
            this.needCheck = true;
            articles = new ConcurrentHashMap<Long, String>();
        }
     
        public void del(long id) throws Exception {
            System.out.println("删除文章");
            articles.remove(id);
        }
     
        public void add(String content) throws Exception {
            if((!isNeedCheck()) || checkContent()) {
                System.out.println("添加文章");
                articles.put(idIncr.getAndIncrement(), content);
            }
        }
     
        public void modify(long id, String content) throws Exception {
            if((!isNeedCheck()) || checkContent()) {
                System.out.println("修改文章内容");
                articles.put(id, content);
            }
        }
     
        public boolean checkContent() throws Exception {
            System.out.println("检查文章内容");
            Random random = new Random();
            int value = random.nextInt(100);
            if(value < 20) {
                //20%概率失败
                throw new Exception("文章内容不合法");
            }
            return true;
        }
     
        public boolean isNeedCheck() {
            return needCheck;
        }
    }

    1、生成一个ArticleInterface实现类的一个对象。

    package com.ticmy.reflect;
    public class Test {
        public static void main(String[] args) throws Exception {
            ArticleInterface inst = (ArticleInterface)Class.forName("com.ticmy.reflect.ArticleManage")
                    .newInstance();
            inst.add("this is a short article");
        }
    }

    我们可能有以上代码来生成一个ArticleManage的实例。如果以前没接触过反射,肯定会纳闷,这里完全可以用ArticleInterface inst = new ArticleManage();这句来取代反射,用反射来创建对象的意义何在。如果在写这段代码的时候,已经知道要new ArticleManage了,可能确实意义不大。但假如在写代码的时候还不知道com.ticmy.reflect.ArticleManage的存在呢或者压根就是留给二次开发的人去做的呢?可能需要一个配置,里面配置着ArticleInterface实现类的全限定名,而此时在不知道其实现类的情况下,可以用上述方式来创建类的对象了。就好像写spring的那些人,他们根本不知道你要注入的对象类型,无论如何他们也无法在代码中使用new的,反射就能很好的解决这个问题。
    上面的代码运行后会发现,newInstance调用后使用的是ArticleManage的默认构造方法,那么该如何使用带boolean参数的构造方法呢?这就是下面要说的内容。

    2、获取类的构造方法。
    Class类中有几个方法可以用来获取构造方法,getConstructors()、getConstructor(Class… parameterTypes)以及getDeclaredConstructors()、getDeclaredConstructor(Class… parameterTypes)。其中含有Declared的表示获取所有的构造方法,不管其是private,protected,public还是包内可见的。Class中还有其它带来Declared的方法,其情况跟这里类似。而不带Declared的,表示获取的是声明为public的内容。再看参数情况,不带参数的表示获取所有能获取的构造方法(getConstructors获取所有public的构造方法,getDeclaredConstructors获取所有声明的构造方法),而带参数的表示构造方法的参数类型。我们在例子中声明的都是public构造方法,想要调的是带boolean参数的构造方法,因此我们可以用如下方式实现:

    package com.ticmy.reflect;
    import java.lang.reflect.Constructor;
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("com.ticmy.reflect.ArticleManage");
            Constructor<?> constructor = clazz.getConstructor(boolean.class);
            ArticleInterface inst = (ArticleInterface)constructor.newInstance(false);
            inst.add("this is a short article");
        }
    }

    Constructor里也有newInstance方法,如果构造方法有参数,就按构造方法声明的顺序将参数传进去;如果没有,则可以不传参数。

    3、访问类中的字段
    可以通过Class中的getFields()、getField(String name)以及getDeclaredFields()、getDeclaredField(String name)来访问类的字段。方法名中带Declared与不带Declared的含义同构造方法。假如现在想改变private类型的needCheck的值,可以用getDeclaredField(String name)方法先获取字段对象,再修改其值:

    package com.ticmy.reflect;
    import java.lang.reflect.Field;
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("com.ticmy.reflect.ArticleManage");
            ArticleInterface inst = (ArticleInterface)clazz.newInstance();
            Field field = clazz.getDeclaredField("needCheck");
            field.setAccessible(true);
            field.setBoolean(inst, false);
            inst.add("this is a short article");
        }
    }

    获取到Field对象后,通过调用其setBoolean方法修改其值。其第一个参数为想要改变字段值的对象,若是static字段,该参数可为null;如果字段为其它类型,可以调用Field类的其它setXXX方法来修改。注意到代码中有一句field.setAccessible(true),如果没有这句会报以下错误,不允许修改private变量的值,将其设置为true,就允许改变了:
    Exception in thread “main” java.lang.IllegalAccessException: Class com.ticmy.reflect.Test can not access a member of class com.ticmy.reflect.ArticleManage with modifiers “private”

    4、访问类中的方法
    Class类中提供了getMethods()、getMethod(String name, Class… parameterTypes)以及getDeclaredMethods()、getDeclaredMethod(String name, Class… parameterTypes),其情况类似于构造方法,毋庸多言。需要说的是其带参数的方法,第一个参数是方法名,后面是对应方法的参数类型。假如这里要调用modify方法,可以先通过getMethod(String name, Class… parameterTypes)获得对应的Method对象,然后通过invoke调用:

    package com.ticmy.reflect;
    import java.lang.reflect.Method;
    public class Test {
        public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("com.ticmy.reflect.ArticleManage");
            ArticleInterface inst = (ArticleInterface)clazz.newInstance();
            Method method = clazz.getMethod("modify", long.class, String.class);
            method.invoke(inst, 3, "this is a short article");
        }
    }

    invoke的第一个参数表示调用的是哪个对象的modify方法,后面是ArticleInterface接口中modify方法按声明顺序排列的实际参数。

    5、上述内容是关于反射中常用的一些方法和用法,更多功能参见java.lang.Class,java.lang.reflect.Method、java.lang.reflect.Field、java.lang.reflect.Constructor等类的JAVA API说明。

    6、反射和动态代理的结合。

    动态代理用于创建动态代理类,这些类不需要以java class文件的形式存在。动态代理相关的类为:java.lang.reflect.Proxy。可以通过其newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)方法创建一个代理类,其中,loader为定义代理类的类加载器,interfaces为生成的代理类要实现的接口列表,h为指派方法调用的调用处理程序。在ArticleManage示例类中,add和modify方法中都有if((!needCheck) || checkContent())的判断,或许还有更多方法需要这种判断,在每个方法里都写这样的判断实在是太麻烦,动态代理+方法反射调用就可以帮我们解脱痛苦:
    现在将ArticleManage实现中的if判断删除:

    package com.ticmy.reflect;
     
    import java.util.Map;
    import java.util.Random;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicLong;
     
    public class ArticleManage implements ArticleInterface {
        private final Map<Long, String> articles;
        private boolean needCheck;
        private final AtomicLong idIncr = new AtomicLong(1);
        public ArticleManage(boolean needCheck) {
            System.out.println("带参数的构造方法");
            this.needCheck = needCheck;
            articles = new ConcurrentHashMap<Long, String>();
        }
     
        public ArticleManage() {
            System.out.println("默认构造方法");
            this.needCheck = true;
            articles = new ConcurrentHashMap<Long, String>();
        }
     
        public void del(long id) throws Exception {
            System.out.println("删除文章");
            articles.remove(id);
        }
     
        public void add(String content) throws Exception {
            System.out.println("添加文章");
            articles.put(idIncr.getAndIncrement(), content);
        }
     
        public void modify(long id, String content) throws Exception {
            System.out.println("修改文章内容");
            articles.put(id, content);
        }
     
        public boolean checkContent() throws Exception {
            System.out.println("检查文章内容");
            Random random = new Random();
            int value = random.nextInt(100);
            if(value < 20) {
                //20%概率失败
                throw new Exception("文章内容不合法");
            }
            return true;
        }
     
        public boolean isNeedCheck() {
            return needCheck;
        }
    }

    通过动态代理为其加上判断功能

    package com.ticmy.reflect;
     
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
     
    public class Test {
        public static void main(String[] args) throws Exception {
            ArticleManage delegationObj = new ArticleManage();
     
            ArticleInterface inst = (ArticleInterface)Proxy.newProxyInstance(
                    ArticleInterface.class.getClassLoader(),
                    new Class[]{ArticleInterface.class},
                    new ArticleManageInvocationHandler(delegationObj));
            inst.add("this is a short article");
     
        }
     
        static class ArticleManageInvocationHandler implements InvocationHandler {
            private ArticleManage delegationObj;
            public ArticleManageInvocationHandler(ArticleManage delegationObj) {
                this.delegationObj = delegationObj;
            }
            public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
                String methodName = method.getName();
                Object ret = null;
                long start = System.currentTimeMillis();
     
                if(methodName.equals("add")) {
                    ret = method.invoke(delegationObj, args);
                } else {
                    if((!delegationObj.isNeedCheck()) || delegationObj.checkContent()) {
                        ret = method.invoke(delegationObj, args);
                    }
                }
                long end = System.currentTimeMillis();
                System.out.println("[方法调用][" + methodName + "]耗时:" + (end - start) + "ms");
                return ret;
            }
        }
    }

    看这部分

    ArticleInterface inst = (ArticleInterface)Proxy.newProxyInstance(
            ArticleInterface.class.getClassLoader(),
            new Class[]{ArticleInterface.class},
            new ArticleManageInvocationHandler(delegationObj));

    让生成的代理类实现ArticleInterface接口,这样就可以将newProxyInstance的对象强制转换成ArticleInterface类型了。在InvocationHandler中,可以看到其invoke方法参数中有个Method,这个Method对象是代理接口中的那些方法,例如这里将其强制转换成ArticleInterface后,假设调用了add方法,InvocationHandler中invoke方法的method就相当于ArticleInterface.class.getMethod(“add”, String.class)。在这个例子中,当调用inst的方法时,就会自动去调用ArticleManageInvocationHandler中的invoke方法。invoke方法的第三个参数,就是调用inst中方法时传的参数。invoke方法的第一个参数即为生成的代理类对象本身,很多时候这个参数用处不大。
    既然调用inst的方法时会调用这个invoke方法,那就可以在里面做一些事情了。像上面在调用实际方法之前给其加了判断,为整个方法调用计时等等,有没有种面向切面的感觉?

    这是很常用的一种编程模式,如在现实中很多操作需要验证用户是否已经登录,那么就可以通过这种方式来做。如果用了spring,可以使用spring的AOP来做,道理是类似的。

  • 相关阅读:
    P2155 [SDOI2008]沙拉公主的困惑
    P4345 [SHOI2015]超能粒子炮·改
    乘法逆元
    P1608 路径统计
    P1342 请柬
    一些网址
    20/08/02测试
    ivqBlog 开源博客 (angularjs + express + mongodb)
    angularjs, nodejs, express, gulp, karma, jasmine 前端方案整合
    参照nopCommerce框架开发(NextCMS)
  • 原文地址:https://www.cnblogs.com/01picker/p/4306370.html
Copyright © 2020-2023  润新知