Detailed explanation of JAVA dynamic agent
- 2021-07-10 19:38:45
- OfStack
Document update instructions
September 24, 2018 First draft of v1.0
Agents are very common in life, such as matchmaking websites, which are actually agents looking for objects; There are also social security agents and personnel agents; There is also looking for scalpers to grab tickets, which is actually an agent; These agents are also implemented in JAVA.
1. Why do you want to proxy dynamically
The function of dynamic agent is to enhance the existing method without modifying the original code.
Key points:
Do not modify the existing code (meet the requirements of the design pattern)
Enhance the existing method
2. Take a chestnut
Let's use a very simple example to illustrate:
Hello
Class, with 1
introduction
Method.
Now our need is not to modify
Hello
Class
introduction
Method, in the
introduction
Before first
sayHello
, in
introduction
Then sayGoodBye
3. Implementation mode
In JAVA, there are two ways to realize dynamic proxy, one is provided by JDK, and the other is provided by CgLib, the third library. The characteristics are as follows:
JDK Dynamic Proxy: The proxy target class needs to implement the interface
CgLib
Mode: You can implement dynamic proxy for any class
3.1. JDK Dynamic Proxy
The JDK dynamic proxy needs to implement the interface, and then implements the dynamic proxy by enhancing the interface method
Therefore, to use JDK dynamic proxy, we must first create an interface, and the proxy method should be in this interface
3.1. 1, Create 1 Interface
We create 1 interface as follows:
Personal.java
public interface Personal {
/**
* Proxy method
*/
void introduction();
}
3.1. 2, implementing the interface
Create an interface implementation class and complete the
introduction
Method
introduction
0
public class PersonalImpl implements Personal {
@Override
public void introduction() {
System.out.println(" I'm a programmer! ");
}
}
3.1. 3, Create a proxy class
The key to JDK proxy is this proxy class, which needs to be implemented
introduction
1
In the proxy class, all method calls are easily distributed to
introduction
2
Method. We're in
introduction
2
Method to complete the enhancement of the method
introduction
4
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyFactory<T> implements InvocationHandler {
/**
* Target object
*/
private T target;
/**
* Constructor is passed into the target object
*
* @param target Target object
*/
public JDKProxyFactory(T target) {
this.target = target;
}
/**
* Get the proxy object
*
* @return Get proxy
*/
public T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Enhance the method
System.out.println(" Hello, everyone! ");
// Call the original method
Object result = method.invoke(target, args);
// Method enhancement
System.out.println(" See you later! ");
return result;
}
}
In this way, the code of JDK dynamic agent is completed, and then write a test code
3.1. 4, Writing Test Code
To facilitate testing, we wrote 1
introduction
5
Method
At the same time, in order to view
introduction
6
File, and also added 1
introduction
7
Method, which can change the generated by the dynamic proxy
introduction
8
Output to file
introduction
9
import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyTest {
@Test
public void testJdkProxy() {
// Generate the target object
Personal personal = new PersonalImpl();
// Get the proxy object
JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
Personal proxy = proxyFactory.getProxy();
// Will proxy Adj. class ByteCode output to file
generatorClass(proxy);
// Call proxy object
proxy.introduction();
}
/**
* Object's class ByteCode output to file
*
* @param proxy Proxy class
*/
private void generatorClass(Object proxy) {
FileOutputStream out = null;
try {
byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
out.write(generateProxyClass);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
}
}
}
3.1. 5, View Run Results
As you can see, running
introduction
5
Method, the console prints as follows:
Hello, everyone!
I'm a programmer!
See you later!
We're in
introduction
Method before and after the successful addition of functionality, so that the programmer's self-introduction instantly become more polite.
3.1. 6, Exploring the Secrets of Dynamic Agents
There is not much code for dynamic proxy, so
Hello
2
How does the bottom help us achieve it?
During the test, we output the class bytecode of the dynamically generated proxy class to the file, so we can decompile it.
The result is a little long, so it won't all be posted, but we can see that there is one in it
introduction
The method is as follows:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
public final void introduction() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
It turns out that the generated proxy object references our
introduction
1
, and then in the
introduction
Method calls the
introduction
1
Adj.
introduction
, and
introduction
1
Is a proxy class written by us, where we add
sayHello
And
introduction
0
Operation of the original object, and then also calls the
introduction
Method, thus completing the dynamic proxy.
3.2. CgLib Dynamic Agent
CgLib Dynamic
3.2. 1, Create Proxy Object
Due to
CgLib
There is no need to implement an interface, so we don't need to create an interface file (of course, you have no problem with an interface)
Directly create the target class and implement the
introduction
Method
introduction
0
public class PersonalImpl {
public void introduction() {
System.out.println(" I'm a programmer! ");
}
}
3.2. 2, Create Proxy Class
Similarly, we also need to create proxy classes and implement enhanced logic here. This time, we are not implementing
introduction
1
Interface, but implements the
CgLib
Provided interface
MethodInterceptor
, they are all similar,
MethodInterceptor
All method calls are handed to the
intercept
Handle, we are in
intercept
Add processing logic.
CgLibProxyFactory.java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CgLibProxyFactory<T> implements MethodInterceptor {
/**
* Get the proxy object
*
* @param tClass Proxy target object
* @return Proxy object
*/
public T getProxyByCgLib(Class<T> tClass) {
// Create an enhancer
Enhancer enhancer = new Enhancer();
// Set the class object of the class to be enhanced
enhancer.setSuperclass(tClass);
// Set callback function
enhancer.setCallback(this);
// Get the enhanced proxy object
return (T) enhancer.create();
}
/**
* Proxy class method call callback
*
* @param obj This is the proxy object, which is [ Target object ] Subclass of
* @param method [ Target object ] Method of
* @param args Parameter
* @param proxy Methods of proxy objects
* @return Returns the result to the caller
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(" Hello, everyone! ");
Object result = proxy.invokeSuper(obj, args);
System.out.println(" See you later! ");
return result;
}
}
3.2. 3, Writing Test Code
In the test method just now, we added 1
cglib
Test method of:
@Test
public void testCgLibProxy() {
// Generate the proxy target object
PersonalImpl personal = new PersonalImpl();
// Get the proxy class
CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());
// Will proxy Adj. class ByteCode output to file
generatorClass(proxy);
// Call proxy object
proxy.introduction();
}
3.2. 4, View Run Results
Running the test case, you can see the following
Hello
2
The realization of 1 kind of effect
Hello, everyone!
I'm a programmer!
See you later!
3.2. 5, Exploring the Secrets of Dynamic Agents
Follow
Hello
2
Test 1, let's also look at the generated
introduction
6
Documents
public final void introduction() throws {
try {
super.h.invoke(this, m7, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
It can be found that, with
Hello
2
There is no difference between the dynamic proxy of.
4. How to choose
Since there are two ways to implement it, how should we choose it?
On two principles:
The target class has an interface implementation,
Hello
2
And
CgLib
You can choose, just be happy
The target class does not implement any interface, so you can only use the
CgLib
It's over
5. Postscript
In fact, when I first saw the dynamic proxy, I couldn't understand why we threw the target class to the proxy class when we all put the target class new out. Why not call the method corresponding to the target class directly?
Later, I discovered that I didn't understand the use scenario of dynamic agents. The scenario is very clear, that is:
Do not modify the existing code (meet the requirements of the design pattern)
Enhance the existing method
The key is enhancement. We can add a lot of processing logic in the proxy class to achieve enhancement effect. Just like scalpers grab tickets better than us.