最近在做一个调度服务器的项目,因为各个服务器上面的定时任务比较多,分别执行的话运维检查起来比较麻烦.就想到搞一个专门的调度服务器来统一管理这些任务.因为随时可能增加新的服务器或者新的命令,要是全写在一起每次要把整个程序替换掉,上线不方便.发现spring可以很好的解决这个问题,新加的程序可以以单独jar包的方式添加,只需要修改ApplicationContext就行了,client端只需要改配置就行了程序可以不用改.个人感觉spinrg自身的RMI类比Java自带的好理解一些.
首先是Server端,需要有一个启动函数,以及各命令对应的实现接口,启动类 .为了部署方便期间,配置文件路径直接改外部输入了
public class StartRMIServer {
public static void startRMIServer(String path) {
if(path != null && path.startsWith("/")) {
new FileSystemXmlApplicationContext("/" + path + "applicationContext.xml");
}else{
new FileSystemXmlApplicationContext(path + "applicationContext.xml");
}
System.out.println("RMI服务端启动!!!");
}
public static void main(String[] args) {
startRMIServer(args[0]);
}
}
关于FileSystemXmlApplicationContext这里一开始不知道,在Linux下输入的根路径会被当成当前路径处理,去查了之后发现是构造函数会把第一个'/'给去掉,所以这里需要加上.也可以用ClassPathXmlApplicationContext直接以file:开头
然后加入接口和实现类,当客户端发起命令时就会调用该接口,接口对应的实现类由spring定义文件来控制,所以就能够实现不同的命令对应不同的实现类.这里定义了两个不同的实现类
public interface IRmiService{
String getMsg(String params) throws Exception;
}
调用的类预留一个字符串参数,便于有需要的实现类解析后使用
public class HelloWordService implements IRmiService{
public String getMsg(String params) throws Exception{
return "Hello World!";
}
}
public class RealTimeService implements IRmiService {
public String getMsg(String params){
return "Real Time!" + params;
}
}
ApplicationContext配置定义如下,两个实现类分别对应不同的命令
<!-- 定义接口实现类 -->
<bean id="helloWordService" class="com.cmb.SpringRmi.HelloWordService" scope="prototype"/>
<!-- 定义服务端接口 -->
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- 将远程接口实现类对象设置到RMI服务中 -->
<property name="service" ref="helloWordService" />
<!-- 设置RMI服务名,将作为RMI客户端的调用接口-->
<property name="serviceName" value="helloWord" />
<!-- 将远程接口设置为RMI服务接口 -->
<property name="serviceInterface" value="com.cmb.SpringRmi.IRmiService" />
<!-- 为RMI服务端远程对象注册表设置端口号-->
<property name="registryPort" value="9090" />
</bean>
<bean id="realTimeService" class="com.cmb.SpringRmi.RealTimeService" scope="prototype"/>
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="service" ref="realTimeService" />
<property name="serviceName" value="realTime" />
<property name="serviceInterface" value="com.cmb.SpringRmi.IRmiService" />
<property name="registryPort" value="9090" />
</bean>
这样server端就配置完成了,可以直接启动试一下
client端相当于是每次给server发一个请求命令,server端接收到之后去执行对应的实现类.我这边实现类加了log4j功能,参数是context路径,命令和命令对应的参数
public class RMIClient {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("file:" + args[0] + "/applicationContext.xml");
IRmiServer helloWord = (IRmiServer) ctx.getBean(args[1]);
LogBean log = (LogBean) ctx.getBean("log");
PropertyConfigurator.configure(log.getPath());
Logger logger = Logger.getLogger(RMIClient.class);
logger.info("Call " + args[1]);
try {
String params = "";
if (args.length > 2 && args[2] != null && !"".equals(args[2])) {
params = args[2];
}
logger.info(helloWord.getMsg(params));
logger.info(args[1] + " succeed");
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.exit(1);
}
}
}
接口名字其实不需要和server端一样
public interface IRmiServer{
String getMsg(String params) throws Exception;
}
spring设置如下,这里设置了两条命令helloWorld和printDate,分别对应类HelloWordService和RealTimeService
<!--bean id为程序调用时输入的第一参数-->
<bean id="helloWord" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<!--ip和端口为server的ip和刚才注册的端口号,服务名为设置的serviceName-->
<property name="serviceUrl" value="rmi://localhost:9090/helloWord"/>
<!--调用的接口-->
<property name="serviceInterface" value="com.cmb.SpringRmiClient.IRmiServer"/>
<!-- 当连接失败时是否刷新远程调用stub -->
<property name="refreshStubOnConnectFailure" value="true"/>
</bean>
<bean id="printDate" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:9090/realTime"/>
<property name="serviceInterface" value="com.cmb.SpringRmiClient.IRmiServer"/>
<property name="refreshStubOnConnectFailure" value="true"/>
</bean>
此时就可以执行client,可以看到如果输入的命令不同,打印出的结果是不同的,说明调用的实现类不同.
部署的时候采用命令行指定java.ext.dirs的方式,这样可以把所有相关的程序jar包都放到文件夹里面,便于后续扩展程序的部署.
java -Djava.ext.dirs="rmiclient/lib" -jar rmiclient/rmiclient.jar D:/test/rmiclient printDate
关于后续添加部署.我现在想要添加一个程序,那么应该把这个jar包放置到server上的lib目录中,把server进程结束,然后在server的ApplicationContext增加一组bean,client的ApplicationContext也对应增加一组,重启之后就可以使用新的程序了.
通过这个小工具的开发对spring工厂和IOC有了些了解,后面要多研究吃透spring
个人GitHub地址: https://github.com/GrayWind33