Preface
"Dynamic Agent" actually originates from the proxy mode in the design mode, and the proxy mode uses the proxy object to complete user requests and blocks users' access to real objects.
To give the simplest example, we want to "FQ" to access foreign websites, because we do not have all foreign IPs, you can send your request datagram to those foreign hosts that are not blocked, and then you forward the request to the destination by configuring the foreign host and forward it back to our domestic host after receiving the response message.
In this example, a foreign host is a proxy object, and those hosts that are dropped by the wall are real objects. We cannot directly access the real objects, but we can indirectly access it through a proxy.
One advantage of the proxy mode is that all external requests pass through the proxy object, and the proxy object has the right to control whether you are allowed to truly access the real object. If there is an illegal request, the proxy object can completely reject you without actually trouble with the real object.
One of the most typical applications of the proxy mode is the Spring framework. Spring's AOP uses aspect-oriented programming to isolate the actual business logic from related log exceptions and other information. Every time you request the business logic, it corresponds to a proxy object. In addition to performing necessary permission checks and log printing, this proxy object is the real business logic processing block.
Static proxy
There are two main implementers of the proxy model, "static proxy" and "dynamic proxy". The essential difference between these two is that the former proxy class requires manual encoding by programmers, while the latter proxy class is automatically generated. So, this is why you have hardly heard of the concept of "static proxy". Of course, it is naturally easier to understand "dynamic proxy".
One thing you need to be clear is that the proxy object proxies all methods of the real object, that is, the proxy object needs to provide at least the same method name as the real object for call, so a proxy object needs to define all methods owned by the real object, including methods in the parent class.
Let's look at a simple static proxy example:
To illustrate the problem, we define an IService interface and let our real class inherit and implement the interface, so that there are two methods in our real class.
So how should a proxy class be defined to complete the proxy for real objects?
Generally speaking, the essence of a proxy class is to define all methods in the real class and add some other operations inside the method, and finally call the method of the real class.
The proxy class needs to proxy all methods in the real class, that is, methods that are exactly the same as those methods in the real class, and the method of the real class will be indirectly called inside these methods.
So generally speaking, the proxy class will choose to directly inherit all interfaces and parent classes of the real class in order to get all parent methods signatures of the real class, that is, to first proximate all parent methods.
Next, the proxy is not a parent method in the real class. In the example here, the doService method is the real class's own method. Our proxy class also needs to define a method with the same method signature to proxy it.
In this way, even if our proxy class is completed, all methods in the real class can be proxyed through the proxy class in the future. Like this:
public static void main(String[] args){ realClass realClass = new realClass(); ProxyClass proxyClass = new ProxyClass(realClass); proxyClass.sayHello(); proxyClass.doService();}proxyClass, as a proxy class object, can proxy all methods in the real class, and print some "insignificant" information before these methods are executed.
This is basically the basic implementation idea of the proxy model, but the difference between dynamic proxy and this kind of static proxy is that dynamic proxy does not require our method definitions one by one, and the virtual machine will automatically generate these methods for you.
JDK dynamic proxy mechanism
What distinguishes dynamic proxy from static proxy is that the proxy class of dynamic proxy is dynamically created by the virtual machine at runtime and cleared when the virtual machine is uninstalled.
We reuse the classes used in the above static proxy to see how the dynamic proxy of JDK can proximate all methods of an instance of a certain class.
Define a Handler processing class:
The dynamic proxy API in the Main function calls JDK to generate a proxy class instance:
There are still quite a lot of code involved, let's analyze it bit by bit. First, realClass implements interface IService as our proxy class and defines its own method doService internally.
Next, we define a processing class that inherits the interface InvocationHandler and implements its uniquely declared invoke method. In addition, we have to declare a member field to store real objects, that is, proxy objects, because any method we proxy is basically based on relevant methods of real objects.
Regarding the role of this invoke method and the significance of various formal parameters, we will conduct a detailed analysis when we reflect the proxy source code.
Finally, define our processing class and basically perform dynamic proxy based on JDK. The core method is the newProxyInstance method of the Proxy class. This method has three parameters. One is a class loader, the second is a collection of all interfaces implemented by the proxy class, and the third is our customized processor class.
The virtual machine uses the class loader you provide at runtime, loads all specified interface classes into the method area, and then reflects and reads the methods in these interfaces and combines the processor class to generate a proxy type.
The last sentence may be a bit abstract. How to "combined with processor classes to generate a proxy type"? In this regard, we specify the virtual machine startup parameters and let it save the generated Class file of the proxy class.
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
We decompile this Class file through third-party tools, and there is a lot of content. We split the analysis:
First of all, the name of this proxy class is very random. If there are multiple proxy classes to be generated in a program, "$Proxy + number" is their class name.
Next, you will notice that this proxy class inherits the Proxy class and the interface IService we specified (previously if multiple interfaces were specified, multiple interfaces would be inherited here).
Then you will find that this constructor requires an InvocationHandler type parameter, and the body of the constructor is to pass this InvocationHandler instance to the corresponding field of the parent class Proxy for storage. This is also one of the reasons why all proxy classes must use Proxy as the parent class, which is to publicize the InvocationHandler field in the parent class. We will know later that this small design will lead to a fatal disadvantage of JDK-based dynamic proxy, which will be introduced later.
This content is also a relatively important part of the proxy class. It will be executed when the virtual machine statically initializes the proxy class. This large piece of code completes the function of reflecting all methods in the interface, and all reflected methods are stored corresponding to a field of Method type.
In addition, the virtual machine also reflects three common methods in Object, that is, the proxy class will also proxy the real object inherited from the Object.
In the last part, what we see is that the virtual machine reflects all the methods to be proxyed based on the static initialization code block and generates proxy methods for them.
These methods look like a lot of code, but in fact they are just one line of code, which takes out the processor class stored during instantiation from the parent class Proxy and calls its invoke method.
The parameters of the method are basically the same. The first parameter is the current proxy class instance (it proves that it is useless to pass this parameter in the past), the second parameter is the Method method instance, and the third parameter is the formal parameter set of the method. If not, it is null.
Now let’s take a look at the customized processor class:
All proxy class methods will call the invoke method of the processor class and pass in the current method of the proxy class. This invoke method can choose to make the method be called normally, or skip the method call, and even do some extra things before and after the method is actually called.
This is the core idea of JDK dynamic proxy. Let’s briefly summarize the entire calling process.
First of all, the definition of a processor class is essential, and it must be associated with a real object, that is, the proxy class instance.
Next, we call any method of the proxy class from the outside, and from the decompiled source code, we know that the proxy class method will instead call the processor's invoke method and pass in the method signature and method formal parameter set.
Finally, whether the method can be called normally depends on whether the processor invoke method body actually calls the method method.
In fact, dynamic proxy implemented based on JDK is flawed, and these defects are not easy to fix, so CGLIB is popular.
Some defects and deficiencies
Single proxy mechanism
I don't know if you noticed that the above examples are not available. The proxy class generated by the virtual machine inherits the Proxy class in order to publicly store its own processor class instances. What does that mean?
Java's single root inheritance tells you that the proxy class can no longer inherit any other class, so the methods in the parent class of the proxy class will naturally be unable to obtain, that is, the proxy class cannot proxy any methods of the parent class in the real class.
Apart from this is another small detail. I wonder if you have noticed it. I wrote it like this.
The sayHello method here is the interface IService implemented, while the doService method is a method that belongs to realClass' own. But we do not see this method from the proxy class, which means that this method is not proxyed.
Therefore, the dynamic proxy mechanism of JDK is single, and it can only proxy methods in the interface collection of the proxy class.
Unfriendly return value
Please note that newProxyInstance returns an instance of the proxy class "$Proxy0", but it is returned as the Object type, and you cannot force the Object instance to the "$Proxy0" type.
Although we know that this Object instance is actually the "$Proxy0" type, the "$Proxy0" type does not exist during the compilation period, and the compiler will naturally not allow you to force it to a non-existent type. Therefore, it will generally only force it to be one of the interfaces implemented by this proxy class.
realClass rc = new realClass();MyHanlder hanlder = new MyHanlder(rc);IService obj = (IService)Proxy.newProxyInstance( rc.getClass().getClassLoader(), new Class[]{IService.class}, hanlder);obj.sayHello();Program run output:
proxy beginning......hello world...proxy ending......
Then the question comes again. If our proxy class implements multiple interfaces, which interface type should you force it to? Now, assuming that the proxy class implements interfaces A and B, then if the last instance is forced to A, you naturally cannot call all the methods in interface B implemented by the proxy class, and vice versa.
This directly leads to a result. You have to know which method is in which interface. If you force it to the corresponding interface before calling a method, it is quite unfriendly.
The above is what we think is not elegant in the dynamic proxy mechanism based on JDK. Of course, its advantages are definitely greater than these disadvantages. In the next article, we will introduce a CGLIB dynamic proxy library widely used by various frameworks. Its underlying layer is based on the bytecode operation framework ASM and no longer relies on inheritance to implement it, perfectly solving the shortcomings of JDK's single proxy.
All codes, images, and files in the article are stored in the cloud on my GitHub:
(https://github.com/SingleYam/overview_java)
You can also choose to download locally.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.