上一篇rmi协议的服务发布已经完成,现在看一下服务引用的实现。ReferenceBean的实现同第四篇中的ServiceBean是不同的,ServiceBean是通过实现spring的InitializingBean接口,在afterPropertiesSet()方法中发布服务的。在spring容器中,ServiceBean初始化之后,就发布服务了,这个bean对象是没有其它对象引用它的。
对ReferenceBean来说,我们在xml配置的这个bean在初始化后,业务bean会引用这个bean的,通过ldubbo:reference的id我们可以得到这个bean,并且这个bean可以转为对应接口类。
在写ReferenceBean类的实现前,先创建一个consumer项目,跟上一篇的pom文件一样,引入ldubbo, ldubbo-api项目和slf4j的依赖,然后创建applicationContext-ldubbo.xml配置文件。
客户端最初约定的配置第二篇有提到,现在看一下客户端的spring配置文件applicationContext-ldubbo.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"
xmlns:ldubbo="http://www.lipenglong.com/schema/ldubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.lipenglong.com/schema/ldubbo
http://www.lipenglong.com/schema/ldubbo/ldubbo.xsd" default-lazy-init="true">
<ldubbo:registry address="127.0.0.1:1099" protocol="rmi"/>
<ldubbo:reference interface="com.lipenglong.ldubbo.api.service.UserService" id="userService"/>
</beans>
配置文件定义好了,接着看一下consumer端我们怎么像dubbo那样调用服务,
package com.lipenglong.ldubbo.consumer;
import com.lipenglong.ldubbo.api.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* com.lipenglong.ldubbo.consumer
* </p>
* Created by lipenglong on 2017/8/30.
*/
public class Consumer {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-ldubbo.xml");
UserService userService = (UserService) context.getBean("userService");
System.out.println(userService);
System.out.println(userService.queryUserList());
System.out.println(userService.getUserById(23L));
}
}
从spring的context中得到“userService”对象,转换为UserService 类,然后我们就可以调用它的方法了。
这里有没有这样的疑问,<ldubbo:reference />
这个配置初始化后,根据id得到bean应该是ReferenceBean啊,在第3篇中LdubboNamespaceHandler的代码中
registerBeanDefinitionParser("reference", new LdubboBeanDefinitionParser(ReferenceBean.class));
reference配置应该转化为ReferenceBean对象,它怎么转换为UserService对象呢?
这里就要用到spring的FactoryBean接口,实现该接口的类,需要实现getObject(),getObjectType()和isSingleton()三个方法,根据该bean的id从spring容器中得到的不是FactoryBean本身,而是getObject()返回的对象。ReferenceBean实现了FactoryBean接口,所以当我们context.getBean("userService")
时,得到并不是ReferenceBean对象,而是getObject()返回的对象,看一下ReferenceBean代码:
package com.lipenglong.ldubbo.config.spring;
import com.lipenglong.ldubbo.config.ReferenceConfig;
import org.springframework.beans.factory.FactoryBean;
/**
* com.lipenglong.ldubbo.config.spring.ReferenceBean
* </p>
* Created by lipenglong on 2017/8/30.
*/
public class ReferenceBean extends ReferenceConfig implements FactoryBean {
private static final long serialVersionUID = -2278602690695158523L;
@Override
public Object getObject() throws Exception {
return get();
}
@Override
public Class<?> getObjectType() {
return getInterfaceClass();
}
@Override
public boolean isSingleton() {
return true;
}
}
返回对象的get()方法在ReferenceConfig中实现,看一下ReferenceConfig的实现:
package com.lipenglong.ldubbo.config;
import com.lipenglong.ldubbo.rpc.Protocol;
import com.lipenglong.ldubbo.rpc.ProtocolFactory;
/**
* com.lipenglong.ldubbo.config.ReferenceConfig
* </p>
* Created by lipenglong on 2017/8/30.
*/
public class ReferenceConfig<T> extends AbstractConfig {
private static final long serialVersionUID = -226929659993307561L;
private String interfaceName;
private RegistryConfig registryConfig;
private Class<?> interfaceClass;
private Protocol protocol;
private transient volatile T ref;
public String getInterface() {
return interfaceName;
}
public void setInterface(String interfaceName) {
this.interfaceName = interfaceName;
}
public RegistryConfig getRegistryConfig() {
return registryConfig;
}
public void setRegistryConfig(RegistryConfig registryConfig) {
this.registryConfig = registryConfig;
}
public T get() {
if (ref == null) {
init();
}
return ref;
}
private void init() {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ref = createProxy();
}
private T createProxy() {
protocol = ProtocolFactory.getProtocol(registryConfig.getProtocol());
return (T) protocol.refer(interfaceClass, registryConfig);
}
public Class<?> getInterfaceClass() {
if (interfaceClass != null) {
return interfaceClass;
}
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return interfaceClass;
}
}
通过setInterface方法,把xml文件中配置的interface属性值赋值给interfaceName,setRegistryConfig方法,把xml配置中的RegistryConfig对象引用过来,因为要创建的interface属性配置的接口的代理类,代理类需要通过address和协议调用远程服务。
get()方法返回了一个泛型类(因为不知道具体的类型),当ref对象不存在时,执行init()方法。init方法中根据interfaceName得到interfaceClass,通过createProxy()方法得到ref对象。createProxy方法中根据registryConfig.getProtocol()获取xml中配置的具体协议(rmi或ldubbo),然后通过ProtocolFactory.getProtocol()方法根据配置的name得到具体的protocol实现类,调用protocol的refer方法得到代理类。
在上一篇rmi协议服务发布中有AbstractProxyProtocol的代码, AbstractProxyProtocol 类的refer方法,调用了抽象方法doRefer,RmiProtocol类的doRefer当时没有实现,现在看一下rmi协议类doRefer方法的具体实现:
@Override
protected <T> T doRefer(Class interfaceClass, RegistryConfig registryConfig) {
RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
rmiProxyFactoryBean.setServiceUrl("rmi://" + registryConfig.getAddress() +
"/" + interfaceClass.getName());
rmiProxyFactoryBean.setServiceInterface(interfaceClass);
rmiProxyFactoryBean.setCacheStub(true);
rmiProxyFactoryBean.setLookupStubOnStartup(true);
rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
rmiProxyFactoryBean.afterPropertiesSet();
return (T) rmiProxyFactoryBean.getObject();
}
doRefer通过spring的RmiProxyFactoryBean类实现服务引用,new完对象后,设置url,接口和其它的一些配置,调用了afterPropertiesSet()方法,最后通过getObject()返回了代理类,所以RmiProxyFactoryBean同时实现了InitializingBean 和FactoryBean,看一下spring的源码,RmiProxyFactoryBean是在afterPropertiesSet()方法中创建代理类的。
RmiProtocol类通过spring rmi发布服务,得到的代理类,ldubbo这里只是把spring的rmi包装了一下,大家看一下spring rmi的相关文档,就发现服务端、客户端的实现就是我们的doExport()和doRefer()方法做的事情。
启动Provider端,然后运行Consumer,输出:
ldubbo框架客户端可以调用远程服务了!
rmi协议都是使用spring rmi的实现,我们可以不定义自己的<ldubbo:service/>
和<ldubbo:reference/>
等标签,直接像spring帮助文档中那样定义相关的<bean/>
就可以实现,ldubbo这里加入rmi就是先理解RPC的服务发布和服务引用的过程,接下来就是ldubbo框架的核心部分了ldubbo协议的服务发布和引用过程。