Design pattern (1-1) - agent pattern

Keywords: Design Pattern

I haven't blogged for months. I changed my job a few days ago and adjusted things well. I can continue to blog again~

After learning the agent mode, this article will talk about how to write dynamic agent and static agent modes. Subsequent articles will talk about the principle of dynamic agent (JDK dynamic agent) and how to use CGLIB to realize the dynamic agent of classes without interface

1, Examples of agents in life

Agent, handling affairs on behalf of the authorized party (wiki interpretation). For example, a client wants to buy a u disk; When the customer wanted to buy a u disk from the western data factory, the security guards stopped him and told him to buy it from Jingdong; There is no way but to buy from Jingdong, which is authorized, and earn the price difference by the hateful middleman.
Although both JD and WD sell u-disks, JD is just an agent. The final u-disk needs to be purchased from WD. JD can issue coupons before customers buy u-disks.
There are many examples of agents in life. Find an intermediary to rent a house. What you don't want to do can be done by the agent, but the agent's ability is limited, and the final thing still needs to be done by yourself.

What's the use of agent mode?

  • The method is enhanced. The agent can not only sell u-disk, but also issue coupons to earn the price difference
  • Control access, the customer went to the western data factory to buy a u disk, and the security guard stopped him directly

2, Static proxy mode implementation

Let's take a look at the implementation of static proxy in Java

Take the trading u order as an example,

Step 1: define a service interface to define the common goal of the manufacturer and the agent (selling u disks)

public interface UpanSell {

    float sell(int amount);
}

Step 2: create a factory class to implement the interface defined in step 1

public class WesternDigitalUpanFactory implements UpanSell {

    @Override
    public float sell(int amount) {

        float price = 85.0f * amount;

        System.out.println("------Western data sales" + amount + "individual u disc, Price: " + price);

        return price;
    }
}

Step 3: create a merchant class (proxy) to implement the same interface

public class jd implements UpanSell {

    private final UpanSell upanSell = new WesternDigitalUpanFactory();

    @Override
    public float sell(int amount) {

        float price = upanSell.sell(amount);

        // -------Enhancements ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

        // Middlemen earn price difference
        price += 25.f * amount;

        System.out.println("------Jingdong sales" + amount + "individual u disc, Price: " + price);
        System.out.println("------Here's a coupon, 0.01 branch");

        return price;
    }
}

Step 4: create a client to call the merchant to buy U SB flash disk

public class Shop {
    public static void main(String[] args) {
        UpanSell upanSell = new jd();

        upanSell.sell(2);
    }
}

Output results

------Western Digital sells 2 u disc, Price: 170.0
------Jingdong sells 2 u disc, Price: 220.0
------Here's a coupon, 0.01 branch

The above agent (jd) did two things: 1. The middleman added the price and 2. Gave us a coupon

We can see that the implementation of static agent is still very simple, and it is also an application of polymorphism. We are the manufacturer class called by the merchant class.
Simple is simple, but

  1. If one more merchant, such as Taobao, also wants to sell, does it need another agent class.
  2. If serviceInterface, whether the manufacturer class and merchant class should be modified.

So, let's take a look at how dynamic agents can avoid these shortcomings

Using JDK to realize dynamic agent 👇

3, Implementation of dynamic agent mode

Dynamic proxy actually means that we do not need to implement the proxy class manually, but dynamically generate the proxy class when the program runs according to the parameters we pass in.

3.1 JDK dynamic agent

Before we look at the code of JDK dynamic agent, don't ask why it is written like this, because we can understand it as a template, which is required to be written like this (don't recite, if you write too much, you will naturally remember it).

Steps 1 and 2 are the same as above

Step 1: define a service interface to define the common goal of the manufacturer and the agent (selling u disks)

public interface UpanSell {

    float sell(int amount);
}

Step 2: create a factory class to implement the interface defined in step 1

public class WesternDigitalUpanFactory implements UpanSell {

    @Override
    public float sell(int amount) {

        float price = 85.0f * amount;

        System.out.println("------Western data sales" + amount + "individual u disc, Price: " + price);

        return price;
    }
}

Step 3: implement invoke() in the InvocationHandler interface (define what the proxy class should do)!!!

public class MySellHandler implements InvocationHandler {

    /**
     * Target object
     */
    private final Object target;

    public MySellHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // Execution target method
        Object res = method.invoke(target, args);

        int amount = (int) args[0];

        if(res != null)
        {
            float price = (Float) res;
            price += 25.f * amount;
            System.out.println("------Jingdong sales" + amount + "individual u disc, Price: " + price);
            System.out.println("------Here's a coupon, 0.01 branch");
            res = price;
        }

        return res;
    }
}

Step 4, get the agent instance and buy the U SB flash disk!!!

public class Shop {
    public static void main(String[] args) {

        // 1. Create target object
        UpanSell factory = new WesternDigitalUpanFactory();

        // 2. Create InvocationHandler object
        InvocationHandler handler = new MySellHandler(factory);

        // 3. Generate a proxy entity class
        UpanSell proxy = (UpanSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                handler
        );

        float price = proxy.sell(2);
    }
}

Output results

------Western Digital sells 2 u disc, Price: 170.0
------Jingdong sells 2 u disc, Price: 220.0
------Here's a coupon, 0.01 branch

The output is of course the same. Step 3 and step 4 are the most important. You can follow suit and write a new JDK dynamic agent (intermediary rental...) without paying attention to details!

We can see that if you add or reduce the serviceInterface method, there are no more modifications than static agents;
It is more flexible to use, and the proxy class is decoupled from the service. The only annoyance may be that it is not easy to understand.

We can see that we did not write the proxy class, but implemented the proxy function. How can we see the generated proxy class?

Add the following attributes to the first line of the main method

...
   // Just add one of these things
   System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

...

Generated proxy class file

package com.sun.proxy;

import com.ukyu.dynamicproxy.service.UpanSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UpanSell {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final float sell(int var1) throws  {
        try {
            return (Float)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.ukyu.dynamicproxy.service.UpanSell").getMethod("sell", Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Let's get familiar with this class file first. The next article starts to understand the principle of JDK dynamic agent.

Posted by Ting on Thu, 28 Oct 2021 05:18:57 -0700