Netty学习笔记
- Netty应用篇
- 高级应用
- 自定义 Tomcat
- 定义Servnet规范
- 定义Tomcat服务器
- 定义业务Servnet
- 手写RPC框架
- RPC 简介
- RPC 框架具体需求
- 定义工程
- rpc接口
- rpc服务端
- rpc客户端
- 自定义Dubbo框架V1
- 原理
- 新增需求
- 定义工程
- dubbo api 接口
- dubbo server 服务端
- dubbo client 客户端
- 测试
- 自定义Dubbo框架V2
- 修改服务提供者端
- 修改消费者端
Netty应用篇
高级应用
自定义 Tomcat
- 这里要手写的是一个 Web 容器,一个类似于 Tomcat 的容器,用于处理 HTTP 请求。该 Web 容器没有实现 JavaEE 的 Servlet 规范,不是一个 Servlet 容器。但其是类比着 Tomcat 来写的,这里定义了自己的请求、响应及 Servlet,分别命名为了 NettyRequest,NettyResponse 与 Servnet。
- 我们这里要定义一个 Tomcat,这个 Web 容器提供给用户后,用户只需要按照使用步骤就可以将其自定义的 Servnet 发布到该 Tomcat 中。我们现在给出用户对于该 Tomcat 的使用步骤:
- 用户只需将自定义的 Servnet 放入到指定的包中,例如 com.yw.netty.example.webapp 包中。
- 用户在访问时,需要将自定义的 Servnet 的简单类名全小写后的字符串作为该 Servnet 的 Name 进行访问。
- 若没有指定的 Servnet,则访问默认的 Servnet。
定义Servnet规范
- 定义请求接口 NettyRequest:Servnet规范之请求规范
public interface NettyRequest {
/**
* 获取URI,包含请求参数,即?后的内容
*/
String getUri();
/**
* 获取请求路径,其不包含请求参数
*/
String getPath();
/**
* 获取请求分发(GET、POST等)
*/
String getMethod();
/**
* 获取所有请求参数
*/
Map<String, List<String>> getParameters();
/**
* 获取指定名称的请求参数
*/
List<String> getParameters(String name);
/**
* 获取指定名称的请求参数的第一个值
*/
String getParameter(String name);
}
- 定义响应接口 NettyResponse:ServNet规范之响应规范
public interface NettyResponse {
/**
* 将响应写入到Channel
*/
void write(String content) throws Exception;
}
- 定义 Servnet 规范:
public abstract class Servnet {
public abstract void doGet(NettyRequest request, NettyResponse response) throws Exception;
public abstract void doPost(NettyRequest request, NettyResponse response) throws Exception;
}
定义Tomcat服务器
- 定义 DefaultNettyRequest 类:Tomcat中对Servnet规范的默认实现
public class DefaultNettyRequest implements NettyRequest {
private HttpRequest request;
public DefaultNettyRequest(HttpRequest request) {
this.request = request;
}
@Override
public String getUri() {
return request.uri();
}
@Override
public String getPath() {
QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
return decoder.path();
}
@Override
public String getMethod() {
return request.method().name();
}
@Override
public Map<String, List<String>> getParameters() {
QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
return decoder.parameters();
}
@Override
public List<String> getParameters(String name) {
return getParameters().get(name);
}
@Override
public String getParameter(String name) {
List<String> parameters = getParameters(name);
if (parameters == null || parameters.size() == 0) {
return null;
}
return parameters.get(0);
}
}
- 定义 DefaultNettyResponse 类:Tomcat中对Servnet规范的默认实现
public class DefaultNettyResponse implements NettyResponse {
private HttpRequest request;
private ChannelHandlerContext context;
public DefaultNettyResponse(HttpRequest request, ChannelHandlerContext context) {
this.request = request;
this.context = context;
}
@Override
public void write(String content) throws Exception {
// 处理content为空的情况
if (StringUtil.isNullOrEmpty(content)) {
return;
}
// 创建响应对象
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
// 根据响应体内容大小为response对象分配存储空间
Unpooled.wrappedBuffer(content.getBytes(CharsetUtil.UTF_8)));
// 获取响应头
HttpHeaders headers = response.headers();
// 设置响应体类型
headers.set(HttpHeaderNames.CONTENT_TYPE, "text/json");
// 设置响应体长度
headers.set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
// 设置缓存过期时间
headers.set(HttpHeaderNames.EXPIRES, 0);
// 若HTTP请求是长连接,则响应也使用长连接
if (HttpUtil.isKeepAlive(request)) {
headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
// 将响应写入到Channel
context.writeAndFlush(response);
}
}
- 定义 DefaultServnet 类:Tomcat中对Servnet规范的默认实现
public class DefaultServnet extends Servnet {
@Override
public void doGet(NettyRequest request, NettyResponse response) throws Exception {
// http://localhost:8888/someservnet/xxx/ooo?name=zs
// uri:/someservnet/xxx/ooo?name=zs
// path:/someservnet/xxx/ooo
String sernetName = request.getUri().split("/")[1];
response.write("404 - no this servnet : " + sernetName);
}
@Override
public void doPost(NettyRequest request, NettyResponse response) throws Exception {
doGet(request, response);
}
}
- 定义服务器类:
- 定义服务器端处理器:
- 定义启动类 TomcatStarter:
public class TomcatStarter {
public static void main(String[] args) throws Exception {
TomcatServer server = new TomcatServer("com.yw.netty.example.webapp");
server.start();
}
}
定义业务Servnet
public class OneServnet extends Servnet {
@Override
public void doGet(NettyRequest request, NettyResponse response) throws Exception {
String uri = request.getUri();
String path = request.getPath();
String method = request.getMethod();
String name = request.getParameter("name");
String content = "uri = " + uri + "\n" +
"path = " + path + "\n" +
"method = " + method + "\n" +
"param = " + name;
response.write(content);
}
@Override
public void doPost(NettyRequest request, NettyResponse response) throws Exception {
doGet(request, response);
}
}
- 启动 TomcatStarter,接口测试:http://localhost:8888/oneservnet/xxx/ooo?name=zs
手写RPC框架
RPC 简介
- RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。在 OSI 网络通信模型中, RPC 跨越了传输层和应用层。RPC 使得开发分布式系统应用变得更加容易。
- RPC 采 用 C /S 模式。请求程序就是 Client ,而服务提供程序就是 Server。首先,Client 发送一个带有请求参数的调用请求到 Server,然后等待响应 。在 Server 端,进程一直处于睡眠状态直到接收到 Client 的调用请求。当一个调用请求到达,Server 会根据请求参数进行计算,并将计算结果发送给Client,然后等待下一个调用请求。Client接收到响应信息,即获取到调用结果,然后根据情况继续发出下一次调用。
RPC 框架具体需求
- 定义一个 RPC 框架,这个框架提供给用户后,用户只需要按照使用步骤就可以完成 RPC 远程调用。使用步骤:
- 用户需要将业务通知到 Server 与 Client,因为业务接口是是服务名称。
- 用户只需将业务接口的实现类写入到 Server 端的指定包下,那么这个包下的实现类就会被 Server 发布。
- Client 端只需根据业务接口名就可获取到 Server 端发布的服务提供者,然后就可以调用到远程 Server 端的实现类方法的执行。
定义工程
- 工程主要包括:05-rpc-custom-api、05-rpc-custom-server、05-rpc-custom-client,其中 05-rpc-custom-server、05-rpc-custom-client 都依赖 05-rpc-custom-api。
rpc接口
- 定义业务接口:
public interface SomeService {
String hello(String name);
}
- 定义数据对象:
@Data
@Accessors(chain = true)
public class Invocation implements Serializable {
/**
* 接口名,即微服务名称
*/
private String className;
/**
* 要远程调用的方法名
*/
private String methodName;
/**
* 参数类型列表
*/
private Class<?>[] paramTypes;
/**
* 参数值列表
*/
private Object[] paramValues;
}
rpc服务端
- rpc服务端server:
public class RpcServer {
private RpcServer() {}
private static final RpcServer INSTANCE = new RpcServer();
public static RpcServer getInstance() {
return INSTANCE;
}
/**
* 服务提供者注册表,key:微服务名称,即业务接口;value:业务接口实例
*/
private Map<String, Object> registerMap = new HashMap<>(32);
/**
* 用于缓存服务提供者的类名
*/
private List<String> classCache = new ArrayList<>();
/**
* 发布服务
*/
public RpcServer publish(String basePackage) throws Exception {
// 将指定包下的业务接口实现类名写入到classCache中
cacheClassName(basePackage);
// 将指定包下的业务接口实现类写入到注册表
doRegister();
return this;
}
private void cacheClassName(String basePackage) {
URL resource = this.getClass().getClassLoader()
.getResource(basePackage.replaceAll("\\.", "/"));
if (resource == null) {
return;
}
File dir = new File(resource.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
// 若当前file为目录,则递归
cacheClassName(basePackage + "." + file.getName());
} else if (file.getName().endsWith(".class")) {
String fileName = file.getName().replace(".class", "").trim();
classCache.add(basePackage + "." + fileName);
}
}
System.out.println(classCache);
}
private void doRegister() throws Exception {
if (classCache.size() == 0) {
return;
}
for (String className : classCache) {
// 将当前遍历的类加载到内存
Class<?> clazz = Class.forName(className);
registerMap.put(clazz.getInterfaces()[0].getName(), clazz.newInstance());
}
System.out.println(registerMap);
}
/**
* 启动服务
*/
public void start() throws InterruptedException {
NioEventLoopGroup parentGroup = new NioEventLoopGroup(1);
NioEventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
// 用于指定当Server的连接请求处理线程全被占用时,临时存放已经完成了三次握手的请求的队列的长度。
// 默认是50
.option(ChannelOption.SO_BACKLOG, 1024)
// 指定使用心跳机制来保证TCP长连接的存活性
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE,
// null: 使用默认的类加载器
ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new RpcServerHandler(registerMap));
}
});
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("==>> 服务端已启动,监听的端口为:8888 <<==");
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
- rpc服务端服务实现类:
public class SomeServiceImpl implements SomeService {
@Override
public String hello(String name) {
return name + "欢迎你";
}
}
- rpc服务端处理器:
public class RpcServerHandler extends SimpleChannelInboundHandler<Invocation> {
private Map<String, Object> registerMap;
public RpcServerHandler(Map<String, Object> registerMap) {
this.registerMap = registerMap;
}
/**
* 解析Client发送来的msg,然后从registerMap注册表中查看是否有对应的接口
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Invocation msg) throws Exception {
Object result = "没有该提供者,或没有该方法";
if (registerMap.containsKey(msg.getClassName())) {
Object provider = registerMap.get(msg.getClassName());
result = provider.getClass().getMethod(msg.getMethodName(), msg.getParamTypes())
.invoke(provider, msg.getParamValues());
}
ctx.writeAndFlush(result);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- rpc服务端启动类:
public class RpcStarter {
public static void main(String[] args) throws Exception {
RpcServer.getInstance().publish("com.yw.netty.example.service")
.start();
}
}
rpc客户端
- rpc客户端远程调用:
public class RpcProxy {
private RpcProxy() {
}
public static <T> T create(Class<?> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 若调用的是Object的方法,则直接进行本地调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 远程调用在这里发生
return rpcInvoke(clazz, method, args);
}
});
}
private static Object rpcInvoke(Class<?> clazz, Method method, Object[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
RpcClientHandler rpcHandler = new RpcClientHandler();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
// Nagle算法开关
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast(rpcHandler);
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
// 形成远程调用的参数实例
val invocation = new Invocation()
.setClassName(clazz.getName())
.setMethodName(method.getName())
.setParamTypes(method.getParameterTypes())
.setParamValues(args);
// 将参数实例发送给Server
future.channel().writeAndFlush(invocation).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
return rpcHandler.getResult();
}
}
- rpc客户端Netty处理器:
public class RpcClientHandler extends SimpleChannelInboundHandler<Object> {
private Object result;
public Object getResult() {
return result;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
this.result = msg;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 消费者:
public class RpcConsumer {
public static void main(String[] args) {
SomeService service = RpcProxy.create(SomeService.class);
System.out.println(service.hello("Netty RPC"));
System.out.println(service.hashCode());
}
}
自定义Dubbo框架V1
原理
- Dubbo框架本身就是一个 RPC 框架,消费者要连接的服务端IP和端口号不是硬编码在客户端,而是从 zk 中读取到的。那么 zk 中的这些信息是从哪里来的呢?
- 一个 Dubbo 应用中会存在很对服务提供者与消费者。个提供者都是一个Netty Server,其会对外暴露自己所在主机的 IP 与 Port。每个消费者都是一个Netty Client,其会通过连接相应主机的 IP 和 Port 来获取相应的服务。
- 服务提供者的IP和Port是如何对外暴露的呢?其会未自己所提供的服务起一个服务名称,一般为业务接口。然后将该服务名称与对应提供者主机的 IP 和 Port 相绑定,注册到zk中。
- 具体注册步骤:
- ① 在 ZK 中创建一个 dubbo 的持久根节点,例如 /mydubbo。
- ② 以服务名称作为节点名称在 /mydubbo 节点下创建一个持久节点,例如 /mydubbo/com.abc.service.SomeService。
- ③ 在服务名称下再创建临时节点,节点名称为当前提供者的主机 ip 与 port,完成注册。由于一个服务的提供者可能有多个,所以这样就可以监控到各个提供者主机的在线情况。例如 /mydubbo/com.abc.service.SomeService/192.168.254.1:8888。
- 服务消费者会从服务注册中心zk中查找自己所需要的服务名称,一般为业务接口名。然后获取到该服务名称对应的所有提供者主机信息,并通过负载均衡方式选取一个主机进行连接,获取相应服务。
- 具体消费过程:
- ① 从 ZK 的 /mydubbo 节点下找到指定服务名称的子节点。
- ② 为该服务节点添加 watcher 监听,以监听其子节点列表的变更情况。
- ③ 获取该服务名称节点的所有子节点,即该服务的所有提供者主机 IP 和 Port。
- ④ 通过负载均衡选择一个提供者主机。
- ⑤ 消费者将其调用信息发送给提供者主机。
- ⑥ 提供者主机将其执行结果返回给消费者。
新增需求
- 当客户端通过负载均衡策略选择了某一提供者主机后,我们这里新增了一个需求:提供者主机中提供同一服务名称(接口名)的实现类有多个。这样,消费者可以指定其要调用的实现类。若消费者没有指定要调用的实现类,其会调用到第一个注册的实现类。
- 为了实现提供者端业务接口可以有多个实现类供客户端选择,这里要求实现类名必须是一个前辍 prefix 后是业务接口名。这样,消费者在进行消费时,可以通过前辍来指定要调用的是哪个实现类。
定义工程
- 复制 05-rpc-custom 重命名为 06-dubbo-custom,并在此基础上修改。
dubbo api 接口
- 业务接口:
public interface PayService {
String pay(String info);
}
- 常量:
public class ZkConstant {
/**
* zk集群地址
*/
public static final String ZK_CLUSTER = "192.168.254.120:2181";
/**
* dubbo在zk中的根节点路径
*/
public static final String ZK_DUBBO_ROOT_PATH = "/mydubbo";
}
- 修改数据对象Invocation:
dubbo server 服务端
- 定义注册中心接口:
public interface RegistryCenter {
/**
* 注册到注册中心
*
* @param serviceName 服务名,一般为接口名称
* @param serviceAddress 服务提供者的ip:port
*/
void register(String serviceName, String serviceAddress) throws Exception;
}
- 注册中心实现类:
/**
* zk 注册中心
*/
public class ZkRegistryCenter implements RegistryCenter {
private volatile CuratorFramework client;
private ZkRegistryCenter() {
// 使用双重检查锁初始化zk client
if (null == client) {
synchronized (this) {
if (null == client) {
// 创建并初始化zk客户端
client = CuratorFrameworkFactory.builder()
// 指定要连接的zk集群地址
.connectString(ZkConstant.ZK_CLUSTER)
// 指定连接超时
.connectionTimeoutMs(10000)
// 指定会话超时
.sessionTimeoutMs(4000)
// 指定重试策略,每重试一次,sleep 1 秒,最多重试10次
.retryPolicy(new ExponentialBackoffRetry(1000, 10))
.build();
// 启动zk客户端
client.start();
}
}
}
}
public static ZkRegistryCenter getInstance() {
return new ZkRegistryCenter();
}
@Override
public void register(String serviceName, String serviceAddress) throws Exception {
// 要创建的服务名称对应的节点路径
String servicePath = ZkConstant.ZK_DUBBO_ROOT_PATH + "/" + serviceName;
if (client.checkExists().forPath(servicePath) == null) {
client.create()
// 若父节点不存在,则会自动创建
.creatingParentsIfNeeded()
// 指定要创建的节点类型:持久节点
.withMode(CreateMode.PERSISTENT)
// 指定要创建的节点名称
.forPath(servicePath);
}
// 要创建的主机对应的节点路径
String hostPath = servicePath + "/" + serviceAddress;
if (client.checkExists().forPath(hostPath) == null) {
client.create()
// 临时节点
.withMode(CreateMode.EPHEMERAL)
.forPath(hostPath);
}
}
}
- 服务接口实现类:
public class AlipayPayService implements PayService {
@Override
public String pay(String info) {
return "使用支付宝支付: " + info;
}
}
public class WechatPayService implements PayService {
@Override
public String pay(String info) {
return "使用微信支付: " + info;
}
}
- 修改服务器类:将当前Server以提供者身份注册到zk;启动 Netty Server 时,将 serviceAddress 解析为 IP 和 Port。
- 修改服务器处理器类:
dubbo client 客户端
- 服务发现接口和实现类:
/**
* 服务发现接口
*/
public interface ServiceDiscovery {
/**
* @param serviceName 服务名称,即接口名
* @return 返回经过负载均衡后的server
*/
String discovery(String serviceName) throws Exception;
}
/**
* zookeeeper做服务发现
*/
public class ZkServiceDiscovery implements ServiceDiscovery {
private volatile CuratorFramework client;
private List<String> providers;
private ZkServiceDiscovery() {
if (null == client) {
synchronized (this) {
if (null == client) {
// 创建并初始化zk客户端
client = CuratorFrameworkFactory.builder()
// 指定要连接的zk集群地址
.connectString(ZkConstant.ZK_CLUSTER)
// 指定连接超时
.connectionTimeoutMs(10000)
// 指定会话超时
.sessionTimeoutMs(4000)
// 指定重试策略:每重试一次,sleep 1秒,最多重试10次
.retryPolicy(new ExponentialBackoffRetry(1000, 10))
.build();
// 启动zk客户端
client.start();
}
}
}
}
public static ZkServiceDiscovery getInstance() {
return new ZkServiceDiscovery();
}
@Override
public String discovery(String serviceName) throws Exception {
// 要获取的服务在zk中的路径
String servicePath = ZkConstant.ZK_DUBBO_ROOT_PATH + "/" + serviceName;
// 获取到指定节点的所有子节点列表,并为该节点设置子节点列表更改的watcher监听
providers = client.getChildren()
// 一旦指定节点的子节点列表发生变更,则马上再获取所有子节点列表
.usingWatcher((CuratorWatcher) evt -> providers = client.getChildren().forPath(servicePath))
.forPath(servicePath);
if (providers.size() == 0) {
return null;
}
// 负载均衡选择一个主机
return RandomLoadBalance.getInstance().choose(providers);
}
}
- 负载均衡接口和实现类:
/**
* 负载均衡接口
*/
public interface LoadBalance {
/**
* 从providers中选择一个
*/
String choose(List<String> providers);
}
/**
* 随机负载均衡
*/
public class RandomLoadBalance implements LoadBalance {
private RandomLoadBalance() {}
private static final RandomLoadBalance INSTANCE = new RandomLoadBalance();
public static RandomLoadBalance getInstance() {
return INSTANCE;
}
@Override
public String choose(List<String> providers) {
return providers.get(new Random().nextInt(providers.size()));
}
}
- 修改RpcProxy:
测试
- 启动多个提供者:
- 启动消费者完成对提供者的调用:
自定义Dubbo框架V2
- 在自定义Dubbo框架V1版本基础上进行修改,引入 spring-boot 父工程依赖和 spring-boot-starter
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--starter 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
修改服务提供者端
- 修改服务提供者端:将注册中心实现类、RpcServer、接口实现类、启动类等交给Spring容器管理
/**
* zk 注册中心
*/
@Component
public class ZkRegistryCenter implements RegistryCenter {
private volatile CuratorFramework client;
@PostConstruct
public void init() {
// ...略:创建并初始化zk客户端,启动zk客户端
}
@Override
public void register(String serviceName, String serviceAddress) throws Exception {
// ...略
}
}
@Component
public class RpcServer {
@Autowired
private ZkRegistryCenter registerCenter;
// ...略
private void doRegister(String serviceAddress) throws Exception {
// ...略
// 将当前Server以提供者身份注册到zk
registerCenter.register(interfaces.getName(), serviceAddress);
}
}
- 启动类:
@SpringBootApplication
public class RpcProvider implements CommandLineRunner {
@Autowired
private RpcServer rpcServer;
public static void main(String[] args) {
SpringApplication.run(RpcProvider.class, args);
}
@Override
public void run(String... args) throws Exception {
String basePackage = "com.yw.netty.example.service";
rpcServer.publish(basePackage, "192.168.254.1:9999");
}
}
修改消费者端
- 将服务发现类、RpcProxy接口实现类、LoadBalance实现类、启动类等交给Spring容器管理
@Component
public class ZkServiceDiscovery implements ServiceDiscovery {
@Autowired
private LoadBalance loadBalance;
private volatile CuratorFramework client;
private List<String> providers;
@PostConstruct
public void init() {
// ...略:创建并初始化zk客户端、启动zk客户端
}
@Override
public String discovery(String serviceName) throws Exception {
// ...略
}
}
@Component
public class RpcProxy {
@Autowired
private ServiceDiscovery serviceDiscovery;
// ...略
}
- 启动类:
@SpringBootApplication
public class RpcConsumer implements CommandLineRunner {
@Autowired
private RpcProxy rpcProxy;
public static void main(String[] args) {
SpringApplication.run(RpcConsumer.class, args);
}
@Override
public void run(String... args) {
PayService service = rpcProxy.create(PayService.class, "WeChat");
for (int i = 0; i < 10; i++) {
System.out.println(service.pay("9.9元"));
}
}
}