search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

Java反射機制應用實踐

引言

Java反射機制是一個非常強大的功能,在很多大型項目比如Spring, Mybatis中都可以看見反射的身影。通過反射機制我們可以在運行期間獲取對象的類型信息,利用這一特性我們可以實現工廠模式和代理模式等設計模式,同時也可以解決Java泛型擦除等令人苦惱的問題。本文我們就從實際應用的角度出發,來應用一下Java的反射機制。

反射基礎

p.s: 本文需要讀者對反射機制的API有一定程度的了解,如果之前沒有接觸過的話,建議先看一下官方文檔的Quick Start。

在應用反射機制之前,首先我們先來看一下如何獲取一個對象對應的反射類Class,在Java中我們有三種方法可以獲取一個對象的反射類。

通過getClass方法

在Java中,每一個Object都有一個getClass方法,通過getClass方法我們可以獲取到這個對象對應的反射類:

String s = "http://www.ziwenxie.site"; Class<?> c = s.getClass;

通過forName方法

我們也可以調用Class類的靜態方法forName:

Class<?> c = Class.forName("java.lang.String");

使用.class

或者我們也可以直接使用.class:

Class<?> c = String.class;

獲取類型信息

在文章開頭我們就提到反射的一大好處就是可以允許我們在運行期間獲取對象的類型信息,下面我們通過一個例子來具體看一下。

首先我們在typeinfo.interfacea包下面新建一個介面A:

package typeinfo.interfacea; public interface A { void f; }

接著我們在typeinfo.packageaccess包下面新建一個類C,類C實現了介面A,並且我們還另外創建了幾個用於測試的方法,注意下面幾個方法的許可權都是不同的。

package typeinfo.packageaccess; import typeinfo.interfacea.A; class C implements A { public void f { System.out.println("public C.f"); } public void g { System.out.println("public C.g"); } protected void v { System.out.println("protected C.v"); } void u { System.out.println("package C.u"); } private void w { System.out.println("private C.w"); } } public class HiddenC { public static A makeA { return new C; } }

在callHiddenMethod方法中我們用到了幾個新的API,其中getDeclaredMethod根據方法名用於獲取Class類指代對象自己聲明的某個方法,然後我們通過調用invoke方法就可以觸發對象的相關方法:

package typeinfo; import typeinfo.interfacea.A; import typeinfo.packageaccess.HiddenC; import java.lang.reflect.Method; public class HiddenImplementation { public static void main(String args) throws Exception { A a = HiddenC.makeA; a.f; System.out.println(a.getClass.getName); // Oops! Reflection still allows us to call g: callHiddenMethod(a, "g"); // And even methods that are less accessible! callHiddenMethod(a, "u"); callHiddenMethod(a, "v"); callHiddenMethod(a, "w"); } static void callHiddenMethod(Object a, String methodName) throws Exception { Method g = a.getClass.getDeclaredMethod(methodName); g.setAccessible(true); g.invoke(a); } }

從輸出結果我們可以看出來,不管是public,default,protect還是private方法,通過反射類我們都可以自由調用。當然這裡我們只是為了顯示反射的強大威力,在實際開發中這種技巧還是不提倡。

public C.f typeinfo.packageaccess.C public C.g package C.u protected C.v private C.w

上面我們只是測試了Method對象,感興趣的讀者在熟悉了反射的API之後,不妨測試一下Filed,這裡我們就不重複了。

利用動態代理實現面向切面編程

AOP是Spring提供的一個強大特性之一,AOP的意思是面向切面編程,就是說要分離和業務不相關的代碼,當我們需要新增相關的事務的時候,我們不想對業務本身做修改。面向切面編程和面向對象變成相比到底有什麼好處呢,我們通過一個例子來看一下,對於新手來說,常常會寫出下面這樣的代碼:

public class Example1 { public void execute { // 記錄日誌 Logger logger = Logger.getLog(...); // 進行性能統計 PerformanceUtil.startTimer(...); // 許可權檢查 if (!user.hasPrevilege) { // 拋出異常 } // 執行真正的業務 executeTransaction; PerformanceUtil.endTimer; } }

雖然我們上面真正要執行的業務只有executeTransaction,但是日誌,性能,許可權相關的代碼差不多要將真正的業務代碼掩蓋了。而且以後如果我們還有一個Example2,它同樣需要實現相同的日誌,性能,許可權代碼。這樣當以後我們需要新增相關的邏輯檢查的時候,我們需要所有Example進行重構,這顯然不符合面向對象的一個基本原則-封裝變化。

上面這個場景利用模板方法和裝飾器模式都可以解決,在Spring中是通過動態代理來實現的,下面我們通過一個例子來模擬一下Spring中的AOP實現。

我們要實現的業務是,統計員工工資的時候程序所執行的時間以及檢查用戶的許可權。首先我們先來實現Salary類,它裡面包含一些實現統計員工工資的業務邏輯:

public interface SalaryInterface { public void doSalary; } public class Salary implements SalaryInterface { public void doSalary { ... } }

通過InvocationHandler我們來實現動態代理,以後當我們調用obj的相關方法之前,都會通過invoke方法進行代理,而不會直接調用obj方法。

public class SimpleProxy implements InvocationHandler { private Object obj; private Object advice; // 綁定代理對象 public Object bind(Object obj, Advice advice) { this.obj = obj; this.advice = advice; return Proxy.newProxyInstance(obj.getClass.getClassLoader, obj.getClass.getInterfaces, this) } // 實現代理 public Object invoke(Object proxy, Method method, Object args) throws Throwalbe { Object result = null; try { advice.before; result = method.invoke(obj, args); advice.after; } catch(Exception e) { e.printStackTrace; } return result } }

模擬Spring中的Advice介面:

public interface Advice { public void before; public void after; }

實現TimeAdvice用於統計程序的執行時間:

public class TimeAdvice implements Advice { long startTime; long endTime; @Override public void before { startTime = System.nanoTime; // 獲取開始時間 } @Override public void after { endTime = System.nanoTime; // 獲取結束時間 } }

客戶端調用代碼如下:

public class Client { public static void main(String args) { SimpleProxy = new SimpleProxy; SalaryInterface salaryInterface = (SalaryInterface) simpleProxy.bind(new Salary, new TimeAdvice); salaryInterface.doSalary; } }

如果我們現在需要新增許可權控制,我們來實現ControlAdvie類:

public class ControlAdvice implements Advice { @Override public void before { if (...) { ... } else { ... } } @Override public void after { ... } }

而我們客戶端的代碼只需要改成simpleProxy.bind(new Salary, new ControlAdvie)就行了,而SimpleProxy本身不需要做任何的修改。

與註解相結合

在單元測試框架比如Junit中反射機制也得到了廣泛的應用,即通過註解的方式。下面我們簡單地來了解一下如何通過反射機制來獲取相關方法的註解信息,比如說我們有下面這樣一個業務場景,當用戶在修改自己密碼的時候,為了保證密碼的安全性,我們要求用戶的新密碼要滿足一些條件,比如說至少要包含一個非數字字元,不能與以前的密碼相同之類的條件等。

import java.lang.annotation.* @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UserCase { public int id; public String description default "no description"; }

下面是我們檢測密碼的工具類的實現:

public class PasswordUtils { @UserCase(id=47, description="Password must contain at least one numeric") public boolean validatePassword(String password) { return (password.matches("\w*\d\w*")); } @UserCase(id=48) public String encryptPassword(String password) { return new StringBuilder(password).reverse.toString; } @UserCase(id=49, description="New passwords can't equal previously used ones") public boolean checkForNewPassword(List<String> prevPasswords, String password) { return !prevPasswords.contains(password); } }

利用反射我們可以寫出更加清晰的測試代碼,其中getDeclaredMethods方法可以獲取相關對象自己聲明的相關方法,而getAnnotation則可以獲取Method對象的指定註解。

public class UseCaseTracker { public static void trackUseCases(List<Integer> useCases, Class<?> cl) { for(Method m : cl.getDeclaredMethods) { UseCase uc = m.getAnnotation(UseCase.class); if(uc != null) { System.out.println("Found Use Case: " + uc.id + " " + uc.description); useCases.remove(new Integer(uc.id)); } } for(int i : useCases) { System.out.println("Warning: Missing use case-" + i); } } public static void main(String args) { List<Integer> useCases = new ArrayList<Integer>; Collections.addAll(useCases, 47, 48, 49, 50); trackUseCases(userCases, PasswordUtils.class); } }

解決泛型擦除

現在有下面這樣一個業務場景,我們有一個泛型集合類List<Class<? extends Pet>>,我們需要統計出這個集合類中每種具體的Pet有多少個。由於Java的泛型擦除,注意類似List<? extends Pet>的做法肯定是不行的,因為編譯器做了靜態類型檢查之後,到了運行期間JVM會將集合中的對象都視為Pet,但是並不會知道Pet代表的究竟是Cat還是Dog,所以到了運行期間對象的類型信息其實全部丟失了。p.s: 關於泛型擦除,我在上一篇文章裡面有詳細解釋,感興趣的朋友可以看一看。

為了實現我們上面的例子,我們先來定義幾個類:

public class Pet extends Individual { public Pet(String name) { super(name); } public Pet { super; } } public class Cat extends Pet { public Cat(String name) { super(name); } public Cat { super; } } public class Dog extends Pet { public Dog(String name) { super(name); } public Dog { super; } } public class EgyptianMau extends Cat { public EgyptianMau(String name) { super(name); } public EgyptianMau { super; } } public class Mutt extends Dog { public Mutt(String name) { super(name); } public Mutt { super; } }

上面的Pet類繼承自Individual,Individual類的的實現稍微複雜一點,我們實現了Comparable介面,重新自定義了類的比較規則,如果不是很明白的話,也沒有關係,我們已經將它抽象出來了,所以不理解實現原理也沒有關係。

public class Individual implements Comparable<Individual> { private static long counter = 0; private final long id = counter++; private String name; // name is optional public Individual(String name) { this.name = name; } public Individual {} public String toString { return getClass.getSimpleName + (name == null ? "" : " " + name); } public long id { return id; } public boolean equals(Object o) { return o instanceof Individual && id == ((Individual)o).id; } public int hashCode { int result = 17; if (name != null) { result = 37 * result + name.hashCode; } result = 37 * result + (int) id; return result; } public int compareTo(Individual arg) { // Compare by class name first: String first = getClass.getSimpleName; String argFirst = arg.getClass.getSimpleName; int firstCompare = first.compareTo(argFirst); if (firstCompare != 0) { return firstCompare; } if (name != null && arg.name != null) { int secendCompare = name.compareTo(arg.name); if (secendCompare != 0) { return secendCompare; } } return (arg.id < id ? -1 : (arg.id == id ? 0 : 1)); } }

下面創建了一個抽象類PetCreator,以後我們通過調用arrayList方法便可以直接獲取相關Pet類的集合。這裡使用到了我們上面沒有提及的newInstance方法,它會返回Class類所真正指代的類的實例,這是什麼意思呢?比如說聲明new Dog.getClass.newInstance和直接new Dog是等價的。

public abstract class PetCreator { private Random rand = new Random(47); // The List of the different getTypes of Pet to create: public abstract List<Class<? extends Pet>> getTypes; public Pet randomPet { // Create one random Pet int n = rand.nextInt(getTypes.size); try { return getTypes.get(n).newInstance; } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } public Pet createArray(int size) { Pet result = new Pet[size]; for (int i = 0; i < size; i++) { result[i] = randomPet; } return result; } public ArrayList<Pet> arrayList(int size) { ArrayList<Pet> result = new ArrayList<Pet>; Collections.addAll(result, createArray(size)); return result; } }

接下來我們來實現上面這一個抽象類,解釋一下下面的代碼,在下面的代碼中,我們聲明了兩個集合類,allTypes和types,其中allTypes中包含了我們呢上面所聲明的所有類,但是我們具體的類型實際上只有兩種即Mutt和EgypianMau,所以我們真正需要new出來的寵物只是types中所包含的類型,以後我們通過調用getTypes便可以得到types中所包含的所有類型。

public class LiteralPetCreator extends PetCreator { @SuppressWarnings("unchecked") public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList( Arrays.asList(Pet.class, Dog.class, Cat.class, Mutt.class, EgyptianMau.class)); private static final List<Class<? extends Pet>> types = allTypes.subList( allTypes.indexOf(Mutt.class), allTypes.size); public List<Class<? extends Pet>> getTypes { return types; } }

總體的邏輯已經完成了,最後我們實現用來統計集合中相關Pet類個數的TypeCounter類。解釋一下isAssignalbeFrom方法,它可以判斷一個反射類是某個反射類的子類或者間接子類。而getSuperclass顧名思義就是得到某個反射類的父類了。

public class TypeCounter extends HashMap<Class<?>, Integer> { private Class<?> baseType; public TypeCounter(Class<?> baseType) { this.baseType = baseType; } public void count(Object obj) { Class<?> type = obj.getClass; if (!baseType.isAssignableFrom(type)) { throw new RuntimeException( obj + " incorrect type " + type + ", should be type or subtype of " + baseType); } countClass(type); } private void countClass(Class<?> type) { Integer quantity = get(type); put(type, quantity == null ? 1 : quantity + 1); Class<?> superClass = type.getSuperclass; if (superClass != null && baseType.isAssignableFrom(superClass)) { countClass(superClass); } } @Override public String toString { StringBuilder result = new StringBuilder("{"); for (Map.Entry<Class<?>, Integer> pair : entrySet) { result.append(pair.getKey.getSimpleName); result.append("="); result.append(pair.getValue); result.append(", "); } result.delete(result.length - 2, result.length); result.append("} "); return result.toString; } }

測試代碼如下:

public static void main(String args) { TypeCounter counter = new TypeCounter(Pet.class); for (Pet pet : Pets.createArray(20)) { System.out.println(pet.getClass.getSimpleName + " "); counter.count(pet); } System.out.println(counter); }

References

THINKING IN JAVA

在這裡小編有話說:b b s . c n i t e d u . c n



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦