Achieve memory function in Java dynamic proxy class Release date: 2004-06-14 Author: Matrix-chris Matrix memory is derived from a design pattern Lisp, Python, and Perl procedural language, it can be the result of the previous calculation Memory.
A function that implements a memory function, with explicit Cache, so the result has been calculated directly from cache without each calculation. Memory can significantly improve the efficiency of large computing amount code. And it is a reusable solution. This article describes the method of using this mode in Java, and provides a "memory class /": foo foo = (foo) Memoizer.Memoize (New FooImpl) )); Here, foo is an interface, which is a way to be remembered. FooImpl is an implementation of foo. FOO is a reference to Foo. The method is basically the same as fooImpl, the difference is that the value returned by Foo will be cached The advantage of a single memory class is that it is very simple to add a memory function for any class: Define an interface that contains methods that requires memories, then calls Memoize to implement an instance. In order to understand how the memory class is implemented, we will take a few steps. To explain. First, I explain why the cache can be implemented in the class that needs it. Then I test how to add a cache wrapper for a specific class. Finally, I explain how to make a cache wrapper can be used Any class. Adding cache / r as a large computing amount, as an example of a large computing quantity program, we consider the example of PibinaRDigitscalculator - calculate binary data Pi. Only public method CalculateBinaryDigit has a parameter: integer n, representative needs Accurate bits. For example, 10,00000, will return one million after the decimal point, return through the byte value - per bit 0 or 1.public class pinarydigitscalculator {/ *** Returns the coefficient of 2 ^ n in the CoEfficient binary * expansion of pi. * @param n the binary digit of pi to calculate. * @throws ValidityCheckFailedException if the validity * check fails, this means the implementation is buggy * or n is too large for sufficient precision to be * retained. * / Public Byte CalculateBinaryDigit (Final in T N) {RETURN RunbbPalgorithm (n);} private int n) {// length} The easiest direct method to cache the return value can be implemented by modifying this class: add one Map previously calculated values to store, as follows: import java.util.HashMap; public class PiBinaryDigitsCalculator {private HashMap cache = new HashMap (); public synchronized byte calculateBinaryDigit (final int n) {final Integer N = new Integer (n) Byte b = (byte) cache.get (n); if (b == null) {byte b = runbbpalgorithm (n); cache.put (n, new byte (b)); return b;} else {Return B.Bytevalue ();
}}}} private bote runbbpalgorithm (Final INT N) {// length}} CalculateBinaryDigit method first checks if this keyword is cached in HashMap, if you find it, return this value directly. Otherwise This lengthy calculation is performed, and the results are saved to the cache. Small conversions are required between the original type and objects when adding to HashMap. Although this method is feasible, but there are several Disadvantages. First, the cached code and normal algorithm code are not significantly separated. A class is not only responsible for calculation, but also to maintain the cache data. This way, it will be very difficult. For example, you can't write A test program to test this algorithm continuously return the same value, because from the second start, the result is obtained from cache. Secondly, when the cache code is no longer needed, remove it will become difficult because It is closely combined with the algorithm block code. So, if you want to know if the cache has brought a high efficiency improvement, it is difficult to write a test program is separated from the cache data. When you improve it Your algorithm, cache is likely to fail - but you don't know. Third, the cache code cannot be reused. Although the code follows a normal model, it is in a class - PibinaryDigitscalculator. In front of the previous two questions You can solve it by constructing a cache wrapper. Cache wrapper / r is easy to separate computing code and cache code by using Decorator mode. First, define an interface, define basic methods .public interface binarydigitscalculator {public Byte calculateBinaryDigit (final int n);} then define two implementations, are responsible for two tasks: public class PiBinaryDigitsCalculatorimplements BinaryDigitsCalculator {public byte calculateBinaryDigit (final int n) {return runBBPAlgorithm (n);} private byte runBBPAlgorithm (final int n) { // longthy routine goes here ...}} import java.util.h ashMap; public class CachingBinaryDigitsCalculator implementsBinaryDigitsCalculator {private BinaryDigitsCalculator binaryDigitsCalculator; private HashMap cache = new HashMap (); public CachingBinaryDigitsCalculator (BinaryDigitsCalculator calculator) {this.binaryDigitsCalculator = calculator;} public synchronized byte calculateBinaryDigit (int n) {final Integer N = new Integer ( n); byte b = (byte) cache.get (n); if (b == null) {byte b = binarydigitscalculator.calculatebinaryDigit (n); cache.put (n, new byte (b)); return b; } Else {return b.bytevalue ();
}}} This is a simple Refactored version that is previously implemented by PibinaRdigitscalculator. CachingBinaryDigitscalculator wrapped the BinaryDigitscalculator handle and added a cache for CalculateBinaryDigit to call. This method improves code readability and maintainability. Users cannot use the BinaryDigitscalculator interface to implement algorithms, so it will be easy to implement if you need to turn off the cache block. Also, the right test program is easy to write. For example, we write a fake binarydigitscalculator implementation, each time CalculateBinaryDigit Call, give the same parameters, return different values. In this way, we can test whether the cache is working, because if each time it returns the same value, the certificate is normal. This kind of test is in the past The implementation is impossible.
By dynamic proxy class to create a universal cache wrapper / r above the second method only disadvantage is that the cache wrapper cannot be reused. Every time we want to add a cache to a class, we must write a special cache. The wrapper gives the target interface. This is a very slow, easy error process. JDK1.3 begins to support dynamic proxy classes: Special classes can determine which interface to implement in the runtime period - the usual mode is determined in the operation period Which interface. With this, we may implement a universal cache wrapper, we call it Memoizer, which interface is implemented in the running period. This is no longer needed. It is called: binarydigitscalculator Calculator = new CachingBinaryDigitsCalculator (new PiBinaryDigitsCalculator ()); Memoizer can be rewritten as follows: BinaryDigitsCalculator calculator = (BinaryDigitsCalculator) Memoizer.memoize (new PiBinaryDigitsCalculator ()); Memoizer code is as follows: import java.lang.reflect.InvocationHandler; import java. lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util. List; import java.util.Map;.. public class Memoizer implements InvocationHandler {public static Object memoize (Object object) {return Proxy.newProxyInstance (object.getClass () getClassLoader (), object.getClass () getInterfaces (), new Memoizer object));} private Object object; private Map caches = new HashMap (); private Memoizer (Object object) {this.object = object;} public Object invoke (Object proxy, Method method, Object [] args) throws Throwable { IF (Method.getRetURNTYPE (). Equals (void.type)) {// don't cache void methodsreturn infoke (method, args);} else {map cache = getCache (Method); List key = arrays.aslist (args ); Object value = cache.get (key); if (value == null&&! Cache.containskey (key)) {value = invoke (Method, Args); cache.put (key, value);} return value; }} private Object invoke (Method method, Object [] args) throws Throwable {try {return method.invoke (object, args);} catch (InvocationTargetException e) {throw e.getTargetException ();
}}} private synchronized map getCache (MAP cache = (map) caches.get (m); if (cache == null) {cache = collections.synchronizedmap (new hashmap ()); caches.put (M, Cache);} Return Cache;}} When the static method Memoize is called, a new proxy instance will be created - is also an instance of java.lang.Reflect.Proxy. Implement an interface set. This interface is Object .getClass (). getInterfaces () is determined. Each proxy instance contains a java.lang.reflect.invocationHandler instance to handle this proxy instance call. In our example, Memoizer is an InvocationHandler instance. When a method In the proxy instance, for example, CalculateBinaryDigit, then the invoke method in the Memoizer instance will be called, and the relevant information is passed to the Invoke method to determine which method is called by the Proxy instance, containing parameter information. In our example The java.lang.method parameter that passed into the Memoizer is CalculateBinaryDigit, and the parameter information is PI requires an accurate bit number-integer n. On this basis, Memoizer can further cache operation. In the example (Caches is a HashMap Cache is a MAP that is used in key, primarily introduced method information: Method objects and parameter objects. In order to achieve simple and versatility, Memoizer has a HashMap Caches about cache, each Method is a key, The corresponding value is a cache. Then convert the parameter information into a list object, as the key of the Cache. It is very convenient to use the list, but also guarantees the equals () method, so it can guarantee that only the same as the parameter information This time will be equal. Once a cache's key is created, then you will find this cache before calculating, if you find it, return the value in cache. Otherwise, if this method with these parameters is not called, then This method will be called by Invoke. In our example, instance Pibinar The CalculateBinaryDigit method in YDIGITSCALCULATOR will be called via Invoke. And the calculation results will be in cache. When used as a general rule, Memoizer can use when any traditional cache, such as mentioned above. In particular, each of the interfaces that need to use memory function needs to meet the following conditions: