反射的概念
- java的放射機制:在程式執行時,程式有能力獲取一個類的所有方法和屬性;並且對於任意一個物件,可以呼叫它的任意方法或者獲取其屬性
- 通俗解析:java檔案需要編譯成.class檔案才能被jvm載入使用,物件的.class資料在jvm裡就是Class<T>;我們如果能拿到這個Class<T>物件,
就能獲取該Class<T>對應的物件型別,及在該型別宣告的方法和屬性值;還可以根據Class<T>建立相應的型別物件,通過Field,Method反過來操作物件
- java相關類介紹
類名 |
描述 |
Class<T> |
代表類的實體,在執行的Java應用程式中表示類或者介面 |
Field |
類的成員變數(成員變數也稱為類的屬性) |
Method |
類的方法 |
Constructor<T> |
類的構造方法 |
獲取Class的三種方法
// 根據Example 獲取Class =》Example.class
public Class<Example> getExample(){
Class<Example> clazz = Example.class;
return clazz;
}
public Class<Example> getExampleByInstance(){
Example example = new Example();
// getClass是Object類裡面的方法;《?》 是萬用字元
Class<?> clazz = example.getClass();
return (Class<Example>)clazz;
}
- 3通過Class.forName獲取全路徑指定類名的class
/** forName0 本地方法,C++實現,jvm呼叫
* 1 className 是個類名 2 initialize 是否延遲載入 3 loader 載入器
*/
private static native Class<?> forName0(String className, boolean initialize,
ClassLoader loader, Class<?> caller) throws ClassNotFoundException;
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
// 兩個forName方法最終都會呼叫forName0方法去載入class
public static Class<?> forName(String name,
boolean initialize, ClassLoader loader) throws ClassNotFoundException {
....
return forName0(name, initialize, loader, caller);
}
// 示例:通過java.lang.Integer
public Class<Integer> getInteger()throws ClassNotFoundException{
Class<?> clazz = Class.forName("java.lang.Integer");
return (Class<Integer>)clazz;
}
JAVA反射API
//獲取所有的構造方法 / private public
public Constructor<?>[] getDeclaredConstructors()
//獲取特定的構造方法 / private public
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
//獲取類的父類
public native Class<? super T> getSuperclass()
//獲取類實現的介面
private Class<?>[] getInterfaces(boolean cloneArray)
//獲取在類內定義的內部類或介面
public Class<?>[] getDeclaredClasses()
//獲取所有的方法
public Method[] getDeclaredMethods() throws SecurityException
//根據方法名和引數獲得特定的方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
//獲取型別的定義的所有屬性
public Field[] getFields() throws SecurityException
// 根據屬性命名獲得特定的Field
public Field getField(String name)
//獲得方法的放回型別
public Class<?> getReturnType()
//獲得方法的傳入引數型別
public Class<?>[] getParameterTypes()
//obj是例項物件,args是方法,反過來由Method控制物件的方法呼叫
public Object invoke(Object obj, Object... args)
//屬性與obj相等則返回true
public boolean equals(Object obj)
//獲得obj中對應的屬性值
public Object get(Object obj)
//設定obj中對應屬性值
public void set(Object obj, Object value)
//根據傳遞的引數建立類的物件:initargs 構造方法引數
public T newInstance(Object... initargs)
//方式一 clazz.newInstance()
Class<Example> clazz = Example.class;
Example example = clazz.newInstance();
//方式二 先獲取再由Constructor:clazz.getConstructors()/getConstructor(...)
//再由Constructor.newInstance 方法構造物件
-----------------------------------------
public class Example {
private int value;
public Example(){ } // 如果只宣告有參建構函式,clazz.newInstance()會報錯
public Example(Integer value){ this.value = value; }
static public void main(String[] args) throws Exception{
Class<Example> clazz = Example.class;
//根據指定建構函式引數獲取Constructor
Constructor<Example> constructor = clazz.getConstructor(Integer.class);
Example example = constructor.newInstance(100);
System.out.println(example.value);
}
}
public class Example {
private int value , count;
static public void main(String[] args) throws Exception{
Class<Example> clazz = Example.class;
//獲取所有的屬性,getField只能獲取public的屬性
Field[] fs = clazz.getDeclaredFields();
//根據名稱獲取指定 Field
Field value = clazz.getDeclaredField("value");
Example example = clazz.newInstance();
//使用反射機制可以打破封裝性,導致了java物件的屬性不安全
value.setAccessible(true); //setAccessible(true)讓private的引數可賦值操作
//由Field反過去設定example的值
value.set(example,100);
System.out.println(example.value);
}
}
- 3由class獲取Method,並反射呼叫例項方法
public class Example {
public static void main(String[] args) throws Exception {
Class<Example> clazz = Example.class;
Example example = clazz.newInstance();
Method[] methods = clazz.getDeclaredMethods();
//getDeclaredMethod和getMethod是:getMethod只能返回public的方法
Method method = clazz.getDeclaredMethod("hello", String.class);
method.setAccessible(true);
method.invoke(example, "cscw");
}
private void hello(String name) { System.out.println(name + " Hello!"); }
}
-----
cscw Hello!
反射機制應用的場景
- 1 動態擴充:假設有同一組類是實現相同的介面,並且類的載入方式不限制。當我們需要那種具體類實現的功能時,只需載入.class檔案,並獲取對應的Class<T>物件。可以由Class或者Constructor例項化物件instance;根據介面定義,可以獲取Class<T>裡的某一方法Method,並配合instance呼叫功能方法
- 2 Spring的IOC就是基於反射機制實現
- 3 JDK的動態代理
反射和JDK動態代理
- 在Java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler介面。通過這個類和介面可以生成JDK動態代理類或動態代理物件
public interface InvocationHandler {
//所有方法都會呼叫此代理方法
Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}
public class Proxy implements Serializable{
...
//根據interfaces和InvocationHandler生成代理物件
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
...
}
- JDK的動態代理由Proxy和InvocationHandler實現;而被代理物件必須實現一個介面。代理物件由Proxy生成,可轉為介面interface的實現類物件OBJ。當呼叫OBJ的方法時,則會觸發InvocationHandler.invoke,引數依次為代理物件,Method物件,和方法Method所需的引數。在invoke方法可以加入擴充的邏輯,如日誌記錄操作;並可以在invoke裡利用反射的技術呼叫 被代理物件方法
- 示例
public class ExampleFactory<T> implements InvocationHandler{
private T target;
public T bind(T obj){
target = obj;
return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//增強邏輯
System.out.println("log start");
//反射呼叫被代理物件方法
Object result = method.invoke(target,objects);
System.out.println("log end");
return result;
}
}
-----------
public interface Face {
void hello(String name);
}
---------
//被代理物件必須實現一個介面,並由介面方法對方提供功能
public class Example implements Face {
public void hello(String name) {
System.out.println(name + " Hello!");
}
public static void main(String[] args) {
//ExampleFactory<Face> 相當於一箇中介人
ExampleFactory<Face> factory = new ExampleFactory<>();
//example 是代理物件
Face example = exampleProxy.bind(new Example());
example.hello("思婷");
}
}
-----
log start
思婷 Hello!
log end
歡迎指正文中錯誤
關注公眾號,一起交流
參考文章