preface

Dynamic proxy is a very important idea in Java, this article will be introduced by shallow and deep dynamic proxy and proxy source analysis, the article is not always right, please read this article with a critical attitude.

Proxy pattern is a kind of design pattern, and in the actual development of the frequency is very high, such as Spring AOP, Mybatis proxy is we often use.

Definition of proxy mode:

When an object cannot be accessed directly or it is difficult to access an object, it can be accessed indirectly through a proxy object. To ensure transparency of client use, the real object accessed needs to implement the same interface as the proxy object.

Static agent

It can be seen from the above introduction that the proxy mode is actually intended to solve the access difficulties or to ensure transparency as a tool, similar to the fact that we cannot access Google and need the help of an intermediary agent. Here is a simple example:

public class ProxyDemo {

    static interface BaseUser{
        void info();
        void real();
    }

    static class ProxyUser implements BaseUser{
        BaseUser baseUser;
        public ProxyUser(BaseUser baseUser) {
            this.baseUser = baseUser;
        }
        public void info() {
            System.out.println("I'm Proxy,I can help you");

        }
        public void real() {
            System.out.println("I will help you visit google");
            baseUser.real();
            System.out.println("I had help you visit google");
        }

    }
    static class TargetUser implements BaseUser{
        public void info() {
            System.out.println("I'm google,what you what do?");
        }
        public void real() {
            System.out.println("I.m google,this is searched info");
        }
    }

    public static void main(String[] args) {
        BaseUser targetUser = new TargetUser();
        BaseUser proxyUser = new ProxyUser(targetUser);
        proxyUser.info();
        proxyUser.real();
    }

}Copy the code

Here we can also think that the agent is the carrier of the two access or interaction, need to be very familiar with both sides, to help you do specific things, just like if I need to buy now, I may need to find a new agent!!

This is what we call a static proxy

Although static proxy can also help me to achieve some functions, but it is not powerful enough, we can use dynamic proxy to help us more flexible to do things

A dynamic proxy

Advantages of dynamic proxy:

1. Reduce the coupling degree between each function module, improve the efficiency of development and convenient maintenance of the program. 2. Reduce the amount of code. 3. Not focusing on achieving your goals.Copy the code

Implementation of dynamic proxy

JDK dynamic proxy

Dynamic proxies come with the JDK primarily through the implementation of InvocationHandler

The main method of InvocationHandler

Object invoke(Object proxy, Method method,Object[] args)throws Throwable

The method call is processed on the proxy instance and the result is returned. When a method is invoked on the proxy instance associated with the method, this method is invoked on the invocation handler. That is, methods that call real business go to this invoke method. Why, more on that later

Method details

  • Parameter: proxy – The proxy instance object of the calling method method – The method instance object of the interface method called by the proxy instance object.
  • Method- Refers to a specific proxied Method. Args – contains arguments to the method call passed on the proxy instance, or null if the interface method does not use arguments.

  • Return: The value returned from the proxy instance’s method call.

  • Throws: Throwable – An exception thrown from a method call on a proxy instance.

case

This example demonstrates the ability to intercept and then log the most common methods.

3.1 Service Interfaces

public interface Base {
    public void hello(String name);
}Copy the code

3.2 Service Implementation LoginImpl

public class LoginImpl implements Base{ @Override public void hello(String name) { System.out.println("welcome "+name+",  success !! 1 "); }}Copy the code

3.3 Proxy LoginProxy

class DynamicProxy implements InvocationHandler { Object originalObj; Object bind(Object originalObj) { this.originalObj = originalObj; return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this); } /** * pointcut calls all object methods * method.invoke calls login method * @param proxy proxy object * @param method proxy object method * @param args @return Return value of the method called by the proxy Object * @throws Throwable */ @Override Public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object invoke = method.invoke(originalObj, args); if (invoke ! = null){ result(invoke); } after(); return invoke; } private void before() {system.out.println (" method before "); } private void after() {system.out.println (); } private void result(Object o) { o.toString(); }}Copy the code

3.4 Test LoginClient

Public class LoginClient {public static void main(String[] args) {// Used to generate proxy files //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Base hello = (Base) new DynamicProxy().bind(new LoginImpl()); hello.hello("zhangsan"); }}Copy the code

3.5 Execution Results:

Method before Hello Zhangsan method afterCopy the code

As we can see from the example above, dynamic proxy effectively reduces the coupling of modules, isolating the code used for logging from the code used for logging. There are no conditions for either. The only time the two are related is when the business is actually called and the logging function is required. Any business that requires logging only needs to create proxy objects through proxy classes, and does not need to create proxy classes repeatedly.

Investigate its principle

System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”); Uncomment this method, we run or compile the code to generate the agent’s file, which by default is in the project root directory in a folder with the same package name. Let’s look at the decompiled content of the generated proxy class:

import ProxyDemo.Base; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; $Proxy0 extends Proxy implements Base {final class $Proxy0 extends Proxy implements Base { / / will be the basis of the tostring, equils, hashcode, and base the method generating method of the interface object private static method m1; private static Method m2; private static Method m4; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void hello(String var1) throws { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void out() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); Static {try {m1 = class.forname (" java.lang.object ").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m4 = Class.forName("ProxyDemo$Base").getMethod("hello", Class.forName("java.lang.String")); m3 = Class.forName("ProxyDemo$Base").getMethod("out"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); }}}Copy the code

Proxy.newproxyinstance (originalobj.getClass ().getClassLoader(), originalobj.getClass ().getinterfaces (), this); The function of the method.

That is, it inherits the Proxy class and implements the Base interface. Base hello = (Base) new DynamicProxy().bind(new LoginImpl()); This is why you can force conversion to a Base object. At the same time, equils, ToString, Hashcode and all the methods of the Base interface in the object class are generated in the proxy method.

Taking Hello method as an example, H refers to the InvocationHandler in proxy class, which refers to the DynamicProxy object before us. Then invoke method will return to DynamicProxy invoke method. We can do a lot of intermediate things again.

public final void hello(String var1) throws { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); }}Copy the code

The Proxy interpretation

After looking at the content of the Proxy class, we need to take a closer look at how Proxy generates $Proxy0. Learn how it works and how it works.

First look at the newProxyInstance method

public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); Final Class<? >[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm ! = null) {/ / check to see if there is to generate the proxy class permissions checkProxyAccess (Reflection. GetCallerClass (), loader, intfs); } // Find or generate the proxy Class<? > cl = getProxyClass0(loader, intfs); // Generate the constructor try {if (sm! = null) {/ / check whether have permission checkNewProxyPermission (Reflection) getCallerClass (), cl); } //public $Proxy0(InvocationHandler var1) final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; // Access modifiers set if (! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() {  cons.setAccessible(true); return null; }}); } // 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 new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); }}Copy the code

In the getProxyClass0 method, the outermost method simply describes generating the proxy and then creating the corresponding proxy object. Let’s look at the checkProxyAccess method

Private static void checkProxyAccess(Class<? > caller, ClassLoader loader, Class<? >... interfaces) { SecurityManager sm = System.getSecurityManager(); if (sm ! = null) { ClassLoader ccl = caller.getClassLoader(); If (vm. isSystemDomainLoader(loader) &&! VM.isSystemDomainLoader(ccl)) { sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); } // ReflectUtil.checkProxyPackageAccess(ccl, interfaces); Public static void checkypackageAccess (ClassLoader var0, Class... var1) { SecurityManager var2 = System.getSecurityManager(); if (var2 ! = null) { Class[] var3 = var1; int var4 = var1.length; for(int var5 = 0; var5 < var4; ++var5) { Class var6 = var3[var5]; ClassLoader var7 = var6.getClassLoader(); if (needsPackageAccessCheck(var0, var7)) { checkPackageAccess(var6); }}}}Copy the code

After verifying permissions, see how to get the proxy class’s getProxyClass0 method

private static Class<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // retrieve from the cache, create return proxyClassCache. Get (loader, interfaces) if it does not exist; }Copy the code

Use proxyClassCache for caching, which is intended for reuse and prevents multiple threads from being created repeatedly. Multiple maps are used for recording in the weekCache class, which we’ll cover in more detail later.

Public V get(K key, P parameter) {objects.requirenonnull (parameter); // Delete expungeStaleEntries(); CacheKey = cachekey. valueOf(key, refQueue); // Check whether the key already exists in Valuemaps ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); If (valuesMap == null) {if (valuesMap == null) {if (valuesMap == null) {if (valuesMap == null) { Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap ! = null) { valuesMap = oldValuesMap; SubKey = objects.requirenonNULL (subkeyFactory.apply (key, parameter)); <V> Supplier = valuesmap. get(subKey); Factory factory = null; While (true) {// If (supplier! <V> instance V value = component.get (); if (value ! = null) {return value return value; Supplier if (factory == null) {factory = new factory (key, parameter, subKey); valuesMap); } if (supplier == null) {// If no supplier exists, save to valuemap supplier = Valuesmap. putIfAbsent(subKey, factory); If (supplier == null) {// Supplier = factory; } else {if (valuesmap. replace(subKey, supplier, factory)) {// Supplier = factory; } else { // retry with current supplier supplier = valuesMap.get(subKey); }}}}Copy the code

The get method first checks to see if there is a cache expiration and then clears it. If not, try to generate metadata for key and value.

The keyFactory.apply method is used to generate the key

Public Object apply(ClassLoader ClassLoader, Class<? >[] interfaces) { switch (interfaces.length) { case 1: return new Key1(interfaces[0]); // the most frequent case 2: return new Key2(interfaces[0], interfaces[1]); case 0: return key0; default: return new KeyX(interfaces); }}Copy the code

It then tries to save the added information and replace it if it already exists. The actual logic is in the supplene.get () method. Let’s see how it works

Public synchronized V get() {// serialize access <V> Supplier = valuesmap.get (subKey); if (supplier ! Valuesmap. replace(subKey, supplier, factory) return null; } // create V value = null; RequireNonNull (valueFactory.apply(key, parameter)); } finally {if (value == null) {// If no proxy object is generated, remove valuesmap.remove (subKey, this) from Valuemap; } } // the only path to reach here is with non-null value assert value ! = null; // Wrap value for acacheValue CacheValue<V> CacheValue = new CacheValue<>(value); // Save to reverseMap reversemap. put(cacheValue, boolea.true); // Try this to replace cacheValue if (! valuesMap.replace(subKey, this, cacheValue)) { throw new AssertionError("Should not reach here"); } return value; }Copy the code

The proxyClassFactory.apply method for value is detailed below.

Public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<? > intf : interfaces) { Class<? > interfaceClass = null; Try {// Use the given Class loader to load the interface interfaceClass = class.forname (intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass ! = intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } // Verify whether the interface is if (! interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } // Verify that the interface is not duplicated if (interfaceset. put(interfaceClass, boil.true)! = null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; / / Modifier int accessFlags = Modifier. PUBLIC | Modifier. The FINAL; /* * Check the visibility of the interface * if the interface is not public and not 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; } // Throw an exception if the interface is not public and not in the same package else if (! pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); }} if (proxyPkg == null) {com.sun.proxy proxyPkg = reflectutil. PROXY_PACKAGE + "."; } / * * the name of the proxy class Increasing order = > $proxy0 * / long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; / * * generated proxy class byte array * / byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); Class return defineClass0(loader, proxyName, proxyClassFile, 0, proxyclassfile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }}}Copy the code

The main steps are as follows

1. Try to load the interface using an existing class loader, if successful

2. Check whether the interface is an interface and whether the interface is duplicated

3. Verify the interface access permission

4. Get package information and class name Settings.

5. Generate the byte array for the proxy

6. Get the specific Class of the byte number through the native method defineClass0

So here’s how to generate a byte array, okay

Public static byte[] generateProxyClass(final String var0, Class<? >[] var1, int var2) { ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); /** * Generate a specific file byte array * 1. Find methods for all interfaces * 2. Add the three methods toString HashCode equils * 3 of the object class. GenerateClassFile (); generateClassFile(); generateClassFile(); generateClassFile(); // private static final boolean saveGeneratedFiles = GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue(); / / that's why we set the sun. The misc. ProxyGenerator. SaveGeneratedFiles = true reason, after setting is generated proxy class file if (saveGeneratedFiles) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { try { int var1 = var0.lastIndexOf(46); Path var2; If (var1 > 0) {// Generate path will. Path var3 = paths.get (var0.substring(0, var1).replace('.', file.separatorchar)); // Create a files.createDirectories (var3); Resolve (var0.substring(var1 + 1, var0.length()) + ".class"); 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

The main method is to generate byte arrays from ProxyGenerator objects. The specific generation steps can be as follows:

2. Add toString HashCode equils 3 of the object class. The traversal generates concrete proxy methods, the logic of which is like calling back to our proxy classCopy the code

I can get an idea of the process by looking at one of the proxy methods shown earlier.

public final void hello(String var1) throws { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); }}Copy the code

The main differences are the method name, the method parameters, and the parameters of the Invoke method. Everything else is pretty much the same. This article will not go into the details of the code.

. Then through the sun. The misc. ProxyGenerator saveGeneratedFiles value, to determine whether generated proxy files to disk.

If generated, the package information is generated, the class information is generated, and the byte array is written to the file. By default and depending on the project, create folders with package names and $proxy+ I proxy files.

The cache

From our code we can see that WeekCache uses multiple maps for recording

/ / cachekey reference queue, in close relations to the JVM details please look at this article http://blog.csdn.net/u012332679/article/details/57489179 private final ReferenceQueue<K> refQueue = new ReferenceQueue<>(); Key =>cacheKey,value => valueMap Private Final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>(); Private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>(); Private final BiFunction<K, P,? > subKeyFactory; Private final BiFunction<K, P, V> valueFactory;Copy the code

A Map key is a cacheKey, which is a weak-application type object, while a reverseMap key is a cacheValue, which is also a weak-application type object. Both are also the primary objects of memory reverseMap. When a key in a map fails, the expungeStaleEntries method is triggered the next time the GET,containsValue, or Size methods are used, and the value is purged from the reverseMap (val) Uemap is deleted from the map. The refQueue is recycled by the ReferenceHandler in Reference. If it is recycled, refqueue.poll will fire successfully and the cleanup operation will be wanted.

private void expungeStaleEntries() { CacheKey<K> cacheKey; while ((cacheKey = (CacheKey<K>)refQueue.poll()) ! = null) { cacheKey.expungeFrom(map, reverseMap); } } void expungeFrom(ConcurrentMap<? ,? extends ConcurrentMap<? ,? >> map, ConcurrentMap<? . Boolean> reverseMap) { // removing just by key is always safe here because after a CacheKey // is cleared and enqueue-ed  it is only equal to itself // (see equals method)... ConcurrentMap<? ,? > valuesMap = map.remove(this); // remove also from reverseMap if needed if (valuesMap ! = null) {for (Object cacheValue: valuesmap.values ()) {// Remove (cacheValue); }}}Copy the code

Key => subkeyFactory. apply(key, Parameter) uses the classLoader and interface[] to form value=> supplier=>Factory or CacheValue

There are two forms of value in Valuemap: 1. It is created as a Factory object; 2. The factory. Applay method is replaced with CacheValue when executed, and CacheValue is saved to a reverseMap

summary

This is the overall logic, through the source code reading, the JDK dynamic proxy implementation is much clearer. With a fundamental understanding of the dynamic proxy implementation process, we can now try to think of a concrete implementation of Spring AOP for ourselves. This paper does not make a detailed analysis and summary of the details of cache and proxy generation, and further research is needed

conclusion

Due to the limited ability of the individual, there are mistakes in the article or valuable opinions, welcome to leave a comment.

I will then write about Spring AOP and cglib dynamic proxies.

With you!!