Məqalələr

Java Reflection`un real proyektdə istifadəsi

java-reflection
Written by Mushfiq Mammadov

Adətən Java ilə bağlı bəzi mövzular olur ki, onları oxuyanda ürəyində fikirləşirsən ki, görəsən heç bunları real proyektlərdə tətbiq edəcəm? Bu suallara öz verdiyin cavablar da əksər vaxtlar “çox güman ki istifadə etməyəcəm” olur. Amma bəzən real proyektlərdə elə situasiyalar ilə rastlaşırsan ki, bu mövzular əsl dərman kimi yerinə düşür. İdeya gəlir, tətbiq edirsən alınır və böyük zövq verir. Bu məqalə də həmin qəbildən olan nümunələrdəndir.

Birbaşa mətləbə keçək. Deməli proyektdə belə bir situasiya ilə rastlaşdım:
Class daxilində təxminən 30-a yaxın metod var və bunların sayı get-gedə arta bilər. Gələn sorğuya (request) əsasən hansı metodun çağırılacağını təyin etməlisən.

Ağıla gələn ilk həll variantı sorğuda göndərilən müvafiq field-ə əsasən, switch-case ilə uyğun metodun təyin edilib çağırılmasıdır. Kodun ilkin variantı təxminən aşağıdakı kimi olacaq. Real proyektdəki kod dəyişdirilib, minimallaşdırılıb, mahiyyət baxımından bənzər kod verilib. Məqalə şişməsin deyə burada kodun ancaq bəzi hissələri göstəriləcək, sonda github linki qeyd olunacaq, tam şəkildə oradan baxmaq olar.

ProvisioningRequest.java
public class ProvisioningRequest {
    private String requestId;
    private int providerId;
    private ProvisioningCommand provisioningCommand;

    //getter-setter
}
ProvisioningCommand.java
public enum ProvisioningCommand {
    INIT_SERVICE,
    OPEN_SERVICE,
    CLOSE_SERVICE,
    CHANGE_SERVICE,
    UPDATE_ACCOUNT,
    REMOVE_ACCOUNT,
    OPEN_VAS,
    CLOSE_VAS,
    CREATE_NAS,
    UPDATE_NAS,
    CREATE_ATTRIBUTE,
    UPDATE_ATTRIBUTE,
    CREATE_SERVICE_PROFILE,
    UPDATE_SERVICE_PROFILE,
    DISCONNECT
}
ProvisioningService.java
public class ProvisioningService {

    public ProvisioningResponse startProvisioning(ProvisioningRequest request) {

        ProvisioningEngine provisioner = getCurrentProvisioner(request.getProviderId());
        if (provisioner == null)
            throw new IllegalArgumentException("Wrong provider id: "+request.getProviderId());

        ProvisioningCommand command = request.getProvisioningCommand();
        switch (command){
            case INIT_SERVICE:
                return provisioner.initService(request);
            case OPEN_SERVICE:
                return provisioner.openService(request);
            case CLOSE_SERVICE:
                return provisioner.closeService(request);
            case CHANGE_SERVICE:
                return provisioner.changeService(request);
            case UPDATE_ACCOUNT:
                return  provisioner.updateAccount(request);
            case REMOVE_ACCOUNT:
                return provisioner.removeAccount(request);
            case OPEN_VAS:
                return provisioner.openVAS(request);
            case CLOSE_VAS:
                return provisioner.closeVAS(request);
            case CREATE_NAS:
                return  provisioner.createNas(request);
            case UPDATE_NAS:
                return  provisioner.updateNas(request);
            case CREATE_ATTRIBUTE:
                return provisioner.createAttribute(request);
            case UPDATE_ATTRIBUTE:
                return provisioner.updateAttribute(request);
            case CREATE_SERVICE_PROFILE:
                return provisioner.createServiceProfile(request);
            case UPDATE_SERVICE_PROFILE:
                return provisioner.updateServiceProfile(request);
            case DISCONNECT:
                return provisioner.disconnect(request);
             /*
                ...other commands...
            */
            default: throw new IllegalArgumentException("UNSUPPORTED COMMAND!");
        }
    }

    public ProvisioningEngine getCurrentProvisioner(int id){
        return  id == 111111 ? Provider1Provisioner.getInstance() :
                id == 222222 ? Provider2Provisioner.getInstance() :
                null;
    }
}

Koddan da göründüyü kimi switch-case ifadələrinin sayı çoxdur. Burada hələ 15-ə yaxın metod qeyd olunub, təsəvvür edin real proyektdə onların sayı 30-a yaxındır və get-gedə arta bilər. Belə olan halda switch-case ifadəsi uzana-uzana gedəcək.

Kodun belə çox uzanmağı xoşuma gəlmədi, fikirləşdim görəsən bunu daha qısa şəkildə etmək olarmı?

Son günlərdə Java Reflection ilə bağlı bir məqalə oxumuşdum, ağlıma gələn ilk ideya java-reflection istifadə edərək metod adını dinamik təyin edib çağırmaq oldu. Deməli enuma konstruktor əlavə edib hər dəyər üçün müvafiq metod adını saxladım və gələn sorğudan da enum dəyərinə uyğun metod adını götürüb reflection istifadə edərək lazımı metodu çağırdım. Və istədiyim alındı 🙂

Dəyişikliklərdən sonra ProvisioningCommandProvisioningService classları aşağıdakı şəkildə oldu:

ProvisioningCommand.java
public enum ProvisioningCommand {
    INIT_SERVICE("initService"),
    OPEN_SERVICE("openService"),
    CLOSE_SERVICE("closeService"),
    CHANGE_SERVICE("changeService"),
    UPDATE_ACCOUNT("updateAccount"),
    REMOVE_ACCOUNT("removeAccount"),
    OPEN_VAS("openVAS"),
    CLOSE_VAS("closeVAS"),
    CREATE_NAS("createNas"),
    UPDATE_NAS("updateNas"),
    CREATE_ATTRIBUTE("createAttribute"),
    UPDATE_ATTRIBUTE("updateAttribute"),
    CREATE_SERVICE_PROFILE("createServiceProfile"),
    UPDATE_SERVICE_PROFILE("updateServiceProfile"),
    DISCONNECT("disconnect");

    private final String methodname;
    ProvisioningCommand(final String methodName){
        this.methodname = methodName;
    }

    public String getMethodName(){
        return this.methodname;
    }
}
ProvisioningService.java
public class ProvisioningService {

    public ProvisioningResponse startProvisioning(ProvisioningRequest request) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        ProvisioningEngine provisioner = getCurrentProvisioner(request.getProviderId());
        if (provisioner == null)
            throw new IllegalArgumentException("Wrong provider id: "+request.getProviderId());

        ProvisioningCommand command = request.getProvisioningCommand();
        Method callingMethod = ProvisioningEngine.class.getDeclaredMethod(command.getMethodName(), ProvisioningRequest.class);
        ProvisioningResponse provisioningResponse = (ProvisioningResponse) callingMethod.invoke(provisioner, request);

        return provisioningResponse;
    }

    public ProvisioningEngine getCurrentProvisioner(int id){
        return  id == 111111 ? Provider1Provisioner.getInstance() :
                id == 222222 ? Provider2Provisioner.getInstance() :
                null;
    }
}

Təxminən 30 sətirlik switch-case ifadəsini (real nümunədə təxminən bundan 2 dəfə böyük) 2 sətirlik kod ilə əvəz etdik. Test sorğu göndərib yoxladıqda görürük ki, hər şey istədiyimiz kimidir:

provisioning-request

Düzdü reflectionun mütləq lazım olmadıqca istifadəsi tövsiyə edilmir, çünki “reflective” əməliyyatlar performans cəhətdən “non-reflective” əməliyyatlara nəzərən yavaş işləyir. Amma bizim nümunədə kiçik bir funksionallıq üçün istifadə etdiyimizdən performans baxımından elə böyük bir problem yaşamayacağıq.

Əlavə bir məsələni də qeyd edim, reflection ilə işləyərkən diqqətli olmaq lazımdı. Bizim nümunəmizdə əgər metod adını düzgün yazmasanız compile zamanı bununla bağlı sizə heç bir xəbərdarlıq verilməyəcək. Java reflectionu ancaq runtime zamanı yoxlayır, əgər metod adı düzgün yazılmazsa, Exception fırladacaq. Ona görə də yaxşı olar ki, reflection əməliyyatlar üçün testlər yazıb öncədən özümüzü sığortalayaq.   

Kod nümunəsinə bütöv şəkildə aşağıdakı github linkindən baxa bilərsiniz:

https://github.com/mmushfiq/java-reflection-example.git

Switch-case ilə olan nümunəyə “switch-case version” commitinə keçərək baxa bilərsiniz:

github-commit-browse-files

Bu məqaləmiz də bu qədər. Mövzu ilə bağlı əlavə fikirləriniz, başqa maraqlı təklifləriniz olarsa kommentdə qeyd etməyi unutmayın.

About the author

Mushfiq Mammadov

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.