Java 反射的实现
< 返回列表时间: 2020-08-11来源:OSCHINA
反射指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
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:阅读一些框架源码后继续深入理解
扩展与参考资料
反射机制介绍 https://snailclimb.gitee.io/javaguide/#/docs/java/basic/reflection
Java基础之—反射(非常重要) https://blog.csdn.net/sinat_38259539/article/details/71799078
java反射机制深入理解剖析 https://www.w3cschool.cn/java/java-reflex.html
Java反射机制的应用场景 https://segmentfault.com/a/1190000010162647
热门排行