一, 服务暴露
motan框架通过实现ApplicationListener<ContextRefreshedEvent>接口的onApplicationEvent()方法, 执行服务暴露操作.
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 首先检查, 是否已经exported服务
if (!getExported().get()) {
export();
}
}
1.1 export()方法进行服务暴露
服务暴露过程源码如下, 下面针对该源码, 进行解析:
public synchronized void export() {
// 再次判断, 该服务是否已经exported服务
if (exported.get()) {
LoggerUtil.warn(String.format("%s has already been expoted, so ignore the export request!", interfaceClass.getName()));
return;
}
checkInterfaceAndMethods(interfaceClass, methods);
// 很核心的一步, 将xml配置文件registry标签配置的内容, 转换成List<URL>对象
// 但是URL的parameters属性, 去掉了下面这四项参数:
// defaultParameters.remove("protocol");
// defaultParameters.remove("host");
// defaultParameters.remove("port");
// defaultParameters.remove("path");
List<URL> registryUrls = loadRegistryUrls();
if (registryUrls == null || registryUrls.size() == 0) {
throw new IllegalStateException("Should set registry config for service:" + interfaceClass.getName());
}
Map<String, Integer> protocolPorts = getProtocolAndPort();
// 从这里可以看出,
for (ProtocolConfig protocolConfig : protocols) {
Integer port = protocolPorts.get(protocolConfig.getId());
if (port == null) {
throw new MotanServiceException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
protocolConfig.getId()));
}
// 参数protocolConfig : <motan:protocol name="motan" default="true" requestTimeout="220" minWorkerThread="20" maxWorkerThread="800" maxContentLength="1048576" maxServerConnection="80000" isDefault="true" id="demoMotan" />
// 参数port : 8001
// 参数registryUrls : [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc], 已经去掉了上面的四项的结果
doExport(protocolConfig, port, registryUrls);
}
afterExport();
}
1.1.1 首先再次判断, 服务是否已经启动, 正在对外暴露服务.
1.1.2 checkInterfaceAndMethods(interfaceClass, methods);
类似于dubbo的服务暴露时的对接口中的方法进行配置:
<dubbo:service>
<dubbo:method></dubbo:method>
</dubbo:service>
同样的motan也提供了类似于这样基于method级别的配置方式:
<motan:service>
<motan:method></motan:method>
</motan:service>
基于这样的配置, checkInterfaceAndMethods(interfaceClass, methods);这行代码的意思就是, check校验该method方法是否在待进行服务暴露接口的接口定义中.
1.1.3 List<URL> registryUrls = loadRegistryUrls(); 很关键的一行代码, 生成了URL核心对象
protected List<URL> loadRegistryUrls() {
List<URL> registryList = new ArrayList<URL>();
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isBlank(address)) {
address = NetUtils.LOCALHOST + ":" + MotanConstants.DEFAULT_INT_VALUE;
}
Map<String, String> map = new HashMap<String, String>();
// 将config对象转换成map类型
config.appendConfigParams(map);
map.put(URLParamType.application.getName(), getApplication());
map.put(URLParamType.path.getName(), RegistryService.class.getName());
map.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));
// 设置默认的registry protocol,parse完protocol后,需要去掉该参数
if (!map.containsKey(URLParamType.protocol.getName())) {
if (address.contains("://")) {
map.put(URLParamType.protocol.getName(), address.substring(0, address.indexOf("://")));
}
map.put(URLParamType.protocol.getName(), MotanConstants.REGISTRY_PROTOCOL_LOCAL);
}
// address内部可能包含多个注册中心地址
List<URL> urls = UrlUtils.parseURLs(address, map);
if (urls != null && !urls.isEmpty()) {
for (URL url : urls) {
url.removeParameter(URLParamType.protocol.getName());
registryList.add(url);
}
}
}
}
return registryList;
}
这行代码时特别关键的一步, , 将xml配置文件<motan:registry>标签配置的内容, 通过其封装类RegistryConfig, 转换成List<URL>对象, 当前URL核心对象中现在封装了<motan:registry>标签配置的ip:port, 以及一些其他的参数, 比如application, path, refreshTimestamp等.
最终执行结果为:[zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc], 很显然就是得到了xml文件中<motan:registry>标签配置的zk地址及端口号, 以及默认生成一个group.
1.1.4 Map<String, Integer> protocolPorts = getProtocolAndPort(); // 从export中获取协议名称及服务暴露的端口号
<motan:basicService export="demoMotan:8002"> //
<motan:service export="demoMotan:8001">
上面两端配置文件都配置export, 若同时存在的话, 将以motan:service中的配置为主, 即覆盖<motan:basicService>中export的配置内容, 存在相同的配置项时, 优先级是<motan:service> 大于 <motan:basicService>.
所以这行代码的执行结果就是: {demoMotan=8001}或者{demoMotan=8002}
1.1.5 从ProtocolConfig封装类中获取xml配置文件中, 关于Protocol的其他配置配置内容:
<motan:protocol id="demoMotan" default="true" name="motan"
requestTimeout="220" maxServerConnection="80000" maxContentLength="1048576"
maxWorkerThread="800" minWorkerThread="20"/>
for (ProtocolConfig protocolConfig : protocols) {
Integer port = protocolPorts.get(protocolConfig.getId());
if (port == null) {
throw new MotanServiceException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
protocolConfig.getId()));
}
// 参数protocolConfig : <motan:protocol name="motan" default="true" requestTimeout="220" minWorkerThread="20" maxWorkerThread="800" maxContentLength="1048576" maxServerConnection="80000" isDefault="true" id="demoMotan" />
// 参数port : 8001
// 参数registryUrls : [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc], 已经去掉了上面的四项的结果
doExport(protocolConfig, port, registryUrls);
}
1.2 传入经过进一步处理后的protocolConfig对象, 待暴露的port端口号, 注册中心registryUrl封装对象, 传入给doExport()方法进行服务暴露.
private void doExport(ProtocolConfig protocolConfig, int port, List<URL> registryURLs) {
String protocolName = protocolConfig.getName();
if (protocolName == null || protocolName.length() == 0) {
protocolName = URLParamType.protocol.getValue();
}
String hostAddress = host;
if (StringUtils.isBlank(hostAddress) && basicServiceConfig != null) {
hostAddress = basicServiceConfig.getHost();
}
if (NetUtils.isInvalidLocalHost(hostAddress)) {
hostAddress = getLocalHostAddress(registryURLs);
}
Map<String, String> map = new HashMap<String, String>();
map.put(URLParamType.nodeType.getName(), MotanConstants.NODE_TYPE_SERVICE);
map.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));
// 按顺序进行config 参数append and override,按照configs出现的顺序,后面的会覆盖前面的相同名称的参数, 即优先级:serviceConfig > extConfig > basicServiceConfig > protocolConfig
// collectConfigParams()方法之前的map:
// {
// nodeType=service,
// refreshTimestamp=1543822818150
// }
collectConfigParams(map, protocolConfig, basicServiceConfig, extConfig, this);
// collectConfigParams()方法之后的map :
// {
// maxContentLength=1048576,
// module=motan-demo-rpc,
// nodeType=service,
// accessLog=false,
// minWorkerThread=20,
// protocol=motan,
// isDefault=true,
// application=myMotanDemo,
// maxWorkerThread=800,
// shareChannel=true,
// refreshTimestamp=1543822818150,
// id=com.weibo.api.motan.config.springsupport.ServiceConfigBean, // 这里着重说下这个属性的来历, spring在解析标签时, 假如某个标签没有id或者是name, 那么就使用该标签的属性包装类的全限定名作为id
// export=demoMotan:8001,
// requestTimeout=200,
// maxServerConnection=80000,
// group=motan-demo-rpc
// }
collectMethodConfigParams(map, this.getMethods());
URL serviceUrl = new URL(protocolName, hostAddress, port, interfaceClass.getName(), map);
if (serviceExists(serviceUrl)) {
LoggerUtil.warn(String.format("%s configService is malformed, for same service (%s) already exists ", interfaceClass.getName(),
serviceUrl.getIdentity()));
throw new MotanFrameworkException(String.format("%s configService is malformed, for same service (%s) already exists ",
interfaceClass.getName(), serviceUrl.getIdentity()), MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
}
List<URL> urls = new ArrayList<URL>();
// injvm 协议只支持注册到本地,其他协议可以注册到local、remote
if (MotanConstants.PROTOCOL_INJVM.equals(protocolConfig.getId())) {
URL localRegistryUrl = null;
for (URL ru : registryURLs) {
if (MotanConstants.REGISTRY_PROTOCOL_LOCAL.equals(ru.getProtocol())) {
localRegistryUrl = ru.createCopy();
break;
}
}
if (localRegistryUrl == null) {
localRegistryUrl =
new URL(MotanConstants.REGISTRY_PROTOCOL_LOCAL, hostAddress, MotanConstants.DEFAULT_INT_VALUE,
RegistryService.class.getName());
}
urls.add(localRegistryUrl);
} else {
for (URL ru : registryURLs) {
urls.add(ru.createCopy());
}
}
for (URL u : urls) {
u.addParameter(URLParamType.embed.getName(), StringTools.urlEncode(serviceUrl.toFullStr()));
registereUrls.add(u.createCopy());
}
// MotanConstants.DEFAULT_VALUE 对应 SimpleConfigHandler
ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);
// 步骤一. 该configHandler.export(interfaceClass, ref, urls)将返回一个export对象
// 步骤二. exporters.add()则是缓存步骤一的export对象
// urls = [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc]
// xxz
Exporter<T> export2 = configHandler.export(interfaceClass, ref, urls);
exporters.add(export2);
//exporters.add(configHandler.export(interfaceClass, ref, urls));
initLocalAppInfo(serviceUrl);
}
① collectConfigParams(map, protocolConfig, basicServiceConfig, extConfig, this);
② collectMethodConfigParams(map, this.getMethods());
③ 判断协议类型
④ jdk的spi机制获取执行SimpleConfigHandler对象进行服务暴露.
1.2.1 collectConfigParams(map, protocolConfig, basicServiceConfig, extConfig, this);
从protocolConfig, basicServiceConfig, extConfig包装对象以及this = serviceConfig对象中, 获取参数, 写入到map.
1.2.2 collectMethodConfigParams(map, this.getMethods());
再从<motan:method>对应的List<MethodConfig>封装对象list中, 获取接口method级别配置参数, 写入map
1.2.3 判断协议类型
if (MotanConstants.PROTOCOL_INJVM.equals(protocolConfig.getId())) {
URL localRegistryUrl = null;
for (URL ru : registryURLs) {
if (MotanConstants.REGISTRY_PROTOCOL_LOCAL.equals(ru.getProtocol())) {
localRegistryUrl = ru.createCopy();
break;
}
}
if (localRegistryUrl == null) {
localRegistryUrl =
new URL(MotanConstants.REGISTRY_PROTOCOL_LOCAL, hostAddress, MotanConstants.DEFAULT_INT_VALUE,
RegistryService.class.getName());
}
urls.add(localRegistryUrl);
} else {
for (URL ru : registryURLs) {
urls.add(ru.createCopy());
}
}
上面的代码很简单, 根据不通的条件实现不同的逻辑, 仔细点阅读基本上没有问题.
1.2.4 jdk的spi机制获取执行SimpleConfigHandler对象进行服务暴露
首先不懂什么是spi机制的, 可以查看本人以前针对jdk的spi机制写过的一篇博客:JDK Spi机制
motan框架相当于扩展了JDk的spi机制, 在motan中如果想要使用Spi机制, 暴露出来的接口需要使用@Spi注解进行标记, 该注解还
可以指定其接口的实现类在创建对象时, 是单例还是多例.
接着, 在其实现类上需要使用@SpiMeta注解, 用来标记该实现类的Spi名称, 根据该名称从META-INF/services/实现类列表文件,
获取一个具体的该类的具体实现类, 完成对应的业务逻辑处理.
以这里的SimpleConfigHandler为例: 这里的MotanConstants.DEFAULT_VALUE = default
ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);
① ConfigHandler接口
@Spi(scope = Scope.SINGLETON)
public interface ConfigHandler {
@Spi注解标记的, 其实现类是单实例创建
② SimpleConfigHandler实现类的创建
@SpiMeta(name = MotanConstants.DEFAULT_VALUE)
public class SimpleConfigHandler implements ConfigHandler {
@SpiMeta注解标记的, 该实现类的Spi名称为MotanConstants.DEFAULT_VALUE = default
所以根据项目的Spi名称同时为default的话, 那么motan利用spi机制就能创建出一个针对该接口的具体的实现类.
③ SimpleConfigHandler调用export()方法进行服务暴露.
configHandler.export(interfaceClass, ref, urls);
进入该方法的具体实现逻辑:
public <T> Exporter<T> export(Class<T> interfaceClass, T ref, List<URL> registryUrls) {
// serviceStr = motan://192.168.99.1:8001/com.weibo.motan.demo.service.MotanDemoService?maxContentLength=1048576&
// module=motan-demo-rpc&nodeType=service&accessLog=false&minWorkerThread=20&protocol=motan&isDefault=true&
// application=myMotanDemo&maxWorkerThread=800&shareChannel=true&refreshTimestamp=1544514033379&
// id=com.weibo.api.motan.config.springsupport.ServiceConfigBean&export=demoMotan:8001&requestTimeout=200&
// maxServerConnection=80000&group=motan-demo-rpc&
String serviceStr = StringTools.urlDecode(registryUrls.get(0).getParameter(URLParamType.embed.getName()));
// 解析serviceStr字符串, 转换成URL对象
URL serviceUrl = URL.valueOf(serviceStr);
// export service
// protocolName = motan
String protocolName = serviceUrl.getParameter(URLParamType.protocol.getName(), URLParamType.protocol.getValue());
// 利用protocol decorator来增加filter特性
Protocol protocol = new ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName));
// 很关键, 初始化一个DefaultProvider对象, server端用于执行反射调用, 执行业务逻辑代码, 并且将结果回写到客户端
Provider<T> provider = new DefaultProvider<T>(ref, serviceUrl, interfaceClass);
Exporter<T> exporter = protocol.export(provider, serviceUrl);
// register service
register(registryUrls, serviceUrl);
return exporter;
}
Protocol protocol = new ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName));
这里的ProtocolFilterDecorator类可以理解成对DefaultRpcProtocol类的一种处理功能的能力增强, 比如, 根据一些其他额外的参数配置, 判断是否需要为该Protocol添加上这一功能, 例如, 根据xml配置文件中的accessLog参数进行判断, 是够需要增加accesslog.
所以最终服务暴露会调用DefaultRpcProtocol类的export()方法进行服务暴露.
④ DefaultRpcProtocol类调用export()方法进行服务暴露
public <T> Exporter<T> export(Provider<T> provider, URL url) {
if (url == null) {
throw new MotanFrameworkException(this.getClass().getSimpleName() + " export Error: url is null",
MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
}
if (provider == null) {
throw new MotanFrameworkException(this.getClass().getSimpleName() + " export Error: provider is null, url=" + url,
MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
}
String protocolKey = MotanFrameworkUtil.getProtocolKey(url);
synchronized (exporterMap) {
Exporter<T> exporter = (Exporter<T>) exporterMap.get(protocolKey);
if (exporter != null) {
throw new MotanFrameworkException(this.getClass().getSimpleName() + " export Error: service already exist, url=" + url,
MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
}
exporter = createExporter(provider, url);
exporter.init();
exporterMap.put(protocolKey, exporter);
LoggerUtil.info(this.getClass().getSimpleName() + " export Success: url=" + url);
return exporter;
}
步骤一, 首先exporter = createExporter(provider, url); 创建一个exporter用于暴露服务的实例对象:
protected <T> Exporter<T> createExporter(Provider<T> provider, URL url) {
// new创建一个DefaultRpcExporter对象时, 即在该类的构造方法中就启动netty server完成了服务的暴露
return new DefaultRpcExporter<T>(provider, url);
}
该createExporter()方法会初始化一个DefaultRpcExporter对象, 在初始化的过程中, 执行DefaultRpcExporter类的构造方法最终会生成一个NettyServer对象, 如下:
DefaultRpcExporter类的构造方法.
public DefaultRpcExporter(Provider<T> provider, URL url) {
super(provider, url);
ProviderMessageRouter requestRouter = initRequestRouter(url);
String param1 = URLParamType.endpointFactory.getName();
String param2 = URLParamType.endpointFactory.getValue();
String result = url.getParameter(param1, param2);
// 利用spi机制实例化一个NettyEndpointFactory对象
endpointFactory =ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(result);
//endpointFactory =
// ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(
// url.getParameter(URLParamType.endpointFactory.getName(), URLParamType.endpointFactory.getValue()));
// 使用NettyEndpointFactory对象创建一个netty server
server = endpointFactory.createServer(url, requestRouter);
}
endpointFactory =ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(result);
再次利用Spi机制生成一个NettyEndpointFactory对象, 这里的result = motan, 根据Spi名称为"motan", 框架很容易就能生成这个NettyEndpointFactory实例对象.
接着NettyEndpointFactory类调用createServer()方法, 生成一个NettyServer对象,
endpointFactory.createServer(url, requestRouter); // 返回一个NettyServer对象, 后面用于开启一个netty server服务, 对外提供服务, 接口client端的请求.
步骤二: DefaultRpcExporter实例对象调用init()方法, open开启netty server服务.
步骤一中执行exporter = createExporter(provider, url);方法, 返回了一个DefaultRpcExporter实例对象, 调用init()方法, 开启netty 服务, exporter.init();
步骤三, 查看init方法的实现
public synchronized void init() {
if (init) {
LoggerUtil.warn(this.getClass().getSimpleName() + " node already init: " + desc());
return;
}
boolean result = doInit();
if (!result) {
LoggerUtil.error(this.getClass().getSimpleName() + " node init Error: " + desc());
throw new MotanFrameworkException(this.getClass().getSimpleName() + " node init Error: " + desc(),
MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
} else {
LoggerUtil.info(this.getClass().getSimpleName() + " node init Success: " + desc());
init = true;
available = true;
}
}
可以看到该方法又调用了doInit()方法, 开启netty server服务, 熟悉设计模式的朋友, 阅读源码时, 看到这里其实一眼就能看出来, 这里采用了模板方法模式(简单说一句), 接着我们就来查看, doInit()方式是怎么开启一个Netty Server服务的.
步骤四, doInit()开启一个Netty Server服务
protected boolean doInit() {
// 开启一个netty server 服务, 监听客户端请求
boolean result = server.open();
return result;
}
可以看到, 代码很简单, 就是调用了NettyServer类的open()方法, 开启netty服务:
public synchronized boolean open() {
if (isAvailable()) {
LoggerUtil.warn("NettyServer ServerChannel already Open: url=" + url);
return true;
}
LoggerUtil.info("NettyServer ServerChannel start Open: url=" + url);
initServerBootstrap();
serverChannel = bootstrap.bind(new InetSocketAddress(url.getPort()));
state = ChannelState.ALIVE;
StatsUtil.registryStatisticCallback(this);
LoggerUtil.info("NettyServer ServerChannel finish Open: url=" + url);
return state.isAliveState();
}
initServerBootstrap(); // 初始化netty server服务配置
serverChannel = bootstrap.bind(new InetSocketAddress(url.getPort()));//从URL对象中取出配置文件中配置的服务暴露端口号, bind()绑定该端口号, netty服务端将监听该端口号, 接收客户端请求.
至此, motan框架就通过启动netty server绑定xml配置的服务暴露端口号, 进行服务暴露, 接收client端客户请求, 处理客户端请求.
二, 总结:
motan框架在进行服务暴露时, 主要是利用Jdk的spi机制加载合适的处理类, 完成对应的功能. 接着就是运用到了一些设计模式, 所以大家再看这一部分代码时, 会觉得绕来绕去的, 其实motan服务暴露过程的代码编写, 是一种特别好的松耦合设计, 以插件的形式根据不同的条件, 加载不同的配置类, 完成自己的某一项功能, 这是一种特别好的编程设计思想, 值得我们每一位编程人员学习借鉴.