反射指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

Java 反射相关的类库位于 java.lang.reflect ,提供了一系列发射相关的工具集,以便开发者能够动态操纵 Java 代码。

Java 反射机制能够让开发者在运行时,可以通过字符串中的类名创建一个对象;可以获得这个类所有的方法和属性;可以调用、修改一个对象的方法、属性。

1、代码示例

实例代码:src/java/ 的 org.xiao.java.reflect 包下。

public class MyMessages implements Serializable {
    private String string;

    public MyMessages(String string) {
        this.string = string;
        System.out.println("有参构造方法");

    }

    public MyMessages() {
        System.out.println("实例化了一个 MyMessages 对象");
    }

    public void sayHello(String messages) {
        System.out.println("调用 public sayHello 方法" + messages);
    }

    private void privateMethod() {
        System.out.println("调用 private sayHello 方法");
    }
}

1.1、获得 Class 对象

Class 类是 Java 运行时为所有的对象维护一个被称为运行时类型标识。一个类有:属性、方法、构造方法、所在的包等信息,而 Class 是专门用于访问这些 Java 这些信息的类。获得类对应的 Class 对象有两种方式,Java 反射相关的其他操作都是在此 Class 对象进行的:

// 方式一,前提是 Import 这个类
MyMessages myMessages = new MyMessages();
Class<?> c1 = myMessages.getClass();
Class<?> c2 = MyMessages.class;
// 方式二,通过字符串,需要完整的包名
Class<?> c3 = Class.forName("org.xiao.java.reflect.MyMessages");

1.2、获得类的包名、父类、实现的接口

// 包名
System.out.println(c1.getName());
// 获得其父类 不能是 Object, Object 没有父类,会报 java.lang.NullPointerException
Class<?> superC1 = c1.getSuperclass();
System.out.println(superC1.getName());

// getInterfaces MyMessages 实现了那些接口
Class<?>[] impl = c1.getInterfaces();
for (int i = 0; i < impl.length; i++) {
    System.out.println((i + 1) + ":" + impl[i].getName());
}

// 获得其构造函数
Constructor<?>[] cons = c1.getConstructors();
// 查看每个构造方法需要的参数
for (int i = 0; i < cons.length; i++) {
    Class<?>[] con = cons[i].getParameterTypes();
    System.out.print("构造方法 " + i + " : ");
    for (Class<?> param : con) {
        System.out.print(param.getName() + ",");
    }
    System.out.println(" ");
}
// 调用构造方法实例一个对象
Object consObject = cons[0].newInstance("Hello Java Reflection");
// 直接根据类名创建一个对象
Class<?> clazz = Class.forName("org.xiao.java.reflect.MyMessages");
Object object = clazz.newInstance();

1.3、getMethod 调用类中的方法

// 后面传入参数的类型,invoke 传入值
Method method = clazz.getMethod("sayHello", String.class);
method.invoke(object, "来自 ReflectTest");

1.4、获得一个类的属性和方法

// 获得某个类的全部成员变变量   Field[] fields = clazz.getFields(); 获得父类的所有成员变量和类型
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    System.out.print("权限修饰符" + Modifier.toString(field.getModifiers()) + "  ");
    System.out.print("变量类型" + field.getType().getName() + "  ");
    System.out.print("变量名称" + field.getName());
}

// 获得某个类的所有成员方法
Method[] methods = clazz.getMethods();
for (Method method1 : methods) {
    System.out.print("权限修饰符" + Modifier.toString(method1.getModifiers()) + "  ");
    System.out.print("方法名称" + method1.getName() + "  ");
    System.out.print("返回类型" + method1.getReturnType().getName() + "  ");
    System.out.print("成员参数" + method1.getName() + "  ");
    Class<?>[] params = method1.getParameterTypes();
    for (Class<?> param : params) {
        System.out.print(param.getName() + ",");
    }
}

1.5、访问 private、protected 的方法和成员

// setAccessible 突破类定义中的 private、protected 访问权限定义的检查
// 直接访问 private、protected 方法
Method method1 = clazz.getDeclaredMethod("privateMethod");
method1.setAccessible(true);
method1.invoke(object);

// // 直接访问、修改  private、protected 属性
Field field = clazz.getDeclaredField("string");
field.setAccessible(true);
field.set(object, "Java 反射改了你的");
System.out.println(field.get(object));

2、反射实现简单的动态代理

定义 Subject 接口和和其实现:

public interface Subject {
    void sayHello(String name);
}
public class SubjectImpl implements Subject {
    @Override
    public void sayHello(String name) {
        System.out.println(name + "这是真的 sayHello");
    }
}
public class SubjectImpl1235...

假设 Subject 接口有多个实现类,如何根据运行时候传入的情况动态调用特定实现类的方法。比如在支付收款的时候,可能有微信、支付宝支付等。

  • 一种方式是:直接 if else 判断,是那种支付就用那个类。如果后期有新的支付方式加进来就继续写 if else。
  • 还有可以通过反射,如下代码所示
// 定义一个公共接口,根据情况实例化不同类名对应的类
Subject test = (Subject) Class.forName("org.xiao.java.reflect.SubjectImpl").newInstance();
test.sayHello("你好,这里是大数据学习笔记");

对于收款,不管是那种支付方式,有些处理是通用的,如果每个实现类都写,一旦处理需求更改了,那么所有的实现类都要改。对于这个问题,可以通过实现 InvocationHandler 接口,覆写 invoke 接口,将公共的处理抽取出来:

public class MyInvocationHandler implements InvocationHandler {
    private Object object;

    public Object bind(Object object) {
        this.object = object;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("定义前处理");
        // 调用具体的方法和传入参数
        Object proxyObject = method.invoke(object, args);
        System.out.println("定义后处理");
        return proxyObject;
    }
}

将上面调用的方法改为如下:

MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
// 实现动态代理
Subject subjectFromProxy = (Subject) myInvocationHandler.bind(
    Class.forName("org.xiao.java.reflect.SubjectImpl").newInstance());
subjectFromProxy.sayHello("通过 MyInvocationHandler 的");

3、实现一个工厂模式

public class MyFactory {
    public static Subject getInstance(String name) {
        Subject subject;
        try {
            subject = (Subject) Class.forName(name).newInstance();
        } catch (Exception ignore) {
            subject = null;
        }
        return subject;
    }
}

使用:

Subject subject1 = MyFactory.getInstance("org.xiao.java.reflect.SubjectImpl");
subject1.sayHello("Big Data Notes");

4、反射的优缺点

反射可以使得开发人员动态地操纵 Java 代码,使代码间的精简。

优点:

  • 代码更加灵活和更加容易扩展,
  • 可以在运行时判断类型和动态加载类

缺点:

  • 性能较低,动态加载类需要通知 JVM 去完成一系列的操作,比起直接指定相关的代码要慢;
  • 安全隐患,可以动态改变类的属性和方法,具有一定的安全隐患。

4.1、Java 动态编译和静态编译

**静态编译:**在编译时确定类型,绑定对象

**动态编译:**运行时确定类型,绑定对象

区别就是一个写代码的时候已经确定好了,一个要在运行的时候才能确定

4.2、反射应用的场景

反射是框架设计的灵魂,许多框架的设计和开发都应用了反射机制。

  • Spring 框架的 IOC 和 AOP
  • RPC 框架
  • JDBC 通过 Class.forName 加载驱动程序

TODO:阅读一些框架源码后继续深入理解