Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to write java Agent

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article introduces the relevant knowledge of "how to write java agent". In the operation of actual cases, many people will encounter such a dilemma. Next, let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

One: the commonly used java agent mode

Generally speaking, there are three kinds of proxy modes that often do java development. The first is static proxy, which is relatively simple and has no magic wand. The other two are JDK proxy and cglib proxy, which proxies the interface proxy and the class class itself respectively. The jdk proxy requires that the class must have one or more interfaces. Bytecode enhancement to the interface to implement a new class class in memory to reflect the implementation class that calls the user target. It needs to be stated here that both the cglic proxy and the jdk proxy occupy the method area resources (jdk8 is called the original space) in memory to achieve the proxy purpose, while the cglib proxy implements the proxy with bytecode enhancement on the class class itself. About more cglib and jdk agent related content you can google search, there are a lot of online here do not do any more explanation. Let's abandon the complex source code of jdk and cglib to implement a proxy pattern ourselves to gain a deeper understanding of how agents are formed.

Second: java native jdk proxy demo and source code analysis

Proxy pattern means that a proxy object is provided to an object, and the reference to the original object is controlled by the proxy object. Popularly speaking, the agency model is a common intermediary in our lives. What is the use of this model? It can enhance the function of the original object on the basis of the original object, such as logging and transaction operations before and after the original object calls a method. Spring AOP uses proxy mode. How to implement the proxy mode? First, let's look at static proxies. A static proxy is a compiled proxy class that exists before the program runs. There are four steps to implementing a static proxy:

① defines the business interface

② is proxied to implement the business interface

③ defines the proxy class and implements the business interface

④ can finally be called through the client. (here it can be understood as the content in the main method of the program)

We follow this step to implement a static proxy. Requirements: print logs before and after adding a user to the database.

JDK DEMO example

IUserService.java

Public interface IUserService {void add (String name);}

UserServiceImpl.java

Public class UserServiceImpl implements IUserService {@ Override public void add (String name) {System.out.println ("users inserted in the database:" + name+ ");}}

MyInvocationHandler.java

Public class MyInvocationHandler implements InvocationHandler {/ / proxied object, Object type private Object target; public MyInvocationHandler (Object target) {this.target = target;} @ Override public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {System.out.println ("ready to insert data into the database"); Object returnvalue = method.invoke (target, args) System.out.println ("database inserted successfully"); return returnvalue;}}

Test class

Public static void main (String [] args) {IUserService target = new UserServiceImpl (); MyInvocationHandler handler = new MyInvocationHandler (target); IUserService proxyObject = (IUserService) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), target.getClass (). GetInterfaces (), handler); proxyObject.add ("Zhang Yulong");}

It is very simple to use, and there are a lot of demo on the Internet, so it is not fully explained. If you are not familiar with this code, you should first understand how to use the jdk agent, and then come back to see the following source code analysis.

Depth Analysis of JDK Agent Source Code

For a faster and better understanding of this section, it is recommended to read the blog while looking at the source code (this article JDK 1.8). After all, it is better to put it into practice. Proxy.newProxyInstance (ClassLoaderloader, Class [] interfaces, InvocationHandler h) produces a proxy object, so we go into the implementation of newProxyInstance:

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 {if (sm! = null) {checkNewProxyPermission (Reflection.getCallerClass (), cl);} final Constructor cons = cl.getConstructor (constructorParams); final InvocationHandler ih = h If (! Modifier.isPublic (cl.getModifiers () {AccessController.doPrivileged (new PrivilegedAction () {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) }}

The core of this code is to get the Class object of the proxy class through getProxyClass0 (loader, intfs), and then get the construction method through the Class object, and then create the proxy object. The next step is to look at getProxyClass0.

/ / this method is also the method private static Class getProxyClass0 (ClassLoader loader, Class...) under the Proxy 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 / / means: if the proxy class is defined by the specified classloader loader and implements the given interface interfaces, / / then the cached proxy class object is returned, otherwise the proxy class is created using ProxyClassFactory. Return proxyClassCache.get (loader, interfaces);}

When you see proxyClassCache here, if you have Cache, you will know that it means cache, which echoes the previous Look up or generate the designated proxy class. Query (already in the cache) or generate a comment on the class object of the specified proxy class.

ProxyClassCache is an object of the WeakCache class, calling proxyClassCache.get (loader, interfaces); you can get a cached proxy class or create a proxy class (in the case of no cache). It shows that there is a get method in WeakCache. Let's first take a look at the definition of the WeakCache class (here we only give the definition and constructor of variables):

/ / K represents the type of key, P represents the type of parameter, and V represents the type of value. / / WeakCache > proxyClassCache indicates that the value stored in proxyClassCache is a Class object, which is exactly the proxy class object we need. Final class WeakCache {private final ReferenceQueue refQueue = new ReferenceQueue (); / / the key type is Object for supporting null key private final ConcurrentMap map = new ConcurrentHashMap (); private final ConcurrentMap reverseMap = new ConcurrentHashMap (); private final BiFunction subKeyFactory; private final BiFunction valueFactory; public WeakCache (BiFunction subKeyFactory, BiFunction valueFactory) {this.subKeyFactory = Objects.requireNonNull (subKeyFactory); this.valueFactory = Objects.requireNonNull (valueFactory);}

The map variable is the core variable to implement the cache, and it is a dual Map structure: (key, sub-key)-> value. Where key is the object wrapped by the incoming Classloader, and the sub-key is generated by KeyFactory () passed from the WeakCache constructor. Value is the object that generates the proxy class, which is generated by ProxyClassFactory () passed down from the WeakCache constructor.

Well, generally speaking, after talking about the role of the class WeakCache, let's go back to the proxyClassCache.get (loader, interfaces); code. Get is the method in WeakCache. The source code is as follows

/ / K and P are generics in the definition of WeakCache, key is the class loader, parameter is the interface class array public V get (K key, P parameter) {/ / check that parameter is not empty Objects.requireNonNull (parameter); / / clear invalid cache expungeStaleEntries (); / / cacheKey is the first-level key in (key, sub-key)-> value, Object cacheKey = CacheKey.valueOf (key, refQueue) / / lazily install the 2nd level valuesMap for the particular cacheKey / / get the ConcurrentMap object according to the first-level key. If it does not exist before, create a new ConcurrentMap and put it in the map together with cacheKey (first-level key). ConcurrentMap valuesMap = map.get (cacheKey); if (valuesMap = = null) {ConcurrentMap oldValuesMap = map.putIfAbsent (cacheKey, valuesMap = new ConcurrentHashMap ()); if (oldValuesMap! = null) {valuesMap = oldValuesMap }} / / create subKey and retrieve the possible Supplier stored by that / / subKey from valuesMap / / this is the code that calls to generate sub-key. Above we have seen how to generate Object subKey = Objects.requireNonNull (subKeyFactory.apply (key, parameter)); / / get supplier Supplier supplier = valuesMap.get (subKey) through sub-key / / supplier is actually the factory Factory factory = null; while (true) {/ / if there is a supplier in the cache, get the proxy object directly through the get method, return, and it's over. Analyze the get method later. If (supplier! = null) {/ / supplier might be a Factory or a CacheValue 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 / / all of the following code aims to create a Factory object if there is no supplier in the cache Assign the factory object to supplier safely in a multithreaded environment. / / because in while (true), after a successful assignment, you go back to the above to call the get method, and the return ends. 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);}}

So next let's look at the get method in the Factory class.

Public synchronized V get () {/ / serialize access / / re-check Supplier supplier = valuesMap.get (subKey) / / check whether the supplier obtained is the current object if (supplier! = this) {/ / something changed while we were waiting: / / might be that we were replaced by a CacheValue / / or were removed because of failure-> / / return null to signal WeakCache.get () to retry / / the loop return null } / / else still us (supplier = = this) / / create new value V value = null This is where the try {/ / proxy class calls the / / valueFactory generated by valueFactory that we pass in new ProxyClassFactory () / / We analyze the apply method value = Objects.requireNonNull (valueFactory.apply (key, parameter) of ProxyClassFactory () later. } 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) / / wraps value as a weak reference CacheValue cacheValue = new CacheValue (value); / / put into reverseMap / / reverseMap is used to achieve cache validity 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;}}

Come to the apply method of ProxyClassFactory, where the proxy class is generated.

/ / the BiFunction here is a functional interface, which can be understood as using the two types of T Proxy U as parameters to get the return value of R type private static final class ProxyClassFactory implements BiFunction > {/ / prefix for all proxy class names / / the prefix private static final String proxyClassNamePrefix = "$Proxy" of all proxy class names / / next number to use for generation of unique proxy class names / / counter used to generate proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong (); @ Override public Class apply (ClassLoader loader, Class [] interfaces) {Map 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 actually represents an * interface. * / if (! interfaceClass.isInterface ()) {throw new IllegalArgumentException (interfaceClass.getName () + "is not an interface");} / * * Verify that this interface is not a duplicate. * / if (interfaceSet.put (interfaceClass, Boolean.TRUE)! = null) {throw new IllegalArgumentException ("repeated interface:" + interfaceClass.getName ());}} / / the package name of the generated proxy class String proxyPkg = null / / package to define proxy class in / / proxy class access control character: public, final int accessFlags = Modifier.PUBLIC | Modifier.FINAL; / * * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. * / / verify that all non-public interfaces are in the same package Public logic does not need to deal with / / generate package name and class name. The package name defaults to com.sun.proxy, and the class name defaults to $Proxy plus a self-incrementing integer value / / if the proxied class is non-public proxy interface, use the same package name for (Class intf: interfaces) {int flags = intf.getModifiers () as the proxied class interface. 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 + ".";} / * Choose a name for the proxy class to generate. * / long num = nextUniqueNumber.getAndIncrement (); / / the fully qualified name of the proxy class, such as com.sun.proxy.$Proxy0.calss String proxyName = proxyPkg + proxyClassNamePrefix + num; / * * Generate the specified proxy class. * / Core part, generate the bytecode byte [] proxyClassFile = ProxyGenerator.generateProxyClass (proxyName, interfaces, accessFlags) of the proxy class Try {/ / loads the proxy class into JVM, so the dynamic proxy process basically ends the return defineClass0 (loader, proxyName, proxyClassFile, 0, proxyClassFile.length). } catch (ClassFormatError e) {/ * * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). * / throw new IllegalArgumentException (e.toString ());}

The analysis is actually done here, but with a deeper attitude, we decided to take a look at the dynamic proxy bytecode generated by JDK, so we saved the bytecode to the class file on disk. The code is as follows:

Public static void main (String [] args) {IUserService target = new UserServiceImpl (); MyInvocationHandler handler = new MyInvocationHandler (target) / / the first parameter is to specify the class loader of the proxy class (we passed in the class loader of the current test class) / / the second parameter is the interface that the proxy class needs to implement (we pass in the interface implemented by the proxy class, so that the generated proxy class and the proxied class implement the same interface) / / the third parameter is invocation handler, which is used to handle method calls. Here pass in our own implementation handler IUserService proxyObject = (IUserService) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), target.getClass (). GetInterfaces (), handler); proxyObject.add ("Zhang Yulong"); String path = "D:/$Proxy0.class"; byte [] classFile = ProxyGenerator.generateProxyClass ("$Proxy0", HelloworldImpl.class.getInterfaces ()); FileOutputStream out = null Try {out = new FileOutputStream (path); out.write (classFile); out.flush ();} catch (Exception e) {e.printStackTrace ();} finally {try {out.close ();} catch (IOException e) {e.printStackTrace () }

Running this code will generate a file called $Proxy0.class on disk D. Through the decompilation tool, we get that the proxy class generated for us by JDK looks like this:

/ / Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.// Jad home page: http://kpdus.tripod.com/jad.html// Decompiler options: packimports (3) fieldsfirst ansi space import com.zhb.jdk.proxy.IUserService;import java.lang.reflect.*;public final class $Proxy0 extends Proxy implements IUserService {private static Method M1; private static Method m2; private static Method m3; private static Method M0 / / the constructor of the proxy class, whose argument is the InvocationHandler instance. / / the Proxy.newInstance method creates the public $Proxy0 (InvocationHandler invocationhandler) {super (invocationhandler) of the proxy instance through this constructor. } / / three methods in the Object class, equals,toString, hashCode public final boolean equals (Object obj) {try {return ((Boolean) super.h.invoke (this, M1, new Object [] {obj})) .booleanValue () } catch (Error) {} catch (Throwable throwable) {throw new UndeclaredThrowableException (throwable);}} public final String toString () {try {return (String) super.h.invoke (this, m2, null) } catch (Error) {} catch (Throwable throwable) {throw new UndeclaredThrowableException (throwable) }} / / Interface proxy method public final void add (String s) {try {/ / invocation handler's invoke method is called here super.h.invoke (this, m3, new Object [] {s}); return } catch (Error) {} catch (Throwable throwable) {throw new UndeclaredThrowableException (throwable);}} public final int hashCode () {try {/ / calls the invoke method here. Return ((Integer) super.h.invoke (this, M0, null). IntValue ();} catch (Error) {} catch (Throwable throwable) {throw new UndeclaredThrowableException (throwable) }} / / static code blocks do some initialization of variables static {try {M1 = Class.forName ("java.lang.Object") .getMethod ("equals", new Class [] {Class.forName ("java.lang.Object")}) M2 = Class.forName ("java.lang.Object"). GetMethod ("toString", new Class [0]); m3 = Class.forName ("com.zhb.jdk.proxy.IUserService") .getMethod ("add", new Class [] {Class.forName ("java.lang.String")}) M0 = Class.forName ("java.lang.Object"). GetMethod ("hashCode", new Class [0]);} catch (NoSuchMethodException nosuchmethodexception) {throw new NoSuchMethodError (nosuchmethodexception.getMessage ());} catch (ClassNotFoundException classnotfoundexception) {throw new NoClassDefFoundError (classnotfoundexception.getMessage ());}

Three methods of the Object class are generated: toString,hashCode,equals. And the way we need to be represented.

Cache clear mechanism of JDK proxy class

As we all know, more and more class are proxied in the project, so jdk will use a cache way to prevent the same proxy interface from repeatedly generating class, which affects performance, and the implementation is not very elegant, so now there will be a problem. When the classloader has no dependency in memory, the proxied proxy class is actually meaningless, so you need to empty the useless cache. Java Proxy uses a very clever "weak reference mechanism". Let's take a look at the following code

Let's continue to look at the source code of the get method.

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 valuesMap = map.get (cacheKey) If (valuesMap = = null) {ConcurrentMap oldValuesMap = map.putIfAbsent (cacheKey, valuesMap = new ConcurrentHashMap ()); if (oldValuesMap! = null) {valuesMap = oldValuesMap;}}.}

There is a method expungeStaleEntries in the source code. Let's go in and have a look at this method.

Private void expungeStaleEntries () {CacheKey cacheKey; while ((cacheKey = (CacheKey) refQueue.poll ())! = null) {cacheKey.expungeFrom (map, reverseMap);}}

Let's see what the source code of the expungeFrom method does.

Void expungeFrom (ConcurrentMap > map, ConcurrentMap)

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report