Java 反射使用看这一篇就够了

##1、什么是反射
反射(Reflection)能够让运行于JVM中的程序检测和修改运行时的行为。
##2、我们为何需要反射
反射能够让我们:

  • 在运行时检测对象的类型;
  • 动态构造某个类的对象;
  • 检测类的属性和方法;
  • 任意调用对象的方法;
  • 修改构造函数、方法、属性的可见性;
  • 以及其他。

##3、Java 为什么支持反射
Java运行时仍然拥有类型信息,它包含了这个类一切:它有哪些字段、哪些方法,各是何种保护级别等等,还有这个类依赖于哪些类。在Java中,类信息以对象的形式存放,这些对象是一种元对象,它们的类型就是Class。拥有了这些信息,无论是动态创建对象还是调用某些方法都是轻而易举的。在C++中,通过RTTI(运行时类型识别),我们也可以知道类的一些信息,但为什么C++中却没有 Reflection,原因是类型信息不完整。RTTI这个名字本身就告诉我们,C++的类型信息是用来进行类型识别的,因此,它也不需要其它额外的信息。并不是C++无法做到这一点,而是C++不希望给用户增加额外的负担。有所得,必然有所失,因此,C++放弃了元对象。
##4、如何使用反射
以类 ReflectionDemo 来作说明

package com.xiuxiuing;

public class ReflectionDemo {
   public static String tag = "demo";
    private String mName;
    private static ReflectionDemo instance;

    public ReflectionDemo() {

    }

    public ReflectionDemo(String name) {
        this.mName = name;
    }

    public static ReflectionDemo getInstance() {
        if (instance == null) {
            instance = new ReflectionDemo();
        }
        return instance;
    }

    public String getName() {
        return mName;
    }

    public void setName(String mName) {
        this.mName = mName;
    }
}
  • 获取类
Class cls = Class.forName("com.xiuxiuing.ReflectionDemo");  
  • 获取类的实例
Object o = cls.newInstance();
  • 有参构造获取类的实例
Constructor<?> csr = cls.getConstructor(String.class);
Object obj = csr.newInstance("张三");
  • 通过单例获取类的实例
Method getInstance = cls.getMethod("getInstance");
Object ob = getInstance.invoke(null);
  • 修改属性的值
Field fieldName = cls.getDeclaredField("mName");
fieldName.setAccessible(true);
fieldName.set(obj, "李四");
  • 调用类的有参方法
Method setMet = cls.getMethod("setName", String.class);
setMet.invoke(obj, "王二");
  • 调用类的无参方法
Method getMet = cls.getMethod("getName");
String name = (String) getMet.invoke(obj);
  • 修改类的静态变量的值
Field tagField = cls.getField("tag");
tagField.set(obj, "reflec");
  • 获取类的静态变量的值
String tag = (String) tagField.get(null);

##5、Java 动态代理
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
我们将通过实例来讲述Java动态代理怎么使用:

  • 定义一个Subject类型的接口
public interface Subject {
    public void rent();
    public void hello(String str);
}
  • 定义一个RealSubject类来实现这个接口
public class RealSubject implements Subject {
    @Override
    public void rent() {
        System.out.println("I want to rent my house");
    }
    
    @Override
    public void hello(String str) {
        System.out.println("hello: " + str);
    }
}
  • 定义一个动态代理 DynamicProxy 类,实现 InvocationHandler 这个接口
public class DynamicProxy implements InvocationHandler {
    // 这个就是我们要代理的真实对象
    private Object subject;
    
    // 构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject) {
        this.subject = subject;
    }
    
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
        
        System.out.println("Method:" + method);
        
        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        
        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }

}
  • 来看看我们的Client类
public class Client {
    public static void main(String[] args) {
        // 我们要代理的真实对象
        Subject realSubject = new RealSubject();

        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);
        
        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}
  • 控制台的输出
$Proxy0

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
I want to rent my house
after rent house

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after rent house

我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

接着我们来看看这两句

subject.rent();
subject.hello("world");

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:

public Object invoke(Object object, Method method, Object[] args)
            throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
        
        System.out.println("Method:" + method);
        
        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        
        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }

我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的

public abstract void com.xiaoluo.dynamicproxy.Subject.rent()

public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)

正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。
##6、如何提高反射的效率

  1. 尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法。
  2. 需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多。

欢迎扫码关注公众号,更好的交流

发布了115 篇原创文章 · 获赞 67 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/meifannao789456/article/details/100079250