CodeGym /Kurslar /JAVA 25 SELF /Təhlükəsizlik, məhdudiyyətlər və refleksiyaya alternativl...

Təhlükəsizlik, məhdudiyyətlər və refleksiyaya alternativlər

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. Təhlükəsizlik: refleksiya nə ilə təhlükəlidir?

Refleksiya — proqramınız üçün sanki pin açarı kimidir: o, adi kodun girməməli olduğu yerlərə belə daxil olmağa imkan verir. Məsələn, refleksiya vasitəsilə private sahələri oxumaq və dəyişmək, private metodları çağırmaq və hətta final-sahələrin dəyərlərini dəyişmək olar (bəli-bəli, belə fəndlər mümkündür, baxmayaraq ki, həmişə nəticəsiz ötüşmür).

Nümunə: enkapsulyasiyanı yan keçmək


import java.lang.reflect.Field;

public class Secret {
    private String secret = "Burada sirr var!";

    public String getSecret() {
        return secret;
    }
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Secret s = new Secret();
        Field field = Secret.class.getDeclaredField("secret");
        field.setAccessible(true); // "Qapını" açırıq
        field.set(s, "Sındırıldı!");
        System.out.println(s.getSecret()); // Sındırıldı!
    }
}

Adi hallarda private sahə qorunur, lakin setAccessible(true) ilə refleksiya bu müdafiəni sındırır. Bu, bir supergücdür — və eyni zamanda böyük məsuliyyətdir.

SecurityManager və məhdudiyyətlər

Əvvəllər Java-da SecurityManager mexanizmi var idi ki, (məsələn, applet-lərdə və ya serverdə) refleksiyanın istifadəsini məhdudlaşdıra bilirdi. Amma Java 17-də SecurityManager deprecated for removal kimi işarələndi, Java 21-də isə platformadan tam çıxarıldı.

Müasir JVM-lərdə təhlükəsizlik fərqli şəkildə təmin olunur: modul sistemi (Java 9+) və daxili siniflərə sərt giriş məhdudiyyətləri vasitəsilə.

Həssizlik nümunəsi: final-sahələri dəyişmək

import java.lang.reflect.Field;

public class FinalDemo {
    private final int number = 42;

    public static void main(String[] args) throws Exception {
        FinalDemo obj = new FinalDemo();
        Field f = FinalDemo.class.getDeclaredField("number");
        f.setAccessible(true);
        f.set(obj, 99);
        System.out.println(obj.number); // 42 (!)
        System.out.println(f.get(obj)); // 99
    }
}

number sahəsinin dəyəri həmişə “lazım olduğu kimi” dəyişmir — kompilyator və JVM final-sahələrlə işi optimallaşdıra bilər və nəticə... gözlənilməz ola bilər! Bu bir daha sübut edir ki, refleksiya — sehrli çubuq deyil, daha çox bəzən işləyən, bəzən isə yox, bir “fomka” kimidir.

2. Refleksiyanın məhdudiyyətləri

Performans itkisi

Metodların çağırılması və sahələrə giriş refleksiya ilə adi çağırışdan daha yavaşdır. JVM belə çağırışları birbaşa metod çağırışı və ya sahəyə müraciət qədər yaxşı optimallaşdıra bilmir. Əgər siz refleksiya vasitəsilə metodu böyük dövrün içində və ya isti icra yolunda çağırırsınızsa — ləngimələrə hazır olun.

public class PerfDemo {
    public void sayHello() {}

    public static void main(String[] args) throws Exception {
        PerfDemo obj = new PerfDemo();
        long start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            obj.sayHello();
        }
        long direct = System.nanoTime() - start;

        var method = PerfDemo.class.getMethod("sayHello");
        start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            method.invoke(obj);
        }
        long reflect = System.nanoTime() - start;

        System.out.printf("Adi çağırış: %d µs\n", direct / 1000);
        System.out.printf("Refleksiya ilə: %d µs\n", reflect / 1000);
    }
}

Nəticə: refleksiya adətən 10–100 dəfə yavaşdır!

Tip təhlükəsizliyinin itirilməsi

Refleksiya Object tipli obyektlərlə işləyir və tiplərin əl ilə çevrilməsini tələb edir. Səhvlər (məsələn, arqumentin yanlış tipi) yalnız icra zamanı üzə çıxır, kompilyasiya mərhələsində yox. Bu isə “sürprizlərin” və tapılması çətin olan xətaların riskini artırır.

İstisnalar və checked-səhvlər

Refleksiya istisnaları sevir: NoSuchFieldException, IllegalAccessException, InvocationTargetException və digərələri. Onları tutmaq lazımdır, əks halda proqram sadəcə çökəcək.

Modul sisteminin məhdudiyyətləri

Java-da modulların (module system) yaranması ilə daxili siniflərə və private üzvlərə çıxış məhdudlaşdırıldı. Başqa moduldakı sinfin private sahəsinə müraciət etməyə çalışsanız, InaccessibleObjectException alacaqsınız.

Nümunə

// Modul tətbiqində:
Field f = SomeClass.class.getDeclaredField("secret");
f.setAccessible(true); // java.lang.reflect.InaccessibleObjectException!

Belə girişə icazə vermək üçün paketi açıq şəkildə açmaq lazımdır (məsələn, JVM parametrləri ilə: --add-opens), bu isə həmişə mümkün və ya təhlükəsiz deyil.

3. Refleksiyaya müasir alternativlər

Refleksiya — elə bir alətdir ki, ondan yalnız onsuz tamamilə keçinmək mümkün olmadıqda istifadə etmək lazımdır. Xoşbəxtlikdən, Java dili və onun ekosistemi inkişaf edir və əksər hallarda refleksiyasız keçinməyə imkan verən yeni imkanlar ortaya çıxır.

Pattern Matching (Java 16+)

Pattern Matching obyektlərin daxilindən refleksiya ilə “qurdalanmağa” ehtiyac olmadan onları yoxlamağa və dəyərləri çıxarmağa zərif imkan yaradır.

// instanceof üçün pattern matching nümunəsi (Java 16+)
if (obj instanceof String s) {
    System.out.println("Bu sətrin uzunluğu: " + s.length());
}

Sealed classes (Java 17+)

Sealed siniflər irsiyyət iyerarxiyasını açıq şəkildə məhdudlaşdırmağa imkan verir, bu da kodun analizini asanlaşdırır və refleksiya vasitəsilə strukturu “təxmin etməyə” ehtiyacı azaldır.

public sealed class Shape permits Circle, Rectangle {}
public final class Circle extends Shape {}
public final class Rectangle extends Shape {}

Record sinifləri (Java 16+)

record-siniflər avtomatik olaraq konstruktorları, getter-ləri, equals, hashCodetoString-i yaradır. Bunun sayəsində obyektlərin seriyalaşdırılması və müqayisəsi daha sadə və daha təhlükəsiz olur — çox vaxt refleksiyaya ehtiyac qalmır.

public record Point(int x, int y) {}

Annotation Processing (APT)

İcra zamanı refleksiya ilə anotasiya analiz etmək əvəzinə, kompilyasiya mərhələsində (@SupportedAnnotationTypes və s.) anotasiya prosessorlarından istifadə edərək lazım olan kodu generasiya etmək olar. Bu, daha sürətli və təhlükəsizdir.

İnterfeyslərdən, fabriklərdən və DI-dan istifadə

Əvvəllər sinif adından obyekt yaratmaq üçün refleksiya tətbiq edilən bir çox hallarda interfeyslər, fabriklər və ya dependency injection‑konteynerləri (məsələn, Spring) istifadə etmək daha məqsədəuyğundur. Bu, sinifləri “sındırmağa” ehtiyac olmadan çevik və genişlənə bilən sistemlər qurmağa imkan verir.

4. Best practices: refleksiya ilə necə işləməli və peşman olmamalı

  • Refleksiyadan yalnız onsuz keçinmək mümkün olmayan yerlərdə istifadə edin. Məsələn, kitabxanalar, freymvörklər, plaginlər, test alətləri yazarkən.
  • Tətbiq sahəsini minimuma endirin. “Hər ehtimala qarşı” bütün sahələri və metodları setAccessible(true) ilə əlçatan etməyin.
  • Refleksiyanın istifadəsini sənədləşdirin. Kodunuzu müşayiət edəcək hər kəs bu aləti harada və nə üçün tətbiq etdiyinizi bilməlidir.
  • Bütün checked istisnaları emal edin. Onları görməməzlikdən gəlməyin — əks halda xətalar ən uyğunsuz anda üzə çıxacaq.
  • final-sahələr, private və daxili siniflərlə ehtiyatlı olun. Onların refleksiya ilə dəyişdirilməsi tətbiqin qeyri‑stabil işləməsinə səbəb ola bilər.
  • Modul sisteminin məhdudiyyətlərini nəzərə alın. Tətbiqiniz modullar (Java 9+) olan mühitdə işləyirsə, siniflərin daxili üzvlərinə giriş ssenarilərini əvvəlcədən düşünün.
  • Refleksiyanı gündəlik tapşırıqlar üçün istifadə etməyin. Çox vaxt dilin adi vasitələri ilə kifayətlənmək olar: interfeyslər, fabriklər, dizayn nümunələri.

5. Praktika: modul tətbiqində private sahəyə çıxış

Gəlin modul tətbiqində refleksiya vasitəsilə başqa sinfin private sahəsinə çıxış almağa cəhd edək və nə baş verəcəyinə baxaq.

Kod nümunəsi

// module-info.java
module my.app {}

// SomeClass.java
package my.app;

public class SomeClass {
    private String secret = "Modul sirri";
}

// Main.java
package my.app;

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        SomeClass obj = new SomeClass();
        Field field = SomeClass.class.getDeclaredField("secret");
        field.setAccessible(true); // java.lang.reflect.InaccessibleObjectException!
        System.out.println(field.get(obj));
    }
}

Nə baş verəcək?

Java 17+ (və yuxarı) üzərində istisna alacaqsınız:

Exception in thread "main" java.lang.reflect.InaccessibleObjectException:
Unable to make field private java.lang.String my.app.SomeClass.secret accessible:
module my.app does not "opens my.app" to unnamed module

Bunu necə düzəltmək olar?

Paketi refleksiya üçün açıq şəkildə açın (məsələn, JVM parametrləri ilə):

--add-opens my.app/my.app=ALL-UNNAMED

Yaxud (daha yaxşısı!) mümkün olduqda ümumiyyətlə refleksiyadan istifadə etməyin.

6. Refleksiya ilə işləyərkən tipik səhvlər və risklər

Səhv №1: setAccessible(true)-dən əsassız istifadə.
Şəxsi sahələrə çıxışı açmaq, sanki açarları soyuducudan çıxarmaq üçün öz mənzilinizi sındırmağa bənzəyir. Bunu yalnız həqiqətən lazım olduqda və nəticələrini anladığınız halda edin.

Səhv №2: checked-istisnaları görməməzlikdən gəlmək.
Refleksiya istisnaları sevir. Onları emal etməsəniz, tətbiq gözlənilmədən çökə bilər. Hətta “məndə hər şey işləyir” desəniz belə — bu, hamıda belə olacaq demək deyil.

Səhv №3: refleksiyanın həmişə eyni cür işləyəcəyini gözləmək.
Modul sistemi, JVM məhdudiyyətləri, Java-nın müxtəlif versiyaları və işə salma parametrləri reflektiv kodunuzu qəfil “sındıra” bilər.

Səhv №4: tipik tapşırıqlar üçün refleksiyadan istifadə.
Əgər interfeyslər, fabriklər, DI ilə keçinmək mümkündürsə — refleksiyadan istifadə etməyin. Bu, mürəkkəbliyi artırır və performansı azaldır.

Səhv №5: final-sahələri refleksiya ilə dəyişmək.
Bu, kompilyator və JVM optimizasiyaları ilə bağlı gözlənilməz və tutması çətin xətalara səbəb ola bilər.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION