Do you understand what dynamic proxies do in Java? What are the implementation methods of dynamic proxy? What is the real implementation principle? With this chapter in mind, you won’t just know the JDK proxy interface and cglib integration subclasses, but the real implementation, when you come across the question of how to implement AOP.

This article will explain the problems that dynamic proxy technology can solve, how to design and implement dynamic proxy yourself, how it is implemented in Java, and some typical applications in open source frameworks.

What is dynamic Proxy and what can you do with it?

With Dynamic Proxy dynamic proxies, we can create implementation classes for interfaces at run time or enhance functionality (AOP functionality) for specific classes. Dynamic proxies are commonly used in framework implementations, such as Retrofit, Spring, and Dubbo, where the framework does not know in advance what a user-written class will look like. Dynamic proxies can be used to implement interfaces or enhance user classes at runtime.

Let me give you some practical examples. For example, in RetroFIT (an Http invocation framework), users define and use remote Http interfaces in the following way.

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}


Copy the code

Define the remote HTTP interface form first, annotate HTTP method, URL, parameter, return object, etc. You can then use the Retrofit class to pass in the interface to get an object to call.

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

Copy the code

The dynamic proxy technology is used to generate the implementation of this interface class. When the user calls the listRepos method, it is encapsulated as an HTTP request and the result is deserialized into the return value of the interface and returned to the user. The user does not need to be aware of these implementation details, which greatly reduces the use cost.

If you don’t use Retrofit Proxy, how do you do that?

List<Repo> listRepos(String user) throws Exception { String url = String.format("https://api.github.com/users/%s/repos",  user); OkHttpClient client = new OkHttpClient().newBuilder() .build(); Request request = new Request.Builder() .url(url) .method("GET", null) .build(); Response response = client.newCall(request).execute(); String body = response.body().string(); return JSONUtils.jsonToModel(body, List.class); }Copy the code

Developing directly with OKHTTP without Retrofit requires the user to manually spell the URL, build a Map of the request parameters, and manually deserialize the String Body into a business-class object after invoking the OKHTTP interface. This requires the consumer to understand the underlying implementation and invocation, and this additional detail adds to the cost of use. Using dynamic proxies can mask these details that can be implemented by the framework, allowing the consumer to program toward the interface and focus more on implementing the business functions rather than the underlying details.

Different implementations of dynamic proxies in Java

Let’s implement dynamic proxy, how to implement

There are several ways to implement dynamic proxies in the Java ecosystem. Before introducing each implementation, let’s think about how we would design this dynamic proxy feature.

The dynamic Proxy factory is responsible for creating the implementation class for the interface class specified by the user (the target class of the Proxy), and can let the user pass in the specified implementation (InvocationHandler). The result is an object instance of the implementation class (Proxy result class) of this interface class at runtime.

The key here is to figure out what class we want to generate and how to generate it at run time.

The first step is to define the content of the class we want to generate. The class we want to generate needs to implement the user-defined interface class and implement all the methods of that interface. The method body of the implementation calls the InvocationHandler object passed in by the user and returns the corresponding result.

For example, let’s define InvocationHandler as

interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

Copy the code

The user-defined interface class (Proxy class) is

interface Service {
    String hello();
}

Copy the code

The proxy class ProxyImpl we want to generate could be something like this, where the constructor receives the InvocationHandler object and, in a concrete method, forwards it to the InvocationHnalder object for processing.

class ProxyImpl implements Service { private static Method helloMethod; static { try { helloMethod = Service.class.getDeclaredMethod("hello", int.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } } private final InvocationHandler invocationHandler; public ProxyImpl(InvocationHandler invocationHandler) { this.invocationHandler = invocationHandler; } @Override public String hello(int arg) { try { return (String) invocationHandler.invoke(this, helloMethod, new Object[]{arg}); } catch (Throwable throwable) { throw new RuntimeException(throwable); }}}Copy the code

With the content of the class to be generated clear, the next step is how to implement it. How do I generate a concrete class at run time? Fortunately, Java’s class loading mechanism makes this possible. Java has the ability to load a class at run time. All we need to do is generate the bytecode of the class file at run time.

The class file format specification in the Java virtual machine (docs.oracle.com/javase/spec… Writing bytecode on your own is tedious and error-prone (like writing machine code). Bytecode tools such as ASM and ByteBuddy can be used to improve efficiency and reduce errors (like assembly). An alternative to bytecode is to assemble the source code and then compile it into bytecode using a Java compiler, which we can do using the Java Compiler API or Javassist.

Finally get the required class bytecode, and then use classLoader to load can get a usable class object, finally use reflection to create the corresponding class instance object back to the user.

Implementation of different dynamic proxies in Java and their respective advantages and disadvantages

Common dynamic proxies in the Java ecosystem include JDK Proxy and Cglib, which provide direct interfaces. Javassist, ASM, is a lower-level, general-purpose bytecode level manipulation tool that can also achieve peer functionality.

Implementation scheme advantages disadvantages
jdk proxy The JDK comes with its own implementation and no external dependencies are required Only proxies of interfaces can be supported
cglib Proxies that support ordinary classes and implement features such as AOP Cglib dependency is required
javassist,asm Capabilities such as framework monitoring, logging, tracing, etc. can be used at run time (via JavaAgent or ATTACH) instead of compile time At the lower level, aop needs to be developed separately

Detailed analysis of the implementation of JDK Proxy

Finally, we take JDK Proxy as an example, with the code to see the specific implementation.

JDK proxy method entry (java.lang.reflect.proxy #newProxyInstance) The proxy class instance is then obtained by passing in the InvocationHandler parameter using the reflection call Constructor.

The getProxyConstructor method calls ProxyBuilder to build the Proxy class.

The resulting function is a Java proxy class. Lang, reflect. ProxyGenerator# generateClassFile.

All generateClassFile does is spell out the corresponding bytecode array according to the bytecode format requirements of the class file we mentioned earlier.

A Method field is created for each Proxy Method, a static block of code is created to get Method, and then an implementation of the methods for each Proxy class is created.

Application of dynamic proxy in open source framework

Let’s examine the use of dynamic proxies in RetroFIT. As in the example at the beginning of this article, retrofit.create(githubService.class) generates objects for the proxy class of the GitHubService interface.

The implementation of the create method is also very simple. The classLoader uses the classLoader of the passed interface class, the interface list uses the passed interface class, The implementation of invocationHandler is invoked using the logic generated by method to call OKHTTP (special treatment is given to JDK8’s default method, which is the user’s implementation and does not require a proxy).

This article uses the article synchronization assistant to synchronize