自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java反射機(jī)制應(yīng)用實(shí)踐

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

[[191526]]

引言

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

反射基礎(chǔ)

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

在應(yīng)用反射機(jī)制之前,首先我們先來看一下如何獲取一個(gè)對(duì)象對(duì)應(yīng)的反射類Class,在Java中我們有三種方法可以獲取一個(gè)對(duì)象的反射類。

通過getClass方法

在Java中,每一個(gè)Object都有一個(gè)getClass()方法,通過getClass方法我們可以獲取到這個(gè)對(duì)象對(duì)應(yīng)的反射類:

  1. String s = "http://www.ziwenxie.site"
  2.  
  3. Class<?> c = s.getClass();  

通過forName方法

我們也可以調(diào)用Class類的靜態(tài)方法forName():

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

使用.class

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

  1. Class<?> c = String.class; 

獲取類型信息

在文章開頭我們就提到反射的一大好處就是可以允許我們?cè)谶\(yùn)行期間獲取對(duì)象的類型信息,下面我們通過一個(gè)例子來具體看一下。

首先我們?cè)趖ypeinfo.interfacea包下面新建一個(gè)接口A:

  1. package typeinfo.interfacea; 
  2.  
  3. public interface A { void f(); }  

接著我們?cè)趖ypeinfo.packageaccess包下面新建一個(gè)類C,類C實(shí)現(xiàn)了接口A,并且我們還另外創(chuàng)建了幾個(gè)用于測(cè)試的方法,注意下面幾個(gè)方法的權(quán)限都是不同的。

  1. package typeinfo.packageaccess; 
  2.  
  3. import typeinfo.interfacea.A; 
  4.  
  5. class C implements A { 
  6.     public void f() { System.out.println("public C.f()"); } 
  7.     public void g() { System.out.println("public C.g()"); } 
  8.     protected void v () { System.out.println("protected C.v()"); } 
  9.     void u() { System.out.println("package C.u()"); } 
  10.     private void w() { System.out.println("private C.w()"); } 
  11.  
  12.  
  13. public class HiddenC { 
  14.     public static A makeA() { return new C(); } 
  15.  

在callHiddenMethod()方法中我們用到了幾個(gè)新的API,其中g(shù)etDeclaredMethod()根據(jù)方法名用于獲取Class類指代對(duì)象自己聲明的某個(gè)方法,然后我們通過調(diào)用invoke()方法就可以觸發(fā)對(duì)象的相關(guān)方法:

  1. package typeinfo; 
  2.  
  3. import typeinfo.interfacea.A; 
  4. import typeinfo.packageaccess.HiddenC; 
  5.  
  6. import java.lang.reflect.Method; 
  7.  
  8. public class HiddenImplementation { 
  9.     public static void main(String[] args) throws Exception { 
  10.         A a = HiddenC.makeA(); 
  11.         a.f(); 
  12.         System.out.println(a.getClass().getName()); 
  13.         // Oops! Reflection still allows us to call g(): 
  14.         callHiddenMethod(a, "g"); 
  15.         // And even methods that are less accessible! 
  16.         callHiddenMethod(a, "u"); 
  17.         callHiddenMethod(a, "v"); 
  18.         callHiddenMethod(a, "w"); 
  19.     } 
  20.  
  21.     static void callHiddenMethod(Object a, String methodName) throws Exception { 
  22.         Method g = a.getClass().getDeclaredMethod(methodName); 
  23.         g.setAccessible(true); 
  24.         g.invoke(a); 
  25.     } 
  26.  

從輸出結(jié)果我們可以看出來,不管是public,default,protect還是private方法,通過反射類我們都可以自由調(diào)用。當(dāng)然這里我們只是為了顯示反射的強(qiáng)大威力,在實(shí)際開發(fā)中這種技巧還是不提倡。

  1. public C.f() 
  2. typeinfo.packageaccess.C 
  3. public C.g() 
  4. package C.u() 
  5. protected C.v() 
  6. private C.w()  

上面我們只是測(cè)試了Method對(duì)象,感興趣的讀者在熟悉了反射的API之后,不妨測(cè)試一下Filed,這里我們就不重復(fù)了。

利用動(dòng)態(tài)代理實(shí)現(xiàn)面向切面編程

AOP是Spring提供的一個(gè)強(qiáng)大特性之一,AOP的意思是面向切面編程,就是說要分離和業(yè)務(wù)不相關(guān)的代碼,當(dāng)我們需要新增相關(guān)的事務(wù)的時(shí)候,我們不想對(duì)業(yè)務(wù)本身做修改。面向切面編程和面向?qū)ο笞兂上啾鹊降子惺裁春锰幠兀覀兺ㄟ^一個(gè)例子來看一下,對(duì)于新手來說,常常會(huì)寫出下面這樣的代碼:

  1. public class Example1 { 
  2.     public void execute() { 
  3.         // 記錄日志 
  4.         Logger logger = Logger.getLog(...); 
  5.         // 進(jìn)行性能統(tǒng)計(jì) 
  6.         PerformanceUtil.startTimer(...); 
  7.         // 權(quán)限檢查 
  8.         if (!user.hasPrevilege()) { 
  9.             // 拋出異常 
  10.         } 
  11.         // 執(zhí)行真正的業(yè)務(wù) 
  12.         executeTransaction(); 
  13.         PerformanceUtil.endTimer(); 
  14.     } 
  15.  

雖然我們上面真正要執(zhí)行的業(yè)務(wù)只有executeTransaction(),但是日志,性能,權(quán)限相關(guān)的代碼差不多要將真正的業(yè)務(wù)代碼掩蓋了。而且以后如果我們還有一個(gè)Example2,它同樣需要實(shí)現(xiàn)相同的日志,性能,權(quán)限代碼。這樣當(dāng)以后我們需要新增相關(guān)的邏輯檢查的時(shí)候,我們需要所有Example進(jìn)行重構(gòu),這顯然不符合面向?qū)ο蟮囊粋€(gè)基本原則-封裝變化。

上面這個(gè)場(chǎng)景利用模板方法和裝飾器模式都可以解決,在Spring中是通過動(dòng)態(tài)代理來實(shí)現(xiàn)的,下面我們通過一個(gè)例子來模擬一下Spring中的AOP實(shí)現(xiàn)。

我們要實(shí)現(xiàn)的業(yè)務(wù)是,統(tǒng)計(jì)員工工資的時(shí)候程序所執(zhí)行的時(shí)間以及檢查用戶的權(quán)限。首先我們先來實(shí)現(xiàn)Salary類,它里面包含一些實(shí)現(xiàn)統(tǒng)計(jì)員工工資的業(yè)務(wù)邏輯:

  1. public interface SalaryInterface { 
  2.     public void doSalary(); 
  3.  
  4. public class Salary implements SalaryInterface { 
  5.     public void doSalary() { 
  6.         ... 
  7.     } 
  8.  

通過InvocationHandler我們來實(shí)現(xiàn)動(dòng)態(tài)代理,以后當(dāng)我們調(diào)用obj的相關(guān)方法之前,都會(huì)通過invoke方法進(jìn)行代理,而不會(huì)直接調(diào)用obj方法。

  1. public class SimpleProxy implements InvocationHandler { 
  2.     private Object obj; 
  3.     private Object advice; 
  4.  
  5.     // 綁定代理對(duì)象 
  6.     public Object bind(Object obj, Advice advice) { 
  7.         this.obj = obj; 
  8.         this.advice = advice; 
  9.         return Proxy.newProxyInstance(obj.getClass().getClassLoader(), 
  10.             obj.getClass().getInterfaces(), this) 
  11.     } 
  12.  
  13.     // 實(shí)現(xiàn)代理 
  14.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwalbe { 
  15.         Object result = null
  16.         try { 
  17.             advice.before(); 
  18.             result = method.invoke(obj, args); 
  19.             advice.after(); 
  20.         } catch(Exception e) { 
  21.             e.printStackTrace(); 
  22.         } 
  23.         return result 
  24.     } 
  25.  

模擬Spring中的Advice接口:

  1. public interface Advice { 
  2.     public void before(); 
  3.     public void after(); 
  4.  

實(shí)現(xiàn)TimeAdvice用于統(tǒng)計(jì)程序的執(zhí)行時(shí)間:

  1. public class TimeAdvice implements Advice { 
  2.     long startTime; 
  3.     long endTime; 
  4.  
  5.     @Override 
  6.     public void before() { 
  7.         startTime = System.nanoTime(); // 獲取開始時(shí)間 
  8.     } 
  9.  
  10.     @Override 
  11.     public void after() { 
  12.         endTime = System.nanoTime(); // 獲取結(jié)束時(shí)間 
  13.     } 
  14.  

客戶端調(diào)用代碼如下:

  1. public class Client { 
  2.     public static void main(String[] args) { 
  3.         SimpleProxy = new SimpleProxy(); 
  4.         SalaryInterface salaryInterface = 
  5.             (SalaryInterface) simpleProxy.bind(new Salary(), new TimeAdvice()); 
  6.         salaryInterface.doSalary(); 
  7.     } 
  8.  

如果我們現(xiàn)在需要新增權(quán)限控制,我們來實(shí)現(xiàn)ControlAdvie類:

  1. public class ControlAdvice implements Advice { 
  2.     @Override 
  3.     public void before() { 
  4.         if (...) { 
  5.             ... 
  6.         } else { 
  7.             ... 
  8.         } 
  9.     } 
  10.  
  11.     @Override 
  12.     public void after() { 
  13.         ... 
  14.     } 
  15.  

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

與注解相結(jié)合

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

  1. import java.lang.annotation.* 
  2.  
  3. @Target(ElementType.METHOD) 
  4. @Retention(RetentionPolicy.RUNTIME) 
  5. public @interface UserCase { 
  6.     public int id(); 
  7.     public String description() default "no description"
  8.  

下面是我們檢測(cè)密碼的工具類的實(shí)現(xiàn):

  1. public class PasswordUtils { 
  2.     @UserCase(id=47, description="Password must contain at least one numeric"
  3.     public boolean validatePassword(String password) { 
  4.         return (password.matches("\\w*\\d\\w*")); 
  5.     } 
  6.  
  7.     @UserCase(id=48) 
  8.     public String encryptPassword(String password) { 
  9.         return new StringBuilder(password).reverse().toString(); 
  10.     } 
  11.  
  12.     @UserCase(id=49, description="New passwords can't equal previously used ones"
  13.     public boolean checkForNewPassword(List<String> prevPasswords, String password) { 
  14.         return !prevPasswords.contains(password); 
  15.     } 
  16.  

利用反射我們可以寫出更加清晰的測(cè)試代碼,其中g(shù)etDeclaredMethods()方法可以獲取相關(guān)對(duì)象自己聲明的相關(guān)方法,而getAnnotation()則可以獲取Method對(duì)象的指定注解。

  1. public class UseCaseTracker { 
  2.     public static void trackUseCases(List<Integer> useCases, Class<?> cl) { 
  3.         for(Method m : cl.getDeclaredMethods()) { 
  4.             UseCase uc = m.getAnnotation(UseCase.class); 
  5.             if(uc != null) { 
  6.                 System.out.println("Found Use Case: " + uc.id() + " " + uc.description()); 
  7.                 useCases.remove(new Integer(uc.id())); 
  8.             } 
  9.         } 
  10.  
  11.         for(int i : useCases) { 
  12.             System.out.println("Warning: Missing use case-" + i); 
  13.         } 
  14.     } 
  15.  
  16.     public static void main(String[] args) { 
  17.         List<Integer> useCases = new ArrayList<Integer>(); 
  18.         Collections.addAll(useCases, 47, 48, 49, 50); 
  19.         trackUseCases(userCases, PasswordUtils.class); 
  20.     } 
  21.  

解決泛型擦除

現(xiàn)在有下面這樣一個(gè)業(yè)務(wù)場(chǎng)景,我們有一個(gè)泛型集合類List<Class<? extends Pet>>,我們需要統(tǒng)計(jì)出這個(gè)集合類中每種具體的Pet有多少個(gè)。由于Java的泛型擦除,注意類似List<? extends Pet>的做法肯定是不行的,因?yàn)榫幾g器做了靜態(tài)類型檢查之后,到了運(yùn)行期間JVM會(huì)將集合中的對(duì)象都視為Pet,但是并不會(huì)知道Pet代表的究竟是Cat還是Dog,所以到了運(yùn)行期間對(duì)象的類型信息其實(shí)全部丟失了。p.s: 關(guān)于泛型擦除,我在上一篇文章里面有詳細(xì)解釋,感興趣的朋友可以看一看。

為了實(shí)現(xiàn)我們上面的例子,我們先來定義幾個(gè)類:

  1. public class Pet extends Individual { 
  2.     public Pet(String name) { super(name); } 
  3.     public Pet() { super(); } 
  4.  
  5. public class Cat extends Pet { 
  6.     public Cat(String name) { super(name); } 
  7.     public Cat() { super(); } 
  8.  
  9. public class Dog extends Pet { 
  10.     public Dog(String name) { super(name); } 
  11.     public Dog() { super(); } 
  12.  
  13. public class EgyptianMau extends Cat { 
  14.     public EgyptianMau(String name) { super(name); } 
  15.     public EgyptianMau() { super(); } 
  16.  
  17. public class Mutt extends Dog { 
  18.     public Mutt(String name) { super(name); } 
  19.     public Mutt() { super(); } 
  20.  

上面的Pet類繼承自Individual,Individual類的的實(shí)現(xiàn)稍微復(fù)雜一點(diǎn),我們實(shí)現(xiàn)了Comparable接口,重新自定義了類的比較規(guī)則,如果不是很明白的話,也沒有關(guān)系,我們已經(jīng)將它抽象出來了,所以不理解實(shí)現(xiàn)原理也沒有關(guān)系。

  1. public class Individual implements Comparable<Individual> { 
  2.     private static long counter = 0; 
  3.     private final long id = counter++; 
  4.     private String name; // name is optional 
  5.  
  6.     public Individual(String name) { this.name = name; } 
  7.  
  8.     public Individual() {} 
  9.  
  10.     public String toString() { 
  11.         return getClass().getSimpleName() + (name == null ? "" : " " + name); 
  12.     } 
  13.  
  14.     public long id() { return id; } 
  15.  
  16.     public boolean equals(Object o) { 
  17.         return o instanceof Individual && id == ((Individual)o).id; 
  18.     } 
  19.  
  20.     public int hashCode() { 
  21.         int result = 17; 
  22.         if (name != null) { 
  23.             result = 37 * result + name.hashCode(); 
  24.         } 
  25.         result = 37 * result + (int) id; 
  26.         return result; 
  27.     } 
  28.  
  29.     public int compareTo(Individual arg) { 
  30.         // Compare by class name first
  31.         String first = getClass().getSimpleName(); 
  32.         String argFirst = arg.getClass().getSimpleName(); 
  33.         int firstCompare = first.compareTo(argFirst); 
  34.         if (firstCompare != 0) { 
  35.             return firstCompare; 
  36.         } 
  37.  
  38.         if (name != null && arg.name != null) { 
  39.             int secendCompare = name.compareTo(arg.name); 
  40.             if (secendCompare != 0) { 
  41.                 return secendCompare; 
  42.             } 
  43.         } 
  44.  
  45.         return (arg.id < id ? -1 : (arg.id == id ? 0 : 1)); 
  46.     } 
  47.  

下面創(chuàng)建了一個(gè)抽象類PetCreator,以后我們通過調(diào)用arrayList()方法便可以直接獲取相關(guān)Pet類的集合。這里使用到了我們上面沒有提及的newInstance()方法,它會(huì)返回Class類所真正指代的類的實(shí)例,這是什么意思呢?比如說聲明new Dog().getClass().newInstance()和直接new Dog()是等價(jià)的。

  1. public abstract class PetCreator { 
  2.     private Random rand = new Random(47); 
  3.  
  4.     // The List of the different getTypes of Pet to create
  5.     public abstract List<Class<? extends Pet>> getTypes(); 
  6.  
  7.     public Pet randomPet() { 
  8.         // Create one random Pet 
  9.         int n = rand.nextInt(getTypes().size()); 
  10.  
  11.         try { 
  12.             return getTypes().get(n).newInstance(); 
  13.         } catch (InstantiationException e) { 
  14.             throw new RuntimeException(e); 
  15.         } catch (IllegalAccessException e) { 
  16.             throw new RuntimeException(e); 
  17.         } 
  18.     } 
  19.  
  20.     public Pet[] createArray(int size) { 
  21.         Pet[] result = new Pet[size]; 
  22.  
  23.         for (int i = 0; i < size; i++) { 
  24.            result[i] = randomPet(); 
  25.         } 
  26.         return result; 
  27.     } 
  28.  
  29.     public ArrayList<Pet> arrayList(int size) { 
  30.         ArrayList<Pet> result = new ArrayList<Pet>(); 
  31.         Collections.addAll(result, createArray(size)); 
  32.         return result; 
  33.     } 
  34.  

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

  1. public class LiteralPetCreator extends PetCreator { 
  2.     @SuppressWarnings("unchecked"
  3.     public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList( 
  4.         Arrays.asList(Pet.class, Dog.class, Cat.class, Mutt.class, EgyptianMau.class)); 
  5.  
  6.     private static final List<Class<? extends Pet>> types = allTypes.subList( 
  7.         allTypes.indexOf(Mutt.class), allTypes.size()); 
  8.  
  9.     public List<Class<? extends Pet>> getTypes() { 
  10.         return types; 
  11.     } 
  12.  

總體的邏輯已經(jīng)完成了,***我們實(shí)現(xiàn)用來統(tǒng)計(jì)集合中相關(guān)Pet類個(gè)數(shù)的TypeCounter類。解釋一下isAssignalbeFrom()方法,它可以判斷一個(gè)反射類是某個(gè)反射類的子類或者間接子類。而getSuperclass()顧名思義就是得到某個(gè)反射類的父類了。

  1. public class TypeCounter extends HashMap<Class<?>, Integer> { 
  2.     private Class<?> baseType; 
  3.  
  4.     public TypeCounter(Class<?> baseType) { 
  5.         this.baseType = baseType; 
  6.     } 
  7.  
  8.     public void count(Object obj) { 
  9.         Class<?> type = obj.getClass(); 
  10.         if (!baseType.isAssignableFrom(type)) { 
  11.             throw new RuntimeException( 
  12.                 obj + " incorrect type " + type + ", should be type or subtype of " + baseType); 
  13.         } 
  14.         countClass(type); 
  15.     } 
  16.  
  17.     private void countClass(Class<?> type) { 
  18.         Integer quantity = get(type); 
  19.         put(type, quantity == null ? 1 : quantity + 1); 
  20.         Class<?> superClass = type.getSuperclass(); 
  21.         if (superClass != null && baseType.isAssignableFrom(superClass)) { 
  22.             countClass(superClass); 
  23.         } 
  24.     } 
  25.  
  26.     @Override 
  27.     public String toString() { 
  28.         StringBuilder result = new StringBuilder("{"); 
  29.  
  30.         for (Map.Entry<Class<?>, Integer> pair : entrySet()) { 
  31.             result.append(pair.getKey().getSimpleName()); 
  32.             result.append("="); 
  33.             result.append(pair.getValue()); 
  34.             result.append(", "); 
  35.         } 
  36.  
  37.         result.delete(result.length() - 2, result.length()); 
  38.         result.append("} "); 
  39.         return result.toString(); 
  40.     } 
  41.  

測(cè)試代碼如下:

  1. public static void main(String[] args) { 
  2.     TypeCounter counter = new TypeCounter(Pet.class); 
  3.     for (Pet pet : Pets.createArray(20)) { 
  4.         System.out.println(pet.getClass().getSimpleName() + " "); 
  5.         counter.count(pet); 
  6.     } 
  7.     System.out.println(counter); 
  8.  

References

THINKING IN JAVA 

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2017-03-24 09:44:33

Java反射機(jī)制

2012-04-05 13:50:38

Java

2015-09-23 09:08:38

java反射

2011-03-09 09:11:52

java反射機(jī)制

2011-09-27 10:23:24

Java反射機(jī)制

2010-04-01 09:22:38

代理模式Java反射機(jī)制

2011-04-01 14:50:56

Java的反射機(jī)制

2009-06-17 13:57:54

java實(shí)例Reflection

2022-10-21 14:12:06

2012-02-08 09:44:52

Java反射

2010-09-17 13:02:11

JAVA反射機(jī)制

2010-09-17 12:39:51

JAVA反射機(jī)制

2023-11-01 13:48:00

反射java

2012-02-08 09:53:25

Java反射

2012-02-08 10:12:19

Java反射

2011-05-26 15:23:34

JavaReflection

2010-08-11 09:40:44

LINQ

2023-06-27 08:37:35

Java反射動(dòng)態(tài)代理機(jī)制

2009-06-19 13:59:41

Java反射機(jī)制

2021-02-23 08:18:04

Java 反射機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)