反射机制使用详解
- 反射定义
- 反射API
- 反射对于单例的破坏
- IOC容器反转控制原理
反射的定义
java反射机制是java开发中有一个非常重要的概念,也是java的重要特征之一。反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,通过反射可以动态调用方法和属性,大部分框架也都是运用反射原理的。比如Spring。
首先大家应该先了解两个概念,编译期和运行期,编译期就是编译器帮你把源代码翻译成机器能识别的代码,比如编译器把java代码编译成jvm识别的字节码文件,而运行期指的是将可执行文件交给操作系统去执行,而我们所聊的JAVA反射机制是在运行状态中的一系列操作,反射机制我们可以从两方面来进行定义:
- 对于给定的一个类(Class)对象,可以获得这个类(Class)对象的所有属性和方法;
- 对于给定的一个对象(new XXXClassName<? extends Object>),都能够调用它的任意一个属性和方法。
这种动态获取类的内容以及动态调用对象的方法和获取属性的机制.就叫做JAVA的反射机制。
如下图,根据Car类获取到Class,并通过Class创建Car的对象、获取Car中属性、执行Car内部方法这就是反射机制。
理解Class
认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。
接下来我们用一个示例来解释RTTI的概念:
总结特点如下:
- Class类也是类的一种,与class关键字是不一样的。
- 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
- 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
- Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
- Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。
反射机制可以动态获取类信息以及调用对象方法,那它是通过什么实现的呢?这就要介绍一下Class类了。首先明确Class也是一个类,只是它是一个描述类的类,也可以生成对象。对于每个类而言,在JRE中有且仅有一个不变的Class类型的对象,而这个Class类型的对象只能由系统建立,封装了当前对象所对应的类的信息,有哪些属性、方法、构造器以及实现了哪些接口等。每个类的实例都会记得自己是由哪个Class实例所生成的。 要获取类信息或调用对象方法,肯定首先要获取到该类或对象对应的Class类的实例。一般获取Class对象有3种方式。
- 通过类名获取,类名.class。
- 通过对象获取,对象.getClass()。
- 通过全类名获取,Class.forName(全类名)。
package
通过Class对象创建实例
package
反射机制带来什么好处?
比如现在我们设计一个车辆生产的工厂类,可以生产小汽车、货车等车辆,代码编写如下:
// 小汽车和火车都继承Car类
此时工厂需要生产火车,工厂代码需要进行修改:
/**
按照以上代码可以发现,当增加汽车类别的时候,需要修改工厂类,非常不方便,如果使用反射机制将会使得工厂类不需要进行再次修改:
class
因此从这方面看使用反射将使得程序变得更加灵活简单。
Class API详解
方法名功能说明static Class<?> forName(String className)返回与具有给定字符串名称的类或接口关联的对象ClassT newInstance()创建由此对象表示的类的新实例ClassString getName()返回此对象表示的实体的名称(类、接口、数组类、基元类型或 void)Package getPackage()获取此类的包int getModifiers()返回此类或接口的 Java 语言修饰符,该修饰符以整数编码Method[] getMethods()返回一个数组,其中包含反映此对象表示的类或接口的所有公共方法的对象,包括由类或接口声明的对象以及从超类和超级接口继承的对象Method getMethod(String ,Class<?>...parameterTypers)返回一个对象,该对象反映此对象表示的类或接口的指定公共成员方法Class<?> getInterfaces()确定由此对象表示的类或接口实现的接口Type getGenericSuperclass()<U> Class<? extends U> asSubclass(Class<U> clazz)强制转换此对象以表示由指定类对象表示的类的子类T cast(Object obj)将对象强制转换到此对象表示的类或接口Class<? super T> getSuperclass()返回表示由 此 表示的实体的超类(类、接口、基元类型或 void)ClassLoader getClassLoader()返回该类的类加载器getFields()获取类中public类型的属性getField(String name)获取类中的属性,name参数指定了属性的名称getDeclaredFields()获取类中所有属性(public、protected、default、private),但不包括继承的属性getDeclaredField(String name)获取类中的属性(public、protected、default、private),name参数指定了属性的名称getConstructors()获取类中的public构造方法getConstructor(Class[] params)获取类的特定构造方法,params参数指定构造方法的参数类型getDeclaredConstructors()获取类中所有的构造方法(public、protected、default、private)getDeclaredConstructor(Class[] params)获取类的特定构造方法(public、protected、default、private),params参数指定构造方法的参数类型getDeclaredMethods()获取类中所有的方法(public、protected、default、private)getDeclaredMethods(String name,Class[] params)获取类中指定名称的方法,name参数指定方法的名字,parms参数指定方法的参数类型
接下来我们将通过实例来演示API的具体使用
现在我们有一个车类,其中具备有三个属性车辆颜色、速度、允许最高速度,还有drive方法。
class
类的操作我们可以从实例的创建、属性值修改以及方法调用三个方面来完成。
创建汽车实例
1. 通过Class中的newInstance方法创建
package
2. 通过构造方法创建
构造方法的获取有如下4种方式
调用不带参构造方法
package
调用带参构造方法
package
操作属性
属性操作API主要有以下4个
公有属性
getField方法调用公有属性
package
私有属性
getDeclaredField可以获取非公有属性
package
方法调用
方法调用API主要有以下4个
公有方法
getMethod方法获取公有属性
package
私有方法
getDeclaredMethod方法获取非公有属性
package
破坏单例模式
如下使用双重锁实现的单例模式,单例模式的特点就是对象只会存在一个,如果需要破坏单例只需要使得对象实例存在多个即可。
class
使用反射破坏单例模式示例:
package
打印结果:false,根据结果可以判断出single1与single2并非一个对象,因此单例得到破坏
控制反转
控制反转定义
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则, Martin Fowler教授提出的一种软件设计模式,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫依赖查找(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
下面举例说明IoC,现在有一辆车,车子中有引擎,普通实现如下:
// 引擎
此时如果需要改变引擎对象,需要修改类Car中的引擎才可以做到,而使用IoC实现,代码如下:
interface
IoC是一个很大的概念,可以用不同的方式实现。其主要形式有两种:
- 依赖查找:容器中的受控对象通过容器的API来查找自己所依赖的资源和协作对象。这种方式虽然降低了对象间的依赖,但是同时也使用到了容器的API,造成了我们无法在容器外使用和测试对象。 依赖查找是一种更加传统的IoC实现方式。依赖查找也有两种类型:依赖拖拽(DP)和上下文化依赖查找(CDL)
- 依赖注入:组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection)
依赖查找
依赖拖拽(Dependency Pull)-DP
依赖拖拽:依赖项是根据需要从注册表中拉来的 。
public
上下文依赖查找(Contextualized Dependency Lookup)-CDL
上下文依赖查找:在某些方面跟依赖拖拽类似,但是上下文依赖查找中,查找的过程是在容器管理的资源中进行的,而不是从集中注册表中,并且通常是作用在某些设置点上
public
依赖注入(Dependency Injection)-DI
依赖注入就是将服务注入到使用它的地方。对象只提供普通的方法让容器去决定依赖关系,容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造子传递给需要的对象。
public