CDI – “Contexts and Dependency Injection” – is a powerful framework of the Java EE standard, which realizes important software design principles including loose coupling and the programming paradigm of aspect-oriented programming, made possible by interceptors intervening in method invocations. CDI has remarkable mechanisms that run in the background and allow comfortable use of such features. One of them is the dynamic generation and use of proxy classes.
But what exactly are proxy classes and when and why does CDI generate them? And what’s more, what does such a class look like and how is it generated? Exactly these questions should be discussed in this blog post.
Note: CDI is an interface with several possible implementations, including Weld, OpenWebBeans, or Daucho CanDI. When talking about CDI and its properties in this article, the reference implementation Weld is always meant. The statements made refer to CDI Version 2.0.
What are proxy classes and when and why does CDI use them?
Proxy classes are classes that ‘wrap around’ an existing source class, surrounding them like a “cloud” (see figure), which contains further possibilities.
- Proxy classes have the same methods as their source class and call the original methods out of them
- They also extend the possibilities of the source class by integrating additional interfaces or offering additional methods
If an object of a CDI managed bean is injected with the annotation @Inject into another CDI managed bean, the CDI container intervenes. It decides each time whether the reference to the created object is placed directly on the actual bean, i.e. an object of exactly this class is generated, or indirectly on a client proxy class of that bean, i.e. a proxy is generated. This decision depends on the lifecycle of the bean, determined by its scope:
- For all “normal scopes” a client proxy is generated, i.e. for all managed beans that are annotated with @ApplicationScoped, @SessionScoped, @ConversationScoped or @RequestScoped. So for all these beans, the real class will actually never be referenced, without the developer realizing this.
- For all “pseudo scopes”, however, an object of the source class is created, i.e. for all managed beans that are annotated @Dependent or (javax.inject) @Singleton. It should be noted that the @Dependent Scope is the default scope, so it also applies to all non-annotated classes. The scope serves only a single client and takes over its lifecycle.
Note: Scopes are explained in the Oracle Documentation and the dependent proxy generation in the Weld Documentation in more detail.
But why are client proxies useful or even necessary?
- Dependency injection between managed beans of different scopes is made possible just by proxies.
Suppose a bean bound to the application scope contains a direct reference to a bean bound to the request scope. The bean with application scope is shared by many different requests. However, each request should see a different instance of the request scope bean, namely the current one.
Now imagine that a bean bound to the session scope contains a direct reference to a bean bound to the application scope. From time to time, the session context is serialized to disk to make more efficient use of memory. However, the bean instance with application scope should not be serialized together with the session scope bean! It can retrieve this reference at any time, so there is no reason for recursive serialization.
Unless a bean has the Default Scope @Dependent, the container must indirectly execute any inserted references to the bean through a proxy object. This client proxy is responsible for choosing exactly the one bean instance (receiving a method call ) which is associated with the current context. The client proxy also makes it possible to serialize beans bound to contexts (such as the session context) to the hard disk without recursively serializing other injected beans.
- Interceptors:
Interceptors in CDI make it possible to intervene in the execution of selected methods and to fit in additional functionalities such as logging, transaction control or security queries. These so-called “aspects” are detached from the actual method logic and can be used by any number of methods (aspect-oriented programming).
Interceptors can be easily and non-intrusively implemented through a proxy by calling the extended functions in addition to the referenced origin class method within the proxy.
In the same way, @PostConstruct and @PreDestroy methods, acting like interceptors as well, can be integrated into the proxy.
- Availability of further interfaces and their methods:
A proxy offers the possibility to integrate further interfaces. For example, a Weld-generated proxy implements the WeldClientProxy interface, which provides access to CDI metadata.
Note: The proxy usage between different scopes is explained in more detail in the Weld Documentation, further explanations can be found in this article on the JAX website.
What does a CDI proxy look like?
So CDI, in our case Weld, relies on proxies instead of their source classes in most cases, and with good reason. But what does a proxy class look like?
Since CDI generates its proxies dynamically at runtime, you will never see such a class in the form of source code, they exist only as bytecode. Nevertheless, the structure of these proxy classes can be determined via reflection, i.e. their names, their superclasses, their implemented interfaces, their fields and methods. When a simple managed bean, such as
@SessionScoped
public
class
Service
implements
Serializable {
private
Attribute a;
@InterceptorAnnotated
public
void
doStuff() {
// doStuff
}
public
Attribute getA() {
return
a;
}
public
void
setA(Attribute a) {
this
.a = a;
}
}
is injected into another managed bean, CDI generates a proxy class with the following structure (specification of the input parameters has been omitted for the sake of clarity):
public
class
Service$Proxy$_$$_WeldClientProxy
extends
Service
implements
Serializable, WeldConstruct, WeldClientProxy, TargetInstanceProxy, LifecycleMixin, ProxyObject {
public
void
lifecycle_mixin_$$_preDestroy() { }
public
void
lifecycle_mixin_$$_postConstruct() { }
public
Object getTargetInstance() { }
public
Class getTargetClass() { }
public
Metadata getMetadata() { }
public
MethodHandler getHandler() { }
public
void
setHandler() { }
@Override
public
void
doStuff() {
// Interceptor operations, before calling proceed()
Service instance = (Service)getTargetInstance();
instance.doStuff();
// Interceptor operations, after calling proceed()
}
@Override
public
void
setA(Attribute a) { }
@Override
public
Attribute getA() { }
}
The following can be made out from the generated proxy:
- The class has the Weld generated name Service $ Proxy $ _ $$ _ WeldClientProxy and its superclass is the source class Service
- There are a lot of other interfaces that bring an equally large number of methods:
- Serializable: This serializes the proxy class instead of the source class
- WeldConstruct: A pure marker interface that displays the generation by Weld
- WeldClientProxy: The interface that is bound to each client proxy and grants access to the metadata via the getMetadata () method
- TargetInstanceProxy: Uses the getTargetInstance () method to access the correct instance of the bean, as explained in the first part
- LifecycleMixin: Fits in the @PostConstruct and @PreDestroy methods via lifecycle_mixin _ $$ _ preDestroy and lifecycle_mixin _ $$ _ postConstruct ()
- ProxyObject: Implemented by all proxy classes and enables the inclusion of a MethodHandler that intervenes in existing methods via the getHandler () and setHandler () methods
- There are no fields. This is not necessary, because the proxy can access fields of the original class via getters and setters
- The methods of the source and superclass have been overridden. Exemplary for the doStuff () method the mechanism used to find the correct instance of the bean is shown and the installation of the interceptor in the method appears (more in this article)
How does CDI generate proxies at runtime?
Weld has the ProxyFactory class at its disposal, which provides everything that was shown above, i.e. name generation, fitting in of the superclass and the necessary interfaces with the required methods and modifying the overridden methods. However, with its 946 lines, the class is so complex and difficult to integrate that we use the ProxyFactory from the javassist package for demonstration purposes:
ProxyFactory factory =
new
ProxyFactory();
factory.setSuperclass(Service.
class
);
factory.setInterfaces(
new
Class[] {Serializable.
class
});
Class<?> serviceProxyClass = factory.createClass();
serviceProxy = (Service)serviceProxyClass.newInstance();
serviceProxy.doStuff(); // execute method
The generation of a proxy at runtime is easily made possible. All you need is a ProxyFactory object that specifies the superclass “Service” for the proxy to be created and “Serializable” as an example interface. The createClass () method is then used to create the proxy class and, via newInstance (), an instance of this class is produced. It can now be addressed like a Service object while owning additional possibilities. Subsequent to this is the already mentioned MethodHandler, which is used to log the method call every time a method of the proxy is called:
MethodHandler methodHandler =
new
MethodHandler() {
@Override
public
Object invoke(Object self, Method thisMethod, Method proceed, Object[] args)
throws
Throwable {
LOGGER.info("Methode aufgerufen: " + thisMethod.getName());
return
proceed.invoke(self, args);
}
};
((ProxyObject)serviceProxy).setHandler(methodHandler);
Summary
CDI is a powerful framework that leverages the use of proxies to implement some of its key features.
If a CDI managed bean is injected into another bean in a normal scope, such a proxy is created in the background and then referenced, without the developer noticing. Beans in the Pseudo Scopes @ Singleton or in the Default Scope @Dependent, however, are referenced directly. The reasons for the use of proxies include the change of scopes when injecting beans, the non-intrusive fitting in of interceptors and the simple addition of further interfaces and methods.
In practice, CDI dynamically creates proxy classes that use their source class as a superclass and have a variety of additional interfaces and methods to meet those needs. To move within the right scope, a mechanism is used to find the right instance of the bean, interceptors are built into overridden methods.
Proxy classes are created by the ProxyFactory class of the Weld implementation, whose mechanism we could easily demonstrate using a related ProxyFactory from the javassist package.