This article brings you an introduction to the four implementation methods of static agents and dynamic agents in Java. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
Interview question: How many implementation methods are there for the Proxy Design Pattern in Java? This question is very similar to Kong Yiji's question, "What are the ways to write the word fennel for fennel beans?"
The so-called proxy mode means that the client does not directly call the actual object (the one in the lower right corner of the picture below) RealSubject), but indirectly calls the actual object by calling the proxy (Proxy).
The proxy mode is generally used because the client does not want to directly access the actual object, or there are technical obstacles to accessing the actual object, so the proxy object is used as a bridge to complete indirect access.
Implementation method one: static proxy
Develop an interface IDeveloper, which contains a method writeCode ,write the code.
public interface IDeveloper { public void writeCode(); }
Create a Developer class to implement 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 code: Create a Developer instance named Jerry and write code!
public class DeveloperTest { public static void main(String[] args) { IDeveloper jerry = new Developer("Jerry"); jerry.writeCode(); } }
Now comes the problem. Jerry's project manager was very dissatisfied that Jerry only wrote code without maintaining any documentation. Suppose Jerry goes on vacation one day, and other programmers come to take over Jerry's work, looking at the unfamiliar code with question marks on their faces. After discussion among the whole group, it was decided that when each developer writes code, the documentation must be updated simultaneously.
In order to force every programmer to remember to write documents when developing without affecting the action of writing code itself, we do not modify the original Developer class, but create a new class and implement the same IDeveloper interface. This new class DeveloperProxy maintains a member variable internally, 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. This ensures that programmers are accompanied by updated documentation when writing code.
Test code:
Advantages of static proxy method
1. Easy Understanding and Implementation
2. The relationship between the proxy class and the real class is determined statically during compilation. Compared with the dynamic proxy introduced immediately below, there is no additional overhead during execution.
Disadvantages of the 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 test engineers, asking test engineers to update the corresponding test documents in a timely manner every time they detect a bug. Then using the static proxy method, the test engineer's implementation class ITester must also 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
I have written an article specifically to introduce the principle of InvocationHandler: The simplest introductory tutorial for Java dynamic proxy InvocationHandler
Through InvocationHandler, I can use an EnginnerProxy proxy class to proxy the behavior of Developer and Tester at the same time.
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 through reflection in the dynamic proxy class.
Test output:
Limitations of dynamic proxy implementation through InvocationHandler
Assume there is a product manager class (ProductOwner) 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 use the EnginnerProxy proxy class to proxy it, and there will be no errors during compilation. What happens at runtime?
ProductOwner po = new ProductOwner("Ross"); ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po); poProxy.defineBackLog();
An error occurs when running. So the limitation is: if the proxied class does not implement any interface, then you cannot proxy its behavior through InvocationHandler dynamic proxy.
Java dynamic proxy implementation method two: CGLIB
CGLIB is a Java bytecode generation library , provides an easy-to-use API to create and modify Java bytecode. For more details about this open source library, please go to CGLIB's repository on github: https://github.com/cglib/cglib
We are now trying to use CGLIB to proxy before using InvocationHandler without success. ProductOwner class (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); return res; } } ); return enhancer.create(); } }
Test code:
ProductOwner ross = new ProductOwner("Ross"); ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross); rossProxy.defineBackLog();
尽管ProductOwner未实现任何代码,但它也成功被代理了:
用CGLIB实现Java动态代理的局限性
如果我们了解了CGLIB创建代理类的原理,那么其局限性也就一目了然。我们现在做个实验,将ProductOwner类加上final修饰符,使其不可被继承:
再次执行测试代码,这次就报错了: Cannot subclass final class XXXX。
所以通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。那么如果被代理类被标记成final,也就无法通过CGLIB去创建动态代理。
Java动态代理实现方式三:通过编译期提供的API动态创建代理类
假设我们确实需要给一个既是final,又未实现任何接口的ProductOwner类创建动态代码。除了InvocationHandler和CGLIB外,我们还有最后一招:
我直接把一个代理类的源代码用字符串拼出来,然后基于这个字符串调用JDK的Compiler(编译期)API,动态的创建一个新的.java文件,然后动态编译这个.java文件,这样也能得到一个新的代理类。
测试成功:
我拼好了代码类的源代码,动态创建了代理类的.java文件,能够在Eclipse里打开这个用代码创建的.java文件,
下图是如何动态创建ProductPwnerSCProxy.java文件:
下图是如何用JavaCompiler API动态编译前一步动态创建出的.java文件,生成.class文件:
下图是如何用类加载器加载编译好的.class文件到内存:
如果您想试试这篇文章介绍的这四种代理模式(Proxy Design Pattern), 请参考我的github仓库,全部代码都在上面。感谢阅读。
https://github.com/i042416/Ja...
The above is the detailed content of Introduction to four implementation methods of static proxy and dynamic proxy in Java. For more information, please follow other related articles on the PHP Chinese website!