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:

  1. Direct modification of the source program, inconsistent with the open and close principle. Should be open for extension, closed for modification
  2. If Calculator had dozens or hundreds of methods, it would be too much modification
  3. Duplicate code exists (all logs are printed before and after core code)
  4. 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:

  1. Direct modification of the source program, inconsistent with the open and close principle. Should be open for extensions, closed for modifications √
  2. If Calculator has dozens or hundreds of methods, the amount of modification is too large
  3. Duplicate code exists (all logs are printed before and after the core code) ×
  4. 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:

  1. Direct modification of the source program, inconsistent with the open and close principle. Should be open for extensions, closed for modifications √
  2. If Calculator has dozens or hundreds of methods, too many changes √
  3. Duplicate code exists (all logs are printed before and after the core code) ×
  4. 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