(引用自Duboo开发指南-框架设计文档)
如图描述Dubbo实现的RPC整体分10层:service、config、proxy、registry、cluster、monitor、protocol、exchange、transport、serialize。
service:使用方定义的接口和实现类; config:负责解析Dubbo定义的配置,比如注解和xml配置,各种参数; proxy:主要负责生成消费者和提供者的代理对象,加载框架功能,比如提供者过滤器链,扩展点; registry:负责注册服务的定义和实现类的装载; cluster:只有消费者有这么一层,负责包装多个服务提供者成一个‘大提供者’,加载负载均衡、路有等扩展点; monitor:定义监控服务,加载监控实现提供者; protocol:封装RPC调用接口,管理调用实体的生命周期; exchange:封装请求响应模式,同步转异步; transport:抽象传输层模型,兼容netty、mina、grizzly等通讯框架; serialize:抽象序列化模型,兼容多种序列化框架,包括:fastjson、fst、hessian2、kryo、kryo2、protobuf等,通过序列化支持跨语言的方式,支持跨语言的rpc调用;Dubbo这么分层的目的在于实现层与层之间的解耦,每一层都定义了接口规范,也可以根据不同的业务需求定制、加载不同的实现,具有极高的扩展性。
从Dubbo核心领域对象的角度看,我们引用Dubbo官方文档说明,如下图所示。Dubbo核心领域对象是Invoker,消费端代理对象是proxy,包装了Invoker的调用;服务端代理对象是一个Invoker,他通过exporter包装,当服务端接收到调用请求后,通过exporter找到Invoker,Invoker去实际执行用户的业务逻辑。
(引用自Dubbo官方文档)
下图出自开发指南-框架设计-暴露服务时序,主要流程是:创建本地服务的代理Invoker,启动tcp服务暴露服务,然后将服务注册到注册中心。
接下来我们结合Dubbo服务的注册和发现,从配置层开始解释每一层的作用和原理。
示例服务接口定义如下:
public interface CouponServiceViewFacade {
/**
* 查询单张优惠券
*/
CouponViewDTO query(String code);
}
public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements
ApplicationContextAware {
public ReferenceAnnotationBeanPostProcessor() {
super(DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class);
}
}
Dubbo服务发现到这一层,Dubbo即将开始构建服务消费者的代理对象,CouponServiceViewFacade接口的代理实现类。
public class ServiceClassPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
private final static List<Class<? extends Annotation>> serviceAnnotationTypes = asList(
DubboService.class,Service.class,com.alibaba.dubbo.config.annotation.Service.class
);
。。。
}
等待Spring容器ContextRefreshedEvent事件,启动Dubbo应用服务监听端口,暴露本地服务。
Dubbo服务注册到这一层,Dubbo即将开始构建服务提供者的代理对象,CouponServiceViewFacade实现类的反射代理类。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.dubbo.common.bytecode;
public class proxy1 implements DC, Destroyable, CouponServiceViewFacade, EchoService {
public static Method[] methods;
private InvocationHandler handler;
public proxy1(InvocationHandler var1) {
this.handler = var1;
}
public proxy1() {
}
public CouponViewDTO query(String var1) {
Object[] var2 = new Object[]{var1};
Object var3 = this.handler.invoke(this, methods[0], var2);
return (CouponViewDTO)var3;
}
}
CouponServiceViewFacade的反射代理实例,服务端接收到请求后,通过该实例的Invoke方法最终执行本地方法query。
/**
* InvokerWrapper
*/
public class AbstractProxyInvoker<CouponServiceViewFacade> implements Invoker<CouponServiceViewFacade> {
// 。。。
public AbstractProxyInvoker(CouponServiceViewFacade proxy, Class<CouponServiceViewFacade> type, URL url) {
//。。。
this.proxy = proxy;
this.type = type;
this.url = url;
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
//。。。
Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
//。。。
}
protected Object doInvoke(CouponServiceViewFacade proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable{
//。。。
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
}
@SPI("javassist")
public interface ProxyFactory {
// 生成服务消费者代理对象
@Adaptive({PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
// 生成服务消费者代理对象
@Adaptive({PROXY_KEY})
<T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
// 生成服务提供者代理对象
@Adaptive({PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
public class JavassistProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
。。。
}
public class Proxy1 extends Proxy implements DC {
public Proxy1() {
}
public Object newInstance(InvocationHandler var1) {
return new proxy1(var1);
}
}
最终生成的CouponServiceViewFacade的代理对象如下,其中handler的实现类是InvokerInvocationHandler,this.handler.invoke方法发起Dubbo调用。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.dubbo.common.bytecode;
public class proxy1 implements DC, Destroyable, CouponServiceViewFacade, EchoService {
public static Method[] methods;
private InvocationHandler handler;
public proxy1(InvocationHandler var1) {
this.handler = var1;
}
public proxy1() {
}
public CouponViewDTO query(String var1) {
Object[] var2 = new Object[]{var1};
Object var3 = this.handler.invoke(this, methods[0], var2);
return (CouponViewDTO)var3;
}
}
public class JavassistProxyFactory extends AbstractProxyFactory {
。。。
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 代理包装类,包装了本地的服务提供者
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
// 代理类入口
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
public abstract class Wrapper {
。。。
abstract public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException, InvocationTargetException;
}
具体Wrapper代理类使用字节码技术动态生成,本地服务CouponServiceViewFacade的代理包装类举例:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.dubbo.common.bytecode;
import com.xxx.CouponServiceViewFacade;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator.DC;
public class Wrapper25 extends Wrapper implements DC {
。。。
public Wrapper25() {
}
public Object invokeMethod(Object var1, String var2, Class[] var3, Object[] var4) throws InvocationTargetException {
CouponServiceViewFacade var5;
try {
var5 = (CouponServiceViewFacade)var1;
} catch (Throwable var8) {
throw new IllegalArgumentException(var8);
}
try {
if ("query".equals(var2) && var3.length == 1) {
return var5.query((String)var4[0]);
}
} catch (Throwable var9) {
throw new InvocationTargetException(var9);
}
throw new NoSuchMethodException("Not found method \"" + var2 + "\" in class com.xxx.CouponServiceViewFacade.");
}
。。。
}
在服务初始化流程中,服务消费者代理对象生成后初始化就完成了,服务消费端的初始化顺序:ReferenceConfig.get->从注册中心订阅服务->启动客户端->创建DubboInvoker->构建ClusterInvoker→创建服务代理对象;
而服务提供端的初始化才刚开始,服务提供端的初始化顺序:ServiceConfig.export->创建AbstractProxyInvoker,通过Injvm协议关联本地服务->启动服务端→注册服务到注册中心。
接下来我们讲注册层。
zookeeper://xxx/org.apache.dubbo.registry.RegistryService?application=xxx&...
@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
注册服务接口定义;
public interface RegistryService {
void register(URL url);
void unregister(URL url);
void subscribe(URL url, NotifyListener listener);
void unsubscribe(URL url, NotifyListener listener);
List<URL> lookup(URL url);
}
@SPI(Cluster.DEFAULT)
public interface Cluster {
String DEFAULT = FailoverCluster.NAME;
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
。。。
}
ClusterInvoker执行逻辑,先路由策略过滤,然后负载均衡策略选择最终的远端服务提供者。示例代理如下:
public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
。。。
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// binding attachments into invocation.
Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
}
// 集群invoker执行时,先使用路由链过滤服务提供者
List<Invoker<T>> invokers = list(invocation);
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
。。。
}
public interface Directory<T> extends Node {
Class<T> getInterface();
List<Invoker<T>> list(Invocation invocation) throws RpcException;
List<Invoker<T>> getAllInvokers();
URL getConsumerUrl();
}
public class RouterChain<T> {
。。。
public List<Invoker<T>> route(URL url, Invocation invocation) {
List<Invoker<T>> finalInvokers = invokers;
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
/**
* Notify router chain of the initial addresses from registry at the first time.
* Notify whenever addresses in registry change.
*/
public void setInvokers(List<Invoker<T>> invokers) {
//路由链监听更新服务提供者
this.invokers = (invokers == null ? Collections.emptyList() : invokers);
routers.forEach(router -> router.notify(this.invokers));
}
}
订阅服务的时候,将路由链注入到RegistryDirectory中;
public class RegistryProtocol implements Protocol {
。。。
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
。。。
// 服务目录初始化路由链
directory.buildRouterChain(subscribeUrl);
directory.subscribe(toSubscribeUrl(subscribeUrl));
。。。
return registryInvokerWrapper;
}
。。。
}
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
@SPI("dubbo")
public interface MonitorFactory {
@Adaptive("protocol")
Monitor getMonitor(URL url);
}
@Adaptive("protocol")
Monitor getMonitor(URL url);
监控服务接口定义如下,定义了一些默认的监控维度和指标项;
public interface MonitorService {
// 监控维度
String APPLICATION = "application";
String INTERFACE = "interface";
String METHOD = "method";
String GROUP = "group";
String VERSION = "version";
String CONSUMER = "consumer";
String PROVIDER = "provider";
String TIMESTAMP = "timestamp";
//监控指标项
String SUCCESS = "success";
String FAILURE = "failure";
String INPUT = INPUT_KEY;
String OUTPUT = OUTPUT_KEY;
String ELAPSED = "elapsed";
String CONCURRENT = "concurrent";
String MAX_INPUT = "max.input";
String MAX_OUTPUT = "max.output";
String MAX_ELAPSED = "max.elapsed";
String MAX_CONCURRENT = "max.concurrent";
void collect(URL statistics);
List<URL> lookup(URL query);
}
1)Invocation是请求会话领域模型,每次请求有相应的Invocation实例,负责包装dubbo方法信息为请求参数; 2)Result是请求结果领域模型,每次请求都有相应的Result实例,负责包装dubbo方法响应; 3)Invoker是实体域,代表一个可执行实体,有本地、远程、集群三类; 4)Exporter服务提供者Invoker管理实体; 5)Protocol是服务域,管理Invoker的生命周期,提供服务的暴露和引用入口;服务初始化流程中,从这一层开始进行远程服务的暴露和连接引用。 对于CouponServiceViewFacade服务来说,服务提供端会监听Dubbo端口启动tcp服务;服务消费端通过注册中心发现服务提供者信息,启动tcp服务连接远端提供者。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
default List<ProtocolServer> getServers() {
return Collections.emptyList();
}
}
Invoker接口定义
Invocation是RPC调用的会话对象,负责包装请求参数;Result是RPC调用的结果对象,负责包装RPC调用的结果对象,包括异常类信息;
public interface Invoker<T> extends Node {
Class<T> getInterface();
Result invoke(Invocation invocation) throws RpcException;
}
public class DubboProtocol extends AbstractProtocol {
。。。
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
。。。
// 开启rpc服务端
openServer(url);
optimizeSerialization(url);
return exporter;
}
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// 创建dubbo invoker,开启rpc客户端
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
。。。
}
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
@Override
public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {
。。。
Invocation inv = (Invocation) message;
Invoker<?> invoker = getInvoker(channel, inv);
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
//调用本地服务
Result result = invoker.invoke(inv);
return result.thenApply(Function.identity());
}
。。。
};
public class DubboInvoker<T> extends AbstractInvoker<T> {
。。。
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
。。。
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = calculateTimeout(invocation, methodName);
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
return AsyncRpcResult.newDefaultAsyncResult(invocation);
} else {
ExecutorService executor = getCallbackExecutor(getUrl(), inv);
CompletableFuture<AppResponse> appResponseFuture =
currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
FutureContext.getContext().setCompatibleFuture(appResponseFuture);
AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
result.setExecutor(executor);
return result;
}
}
}
@SPI(HeaderExchanger.NAME)
public interface Exchanger {
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;
}
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;
交换层模型类图:
public class HeaderExchangeHandler implements ChannelHandlerDelegate {
。。。
void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
//封装响应
Response res = new Response(req.getId(), req.getVersion());
。。。
Object msg = req.getData();
try {
CompletionStage<Object> future = handler.reply(channel, msg);
future.whenComplete((appResult, t) -> {
try {
if (t == null) {
res.setStatus(Response.OK);
res.setResult(appResult);
} else {
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
channel.send(res);
} catch (RemotingException e) {
logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
}
});
} catch (Throwable e) {
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(e));
channel.send(res);
}
}
。。。
}
final class HeaderExchangeChannel implements ExchangeChannel {
。。。
//封装请求实体
@Override
public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
。。。
// create request.
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
//RpcInvocation
req.setData(request);
DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
try {
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
。。。
}
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
自定义传输层模型
通过SPI的方式,动态选择具体的传输框架,默认是netty;
public class Transporters {
。。。
public static RemotingServer bind(URL url, ChannelHandler... handlers) throws RemotingException {
。。。
return getTransporter().bind(url, handler);
}
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
。。。
return getTransporter().connect(url, handler);
}
public static Transporter getTransporter() {
return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
}
netty框架的channel适配如下,采用装饰模式,使用netty框架的channel作为Dubbo自定义的channel做实现;
final class NettyChannel extends AbstractChannel {
private NettyChannel(Channel channel, URL url, ChannelHandler handler) {
super(url, handler);
if (channel == null) {
throw new IllegalArgumentException("netty channel == null;");
}
this.channel = channel;
}
}
@SPI("hessian2")
public interface Serialization {
byte getContentTypeId();
String getContentType();
@Adaptive
ObjectOutput serialize(URL url, OutputStream output) throws IOException;
@Adaptive
ObjectInput deserialize(URL url, InputStream input) throws IOException;
}
vivo互联网服务器团队-Wang Genfu
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:
我正在尝试创建密码规则来设计可恢复的密码更改。我通过passwords_controller.rb做了一个父类(superclass),但我需要在应用规则之前检查用户角色,但我所拥有的只是reset_password_token。 最佳答案 假设您的模型是用户:User.with_reset_password_token(your_token_here)Source 关于ruby-on-rails-设计通过reset_password_token获取用户,我们在StackOverflow
我已经使用Apartment设置了一个Rails5应用程序(1.2.0)和Devise(4.2.0)。由于某些DDNS问题,应用只能在app.myapp.com下访问(请注意子域app)。myapp.com重定向到app.myapp.com。我的用例是每个注册该应用的用户(租户)都应该通过他们的子域(例如tenant.myapp.com)访问他们的特定数据。用户不应限定在其子域内。基本上应该可以从任何子域登录。重定向到租户的正确子域由ApplicationController处理。根据Devise标准,登录页面位于app.myapp.com/users/sign_in。这就是问题开始的
我在关注RyanbatesRailsCast的devise和omniauth(第235集-devise-and-omniauth-revised)。当我尝试使用Twitter登录时,标题中不断出现错误。defself.new_with_session(params,session)ifsession["devise.user_attributes"]new(session["devise.user_attributes"],without_protection:true)do|user|user.attributes=paramsuser.valid?end完整跟踪:C:/Ruby20
1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>