Achtergrond:
Proxy-objecten en het Proxy-patroon zijn in Java geïntroduceerd om vervangende objecten te creëren die de toegang tot een echt object kunnen controleren, het gedrag ervan kunnen wijzigen of cross-cutting-functies (beveiliging, logging, metrics) kunnen invoegen. Sinds Java 1.3 is er ondersteuning voor standaard dynamische proxies in het pakket java.lang.reflect, en later populaire bibliotheken zoals CGLIB en ByteBuddy.
Probleem:
Het is niet altijd mogelijk of handig om de logica van een bestaand klasse rechtstreeks te wijzigen. Vaak is het nodig om gedrag transparant toe te voegen (bijvoorbeeld logging, caching, transactionele verwerking) zonder de broncode van het object te wijzigen, wat niet mogelijk is met standaard overerving.
Oplossing:
Proxy-objecten kunnen statisch (door handmatige subclassing) en dynamisch (door middel van reflectie of bytecode) worden geïmplementeerd. Een dynamische proxy maakt het mogelijk om on-the-fly objecten te creëren die de vereiste interface implementeren en de aanroepen doorgeven aan het echte object binnen de methode invoke(), wat flexibiliteit biedt voor het invoegen van extern gedrag.
Codevoorbeeld:
import java.lang.reflect.*; interface Service { void doWork(); } class RealService implements Service { public void doWork() { System.out.println("Doing work!"); } } class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calling: " + method.getName()); return method.invoke(target, args); } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( real.getClass().getClassLoader(), new Class[] {Service.class}, new LoggingInvocationHandler(real)); proxy.doWork(); // Het aanroepen wordt gelogd
Belangrijkste kenmerken:
Kan de standaard dynamische Proxy in Java werken met klassen die geen interfaces implementeren?
Nee. De standaard Proxy in Java werkt alleen met interfaces. Voor klassen zonder interfaces (bijvoorbeeld als je een gewone klasse wilt proxyfying) zijn externe middelen nodig (bijvoorbeeld CGLIB, ByteBuddy).
Kan er een proxy worden gemaakt voor een interface met private methoden?
Nee. Interfaces in Java kunnen geen private methoden bevatten die moeten worden geproxyfied. Dynamische proxy's implementeren de publieke methoden van de interface. Sinds Java 9 zijn er private default methoden, maar deze zijn niet toegankelijk via proxy.
Wat is het verschil tussen InvocationHandler en MethodInterceptor (CGLIB)?
InvocationHandler is de standaard JDK-interface die wordt gebruikt voor dynamische proxies. Het accepteert aanroepen van de methoden van de interface. MethodInterceptor is een CGLIB-interface waarmee aanroepen van methoden in willekeurige klassen kunnen worden onderschept via dynamische overerving. Het verschil zit in toepasbaarheid en niveau: JDK werkt alleen met interfaces, CGLIB met willekeurige klassen.
Een engineer herhaalt handmatig de logcode in elke methode van alle services. Bij het toevoegen van nieuwe methoden herhaalt hij constant de logica, vergeet vaak de benodigde aanroepen toe te voegen.
Pluspunten:
Minpunten:
AOP is in het project geïmplementeerd via dynamische proxy: logging en transactiecontrole wordt uitgevoerd via vervangende wrappers die centraal worden geïmplementeerd.
Pluspunten:
Minpunten: