JAVA反射及IoC原理、JAVA内省
1. 反射
反射是框架设计的灵魂,使用前提:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码文件)。
1.1 反射概述
主要指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
Java反射机制:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
1.2 反射机制的作用
在运行时判断任意一个对象所属的类
在运行时获取类的对象
在运行时访问java对象的属性,方法,构造方法等
1.3 反射机制的优缺点
优点:运行时确定类型,再绑定(创建)对象,体现多态性,可降低类之间的耦合性。
缺点:对性能有一定影响,使用反射可认为一种解释操作,告诉JVM想要进行何种操作,这类操作总是慢于直接执行相同的操作(如直接创建(new)对象)。
1.4 反射的使用
获取Class对象
获取类的构造方法
获取类的成员变量
获取类的成员方法
1.4.1 获取Class对象
package Reflect; /*示例类Reflect.Domo,以下例子均用该类*/ class Demo{ private String name; public Demo(){} public Demo(String name){ this.name = name; } public String getName(){ return name; } public void setName(String name){ this.name = name; } @override public String toString(){ return "["+this.name+"]"; } private String show(String name){ return name; } }
/** *获取Class对象的三种方法 *1. Object-->getClass()方法; *2. 任何数据类型(包括基本数据类型)都有一个“静态”的class属性 *3. 通过Class类的静态方法:forName(String className); *4. 一般采用第三种方式获取 */ class Hello{ public static void main(String[] args){ Class<?> demo = null; demo = class.forName("Reflect.Domo"); System.out.println("demo.getName()"); } } //[运行结果]:Reflect.Demo
/** *通过Class实例化其他类的对象 *经过以上步骤获取了一个Demo类的Class实例 */ class Hello{ public static void main(String[] args){ Class<?> demo = null; demo = class.forName("Reflect.Demo"); Demo d = null; d = (Demo)d.newInstance(); d.setName("leo"); System.out.println(d); } } //[运行结果]:[leo]
注:
在运行期间,一个类,只有一个Class对象产生
若需要反射的类只定义了一个有参数的构造函数之后,会出现错误,即使用不到,也应该编写一个无参的构造函数
1.4.2 获取类的构造方法
class Hello{ public static void main(String[] args){ Class<?> demo = null; demo = class.forName("Reflect.Domo"); //取得全部构造函数 Constructor<?> cons[] = demo.getConstructors(); Demo d1 = null; Demo d2 = null; d1 = cons[0].newInstance(); d2 = cons[1].newInstance("leo"); } } //[运行结果]: //[null] //[leo]
1.4.3 获取类的成员变量
/** *获取类的成员变量并调用 *1. 批量的 *1.1) Field[] getFields():获取所有的“公有字段” *1.2) Field[] getDeclareFields():获取所有的字段,包括:私有、受保护、默认、公有 *2. 获取单个的 *2.1) public Field getField(String fieldName):获取某个公有字段 *2.2) public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的) * * *设置字段的值 *Field-->public void set(Object obj, object value) *参数说明: *1.obj:要设置的字段所在的对象 *2.value:要为字段设置的值 */ class Hello{ public static void main(String[] args){ Class<?> demo = null; demo = class.forName("Reflect.Domo"); //获取字段 Field[] fieldArray = demo.getFields(); for(Field f:fieldArray){ System.out.println(f); } //获取公有字段并调用,假设Demo类的name字段为公有 Field f = demo.getField("name"); System.out.println(f); //获取对象并为对象内字段设置值 Demo d = (Demo)demo.getConstructor().newInstance(); f.set(d,"leo"); } }
1.4.4 获取类的成员方法
/** *获取类的成员变量并调用 *1. 批量的 *1.1) public Method[] getMethods():获取所有公有方法(包含父类的方法也包含Object类) *1.2) public Method[] getDeclareMethods():获取所有的成员方法,包括私有的(不包括继承的) *2. 获取单个的 *2.1) public Method getMethod(String name,Class<?>...parameterTypes): *参数说明: *name:方法名 *Class...:形参的Class类型对象 *2.2) public Method getDeclareMethod(String name,Class<?>...parameterTypes) * * *调用方法: *Method-->puvlic Object invoke(Object obj,Object...args) *参数说明: *obj:要调用方法的对象 *args:调用方式时所传递的实参 * */ class Hello{ public static void main(String[] args){ Class<?> demo = null; demo = class.forName("Reflect.Domo"); //获取所有共有方法 Method[] methodArray = demo.getMethods(); for(Method m:methodArray){ System.out.println(m); } //获取公有的toString()方法 Method m = demo.getMethod("toString",String.class); System.out.println(m); //获取私有的show()方法 m = demo.getDeclareMethod("show",String.class); m.setAccessible(true); //解除私有限定 //获取一个对象 Demo d = demo.getConstructor().newInstance(); Object result = m.invoke(d,"Yooo"); System.out.println("["+result+"]") //[运行结果]:Yooo } }
1.5 IoC原理
Ssoring中的IoC的实现原理就是工厂模式加反射机制。
1.不使用反射机制的工厂模式
/** *工厂模式 */ interface fruit{ public abstract void eat(); } class Apple implements fruit{ public void eat(){ System.out.println("Apple"); } } class Orange implements fruit{ public void eat(){ System.out.println("Orange"); } } // 构造工厂类 // 也就是说以后如果我们在添加其他的实例的时候只需要修改工厂类就行了 class Factory{ public static fruit getInstance(String fruitName){ fruit f=null; if("Apple".equals(fruitName)){ f=new Apple(); } if("Orange".equals(fruitName)){ f=new Orange(); } return f; } } class hello{ public static void main(String[] a){ fruit f=Factory.getInstance("Orange"); f.eat(); } }
当我们在添加一个子类的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改的就会很多。
2.利用反射机制的工厂模式
package Reflect; interface fruit{ public abstract void eat(); } class Apple implements fruit{ public void eat(){ System.out.println("Apple"); } } class Orange implements fruit{ public void eat(){ System.out.println("Orange"); } } class Factory{ public static fruit getInstance(String ClassName){ fruit f=null; try{ f=(fruit)Class.forName(ClassName).newInstance(); }catch (Exception e) { e.printStackTrace(); } return f; } } class hello{ public static void main(String[] a){ fruit f=Factory.getInstance("Reflect.Apple"); if(f!=null){ f.eat(); } } }
现在就算我们添加任意多个子类的时候,工厂类就不需要修改。
使用反射机制的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们通过属性文件的形式配置所需要的子类。
3.使用反射机制并结合属性文件的工厂模式(即IoC)
先创建一个fruit.properties的资源文件
apple=Reflect.Apple orange=Reflect.Orange
然后编写主类代码
package Reflect; import java.io.*; import java.util.*; interface fruit{ public abstract void eat(); } class Apple implements fruit{ public void eat(){ System.out.println("Apple"); } } class Orange implements fruit{ public void eat(){ System.out.println("Orange"); } } //操作属性文件类 class init{ public static Properties getPro() throws FileNotFoundException, IOException{ Properties pro=new Properties(); File f=new File("fruit.properties"); if(f.exists()){ pro.load(new FileInputStream(f)); }else{ pro.setProperty("apple", "Reflect.Apple"); pro.setProperty("orange", "Reflect.Orange"); pro.store(new FileOutputStream(f), "FRUIT CLASS"); } return pro; } } class Factory{ public static fruit getInstance(String ClassName){ fruit f=null; try{ f=(fruit)Class.forName(ClassName).newInstance(); }catch (Exception e) { e.printStackTrace(); } return f; } } class hello{ public static void main(String[] a) throws FileNotFoundException, IOException{ Properties pro=init.getPro(); fruit f=Factory.getInstance(pro.getProperty("apple")); if(f!=null){ f.eat(); } } } //[运行结果]:Apple
2. 内省
2.1 内省概述
可参看本页#3.1反射和内省的区别#。
在Java内省中,用到以上及各类。通过BeanInfo这个类可以获取到类中方法和属性。即通过类Introspector的getBeanInfo方法获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器可以获取某个属性对应的Getter/Setter方法,然后我们可以通过反射机制在调用这些方法,即为内省机制。
2.2 JDK内省类库
java.beans.Introspector:Introspector 类为通过工具学习有关受目标 Java Bean 支持的属性、事件和方法的知识提供了一个标准方法。
java.beans.BeanInfo接口:希望提供有关其 bean 的显式信息的 bean 实现者可以提供某个 BeanInfo 类,该类实现此 BeanInfo 接口并提供有关其 bean 的方法、属性、事件等显式信息。
java.beans.PropertyDescriptor:PropertyDescriptor 描述 Java Bean 通过一对存储器方法导出的一个属性。
2.3 内省类库测试代码示例
//被测类 public class User{ private String name; public String getName(){return name} public void setName(String name){ this.name = name; } } public class IntrospectorTest { private User user ; public void init() { user = new User() ; user.setName("张三") ; } public void getBeanPropertyInfo() throws Exception { //获取User-BeanInfo对象:beanInfo是对一个Bean的描述,可以通过它取得Bean内部的信息 /** *获取User-BeanInfo对象 *1.Introspector类 * 是一个工具类,提供了一系列取得BeanInfo的方法; *2.BeanInfo接口 * 对一个JavaBean的描述,可以通过它取得Bean内部的信息; *3.PropertyDescriptor属性描述器类 * 对一个Bean属性的描述,它提供了一系列对Bean属性进行操作的方法 */ BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class) ; PropertyDescriptor[] pds = userBeanInfo.getPropertyDescriptors() ; for (PropertyDescriptor pd : pds) { Method method = pd.getReadMethod() ; String methodName = method.getName() ; Object result = method.invoke(user) ; System.out.println(methodName + "-->" + result); } } public void getBeanPropertyByName() throws Exception { //获取name属性的属性描述器 PropertyDescriptor pd = new PropertyDescriptor("name", user.getClass()) ; //得到name属性的getter方法 Method readMethod = pd.getReadMethod() ; //执行getter方法,获取返回值,即name属性的值 String result = (String) readMethod.invoke(user) ; System.out.println("user.name" + "-->" + result); //得到name属性的setter方法 Method writeMethod = pd.getWriteMethod() ; //执行setter方法,修改name属性的值 writeMethod.invoke(user, "李四") ; System.out.println("user.name" + "-->" + user.getName()); } }
总结:内省操作非常繁琐,所以Apache开发了一套简单、易用的API来操作Bean属性——BeanUtils工具包。
3. 反射和内省的区别
反射
反射就是Java运行状态吧Java类中的各种成分映射成相应的Java类,可以动态地获取所有的属性以及动态调用任意一个方法(包括成员变量、成员方法、构造器等),强调的是运行状态。
内省
内省(IntroSpector)是Java语言对Bean类属性、事件的一种缺省处理方法。
JavaBean:一种特殊的类,用于传递数据信息,这种类的方法主要用于访问私有的字段,切方法名符合某种命名规则,即Getter和Setter方法。用于两个模块之间传递信息的JavaBean称为“值对象”(Value Object,"VO")。
内省机制通过反射来实现,用来暴露一个Bean的属性、方法和事件。