Summary: Starting with this article, we will continue the second in the Kotlin Encounters Design patterns series on proxy patterns. The proxy pattern can be described as a design pattern confused by many entry-level and mid-level developers. But it does have a wide range of applications, not to mention the very familiar Retrofit framework, which uses the dynamic proxy design pattern internally to annotate network request parameter passing to allow for greater decoupling. However, there is a naturally supported attribute proxy syntax feature in Kotlin that simplifies the template proxy for proxy pattern implementations in Java.

Introduce a,

Proxy Pattern, also known as the delegate Pattern, just as the name implies that the implementation of an object is delegated to another Proxy object for implementation for external invocation.

Second, the definition of

Provide a proxy for other objects to control access to an object, thereby ensuring that the object is more transparent for external use.

Three, basic requirements

  • 1. The delegate object (or proxied object) needs to implement the same interface as the proxy object.
  • 2. The actual delegate object reference is preserved in the proxy object, and the external invoked operation or behavior is internally handed over to the actual delegate object.
  • For internal concealment, external callers communicate directly with their common interfaces.

Three, use scenarios

You can access an object indirectly through a proxy object when you cannot or do not want to access an object directly or when it is difficult to access an object. Proxies can implement method enhancements such as common logging, caching, and so on; Method interception can also be implemented by modifying the parameters and return values of the original method through proxy methods

4. UML class diagram

The agency model is very common in life. Since my colleagues are discussing buying a house recently, I will take the housing agency as an example to introduce our agency model today. First we need to visually represent the idea of the proxy pattern using UML class diagrams.

As can be seen from the above UML class diagram, there are four main roles involved:

  • 1. Client: The Client class, which can be regarded as an outsider of proxy mode invocation
  • IPurchaseHouse: abstract house buying interface. The main responsibility of this interface is to declare the common interface method of HouseOwner(actual HouseOwner) and HouseAgent (real estate agent). This class can be an interface or abstract class
  • 3. HouseOwner: that is, the actual entrusted object or proxy-object in the proxy mode. The external caller Client class indirectly calls the methods defined in the actual entrusted object through the proxy object (intermediary)
  • 4. HouseAgent: The real estate agent, which is also the proxy object in the proxy mode. This class holds a real HouseOwner reference and calls the HouseOwner method in the interface method of the proxy class to achieve the proxy function.

Static proxy

1. Java implements static proxy

Static proxies are relatively simple to implement in Java, as defined by the analysis role rules in UML above. Here we use Java to implement the above example:

//IPurchaseHouse: abstract purchase interface
interface IPurchaseHouse {
    void inquiryPrice(a);/ / inquiry

    void visitHouse(a);/ / the checking

    void payDeposit(a);/ / to pay a deposit

    void signAgreement(a);/ / sign the contract

    void payMoney(a);/ / pay

    void getHouse(a);/ / room
}

//HouseOwner:
class HouseOwner implements IPurchaseHouse {// Implement IPurchaseHouse common interface
    @Override
    public void inquiryPrice(a) {
        System.out.println("HouseOwner put forward the house price: 200W RMB");
    }

    @Override
    public void visitHouse(a) {
        System.out.println("The HouseOwner agrees to let the buyer look at the house.");
    }

    @Override
    public void payDeposit(a) {
        System.out.println("HouseOwner received RMB 10,000 deposit from the buyer.");
    }

    @Override
    public void signAgreement(a) {
        System.out.println("The HouseOwner signs a contract with the HouseOwner.");
    }

    @Override
    public void payMoney(a) {
        System.out.println("The homeowner pays the HouseOwner.");
    }

    @Override
    public void getHouse(a) {
        System.out.println("The buyer gets the house."); }}//HouseAgent
class HouseAgent implements IPurchaseHouse {
    private IPurchaseHouse mHouseOwner;// HouseOwner is referenced by the proxy object

    public HouseAgent(IPurchaseHouse houseOwner) {
        mHouseOwner = houseOwner;
    }

    @Override
    public void inquiryPrice(a) {
        mHouseOwner.inquiryPrice();// Call inquiryPrice with the HouseOwner reference
    }

    @Override
    public void visitHouse(a) {
        mHouseOwner.visitHouse();// Call visitHouse with the HouseOwner reference
    }

    @Override
    public void payDeposit(a) {
        mHouseOwner.payDeposit();// Call payDeposit with the HouseOwner reference
    }

    @Override
    public void signAgreement(a) {
        mHouseOwner.signAgreement();// Call signAgreement with the specific HouseOwner reference
    }

    @Override
    public void payMoney(a) {
        mHouseOwner.payMoney();// Call payMoney with the specific HouseOwner reference
    }

    @Override
    public void getHouse(a) {
        mHouseOwner.getHouse();// Call getHouse with the HouseOwner reference}}/ / Client customer class
class Client {
    public static void main(String[] args) {
        IPurchaseHouse houseOwner = new HouseOwner();
        IPurchaseHouse houseAgent = new HouseAgent(houseOwner);// Pass in an instance of the proxied class
        houseAgent.inquiryPrice();// Ask about the price
        houseAgent.visitHouse();/ / the checking
        houseAgent.payDeposit();// Pay a deposit
        houseAgent.signAgreement();/ / sign the contract
        houseAgent.payMoney();/ / pay
        houseAgent.getHouse();/ / room}}Copy the code

Running results:

The HouseOwner suggested the price of the house: The HouseOwner agrees the HouseOwner to come to see the house and receives the deposit of the HouseOwner. The HouseOwner signs a contract with the HouseOwner and the HouseOwner pays the HouseOwner to get the houseexit code 0
Copy the code

This is how static proxies are implemented, and some may not see the benefit of the proxy pattern, looking as if the proxy class did the actual forward call. In fact, there is an obvious advantage: you can insert specific operations or behaviors throughout the process in the HouseAgent class without affecting the implementation of the internal HouseOwner, protecting the internal implementation. Another advantage is that the proxy class can extend other behaviors while preserving the HouseOwner core functionality.

The above conclusion may be A little abstract. If there is A different demand now, for example, real estate agent A must sign A house-viewing agreement before house-viewing, but this agreement only involves the agreement between the purchasing user and the agent. So it’s easy to implement based on the proxy pattern.

// Revised HouseAgentA
class HouseAgentA implements IPurchaseHouse {
    private IPurchaseHouse mHouseOwner;// HouseOwner is referenced by the proxy object
    private boolean mIsSigned;

    public HouseAgentA(IPurchaseHouse houseOwner) {
        mHouseOwner = houseOwner;
    }

    @Override
    public void inquiryPrice(a) {
        mHouseOwner.inquiryPrice();// Call inquiryPrice with the HouseOwner reference
    }

    @Override
    public void visitHouse(a) {
        if (mIsSigned) {
            System.out.println("You've signed a viewing agreement. You're ready to see the house.");
            mHouseOwner.visitHouse();// Call visitHouse with the HouseOwner reference
        } else {
            System.out.println("I'm sorry, you haven't signed the house viewing agreement, so you can't see the house now."); }}public void signVisitHouseAgreement(boolean isSigned) {
        mIsSigned = isSigned;
    }

    @Override
    public void payDeposit(a) {
        mHouseOwner.payDeposit();// Call payDeposit with the HouseOwner reference
    }

    @Override
    public void signAgreement(a) {
        mHouseOwner.signAgreement();// Call signAgreement with the specific HouseOwner reference
    }

    @Override
    public void payMoney(a) {
        mHouseOwner.payMoney();// Call payMoney with the specific HouseOwner reference
    }

    @Override
    public void getHouse(a) {
        mHouseOwner.getHouse();// Call getHouse with the HouseOwner reference}}/ / Client customer class
class Client {
    public static void main(String[] args) {
        IPurchaseHouse houseOwner = new HouseOwner();
        IPurchaseHouse houseAgent = new HouseAgentA(houseOwner);// Pass in an instance of the proxied class
        houseAgent.inquiryPrice();// Ask about the price
        ((HouseAgentA) houseAgent).signVisitHouseAgreement(true);// Sign a viewing contract
        houseAgent.visitHouse();/ / the checking
        houseAgent.payDeposit();// Pay a deposit
        houseAgent.signAgreement();/ / sign the contract
        houseAgent.payMoney();/ / pay
        houseAgent.getHouse();/ / room}}Copy the code

Running results:

The HouseOwner suggested the price of the house: You have signed the house viewing agreement. The HouseOwner agrees to let the HouseOwner look at the house. The HouseOwner receives RMB 10,000 deposit from the HouseOwner. The HouseOwner signs a contract with the HouseOwner and the HouseOwner pays the HouseOwner to get the houseexit code 0
Copy the code

2. Kotlin implements static proxies

HouseAgent in Java and HouseAgent in the proxy class to implement forward delegate is not a bit brainless ah, a bit mechanical, just like writing Java setter and getter methods, too much boilerplate code. Call it Kotlin. It will make your proxy class look more concise and elegant, because Kotlin has a natural advantage in implementing the proxy pattern. Those familiar with Kotlin know that Kotlin has a proxy-specific syntax that makes it easy to implement the proxy pattern.

//IPurchaseHouseKt: abstract house purchase interface
interface IPurchaseHouseKt {
    fun inquiryPrice(a) / / inquiry

    fun visitHouse(a) / / the checking

    fun payDeposit(a) / / to pay a deposit

    fun signAgreement(a) / / sign the contract

    fun payMoney(a) / / pay

    fun getHouse(a) / / room
}
//HouseOwnerKt: HouseOwnerKt
class HouseOwnerKt : IPurchaseHouseKt {
    override fun inquiryPrice(a) {
        println("HouseOwner put forward the house price: 200W RMB")}override fun visitHouse(a) {
        println("The HouseOwner agrees to let the buyer look at the house.")}override fun payDeposit(a) {
        println("HouseOwner received RMB 10,000 deposit from the buyer.")}override fun signAgreement(a) {
        println("The HouseOwner signs a contract with the HouseOwner.")}override fun payMoney(a) {
        println("The homeowner pays the HouseOwner.")}override fun getHouse(a) {
        println("The buyer gets the house.")}}//HouseAgentKt: Real estate agent. Note that Kotlin replaces all the boilerplate code for Java proxy classes in a single line
class HouseAgentKt(houseOwnerKt: IPurchaseHouseKt) : IPurchaseHouseKt by houseOwnerKt// Implement the proxy with the by keyword, omits a lot of boilerplate code in the proxy class, which requires get
/ / Client calls
fun main(args: Array<String>) {
    val houseOwnerKt = HouseOwnerKt()
    HouseAgentKt(houseOwnerKt).run {
        inquiryPrice()// Ask about the price
        visitHouse()/ / the checking
        payDeposit()// Pay a deposit
        signAgreement()/ / sign the contract
        payMoney()/ / pay
        getHouse()/ / room}}Copy the code

Running results:

The HouseOwner suggested the price of the house: The HouseOwner agrees the HouseOwner to come to see the house and receives the deposit of the HouseOwner. The HouseOwner signs a contract with the HouseOwner and the HouseOwner pays the HouseOwner to get the houseexit code 0
Copy the code

If you use the by keyword to give all methods to the proxy at once, you need to insert a piece of logic when a method is called. This is also very convenient, you just need to rewrite the method that you want to change. Take a look:

// Modified HouseAgentAKt
class HouseAgentAKt(houseOwnerAKt: IPurchaseHouseKt) : IPurchaseHouseKt by houseOwnerAKt {
    private val mHouseOwnerAKt = houseOwnerAKt
    var mIsSigned: Boolean = false
    override fun visitHouse(a) {// Just rewrite visitHouse
        if (mIsSigned) {
            println("You've signed a viewing agreement. You're ready to see the house.")
            mHouseOwnerAKt.visitHouse()
        } else {
            println("I'm sorry, you haven't signed the house viewing agreement, so you can't see the house now.")}}}/ / Client calls
fun main(args: Array<String>) {
    val houseOwnerKt = HouseOwnerKt()
    HouseAgentAKt(houseOwnerKt).run {
        mIsSigned = true
        inquiryPrice()
        visitHouse()
        payDeposit()
        signAgreement()
        payMoney()
        getHouse()
    }
}
Copy the code

Running results:

The HouseOwner suggested the price of the house: You have signed the house viewing agreement. The HouseOwner agrees to let the HouseOwner look at the house. The HouseOwner receives RMB 10,000 deposit from the HouseOwner. The HouseOwner signs a contract with the HouseOwner and the HouseOwner pays the HouseOwner to get the houseexit code 0
Copy the code

3. Unicing the by proxy syntax in Kotlin

What’s going on underneath the by keyword in Kotlin, and why it reduces boilerplate code in proxy classes?

In fact, in Kotlin the supertype IPurchaseHouseKt of the proxy class HouseAgentKt by houseOwnerKt means that houseOwnerKt will be stored internally in HouseAgentKt, And the compiler will automatically generate all IPurchaseHouseKt interface methods that are delegated to houseOwnerKt.

We can take a look at the decompiled code to verify our conclusion:

public final class HouseAgentKt implements IPurchaseHouseKt {
   // $FF: synthetic field
   private finalIPurchaseHouseKt ? delegate_0;//houseOwnerKt delegate_0

   public HouseAgentKt(@NotNull IPurchaseHouseKt houseOwnerKt) {
      Intrinsics.checkParameterIsNotNull(houseOwnerKt, "houseOwnerKt");
      super(a);this.? delegate_0 = houseOwnerKt; }public void getHouse(a) {
      this.? delegate_0.getHouse();// Delegate to? Delegate_0 (also called houseOwnerKt)getHouse method
   }

   public void inquiryPrice(a) {
      this.? delegate_0.inquiryPrice();// Delegate to? The delegate_0(aka houseOwnerKt passed in)inquiryPrice method
   }

   public void payDeposit(a) {
      this.? delegate_0.payDeposit();// Delegate to? Delegate_0 (aka houseOwnerKt passed)payDeposit method
   }

   public void payMoney(a) {
      this.? delegate_0.payMoney();// Delegate to? Delegate_0 (aka houseOwnerKt passed in)payMoney method
   }

   public void signAgreement(a) {
      this.? delegate_0.signAgreement();// Delegate to? Delegate_0 (which is passed houseOwnerKt)signAgreement method
   }

   public void visitHouse(a) {
      this.? delegate_0.visitHouse();// Delegate to? Delegate_0 (aka houseOwnerKt passed in)visitHouse method}}Copy the code

Dynamic proxy

Now we need to increase, now need to add more agents, may have a lot of partners to say that in accordance with the rules to add a few proxy classes. While Kotlin solves the problem of writing a lot of boilerplate code in Java, it is still static. Static means that the agent class needs to be written manually by the developer, and the class compilation file of the agent class exists before the code runs. Even more, it may not be the number of proxy classes that can be determined before compilation, but the number of proxy mediations that can be increased at run time. In such a scenario, static proxies may not be able to do anything, which leads to the following dynamic proxies

Java dynamic proxy implementation

Java provides us with a very convenient dynamic proxy interface called InvocationHandler. Simply implement this interface and override its abstract method invoke()

/ / DynamicProxy class
class DynamicProxy implements InvocationHandler {
    private Object object;// Referenced by the proxy class

    DynamicProxy(Object object) {// Pass is referenced by an instance of the proxy class
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        returnmethod.invoke(object, args); }}/ / Client classes
class Client {
    public static void main(String[] args) {
        IPurchaseHouse houseOwner = new HouseOwner();
        DynamicProxy dynamicProxy = new DynamicProxy(houseOwner);
        The proxy. newProxyInstance method constructs a Proxy mediation dynamically, passing in the ClassLoader of the proxied class, the common interface collection, and the dynamicProxy instance object
        IPurchaseHouse agentA = (IPurchaseHouse) Proxy.newProxyInstance(houseOwner.getClass().getClassLoader(), newClass[]{IPurchaseHouse.class}, dynamicProxy); agentA.inquiryPrice(); agentA.visitHouse(); agentA.payDeposit(); agentA.signAgreement(); agentA.payMoney(); agentA.getHouse(); }}Copy the code

Running results:

The HouseOwner suggested the price of the house: The HouseOwner agrees the HouseOwner to come to see the house and receives the deposit of the HouseOwner. The HouseOwner signs a contract with the HouseOwner and the HouseOwner pays the HouseOwner to get the houseexit code 0
Copy the code

2. Kotlin implements dynamic proxy

In fact, the dynamic proxy implementation in Java has been very streamlined, so Kotlin’s dynamic proxy implementation is not particularly different from the Java implementation. So I’m not going to repeat the implementation here, I’m just going to change the Kotlin language and it doesn’t make any difference.

Dynamic proxy principle analysis

1. Explanation of principle and conclusion

Dynamic proxy differs from static proxy in that it can generate any proxy object dynamically without requiring developers to write proxy class code manually. The dynamic proxy mechanism dynamically generates the proxy Class bytecode byte array at runtime, then deserializes the corresponding proxy Class object through the JVM, and then creates the proxy Class instance through the reflection mechanism.

2. Source code analysis and demonstration

  • 1, the first step is fromProxy.newProxyInstanceMethod into the exploration, through which the external is more intuitive is to obtain the proxy class object.
class Client {
    public static void main(String[] args) {
        IPurchaseHouse houseOwner = new HouseOwner();
        DynamicProxy dynamicProxy = new DynamicProxy(houseOwner);
        // Start with proxy. newProxyInstance
        IPurchaseHouse agentA = (IPurchaseHouse) Proxy.newProxyInstance(
                houseOwner.getClass().getClassLoader(),
                newClass[]{IPurchaseHouse.class}, dynamicProxy ); agentA.inquiryPrice(); agentA.visitHouse(); agentA.payDeposit(); agentA.signAgreement(); agentA.payMoney(); agentA.getHouse(); }}Copy the code
  • 2, the second step to enterProxy.newProxyInstanceMethod definition

Proxy.newProxyInstance takes three parameters:

Loader (ClassLoader): This parameter is the actual ClassLoader instance of the proxied class.

interfaces(Class
[]): Class array of interfaces implemented by both the proxied and proxied classes

H (InvocationHandler): Proxy interceptor interface. It is generally required to implement the interface using subclasses or anonymous classes

    public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
       
        finalClass<? >[] intfs = interfaces.clone();Clone a copy of interfaces' Class array and assign it to intfs
        final SecurityManager sm = System.getSecurityManager();
        if(sm ! =null) {// Check that permissions are required to create a new proxy class
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /* * Look up or generate the designated proxy class. */
         // Note 1: getProxyClass0 gets the Class instance cl of the proxy Class
         Intfs (a copy of the Class[] implemented by the proxy-class);Class<? > cl = getProxyClass0(loader, intfs);/* * Invoke its constructor with the designated invocation handler. */
        try {
            if(sm ! =null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            
            // Note 2: After getting the CL instance, create the proxy class instance through reflection
            finalConstructor<? > cons = cl.getConstructor(constructorParams);// Get the Constructor instance cons of the proxy class
            final InvocationHandler ih = h;
            // Check if the proxy class constructor is public. If it is not, AccessController changes the access so that instances of the proxy class can be created
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run(a) {
                        cons.setAccessible(true);// Set access to accessible
                        return null; }}); }// Note 3: After getting the constructor instance cons, it is time for the crucial last step, creating the proxy class instance.
            // It is important to note, however, that the constructor is reflected as an instance of InvocationHandler, and that the generated proxy class has a constructor that takes InvocationHandler as an argument.
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
            throw newInternalError(e.toString(), e); }}Copy the code

NewProxyInstance: newProxyInstance:

First we pass in the loader, interfaces, and h parameters, save a copy of InterfacesClone in intFS, and then check the permissions required to create a new proxy class. Get the Class object instance of the proxy Class using the getProxyClass0 method (which requires passing in loader and INTFS arguments). Once we have the proxy class instance, we create the proxy class instance through reflection.

For our second point, get the constructor cons via the proxy Class cl and check whether the constructor is public or not.

In addition to creating a cons.newInstance, h(InvocationHandler object) is passed in the constructor reflection, indicating that we can infer that the generated proxy class has a constructor that takes InvocationHandler as a parameter.

  • 3, the third step to entergetProxyClass0Method, the parameter passed inloaderandintfsWithin the method, delegates toproxyClassCacheGet method of,If the proxy class defined in the given classloader implements the given interface, it returns the copy in the cache directly, otherwise it passesProxyClassFactoryCreating a proxy class.
    private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        // If the proxy class defined in a given classloader implements the given interface,
        // Then the copy in the cache is returned directly, otherwise it creates the proxy class via ProxyClassFactory
        // proxyClassCache; Note 2: ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
Copy the code
  • Step 4proxyClassCacheThe introduction and definition, please note the creationproxyClassCacheThe constructor is passed with two arguments:KeyFactoryandProxyClassFactory
    /** * a cache of proxy classes */
    private static finalWeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache =new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
Copy the code

ProxyClassCache is a WeakCache

object. K in WeakCache

represents the key value,P represents the parameter, and V represents the stored value. This class is used to cache (key, sub-key) -> value key-value pairs. The internal implementation is implemented with the help of ConcurentMap
>>,Supplier is an interface, just a get method used to get values, but is a wrapper class for generic V, The first Object is a key (we don’t use the generic K because key can be null). The second is a sub-key. And specific cache also not generic P, this needs lead to another function interface BiFunction < T, U, R >, the interface is internal R apply (T, T, U, U) method, this method is based on the incoming two generic T and U value after a certain generic R value is calculated. There are two BiFunction objects in WeakCache

class:
,p,v>
,>
,p,v>
,p,v>

final class WeakCache<K.P.V> {
    private final ReferenceQueue<K> refQueue
        = new ReferenceQueue<>();
    // the key type is Object for supporting null key
    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
        = new ConcurrentHashMap<>();
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
        = new ConcurrentHashMap<>();
    private finalBiFunction<K, P, ? > subKeyFactory;private final BiFunction<K, P, V> valueFactory;
    
    public WeakCache(BiFunction
       
         subKeyFactory, BiFunction
        
          valueFactory)
        ,>
       ,> {
        // Get sub-key algorithm according to K,P
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        // Get the value algorithm according to K,P
        this.valueFactory = Objects.requireNonNull(valueFactory); }... }Copy the code

WeakCahe Class has only one core get method, which contains the entire cache logic. Note that we obtain the proxyClass object through proxyClassCache. Get (loader, interfaces); It is actually calling the get method in WeakCache.

// The K generic is the cache key of the first-level map, and the P generic is the parameter that corresponds to the loader and interfaces that are passed in externally
public V get(K key, P parameter) {...// Get the final map by passing in a key to a level map via CacheKey
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        // Lazy initializes the level-2 valuesMap corresponding to cacheKey. If valuesMap is empty, an empty valueMap of ConcurrentMap is created and put into the level-1 cache map
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
        // If valuesMap is empty, an empty valueMap of ConcurrentMap is created
            ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());
            If the old oldValuesMap already exists inside, use it directly
            if(oldValuesMap ! =null) { valuesMap = oldValuesMap; }}/ / -- -- -- -- -- - note point 1: subKeyFactory. Apply (key parameter) -- -- -- -- --
        // Obtain the sub-key from the apply method in subKeyFactory based on the key and parameter of the map.
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        // We then fetch the supplier object from valuesMap of the secondary cache through our sub-key
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if(supplier ! =null) {
                // Supplier may be a Factory or CacheValue
      
        object,
      
                // So the supplier. Get () method is either calling the get method in Factory or the get method in CacheValue
      
//-------- note 2: component.get ()-----
                V value = supplier.get();
                // Return value if value is not empty, terminating the entire get call
                if(value ! =null) {
                    returnvalue; }}// If no supplier object exists in the cache
            // get returns null in supplier
            // Or the Factory object is not successfully created in CacheValue
            
            // If factory is null, a new factory instance is created
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            / / : supplier to null
            if (supplier == null) {
                // Retrieve the supplier object based on the newly created factory and subKey. If there are subKey and factory key pairs in valuesMap, it returns the existing values instead of null
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                 // If the supplier is null, the supplier becomes a factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else {
                    // Get the supplier through valuesmap.get ()supplier = valuesMap.get(subKey); }}}}Copy the code

Let’s sort out the logic of WeakCache: First, proxyClassCache is a WeakCache instance object. ProxyClassCache = new WeakCache<>(new KeyFactory(), KeyFactory and ProxyClassFactory in new ProxyClassFactory()).

Then there is a second-level ConcurrentHashMap inside WeakCache. The key of the first-level map is the key passed in by the GET method, which is used to get the cacheKey, and then the corresponding valuesMap second-level map is obtained.

The sub key is obtained by applying the subKeyFactory method based on the key and parameter of the passed map. The sub key is used to retrieve the Supplier object stored in the map. It could be a CacheValue or it could be a Factory,

Finally, the Factory get method is used to get the actual value.

There are two key caveats to this

The subKey is obtained by subkeyfactory. apply(key,parameter)

WeakCache: Object subKey = objects.requirenonNULL (subkeyFactory.apply (key, parameter)); //weakCache: Object subKey = objects.requirenonNULL (subkeyFactory.apply (key, parameter)); Private static final class implements BiFunction<ClassLoader, class <? >[], Object> { @Override public Object apply(ClassLoader classLoader, Class<? >[] interfaces) {// subKey switch (interfaces.length) {// If only one interface is implemented, Case 1: return new Key1(interfaces[0]); Case 2: return new Key2(interfaces[0], interfaces[1]); Case 0: return key0; // If the proxied class implements more than two interfaces default: return new KeyX(interfaces); }}}Copy the code

2—-> supplent.get ()

We all know that supplier corresponds to the Factory object, which finally calls the get method in Factory.

  @Override
        public synchronized V get(a) { // serialize access
            // Check supplier again and return NULL if the passed in from valuesMap is not equal to the current Factory object since it may already be CacheValue
            Supplier<V> supplier = valuesMap.get(subKey);
            if(supplier ! =this) {
                return null;
            }
            
            // Create a new value
            V value = null;
            try {
               Valuefactory.apply (key, parameter
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this); }}// Check whether value is null
            assertvalue ! =null;

            // Wrap the value as a CacheValue
            CacheValue<V> cacheValue = new CacheValue<>(value);

            // Try to replace the Factory corresponding to subKey in valuesMap with cacheValue,
            // This is why the Supplier fetched from valuesMap may be a Factory or CacheValue
            if (valuesMap.replace(subKey, this, cacheValue)) {
                // After a successful replacement, put the cacheValue into the reverseMap
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {
                throw new AssertionError("Should not reach here");
            }

            // The new CacheValue was successfully replaced and the final value is returned
            return value;
        }
Copy the code

From the above code analysis, we know that the final value is obtained from the apply method in valueFactory. Remember what valueFactory is? That’s right, it’s ProxyClassFactory and that’s where you finally locate the apply method in ProxyClassFactory. This is why we said that if there is a copy of the cache returned directly from the cache, create a proxy object in the ProxyClassFactory if there is not.

  • 5. Step 5 EnterProxyClassFactoryIn theapplyMethod, which is the only source for creating new proxy Class objects.
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? $Proxy private static Final String proxyClassNamePrefix = "$Proxy"; private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<? > intf: interfaces) {// Verify that the Class loader resolves the name of this interface to the same Class object Class<? > interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass ! = intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } // Verify that the Class object of interfaceClass is an interface if (! interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } // Verify that this interface is not duplicated if (interfaceset. put(interfaceClass, boils.true)! = null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; // Record the package of the non-public proxy interface so that the Proxy class is defined in the same package. Verify that all non-public proxy interfaces are in the same package for (Class<? > intf : interfaces) { int flags = intf.getModifiers(); if (! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (! pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } / / generates only the proxy class name of the identifier long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; / / generated proxy Class Class files byte array / / -- -- -- -- -- - be careful ProxyGenerator. GenerateProxyClass - byte [] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); Return defineClass0(loader, proxyName, Class name) return defineClass0 proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }}}Copy the code

Rearrange the logic in Apply in ProxyClassFactory, first do some interface validation operations, Then through ProxyGenerator. GenerateProxyClass generated to determine the proxy Class Class files byte array, finally through defineClass0 method was introduced into the proxy Class Class loader, the proxy Class only name, the generated proxy Class file deserialized into a proxy Class Cla Ss object

  • 6. Step 6 EnterProxyGeneratorIn thegenerateProxyClassMethod to explore, mainly through it to generate proxy Class files.generateProxyClassMethods pass in the following parameters: proxyName(unique proxy Class name), interfaces(array of interfaces that require the proxy), accessFlags(access permission flags)
    public static byte[] generateProxyClass(finalString var0, Class<? >[] var1,int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        //----- note ---- call the generateClassFile method in ProxyGenerator
        final byte[] var4 = var3.generateClassFile();
        // Whether the generated Class file needs to be saved in a local file can be configured externally
        //boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('. ', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: "+ var4x); }}}); }return var4;
    }
Copy the code
  • 7. Step 7 EntergenerateClassFile()Method, which mainly generates a Class file.
 private byte[] generateClassFile() {
        ---- add some default methods of the Object class to the generated proxy class, such as common hashCode, equal, toString methods
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        // Get the Class array of the proxy Class interface
        Class[] var1 = this.interfaces;
        int var2 = var1.length;

        int var3;
        Class var4;
        //---- note 2-- Add methods from the proxy interface to the generated proxy Class by iterating through the Class array of the interface
        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
            // Get all Method objects defined in each interface
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;
            // Then iterate over all Method objects and add them to the generated proxy class
            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                this.addProxyMethod(var8, var4); }}...try {
        //---- Note 3 Add generateConstructor---- to the generated proxy class
            this.methods.add(this.generateConstructor());
            var11 = this.proxyMethods.values().iterator(); .//---- Note 4 Add static initialization block ---- to generated proxy class
            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var10) {
            throw new InternalError("unexpected I/O Exception", var10); }...// Create a byte array output stream
            ByteArrayOutputStream var13 = new ByteArrayOutputStream();
            DataOutputStream var14 = new DataOutputStream(var13);

            try{... var14.writeShort(this.fields.size());
                var15 = this.fields.iterator();
                // Write field information of the generated proxy class to the output stream
                while(var15.hasNext()) {
                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                    var20.write(var14);
                }

                var14.writeShort(this.methods.size());
                var15 = this.methods.iterator();
                // Write information about the generated proxy Method Method to the output stream
                while(var15.hasNext()) {
                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                    var21.write(var14);
                }

                var14.writeShort(0);
                // Returns the final Class file's byte array
                return var13.toByteArray();
            } catch (IOException var9) {
                throw new InternalError("unexpected I/O Exception", var9); }}}Copy the code
  • 8. This is the whole dynamic proxy class code generation process, in order to further understand the dynamic mechanism, such as invoke how to call. Let’s save the generated code in a local file and take a look at what the generated proxy class looks like.
public final class $Proxy1 extends Proxy implements IPurchaseHouse {
    private static Method m1;
    private static Method m7;
    private static Method m8;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m6;
    private static Method m0;
    private static Method m5;

    //---- Note that the constructor in the generate proxy class has the InvocationHandler parameter ----
    public $Proxy1(InvocationHandler var1) throws  {
        super(var1);// And pass it to its parent Proxy h(InvocationHandler)
    }

    // Generate the equals method
    public final boolean equals(Object var1) throws  {
        try {
          //-- notice the appearance of super.h.invoke--
          // Invoke the invoke Method from 'h' of Proxy class 'h' and call back the currently generated Proxy class instance this, the corresponding 'Method' object and the parameter array 'args' through' invoke '. The 'invoke' method in the 'InvocationHandler' subclass will fire
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}// Generate the default hashCode method in the Object class
    public final int hashCode(a) throws  {
        try {
          //-- notice the appearance of super.h.invoke--
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate the default toString method in the Object class
    public final String toString(a) throws  {
        try {
          //-- notice the appearance of super.h.invoke--
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate the payDeposit method in the proxy interface
     public final void payDeposit(a) throws  {
        try {
            //-- notice the appearance of super.h.invoke
            super.h.invoke(this, m7, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate the signAgreement method in the proxy interface
    public final void signAgreement(a) throws  {
        try {
          //-- notice the appearance of super.h.invoke
            super.h.invoke(this, m8, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate the payMoney method in the proxy interface
    public final void payMoney(a) throws  {
        try {
          //-- notice the appearance of super.h.invoke
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate the getHouse method in the proxy interface
    public final void getHouse(a) throws  {
        try {
         //-- notice the appearance of super.h.invoke
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate the visitHouse method in the proxy interface
    public final void visitHouse(a) throws  {
        try {
         //-- notice the appearance of super.h.invoke
            super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate the inquiryPrice method in the proxy interface
    public final void inquiryPrice(a) throws  {
        try {
          //-- notice the appearance of super.h.invoke
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}// Generate static initializer block, use reflection to get corresponding Method object,
   // This includes methods in Object and methods in the proxy interface
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m7 = Class.forName("com.mikyou.design_pattern.delegates.dynamic.IPurchaseHouse").getMethod("payDeposit");
            m8 = Class.forName("com.mikyou.design_pattern.delegates.dynamic.IPurchaseHouse").getMethod("signAgreement");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.mikyou.design_pattern.delegates.dynamic.IPurchaseHouse").getMethod("payMoney");
            m3 = Class.forName("com.mikyou.design_pattern.delegates.dynamic.IPurchaseHouse").getMethod("getHouse");
            m6 = Class.forName("com.mikyou.design_pattern.delegates.dynamic.IPurchaseHouse").getMethod("visitHouse");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m5 = Class.forName("com.mikyou.design_pattern.delegates.dynamic.IPurchaseHouse").getMethod("inquiryPrice");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

When you look at the generated proxy class code, the dynamic proxy mechanism is quite obvious. You will know when the Invoke method in InvocationHandler is called. Let’s review the core mechanism of dynamic proxy as a whole. In fact, the most core mechanism is InvocationHandler:

First, we need to implement a subclass of InvocationHandler that overrides its invoke method, which calls back three arguments: Object Proxy, Method Method, Object[] args, and then we only need to call Method’s invoke Method and pass in the ARgs parameter.

NewProxyInstance will pass in an instance of the InvocationHandler subclass as a constructor function argument to generate the newProxy class. This parameter is passed to Proxy, the parent of the new Proxy class, where the instance H of the InvocationHandler subclass is maintained.

Then, according to the generated Proxy class code above, all methods will be converted into the corresponding Method object, which will be initialized by reflection in the static initialization block. Then, the internal invocation implementation of each Method will delegate the invoke Method in the parent class Proxy h to realize the invocation. Invoke the generated proxy class instance, Method object and parameter array args via invoke. Then the invoke Method in the InvocationHandler subclass will be triggered, and the invoke Method will be converted into Method inside. Passing in the args argument is equivalent to calling the method with reflection.

Finally, the dynamic proxy content is finished.

Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~

Welcome to Kotlin’s series of articles:

Kotlin Encounter Design Pattern collection:

  • When Kotlin Meets Design Patterns perfectly singleton Patterns (PART 1)

Data Structure and Algorithm series:

  • Binary Search for algorithms every Week (described by Kotlin)
  • Weekly Linked List of Data structures (Kotlin description)

Translation series:

  • What Kotlin said about the Companion Object
  • Remember a Kotlin official document translation PR(inline type)
  • Exploration of autoboxing and High performance for inline classes in Kotlin (ii)
  • Kotlin inline class full resolution
  • Kotlin’s trick of Reified Type Parameter
  • When should type parameter constraints be used in Kotlin generics?
  • An easy way to remember Kotlin’s parameters and arguments
  • Should Kotlin define functions or attributes?
  • How to remove all of them from your Kotlin code! (Non-empty assertion)
  • Master Kotlin’s standard library functions: run, with, let, also, and apply
  • All you need to know about Kotlin type aliases
  • Should Sequences or Lists be used in Kotlin?
  • Kotlin’s turtle (List) rabbit (Sequence) race

Original series:

  • How do you fully parse annotations in Kotlin
  • How do you fully parse the type system in Kotlin
  • How do you make your callbacks more Kotlin
  • Jetbrains developer briefing (3) Kotlin1.3 new features (inline class)
  • JetBrains developer briefing (2) new features of Kotlin1.3 (Contract and coroutine)
  • Kotlin/Native: JetBrains Developer’s Day (Part 1
  • How to overcome the difficulties of generic typing in Kotlin
  • How to overcome the difficulties of Generic typing in Kotlin (Part 2)
  • How to overcome the difficulties of Generic typing in Kotlin (Part 1)
  • Kotlin’s trick of Reified Type Parameter (Part 2)
  • Everything you need to know about the Kotlin property broker
  • Source code parsing for Kotlin Sequences
  • Complete analysis of Sets and functional apis in Kotlin – Part 1
  • Complete parsing of lambdas compiled into bytecode in Kotlin syntax
  • On the complete resolution of Lambda expressions in Kotlin’s Grammar
  • On extension functions in Kotlin’s Grammar
  • A brief introduction to Kotlin’s Grammar article on top-level functions, infix calls, and destruct declarations
  • How to Make functions call Better
  • On variables and Constants in Kotlin’s Grammar
  • Elementary Grammar in Kotlin’s Grammar Essay

Translated by Effective Kotlin

  • The Effective Kotlin series considers arrays of primitive types for optimal performance (5)
  • The Effective Kotlin series uses Sequence to optimize set operations (4)
  • Exploring inline modifiers in higher-order functions (3) Effective Kotlin series
  • Consider using a builder when encountering multiple constructor parameters in the Effective Kotlin series.
  • The Effective Kotlin series considers using static factory methods instead of constructors (1)

Actual combat series:

  • Use Kotlin to compress images with ImageSlimming.
  • Use Kotlin to create a picture compression plugin.
  • Use Kotlin to compress images.
  • Simple application of custom View picture fillet in Kotlin practice article