The specific implementation of Java proxy design pattern (Proxy): static proxy and dynamic proxy

The specific implementation of Java proxy design pattern (Proxy): static proxy and dynamic proxy

  • Implementation method 1: static proxy
  • Advantages of static proxy method
  • Disadvantages of static proxy method
  • Java dynamic proxy implementation method 1: InvocationHandler
  • Java dynamic proxy implementation method 2: CGLIB
  • Limitations of Implementing Java Dynamic Proxy with CGLIB

Interview question: How many ways are there to implement the Proxy Design Pattern in Java? This topic is very similar to Kong Yiji asking, "What are the ways to write the word fennel for fennel bean?

The so-called proxy mode means that the client (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).

The use of proxy mode is generally because the client does not want to directly access the actual object, or there are technical obstacles to accessing the actual object, so the indirect access is completed through the proxy object as a bridge.

img

Implementation method 1: static proxy

Develop an interface IDeveloper, the interface contains a method writeCode, write code.

public interface IDeveloper {
    
    

     public void writeCode();

}

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() {
    
    
		System.out.println("Developer " + name + " writes code");
	}
}

Test the code: Create a Developer instance named Jerry and go write the code!

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

Now comes the question. Jerry's project manager was dissatisfied with Jerry's writing code without maintaining any documentation. Suppose that one day Jerry went on vacation, and other programmers came to take over Jerry's work, looking at the unfamiliar code with question marks. After the whole group discussion, it was decided that each developer must update the documentation synchronously when writing code.

In order to force each programmer to remember to write documentation during development without affecting the action of writing code itself, we did not modify the original Developer class, but created a new class that also implements the IDeveloper interface. This new class DeveloperProxy internally maintains a member variable pointing to the original IDeveloper instance:

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

In the writeCode method implemented by this proxy class, before calling the actual programmer's writeCode method, a call to write the document is added, which ensures that the programmer is accompanied by the document update when writing code.

Test code:

img

Advantages of static proxy method

  1. Easy to understand and implement

  2. The relationship between the proxy class and the real class is statically determined at compile time. Compared with the dynamic proxy described below, there is no additional overhead in execution.

Disadvantages of static proxy method

Every real class requires a new proxy class to be created. Taking the above document update as an example, suppose the boss also puts forward new requirements for the test engineer, so that each time the test engineer detects a bug, the corresponding test document should also be updated in time. Then using the static proxy method, the test engineer's implementation class ITester also has to create a corresponding ITesterProxy class.

public interface ITester {
    
    
	public void doTesting();
}
Original tester implementation class:
public class Tester implements ITester {
    
    
	private String name;
	public Tester(String name){
    
    
		this.name = name;
	}
	@Override
	public void doTesting() {
    
    
		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() {
    
    
		System.out.println("Tester is preparing test documentation...");
		tester.doTesting();
	}
}

It is precisely because of this shortcoming of the static code method that Java's dynamic proxy implementation method was born.

Java dynamic proxy implementation method 1: InvocationHandler

With InvocationHandler, I can use an EngineProxy proxy class to proxy both Developer and Tester behavior.

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);
		return res;
	}
}

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

Test output:

img

Limitations of dynamic proxy through InvocationHandler

Suppose there is a product manager class (ProductOwner) that does not implement any interface.

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

We still take the EngineProxy proxy class to proxy it, and compile without error. What happens at runtime?

ProductOwner po = new ProductOwner("Ross");

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

poProxy.defineBackLog();

Error when running. So the limitation is: if the class to be proxied does not implement any interface, its behavior cannot be proxied by dynamic proxying through InvocationHandler.

img

Java dynamic proxy implementation method 2: CGLIB

CGLIB is a Java bytecode generation library that provides an easy-to-use API to create and modify Java bytecodes. For more details about this open source library, please go to the CGLIB repository on github: https://github.com/cglib/cglib

We now try to use CGLIB to proxy the ProductOwner class (which does not implement any interface) that was unsuccessfully proxied with InvocationHandler before.

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);
				return res;
			}
		}
		);
		return enhancer.create();
	}
}

Test code:

ProductOwner ross = new ProductOwner("Ross");

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

rossProxy.defineBackLog();

Although ProductOwner does not implement any code, it is also successfully proxied:

img

Limitations of Implementing Java Dynamic Proxy with CGLIB

If we understand the principle of CGLIB to create proxy classes, then its limitations will be clear at a glance. Let's do an experiment now and add the final modifier to the ProductOwner class to make it non-inheritable:

img

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

Therefore, the dynamic proxy successfully created by CGLIB is actually a subclass of the proxy class. Then if the proxy class is marked as final, it is impossible to create a dynamic proxy through CGLIB.

Java dynamic proxy implementation mode 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 interfaces. In addition to InvocationHandler and CGLIB, we have a last resort:

I directly spell out the source code of a proxy class with a string, and then call the JDK's Compiler (compile-time) API based on this string, dynamically create a new .java file, and then dynamically compile the .java file, which also Can get a new proxy class.

img

test was successful:

img

I have assembled the source code of the code class, dynamically created the .java file of the proxy class, and can open the .java file created with the code in Eclipse,

img

img

The following figure is how to dynamically create the ProductPwnerSCProxy.java file:

img

The following figure shows how to use the JavaCompiler API to dynamically compile the .java file dynamically created in the previous step to generate a .class file:

img

The following figure is how to use the class loader to load the compiled .class file into memory:

img

Guess you like

Origin blog.csdn.net/qq_43842093/article/details/123930019