Interview question: How many ways are Proxy Design patterns implemented in Java? This topic is like Kong Yiji asking “What are the ways of writing hui in anise beans flavored with fennel?”

The so-called Proxy mode refers to that the Client does not directly call the actual object (RealSubject in the lower right corner of the figure below), but indirectly calls the actual object by calling the Proxy.

Proxy mode is usually used when the client does not want to directly access the real object, or there is a technical barrier to access the real object, so proxy objects serve as a bridge to complete indirect access.

Implementation method 1: static proxy

Develop an interface, IDeveloper, that contains a method, writeCode, to writeCode.

public interface IDeveloper {

     public void writeCode(a);

}
Copy the code

Create a Developer class that implements this interface.

public class Developer implements IDeveloper{
    private String name;
    public Developer(String name){
        this.name = name;
    }
    @Override
    public void writeCode(a) {
        System.out.println("Developer " + name + " writes code"); }}Copy the code

Test code: Create an instance of Developer named Jerry to write code!

public class DeveloperTest {
    public static void main(String[] args) {
        IDeveloper jerry = new Developer("Jerry"); jerry.writeCode(); }}Copy the code

Now here’s the problem. Jerry’s project manager was unhappy with Jerry for writing code without maintaining any documentation. Let’s say Jerry goes on vacation one day, and some other programmer steps in to take over Jerry’s work, asking questions about unfamiliar code. After a group-wide discussion, it was decided that each developer must update the documentation as they write the code.

Instead of modifying the original Developer class, we created a new class that also implements the IDeveloper interface. This new DeveloperProxy class maintains a member variable that points to the original IDeveloper instance:

public class DeveloperProxy implements IDeveloper{
    private IDeveloper developer;
    public DeveloperProxy(IDeveloper developer){
        this.developer = developer;
    }
    @Override
    public void writeCode(a) {
        System.out.println("Write documentation...");
        this.developer.writeCode(); }}Copy the code

The proxy class implements a call to write documentation in the writeCode method before calling the actual programmer’s writeCode method, ensuring that programmers writeCode with documentation updates.

Test code:

Advantages of the static proxy approach

1. Easy to understand and implement

2. The relationship between proxy classes and real classes is determined statically at compile time and has no overhead at execution compared to dynamic proxies, which are described shortly.

Disadvantages of the static proxy approach

Each real class needs a proxy class to create a new one. Using the above document update as an example, suppose that the boss also requires the test engineer to update the corresponding test document every time a bug is detected. Using static proxies, the test engineer’s implementation class, ITester, also creates a corresponding ITesterProxy class.

public interface ITester {
    public void doTesting(a);
}
Original tester implementation class:
public class Tester implements ITester {
    private String name;
    public Tester(String name){
        this.name = name;
    }
    @Override
    public void doTesting(a) {
        System.out.println("Tester " + name + " is testing code"); }}public class TesterProxy implements ITester{
    private ITester tester;
    public TesterProxy(ITester tester){
        this.tester = tester;
    }
    @Override
    public void doTesting(a) {
        System.out.println("Tester is preparing test documentation..."); tester.doTesting(); }}Copy the code

Because of this disadvantage of the static code approach, Java’s dynamic proxy implementation was born.

Java dynamic proxy implementation method 1: InvocationHandler

InvocationHandler is the simplest introduction to Java dynamic proxy

With InvocationHandler, I can use an EnginnerProxy proxy class to proxy both Developer and Tester actions.

public class EnginnerProxy implements InvocationHandler {
    Object obj;
    public Object bind(Object obj)
    {
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable
    {
        System.out.println("Enginner writes document");
        Object res = method.invoke(obj, args);
        returnres; }}Copy the code

The writeCode and doTesting methods of real classes are executed by reflection in dynamic proxy classes.

Test output:

The limitations of implementing dynamic proxies through InvocationHandler

Suppose you have a Product Manager class that doesn’t implement any interfaces.

public class ProductOwner {
    private String name;
    public ProductOwner(String name){
        this.name = name;
    }
    public void defineBackLog(a){
        System.out.println("PO: " + name + " defines Backlog."); }}Copy the code

We still use the EnginnerProxy proxy class to proxy it, and compile without error. What happens at run time?

ProductOwner po = new ProductOwner("Ross");

ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po);

poProxy.defineBackLog();
Copy the code

Error when run. The limitation is that if the proxied class does not implement any interface, its behavior cannot be dynamically proxied through InvocationHandler.

Java dynamic proxy implementation method 2: CGLIB

CGLIB is a Java bytecode generation library that provides an easy-to-use API for creating and modifying Java bytecode. For more details about this open source library, visit CGLIB’s github repository at github.com/cglib/cglib

We are now trying to use CGLIB to delegate the ProductOwner class that failed to delegate using InvocationHandler (this class does not implement any interface).

Now I use the CGLIB API instead to create the proxy class:

public class EnginnerCGLibProxy {
    Object obj;
    public Object bind(final Object target)
    {
        this.obj = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
            {
                System.out.println("Enginner 2 writes document");
                Object res = method.invoke(target, args);
                returnres; }});returnenhancer.create(); }}Copy the code

Test code:

ProductOwner ross = new ProductOwner("Ross");

ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross);

rossProxy.defineBackLog();
Copy the code

Although ProductOwner didn’t implement any code, it was successfully proxied:

Limitations of implementing Java dynamic proxies with CGLIB

If we look at how CGLIB creates proxy classes, the limitations become clear. We now do an experiment by adding the final modifier to the ProductOwner class to make it uninheritable:

Execute the test code again, this time the error: Cannot subclass final class XXXX.

So the dynamic proxy successfully created with CGLIB is actually a subclass of the proxied class. If the proxyed class is marked final, there is no way to create a dynamic proxy using CGLIB.

Java dynamic proxy implementation 3: Dynamically create proxy classes through the API provided at compile time

Suppose we do need to create dynamic code for a ProductOwner class that is both final and does not implement any interface. In addition to InvocationHandler and CGLIB, we have one last trick:

I simply spell out the source code of a proxy class as a string, then call the JDK Compiler API based on that string, dynamically create a new.java file, and then dynamically compile the.java file to create a new proxy class.

Test success:

I put together the source code for the code class, and I dynamically created the.java file for the proxy class, and I was able to open this.java file that I created with the code in Eclipse,

Here is how to dynamically create the productPwnerscproxy.java file:

This is how the.java file is dynamically created using the JavaCompiler API, and the.class file is generated:

Here is how to load a compiled. Class file into memory using the class loader:

If you want to try out the four Proxy Design patterns described in this article, refer to my Github repository, where all the code is found. Thanks for reading.

Github.com/i042416/Jav…

For more of Jerry’s original technical articles, please follow the public account “Wang Zixi” or scan the following QR code: