Recently received a big project, ha ha ha ~ article finally out ~ fortunately not stillborn. My recent realization is that only by doing small things well can I have the opportunity to do big things and undertake big projects!

Recently learning Kotlin foundation, found that the dynamic proxy before this or not understand, so learned under Kotlin proxy mode writing, found something a little more, then alone written, welcome everyone to shoot brick!

primers

What are dynamic proxies used for? Basically, you want to add some processing logic of your own when you call some method of another class. For example, counting the execution time of these methods, etc., is also the idea of aspect oriented programming.

The proxy pattern

Dynamic proxies are derived from a common pattern in design patterns: the proxy pattern. In Java, this is to provide A proxy object for A method B of an object A that has complete control over the actual execution of A method B of an object A. Four key words: 1) Proxy object; 2) The proxy object; 3) Proxy behavior; 4) Complete control over behavior.

That’s too abstract. Let me give you a practical example. If we need to rent a house through a housing agent, it is a simple agent model. There are three roles: 1) the landlord — the object to be represented; 2) Real estate agent — the agent; 3) Tenant — the user or caller. When the landlord hands over all the rental matters to the intermediary, the intermediary has complete control over the rental behavior, and the tenant can only deal with the intermediary.

Looking at the four keywords above, mediation corresponds to proxy objects; The landlord corresponds to the agent; Rental related matters correspond to the acts of the agent, such as looking for tenants, appointment of house viewing time, house viewing, signing, etc. The intermediary can completely control the behavior of the rental and the corresponding behavior, such as the intermediary can increase the price and provide cleaning services.

Basic elements of the agency pattern

  1. The proxy object and the proxied object need to implement the same interface. That is, for rental housing, is the rental housing related matters, landlords and intermediaries must complete the rental process procedures.
  2. The proxy object has a reference to the proxied object, so that when an external caller calls a method on the proxy object, the proxy object is internally handed over to the proxied object for actual execution. That is, the intermediary can rent the house instead of the landlord, and the landlord has the final say.
  3. The external caller directly uses the interface to make the invocation, which has certain protection effect on the information of the proxy object. In other words, the tenant can only communicate with the agent through the procedures of renting the house and does not know the personal information of the landlord.

Rent code instance – Java edition

Talk is cheap, Show me your Code! So let’s see what the code does. First of all, some processes of renting, that is, the behavior of the agent, only three steps are written here for convenience. Usually in the form of an abstract class or interface:

// code 1 interface IRentHouse {void visitHouse(); Void argueRent(int rent); Void signAgreement(); }Copy the code

Secondly, the realization of the landlord, that is, the behavior of the agent, the rights of the landlord.

// code 2 class HouseOwner implements IRentHouse{ @Override public void visitHouse() { System.out.println("HouseOwner Show the house and introduce the features of the house "); } @override public void argueRent(int rent) {system.out.println ("HouseOwner proposes rent: "+ rent); } @override public void signAgreement() {system.out.println ("HouseOwner sign contract "); }}Copy the code

The next step is the realization of the real estate agent. Since the agent is authorized by the landlord, he can have all the rights of the landlord. He acts for the landlord to operate the relevant matters of the rental house. Therefore, the agent will first ask the landlord for permission to obtain authorization, and the performance in the code is to hold the landlord’s reference.

// code 3 public class HouseAgent implements IRentHouse{ IRentHouse mHouseOwner; Public houseOwner (IRentHouse houseOwner) {mHouseOwner = houseOwner; } @Override public void visitHouse() { mHouseOwner.visitHouse(); } @Override public void argueRent(int rent) { mHouseOwner.argueRent(rent); } @Override public void signAgreement() { mHouseOwner.signAgreement(); }}Copy the code

This can be called from the main method as follows:

// code 4 HouseAgent agent = new HouseAgent(new HouseOwner()); Agent. VisitHouse (); agent. VisitHouse (); agent.argueRent(300); agent.signAgreement();Copy the code

This is actually an example of a static proxy. Why “static”? Because the proxy methods here are written dead, if you add methods to the HouseOwner object, you’ll have to add code to both the HouseAgent and the interface, which is not very flexible.

So what does the agency model do? It looks as if the proxy class does a layer of encapsulation of the proxy class, but in fact, this layer of encapsulation to a certain extent to protect the information of the proxy class, the user does not have to care about the internal implementation. In addition, the proxy class can be extended based on its own characteristics and customer needs, extending other behaviors while ensuring that the core methods of the HouseOwner class execute correctly. For example: The tenant, a recent graduate with little social experience, meets a shady agent and needs to tip you before you look at the house:

// code 5 public class HouseAgent implements IRentHouse{ IRentHouse mHouseOwner; Int mTip = 0; Public houseOwner (IRentHouse houseOwner) {mHouseOwner = houseOwner; } @Override public void visitHouse() { if (mTip > 10) { mHouseOwner.visitHouse(); } else {system.out.println (" the tip is not enough "); } } @Override public void argueRent(int rent) { mHouseOwner.argueRent(rent); } @Override public void signAgreement() { mHouseOwner.signAgreement(); } public void giveTip(int tip) { mTip = tip; }}Copy the code

Rent code instance – Kotlin version

The Java code for the static proxy above is easy to write, but the same routine is tedious. In the Kotlin code, you can use the by keyword, which is very convenient. Again, the Kotlin code is:

// code 6 interface IRentHouse {// show the tenant fun visitHouse() // Bargain fun argueRent(rent: Int) // sign a contract IRentHouse {override fun argueRent() {println(" show the house ")} Override Fun argueRent(rent: Int) {println(" $rent")} override fun signAgreement() {println(" $rent")}} class HouseAgent(houseOwner: IRentHouse): IRentHouse by houseOwner {} houseOwner ().visithouse () HouseAgent(houseOwner ()).arguerent (500) HouseAgent(HouseOwner()).signAgreement()Copy the code

A by keyword, and you avoid writing a lot of duplicate code. If the proxy class wants to add its own methods or functionality, it simply overrides the methods in the proxy object. Again, take the example above:

// code 7 class HouseAgent(houseOwner: IRentHouse): IRentHouse by houseOwner { var mTip = 0 private val mHouseOwner = houseOwner fun giveTip(tip: Int){mTip = tip} override fun visitHouse() {if (mTip > 10) {mhouseowner.visithouse ()} else {println(" tips are not enough, ")}}}Copy the code

Therefore, static proxy means that the code of the proxy relationship is written dead. If new methods are added to the proxy object, then the proxy class and interface need to be changed. Of course, if you’re writing in Kotlin, you just need to change the interface. However, if we wanted to slice programming and add our own processing to each method of the propped object, for example, I needed to know when each method step was executed, then it would be tedious to record timestamps before and after each method call. And you have to write the same code repeatedly for each new method, in which case you need a dynamic proxy.

Java dynamic proxy implementation

To create a dynamic Proxy for Java, use the Proxy class:

// code 8
java.lang.reflect.Proxy
Copy the code

You create a proxy class for a class by calling its newProxyInstance method. For example, create a proxy class for Map:

// code 9 Map mapProxy = (Map) Proxy.newProxyInstance( HashMap.class.getClassLoader(), new Class[]{Map.class}, new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { return null; }});Copy the code

To analyze this method, the method signature is:

// code 10 public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h)Copy the code
  1. Loader object of type ClassLoader: the ClassLoader of the proxied class;
  2. The “interfaces” object of the Class array: the “interfaces” that correspond to the behavior of the “interfaces” in the preceding keyword. In this case, the interface is passed in, not a specific Class, so it represents the behavior.
  3. The InvocationHandler interface object H: the specific behavior of the proxy, corresponding to the full control of the behavior in the previous keyword, which is at the heart of Java dynamic proxy;
  4. Object returned: corresponds to the proxy Object in the previous keyword.

So, to implement a dynamic proxy, there are roughly the following steps:

For example, Code1 defines the behavior to be proxied.

The object defined by proxy is the class that implements the interface, such as the landlord that implements the renting process, see Code2 for details.

This is the core of the proxy. We need a class that implements the InvocationHandler interface:

// code 11 public class AgentHandler implements InvocationHandler { private Object target; public AgentHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {system.out.println (" before method execution "); Object result = method.invoke(target, args); System.out.println(" after method execution "); return result; }}Copy the code

The Invoke method in the interface is important. The proxy of type Object in the method body is the final generated proxy Object; Method of type Method is the Method being proxied; The args of the Object[] array type are the parameters required for execution by the proxy method. The target object is the proxy class passed in. In this class, we can insert the code we need to execute before and after the Invoke method. Doing so will cause the proxy class object to execute the code we inserted whenever any method is executed. For example, we can record the timestamp before and after the Invoke method, so that we can derive the execution time of each method executed by the proxy class object.

The proxy class then uses the InvocationHandler to proxy:

// code 12 public class HouseAgentSmart { private IRentHouse mHouseOwner; Public HouseAgentSmart(IRentHouse houseOwner) {mHouseOwner = houseOwner; mHouseOwner = (IRentHouse) Proxy.newProxyInstance( houseOwner.getClass().getClassLoader(), New Class[]{IRentHouse. Class}, new AgentHandler(mHouseOwner) // will be passed in by the proxy object); } public IRentHouse getAccess() {return mHouseOwner; }}Copy the code

Iv. Executor call

// code 13
IRentHouse smartAgent = new HouseAgentSmart(new HouseOwner()).getAccess();
        smartAgent.visitHouse();
        smartAgent.argueRent(400);
        smartAgent.signAgreement();
Copy the code

Kotlin dynamic proxy implementation

The above is a Java implementation of dynamic proxies, so how do you do it in Kotlin? It’s the same, just the syntax of the programming language is different

// code 14 // irenthousekt.kt 1, interface IRentHouseKT {// show the house fun visitHouse() // Bargain fun argueRent(rent) : Int) // houseownerkt. kt = houseownerkt. kt = houseownerkt. kt = HouseOwnerKT; IRentHouseKT {override fun argueRent() {println("HouseOwner ")} Override Fun argueRent(rent: Int) {println("HouseOwner offers rent for: $rent")} override fun signAgreement() {println("HouseOwner sign contract ")}} // agenthandlerkt.kt AgentHandlerKT : InvocationHandler { private var mTarget : Any? = null constructor(target : Any?) { this.mTarget = target } override fun invoke(proxy: Any? , method: Method, args: Array<out Any>?) : Any? {println(" before method execution ") // Since the passed argument args is uncertain, Val result = method.invoke(mTarget); * args.orempty ()) println(" after ") return result}} // HouseAgentSmartkt. kt {var mHouseOwner: IRentHouseKT? = null constructor(houseOwner : IRentHouseKT) { mHouseOwner = houseOwner mHouseOwner = Proxy.newProxyInstance( houseOwner.javaClass.classLoader, arrayOf(IRentHouseKT::class.java), AgentHandlerKT(mHouseOwner)) as IRentHouseKT} HouseAgentSmartKT(HouseOwnerKT()).mHouseOwner smartAgent? .visitHouse() smartAgent? .argueRent(500) smartAgent? .signAgreement()Copy the code

And this is where someone asks, huh? Wasn’t it possible to delegate in Kotlin with the BY keyword? Why do you need to write Proxy patterns using the proxy.newProxyInstance () method like Java? What’s the difference between the two?

First, both of these approaches can implement the proxy pattern in Kotlin, but for different scenarios. 1, by keyword implementation of the proxy pattern. It is more convenient to use, the granularity of the proxy is smaller, and some methods in the proxy class can be overridden according to the needs of the proxy class. The proxy.newProxyInstance () method implements the Proxy pattern. The implementation is tedious, the granularity of the agent is large, once the agent, is the agent of all methods. You can insert the code you want to execute before and after the original method, suitable for burying point log log, record method execution time, etc.

reference

  1. Java Dynamic Proxy — Application scenarios and rationale in the framework
  2. Proxy Patterns when Kotlin encounters Design Patterns (part 2)

Not enough? Welcome to browse my public number. Search: Zhihu is also welcome to pay attention to xiuzhu ~

If you give a rose, you leave a fragrance in your hand. Welcome to retweet, share and follow, your recognition is the spiritual source of my continued creation.