Java反射及 IoC原理、內省機制
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的屬性、方法和事件。
Java反射及 IoC原理、內省機制