DEV Community

Efim Smykov
Efim Smykov

Posted on

Static proxies in Quarkus

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();
    }

}
Enter fullscreen mode Exit fullscreen mode

WorkService bean - not implementing any interfaces

@Singleton
public class WorkService {

    @Greeter
    public void serve() {
        System.out.println("I'm doing work");
    }

}
Enter fullscreen mode Exit fullscreen mode

ServiceImpl bean - implement interface Service

@Singleton
public class ServiceImpl implements Service {

    @Greeter
    @Override
    public void serve() {
        System.out.println("I'm doing work");
    }

}
Enter fullscreen mode Exit fullscreen mode

FinalService bean - not implementing interfaces, has final modifier

@Singleton
public final class FinalService {

    @Greeter
    public void serve() {
        System.out.println("I'm doing work");
    }

}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)