In the previous article, we analyzed how the proxy class is generated inside the Proxy class. We saw that the cache mechanism is used inside the Proxy. If the proxy class can be found in the cache based on the provided class loader and interface array, the proxy class will be returned directly. Otherwise, the ProxyClassFactory factory will be called to generate the proxy class. The cache used here is a secondary cache, and its first-level cache key is generated based on the class loader, and the second-level cache key is generated based on the interface array. We will directly post the code to explain the specific internal mechanism in detail.
//Reference reference queue private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();//The underlying implementation of cache, the key is first-level cache and the value is second-level cache. In order to support null, the key type of map is set to Objectprivate final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();//reverseMap records whether all proxy class generators are available. This is to implement the cache expiration mechanism private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();//The factory that generates the secondary cache key, the KeyFactoryprivate final BiFunction<K, P, ?> subKeyFactory;//The factory that generates the secondary cache value, here is the ProxyClassFactoryprivate final BiFunction<K, P, V> valueFactory;//Constructor, the factory that generates the secondary cache key and the factory that generates the secondary cache value public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) { this.subKeyFactory = Objects.requireNonNull(subKeyFactory); this.valueFactory = Objects.requireNonNull(valueFactory);}First, let's take a look at the member variables and constructor of WeakCache. The internal implementation of WeakCache cache is completed through ConcurrentMap. The member variable map is the underlying implementation of the secondary cache. The reverseMap is to implement the cache expiration mechanism. The subKeyFactory is the generation factory of the secondary cache key. It is passed in through the constructor. The value passed here is the KeyFactory of the Proxy class, and the valueFactory is the generation factory of the secondary cache value. It is passed in through the constructor. The Proxy class ProxyClassFactory is passed here. Next, let’s take a look at the get method of WeakCache.
public V get(K key, P parameter) { //The interface required to be implemented here cannot be empty Objects.requireNonNull(parameter); //Clear expired cache expungeStaleEntries(); //Wrap ClassLoader into CacheKey as a key for the first-level cache Object cacheKey = CacheKey.valueOf(key, refQueue); //Get the secondary cache ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); //If the corresponding value is not obtained based on ClassLoader if (valuesMap == null) { //Put it in CAS, If it does not exist, put it in, otherwise return the original value ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); //If oldValuesMap has a value, it means that the placement failed if (oldValuesMap != null) { valuesMap = oldValuesMap; } } //Create the second-level cache key based on the interface array implemented by the proxy class, divided into key0, key1, key2, keyx Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); //The value of the secondary cache is obtained through subKey. Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; //This loop provides a polling mechanism. If the condition is false, continue to try again until the condition is true while (true) { //If the value retrieved through subKey is not empty if (supplier != null) { //Here supply may be a Factory or a CacheValue //No judgment is made here, but verify in the get method of the Supplier implementation class V value = supplier.get(); if (value != null) { return value; } } if (factory == null) { //Create a new Factory instance as the corresponding value of subKey factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { //So here it means that subKey has no corresponding value, put factory as the value of subKey in supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { //So here it means that the factory is successfully placed in the cache supplier = factory; } // Otherwise, other threads may modify the value during the period, Then we no longer continue to assign values to subKey, but take them out and use them directly } else { // During this period, other threads may have modified the value, so replace the original value if (valuesMap.replace(subKey, supplier, factory)) { //Successfully replace the factory with a new value supplier = factory; } else { //Replace failed, continue to use the original value supplier = valuesMap.get(subKey); } } }}WeakCache's get method does not synchronize with locks, so how does it achieve thread safety? Because all its modified member variables use ConcurrentMap, this class is thread-safe. Therefore, it delegates its own thread safety to ConcurrentMap, and the get method reduces the synchronization code blocks as much as possible, which can effectively improve the performance of WeakCache. We see that ClassLoader is a key of Level 1 cache, so that we can filter it first according to ClassLoader, because the classes loaded by different ClassLoaders are different. Then it uses an interface array to generate the key of the secondary cache. Here it has some optimizations. Because most classes implement one or two interfaces, the secondary cache key is divided into key0, key1, key2, and keyX. Key0 to key2 means that 0 to 2 interfaces are implemented, and keyX means that 3 or more interfaces are implemented. In fact, most of them will only use key1 and key2. The generation factory of these keys is in the Proxy class, and the key factory is passed in through the WeakCache constructor. The value of the second-level cache here is a Factory instance, and the final value of the proxy class is obtained through the Factory factory.
private final class Factory implements Supplier<V> { //Level 1 cache key, generate private final K key according to ClassLoader; //Interface array private final P parameter implemented by the proxy class; //Level 2 cache key, generate private final Object subKey according to the interface array; //Level 2 cache private final ConcurrentMap<Object, Supplier<V>> valuesMap; Factory(K key, P parameter, Object subKey, ConcurrentMap<Object, Supplier<V>> valuesMap) { this.key = key; this.parameter = parameter; this.subKey = subKey; this.valuesMap = valuesMap; } @Override public synchronized V get() { //Here I go to the secondary cache again to get Supplier to verify whether it is Factory itself Supplier<V> supplier = valuesMap.get(subKey); if (supplier != this) { //Here I verify whether the supplier is the Factory instance itself, if not, return null and let the caller continue to poll and retry// During the period, the supplier may be replaced with CacheValue, or it is removed from the secondary cache due to the failure to generate the proxy class; } V value = null; try { //Delegate the valueFactory to generate the proxy class, here the proxy class value will be generated through the incoming ProxyClassFactory = Objects.requireNonNull(valueFactory.apply(key, parameter)); } finally { //If the proxy class generation fails, delete this secondary cache if (value == null) { valuesMap.remove(subKey, this); } } //Only the value of the value is not empty can it reach here assert value != null; //The proxy class CacheValue<V> cacheValue = new CacheValue<>(value); //Put the packaged cacheValue into the secondary cache. This operation must be successful, otherwise an error will be reported if (valuesMap.replace(subKey, this, cacheValue)) { //After successfully put cacheValue into the secondary cache, mark it reverseMap.put(cacheValue, Boolean.TRUE); } else { throw new AssertionError("Should not reach here"); } //Finally return the proxy class return value that is not wrapped with weak reference; }}Let’s look at the internal factory class Factory again, and we can see that its get method is synchronized using the synchronized keyword. After performing the get method, first verify whether the supplier corresponding to subKey is the factory itself. If not, it will return null, and the WeakCache get method will continue to try again. If it is indeed the factory itself, then ProxyClassFactory will be delegated to generate the proxy class, which is passed in when constructing WeakCache. So here explains why the ProxyClassFactory internal factory in Proxy is called to generate the proxy class in the end. After generating the proxy class, it uses a weak reference to wrap it and put it in reverseMap, and finally returns the original proxy class.
So far, we have revealed in detail the implementation of WeakCache cache including its first-level cache and second-level cache implementation principles, as well as the principle of generating the second-level cache key, and how it calls ProxyClassFactory to generate the proxy class in the end. In the next article, we will go into the ProxyGenerator class to see the bytecode generation process of the specific proxy class.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.