0Pricing
Java Academy · Lekcja

Dynamiczne proxy z InvocationHandler

Twórz dynamiczne proxy JDK do przechwytywania wywołań metod interfejsów na potrzeby logowania, buforowania i ponawiania prób.

Dynamiczne proxy z InvocationHandler to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Czym jest dynamiczne proxy?

Dynamiczne proxy JDK to obiekt tworzony w czasie działania, który implementuje co najmniej jeden interfejs. Wszystkie wywołania metod są przekazywane do dostarczonego handlera InvocationHandler.

Tworzenie proxy za pomocą Proxy.newProxyInstance

Aby utworzyć proxy, należy wywołać Proxy.newProxyInstance(classLoader, interfaces[], handler). Proxy implementuje wszystkie wymienione interfejsy.

UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    new LoggingHandler(realService)
);
proxy.findById(42L); // intercepted by LoggingHandler

Interfejs InvocationHandler

Należy zaimplementować InvocationHandler, aby obsługiwać każde wywołanie metody. Handler otrzymuje obiekt proxy, wywoływaną metodę oraz argumenty.

public class LoggingHandler implements InvocationHandler {
    private final Object target;
    public LoggingHandler(Object target) { this.target = target; }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">> " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("<< " + method.getName());
        return result;
    }
}

Proxy do buforowania wyników

InvocationHandler może buforować wyniki: jeśli bufor zawiera wynik dla podanych argumentów, należy go zwrócić; w przeciwnym razie wywołać rzeczywistą metodę i zapisać wynik.

public class CachingHandler implements InvocationHandler {
    private final Object target;
    private final Map<String, Object> cache = new HashMap<>();
    public CachingHandler(Object target) { this.target = target; }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String key = method.getName() + Arrays.toString(args);
        return cache.computeIfAbsent(key, k -> {
            try { return method.invoke(target, args); }
            catch (Exception e) { throw new RuntimeException(e); }
        });
    }
}

Proxy do ponawiania prób

Wywołania metod należy opakować w pętlę ponawiania prób. Jeśli wystąpi wyjątek, próbę należy ponowić maksymalnie N razy, a następnie ponownie zgłosić wyjątek.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    for (int attempt = 1; attempt <= 3; attempt++) {
        try { return method.invoke(target, args); }
        catch (InvocationTargetException e) {
            if (attempt == 3) throw e.getCause();
            Thread.sleep(1000L * attempt);
        }
    }
    throw new RuntimeException("unreachable");
}

Proxy do zarządzania transakcjami

Spring's @Transactional używa dynamicznego proxy do rozpoczęcia transakcji przed wywołaniem metody oraz zatwierdzenia lub wycofania transakcji po nim — wszystko bez wiedzy osoby wywołującej.

Sprawdzanie, czy obiekt jest proxy

Do wykrywania proxy należy użyć Proxy.isProxyClass(obj.getClass()), a do pobrania handlera — Proxy.getInvocationHandler(proxy).

if (Proxy.isProxyClass(service.getClass())) {
    InvocationHandler h = Proxy.getInvocationHandler(service);
    System.out.println("Handler: " + h.getClass().getSimpleName());
}

Ograniczenia dynamicznych proxy JDK

Dynamiczne proxy JDK działają wyłącznie z interfejsami — nie mogą tworzyć proxy dla klas konkretnych. Do tworzenia proxy na podstawie klas należy użyć CGLIB, którego Spring używa również wewnętrznie dla klas @Configuration.

CGLIB jako alternatywa

CGLIB generuje w czasie działania podklasę klasy docelowej, przesłaniając wszystkie metody inne niż final. Spring używa go, gdy nie ma dostępnych interfejsów. Nie jest wymagana żadna adnotacja — działanie jest przezroczyste.

Proxy w Hibernate

Hibernate ładuje asocjacje encji leniwie za pomocą proxy CGLIB/Byte Buddy. Gdy uzyskuje się dostęp do leniwej kolekcji, proxy wykonuje zapytanie SQL.

// Hibernate creates a proxy for:
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
// Accessing dept triggers a DB query via the proxy

Wydajność proxy

Dysponowanie metodą przez dynamiczne proxy jest około 5–10 razy wolniejsze niż wywołanie bezpośrednie. W przypadku bardzo często wykonywanych ścieżek warto rozważyć generowanie kodu w czasie kompilacji (APT) lub MethodHandles zamiast dynamicznych proxy.

Szybkie sprawdzenie

Jakie jest główne ograniczenie dynamicznych proxy JDK?

Podsumowanie

Dynamiczne proxy JDK przechwytują wywołania metod interfejsów za pomocą InvocationHandler. Można ich używać do rejestrowania logów, buforowania wyników, ponawiania prób i zarządzania transakcjami. Do tworzenia proxy dla klas należy użyć CGLIB. Tworzenie proxy należy buforować, ponieważ wykorzystuje refleksję i nie jest bezkosztowe.

Często zadawane pytania

Czy lekcja „Dynamiczne proxy z InvocationHandler” jest bezpłatna?

Tak — pełny tekst „Dynamiczne proxy z InvocationHandler” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Dynamiczne proxy z InvocationHandler”?

Twórz dynamiczne proxy JDK do przechwytywania wywołań metod interfejsów na potrzeby logowania, buforowania i ponawiania prób. Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Dynamiczne proxy z InvocationHandler”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Inspekcja klas za pomocą refleksji
  2. Wywoływanie metod i uzyskiwanie dostępu do pól
  3. Dynamiczne proxy z InvocationHandler
  4. Wydajność refleksji i alternatywy
← Powrót do Java Academy