发表于: 2021-10-20 19:52:28

1 868


一,今天完成的事情

任务八


1,任务八的要求包含:

1.去Spring的官方网站查找Spring RMI 的官方手册,将原有学员系统中Service中拆分出来,变成一个RMI的Service。

2.在原来的WEB中调用Service。

3.部署两台Service,在WEB中随机访问任意一台Service。

4.部署两台WEB,通过Nginx配置两台WEB随机访问,两台WEB可以随机访问两台Service。

验收要求:1.任何一台WEB/Service 挂掉,不影响正常使用


2,我在前面任务在Linux云上部署了3台web Server, tomcat, jetty, resin。任务七利用其中的tomcat和resin,使用Nginx的Upstream在WEB中设置成功了随机访问两台Server。我没有设置权重,所以两台可能性都一样。所以任务八要求3我目前没有更改Nginx的情况下目前就是已经完成。


3,任务八要求1.去Spring的官方网站查找Spring RMI 的官方手册

如果需要使用什么产品,一定要看官方文档。Spring RMI 就有官方手册。

通过搜索,我看到 https://docs.spring.io/spring-integration/reference/html/rmi.html ,这个网址带.spring.io,应该就是Spring的官方文档。地址带rmi,进入查看确实是RMI的Spring官方文档。但是只是一小部分,并且我发现它的倾向在于OutboundGateway和InboundGateway,没发现它能和我需要把service包含为界分离出去有关。但是这个只是我查看的第一个文档,不合适很正常。


然后我查看 https://spring.getdocs.org/en-US/spring-framework-docs/docs/spring-integration/remoting/remoting-rmi.html  。这个域名是org结尾,带rmi。这个总体值得参考,但是RMI开发者很可能希望使用者通过查看RMI源代码,一起理解怎么使用RMI。

Often, your application server also maintains an RMI registry, and it is wise to not interfere with that one.

Furthermore, the service name is used to bind the service. 

So, in the preceding example, the service is bound at 'rmi://HOST:1199/AccountService'. We use this URL later on to link in the service at the client side.


4,学员报名系统相应集成,我准备集成任务七的代码,改成service分离。一般分布式系统会有运行先后顺序区别,先运行server,再运行client。

之前日报给的Demo一般是下面的方式使用RMI的。使用

LocateRegistry.createRegistry(8020);
Naming.bind("rmi://localhost:8888/Hello", helloWorld);

达到使用RMI的目的。


具体有4段代码,我放在同一个包里成功运行。当然是先server,后client。

package com.smi;

import java.rmi.Remote;
import java.rmi.RemoteException;

/**
* 在Java中,只要一个类extendsjava.rmi.Remote接口,即可成为存在于服务器端的远程对象,
* 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上
* 调用的接口。任何远程对象都必须直接或间接实现此接口。只有在远程接口
* (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。
*/
public interface HelloWorld extends Remote {
/**extendsRemote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常,
       * 则表明该方法可被客户端远程访问调用。
    */
   public String sayHelloToSomeBody(String someBodyName) throws RemoteException;
}


package com.smi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时,
该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为存根
而服务器端本身已存在的远程对象则称之为骨架。其实此时的存根是客户端的一个代理,用于与服务器端的通信,
而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
*/
// java.rmi.server.UnicastRemoteObject构造函数中将生成stubskeleton
public class HelloWorldServerImpl extends UnicastRemoteObject implements HelloWorld {
// 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
   public HelloWorldServerImpl () throws RemoteException {
super();
   }
@Override
   public String sayHelloToSomeBody(String someBodyName) throws RemoteException {
System.out.println("server impl");
       return someBodyName+". this is input. ";
   }
}


package com.smi;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

/**注册远程对象,向客户端提供远程对象服务
* 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称
* 但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求
* 到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了
*/
public class HelloWorldServer {
public static void main(String args[]) {
try {
/* 生成stubskeleton,并返回stub代理引用 */
           HelloWorld helloWorld = new HelloWorldServerImpl();
//加上此程序,就可以不要在控制台上开启RMI的注册程序,1099RMI服务监视的默认端口
           LocateRegistry.createRegistry(8020);
// 如果配置在远程服务器,把地址换成你的ip
           System.setProperty("java.rmi.server.hostname", "127.0.0.1");
           Naming.bind("rmi://localhost:8020/Hello", helloWorld);
           System.out.println(">>>>>INFO:远程IHello对象绑定成功!");
       } catch (RemoteException e) {
System.out.println("创建远程对象发生异常!");
           e.printStackTrace();
       } catch (AlreadyBoundException e) {
System.out.println("发生重复绑定对象异常!");
           e.printStackTrace();
       } catch (MalformedURLException e) {
System.out.println("发生URL畸形异常!");
           e.printStackTrace();
       }
}
}


package com.smi;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class SpringRMIClient {
public static void main(String args[]) {
try {
// 填写服务器ip
           HelloWorld helloWorld = (HelloWorld) Naming.lookup("rmi://127.0.0.1:8020/Hello");
           System.out.println(helloWorld.sayHelloToSomeBody("client"));
       } catch (NotBoundException e) {
} catch (MalformedURLException e) {
e.printStackTrace();
           e.printStackTrace();
       } catch (RemoteException e) {
e.printStackTrace();
       }
}
}


通过这4个类,又了解了RMI的使用方式。但是这个代码因为使用lookup 和 bind在类中,侵入严重。和3中举出的方式比,肯定不方便。3中的类似代码才是以service为界分离代码的比较可能的方式。


5,看了这个之前日报里经常出现的RMI的使用方式,加上这次日报的中的第3点,RMI也使用了分布式系统常用的称呼:server 和 client。

我比较推荐下面的demo来分离已有代码。还是为了展示RMI,代码中的Host和Client是为了拉起程序。没有Redis,看起来也没有service外的层次介入这个Demo。一共六个部分。

package com.javatpoint;

public interface Calculation {
int cube(int number);
}


package com.javatpoint;

public class CalculationImpl implements Calculation{

@Override
   public int cube(int number) {
return number*number*number;
   }

}


applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    In this xml file we are defining the bean for CalculationImpl
class and RmiServiceExporter class. We need to provide values for the following properties
of RmiServiceExporter class.

   service
   serviceInterface
   serviceName
   replaceExistingBinding
   registryPort-->
   <bean id="calculationBean" class="com.javatpoint.CalculationImpl"></bean>
   <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
       <property name="service" ref="calculationBean"></property>
       <property name="serviceInterface" value="com.javatpoint.Calculation"></property>
       <property name="serviceName" value="CalculationService"></property>
       <property name="replaceExistingBinding" value="true"></property>
       <property name="registryPort" value="1099"></property>
   </bean>
</beans>


package com.javatpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//It is simply getting the instance of ApplicationContext. But you need to run
//        this class first to run the example.
public class Host{
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       System.out.println("Waiting for requests");
   }
}


client-beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    In this xml file, we are defining bean for RmiProxyFactoryBean.
You need to define two properties of this class.

   serviceUrl
   serviceInterface-->
   <bean id="calculationBean" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
       <property name="serviceUrl" value="rmi://localhost:1099/CalculationService"></property>
       <property name="serviceInterface" value="com.javatpoint.Calculation"></property>
   </bean>
</beans>



package com.javatpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//This class gets the instance of Calculation and calls the method.
public class Client {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("client-beans.xml");
       Calculation calculation = (Calculation)context.getBean("calculationBean");
       System.out.println(calculation.cube(7));
   }
}


特别注意有4个属性在server一定要设置

       <property name="service" ref="calculationBean"></property>
       <property name="serviceInterface" value="com.javatpoint.Calculation"></property>
       <property name="serviceName" value="CalculationService"></property>
       <property name="registryPort" value="1099"></property>

对应client的

<property name="serviceUrl" value="rmi://localhost:1099/CalculationService"></property>



这4个属性如果查看

org.springframework.remoting.rmi.RmiServiceExporter

会发现有2个不是在这里直接定义的。RMI的设计就有

public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean

这样的写法要适应,有往下看。特别注意过

public void prepare() throws RemoteException {
this.checkService();


public abstract class RemoteExporter extends RemotingSupport 

RemotingSupport 是很多分布式的基础。有一点时间久看下去了。


还是先server(这个代码中叫host)。再运行client。正常运行。然后项目要求做成2个,是用这里的方式能做5个或者以上。所以我写了类似的希望RMI server client能调用的方法。根据代码思路,demo想得到的方法,尝试成功后再改变任务七结构。尤其是Redis大部分在service层使用,但是controller有时候用Redis帮助也没有问题的情况。


现在可能是多个service,抽象出来的client就只有一个。service可以是一样,互相负担。多个service分开当然也可能是提供不同的service。但是按照任务八意思,随机访问一台service或者server提供的服务应该都相同,所以任务八的两个service应该提供的服务相同。


因为demo结构简单,我写了相同另一个,不同另一个service。成功运行。自娱自乐,参考好这个代码就差不多能做完任务八了。


6,这些代码都已经包含编写相应的Stub(Client端)和Skeleton(Server端)程序。图示。通过之前的代码我也理解到  client有service的interface,没有service的impl。server端才提供 service的impl,当然也有service的interface。

See the source image


二,今天问题

希望有创造,编程制造工具的更多心得。至少能和其他人一起写出RMI工具的水平。


三,今天的收获

使用工具,甚至是为了创造新的工具,类似需求,也可能不那么不类似。我很高兴的阅读了葡萄藤老大的《通用进项目须知》,对于任务到复盘的阶段,这个须知已经提醒得很到位。


四,明天的计划

任务八



返回列表 返回列表
评论

    分享到