zoukankan      html  css  js  c++  java
  • Java实现远程服务生产与消费(RPC)的4种方法-RMI,WebService,HttpClient,RestTemplate

    本文将通过具体的远程服务发布与消费案例展示4种RPC远程调用方法.

    一. 通过rmi实现远程服务的生产与消费

    • Java自身提供了java.rmi包, 方便开发者进行远程服务的部署与消费, 下面将通过具体案例进行讲解.

    远程服务提供者实现.

    创建rmi-provider项目(Maven)

    1. 创建UserService接口.
    //将要发布的服务的接口
    public interface UserService extends Remote {
        public String helloRmi(String name) throws RemoteException;
    }
    
    1. 创建UserServiceImpl实现类
    • 注意, UserServiceImpl除了实现UserService接口外, 还要继承UnicastRemoteObject类, 你可以理解为它是一个发布出去供他人调用的类, 当UserServiceImpl实现了这个类后, UserServiceImpl就能被发布出去供别人调用.
    //将要发布的服务的实现类
    public class UserServiceImpl extends UnicastRemoteObject implements UserService {
        public UserServiceImpl() throws RemoteException {
            super();
        }
    
        public String helloRmi(String name) throws RemoteException {
            return "hello " + name;
        }
    }
    
    1. 发布远程服务
    public static void main(String[] args) {
        try {
            //完成远程服务的发布
            LocateRegistry.createRegistry(8888);//将远程服务发布在本地的8888端口
            String name = "rmi://localhost:8888/rmi";//发布的远程服务被访问的url
            UserService userService = new UserServiceImpl();//创建一个提供具体服务的远程对象
            Naming.bind(name, userService);//给远程服务绑定一个url
            System.out.println("--- 已发布rmi远程服务 ---");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    远程服务消费者实现

    创建rmi-consumer项目

    1. rmi-provider项目种的UserService接口与UserServiceImpl实现类复制到本rmi-consumer项目中.(这一步可以进行优化解耦, 我们可以多创建一个rmi-resource项目, 让rmi-providerrmi-consumer共同依赖rmi-resource项目, 然后把资源文件比如远程服务所用到的UserService等放入rmi-resource项目中)
    2. 远程服务消费者对远程服务发起调用.
    public static void main(String[] args) {
        try {
            //发布远程服务的访问url
            String name = "rmi://localhost:8888/rmi";
            //通过发布远程服务的url, 获取远程服务的代理对象
            UserService userService = (UserService) Naming.lookup(name);
            System.out.println("获得的远程服务的代理对象:" + userService.getClass().getName());
            String result = userService.helloRmi("rmi");//拿到远程方法调用的结果
            System.out.println("result: " + result);
    
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    //最后输出
    获得的远程服务的代理对象:com.sun.proxy.$Proxy0
    result: hello rmi
    
    • 通过最后的输出我们看到获得的远程服务对象是动态代理产生的.

     

    二. 通过WebService实现远程服务的生产与消费

    • WebService协议是RPC的一种具体实现, 服务提供方和消费方通过http + xml进行通信.

    远程服务提供者实现.

    1. 首先创建远程服务接口UserService及其实现类UserServiceImpl.
    • 注意, 使用WebService时需要对远程服务加上注解@WebService
    @WebService
    public interface UserService {
        public String sayHello(String name);
    }
    
    @WebService
    public class UserServiceImpl implements UserService {
        @Override
        public String sayHello(String name) {
            return "hello " + name + "~";
        }
    }
    
    1. 发布远程服务, 过程和rmi差不多, 需要提供远程服务的访问地址和具体的远程服务实现类, 使用Endpoint类的publish()方法进行发布, 这都是JDK封装好的.
    public class WsProviderApp {
        public static void main(String[] args) {
            //发布的WebService的被访问地址
            String address = "http://localhost:9999/ws";
            //创建远程服务对象
            UserService userService = new UserServiceImpl();
            //发布服务
            Endpoint.publish(address, userService);
            System.out.println("远程服务已经发布...");
        }
    }
    

    查看远程服务文档wdsl

    • rmi不同的是, WebService发布后, 调用者可以通过查看它的文档对远程服务发起调用.
    • 查看的方法是在浏览器中输入远程服务的访问地址加上?wdsl, 比如本案例中是http://localhost:9999/ws?wsdl
    • 注意, 在客户端调用远程方法时需要用工具对wdsl文档进行解析, 并获得调用远程方法的工具类. 具体操作见下一段.

    远程服务消费者实现.

    1. 首先根据文档获得调用远程服务的工具类, JDK已经为我们封装好了获取的工具, 它在bin目录下, 名字是wsimport
    2. 打开命令行, 在命令行中输入解析命令
    wsimport -keep -d C:githubRepositoriesshoppingws-consumersrcmainjava -p com.shenghao.client http://localhost:9999/ws?wsdl
    
    解释:
    1. wsimport 是命令的名字
    2. -keep 用于保留生成的类, 如果没有该指令会只生成class文件
    3. -d 后面接项目中存放这些工具类的包, 填绝对路径
    4. -p 填wdsl文档的地址
    

     
    5. 可以看到命令执行完后, 指定的包中出现一堆相关的类, 最直接调用到的类是UserServiceImplService. 下面演示对远程方法进行调用.

    public static void main(String[] args) {
        //创建服务类对象
        UserServiceImplService service = new UserServiceImplService();
        //获得远程服务的代理对象
        UserServiceImpl userService = service.getUserServiceImplPort();
        System.out.println(userService.getClass().getName());
        //对远程服务对象的方法进行调用
        String result = userService.sayHello("炭烧生蚝");
        System.out.println(result);
    }
    
    //结果输出
    com.sun.proxy.$Proxy32
    hello 炭烧生蚝~
    

    三. 通过HttpClient实现远程服务的生产与消费

    • 这里我们换一个案例进行演示. 假设现在有一套用户系统和一套订单系统, 要实现用户系统访问订单系统以获得某个用户的订单信息.

    远程服务提供者实现

    • 提供远程服务的过程和响应web请求很相似, 只不过响应的不是<html>标签, 而是json字符串. 微信小程序前后端通信也是这个原理.
    1. 创建名为order-sys的Maven项目, 指定打包为war包.
    点击这里查看pom.xml文件, 常规操作

    <properties>
        <!-- spring 依赖 -->
        <spring.version>4.3.18.RELEASE</spring.version>
        <jstl.version>1.2</jstl.version>
        <servlet-api.version>2.5</servlet-api.version>
        <jsp-api.version>2.0</jsp-api.version>
        <jackson.version>2.9.0</jackson.version>
    </properties>
    
    <dependencies>
        <!-- jsp相关依赖 -->
        <!-- servlet依赖 -->
        <!-- jstl依赖 -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>${servlet-api.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>${jsp-api.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- springmvc依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>order</finalName>
        <plugins>
            <!-- 配置Tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/order</path>
                    <port>7070</port>
                </configuration>
            </plugin>
    
        </plugins>
    </build></code></p>
    

     
    2. 创建订单类

    public class Order {
        private String id;
        private Double total;
        private String date;
    
        //get / set ...
    }
    
    1. 对外提供服务, 发布时打包发布到Tomcat
    @Controller
    public class OrderController {
        /**
         * 接收http请求, 响应订单集合, 异步响应
         * 将list集合序列化为json串响应
         * @param uid
         * @return
         */
        @RequestMapping("/loadOrderList2")
        @ResponseBody
        public List<Order> loadOrderList2(String uid){
            System.out.println("uid: " + uid);
    
            //模拟订单数据
            Order o1 = new Order();
            o1.setId("111");
            o1.setTotal(333.33);
            o1.setDate("2019-4-29");
    
            Order o2 = new Order();
            o2.setId("222");
            o2.setTotal(444.44);
            o2.setDate("2019-5-29");
    
            Order o3 = new Order();
            o3.setId("333");
            o3.setTotal(555.55);
            o3.setDate("2019-6-29");
    
            List<Order> list = new ArrayList<>();
            list.add(o1);
            list.add(o2);
            list.add(o3);
    
            return list;
        }
    }
    

    远程服务消费者实现

    1. 在服务消费端使用HttpClient发送请求, 可以理解为模拟浏览器发送post/get请求. HttpClient为我们封装了拼接一个请求的细节, 使得发送一个请求变得容易.
    public static void main(String[] args) throws IOException {
        //发送远程的http请求的地址
        String url = "http://localhost:7070/order/loadOrderList2";
        //创建HttpClient对象
        CloseableHttpClient client = HttpClients.createDefault();
        //创建HttpPost对象, 发送post请求
        HttpPost method = new HttpPost(url);
        //封装发送到服务提供者的参数
        NameValuePair id = new BasicNameValuePair("uid", "10001");
        List<NameValuePair> params = new ArrayList<>();
        params.add(id);
        //封装请求体数据
        method.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
        //发送具体的http请求
        HttpResponse response = client.execute(method);
    
        //获得服务提供者响应的具体数据
        HttpEntity entity = response.getEntity();
        //获得http的响应体
        InputStream is = entity.getContent();
    
        int len = 0;
        char[] buf = new char[1024];
        //使用字符流读
        InputStreamReader reader = new InputStreamReader(is);
        StringBuffer sb = new StringBuffer();
        while((len = reader.read(buf)) != -1){
            sb.append(String.valueOf(buf, 0, len));
        }
        System.out.println(sb);
    
        //将响应回来的json字符串解析为Order集合
        List<Order> list = JSON.parseArray(sb.toString(), Order.class);
        for(Order o : list){
            System.out.println(o.getId() + "	" + o.getTotal() + "	" + o.getDate());
        }
    }
    

     

    四. 通过spring提供的RestTemplate实现远程服务的生产与消费

    • 通过一个红包系统和订单系统进行演示, 红包系统访问订单系统, 获得某个用户的订单信息, 派发红包.
    • 订单系统继续沿用HttpClient中的订单系统, 通过访问loadOrderList2方法能返回一个订单集合Json字符串.

    远程服务消费者实现.

    @Controller
    public class RedController {
        //注入由spring提供的RestTemplate对象
        @Autowired
        private RestTemplate restTemplate;
        /**
         * 发送远程的http请求, 消费http服务
         * 获得订单对象的集合
         */
        @RequestMapping("/loadOrderList3")
        @ResponseBody
        public List<ResponseEntity<Order[]>> loadOrderList3(String uid){
            //发送远程http请求的url
            String url = "http://localhost:7070/order/loadOrderList2";
            //发送到远程服务的参数
            MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
            params.add("uid", uid);
    
            //通过RestTemplate对象发送post请求
            ResponseEntity<Order[]> entitys = restTemplate.postForEntity(url, params, Order[].class);
    
            //查看响应的状态码
            System.out.println(entitys.getStatusCodeValue());
    
            //查看响应头
            HttpHeaders headMap = entitys.getHeaders();
            for(Map.Entry<String, List<String>> m : headMap.entrySet()){
                System.out.println(m.getKey() + ": " + m.getValue());
            }
    
            return Arrays.asList(entitys);
        }
    }
    
  • 相关阅读:
    Postgresql修改序列方法 select setval('dataset_id_seq',1,false);
    再谈Elasticsearch全文搜索:你不知道的query_string、match、term、match_phrase的区别
    crosstab(unknown, unknown) does not exist
    Elasticsearch High Level Rest Client 发起请求的过程分析
    提取经过Jar2Exe编译加密的源代码 教程1
    Unity 协程与线程
    box unboxing(装箱 拆箱) C#编程指南
    C++基础笔记(四)C++内存管理
    C++ 箭头-> 双冒号:: 点号.操作符区别
    ref out 方法参数
  • 原文地址:https://www.cnblogs.com/tanshaoshenghao/p/10796319.html
Copyright © 2011-2022 走看看