Java proxy
There are several common agents in Java:
-
Static agent
-
Dynamic proxy based on JDK
-
Dynamic proxy based on CGlib
-
Spring’s dynamic proxy
-
Other forms of dynamic proxy
Jdk-based proxy classes and proxied classes must implement the same interface.
Spring-based Dynamic Proxy (Spring+Aspectj)
Spring’s dynamic proxies are based on JDK dynamic proxies and CGlib dynamic proxies. In Spring’s dynamic proxy, there is the following configuration:
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setProxyTargetClass(true);
proxyFactory.setOptimize(true);
proxyFactory.setFrozen(true);
Copy the code
SetProxyTargetClass: this indicates whether it represents the target class. The default value is false, meaning the proxy interface. When set to true, the proxy class.
What does that mean? By default, Spring’s dynamic proxy is based on the JDK’s dynamic proxy interface. When setting proxyTargetClass to true, Spring’s dynamic proxy is based on CGlib’s dynamic proxy class.
SetOptimize: This indicates that optimization policies are generated for the agent. The default value is false, meaning that it is not enabled. When this parameter is set to true, the JDK proxy is used if the proxy object has an implementation interface. If the proxy object does not have an implementation interface, the CGlib proxy is used.
SetFrozen: the configuration of the agent cannot be modified after the agent is configured.
JDK proxies can only proxy objects that implement interfaces, whereas CGlib can proxy objects that do not implement interfaces and can also proxy objects that have interfaces. CGlib is a dynamic proxy that can be used in the JDK.
CGlib is slow to create proxies, but very fast to run once they are created, whereas JDK dynamic proxies are the opposite. It is recommended that you use CGlib to create proxies during system initialization and place them in the Spring ApplicationContext for later use.
One small requirement: add log printing to the old method
Suppose we now have a Calculator class that represents a Calculator that can add, subtract, multiply and divide
public class Calculator {
/ / add
public int add(int a, int b) {
int result = a + b;
return result;
}
/ /
public int subtract(int a, int b) {
int result = a - b;
return result;
}
// Multiply, divide...
}
Copy the code
There is a requirement to print a log before and after each method execution. Do you have a good plan?
Directly modifying
The most intuitive idea for many people is to modify the Calculator class directly:
public class Calculator {
/ / add
public int add(int a, int b) {
System.out.println("Start add method...");
int result = a + b;
System.out.println("End of add method...");
return result;
}
/ /
public int subtract(int a, int b) {
System.out.println("Subtract method starts...");
int result = a - b;
System.out.println("Subtract method ends...");
return result;
}
// Multiply, divide...
}
Copy the code
The above scheme is problematic:
- Direct modification of the source program, inconsistent with the open and close principle. Should be open for extension, closed for modification
- If Calculator had dozens or hundreds of methods, it would be too much modification
- Duplicate code exists (all logs are printed before and after core code)
- Log printing is hard coded in the agent class, which is not conducive to later maintenance: for example, when you finish writing after a whole morning, the team leader tells you that this function is cancelled, so you have to open Calculator again and delete the log printing code for ten minutes!
So, this kind of scheme PASS!
A proxy is a pattern that provides indirect access to a target object through a proxy. In this way, it is easy to add additional functional operations on the basis of target realization, such as pre-intercept and post-intercept, to meet their own business needs.
The implementation of a static proxy is simple: write a proxy class that implements the same interface as the target object and maintains a reference to the target object internally. The constructor inserts the target object, invokes the target object’s method of the same name in the proxy object, and adds the required business functions such as pre-intercept and post-intercept.
As described above, the proxy class and the target class need to implement the same interface, so I’m going to do this:
- Extract Calculator as interface
- Create the target class CalculatorImpl to implement Calculator
- Create the proxy class CalculatorProxy to implement Calculator
interface
/** * Calculator interface */
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
Copy the code
Target object implementation class
/** * The object implements the Calculator interface */
public class CalculatorImpl implements Calculator {
/ / add
public int add(int a, int b) {
int result = a + b;
return result;
}
/ /
public int subtract(int a, int b) {
int result = a - b;
return result;
}
// Multiply, divide...
}
Copy the code
The proxy object implementation class
/** * The proxy object implements the Calculator interface
public class CalculatorProxy implements Calculator {
// The proxy object maintains a target object reference internally
private Calculator target;
// The constructor passes in the target object
public CalculatorProxy(Calculator target) {
this.target = target;
}
// Call add on the target object and print the log before and after
@Override
public int add(int a, int b) {
System.out.println("Start add method...");
int result = target.add(a, b);
System.out.println("End of add method...");
return result;
}
// Call subtract of the target object and print the log before and after
@Override
public int subtract(int a, int b) {
System.out.println("Subtract method starts...");
int result = target.subtract(a, b);
System.out.println("Subtract method ends...");
return result;
}
// Multiply, divide...
}
Copy the code
Use proxy objects to add, subtract, multiply and divide, and print a log
public class Test {
public static void main(String[] args) {
// Insert the target object into the proxy object through the constructor
Calculator calculator = new CalculatorProxy(new CalculatorImpl());
// The proxy object calls the target object method to complete the calculation and prints logs before and after
calculator.add(1.2);
calculator.subtract(2.1); }}Copy the code
Advantages of static proxies: You can extend and intercept the functionality of the target object without modifying it. But it only addresses the first of four weaknesses:
- Direct modification of the source program, inconsistent with the open and close principle. Should be open for extensions, closed for modifications √
- If Calculator has dozens or hundreds of methods, the amount of modification is too large
- Duplicate code exists (all logs are printed before and after the core code) ×
- Log printing is hard coded in the agent class, which is not conducive to later maintenance: for example, when you finish writing after a whole morning, the team leader tells you that this function is cancelled, so you have to open Calculator again and delete all the new code in ten minutes! x
Static proxy problems
In the example above, the proxy class is written in advance and implements the same interface as the target object class. Since CalculatorImpl requires logging, we wrote CalculatorProxy, passed in the constructor, and added the enhancement code while calling the method with the same name of the target object.
But there’s a problem! The proxy object constructor has a parameter type of Calculator, which means that it can only accept Calculator implementation objects. That is, the proxy class CalculatorProxy we wrote can only proxy Calculator, they are bound to death!
If we were to overhaul the system and add logging to other classes, we would have to write a proxy class for each of the other hundreds of interfaces…
It’s too cumbersome to write a class and implement the interface yourself. Come to think of it, ** what we really want is not a proxy class, but a proxy object! ** So, can you make the JVM automatically generate proxy objects based on the interface?
For example, is there a method that if I pass in an interface, it automatically returns a proxy object to me?
The answer is yes.
Static agent
Specific practices are as follows:
1. Write a proxy class for each existing class and have it implement the same interface as the target class (assuming both)
2. When creating a proxy object, use the constructor to insert a target object, then call a method with the same name of the target object inside the proxy object’s method, and print logs before and after the invocation. That is, the proxy object = enhancement code + target object (the original object), with the proxy object, the original object is no longer used
Pitfalls of static proxies
The programmer manually writes the corresponding proxy class for each target class. If the current system already has hundreds or thousands of classes, it’s too much work. So, the direction of our efforts now is: how to write less or no proxy class, but can accomplish the proxy function?
Feasibility analysis of creating objects through interfaces
Review the object creation process
First, for many beginners, the relationship between classes and objects looks like this:
You know that source code compiled by the Javac command produces bytecode files (.class files) on disk, and you know that the Java command starts the JVM to load bytecode files into memory, but that’s as far as it goes. They don’t know exactly what happens when the bytecode files are loaded into memory to produce objects in the heap.
Everything is an object, and bytecode files are also objects. Once it’s loaded into memory, the JVM creates an object for it, and all subsequent instances of that class use it as a template. This object is called a Class object, and it is an instance of Class.
The Class Class is used to describe all classes, like the Person Class, the Student Class… So how do I create a Class object of the Person Class from the Class Class? Do this:
Class clazz = new Class();
Copy the code
This is a Class object of the Student Class. A little dizzy…
In fact, programmers cannot create a Class object themselves; it is only created by the JVM.
- The constructor of the Class Class is private, preventing the creation of Class objects through new. When a program needs a Class, the JVM itself calls this constructor, passing in a ClassLoader to load the bytecode file into memory, and then creating the corresponding Class object for it
- To make this distinction easier, the Class object is denoted as Class, Class
So let’s take this opportunity to look at classes and objects in a different way:
In other words, to get an instance of a Class, the key is to get the Class object of that Class first! ** It’s just that the new keyword is so convenient that it hides so many of the underlying details that I didn’t even realize Class objects existed when I first started learning Java.
The difference between interface Class and Class Class
Let’s examine the difference between an interface Class and a Class Class. Take the Class object of the Calculator interface and the Class object of the CalculatorImpl implementation Class:
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
public class ProxyTest {
public static void main(String[] args) {
/* The Calculator interface's Class object can be obtained in three ways: 1.class.forname (XXX) 2.xxx.class 3.xxx.getClass() note that we are not creating a new Class object, but the virtual machine is loading and creating the Class object */
Class<Calculator> calculatorClazz = Calculator.class;
// Constructor information for the Calculator interface
Constructor[] calculatorClazzConstructors = calculatorClazz.getConstructors();
// Method information for the Calculator interface
Method[] calculatorClazzMethods = calculatorClazz.getMethods();
/ / print
System.out.println("------ constructor information for interface Class ------");
printClassInfo(calculatorClazzConstructors);
System.out.println("------ interface Class method information ------");
printClassInfo(calculatorClazzMethods);
//Calculator implements the Class object of the Class
Class<CalculatorImpl> calculatorImplClazz = CalculatorImpl.class;
//Calculator implements the constructor information for the classConstructor<? >[] calculatorImplClazzConstructors = calculatorImplClazz.getConstructors();//Calculator implements method information for the class
Method[] calculatorImplClazzMethods = calculatorImplClazz.getMethods();
/ / print
System.out.println("------ Implements constructor information for Class Class ------");
printClassInfo(calculatorImplClazzConstructors);
System.out.println("------ Method information for implementing the Class ------");
printClassInfo(calculatorImplClazzMethods);
}
public static void printClassInfo(Executable[] targets){
for (Executable target : targets) {
// Constructor/method name
String name = target.getName();
StringBuilder sBuilder = new StringBuilder(name);
// Concatenate the left parenthesis
sBuilder.append('(');
Class[] clazzParams = target.getParameterTypes();
// Concatenate parameters
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(', ');
}
// Remove the comma of the last argument
if(clazzParams! =null&& clazzParams.length ! =0) {
sBuilder.deleteCharAt(sBuilder.length()-1);
}
// Concatenate the close parenthesis
sBuilder.append(') ');
// Print the constructor/methodSystem.out.println(sBuilder.toString()); }}}Copy the code
Running results:
- The Interface Class object has no constructor, so the Calculator interface cannot directly new the object
- Implementation Class objects have constructors, so CalculatorImpl implementation classes can new objects
- Interface Class objects have two methods Add () and subtract()
- We implement Class objects in addition to add(), subtract(), and subtract() methods from Object
That is, the Class information of the interface and implementation classes is basically similar, except for the constructor.
Since we want to create instances through interfaces, we cannot avoid the following two problems:
1. The interface method body is missing
First, the Class object of the interface, which describes the method information, has been obtained.
But it has no method.
It doesn’t matter, the proxy object’s method is an empty shell anyway, just call the target object’s method.
The JVM can fool around with an empty method body when creating a proxy object, and we’ll find a way to cram the target object into it later anyway.
So the problem is, sort of, solved.
2. The interface Class has no constructor and cannot be new
There seems to be no solution to this problem… After all, for so many years, I really haven’t heard any direct new interface.
But, if you think about it, the reason the interface can’t be new is because it lacks a constructor, which itself has complete class structure information. Like a eunuch with great martial skills, he has a peerless magic power, but no successor. If there is a magic hand on the river’s lake saint doctor, can clone his a suit of martial arts, so the clone is not martial arts high strong at the same time, but also can have children? So we wondered, does the JDK provide a method, such as getXxxClass(), where we pass in an interface Class object that helps us clone a new Class object with the same Class structure information and a constructor?
At this point, the analysis is complete and we cannot create objects directly from the interface (duh).
So how do dynamic proxies create instances? Does it have a method like getXxxClass()?
A dynamic proxy
Yes, dynamic proxies do have methods like getXxxClass().
We need Java. Lang. Reflect the InvocationHandler interface and Java. Lang. Reflect. The Proxy class support. The InvocationHandler comes after Proxy, so I’m going to start with Proxy. First, to clarify our thinking again:
Looking at the API, we found that the Proxy class has a static method that helps us.
Proxy.getproxyclass () : Returns the Class object of the ProxyClass. Finally found the magic doctor.
That is, by passing in the Class object of the interface implemented by the target Class, the getProxyClass() method returns the proxy Class object without actually writing the proxy Class. What is this equivalent to?
Cut the crap and get to work.
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
Parameter 2: The proxy object needs to implement the same interface as the target object to Calculator * */
Class calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
// Compare the Class object of the Calculator implementation Class to see what type the agent Class is
System.out.println(CalculatorImpl.class.getName());
System.out.println(calculatorProxyClazz.getName());
// Prints the constructor for the proxy Class object
Constructor[] constructors = calculatorProxyClazz.getConstructors();
System.out.println("---- constructor ----");
printClassInfo(constructors);
// Prints the method of the proxy Class object
Method[] methods = calculatorProxyClazz.getMethods();
System.out.println("-- -");
printClassInfo(methods);
}
public static void printClassInfo(Executable[] targets) {
for (Executable target : targets) {
// Constructor/method name
String name = target.getName();
StringBuilder sBuilder = new StringBuilder(name);
// Concatenate the left parenthesis
sBuilder.append('(');
Class[] clazzParams = target.getParameterTypes();
// Concatenate parameters
for (Class clazzParam : clazzParams) {
sBuilder.append(clazzParam.getName()).append(', ');
}
// Remove the comma of the last argument
if(clazzParams ! =null&& clazzParams.length ! =0) {
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
// Concatenate the close parenthesis
sBuilder.append(') ');
// Print the constructor/methodSystem.out.println(sBuilder.toString()); }}}Copy the code
Running results:
Do you remember the printed information for the interface Class?
In other words, we get an enhanced Class by passing the Class loader and interface Class object to proxy.getProxyClass () : Subtract () includes the interface method information Add (), subtract(), and the constructor $Proxy0(InvocationHandler), as well as some of our own methods and methods inherited from Object.
To sort it out:
1. We originally intended to get the proxy object directly from the interface Class, but the interface Class only has method information, no constructor
2. Is there a way to create a Class object that contains the method information of the interface Class and also contains a constructor for creating proxy instances?
3. Use the Proxy static method getProxyClass() method, pass it an interface Class object, it can return an enhanced version of the Class object. GetProxyClass () : create a Class from a Class.
Thanks to the Proxy Class and the JVM, we get the Proxy Class object without writing the Proxy Class.
Static agent
Dynamic proxy: Build a Class from a Class
Since Class<$Proxy0> has method information and constructor, let’s try using it to get the proxy instance:
We find that newInstance() fails to create an object. Because the newInstance() method of Class uses a no-argument constructor at the bottom. Proxy0 does not have a Class with any parameters, but it does not have a Class with any parameters. Proxy0 does not have a Class with any parameters. Construct Proxy0 with only parameters (InvocationHandler). That depends on it:
Constructive.newinstance () requires passing in an InvocationHandler object, which uses the method of an anonymous object. The invoke() method does not implement it, but returns NULL
Comfortable ~
The secret of Proxy. GetProxyClass ()
One quick question
Use proxy.getProxyClass () to get the ProxyClass, and use reflection to get the Proxy object.
Embarrassingly, a null pointer exception has occurred. Subtract () and add() return an int, not a null pointer, throughout the code. And the code above the previous compilation is passed, should be no problem ah. On second thought, we find that invoke() of the anonymous object InvocationHandler returns NULL. Is that it? Do an experiment: Invoke () returns 1, and observe the result.
Result Add and Subtract of the proxy object both return 1
Coincidence? Probably not. Invoke () is called every time a proxy object’s method is called, and the return value of invoke() is the return value of the proxy method. Add () and suntract() expect the return type to be int, but invoke() returned null, and the type did not match.
Just in case, verify the relationship between invoke() and proxy object methods again:
All right, we don’t have to say anything. Based on the experiments so far, the call should look like this:
Dynamic proxy underlying call logic
And again, once we know the result, we can work backwards.
Static proxy: The target object is passed to the constructor of the proxy object, and the proxy object calls the method of the same name on the target object.
To create a proxy object, we need to pass in InvocationHandler. The proxy object has an internal member variable called InvocationHandler:
As expected. Then the general design idea of dynamic proxy is:
Why?
For decoupling, but also for generality.
If the JVM generates a proxy object at the same time as the method body of a particular logic, there is no room for extension of the proxy object at a later stage and only one way to play it. The benefits of introducing InvocationHandler are:
- The JVM doesn’t have to worry about method implementation to create a proxy object, just an empty shell, comfortable
- What method implementation does the proxy object want, which I sent to the invoke() method of the invocationHandler object
So, the invocationHandler acts as a way of separating the “method” from the “method body.” The JVM just creates an empty proxy object for you to play with, and you assemble it yourself. The proxy object has a member variable invocationHandler, and each method has only one sentence: handler.invoke(). So calling any proxy method ends up calling the invoke() method.
The invoke() method is a bridge between the proxy object and the target object.
But what we really want is to call the methods of the target object when the methods of the proxy object are called.
So, the next step is to try to get the target object in the invoke() method and call its namesake method.
The proxy object invokes the target object method
So how do you get the target object inside the invoke() method? Let’s see if we can get a clue from the invoke() parameter:
- Object Proxy: Unfortunately, the proxy Object itself, not the target Object (do not call it, it will recurse indefinitely)
- Method Method: indicates the Method of the proxy object to be invoked
- Obeject[] args: Method parameter of the proxy object to be called this time
Unfortunately, proxy is not a proxy object. When you think about it, there is no target object involved in the creation of the proxy object, so there is no association. Also, an interface can be implemented by multiple classes at the same time, so the JVM cannot determine which target object the current proxy object wants to proxy. Fortunately, we already know the Method name and parameter (ARGS) of this call. All we need to do is get the target object and call the method with the same name, and then give it the arguments.
How do you get the target object? I have no choice but to new… Hahaha. Invoke () : Invoke () : Invoke () : Invoke () Don’t worry. Have some fun first. It will be improved later:
But this is clearly a step back 30 years to the pre-liberation era. We need to improve by wrapping proxy.getProxyClass () so that the target object can be passed as a parameter:
public class ProxyTest {
public static void main(String[] args) throws Throwable {
CalculatorImpl target = new CalculatorImpl();
// Pass in the target object
// Purpose: 1. Generate a proxy object based on the interface it implements. 2
Calculator calculatorProxy = (Calculator) getProxy(target);
calculatorProxy.add(1.2);
calculatorProxy.subtract(2.1);
}
private static Object getProxy(final Object target) throws Exception {
// The proxy object implements the same interface as the target object
Class proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
Object proxy = constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "Method starts executing...");
Object result = method.invoke(target, args);
System.out.println(result);
System.out.println(method.getName() + "Method execution completed...");
returnresult; }});returnproxy; }}Copy the code
Cool, cool… Unfortunately, it’s still too much trouble. Is there an easier way to get a proxy object? There are!
Return the proxy object directly, not the proxy object Class
It’s been there since the beginning lol. But I think the getProxyClass() cut makes more sense.
public class ProxyTest {
public static void main(String[] args) throws Throwable {
CalculatorImpl target = new CalculatorImpl();
Calculator calculatorProxy = (Calculator) getProxy(target);
calculatorProxy.add(1.2);
calculatorProxy.subtract(2.1);
}
private static Object getProxy(final Object target) throws Exception {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),/* Class loader */
target.getClass().getInterfaces(),/* Make the proxy object and target object implement the same interface */
new InvocationHandler(){/* Proxy object methods are eventually directed by the JVM to its invoke method */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "Method starts executing...");
Object result = method.invoke(target, args);
System.out.println(result);
System.out.println(method.getName() + "Method execution completed...");
returnresult; }});returnproxy; }}Copy the code
Write generic methods for generating proxies and inserting notifications
The above code is already much better than modifying the target class directly at the beginning of the previous article. Let’s look at the four weaknesses of the time:
- Direct modification of the source program, inconsistent with the open and close principle. Should be open for extensions, closed for modifications √
- If Calculator has dozens or hundreds of methods, too many changes √
- Duplicate code exists (all logs are printed before and after the core code) ×
- Log printing is hard coded in the agent class, which is not conducive to later maintenance: for example, when you finish writing after a whole morning, the team leader tells you that this function is cancelled, so you have to open Calculator again and delete the log printing code for ten minutes! x
With dynamic proxies, let’s avoid writing proxy classes by hand and simply pass a target to the getProxy() method to generate the corresponding proxy object. But log printing is still hard-coded in the invoke() method. Make only one change, but don’t forget the “open and close principle”. So it is better to be able to separate the log print out and pass it in as a parameter like the target object.
Log printing is essentially the notification concept of AOP. I’m going to define an Advice interface and write a MyLogger that implements it.
Notification interface
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
Copy the code
Log print
public class MyLogger implements Advice {
public void beforeMethod(Method method) {
System.out.println(method.getName() + "Method execution begins...");
}
public void afterMethod(Method method) {
System.out.println(method.getName() + "Method execution completed..."); }}Copy the code
The test class
public class ProxyTest {
public static void main(String[] args) throws Throwable {
CalculatorImpl target = new CalculatorImpl();
Calculator calculatorProxy = (Calculator) getProxy(target, new MyLogger());
calculatorProxy.add(1.2);
calculatorProxy.subtract(2.1);
}
private static Object getProxy(final Object target, Advice logger) throws Exception {
/* Proxy object methods are eventually directed by the JVM to its invoke method */
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),/* Class loader */
target.getClass().getInterfaces(),/* Make the proxy object and target object implement the same interface */
(proxy1, method, args) -> {
logger.beforeMethod(method);
Object result = method.invoke(target, args);
System.out.println(result);
logger.afterMethod(method);
returnresult; });returnproxy; }}Copy the code
Almost perfect. Next, more perfect.
Class loader supplement
Beginners may be unfamiliar with things like “bytecode files” and Class objects. So here’s a little bit about how class loaders work. If we want to define a ClassLoader, we need to inherit the ClassLoader class and override the findClass() method:
@Override
publicClass<? > findClass(String name)throws ClassNotFoundException {
try {
/* Write a separate getClassData() to get an array of bytes */ from the XXX. Class file at the specified location via the IO stream
byte[] datas = getClassData(name);
if(datas == null) {
throw new ClassNotFoundException("Class not found:" + name);
}
// Call the defineClass() method of the Class loader itself to get the Class object from the bytecode
return this.defineClass(name, datas, 0, datas.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("Class not found:"+ name); }}Copy the code
So, this is the deep reason why class loading can load xxx.class files into memory and create corresponding class objects. Specific article can refer to gay friends to write another article: please call me program ape adult: scary class loader
summary
Static agent
The Proxy class CalculatorProxy is written in advance and compiled to get the proxy.class bytecode file. It is then loaded into memory with the target Class by the ClassLoader, which generates the Class object, and finally the instance object. The proxy object has a reference to the target object, calls a method of the same name and is preceded by a log print.
Advantages: No need to modify the source code of the target class
Disadvantages: Highly bound, not universal. Hard coded, not easy to maintain.
A dynamic proxy
We wanted to create the proxy instance directly through the interface Class, but unfortunately, the interface Class has the method information description, but there is no constructor, can not create the object. So we want the JDK to provide a set of apis where we pass in an interface Class, which automatically copies the method information in it, and makes a proxy Class object with a constructor that can create instances.
Advantages:
- Instead of writing proxy classes, proxy objects are generated directly from the target object
- Notifications can be passed in, not hard-coded
eggs
The discussion above has deliberately avoided the types of proxy objects, and will come to the end.
Finally, I’ll discuss what type a proxy object is.
First, distinguish between two concepts: proxy Class objects and proxy objects.
The agent Class and Calculator interface are certainly very different from each other by name alone, but we can say that the agent object is assigned to the interface type:
But who says you can copy to an interface by name? Shouldn’t you just implement the interface?
The essence of a proxy object is an instance that implements the same interface as the target object. A proxy Class can be called whatever, as long as it implements an interface, it can be of that interface type.
I wrote a MyProxy Class, so its Class name must be MyProxy. ** But this has nothing to do with whether a value can be assigned to an interface. ** Since it implements Serializable and Collection, myProxy (proxy instance) is the type of both interfaces.
I’ve come up with a very flirtatious metaphor that I hope will clarify:
The interface Class object is a big eunuch, and the methods and fields in it do all their tricks, but it doesn’t have a small DD (constructor), so it can’t new instances. A martial arts successor no one.
So what to do?
Normal path (implements) :
Write a class that implements this interface. It’s like pulling a guy off the street and making him your godfather. A martial arts to him, but more than his godfather small DD, can new instance.
Abnormal path (dynamic proxy) :
Use proxy.getProxyClass () to clone a Class with DD. So this clone Class can create instances, which are proxy objects.
A proxy Class is essentially an interface Class with a constructor, the same Class structure information, but can create instances.
JDK dynamic proxy generation instance
Example of CGLib dynamic proxy generation
If the inherited parent is the parent (there is only one), the implemented interface is the parent (there can be more than one).
Implementing an interface is a process of class identification. An interface cannot create an object, but a class that implements the interface can.
Such as
class Student extends Person implements A, B
Copy the code
An instance of this class new comes out and you ask it: Who is your father? It will tell you: I have only one father Person.
But student instanceof an interface, or student instanceof B interface, it’s going to tell you that both of them are true, both of them can be used to receive it.
However, every advantage has its disadvantages.
That is, dynamic proxies generate proxy objects that can eventually be received by the interface, forming polymorphism with the target object, and can be switched to display different functions at will. However, only the methods defined by the interface can be used during the switch.
After introducing the JDK dynamic proxy, today we do a small case: simulating Spring transaction management.
Main Contents:
- Familiar stranger
- Shanzhai AOP transaction requirements analysis
- AOP transaction concrete code implementation
Familiar stranger
If an interviewer asks, “Tell me about your understanding of Spring,” many people will probably blurt out: IOC and AOP. IOC is probably the most immediate image that people have of Spring. It’s a big container with lots of beans and dependency injection for you.
IOC
But with AOP, many people don’t really know much about it and don’t know where Spring uses AOP. It seems that the transaction uses the aspect, but the details are not understood. UserServiceImpl implements UserService; UserServiceImpl implements UserService; UserServiceImpl implements UserService;
@Autowired
private UserService userService;
Copy the code
So, this userService must be an instance of the UserServiceImpl we wrote?
If you don’t understand what I’m asking, your own knowledge of Spring is still too limited to IOC.
In fact, the Spring dependency injection object is not necessarily an instance of a class we wrote ourselves, but could also be a proxy object for userServiceImpl. Here are two examples:
- Inject the userServiceImpl object
The injected type is UserServiceImpl
- Proxy object injected into userServiceImpl (CGLib dynamic proxy)
The injected proxy object is the userServiceImpl proxy object generated by the CGLib dynamic proxy
Why are the injected objects different?
Because the second time I annotated UserServiceImpl with @Transactional.
Spring now reads the annotation and knows that we are going to use transactions. The UserService class we wrote does not contain any transaction-related code. What would you do with it? Dynamic proxy!
However, in order to use dynamic proxy to complete transaction management, we need to write a notification class, and pass the notification object into the proxy object, the notification is responsible for the start and submission of transactions, and call the method of the same name of the target object inside the proxy object to complete business functions.
Spring certainly knows all the solutions we can think of. Similarly, Spring writes a notification class, TransactionManager, to implement transactions. When a proxy object is created using a dynamic proxy, Spring weaves the transactionManager into the proxy object, which is then injected into the UserController.
So the userService we use in the UserController is actually a proxy object, and a proxy object supports transactions.
Shanzhai AOP transaction requirements analysis
Now that you know the general flow of a Spring transaction, let’s examine how you can write a copycat AOP transaction.
AOP transactions, there are two concepts: AOP and transactions.
Transactions, as you are already familiar with, focus here on what AOP is. AOP, it is ** aspect-oriented Programming (Aspect-Oriented Programming) ** English abbreviation. What is aspect oriented programming? Sometimes it’s hard to just say what something is. But when it comes to what it does, it immediately catches on.
In our system, there are often overlapping services, such as transactions, logging, and so on. UserService uses it for method1, and BrandService uses it for method2. A cross business is to tap into one aspect of the system. Specific code display is:
This aspect can be a log or a transaction
The programming problem of interleaved business is section-oriented programming. The goal of AOP is to modularize cross businesses. You can move the aspect code around the original method:
Previously, when AOP was not used, interleaved services were written directly before and after methods, and with AOP interleaved services were written before and after method calls. This is related to the underlying implementation of AOP: dynamic proxies are basically proxy objects calling methods of the same name on target objects, with enhancement code added before and after the invocation. But the end result is the same.
By modularity, MY personal understanding is to make the aspect code into a manageable state. Log printing, for example, is no longer hard-coded into a method, but a notification class that executes aspect code.
So, now that the requirements are clear, we need a notification class (TransactionManager) to perform transactions, a proxy factory to help generate proxy objects, and a dynamic proxy to weave the transaction code into the methods of the proxy object.
For example, the following three services do not originally start a transaction:
What we want to achieve is that when I add @MyTransactional, the proxy factory returns me a proxy object:
The proxy factory uses dynamic proxies to create one proxy object for each target object
Detail analysis:
TxManager actually performs transactions before and after the target object test() method, not inside the method
That is, the proxy object method = transaction + target object method.
There is also a tricky problem: transactions must use the same Connection object. How to guarantee? When the first Connection object is fetched from the data source and the transaction is started, it is stored in the ThreadLocal of the current thread. When the DAO layer arrives, it is fetched from the ThreadLocal again. This ensures that the same Connection object is used to start the transaction and operate the database.
After the transaction is started, the Controller does not directly call our own Service, but rather a spring-provided proxy object
This is how transactions are implemented.
AOP transaction concrete code implementation
ConnectionUtils tools
package com.demo.myaopframework.utils;
import org.apache.commons.dbcp.BasicDataSource;
import java.sql.Connection;
/** * connection utility class, which is used to get a connection from the data source and implement the thread binding */
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private static BasicDataSource dataSource = new BasicDataSource();
// Static code block that sets parameters for connecting to the database
static{
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
}
/** * gets the connection * on the current thread@return* /
public Connection getThreadConnection(a) {
try{
//1. Obtain from ThreadLocal
Connection conn = tl.get();
//2. Check whether there are connections on the current thread
if (conn == null) {
//3. Get a connection from the data source and store it in ThreadLocal
conn = dataSource.getConnection();
tl.set(conn);
}
//4. Return the connection on the current thread
return conn;
}catch (Exception e){
throw newRuntimeException(e); }}/** * unbind the connection and thread */
public void removeConnection(a){ tl.remove(); }}Copy the code
AOP notification (transaction manager)
package com.demo.myaopframework.utils;
/** * transaction management related utility class, which includes, start transaction, commit transaction, rollback transaction and release connection */
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/** * start transaction */
public void beginTransaction(a){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch(Exception e){ e.printStackTrace(); }}/** * Commit transaction */
public void commit(a){
try {
connectionUtils.getThreadConnection().commit();
}catch(Exception e){ e.printStackTrace(); }}/** * rollback transaction */
public void rollback(a){
try {
connectionUtils.getThreadConnection().rollback();
}catch(Exception e){ e.printStackTrace(); }}/** * release connection */
public void release(a){
try {
connectionUtils.getThreadConnection().close();// return to the connection pool
connectionUtils.removeConnection();
}catch(Exception e){ e.printStackTrace(); }}}Copy the code
Custom annotations
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactional {
}
Copy the code
Service
public interface UserService {
void getUser(a);
}
public class UserServiceImpl implements UserService {
@Override
public void getUser(a) {
System.out.println("The service execution..."); }}Copy the code
Instance factory
public class BeanFactory {
public Object getBean(String name) throws Exception {
// Get the target Class objectClass<? > clazz = Class.forName(name);// Get the target object
Object bean = clazz.newInstance();
// Get the @myTransactional annotation on the target class
MyTransactional myTransactional = clazz.getAnnotation(MyTransactional.class);
// Return the proxy object if annotated @myTransactional, otherwise return the target object
if (null! = myTransactional) { ProxyFactoryBean proxyFactoryBean =new ProxyFactoryBean();
TransactionManager txManager = new TransactionManager();
txManager.setConnectionUtils(new ConnectionUtils());
// Assemble notifications and target objects
proxyFactoryBean.setTxManager(txManager);
proxyFactoryBean.setTarget(bean);
Object proxyBean = proxyFactoryBean.getProxy();
// Return the proxy object
return proxyBean;
}
// Return the target object
returnbean; }}Copy the code
The agent factory
public class ProxyFactoryBean {
/ / notice
private TransactionManager txManager;
// Target object
private Object target;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void setTarget(Object target) {
this.target = target;
}
// Pass in the target object, assemble the notification for it, and return the proxy object
public Object getProxy(a) {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),/*1. Class loader */
target.getClass().getInterfaces(), /*2. The target object implements the interface */
new InvocationHandler() {/*3.InvocationHandler*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//1. Start transaction
txManager.beginTransaction();
//2. Perform operations
Object retVal = method.invoke(target, args);
//3. Commit transaction
txManager.commit();
//4. Return the result
return retVal;
} catch (Exception e) {
//5. Rollback transaction
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6. Release connectiontxManager.release(); }}});returnproxy; }}Copy the code
The code structure
Get a normal UserService:
Add the @MyTransactional annotation to UserServiceImpl to get the proxy object:
References:
- Spread wisdom podcast Zhang Xiaoxiang Java high technology