In previous post, Quarkus build process was described and how it helps to do DI in build time. In this post we discuss standard approaches for proxy creation and how Quarkus does it.
CDI and interceptors
All Quarkus applications use CDI - Contexts and Dependency Injection. It has annotation for declarative creating interceptor. As a result for target class creating proxy with interceptor logic. But how proxy is created depends on implementation.
How proxy can be created
First, let’s see example of interceptor. GreeterInterceptor
- interceptor and WorkService
, ServiceImpl
, and FinalService
- bean classes which will be proxied.
GreeterInterceptor
interceptor - printing greeting from intercepted bean
@Greeter
@Interceptor
public class GreeterInterceptor {
@AroundInvoke
public Object greet(InvocationContext ctx) throws Exception {
System.out.println("Hello from " + ctx.getTarget());
return ctx.proceed();
}
}
WorkService
bean - not implementing any interfaces
@Singleton
public class WorkService {
@Greeter
public void serve() {
System.out.println("I'm doing work");
}
}
ServiceImpl
bean - implement interface Service
@Singleton
public class ServiceImpl implements Service {
@Greeter
@Override
public void serve() {
System.out.println("I'm doing work");
}
}
FinalService
bean - not implementing interfaces, has final modifier
@Singleton
public final class FinalService {
@Greeter
public void serve() {
System.out.println("I'm doing work");
}
}
Dynamic proxy
Dynamic proxy is a proxy created at runtime. There is 2 way to do this: bytecode generation and Java Dynamic Proxy Class API.
Java Dynamic Proxy Class API
With this API you can create proxy via code like this
InvocationHandler handler = new MyInvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy logic
}
}
Service f = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class<?>[] { Service.class },
handler);
Main limitation of this API, that you can only create proxy for interfaces, so it can only be used for ServiceImpl
bean in our examples.
Runtime bytecode generation
This approach can be implemented using bytecode generation libs such as cglib (but they do not support JDK17+) or ByteBuddy. With bytecode generation you can create proxy for WorkService
and ServiceImpl
from our examples. But bytecode generation can’t help if target class is final like FinalService
.
Proxy creation in Quarkus
Unlike in cases described above Quarkus knows which class need proxy at build time. So bytecode for proxy can be generated at build time without wasting resources at runtime, so it called a static proxy.
Also Quarkus can modify bytecode of classes with final
modifier, so they can be proxied. As a result Quarkus can create proxy for all our examples - WorkService
(ordinary class), ServiceImpl
(implementing interface) and FinalService
(class with final
modifier).
Summary
In this topic we discussed main approaches for creating proxy - bytecode generation and Java Dynamic Proxy Class API. Quarkus combines bytecode generation, bytecode transformation and knowledge about all classes to generate proxy at build time. Thereby giving better perfomance at runtime and possibility for creating proxy for final classes.
Top comments (0)