Java反射機制應用實踐

引言

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

反射基礎

p.s: 本文須要讀者對反射機制的API有必定程度的瞭解,若是以前沒有接觸過的話,建議先看一下官方文檔的Quick Startgit

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

經過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;

獲取類型信息

在文章開頭咱們就提到反射的一大好處就是能夠容許咱們在運行期間獲取對象的類型信息,下面咱們經過一個例子來具體看一下。oracle

首先咱們在typeinfo.interfacea包下面新建一個接口Aapp

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()方法就能夠觸發對象的相關方法:less

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);
    }
}

從輸出結果咱們能夠看出來,不論是publicdefaultprotect仍是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類繼承自IndividualIndividual類的的實現稍微複雜一點,咱們實現了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;
    }
}

接下來咱們來實現上面這一個抽象類,解釋一下下面的代碼,在下面的代碼中,咱們聲明瞭兩個集合類,allTypestypes,其中allTypes中包含了咱們呢上面所聲明的全部類,可是咱們具體的類型實際上只有兩種即MuttEgypianMau,因此咱們真正須要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 class Pets {
    public static final PetCreator creator = new LiteralPetCreator();
    public static Pet randomPet() {
        return creator.randomPet();
    }
    public static Pet[] createArray(int size) {
        return creator.createArray(size);
    }
    public static ArrayList<Pet> arrayList(int size) {
        return creator.arrayList(size);
    }
}
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

Contact

GitHub: https://github.com/ziwenxie
Blog: https://www.ziwenxie.site

本文爲做者原創,轉載請聲明博客出處:)

相關文章
相關標籤/搜索