JAVA反射机制

作者: 卡路fly | 来源:发表于2017-04-30 15:03 被阅读128次

    反射机制:

    允许程序在运行时取得任何一个已知名称的class的内部信息,容许程序在运行时加载、探知、使用编译期间未知的class。加载一个运行时才得知名称的Class,获得其完整结构。

    1. 获取到Java中要反射类的字节码
      获取字节码方式:(实例化Class类对象的方法)
    • Class.forName(className)
    • 类名.class
    • this.getClass()
    1. 将字节码中的方法,变量,构造函数等映射成相应的<code> Method</code>、<code> Filed</code> 、<code> Constructor</code> 等类,这些类提供了丰富的方法可以被我们所使用。

    Object类是Java反射机制的源头,即可以通过对象反射求出类的名称。
    Class类使java允许通过一个对象来找到其所在类的信息。

    • 实例化对象
      类中无参构造方法存在
      PS:操作中类(Person)中必须存在无参构造方法,否则无法实例化。否则必须明确指定要调用的构造方法,并传递参数。
      操作步骤:
      • 1.通过Class类中的<code>getConstructors()</code>取得本类中全部构造方法
      • 2.向构造方法中传递一个对象数组,包含了构造方法中所需的各个参数。
      • 3.通过<code>Constructor</code>实例化对象


        类中无参构造方法不存在

    反射借助的类


    反射的功能


    反射机制的深入——取得类的结构

    通过反射得到一个类的完整结构,需要使用<code>java.lang.reflect</code>中的几个类
    1. 通过反射调用类的方法
    public Method getMethod(String name,Class<?> ... parameterTypes) 
                                throws NoSuchMehtodException,SercurityException
    
    public Object invoke(Object obj,Object...args) 
                         throws IllegalAccessException,
                               IllegalArgumentException,
                               InvocationTargetException
    
    无参 有参
    1. 通过反射调用类setter及getter
      setter及getter方法是一个标准的属性访问方法,如果一个类的属性被封装,必须通过setter及getter设置取得,此方法的操作之所以这样规定,主要由于反射机制可以给予支持。
    package org.lxh.demo15.invokedemo ;
    import java.lang.reflect.Method ;
    public class InvokeSetGetDemo{
        public static void main(String args[]){
            Class<?> c1 = null ;
            Object obj = null ;
            try{
                c1 = Class.forName("org.lxh.demo15.Person") ;   // 实例化Class对象
            }catch(Exception e){}
            try{
                obj = c1.newInstance() ;
            }catch(Exception e){}
            setter(obj,"name","Emma",String.class) ;    // 调用setter方法
            setter(obj,"age",21,int.class) ;    // 调用setter方法
            System.out.print("姓名:") ;
            getter(obj,"name") ;
            System.out.print("年龄:") ;
            getter(obj,"age");
        }
        /**
            Object obj:要操作的对象
            String att:要操作的属性
            Object value:要设置的属性内容
            Class<?> type:要设置的属性类型
        */
        public static void setter(Object obj,String att,Object value,Class<?> type){
            try{
                Method met = obj.getClass().getMethod("set"+initStr(att),type) ;    // 得到setter方法
                met.invoke(obj,value) ; // 设置setter的内容
            }catch(Exception e){
                e.printStackTrace() ;
            }
        }
        public static void getter(Object obj,String att){
            try{
                Method met = obj.getClass().getMethod("get"+initStr(att)) ; // 得到setter方法
                System.out.println(met.invoke(obj)) ;   // 调用getter取得内容
            }catch(Exception e){
                e.printStackTrace() ;
            }
        }
        public static String initStr(String old){   // 将单词的首字母大写
            String str = old.substring(0,1).toUpperCase() + old.substring(1) ;
            return str ;
        }
    }
    
    1. 通过反射调用属性
      得到公共属性:
    public Field getField(String name) 
                        throws NoSuchFieldException,SecurityException
    

    得到本类属性

    public Field getDeclaredField(String name) 
                        throws NoSuchFieldException,SecurityException
    

    取得属性内容

    public Object get(Object obj) 
                        throws IllegalArgumentException,IllegalAccessException
    

    设置属性内容

    public Object set(Object obj,Object value) 
                        throws IllegalArgumentException,IllegalAccessException
    

    访问私有属性必须让这个属性可见(设置为true)

    public void setAccessible(boolean flag) 
                        throws SecurityException
    
    1. 通过反射操作数组
      Class类中存在以下一个方法
    public Class<?> getComponentType() 
    

    得到数组下标内容:get(Objcet array,int index);
    修改内容:set(Objcet array,int index,Object value);
    开辟新数组:newInstance(Class<?> componentType,int ... dimensions)

    取得数组信息并修改内容:
    修改的过程就是一个创建的过程,创建一个新的,然后把原有内容拷贝到新数组中。

    package org.lxh.demo15.invokedemo ;
    import java.lang.reflect.Array ;
    public class ChangeArrayDemo{
        public static void main(String args[]) throws Exception{
            int temp[] = {1,2,3} ;// 声明一整型数组
            int newTemp[] = (int []) arrayInc(temp,5) ; // 重新开辟空间5
            print(newTemp) ;
            System.out.println("\n-------------------------") ;
            String t[] = {"lxh","mldn","mldnjava"} ;
            String nt[] = (String [])arrayInc(t,8) ;
            print(nt) ;
        }
        public static Object arrayInc(Object obj,int len){
            Class<?> c = obj.getClass() ;
            Class<?> arr = c.getComponentType() ;   // 得到数组的
            Object newO = Array.newInstance(arr,len) ;  // 开辟新的大小
            int co = Array.getLength(obj) ;
            System.arraycopy(obj,0,newO,0,co) ; // 拷贝内容
            return newO ;
        }
        public static void print(Object obj){   // 数组输出
            Class<?> c = obj.getClass() ;
            if(!c.isArray()){   // 判断是否是数组
                return;
            }
            Class<?> arr = c.getComponentType() ;
            System.out.println(arr.getName()+"数组的长度是:" + Array.getLength(obj)) ;     // 输出数组信息
            for(int i=0;i<Array.getLength(obj);i++){
                System.out.print(Array.get(obj,i) + "、") ;  // 通过Array输出
            }
        }
    }
    

    反射机制的应用

    通过Modifier.toString()还原修饰符

            Class<?> c1 = null ;        // 声明Class对象
            try{
                c1 = Class.forName("org.lxh.demo15.Person") ;   // 实例化对象
            }catch(ClassNotFoundException e){
                e.printStackTrace() ;
            }
            Constructor<?> con[] = c1.getConstructors() ;   // 取得一个类中的全部构造
            for(int i=0;i<con.length;i++){
                Class<?> p[] = con[i].getParameterTypes() ;     // 得到构造方法中的全部参数
                System.out.print("构造方法:" ) ;     // 输出构造,直接打印
                int mo = con[i].getModifiers() ; // 得到所要的访问权限
                System.out.print(Modifier.toString(mo) + " ") ; // 得到修饰符
                System.out.print(con[i].getName()) ;    // 取得构造方法的名字
                System.out.print("(") ;
                for(int j=0;j<p.length;j++){
                    System.out.print(p[j].getName() + " arg" + i) ;
                    if(j<p.length-1){
                        // 判断此是否是最后一个参数
                        System.out.print(",");  // 输出“,”
                    }
                }
                System.out.println("){}") ;
            }
    

    编译方式

    动态编译最大限度发挥了Java的灵活性,体现了多态的应用,有以降低类之间的藕合性


    反射机制优缺点

    • 优点:就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中 它的灵活性就表现的十分明显。

    • 缺点:对性能有影响。
      使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。并且它饶过了源码,会干扰原来的内部逻辑。


    动态代理

    代理设计:一个操作的接口有两个子类,一个是真实主题的实现类,另一个是代理类。代理实现类要完成比真实主题实现类更多的内容,本身还需要处理一些与具体业务有关的程序代码。

    静态代理

    import java.lang.reflect.InvocationHandler ;
    import java.lang.reflect.Proxy ;
    import java.lang.reflect.Method ;
    interface Subject{
        public String say(String name,int age) ;    // 定义抽象方法say
    }
    class RealSubject implements Subject{   // 实现接口
        public String say(String name,int age){
            return "姓名:" + name + ",年龄:" + age ;
        }
    };
    class MyInvocationHandler implements InvocationHandler{
        private Object obj ;
        public Object bind(Object obj){
            this.obj = obj ;    // 真实主题类
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this) ;
        }
        public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
            Object temp = method.invoke(this.obj,args) ;    // 调用方法
            return temp ;
        }
    };
    public class DynaProxyDemo{
        public static void main(String args[]){
            Subject sub = (Subject)new MyInvocationHandler().bind(new RealSubject()) ;
            String info = sub.say("Emma",21) ;
            System.out.println(info) ;
        }
    };
    

    InvocationHandler接口
    刻意将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject.

    public interface InvocationHandler{
              public Object invoke(Object proxy,Method method,Object[] args)  throws Throwable
    }
    
    • Object proxy:被代理的对象
    • Method method:要调用的方法
    • Object[] args:方法调用时所需要的参数

    Proxy类
    专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,提供了如下操作方法

    public static Objcet newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
    
    • ClassLoader loader:类加载器
    • Class<?>[] interfaces:得到全部的接口
    • InvocationHandler h:得到InvocationHandler接口的子类实例

    ArrayList的动态代理类

    final List<String> list = new ArrayList<String>();
            List<String> proxyInstance = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(),
                    list.getClass().getInterfaces(), new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            return method.invoke(list, args);
                        }
                    });
            proxyInstance.add("你好");
            System.out.println(list);
    

    动静态代理的区别&使用场景

    • 静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类
    • 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
    • 动态代理是实现 JDK 里的<code> InvocationHandler</code> 接口的 <code> invoke</code> 方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过 <code> Proxy</code> 里的 <code> newProxyInstance</code> 得到代理对象。
    • 还有一种动态代理 <code> CGLIB</code> ,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
    • <code>AOP </code> 编程就是基于动态代理实现的,比如著名的 <code>Spring </code> 框架、 <code>Hibernate </code> 框架等都是动态代理的使用例子。

    相关文章

      网友评论

      本文标题:JAVA反射机制

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