阅读 4417

基础篇:深入解析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 ...
    //根据interfacesInvocationHandler生成代理对象
    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);
    }
    /** Object o 是代理对象; o的方法调用 -> ExampleFactory.invoke 
    *  invoke(...) -> 在invoke方法里面 反射调用代理对象方法+增强逻辑
    */
    @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
复制代码

欢迎指正文中错误

关注公众号,一起交流

参考文章