Analysis of dynamic proxy and RPC framework

Detailed Implementation of Dynamic Proxy and RPC Principles

1. Dynamic Proxy

1. What is proxy mode?

The proxy mode provides a proxy for other objects to control access to this object. According to the timing and method of creating the proxy class, it can be divided into two forms: static proxy and dynamic proxy: before the program runs The existing compiled proxy class is a static proxy, and the proxy class and its instances are dynamically created according to the needs during the running of the program to complete specific functions, which is a dynamic proxy.

The purpose of the proxy mode is to provide a proxy object for the real business object to control access to the real business object. The role of the proxy object is as follows:

  • The value of proxy objects is mainly used to intercept access to real business objects;
  • The proxy object has a common interface with the target object (real business object) or inherits from the same class ;
  • Proxy objects are enhancements to target objects to allow pre-processing and post-processing of messages.

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-1rz7lt6c-1666005213203) (proxy and RPC.assets/image-20221013173958276.png)]

2. Reflection

The core of the implementation of dynamic proxy is reflection, and all proxy operations of dynamic proxy are realized by reflection. So it is necessary to have a certain introduction to reflection knowledge first.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-5low5151-1666005213204) (proxy and RPC.assets/38ef5d12b8514087b380bf36415b0c11.jpeg)]

2.1 Reflection implementation steps

  1. Obtain the .class file of the object through reflection, that is, the class object (there are three ways)

    //方式一:使用Class.forName方法进行包名+类名的定位
    Class c1=Class.forName("zk.reflect.Person");
    //方式二:采用类名.class方法获取反射
    Class c2=Person.class;
    //方式三:采用对象名.class方法获取反射(运行过程中)
    Person ps=new Person();
    Class c3 = ps.getClass();
    
  2. Through the acquired object, the constructor of the object class is called, and the member variable is called

    Class c =Class.forName(com.zk.User);
    //获取类名
    c.getName(); //获取的是包名+类名(返回--->com.zk.User)
    c.getSimpleName(); //类名(返回--->User)
    //获取类的属性
    Fields[] fi=c.getFields(); //只能获取public属性
    Fields[] fi2=c.getDeclaredFields(); //可以获取类的全部属性(包括私有属性)
    Fields name= c.getDeclaredField(“name”); //获取指定属性值,
    //获取类的构造器
    Constructor[] co=c.getConstructors(); //仅获得public构造器
    Constructor[] co2=c.getDeclaredConstructor();//获取全部构造器
    //获取指定构造器,必须参数
    Constructor con=c.getConstructor(String.class,int.class,int.class);
    
  3. Get the object obtained by reflection, get the method in the object class, and use the invoke([instantiated object],[parameter corresponding to the method]) method to use the method.

    //获取类的方法
    Method[] m =c.getMethods(); //获得本类和其父类的全部public方法
    Method[] m2=c.getDeclaredMethods(); //仅获取本类的全部方法(包括私有方法)
    //获取指定方法,并使用
        //通过实例对象获取反射
        //Person p =new Person();
        //获取Person类对象
        //Class c=p.getClass();
        Class c=Person.class;
        Person p= (Person) c.newInstance();
        //获取该类实例对象中的具体方法--第一个参数要写Person类的方法名称才能匹配上;后面参数是方法参数类型
        Method m=c.getDeclaredMethod("eat",String.class);
        //使用invoke方法对反射获取的方法进行激活---第一个参数是实例化对象,后面参数是方法对应参数值
        String s= (String) m.invoke(p,"zhangkai");
        System.out.println(s);
    //获取指定方法,必须加入参数,没有加null;因为存在重载,只有方法名和参数个数两个才能精确定位方法
    Method getid=c.getMethod(“getid”,null); 
    Method setid=c.setMethod(“setid”,int.class);
    

3. Static proxy principle

Static proxy is one of the implementation methods of proxy mode, which is relative to dynamic proxy. The so-called static proxy means that before the program runs, the source code is created by the programmer or a specific tool automatically generates and compiles it to generate a .class file. The implementation of static proxy only needs three steps: first, define the business interface; second, realize the business interface; then, define the proxy class and realize the business interface; finally, it can be called by the client.

The real business function is still implemented by the delegate class , but some common logic can be added before and after the business function is realized to enhance the business function. That is to say, the core function is still implemented by the methods in the delegate class, and the proxy class can package additional information on this basis. For example, we did not add cache, log and other functions in the early development of the project. If we want to add them later, we can use agents to implement them without changing the original code. Therefore, the proxy model is a typical practice of the opening and closing principle, and it is also the basis for the realization of the AOP concept.

4. The principle of dynamic proxy

For the proxy mode, generally speaking, there is a one-to-one correspondence between the specific theme class and its proxy class, which is also the characteristic of static proxy. However, there is also such a situation: there are N theme classes, but the "pre-processing and post-processing" in the proxy class are the same, only the calling theme is different. Then, if a static proxy is used, it is necessary to manually create N proxy classes, which is obviously quite unpleasant. Dynamic proxy can simply generate proxy classes for each theme class and share the "pre-processing, post-processing" function, which can greatly reduce the program size, which is also a highlight of dynamic proxy. ( Popularly speaking, a dynamic proxy is no longer a proxy class proxying a delegate class, but like a big housekeeper, specifying the delegate object, who is the agent, but the general logic in the proxy class will apply to each delegate class )

In a dynamic proxy, the proxy class is inruntimeGenerated. Therefore, compared with static proxies, dynamic proxies can easily enhance the related methods of the delegate class, such as adding the number of method calls, adding log functions, and so on. Dynamic agents are mainly divided intoJDK dynamic proxyandCGLIB dynamic proxyTwo categories.

The following is a simulated dynamic proxy case to realize the realization idea of ​​dynamic proxy:

There are two Service classes, namely User and Order. The ProxyUtils class is used to dynamically proxy the abstract interfaces of these two types. During the calling process, the program directly goes to the proxy class through the interface, and the proxy class implements the functions of the interface implementation class and the proxy class. Some enhancements and general functions of its own.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-a4I7PZ4V-1666005213205) (proxy and RPC.assets/image-20221016152901768.png)]

  1. interface class

    //User的一些方法
    public interface UserService {
          
          
        String login(String s,String p) throws InterruptedException;
        void selectById(int id);
        void delect();
    }
    //Order的一些方法
    public interface OrderService {
          
          
        String zhuyi();
        void select();
        String delectById(int id);
    }
    
  2. Implementation class

    //UserServiceImpl实现类
    public class UserServiceImpl implements UserService {
          
          
    
        @Override
        public String login(String name,String password) {
          
          
            String resword="用户名或密码错误!";
            if(name.equals("admin") && password.equals("123456")){
          
          
                resword="登录成功";
            }
            try {
          
          
                Thread.sleep(2000);
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
            return resword;
        }
    
        @Override
        public void selectById(int id) {
          
          
            try {
          
          
                Thread.sleep(1500);
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
            System.out.println(id+"的信息如下:----");
        }
    
        @Override
        public void delect() {
          
          
            try {
          
          
                Thread.sleep(3000);
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
            System.out.println("正在删除中------");
        }
    }
    //OrderServiceImpl实现类
    public class OrderServiceImpl implements OrderService{
          
          
    
        @Override
        public String zhuyi() {
          
          
            return "订单功能区在这里————————";
        }
    
        @Override
        public void select() {
          
          
            System.out.println("全部订单如下所示--------");
        }
    
        @Override
        public String delectById(int id) {
          
          
            String s=id+"的信息正在删除中。。。。。";
            return s;
        }
    }
    
  3. Dynamic proxy class ProxyUtils

    • The proxyService method in this class uses generic ==== to enable the proxyService method to proxy a variety of different types of Service interfaces;
    • return an instance object provided by Proxy through newProxyInstance()
    • newProxyInstance() takes a three-part argument. The first part is to create the class loader object of the method parameter object obj; the second part is to create the class interface object of the method parameter object obj (both are fixed methods); the third part is to create == an InvocationHandler()== method , which means calling a method of a class object method;
    • The enhanced functions in the proxy class can be written in the InvocationHandler() method. This example implements the detection function of a method running time;
    /**
     * @Date:2022/10/15-10-15-21:40
     * @Description: 动态代理类
     */
    public class ProxyUtils {
          
          
        //这里使用泛型思想,可以使proxyService方法代理多种不同类的Service接口
        public static <T> T proxyService(T obj){
          
          
            return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
          
          
                //利用反射对被代理对象的方法进行实现,使用反射中的invoke方法进行obj对象,和args参数的传入
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          
          
                    //代理类中增强功能在此内方法中实现,通用所有代理类方法
                    //增加一个方法运行效率检测的功能
                    long startTime = System.currentTimeMillis();
                    //调用代理实现类obj对象的方法,使用invoke激活,args考虑方法可能存在参数传入,args可以为null
                    Object o = method.invoke(obj, args);
    
                    long endTime = System.currentTimeMillis();
                    System.out.println(method.getName()+"方法的运行时间为:"+(endTime-startTime)/1000.0+"s");
                    //返回调用方法的执行结果
                    return o;
                }
            });
        }
    }
    
  4. Test class (application class)

    /**
     * @Date:2022/10/15-10-15-21:58
     * @Description: java_workspace
     */
    public class ProxyTest {
          
          
        public static void main(String[] args) throws Exception {
          
          
            //创建一个User类的代理对象,代理对象中也传入了一个新建的User实现类对象
            UserService userService=ProxyUtils.proxyService(new UserServiceImpl());
            String rs = userService.login("admin", "123456");
            System.out.println(rs);
            userService.selectById(2);
            userService.delect();
            //创建一个Order类的代理对象
            OrderService orderService=ProxyUtils.proxyService(new OrderServiceImpl());
            String ss = orderService.delectById(9);
            System.out.println(ss);
        }
    }
    

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-UqOZdplm-1666005213205) (proxy and RPC.assets/image-20221016155759390.png)]

The above is the full implementation of the dynamic proxy case. The core call logic is the call logic in the previous flow chart, using the dynamic proxy classNot only can the reuse of code be greatly improved, but also some public enhanced functions can be realized on the basis of the proxy class. This is actually the core implementation principle of AOP in Spring. For example, this example implements the running time detection function of all the methods of the proxied Service, and realizes the detection function for multiple Services, which is actually a kind of horizontal programming idea.

2. RPC framework

1. The reason for the emergence of RPC

Splitting different businesses into multiple applications and allowing different applications to undertake different functions is a nirvana to solve these problems. After splitting different businesses into different applications, it can not only greatly improve the stability of the system, but also help to enrich the technology selection and further ensure the performance of the system. In general, from a single application to a distributed multi-body application is the only way to upgrade the system.

When a monolithic application evolves into a multi-body application, remote calls come into play. In an application, mutual communication can be completed directly through local calls, but when it becomes a multi-body application, mutual communication must rely on remote calls. At this time, an efficient and stable RPC framework is very necessary. Some students may think that there is no need to use the RPC framework. Isn't a simple HTTP call also possible for remote communication? Indeed, simple HTTP calls can indeed achieve remote communication, but it is not so suitable for two reasons:

  • RPC remote calls are as clean and concise as local calls, but other methods are more intrusive to the code;

  • Generally, using the RPC framework to achieve remote communication is more efficient than other methods.

2. Introduction to RPC framework

For multi-body applications, since each service is deployed on different machines, the call between services cannot avoid the network communication process. Every time a service consumer calls a service, it has to write a bunch of code related to network communication, which is not only complicated but also extremely error-prone. If there is a way for us to call remote services like calling local services, and make the caller transparent to the details of network communication, it will greatly free the hands of programmers and greatly improve productivity. For example, when the service consumer executes helloService.hi("Panda"), it actually calls the remote service. This method is actually RPC (Remote Procedure Call Protocol), which is widely used in major Internet companies, such as Alibaba's HSF, Dubbo (open source), Facebook's Thrift (open source), Google GRPC (open source), Twitter's Finagle (open source) etc.

The main functional goal of RPC is to make it easier to build distributed computing (applications), without losing the semantic simplicity of local calls while providing powerful remote call capabilities . To achieve this goal, the RPC framework needs to provide a transparent call mechanism so that users do not need to explicitly distinguish between local calls and remote calls. To make network communication details transparent to users, we need to encapsulate the communication details. The following is a typical RPC call process, which reflects some of the communication details involved:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-tDqoiFrG-1666005213205) (proxy and RPC.assets/image-20221016160620328.png)]
(1). The service consumer (client) calls the service locally;
 (2). After receiving the call, the client stub is responsible for assembling methods, parameters, etc. into a message body that can be transmitted over the network;
 (3). The client stub finds Service address, and send the message to the server;
 (4). The server stub decodes the message after receiving it;
 (5). The server stub calls the local service according to the decoding result reflection;
 (6). The local service executes and returns the result To the server stub;
 (7). The server stub packs the returned result into a message and sends it to the consumer;
 (8). The client stub receives the message and decodes it;
 (9). The service consumer gets the final result.

The RPC framework is to encapsulate the steps 2~8, so that the user is transparent to these details, so that the remote method call looks like calling a local method.
The following is a code to implement a simple RPC framework

From the simple implementation of the RPC framework, the RPC client logic is: 1. First create a Socket client and establish a connection with the server, 2. Then use Java's native serialization/deserialization mechanism to send the call request to the client end, including the name of the called method, parameter list, and return the response of the server to the user. So far, the client process of a simple PRC call is completed. In particular, from the perspective of code implementation, the key to realizing transparent PRC calls isdynamic proxy, which is the soul of the RPC framework implementation.

  1. Server

    The server provides the service that the client expects, and generally includes three parts: service interface, service implementation, and service registration and exposure.

    //服务端接口
    public interface HelloService {
          
          
        String hello(String name);
        String hi(String msg);
    }
    
    //服务实现类
    public class HelloServiceImpl implements HelloService{
          
          
        @Override
        public String hello(String name) {
          
          
            return "Hello " + name;
        }
    
        @Override
        public String hi(String msg) {
          
          
            return "Hi, " + msg;
        }
    }
    
    //(重要)服务暴露
    public class RpcProvider {
          
          
        public static void main(String[] args) throws Exception {
          
          
            HelloService service = new HelloServiceImpl();
            // 自实现的RpcFramework,RPC框架使用export()方法将服务暴露出来,供客户端消费
            RpcFramework.export(service, 1234);
        }
    }
    
  2. client

    The client consumes the services provided by the server, generally including two parts: service reference and service interface

    //(服务引用)消费端通过RPC框架进行远程调用,这也是RPC框架功能之一
    public class RpcConsumer {
          
          
        public static void main(String[] args) throws Exception {
          
          
            // 由RpcFramework类中refer()方法生成的HelloService接口的代理
            HelloService service = RpcFramework.refer(HelloService.class, "127.0.0.1", 1234);
            //使用代理对象进行实现类中hello()方法的调用
            String hello = service.hello("World");
            System.out.println("客户端收到远程调用的结果 : " + hello);
        }
    }
    
    
    //与服务端共享同一个服务接口
    public interface HelloService {
          
          
        String hello(String name);
        String hi(String msg);
    }	
    
  3. RPC framework class RpcFramework

    public class RpcFramework {
          
          
        /**
         * 暴露服务
         * @param service 服务实现
         * @param port    服务端口
         * @throws Exception
         */
        public static void export(final Object service, int port) throws Exception {
          
          
            //如果服务和端口存在问题,抛出异常
            if (service == null) {
          
          
                throw new IllegalArgumentException("service instance == null");
            }
            if (port <= 0 || port > 65535) {
          
          
                throw new IllegalArgumentException("Invalid port " + port);
            }
            System.out.println("Export service " + service.getClass().getName() + " on port " + port);
            // 建立Socket服务端
            ServerSocket server = new ServerSocket(port);
            for (; ; ) {
          
          
                try {
          
          
                    // 监听Socket请求
                    final Socket socket = server.accept();
                    new Thread(new Runnable() {
          
          
                        @Override
                        public void run() {
          
          
                            try {
          
          
                                try {
          
          
                                    /* 获取请求流,Server解析并获取请求*/
                                    // 构建对象输入流,从源中读取对象到程序中
                                    ObjectInputStream input = new ObjectInputStream(
                                        socket.getInputStream());
                                    try {
          
          
    
                                        System.out.println("\nServer解析请求 : ");
                                        String methodName = input.readUTF();
                                        System.out.println("methodName : " + methodName);
                                        // 泛型与数组是不兼容的,除了通配符作泛型参数以外
                                        Class<?>[] parameterTypes = (Class<?>[])input.readObject();
                                        System.out.println(
                                            "parameterTypes : " + Arrays.toString(parameterTypes));
                                        Object[] arguments = (Object[])input.readObject();
                                        System.out.println("arguments : " + Arrays.toString(arguments));
    
    
                                        /* Server 处理请求,进行响应*/
                                        ObjectOutputStream output = new ObjectOutputStream(
                                            socket.getOutputStream());
                                        try {
          
          
                                            // service类型为Object的(可以发布任何服务),故只能通过反射调用处理请求
                                            // 反射调用,处理请求
                                            Method method = service.getClass().getMethod(methodName,
                                                parameterTypes);
                                            //invoke方法调用对应方法,得到处理返回值保存在result中
                                            Object result = method.invoke(service, arguments);
                                            System.out.println("\nServer 处理并生成响应 :");
                                            System.out.println("result : " + result);
                                            //将请求处理结果写回socket信息
                                            output.writeObject(result);
                                        } catch (Throwable t) {
          
          
                                            output.writeObject(t);
                                        } finally {
          
          
                                            output.close();
                                        }
                                    } finally {
          
          
                                        input.close();
                                    }
                                } finally {
          
          
                                    socket.close();
                                }
                            } catch (Exception e) {
          
          
                                e.printStackTrace();
                            }
                        }
                    }).start();
                } catch (Exception e) {
          
          
                    e.printStackTrace();
                }
            }
        }
    
    
        /**
         * 引用服务
         *
         * @param <T>            接口泛型
         * @param interfaceClass 接口类型
         * @param host           服务器主机名
         * @param port           服务器端口
         * @return 远程服务,返回代理对象
         * @throws Exception
         */
        @SuppressWarnings("unchecked")
        public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
          
          
            if (interfaceClass == null) {
          
          
                throw new IllegalArgumentException("Interface class == null");
            }
            // JDK 动态代理的约束,只能实现对接口的代理
            if (!interfaceClass.isInterface()) {
          
          
                throw new IllegalArgumentException(
                    "The " + interfaceClass.getName() + " must be interface class!");
            }
            if (host == null || host.length() == 0) {
          
          
                throw new IllegalArgumentException("Host == null!");
            }
            if (port <= 0 || port > 65535) {
          
          
                throw new IllegalArgumentException("Invalid port " + port);
            }
            System.out.println(
                "Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
    
            // JDK 动态代理,使用泛型实现广泛代理
            T proxy = (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                new Class<?>[] {
          
          interfaceClass}, new InvocationHandler() {
          
          
                    // invoke方法本意是对目标方法的增强,在这里用于发送RPC请求和接收响应
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
          
          
                        // 创建Socket客户端,并与服务端建立链接
                        Socket socket = new Socket(host, port);
                        try {
          
          
                            /* 客户端向服务端进行请求,并将请求参数写入流中*/
                            // 将对象写入到对象输出流,并将其发送到Socket流中去
                            ObjectOutputStream output = new ObjectOutputStream(
                                socket.getOutputStream());
                            try {
          
          
                                // 发送请求
                                System.out.println("\nClient发送请求 : ");
                                output.writeUTF(method.getName());
                                System.out.println("methodName : " + method.getName());
                                output.writeObject(method.getParameterTypes());
                                System.out.println("parameterTypes : " + Arrays.toString(method
                                    .getParameterTypes()));
                                output.writeObject(arguments);
                                System.out.println("arguments : " + Arrays.toString(arguments));
                                /* 客户端读取并返回服务端的响应*/
                                ObjectInputStream input = new ObjectInputStream(
                                    socket.getInputStream());
                                try {
          
          
                                    Object result = input.readObject();
                                    //如果result是一个异常说明服务端返回没成功,客户端只能抛出异常
                                    if (result instanceof Throwable) {
          
          
                                        throw (Throwable)result;
                                    }
                                    System.out.println("\nClient收到响应 : ");
                                    System.out.println("result : " + result);
                                    return result;
                                } finally {
          
          
                                    input.close();
                                }
                            } finally {
          
          
                                output.close();
                            }
                        } finally {
          
          
                            socket.close();
                        }
                    }
                });
            //给消费端返回代理对象,供使用
            return proxy;
        }
    }
    

3. Explanation of several issues about the RPC framework

(1). How does the RPC framework achieve transparent remote service calls?

How to encapsulate communication details so that users can call remote services like local calls? As far as Java is concerned,dynamic proxyIt is the solution. There are two ways of Java dynamic proxy: JDK dynamic proxy and CGLIB dynamic proxy . Although the proxy implemented by bytecode generation is more powerful and efficient, it is not easy to maintain the code. Therefore, most implementations of the RPC framework still choose the JDK dynamic proxy method. In the above example, the invoke method in the RPCFramework implementation encapsulates the details of communication with the remote service. The consumer first obtains the interface of the service provider from the RPCFramework. When the helloService.hi("Panda") method is executed, invoke will be called method.

(2). How to publish your own service?

How to let others use our service? Is it possible to directly write the IP and port of the service just like our code above? In fact, in the actual production implementation, it is unrealistic to use the method of human notification, because the service machine goes online/offline too frequently in actual production. If you find that the service provided by one machine is not enough, and you need to add another one, you must tell the caller that I have two IPs now, and you need to poll and call to achieve load balancing; One day a machine was down, and the caller found that half of the service was unavailable, so he had to manually modify the code to delete the ip of the machine that was down. This must have been quite painful!

Is there a way to realize automatic notification, that is, the online/offline of the machine is transparent to the caller, and the caller no longer needs to write the address of the service provider? Of course, all RPC frameworks in production use automatic notification. For example, HSF, the RPC framework used internally by Ali, completes this task through ConfigServer . In addition, Zookeeper is also widely used to implement automatic service registration and discovery functions . Regardless of the specific technology used, most of them usepublish/subscribe model

(3). Serialization and deserialization

We know that Java objects cannot be transmitted directly on the network. So, how does our RPC request be sent to the server, and how does the client receive the response from the server? The answer is that when a Java object is transferred, it is first serialized and then deserialized at the corresponding terminal to restore the object for processing. In fact, there are many serialization/deserialization technologies, such as Java's native serialization method,JSON, Ali's Hessian and ProtoBuff serialization, etc., they have differences in efficiency, but they have their own characteristics.

In addition to the three issues mentioned above, there are many things to consider in the RPC framework used in production, so I won't discuss it here. The purpose of this article is to let readers have a perceptual and in-depth understanding of the RPC framework. If this purpose is achieved, the author's purpose is basically achieved.

4. Thanks

The above content about the RPC framework is excerpted from the blogger linked below. It is only for learning and thinking. Thank you very much for the concise and transparent implementation ideas provided by the boss. RPC implementation requires details in all aspects, and it needs continuous learning and improvement!
Original link: https://blog.csdn.net/justloveyou_/article/details/79407248

Guess you like

Origin blog.csdn.net/zhangkai__/article/details/127373103