1.SOFA RPC源码解析
1.1 Rest服务
1.1.1 服务发布
以SOFABoot自带的RPC案例sofaboot-sample-with-rpc为例,详细描述SOFABoot Rest服务发布原理。
在此提前说明,源码分析主要分析主流程,以及本人认为比较重要的一些内容,对于其它部分,大家可以基于本文档,自行研读。
RPC案例的SpringXML配置文件内容如下:
1. <?xml version="1.0"encoding="UTF-8"?>
2. <beansxmlns="http://www.springframework.org/schema/beans"
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xmlns:sofa="http://sofastack.io/schema/sofaboot"
5. xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
6. http://sofastack.io/schema/sofaboot http://sofastack.io/schema/sofaboot.xsd"
7. default-autowire="byName">
8.
9. <bean id="personServiceImpl"class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl"/>
10.
11. <sofa:serviceref="personServiceImpl" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
12. <sofa:binding.rest/>
13. </sofa:service>
14.
15. <sofa:referenceid="personReferenceRest"interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"jvm-first="false">
16. <sofa:binding.rest>
17. <sofa:global-attrstimeout="300000" address-wait-time="2000"/>
18. <sofa:routetarget-url="127.0.0.1:8341"/>
19. <sofa:methodname="sayName" timeout="300000"/>
20. </sofa:binding.rest>
21. </sofa:reference>
22.
23. <bean id="personFilter"class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceFilter"/>
24.
25. </beans>
在SpringXML配置文件中:
1. 通过XML元素bean定义PersonService接口实现类PersonServiceImpl;
2. 通过XML元素sofa:service以restful格式发布PersonService接口服务;
启动本应用,启动流程在《启动原理》中已经详细描述,在此不再详述,直接到从SpringXML配置文件加载Bean定义开始分析。
一、 注册ServiceFactoryBean类对应的Bean定义
调用AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh())方法刷新Spring应用上下文过程,通过invokeBeanFactoryPostProcessors(beanFactory)方法调用beanFactory中所有实现了BeanFactoryPostProcessor接口的类。当调用到ConfigurationClassPostProcessor类processConfigBeanDefinitions方法时,在循环处理@Configuration配置类过程中,调用ConfigurationClassBeanDefinitionReader类loadBeanDefinitions(configClasses)方法,处理解析完成的@Configuration配置类。此时,其中有一步是调用ConfigurationClassBeanDefinitionReader类loadBeanDefinitionsFromImportedResources(configClass.getImportedResources())方法,加载@ImportResource注解里面配置的SpringXML配置文件中定义的Bean。在这个方法中,使用XMLBeanDefinitionReader加载SpringXML配置文件中定义的Bean。
接下来就是SpringXML配置文件中各种标签的解析过程。
对于XML标签bean,由于是最基本的SpringXML标签,大家都应用比较熟悉了,在此不再详述。
对于XML标签sofa:service,其处理过程如下:
XMLBeanDefinitionReader类调用DefaultBeanDefinitionDocumentReader类registerBeanDefinitions方法注册Bean定义;
DefaultBeanDefinitionDocumentReader类调用BeanDefinitionParserDelegate类parseCustomElement方法解析自定义的XML元素。
在此,对于XML标签sofa:service,根据命名空间sofa对应的值http://sofastack.io/schema/sofaboot,在spring.handlers(此文件位于infra-sofa-boot-starter.jar)文件中,查找到XML标签的处理类SofaBootNamespaceHandler:
1. http\://sofastack.io/schema/sofaboot=com.alipay.sofa.infra.config.spring.namespace.handler.SofaBootNamespaceHandler
在SofaBootNamespaceHandler类中,调用findParserForElement方法,查找指定XML元素的解析类,此处service对应的BeanDefinitionParser解析类为com.alipay.sofa.runtime.spring.parser.ServiceDefinitionParser。
使用ServiceDefinitionParser类把SpringXML配置文件中sofa:service标签定义的服务转换为ServiceFactoryBean,并注册到Spring应用上下文中。
二、 创建ServiceFactoryBean类的实例
调用AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh())方法刷新Spring应用上下文过程,通过finishBeanFactoryInitialization(beanFactory)方法实例化beanFactory中所有剩余的非延迟初始化的单实例对象。
当实例化ServiceFactoryBean时,由于ServiceFactoryBean实现了Initializing接口,所以在调用ServiceFactoryBean类的初始化方法时,会调用ServiceFactoryBean类的afterPropertiesSet方法,进行实例的初始化操作。
到现在为止,开始Rest服务发布流程:
1. 根据XML标签sofa: service所包含的子标签sofa:binding.*(一个或多个,此处是sofa:binding.rest),解析出引用服务的类型,此处是restful风格的服务。
2. 创建DefaultImplementation实例implementation,并设置其属性ref为SpringXML文件中sofa-service元素的ref属性中指定的接口实现,此处为SpringXML文件中id为personServiceImpl的com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl类的实例。
3. 创建Service接口的实现ServiceImpl,其中,服务接口类型为com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService,接口模式为spring(表明此引用服务来自于SpringXML配置文件),target为ref(即id为personServiceImpl的PersonServiceImpl实例)。
4. 如果解析出的bindings为空,则把JvmBinding增加到bindings列表中。
5. 把解析出的binding(此处为RestBinding)增加到service实例的属性bindings列表中。
6. 创建ServiceComponent实例componentInfo。
7. 从sofaRuntimeContext实例中获取组件管理器实例componentManager,并调用其register方法注册刚才创建的服务组件componentInfo,具体注册操作在doRegister方法完成:
1. private ComponentInfodoRegister(ComponentInfo ci) {
2. ComponentName name = ci.getName();
3. if (isRegistered(name)) {
4. SofaLogger.error("Componentwas already registered: {0}", name);
5. return getComponentInfo(name);
6. }
7.
8. try {
9. ci.register();
10. } catch (Throwable e) {
11. SofaLogger.error(e, "Failed toregister component: {0}", ci.getName());
12. return null;
13. }
14.
15. SofaLogger.info("Registeringcomponent: {0}", ci.getName());
16.
17. try {
18. ComponentInfo old = registry.putIfAbsent(ci.getName(), ci);
19. if (old != null) {
20. SofaLogger.error("Componentwas already registered: {0}", name);
21. return old;
22. }
23. if (ci.resolve()) {
24. typeRegistry(ci);
25. ci.activate();
26. }
27. } catch (Throwable e) {
28. ci.exception(new Exception(e));
29. SofaLogger.error(e, "Failed tocreate the component {0}", ci.getName());
30. }
31.
32. return ci;
33. }
在组件管理器中注册组件的主要过程如下:
- 获取组件名称,此处为service:com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService。
- 判断组件管理器componentManager中是否存在指定名字的组件。如果有,则直接从组件管理器获取组件,并返回。如果没有,则继续注册流程。
- 调用组件ServiceComponent类的register方法,更新组件状态为registered。
- 把组件置入组件管理器的registry中(ConcurrentMap<ComponentName, ComponentInfo>)。
- 调用组件ServiceComponent类的resolve方法,更新组件状态为resolved。
- 把组件置入组件管理器的resolvedRegistry中(ConcurrentMap<ComponentType, Map<ComponentName,ComponentInfo>>)。
- 调用组件ServiceComponent的activate方法,激活组件,并更新组件状态为activated。
1. public void activate() throwsServiceRuntimeException {
2.
3. activateBinding();
4. super.activate();
5. }
调用activateBinding方法,激活服务组件的所有Binding。
1. private void activateBinding() {
2.
3. Object target = service.getTarget();
4. ……略
5. if (service.hasBinding()) {
6. boolean allPassed = true;
7. Set<Binding> bindings =service.getBindings();
8. for(Binding binding : bindings) {
9. BindingAdapter<Binding>bindingAdapter = this.bindingAdapterFactory
10. .getBindingAdapter(binding.getBindingType());
11.
12. if (bindingAdapter == null) {
13. throw newServiceRuntimeException(……略);
14. }
15.
16. Object outBindingResult;
17. ……略
18. try {
19. outBindingResult =bindingAdapter.outBinding(service, binding, target,
20. getContext());
21. } catch (Throwable t) {
22. allPassed = false;
23. ……略
24. continue;
25. }
26. if(!Boolean.FALSE.equals(outBindingResult)) {
27. ……略
28. } else {
29. binding.setHealthy(false);
30. SofaLogger.info("<<Out Binding [{0}] Fails, Don't publish service - {1}.",
31. binding.getBindingType(),service);
32. }
33. }
34.
35. if (!allPassed) {
36. throw newServiceRuntimeException(……略);
37. }
38. }
39.
40. SofaLogger.info("Register Service- {0}", service);
41. }
获取服务的目标类target;
依次遍历服务所有Binding,分别为每种Binding发布服务,具体过程如下:
根据Binding类型(此处为rest),获取BindingAdapter接口的实现,此处为:com.alipay.sofa.rpc.boot.runtime.adapter.RestBindingAdapter。
调用RestBindingAdapter类outBinding方法,对外发布服务:
1. public Object outBinding(Object contract,RpcBinding binding, Object target, SofaRuntimeContext sofaRuntimeContext) {
2.
3. String uniqueName = SpringBridge.getProviderConfigContainer().createUniqueName((Contract)contract, binding);
4. ProviderConfig providerConfig =SpringBridge.getProviderConfigContainer().getProviderConfig(uniqueName);
5.
6. if (providerConfig == null) {
7. throw new ServiceRuntimeException(……略);
8. }
9.
10. try {
11. providerConfig.export();
12. } catch (Exception e) {
13. throw new ServiceRuntimeException(……略);
14. }
15.
16. if(SpringBridge.getProviderConfigContainer().isAllowPublish()) {
17. Registry registry =SpringBridge.getRegistryConfigContainer().getRegistry();
18. providerConfig.setRegister(true);
19. registry.register(providerConfig);
20. }
21. return Boolean.TRUE;
22. }
通过工具类SpringBridge获取服务提供者配置容器类ProviderConfigContainer,然后调用ProviderConfigContainer类createUniqueName方法,根据接口类型和Binding类型,创建唯一ID,例如:com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService:1.0:rest。
根据ID从ProviderConfigContainer中获取服务提供者配置类ProviderConfig。
调用服务提供者配置类ProviderConfig的export方法,开始指定Binding类型的发布服务流程,此处为RestBinding类型的发布服务流程。
1. public synchronized void export() {
2. if (providerBootstrap == null) {
3. providerBootstrap =Bootstraps.from(this);
4. }
5. providerBootstrap.export();
6. }
通过工具类Bootstraps,使用ExtensionLoader扩展机制,获取名字为ProviderConfig实例中bootstrap属性(此处为rest)指定的ProviderBootstrap接口实现类,此处为com.alipay.sofa.rpc.bootstrap.rest.RestProviderBootstrap。
调用RestProviderBootstrap类export方法,开始真正的Rest服务发布流程:
1. public void export() {
2. if (providerConfig.getDelay() > 0) {// 延迟加载,单位毫秒
3. Thread thread = new Thread(newRunnable() {
4. @Override
5. public void run() {
6. try {
7. Thread.sleep(providerConfig.getDelay());
8. } catch (Throwable ignore){ // NOPMD
9. }
10. doExport();
11. }
12. });
13. thread.setDaemon(true);
14. thread.setName("DelayExportThread");
15. thread.start();
16. } else {
17. doExport();
18. }
19. }
如果服务提供者配置类中delay值大于0,表示延时加载,则启动新线程,在新线程运行过程中,休眠delay毫秒,然后调用doExport方法发布服务。否则,直接调用doExport方法发布服务。
1. private void doExport() {
2. if (exported) {
3. return;
4. }
5. String key = providerConfig.buildKey();
6. String appName =providerConfig.getAppName();
7. // 检查参数
8. checkParameters();
9. ……略
10. // 注意同一interface,同一uniqleId,不同server情况
11. AtomicInteger cnt = EXPORTED_KEYS.get(key);// 计数器
12. if (cnt == null) { // 没有发布过
13. cnt =CommonUtils.putToConcurrentMap(EXPORTED_KEYS, key, new AtomicInteger(0));
14. }
15. int c = cnt.incrementAndGet();
16. int maxProxyCount =providerConfig.getRepeatedExportLimit();
17. if (maxProxyCount > 0) {
18. if (c > maxProxyCount) {
19. cnt.decrementAndGet();
20. // 超过最大数量,直接抛出异常
21. throw newSofaRpcRuntimeException(……略);
22. } else if (c > 1) {
23. ……略
24. }
25. }
26.
27. try {
28. // 构造请求调用器
29. providerProxyInvoker = newProviderProxyInvoker(providerConfig);
30. // 初始化注册中心
31. if (providerConfig.isRegister()) {
32. List<RegistryConfig>registryConfigs = providerConfig.getRegistry();
33. if(CommonUtils.isNotEmpty(registryConfigs)) {
34. for (RegistryConfigregistryConfig : registryConfigs) {
35. RegistryFactory.getRegistry(registryConfig);// 提前初始化Registry
36. }
37. }
38. }
39. // 将处理器注册到server
40. List<ServerConfig>serverConfigs = providerConfig.getServer();
41. for (ServerConfig serverConfig :serverConfigs) {
42. try {
43. Server server =serverConfig.buildIfAbsent();
44. // 注册序列化接口
45. server.registerProcessor(providerConfig,providerProxyInvoker);
46. if(serverConfig.isAutoStart()) {
47. server.start();
48. }
49. } catch(SofaRpcRuntimeException e) {
50. throw e;
51. } catch (Exception e) {
52. LOGGER.errorWithApp(appName,"Catch exception when register processor to server: "
53. + serverConfig.getId(),e);
54. }
55. }
56.
57. // 注册到注册中心
58. providerConfig.setConfigListener(newProviderAttributeListener());
59. register();
60. } catch (Exception e) {
61. cnt.decrementAndGet();
62. if (e instanceofSofaRpcRuntimeException) {
63. throw (SofaRpcRuntimeException)e;
64. } else {
65. throw newSofaRpcRuntimeException("Build provider proxy error!", e);
66. }
67. }
68. // 记录一些缓存数据
69. RpcRuntimeContext.cacheProviderConfig(this);
70. exported = true;
71. }
在doExport方法中,其主要处理逻辑如下:
1) 构建服务提供者配置类key:格式:interfaceId + ":" +uniqueId;例如: com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService:
2) 检查参数,这是一个扩展点,通过在子类重写此方法,实现参数和属性的个性化处理。此处检查注入的ref是否是接口实现类、server不能为空。
3) 判断该服务的发布数量(即创建代理的数量)是否超出最大重复发布数量repeatedExportLimit的设置,如果超出最大数量,则直接抛出异常。否则,继续。
4) 创建请求调用器ProviderProxyInvoker实例,同时创建请求调用器中的过滤器链,最底层是调用过滤器ProviderInvoker;
1. public ProviderProxyInvoker(ProviderConfigproviderConfig) {
2. this.providerConfig = providerConfig;
3. this.filterChain =FilterChain.buildProviderChain(providerConfig,
4. newProviderInvoker(providerConfig));
5. }
5) 初始化注册中心,待细化;
6) 遍历服务提供者配置的所有Server,依次创建Server实例server,将处理器注册到server,如果配置server自动启动,则启动server。此处只配置了一个server,即ServerConfig [protocol=rest, port=8341, host=0.0.0.0];
针对每个Server,其主要处理过程如下:
调用ServerConfig的buildIfAbsent方法,创建指定类型的Server:
1. public synchronized Server buildIfAbsent(){
2. if (server != null) {
3. return server;
4. }
5. server= ServerFactory.getServer(this);
6. return server;
7. }
通过ServerFactory类getServer方法初始化Server实例:
1. public synchronized static ServergetServer(ServerConfig serverConfig) {
2. try {
3. Server server =SERVER_MAP.get(Integer.toString(serverConfig.getPort()));
4. if (server == null) {
5. // 算下网卡和端口
6. resolveServerConfig(serverConfig);
7.
8. ExtensionClass<Server>ext = ExtensionLoaderFactory.getExtensionLoader(Server.class)
9. .getExtensionClass(serverConfig.getProtocol());
10. if (ext == null) {
11. throwExceptionUtils.buildRuntime("server.protocol",serverConfig.getProtocol(),
12. "Unsupported protocol ofserver!");
13. }
14. server = ext.getExtInstance();
15. server.init(serverConfig);
16. SERVER_MAP.put(serverConfig.getPort()+ "", server);
17. }
18. return server;
19. } catch (SofaRpcRuntimeException e) {
20. throw e;
21. } catch (Throwable e) {
22. throw newSofaRpcRuntimeException(e.getMessage(), e);
23. }
24. }
在getServer方法中,创建Server的主要处理过程如下:
1) 从SERVER_MAP缓存中按照端口号获取指定端口的Server实现。如果有,则直接返回。否则,创建新的Server实例。
2) 绑定Server的IP和端口;
3) 利用RPC扩展机制获取Server接口扩展实现。此处别名为rest,所以获取到com.alipay.sofa.rpc.server.rest.RestServer类的实例;
4) 调用RestServer的init方法,初始化server;
5) 把server缓存到SERVER_MAP,key为端口号,value为server实例;
在创建完Server实例以后,将处理器注册到server:
1. public void registerProcessor(ProviderConfigproviderConfig, Invoker instance) {
2. if (!isStarted()) {
3. start();
4. }
5. // 在httpserver中注册此jaxrs服务
6. if (LOGGER.isInfoEnabled()) {
7. LOGGER.info("Register jaxrsservice to base url http://" + serverConfig.getHost() + ":"
8. + serverConfig.getPort() +serverConfig.getContextPath());
9. }
10. try {
11. httpServer.getDeployment().getRegistry()
12. .addResourceFactory(newSofaResourceFactory(providerConfig), serverConfig.getContextPath());
13.
14. invokerCnt.incrementAndGet();
15. } catch (Exception e) {
16. LOGGER.error("Register jaxrsservice error", e);
17. }
18. }
在registerProcessor方法中,首先判断server是否启动,如果没有启动,则启动server:
1. public void start() {
2. if (started) {
3. return;
4. }
5. synchronized (this) {
6. if (started) {
7. return;
8. }
9. // 绑定到端口
10. try {
11. httpServer = buildServer();
12. httpServer.start();
13. if (LOGGER.isInfoEnabled()) {
14. LOGGER.info("Start thehttp rest server at port {}", serverConfig.getPort());
15. }
16. } catch (Exception e) {
17. throw new SofaRpcRuntimeException(
18. "Failed to start jettyserver at port " + serverConfig.getPort() + ", cause: " +e.getMessage(), e);
19. }
20. started = true;
21. }
22. }
Server启动的主要过程如下:
1) 判断server是否启动
如果没有启动,则同步代码块中启动server。在同步代码块开始部分,再次判断server是否启动,主要是避免在获取该Server的对象锁过程中,其它线程启动该Server。如果没有启动,则继续下面处理逻辑,启动Server。
2) 创建Server实例
此处是rest类型绑定,所以采用com.alipay.sofa.rpc.server.rest.SofaNettyJaxrsServer作为服务器。SofaNettyJaxrsServer参考NettyJaxrsServer实现(来自org.jboss.resteasy.plugins.server.netty.NettyJaxrsServer),增加了自定义功能,区别搜索CHANGE。在此不再详述。从名字可以看出,SofaNettyJaxrsServer采用Netty4作为网络通讯层,底层采用NIO,保证了网络通讯性能。
设置Server实例,包括Worker线程数(此处boss和work采用同一个NioEventLoopGroup,所以是boss与worker线程总数)、业务线程池最大线程数,最大请求报文大小、主机名称、端口号、keepAlive、是否为Daemon线程、是否开启Telnet测试等。
获取Server实例的属性deployment(接口类型为ResteasyDeployment),此处为com.alipay.sofa.rpc.server.rest.SofaResteasyDeployment。
调用SofaResteasyDeployment类start方法,主要完成以下工作:
- 为每个deployment设置其独有的ResteasyProviderFactory,以保证每个war包使用自己的provider集合。
- 根据asyncJobServiceEnabled标志,选择创建异步请求分发器AsynchronousDispatcher或同步请求分发器SofaSynchronousDispatcher,此处创建同步请求分发器。
- 设置同步请求分发器,主要包括上下文对象、providerFactory等;
- 注册所有的provider;
在providerFactory中注册provider,包含内置的和自定义的。
最后返回创建好的Server实例。
3) 启动Server
调用Server实例的start方法,启动Server。
1. public void start() {
2. // CHANGE: 增加线程名字
3. eventLoopGroup = newNioEventLoopGroup(ioWorkerCount, newNamedThreadFactory("SOFA-REST-IO-" + port, daemon));
4. eventExecutor = newNioEventLoopGroup(executorThreadCount, newNamedThreadFactory("SOFA-REST-BIZ-" + port,
5. daemon));
6. // Configure the server.
7. bootstrap.group(eventLoopGroup)
8. .channel(NioServerSocketChannel.class)
9. .childHandler(createChannelInitializer())
10. .option(ChannelOption.SO_BACKLOG,backlog)
11. .childOption(ChannelOption.SO_KEEPALIVE,keepAlive); // CHANGE:
12.
13. for (Map.Entry<ChannelOption,Object> entry : channelOptions.entrySet()) {
14. bootstrap.option(entry.getKey(),entry.getValue());
15. }
16.
17. for (Map.Entry<ChannelOption, Object>entry : childChannelOptions.entrySet()) {
18. bootstrap.childOption(entry.getKey(),entry.getValue());
19. }
20.
21. final InetSocketAddress socketAddress;
22. if (null == hostname ||hostname.isEmpty()) {
23. socketAddress = newInetSocketAddress(port);
24. } else {
25. socketAddress = newInetSocketAddress(hostname, port);
26. }
27.
28. bootstrap.bind(socketAddress).syncUninterruptibly();
29. }
看到这里,熟悉Netty4的同学们都明白了吧。此处就是设置ServerBootstrap实例bootstrap的相关属性,然后调用bind方法开始监控指定的端口。
Server启动以后,在httpserver中注册此jaxrs服务。
1. httpServer.getDeployment().getRegistry()
2. .addResourceFactory(new SofaResourceFactory(providerConfig), serverConfig.getContextPath())
判断autoStart是否为true。True表示自动启动Server。否则,不启动Server。此处为false,不自动启动Server。由于在把处理器注册到server时,已经启动了Server,所以此时Server已经启动。
至此,Server启动成功,服务请求路径设置完成,准备接受客户端发起的restful类型的服务请求。
再返回看看Rest服务发布的doExport方法:
1. private void doExport() {
2. ……略
3.
4. try {
5. // 构造请求调用器
6. ……略
7. // 初始化注册中心
8. ……略
9. // 将处理器注册到server
10. List<ServerConfig>serverConfigs = providerConfig.getServer();
11. for (ServerConfig serverConfig :serverConfigs) {
12. try {
13. Server server =serverConfig.buildIfAbsent();
14. // 注册序列化接口
15. server.registerProcessor(providerConfig,providerProxyInvoker);
16. if(serverConfig.isAutoStart()) {
17. server.start();
18. }
19. } catch (SofaRpcRuntimeException e) {
20. throw e;
21. } catch (Exception e) {
22. ……略
23. }
24. }
25.
26. // 注册到注册中心
27. providerConfig.setConfigListener(newProviderAttributeListener());
28. register();
29. } catch (Exception e) {
30. cnt.decrementAndGet();
31. if (e instanceofSofaRpcRuntimeException) {
32. throw (SofaRpcRuntimeException)e;
33. } else {
34. throw new SofaRpcRuntimeException("Buildprovider proxy error!", e);
35. }
36. }
37. // 记录一些缓存数据
38. RpcRuntimeContext.cacheProviderConfig(this);
39. exported = true;
40. }
当服务配置的所有Server启动成功以后,设置服务提供者配置属性监听器ProviderAttributeListener,监听属性值变化。
最后,在所有配置的注册中心中注册服务提供者。
到现在为止,按照某种服务类型(如:restful风格服务)成功发布了指定的服务。