Spring核心技术与最佳实践 –Spring 提供的远程访问

 

如果需要将应用程序的某些功能暴露给远程客户端访问,就需要某种远程调用机制。除了Java内置的标准的RMI远程调用机制外,还有众多可供选用的远程调用方案。Spring框架提供了对各种远程访问的良好支持,包括RMI、HTTP远程调用和WEB服务。

 

8.1RMI远程调用

 

RMI是Java标准的远程方法调用接口,即Rmote Method Invocation的缩写。RMI从JDK1.1就引入了,它基于Java的序列化机制实现远程方法调用。下面我们分别来看看手动使用RMI和在Spring中使用RMI的异同。

 

 

8.1.1 实现RMI

 

让我们来看一个常见的例子。假定应用程序已经设计并实现了一个用户管理的模块,允许创建新用户并让用户登陆,定义RMI工程结构如下:

 

 

User 对象代表一个用户。

 

public classUserimplements Serializable {

 

    private Stringusername;

    private Stringpassword;

 

    public User(String username, String password) {

       this.username

       this.password

    }

 

    public String getUsername() {

       returnusername;

    }

    public String getPassword() {

       returnpassword;

    }

}

 

 

UserService定义了用户服务的接口

public interface UserService {

 

    User login(String username, String password);

 

    void create(String username, String password);

}

 

 

并且有一个默认的实现类。

public class UserServiceImpl implements UserService {

 

    private Map<String, String>users =new HashMap<String, String>();

   

@Override

    public void create(String username, String password) {

        if(username==null || password==null){

        throw new IllegalArgumentException("Invalid args.");

        }           

        if(users.get(username)!=null){

        throw new RuntimeException("User exist!");

        }           

users.put(username, password);

    }

 

@Override

    public User login(String username, String password) {

        if(username==null || password==null){

        throw new IllegalArgumentException("Invalid args.");

        }           

        if(password.equals(users.get(username))){

        return new User(username, password);

        }           

        throw new RuntimeException("Login failed.");

    }

}

 

 

 

这个模块在应用程序中运行良好,现在,我们希望将它暴露为远程服务,以便其他远程应用程序也能够通过这个模块来管理用户。为了以RMI的方式来实现这个远程服务,我们先看看实现RMI的必要条件。

 

(1)      服务接口必须从Remote派生,并且在每个远程方法中抛出RemoteException,很明显UserServcie不符合这个条件。

(2)      实现类处理实现服务接口外,还必须从UnicatRemoteObject派生,因此,UserServiceImpl也不符合这个条件。

(3)       

 

所有的方法的参数和返回值都必须是基本类型,或者实现了Serialzable接口,或者实现了Remote接口,幸运的是,对User对象添加一个Serializable接口是非常容易的。

 

为了实现RMI远程调用,按照传统的RMI调用方式,我们不得不修改UserService接口。然后,如果让UserService接口的每个方法都抛出RemoteException,由于RemoteException是CheckedExcpetion,势必造成应用程序其他派生依赖UserService接口的代码无法编译通过,其代价是巨大的。因此,只好考虑定义另一个RmiUserService接口。

 

 

public interface RmiUserService extends Remote {

   

    User login(String username, String password) throws RemoteException;

 

    void create(String username, String password)throws RemoteException;

}

 

 

 

然后,为RmiUserService接口在编写一个实现类。

public classRmiUserServiceImplextends UnicastRemoteObjectimplements RmiUserService {

 

service = new UserServiceImpl();

 

    public RmiUserServiceImpl()throws RemoteException {   

    }  

 

@Override

    public void create(String username, String password) throws RemoteException {

service.create(username, password);      

    }

 

@Override

    public User login(String username, String password)throws RemoteException {   

       returnservice.login(username, password);

    }

 

    public static void main(String[] args)throws Exception {

        LocateRegistry.createRegistry(1099); //

        Naming.bind("rmi://localhost:1099/UserService",new RmiUserServiceImpl());

        System.out.println("服务端已经启动............");       

    }

}

 

 

 

现在,我们终于可以编写客户端来调用这个RmiUserService了。编写一个Client类来带哦用RMI服务。

 

public class Client {

    public static void main(String[] args)throws Exception {

    RmiUserService service = (RmiUserService)Naming.lookup("rmi://localhost:1099/UserService");              

"new_user","a_test");     

"new_user","a_test");

    if(user!=null){

        System.out.println("------------------------------");

        System.out.println("用户名:" + user.getUsername() +"登陆成功!");   

       System.out.println("------------------------------");

    }

    }

}

 

首先启动RMI 服务(RmiUserServiceImpl.java),然后再启动客户端(Client.java).,客户端运行后,控制台打印如下信息:

------------------------------

用户名:new_user登陆成功!

------------------------------

 

 

 

 

 

从上面的例子可以看到,为了实现一个简单的RMI远程调用,我们不得不遵循RMI规范,定义规范符合RMI调用的接口和实现类,还必须手动编译出Stub类共客户端使用。这实在是台麻烦了,为了避免这么复杂的步骤,我们看看用Spring能否简化RMI的调用。


 

 

8.1.2 在Spring中输出RMI


我们新建一个RMI_Spring工程,复制User、UserService、UserServiceImpl者3个类,然后我们看看如何在Spring中将UserService作为一个RMI服务输出。

 

令我们非常兴飞的是,不用编写一行代码,就可以直接在Spring的XML配置文件中将UserService作为RMI服务输出。

 

<beanid="userService"class="com.zsw.service.UserServiceImpl"/>

 

<beanid="rmiService"class="org.springframework.remoting.rmi.RmiServiceExporter">

<propertyname="serviceName"value="UserService"/>

<propertyname="service"ref="userService"/>

<propertyname="serviceInterface"value="com.zsw.service.UserService"/>

<propertyname="registryPort"value="1099"/>

</bean>

 

唯一的代码是编写Main.java中的main()方法启动的Spring容器。

    public static void main(String[] args) {

        new ClassPathXmlApplicationContext("server.xml");

        System.out.println("Server has bean started!");

    }

 

编写客户端稍微麻烦一点,需要用到Spring框架提供的RmiProxyFactoryBean.

    public static void main(String[] args)throws Exception {

      

RmiProxyFactoryBean factory =new RmiProxyFactoryBean();       

        factory.setServiceInterface(UserService.class);       

"rmi://localhost:1099/UserService");

        factory.afterPropertiesSet();

        UserService userService = (UserService)factory.getObject();

       

       

       

"test","password");

        System.out.println(userService.login("test","password"));

        try {

"test","bad-password");

        }catch(Exception e) {

            System.err.println(e.getMessage());

        }      

    }

 

 

先执行Main中的main()方法,启动RMI服务,在启动客户的进行调用测试,输出结果如下:

 

信息: Binding service 'UserService' to RMI registry: RegistryImpl[UnicastServerRef [liveRef: [endpoint:[169.254.53.167:1099](local),objID:[0:0:0, 0]]]]

Server has bean started!

 

com.zsw.entity.User@173831b

Login failed.

 

令人非常兴奋的是,整个过程没有引入任何RMI必须的接口,虽然客户段调用代码稍微复杂,并且需要Spring的jar包支持,但是,由于不需要手动调用rmic生成Stub类,整个带哦用过程被大大简化了。


 

8.1.3 访问RMI

 

事实上,如果客户端也在Spring容器中启动,则完全可以在XML配置文件中定义UserService并直接使用。

 

<bean id="userService" class="com.zsw.service.UserServiceImpl" />

<bean id="rmiService" class="org.springframework.remoting.rmi.RmiServiceExporter">

<property name="serviceName" value="UserService" />

<property name="service" ref="userService" />

<property name="serviceInterface" value="com.zsw.service.UserService" />

<property name="registryPort" value="1099" />

</bean>

 

这样,客户端完全不必编写远程服务接口的代码,就可以直接将userService注入到需要的组件中去。

 

对于传统的RMI调用,服务段发布了RMI服务后,还必须通过rmic编译出存根共客户端使用,这个调用过程如下:

 

 

 

Stub的作用便是将客户端对RmiService接口的调用转变成实际的远程调用,在Spring中,由于使用了JDK动态代理,因此可以在运行期动态实现RmiServiceStub,免去了使用rmic编译存根的麻烦。

 

如果RMI服务不是由Spring包装后启动的,而是直接以Remote接口实现,例如,在RMI工程中的RmiUserServiceImpl类实现了RMI服务,能否在Spring中调用呢?答案是肯定的,只需要将Client代码修改为实际的RMI服务接口。

    public static void main(String[] args) throws Exception {      

       BeanFactory context = new ClassPathXmlApplicationContext("client.xml");

"userService");

 

"test", "password");

        System.out.println(userService.login("test", "password"));

        try {

"test", "bad-password");

        }catch(Exception e) {

            System.err.println(e.getMessage());

        }      

    }

 

执行,跟RmiProxyFactoryBean执行的效果一样。