zoukankan      html  css  js  c++  java
  • 基础篇:深入解析JAVA反射机制

    反射的概念

    • java的放射机制:在程序运行时,程序有能力获取一个类的所有方法和属性;并且对于任意一个对象,可以调用它的任意方法或者获取其属性
    • 通俗解析:java文件需要编译成.class文件才能被jvm加载使用,对象的.class数据在jvm里就是Class<T>;我们如果能拿到这个Class<T>对象,
      就能获取该Class<T>对应的对象类型,及在该类型声明的方法和属性值;还可以根据Class<T>创建相应的类型对象,通过Field,Method反过来操作对象
    • java相关类介绍
    类名 描述
    Class<T> 代表类的实体,在运行的Java应用程序中表示类或者接口
    Field 类的成员变量(成员变量也称为类的属性)
    Method 类的方法
    Constructor<T> 类的构造方法

    获取Class的三种方法

    • 1通过已知的类型获取class
    // 根据Example 获取Class =》Example.class
    public Class<Example> getExample(){
        Class<Example> clazz = Example.class;
        return clazz;
    }
    
    • 2通过实例对象获取class
    public Class<Example> getExampleByInstance(){
        Example example = new Example();
        // getClass是Object类里面的方法;《?》 是通配符
        Class<?> clazz = example.getClass();
        return (Class<Example>)clazz;
    }
    
    • 3通过Class.forName获取全路径指定类名的class
    /** forName0 本地方法,C++实现,jvm调用
     *	1 className 是个类名  2 initialize 是否延迟加载  3 loader 加载器
     */
    private static native Class<?> forName0(String className, boolean initialize,
    				ClassLoader loader, Class<?> caller) throws ClassNotFoundException;
    
    public static Class<?> forName(String className) throws ClassNotFoundException {
            Class<?> caller = Reflection.getCallerClass();
            return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
        }
    // 两个forName方法最终都会调用forName0方法去加载class   
    public static Class<?> forName(String name,
    		boolean initialize, ClassLoader loader) throws ClassNotFoundException {
            ....
            return forName0(name, initialize, loader, caller);
        }
    
    // 示例:通过java.lang.Integer 
    public Class<Integer> getInteger()throws ClassNotFoundException{
        Class<?> clazz = Class.forName("java.lang.Integer");
        return (Class<Integer>)clazz;
    }
    

    JAVA反射API

    • Class常用操作方法
    //获取所有的构造方法 / private public
    public Constructor<?>[] getDeclaredConstructors()
    //获取特定的构造方法 / private public
    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)    
    //获取类的父类
    public native Class<? super T> getSuperclass()    
    //获取类实现的接口
    private Class<?>[] getInterfaces(boolean cloneArray)  
    //获取在类内定义的内部类或接口
    public Class<?>[] getDeclaredClasses()
    //获取所有的方法
    public Method[] getDeclaredMethods() throws SecurityException
    //根据方法名和参数获得特定的方法
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)  
    //获取类型的定义的所有属性
    public Field[] getFields() throws SecurityException
    // 根据属性命名获得特定的Field
    public Field getField(String name) 
    
    • Method常用的操作方法
    //获得方法的放回类型
    public Class<?> getReturnType()   
    //获得方法的传入参数类型
    public Class<?>[] getParameterTypes()   
    //obj是实例对象,args是方法,反过来由Method控制对象的方法调用
    public Object invoke(Object obj, Object... args)
    
    • Field常用的操作方法
    //属性与obj相等则返回true
    public boolean equals(Object obj)
    //获得obj中对应的属性值
    public Object get(Object obj)
    //设置obj中对应属性值
    public void set(Object obj, Object value) 
    
    • Constructor
    //根据传递的参数创建类的对象:initargs 构造方法参数
    public T newInstance(Object... initargs) 
    
    • 1根据class创建对象
    //方式一 clazz.newInstance()
    Class<Example> clazz = Example.class;
    Example example = clazz.newInstance();
    //方式二 先获取再由Constructor:clazz.getConstructors()/getConstructor(...) 
    //再由Constructor.newInstance 方法构造对象
    -----------------------------------------
    public class Example {
        private int value;
        public Example(){ } // 如果只声明有参构造函数,clazz.newInstance()会报错
        public Example(Integer value){  this.value  = value;  }
        static public void main(String[] args) throws Exception{
            Class<Example> clazz = Example.class;
            //根据指定构造函数参数获取Constructor
            Constructor<Example> constructor = clazz.getConstructor(Integer.class);
            Example example = constructor.newInstance(100);
            System.out.println(example.value);
        }
    }    
    
    • 2由class获取Field,并操作实例的属性
    public class Example {
        private int value , count;
        static public void main(String[] args) throws Exception{
            Class<Example> clazz = Example.class;
            //获取所有的属性,getField只能获取public的属性
    		Field[] fs = clazz.getDeclaredFields();
            //根据名称获取指定 Field
            Field value = clazz.getDeclaredField("value");
            Example example = clazz.newInstance();
            //使用反射机制可以打破封装性,导致了java对象的属性不安全  
            value.setAccessible(true); //setAccessible(true)让private的参数可赋值操作
            //由Field反过去设置example的值
            value.set(example,100);
            System.out.println(example.value);
        }
    }
    
    • 3由class获取Method,并反射调用实例方法
    public class Example {
        public static void main(String[] args) throws Exception {
            Class<Example> clazz = Example.class;
            Example example = clazz.newInstance();
            Method[] methods = clazz.getDeclaredMethods();
            //getDeclaredMethod和getMethod是:getMethod只能返回public的方法
            Method method = clazz.getDeclaredMethod("hello", String.class);
            method.setAccessible(true);
            method.invoke(example, "cscw");
        }
        private void hello(String name) { System.out.println(name + " Hello!"); }
    }
    -----
    cscw Hello!
    

    反射机制应用的场景

    • 1 动态拓展:假设有同一组类是实现相同的接口,并且类的加载方式不限制。当我们需要那种具体类实现的功能时,只需加载.class文件,并获取对应的Class<T>对象。可以由Class或者Constructor实例化对象instance;根据接口定义,可以获取Class<T>里的某一方法Method,并配合instance调用功能方法
    • 2 Spring的IOC就是基于反射机制实现
    • 3 JDK的动态代理

    反射和JDK动态代理

    • 在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口。通过这个类和接口可以生成JDK动态代理类或动态代理对象
    public interface InvocationHandler {
    	//所有方法都会调用此代理方法
        Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
    }	 
    public class Proxy implements Serializable{
    	...
        //根据interfaces和InvocationHandler生成代理对象
        public static Object newProxyInstance(ClassLoader loader,
        		Class<?>[] interfaces, InvocationHandler h) 
        ...    
    }
    
    • JDK的动态代理由Proxy和InvocationHandler实现;而被代理对象必须实现一个接口。代理对象由Proxy生成,可转为接口interface的实现类对象OBJ。当调用OBJ的方法时,则会触发InvocationHandler.invoke,参数依次为代理对象Method对象,和方法Method所需的参数。在invoke方法可以加入拓展的逻辑,如日志记录操作;并可以在invoke里利用反射的技术调用 被代理对象方法
    • 示例
    public class ExampleFactory<T> implements InvocationHandler{
        private T target;
        public T bind(T obj){
            target = obj;
            return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
            			obj.getClass().getInterfaces(),this);
        }
        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            //增强逻辑
            System.out.println("log start");
            //反射调用被代理对象方法
            Object result = method.invoke(target,objects);
            System.out.println("log end");
            return result;
        }
    }
    -----------
    public interface Face {
        void hello(String name);
    }
    ---------
    //被代理对象必须实现一个接口,并由接口方法对方提供功能
    public class Example implements Face {
    	public void hello(String name) {
            System.out.println(name + " Hello!");
        }
        public static void main(String[] args)  {
        	//ExampleFactory<Face> 相当于一个中介人
            ExampleFactory<Face> factory = new ExampleFactory<>();
            //example 是代理对象
            Face example = exampleProxy.bind(new Example());
            example.hello("思婷");
        }
    }
    -----
    log start
    思婷 Hello!
    log end
    

    欢迎指正文中错误

    关注公众号,一起交流

    参考文章

  • 相关阅读:
    0593. Valid Square (M)
    0832. Flipping an Image (E)
    1026. Maximum Difference Between Node and Ancestor (M)
    0563. Binary Tree Tilt (E)
    0445. Add Two Numbers II (M)
    1283. Find the Smallest Divisor Given a Threshold (M)
    C Primer Plus note9
    C Primer Plus note8
    C Primer Plus note7
    C Primer Plus note6
  • 原文地址:https://www.cnblogs.com/cscw/p/13747357.html
Copyright © 2011-2022 走看看