This article code content is more, say may be a little rough, you can read selectively.
The purpose of this article is to simply analyze the principle of dynamic Proxy and imitate JDK Proxy handwritten dynamic Proxy and several agents to do a summary.
For the introduction and explanation of the agent model, there are already many high-quality articles on the Internet, I will not introduce too much, here recommend several high-quality articles as a reference:
- Explain the agency model to your girlfriend
- Easy to learn, Java proxy pattern and dynamic proxy
In addition, I also have the basic sample code in the corresponding directory of my Github repository: github.com/eamonzzz/ja…
JDK Proxy Dynamic Proxy
The concept of dynamic proxy is not explained here; Dynamic proxies are more powerful than static proxies and more adaptable as services expand.
Before we talk about the principle of dynamic proxies, let’s look at the general use of dynamic proxies.
use
This article uses an example of the simplest proxy pattern code as an example, I believe that you have seen or touched these codes when learning or understanding the proxy pattern.
- So let’s create one
Subject
Principal Abstract interface:
/**
* @author eamon.zhang
* @date 2019-10-09 下午4:06
*/
public interface Subject {
void request();
}Copy the code
- Create a real body
RealSubject
To deal with our real logic:
/** * @author * @date 2019-10-09 PM */ public implements Subject {@override public Void request() {system.out.println (" real processing logic!" ); }}Copy the code
- Without changing
RealSubject
Class case if we want to implement in executionRealSubject
In the classrequest()
How to implement a piece of logic before or after a method? This creates a proxy class to enhance the original code. So now create a JDK dynamic proxy classRealSubjectJDKDynamicProxy
:
/ * * * @ author eamon. Zhang 4:08 * * @ the date 2019-10-09 afternoon/public class RealSubjectJDKDynamicProxy implements InvocationHandler {// Private Object target; / / by the constructor incoming Object reference public RealSubjectJDKDynamicProxy Object (target) {this. Target = target; Public Object getInstance() {Class<? > clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { before(); Object invoke = method.invoke(target, objects); after(); return invoke; } private void before() {system.out.println ("前 提 前 提 出 "! ); } private void after() {system.out.println () {system.out.println (); ); }}Copy the code
- Test code:
@Test
public void test(){
Subject realSubject = new RealSubject();
RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject);
Subject instance = (Subject) proxy.getInstance();
instance.request();
System.out.println(realSubject.getClass());
System.out.println(instance.getClass());
}Copy the code
- The test results
Front-facing enhancement! Real processing logic! Afterboost! class com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject class com.sun.proxy.$Proxy8Copy the code
As a result, the code above has achieved our enhancement purpose.
The principle of analysis
I don’t know if you noticed in the test code above, the last two lines I printed out the class object before and after the proxy; And discovered that these two objects are not the same, and most importantly, After the proxy object of the Subject is com. Sun. Proxy. $Proxy8 not com. Eamon. Javadesignpatterns. Proxy. Dynamic. The JDK. RealSubject or com. Eamon. Javadesi Gnpatterns. Proxy. Dynamic. JDK. Subject, then this instance exactly where it came from? With this question, let’s use JDK Proxy source code to analyze it:
We follow up RealSubjectJDKDynamicProxy class Proxy newProxyInstance (clazz. GetClassLoader (), clazz. GetInterfaces (), this); Methods:
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) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<? > cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { ... final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; 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); }... }Copy the code
NewProxyInstance getProxyClass0(loader, intfs);
/** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */ private static Class<? > 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 return proxyClassCache.get(loader, interfaces); }Copy the code
The code logic is simple and does two things:
- Check whether the number of interfaces of the class exceeds
65535
, the number of interfaces is 2byte
Storage, maximum support65535
A. - from
proxyClassCache
From the comment, it will be called if the cache does not have oneProxyClassFactory
To create.
ProxyClassCache. Get (loader, interfaces)
public V get(K key, P parameter) { Objects.requireNonNull(parameter); expungeStaleEntries(); Object cacheKey = CacheKey.valueOf(key, refQueue); // lazily install the 2nd level valuesMap for the particular cacheKey ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap ! = null) { valuesMap = oldValuesMap; } } // create subKey and retrieve the possible Supplier<V> stored by that // subKey from valuesMap Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; // Call factory. Get () if (supplier!) if (supplier! = null) { // supplier might be a Factory or a CacheValue<V> instance // V value = supplier.get(); if (value ! = null) { return value; } } // else no supplier in cache // or a supplier that returned null (could be a cleared CacheValue // or a Factory that wasn't successful in installing the CacheValue) // lazily construct a Factory if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { // successfully installed Factory supplier = factory; } // else retry with winning supplier } else { if (valuesMap.replace(subKey, supplier, factory)) { // successfully replaced // cleared CacheEntry / unsuccessful Factory // with our Factory supplier = factory; } else { // retry with current supplier supplier = valuesMap.get(subKey); }}}}Copy the code
The code may be a bit long, but the logic is to call proxyClassFactory.apply () to generate the proxy class. From while(true) we split the code into two parts:
- So the first half, we get it from the cache
ProxyClassFactory
If the key is created successfully, it can be retrieved from the cache. - Then watch
while(true)
The logic in the code block,if (supplier ! = null)
This judgment, if created in the cacheProxyClassFactory
Will performsupplier.get()
And terminate the loop; If not, it will be executednew Factory(key, parameter, subKey, valuesMap);
To createfactory
, and then put it into the cachesupplier
, then continue the loop, at which point it will executeif (supplier ! = null)
The logic in the code block, let’s analyze the code inside the code block again:
if (supplier ! = null) { // supplier might be a Factory or a CacheValue<V> instance V value = supplier.get(); if (value ! = null) { return value; }}Copy the code
Follow up with the supplier. Get () method. We know from the above analysis that the supplier is actually a Factory, so we look at the implementation of Factory, focusing on the get() method:
private final class Factory implements Supplier<V> { ... @Override public synchronized V get() { // serialize access ... // create new value V value = null; try { value = Objects.requireNonNull(valueFactory.apply(key, parameter)); } finally { if (value == null) { // remove us on failure valuesMap.remove(subKey, this); } } // the only path to reach here is with non-null value assert value ! = null; // wrap value with CacheValue (WeakReference) CacheValue<V> cacheValue = new CacheValue<>(value); // put into reverseMap reverseMap.put(cacheValue, Boolean.TRUE); // try replacing us with CacheValue (this should always succeed) if (! valuesMap.replace(subKey, this, cacheValue)) { throw new AssertionError("Should not reach here"); } // successfully replaced us with new CacheValue -> return the value // wrapped by it return value; }}Copy the code
We notice that the emphasis in the code is on objects.requirenonNULL (valueFactory.apply(key, parameter)); What is valueFactory in this code? Let’s look at the definition of proxyClassCache in Proxy
private static final WeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());Copy the code
WeakCache second parameter is new ProxyClassFactory(), let’s look at the corresponding constructor:
public WeakCache(BiFunction<K, P, ? > subKeyFactory, BiFunction<K, P, V> valueFactory) { this.subKeyFactory = Objects.requireNonNull(subKeyFactory); this.valueFactory = Objects.requireNonNull(valueFactory); }Copy the code
Does that make sense? ProxyClassFactory()
What does valueFactory.apply(key, parameter) do? Let’s look directly at the ProxyClassFactory code
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >> { // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { ... /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }}}Copy the code
Throughout overview, it’s not hard to analyze, in the code is in the middle of the create $Proxy the Proxy class, one byte [] proxyClassFile is assembled in a code block after class bytecode file data, through ProxyGenerator. GenerateProxyClass () generation; The bytecode is then dynamically loaded through the Classloader, and a Class instance of the dynamic proxy Class is generated and returned.
. We’ll follow up ProxyGenerator generateProxyClass () method, to see the processing logic in the process of generating the proxy class, look at the key code:.
public static byte[] generateProxyClass(final String var0, Class<? >[] var1, int var2) { ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); final byte[] var4 = var3.generateClassFile(); . return var4; }Copy the code
GenerateClassFile (); generateClassFile(); generateClassFile();
private byte[] generateClassFile() { this.addProxyMethod(hashCodeMethod, Object.class); this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); Class[] var1 = this.interfaces; int var2 = var1.length; int var3; Class var4; for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; Method[] var5 = var4.getMethods(); int var6 = var5.length; for(int var7 = 0; var7 < var6; ++var7) { Method var8 = var5[var7]; this.addProxyMethod(var8, var4); }}... }Copy the code
The code is a bit long, so I won’t expand it all here. Interested friends can follow me to have a look in detail. Add hashCode, Equals, and toString to generate the proxy class. Then the logic is to iterate through all the interfaces of the proxy object and regenerate all its methods. Bytecode is then generated.
Finally, the proxy classes are loaded into the JVM.
Look at theJDK Proxy
Generated proxy classes$Proxy
We output the $Proxy file to a file with the following code:
@Test public void test1(){ System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); RealSubject realSubject = new RealSubject(); RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject); Subject instance = (Subject) proxy.getInstance(); try { byte[] proxychar= ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Subject.class}); OutputStream outputStream = new FileOutputStream("/Users/eamon.zhang/IdeaProjects/own/java-advanced/01.DesignPatterns/design-patterns/"+instance.getClas s().getSimpleName()+".class"); outputStream.write(proxychar); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } instance.request(); System.out.println(instance.getClass()); }Copy the code
$Proxy0 $Proxy0 $Proxy0
public final class $Proxy0 extends Proxy implements Subject { private static Method m1; private static Method m2; 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}); } 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 request() 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); } 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"); m3 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject").getMethod("request"); 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
conclusion
To summarize the steps for implementing JDK Proxy:
- Get a reference to the proxied object and get all its interfaces (via reflection)
JDK Proxy
Class regenerates a new class that implements all the interfaces implemented by the proxied class, andHashCode, equals, toString
These three methods- Dynamically generated
Java
Code, the newly added business logic method by a certain logic code to call (reflected in the code) - Compile the newly generated
Java
The code of.class
file - Reload to
JVM
Running in the
Emulates handwritten JDK Proxy
With this in mind, we can actually try to implement a JDK Proxy manually:
We refer to the implementation principle of JDK Proxy to analyze what we need to write by hand:
- First we need to have a proxy class
MimeProxy
- And then from the proxy class, you need to have
newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)
In this method, the method parameters are:(ClassLoader loader,Class[] interfaces,InvocationHandler h)
, so we need to create oneClassLoader
,InvocationHandler
;
Let’s create it step by step:
- First create
MimeClassLoader
Class, inherited fromClassLoader
And writefindClass()
Methods:
/** * @author extends ClassLoader {private Object} /** * @author extends ClassLoader {private Object target; public MimeClassLoader(Object target) { this.target = target; } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { String classname = target.getClass().getPackage().getName() + "." + name; String filePath = MimeClassLoader.class.getResource("").getPath() + name + ".class"; try { URI uri = new URI("file:///" + filePath); Path path = Paths.get(uri); File file = path.toFile(); if (file.exists()) { byte[] fileBytes = Files.readAllBytes(path); return defineClass(classname, fileBytes, 0, fileBytes.length); } } catch (Exception e) { e.printStackTrace(); } return null; }}Copy the code
- create
MimeInvocationHandler
Class:
/**
* @author eamon.zhang
* @date 2019-10-10 下午2:46
*/
public interface MimeInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}Copy the code
- create
MimeProxy
Class, which is used to assemble proxy classes and load them intoJVM
And return the proxy object:
/** * @author eamon.zhang * @date 2019-10-10 11:00pm */ public class MimeProxy {private static Final String ln = "\r\n"; private static final String semi = ";" ; private static Map<Class, Class> mappings = new HashMap<Class, Class>(); static { mappings.put(int.class, Integer.class); } public static Object newProxyInstance(MimeClassLoader loader, Class<? >[] interfaces, MimeInvocationHandler h) throws IllegalArgumentException { try { // 1. Dynamically generate.java file String SRC = generateSrc(interfaces); // System.out.println(src); / / 2. Java file output to disk String filePath = MimeProxy. Class. The getResource (" "). GetPath (); // System.out.println(filePath); File f = new File(filePath + "$Proxy8.java"); // f.deleteOnExit(); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); / / 3. Compile Java files into. Class files JavaCompiler compiler. = ToolProvider getSystemJavaCompiler (); StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> iterable = sjfm.getJavaFileObjects(f); JavaCompiler.CompilationTask task = compiler.getTask(null, sjfm, null, null, null, iterable); task.call(); sjfm.close(); // 4. Load the. Class file into the JVM class <? > proxyClass = loader.findClass("$Proxy8"); Constructor<? > c = proxyClass.getConstructor(MimeInvocationHandler.class); f.delete(); // 5. Return c.newinstance (h); } catch (Exception e) { e.printStackTrace(); } return null; } /** * generateSrc private static String interfaces (Class<? >[] interfaces) {// here StringBuffer thread-safe StringBuffer sb = new StringBuffer(); sb.append("package ").append(interfaces[0].getPackage().getName()).append(semi).append(ln); sb.append("import ").append(interfaces[0].getName()).append(semi).append(ln); sb.append("import java.lang.reflect.*;" ).append(ln); sb.append("import ").append(interfaces[0].getPackage().getName()).append(".mimeproxy.MimeInvocationHandler;" ).append(ln); sb.append("public class $Proxy8 implements ").append(interfaces[0].getSimpleName()).append(" {").append(ln); sb.append("MimeInvocationHandler h;" + ln); sb.append("public $Proxy8(MimeInvocationHandler h) {").append(ln); sb.append("this.h = h;" ).append(ln); sb.append("}").append(ln); for (Method method : interfaces[0].getMethods()) { Class<? >[] params = method.getParameterTypes(); StringBuffer paramNames = new StringBuffer(); StringBuffer paramValues = new StringBuffer(); StringBuffer paramClasses = new StringBuffer(); for (Class<? > clazz : params) { String type = clazz.getName(); String paramName = toLowerFirstCase(clazz.getSimpleName()); paramNames.append(type).append(" ").append(paramName); paramValues.append(paramName); paramClasses.append(clazz.getName()).append(".class"); for (int i = 0; i < params.length; i++) { paramNames.append(","); paramValues.append(","); paramClasses.append(","); } } sb.append("public ").append(method.getReturnType().getName()).append(" ").append(method.getName()) .append("(").append(paramNames.toString()).append(") {").append(ln); sb.append("try {").append(ln); // Method m = interfaces[0].getName().class.getMethod(method.getName()),new Class[]{paramClasses.toString()}); sb.append("Method m = ").append(interfaces[0].getName()).append(".class.getMethod(\"") .append(method.getName()).append("\", new Class[]{").append(paramClasses.toString()).append("});" ) .append(ln); // return this.h.invoke(this, m, new Object[]{paramValues}, method.getReturnType()); sb.append(hasReturnValue(method.getReturnType()) ? "return " : "") .append(getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", method.getReturnType())) .append(";" ) .append(ln); sb.append("} catch (Error _ex) {}").append(ln); sb.append("catch (Throwable e) {").append(ln); sb.append("throw new UndeclaredThrowableException(e);" ).append(ln); sb.append("}"); sb.append(getReturnEmptyCode(method.getReturnType())).append(ln); sb.append("}"); } sb.append("}").append(ln); return sb.toString(); } /** * Get the return value type ** @param returnClass * @return */ private static String getremptycode (Class<? > returnClass) { if (mappings.containsKey(returnClass)) { return "return 0;" ; } else if (returnClass == void.class) { return ""; } else { return "return null;" ; }} /** * concatenate invocationHandler to execute code ** @param code * @param returnClass * @return */ private static String getCaseCode(String code, Class<? > returnClass) { if (mappings.containsKey(returnClass)) { return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()"; } return code; } @param clazz @return */ private static Boolean hasReturnValue(Class<? > clazz) { return clazz ! = void.class; } private static String toLowerFirstCase(String SRC) {char[] chars = * private static String toLowerFirstCase(String SRC) {char[] chars = src.toCharArray(); chars[0] += 32; return String.valueOf(chars); }}Copy the code
In this way, you write a dynamic proxy of your own, of course, the proxy method is not perfect, but for this example is written, interested friends can try to change it to more general code.
CGlib dynamic proxy
Let’s look at the use of CGlib’s dynamic proxy
use
Start by creating the RealSubject class. Note that this class does not implement any interface:
/** * @author eamon.zhang * @date 2019-10-09 PM 4:22 */ public class RealSubject {public void request(){ System.out.println(" Real processing logic! ") ); }}Copy the code
Then create RealSubjectCglibDynamicProxy proxy class, it must implement the MethodInterceptor interface:
/ * * * @ author eamon. Zhang 4:23 * * @ the date 2019-10-09 afternoon/public class RealSubjectCglibDynamicProxy implements MethodInterceptor { public Object getInstance(Class<? > clazz) {Enhancer Enhancer = new Enhancer(); // which to set as the parent of the new class to be generated, enhancer.setsuperclass (clazz); // Set the callback object, enhancer.setcallback (this); Return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { before(); Object invokeSuper = proxy.invokeSuper(obj, args); after(); return invokeSuper; } private void before() {system.out.println ("前 提 前 提 出 "! ); } private void after() {system.out.println () {system.out.println (); ); }}Copy the code
Now that a simple CGlib dynamic proxy implementation is complete, let’s create the test code:
@Test
public void test(){
RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();
RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);
instance.request();
}Copy the code
Test results:
Front-facing enhancement! Real processing logic! Afterboost!Copy the code
The principle of analysis
The core of both JDK Proxy and CGlib is to create Proxy classes, so we just need to understand the process of creating Proxy classes.
As you can see from the simple example above, to use CGlib dynamic proxy, the proxy class must implement a MethodInterceptor. The source code for the MethodInterceptor interface is as follows:
/** * General-purpose {@link Enhancer} callback which provides for "around advice". * @author Juozas Baliuka <a href="mailto:[email protected]">[email protected]</a> * @version $Id: MethodInterceptor. Java,v 1.8 2004/06/24 21:15:20 Herbyderby Exp $*/ public Interface Extends Callback { /** * All generated proxied methods call this method instead of the original method. * The original method may either be invoked by normal reflection using the Method object, * or by using the MethodProxy (faster). * @param obj "this", the enhanced object * @param method intercepted Method * @param args argument array; primitive types are wrapped * @param proxy used to invoke super (non-intercepted method); may be called * as many times as needed * @throws Throwable any exception may be thrown; if so, super method will not be invoked * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value. * @see MethodProxy */ public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable; }Copy the code
There is only one Intercept method in the interface, passing in the argument:
obj
Represents an enhanced object, that is, an object that implements this interface class;method
Represents the method to be intercepted;args
Represent method parameters;proxy
Represents the method object to fire the parent class;
In the logic getInstance(Class clazz) that creates the proxy object, the enhancer.create() method is called.
/**
* Generate a new class if necessary and uses the specified
* callbacks (if any) to create a new object instance.
* Uses the no-arg constructor of the superclass.
* @return a new instance
*/
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}Copy the code
If necessary, generate a new class and use the specified callback (if any) to create a new object instance. Instantiate the parent class using the constructor of the arguments to the parent class.
The core of this is createHelper(); Method:
private Object createHelper() { preValidate(); Object key = KEY_FACTORY.newInstance((superclass ! = null) ? superclass.getName() : null, ReflectUtils.getNames(interfaces), filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter), callbackTypes, useFactory, interceptDuringConstruction, serialVersionUID); this.currentKey = key; Object result = super.create(key); return result; }Copy the code
The preValidate() method prevalidates whether the callbackTypes and filter are null and how to handle them if they are null.
We then create an EnhancerKey object using key_factory.newinstance () and pass it as a parameter to the super.create(key) method. Let’s look at the create() method. It is found to be a method in the Enhancer class’s parent, AbstractClassGenerator:
protected Object create(Object key) { try { ClassLoader loader = getClassLoader(); Map<ClassLoader, ClassLoaderData> cache = CACHE; ClassLoaderData data = cache.get(loader); if (data == null) { synchronized (AbstractClassGenerator.class) { cache = CACHE; data = cache.get(loader); if (data == null) { Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache); data = new ClassLoaderData(loader); newCache.put(loader, data); CACHE = newCache; } } } this.key = key; Object obj = data.get(this, getUseCache()); if (obj instanceof Class) { return firstInstance((Class) obj); } return nextInstance(obj); } catch (RuntimeException e) { throw e; } catch (Error e) { throw e; } catch (Exception e) { throw new CodeGenerationException(e); }}Copy the code
This method finally calls the nextInstance(obj) method, whose corresponding implementation is in the Enhancer class:
protected Object nextInstance(Object instance) {
EnhancerFactoryData data = (EnhancerFactoryData) instance;
if (classOnly) {
return data.generatedClass;
}
Class[] argumentTypes = this.argumentTypes;
Object[] arguments = this.arguments;
if (argumentTypes == null) {
argumentTypes = Constants.EMPTY_CLASS_ARRAY;
arguments = null;
}
return data.newInstance(argumentTypes, arguments, callbacks);
}Copy the code
The data.newinstance (argumentTypes, arguments, callbacks) method is called again, with the first argument being the constructor type of the proxy object, the second being the constructor parameter of the proxy object, and the third being the corresponding callback object. The source code is as follows:
public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) { setThreadCallbacks(callbacks); try { // Explicit reference equality is added here just in case Arrays.equals does not have one if (primaryConstructorArgTypes == argumentTypes || Arrays.equals(primaryConstructorArgTypes, argumentTypes)) { // If we have relevant Constructor instance at hand, just call it // This skips "get constructors" machinery return ReflectUtils.newInstance(primaryConstructor, arguments); } // Take a slow path if observing unexpected argument types return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments); } finally { // clear thread callbacks to allow them to be gc'd setThreadCallbacks(null); }}Copy the code
We can use cglib’s proxy class to write an in-memory class file to a local disk:
@test public void test1(){// Use cglib's proxy class to write in-memory class files to local disks System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/eamon.zhang/Documents/cglib"); RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy(); RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class); instance.request(); }Copy the code
After executing, you can see the three.class files in the corresponding directory as shown below:
Through the debug trace, we found that the RealSubject? EnhancerByCGLIB? The 5389CDCA is a proxy class generated by CGLib that inherits from the RealSubject class. View the source code through IDEA:
public class RealSubject? EnhancerByCGLIB? 5389cdca extends RealSubject implements Factory { ... static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject? EnhancerByCGLIB? 5389cdca"); Class var1; CGLIB$request$0$Method = ReflectUtils.findMethods(new String[]{"request", "()V"}, (var1 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject")).getDeclaredMethods())[0]; CGLIB$request$0$Proxy = MethodProxy.create(var1, var0, "()V", "request", "CGLIB$request$0"); Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;) Z", "toString", "()Ljava/lang/String;" , "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$equals$1$Method = var10000[0]; CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;) Z", "equals", "CGLIB$equals$1"); CGLIB$toString$2$Method = var10000[1]; CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;" , "toString", "CGLIB$toString$2"); CGLIB$hashCode$3$Method = var10000[2]; CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3"); CGLIB$clone$4$Method = var10000[3]; CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;" , "clone", "CGLIB$clone$4"); } final void CGLIB$request$0() { super.request(); } public final void request() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 ! = null) { var10000.intercept(this, CGLIB$request$0$Method, CGLIB$emptyArgs, CGLIB$request$0$Proxy); } else { super.request(); }}... }Copy the code
As you can see from the source code of the proxy class, the proxy class gets all the methods that are inherited from the parent class, and there’s a MethodProxy that corresponds to that, Methods such as Method CGLIB$request$0$Method and MethodProxy CGLIB$request$0$Proxy are called in the Proxy class reuqest().
InvokeSuper -> CGLIB$request$0() -> Propped object request() method. At this point, we see that the proxy method is called by the invokeSuper method in MethodProxy in the MethodInterceptor.
MethodProxy is key, so let’s look at what it does:
public class MethodProxy { private Signature sig1; private Signature sig2; private CreateInfo createInfo; private final Object initLock = new Object(); private volatile FastClassInfo fastClassInfo; /** * For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class * for similar functionality. */ public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { MethodProxy proxy = new MethodProxy(); proxy.sig1 = new Signature(name1, desc); proxy.sig2 = new Signature(name2, desc); proxy.createInfo = new CreateInfo(c1, c2); return proxy; }... private static class CreateInfo { Class c1; Class c2; NamingPolicy namingPolicy; GeneratorStrategy strategy; boolean attemptLoad; public CreateInfo(Class c1, Class c2) { this.c1 = c1; this.c2 = c2; AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent(); if (fromEnhancer ! = null) { namingPolicy = fromEnhancer.getNamingPolicy(); strategy = fromEnhancer.getStrategy(); attemptLoad = fromEnhancer.getAttemptLoad(); }}}...Copy the code
Moving on to the invokeSuper() method:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
private static class FastClassInfo
{
FastClass f1;
FastClass f2;
int i1;
int i2;
}Copy the code
The above code calls the FastClass of the proxy class and executes the proxy method. Remember generating three class files earlier? RealSubject? EnhancerByCGLIB? 5389cdca? FastClassByCGLIB? 57b94d72.class is the FastClass of the proxy class, RealSubject? FastClassByCGLIB? Ed23432. class is the FastClass of the proxied class.
The CGLib dynamic proxy executes the proxy method more efficiently than the JDK because CGLib uses the FastClass machine.
- Generate a Class for the proxy Class and a proxied Class, and this Class allocates one for the proxy Class or proxied Class’s methods
The index (int)
. thisindex
As an input parameter,FastClass
You can directly locate the method to be called and call it directly, which eliminates the need for reflection calls, so the call efficiency ratioJDK
Dynamic proxy calls high through reflection.
The principle of Cglib dynamic proxy is basically clear. If you are interested in the details of the code, you can further study it.
Compare JDK Proxy with CGlib
JDK
Dynamic proxy isimplementationThe interface of the proxied object,CGLib
是inheritanceThe proxied object.JDK
和CGLib
Both generate bytecode at run time,JDK
Is to write directlyClass
Bytecode,CGLib
useASM
Framework to writeClass
Bytecode,Cglib
Proxy implementations are more complex,Generating proxy classes 比JDK
Efficiency is low.JDK
The proxy method is called by reflection,CGLib
Is through theFastClass
Mechanisms call methods directly,CGLib
Execution efficiencyhigher
The proxy pattern versus Spring
Proxy selection principles in Spring
- when
Bean
When there is an implementation interface,Spring
Will useJDK
Dynamic proxy of - when
Bean
When no interface is implemented,Spring
chooseCGLib
. Spring
It can be used forcibly through configurationCGLib
, just inSpring
Add the following code to the config file:
<aop:aspectj-autoproxy proxy-target-class="true"/>Copy the code
Reference data: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
conclusion
Essential difference between static proxy and dynamic proxy
- A static proxy can only be manually performed. If a new method is added to the proxy class, the proxy class must be added at the same time, which violates the open/close principle.
- Dynamic proxy adopts the method of dynamically generating code at run time, which removes the limitation of extending the proxied class and follows the open and closed principle.
- If the dynamic proxy wants to extend the enhanced logic of the target class, combined with the policy mode, it only needs to add the policy class to complete, without modifying the code of the proxy class.
Advantages and disadvantages of the proxy model
advantages
- The proxy pattern separates the proxy object from the actual target object being invoked.
- The coupling degree of the system is reduced to a certain extent, and the expansibility is good.
- It can protect the target object.
- You can enhance the functionality of the target object
disadvantages
- The proxy pattern increases the number of classes in the system design.
- Adding a proxy object to both the client and the target object slows down request processing.
- Increased system complexity.
—
The source directory for this article: https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/main/java/com/eamon/javadesi gnpatterns/proxy
Test class source directory: https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/test/java/com/eamon/javadesi gnpatterns/proxy
Welcome everyone star source code, common progress, I will learn in accordance with the git outline at the same time, record the article and source ~
If you have any mistakes or suggestions, please point them out in the comments and learn from us
This article is published by OpenWrite!