Java Theory and Practice: Using Dynamic proxies for Modification

Source: Internet
Author: User

Introduction: The dynamic proxy tool is java. lang. add a part of the reflect package to JDK 1.3, which allows the program to create proxy objects. The proxy object can implement one or more known interfaces and use reflection to replace the built-in virtual method assignment, programmatically calls the method of the peer interface. This process allows you to call "intercept" methods, reroute them, or dynamically add functions. In this article, Brian Goetz introduced several applications for dynamic proxy. Please share your thoughts on this article with the author and other readers on the forum that is accompanied by this article.
Dynamic Proxy provides an alternative dynamic mechanism for implementing many common design patterns (including Facade, Bridge, Interceptor, Decorator, Proxy (including remote and virtual Proxy), and Adapter mode. Although these modes do not use dynamic proxies, they can be implemented only by using common classes, but in many cases, the dynamic proxy method is more convenient and more compact, and can clear many handwritten or generated classes.
Proxy Mode
In Proxy mode, you need to create "stub" or "surrogate" objects to accept the requests and forward the requests to other objects that actually execute the work. Remote method call (RMI) uses the Proxy mode, so that the objects executed in other JVMs are like local objects; Enterprise JavaBeans (EJB) use Proxy mode to add remote calls, security, and transaction boundaries, while JAX-RPC Web services use Proxy mode to make remote services behave like local objects. In each case, the potential behavior of remote objects is defined by the interface, and the interface essentially accepts multiple implementations. Callers (in most cases) cannot differentiate that they only hold a reference to stub rather than the actual object, because they implement the same interface; the job of stub is to find the actual object, mail parameters, send parameters to the actual object, unban the MAIL return value, and return the return value to the caller. Proxy can be used to provide remote control (as in RMI, EJB, and JAX-RPC), wrap objects (ejbs) with security policies, for expensive objects (EJB entity beans) provides inert loading or adding detection tools (such as logging ).
In JDK earlier than 5.0, RMI stub (and its equivalent skeleton) is a class generated by RMI Compiler (rmic) during compilation, and RMI compiler is part of the JDK tool set. A stub (proxy) class is generated for each remote interface. It represents a remote object and also generates a skeleton object, it does the opposite in the remote JVM: unblocking parameters and calling the actual object. Similarly, JAX-RPC tools for Web Services also generate proxy classes for remote Web services, making remote Web services look like local objects.
No matter whether stub class is generated by source code or byte code, code generation will still add some additional steps to the compilation process, and the flooding of similar classes will lead to the possibility of fuzzy meaning. On the other hand, the dynamic proxy mechanism supports creating proxy objects at runtime without generating stub classes during compilation. In JDK 5.0 and later versions, the RMI tool replaces the generated stub with a dynamic proxy, and RMI becomes easier to use. Many J2EE containers also use dynamic proxies to implement EJB. The EJB technology relies heavily on interception to achieve security and transaction boundaries. Dynamic proxies provide a centralized control flow path for all methods called on interfaces.
Dynamic proxy mechanism
The core of the dynamic proxy mechanism is the InvocationHandler interface, as shown in Listing 1. The call handle function indicates that the dynamic proxy actually executes the requested method call. A Method object (from java. lang. in the reflect package), the parameter list is passed to the Method; in the simplest case, it may be just to call the reflective Method. invoke () and return the result.
 
Public interface InvocationHandler {
Object invoke (Object proxy, Method method, Object [] args)
Throws Throwable;
}
Each proxy has a call handle associated with it, which is called as long as the proxy method is called. According to the general design principle: interface definition type and class definition implementation, proxy objects can implement one or more interfaces, but cannot implement classes. Because the proxy classes do not have accessible names and they cannot have constructors, they must be created by the factory. Listing 2 shows the simplest possible implementation of dynamic proxy. It implements the Set interface and distributes all the Set methods (and all Object methods) to the encapsulated Set instance.
Listing 2. Simple Dynamic proxy for packaging Set
 
 
Public class SetProxyFactory {
Public static Set getSetProxy (final Set s ){
Return (Set) Proxy. newProxyInstance
(S. getClass (). getClassLoader (),
New Class [] {Set. class },
New InvocationHandler (){
Public Object invoke (Object proxy, Method method,
Object [] args) throws Throwable {
Return method. invoke (s, args );
}
});
}
}
 
The SetProxyFactory class contains a static factory method getSetProxy (), which returns a dynamic proxy that implements Set. The proxy object actually implements Set -- the caller cannot distinguish (unless through reflection) the returned object is a dynamic proxy. The proxy returned by SetProxyFactory only does one thing and assigns the method to the Set instance passed to the factory method. Although the reflected code is usually hard to read, there is little content here and it is not difficult to keep up with the control process-as long as a method is called on the Set proxy, it is assigned to the call handle, the call handle only calls the target method on the underlying encapsulated object through reflection. Of course, a proxy that never does anything may be a bit silly, right?
An adapter that does nothing
For posters like SetProxyFactory that do nothing, there is actually a good application-it can be used to safely narrow the scope of object reference to a specific interface (or interface set, the method is that the caller cannot upgrade the reference type, so that the object reference can be passed to untrusted code more securely (such as plug-ins or callbacks ). Listing 3 contains a set of class definitions that implement typical callback scenarios. From this, we can see that the dynamic proxy can more easily replace the Adapter mode that is usually implemented by hand (or by using the code generation Wizard provided by IDE.
Listing 3. Typical callback scenarios
 
 
Public interface ServiceCallback {
Public void doCallback ();
}
Public interface Service {
Public void serviceMethod (ServiceCallback callback );
}
Public class ServiceConsumer implements ServiceCallback {
Private Service service;
...
Public void someMethod (){
...
Service. serviceMethod (this );
}
}
 
The ServiceConsumer class implements ServiceCallback (which is usually a convenient way to support callback) and passes this reference to serviceMethod () as the callback reference. The problem with this method is that there is no mechanism to prevent the Service implementation from promoting ServiceCallback to ServiceConsumer and calling the method that ServiceConsumer does not want the Service to call. Sometimes you don't care about this risk-but sometimes you do. If you are concerned, you can use the callback object as an internal class, or write an adapter class that does nothing (see ServiceCallbackAdapter in Listing 4) and package ServiceConsumer with ServiceCallbackAdapter. ServiceCallbackAdapter prevents the Service from promoting ServiceCallback to ServiceConsumer.
Listing 4. An adapter class used to safely restrict objects to an interface so that they are not blocked by malicious code
 
 
Public class ServiceCallbackAdapter implements ServiceCallback {
Private final ServiceCallback cb;
Public ServiceCallbackAdapter (ServiceCallback cb ){
This. cb = cb;
}
Public void doCallback (){
Cb. doCallback ();
}
}
 
Compiling an adapter class like ServiceCallbackAdapter is simple but boring. The redirection class must be written for each method in the encapsulated interface. In the ServiceCallback example, there is only one method to implement, but some interfaces, such as Collections or JDBC interfaces, contain many methods. Modern IDE provides the "Delegate Methods" Wizard, which reduces the workload of writing the adapter class, but you still have to write an adapter class for each interface you want to wrap, in addition, there are some unsatisfactory aspects for classes that only contain generated code. It seems that there should be a way to more compact the expression of "no limit on adapter mode ".
Generic adapters
The SetProxyFactory class in Listing 2 is certainly more compact than the equivalent adapter class for Set, but it still applies only to one interface: Set. However, by using generics, you can easily create a common proxy factory that does the same work for any interface, as shown in listing 5. It is almost the same as SetProxyFactory, but can be applied to any interface. Now you no longer need to write the restriction adapter class! If you want to create a proxy object to safely restrict the object to interface T, you only need to call getProxy (T. class, object), without the additional burden of a bunch of adapter classes.
Listing 5. Generic restriction Adapter Factory class
 
Public class GenericProxyFactory {
Public staticT getProxy (Classintf,
Final T obj ){
Return (T)
Proxy. newProxyInstance (obj. getClass (). getClassLoader (),
New Class [] {intf },
New InvocationHandler (){
Public Object invoke (Object proxy, Method method,
Object [] args) throws Throwable {
Return method. invoke (obj, args );
}
});
}
}
Dynamic proxy as a Decorator
Of course, what dynamic proxy tools can do is far from limiting the object type to a specific interface. The simple restriction adapter from List 2 and List 5 to Decorator mode is a small leap. In Decorator mode, agents use additional features (such as security detection or logging) package the call. Listing 6 shows a log InvocationHandler. In addition to calling methods on the target object, it also writes a log to display the called methods, transmitted parameters, and returned values. Except for reflective invoke () calls, all the code here is only part of the debugging information generated-not too many. The code of the proxy factory method is almost the same as that of GenericProxyFactory. The difference is that it uses LoggingInvocationHandler instead of an anonymous call handle.
Listing 6. Proxy-based Decorator, which generates debugging logs for each method call
 
 
Private static class LoggingInvocationHandler
Implements InvocationHandler {
Final T underlying;
Public LoggingHandler (T underlying ){
This. underlying = underlying;
}
Public Object invoke (Object proxy, Method method,
Object [] args) throws Throwable {
StringBuffer sb = new StringBuffer ();
Sb. append (method. getName (); sb. append ("(");
For (int I = 0; args! = Null & I}
System. out. println (sb );
Return ret;
}
}
 
If HashSet is packaged with a log proxy and the following simple test program is executed:
 
Set s = newLoggingProxy (Set. class, new HashSet ());
S. add ("three ");
If (! S. contains ("four "))
S. add ("four ");
System. out. println (s );
The following output is displayed:
Add (three)-> true
Contains (four)-> false
Add (four)-> true
ToString ()-> [four, three]
[Four, three]
This method is a good and easy way to add a debugging package to the object. Of course, it is much easier (and more common) than generating proxy classes and manually creating a large number of println () statements ). I have further improved this method. Instead of generating debugging output unconditionally, the agent can query the dynamic configuration storage (initialized from the configuration file, which can be dynamically modified by JMX MBean ), determine whether debugging statements need to be generated, or even on the basis of classes or instances one by one.
At this point, I think the readers of the AOP fans will almost jump out and say, "This is exactly what AOP is good !" Yes, but there are more than one solution to the problem-simply because a technology can solve a problem doesn't mean it is the best solution. Under any circumstances, dynamic proxy has the advantage of working completely in the scope of "pure Java", not every company uses (or should use) AOP.
Dynamic proxy as an adapter
The proxy can also be used as a real adapter to provide a view of the object and export interfaces different from those implemented by the underlying object. The call handle does not need to assign each method call to the same underlying object. It can check the name and assign different methods to different objects. For example, assume that there is a set of JavaBean interfaces that indicate persistent entities (Person, Company, and PurchaseOrder), specify the getter and setter of the attribute, and write a persistence layer, map database records to objects that implement these interfaces. Currently, you do not need to write or generate classes for each interface. You can use only one universal proxy class of the JavaBean style to save the attributes in Map.
The dynamic proxy shown in listing 7 checks the name of the called method and directly implements the getter and setter methods by querying or modifying the attribute graph. Now, this proxy class can implement the objects of multiple JavaBean-style interfaces.
Listing 7. Dynamic proxy class used to assign getter and setter to Map
 
 
Public class JavaBeanProxyFactory {
Private static class JavaBeanProxy implements InvocationHandler {
Map properties = new HashMap ();
Public JavaBeanProxy (Mapproperties ){
This. properties. putAll (properties );
}
Public Object invoke (Object proxy, Method method,
Object [] args)
Throws Throwable {
String meth = method. getName ();
If (meth. startsWith ("get ")){
String prop = meth. substring (3 );
Object o = properties. get (prop );
If (o! = Null &&! Method. getReturnType (). isInstance (o ))
Throw new ClassCastException (o. getClass (). getName () +
"Is not a" + method. getReturnType (). getName ());
Return o;
}
Else if (meth. startsWith ("set ")){
// Dispatch setters similarly
}
Else if (meth. startsWith ("is ")){
// Alternate version of get for boolean properties
}
Else {
// Can dispatch non get/set/is methods as desired
}
}
}
Public staticT getProxy (Classintf,
Map values ){
Return (T) Proxy. newProxyInstance
(Javanproxyfactory. class. getClassLoader (),
New Class [] {intf}, new JavaBeanProxy (values ));
}
}
 
Although reflection may cause potential loss of type security when working on objects, getter processing in JavaBeanProxyFactory performs some additional type detection, this is just like how I use isInstance () to detect getter.
Performance cost
As you can see, dynamic proxies have the potential to simplify a large amount of Code-not only can replace a lot of generated code, but also a single proxy class can replace multiple handwritten classes or generated code. What is the cost? Because of the reflection distribution method rather than the built-in virtual method, there may be some performance costs. In early JDK, reflection performance was poor (just like the performance of almost everything in early JDK), but reflection has become much faster in the last 10 years.
Without having to enter the topic of the benchmark test structure, I wrote a simple and unscientific test program that cyclically fills data into the Set, insert, query, and delete elements in a Set randomly. I use three sets to run it: An undecorated HashSet, a handwritten Set adapter that forwards all the methods to the underlying HashSet, there is also a proxy-based Set adapter that only forwards all methods to the underlying HashSet. Each iteration generates several random numbers and executes one or more Set operations. Hand-written adapters generate only a small percentage of performance load compared to the original HashSet (probably because of JVM-level effective inline buffering and hardware-level branch prediction); proxy adapters are significantly slower than the original HashSet, however, the overhead is less than two levels.
I have come to the conclusion from this experiment that, in most cases, the proxy method performs well even for lightweight methods, and as the operations being processed by the proxy become increasingly heavyweight (such as remote method calls, or use serialization, IO execution, or data retrieval methods from the database), the agent overhead is effectively close to 0. Of course, there are some situations where the performance overhead of the proxy method is unacceptable, but these are usually only a few cases.
Conclusion
Dynamic Proxy is a powerful but underutilized tool that can be used to implement many design patterns, including Proxy, Decorator, and Adapter. These modes are easy to write based on proxies, making them more difficult to make mistakes and more universal. In many cases, a dynamic Proxy class can act as the Decorator or Proxy of all interfaces, in this way, you do not need to write a static class for each interface. In addition to applications that focus most on performance, dynamic proxy may be more desirable than hand-writing or machine-generated stub.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.