grpc java 泛型_gRPC学习记录(五)-拦截器分析 - Java 技术驿站-Java 技术驿站

本文介绍了gRPC Java中的拦截器机制,通过一个简单的例子展示了如何使用拦截器来添加额外的功能,例如在客户端添加token到header。文章详细分析了`ClientCall`的调用流程,并给出了客户端和服务器端拦截器的实现,强调了拦截器在处理请求前后的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于此类调用拦截器是必不可少的,本篇就分析下拦截器的实现.(博主本来想分析源码的,但是水平不足,并发知识欠缺,看的不是很懂,哎,仍需努力),另外貌似不同版本有些差异,这里使用的是1.0.3版本.

1.一个拦截器的小例子

在分析之前先看一种设计.

有一个接口如下:

/**

* 主调用接口

*/

public abstract class Client {

public abstract void start(String say);

}

/**

* 上述接口实现类

*/

public class ClientImp extends Client {

@Override

public void start(String say) {

System.out.println(say);

}

}

对此接口相关的转换器:

/**

* 用于包装Client到另一个Client

*/

public abstract class ForwardingClient extends Client{

//要包装的对象

protected abstract Client delegate();

@Override

public void start(String say) {

delegate().start(say);

}

}

/**

* 一个简单的包装实现类,必须要传入要包装的对象

*/

public class ForwardingClientImpl extends ForwardingClient{

//被委托对象

private final Client client;

public ForwardingClientImpl(Client client) {

this.client = client;

}

@Override

protected Client delegate() {

return client;

}

}

然后在下列方法中调用:

public class InterceptTest {

public static void main(String[] args) {

Client client = new ClientImp();//主要想执行的方法

//构造第一个拦截器

Client intercept1 = new ForwardingClientImpl(client){

@Override

public void start(String say) {

System.out.println("拦截器1");

super.start(say);

}

};

//构造第二个拦截器

Client intercept2 = new ForwardingClientImpl(intercept1){

@Override

public void start(String say) {

System.out.println("拦截器2");

super.start(say);

}

};

//执行主方法

intercept2.start("这是要执行的方法");

}

}

毫无疑问会输出

拦截器2

拦截器1

这是要执行的方法

分析一下针对Client接口,通过ForwardingClient可以实现自身的嵌套调用,从而达到了类似拦截器的效果.在gRPC中有很多类似的嵌套类,其本质和上面差不多,上面例子有助于对gRPC拦截器的掌握.

2.gRPC的ClientCall

该抽象类就是用来调用远程方法的,实现了发送消息和接收消息的功能,该接口由两个泛型ReqT和ReqT,分别对应着请求发送的信息,和请求收到的回复.

ClientCall抽象类主要有两个部分组成,一是public abstract static class Listener用于监听服务端回复的消息,另一部分是针对客户端请求调用的一系列过程,如下代码流程所示:

该类中方法都是抽象方法,规定了整个调用顺序,如下:

call = channel.newCall(unaryMethod, callOptions);

call.start(listener, headers);

call.sendMessage(message);

call.halfClose();

call.request(1);

// wait for listener.onMessage()

在ClientCall的子类中有ForwardingClientCall,该类的作用和之前的Demo一样,用于包装ClientCall,然后实现委托嵌套调用,里面方法都如下代码所示:

@Override

public void start(Listener responseListener, Metadata headers) {

delegate().start(responseListener, headers);

}

@Override

public void request(int numMessages) {

delegate().request(numMessages);

}

```

那和之前的Demo一对比,拦截器怎么使用就变得很容易了.

创建一个客户端拦截器,其中为header添加了token参数.之所以要实现`ClientInterceptor`接口,因为Channel本身也是可以嵌套的类,所以创建ClientCall也是被一层一层的调用.

/**

* 客户端拦截器

* @author Niu Li

* @date 2017/2/4

*/

//ClientInterceptor接口是针对ClientCall的创建进行拦截

public class ClientInterruptImpl implements ClientInterceptor {

@Override

public ClientCall interceptCall(MethodDescriptor method,

CallOptions callOptions, Channel next) {

//创建client

System.out.println("创建client1");

ClientCall clientCall = next.newCall(method,callOptions);

return new ForwardingClientCall() {

@Override

protected ClientCall delegate() {

return clientCall;

}

@Override

public void start(ListenerresponseListener, Metadata headers) {

System.out.println("拦截器1,在此可以对header参数进行修改");

Metadata.Keytoken = Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER);

headers.put(token,"123456");

super.start(responseListener, headers);

}

};

}

}

调用输出如下:

创建client1

拦截器1,在此可以对header参数进行修改

这是针对客户端调用前的拦截,对于客户端收到的回复拦截则通过ClientCall的静态内部类Listener来实现,该Listener也是可以嵌套的,其内有如下方法:

public void onHeaders(Metadata headers) {}

public void onMessage(T message) {}

public void onClose(Status status, Metadata trailers) {}

public void onReady() {}

对之前start方法改造下,让其判断返回的header中有没有传送过去的token,没有则该请求视为失败.

@Override

public void start(ListenerresponseListener, Metadata headers) {

System.out.println("拦截器1,在此可以对header参数进行修改");

Metadata.Keytoken = Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER);

headers.put(token,"123456");

ListenerforwardListener = new ForwardingClientCallListener.

SimpleForwardingClientCallListener(responseListener) {

@Override

public void onHeaders(Metadata headers) {

Metadata.Keytoken = Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER);

if (!"123456".equals(headers.get(token))){

System.out.println("返回参数无token,关闭该链接");

super.onClose(Status.DATA_LOSS,headers);

}

super.onHeaders(headers);

}

};

super.start(forwardListener, headers);

}

最后再Channel创建的时候使用`intercept(new ClientInterruptImpl())`加入拦截器这样就简单实现了客户端的拦截了.

* * *

### 3.gRPC的ServerCall

有一点要搞明白,ClientCall是针对客户端要调用的方法的,而ServerCall是针对ClientCall的.看如下例子:

public class ServerInterruptImpl implements ServerInterceptor{

@Override

public ServerCall.ListenerinterceptCall(ServerCall call,

Metadata headers, ServerCallHandler next) {

System.out.println("执行server拦截器1,获取token");

//获取客户端参数

Metadata.Keytoken = Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER);

String tokenStr = headers.get(token);

if (StringUtil.isNullOrEmpty(tokenStr)){

System.out.println("未收到客户端token,关闭此连接");

call.close(Status.DATA_LOSS,headers);

}

//服务端写回参数

ServerCall serverCall = new ForwardingServerCall.SimpleForwardingServerCall(call) {

@Override

public void sendHeaders(Metadata headers) {

System.out.println("执行server拦截器2,写入token");

headers.put(token,tokenStr);

super.sendHeaders(headers);

}

};

return next.startCall(serverCall,headers);

}

}

当服务端接收到请求的时候就会打印出来如下的日志.这样就实现了服务端接手前的拦截和写回时的拦截.

执行server拦截器1,获取token

收到的信息:world:0

执行server拦截器2,写入token

```

关于更多使用还在琢磨中,目前欠缺并发知识,所以下一步打算看看并发相关的资料.

附录:

<think>我们正在讨论服务端开发中是否建议广使用try-except进行异常处理。结合之前的讨论和引用内容,我们可以从几个方面来考虑: 1. **引用[1]中提到**:在数据库操作中,有些操作(如UPDATE)可能不会抛出异常,而是通过返回影响行数来判断是否成功。同时,异常对性能的影响主要发生在异常触发时(堆栈展开),但未触发异常的代码几乎不受影响。因此,建议大胆使用异常,但不要滥用。 2. **引用[3]中提到**:gRPC中定义了各种异常情况(如超时、资源耗尽等),这些异常需要在服务端和客户端进行适当处理。 3. **服务端开发的特点**:服务端需要高可用性、健壮性和容错能力。异常处理是保证这些特性的关键手段之一。 **结论**:在服务端开发中,推荐使用异常处理机制,但需要遵循以下原则: ### 1. 区分异常情况与正常流程 - 使用异常处理真正的“异常”情况(如网络中断、数据库连接失败、非法参数等),而不是用于控制正常流程。 - 对于预期内的错误(如用户输入验证失败),应通过返回错误码或结果对象(如`Result`模式)处理。 ### 2. 精确捕获异常 - 避免笼统的`except Exception`,而应捕获具体的异常类(如`FileNotFoundError`、`ConnectionError`等)。 - 例如,在数据库操作中,除了检查影响行数,还应捕获特定的数据库异常(如引用[1]中的`MySQLException`)。 ### 3. 资源清理 - 使用上下文管理器(`with`语句)或`try-finally`确保资源(如数据库连接、文件句柄)被正确释放。 - 例如: ```python try: conn = database.connect() # 执行操作 except DatabaseError as e: logging.error("Database error occurred", exc_info=e) finally: if conn: conn.close() # 确保连接关闭 ``` ### 4. 分层处理原则 - **底层库**:抛出具有明确语义的异常(如`AuthenticationError`)。 - **服务层**:捕获底层异常并转换为适合上层的错误表示(如将数据库异常转换为`ServiceUnavailableError`)。 - **顶层(如API入口)**:设置全局异常处理器,捕获未处理异常并返回统一错误响应(HTTP 500)。 ### 5. 记录与监控 - 在捕获异常时记录详细上下文(如`logging.exception()`),便于排查问题。 - 对关键异常进行监控告警(如通过Prometheus/Grafana)。 ### 6. 避免滥用 - 不要用异常代替条件检查(如避免用`try`检查数据是否存在,而应优先用`if`)。 - 高频执行路径中(如循环内),优先使用错误码或状态检查,避免异常处理成为性能瓶颈。 ### 示例:服务端API中的异常处理 ```python @app.route("/update_user", methods=["POST"]) def update_user(): try: user_id = request.json["id"] new_data = request.json["data"] # 业务逻辑层调用 updated = user_service.update_user(user_id, new_data) if not updated: # 正常业务错误(如用户不存在) return jsonify({"error": "User not found"}), 404 return jsonify({"status": "success"}) except KeyError: # 请求数据缺失 return jsonify({"error": "Invalid request"}), 400 except DatabaseConnectionError: # 特定数据库异常 return jsonify({"error": "Database unavailable"}), 503 except Exception as e: # 全局兜底 logging.critical("Unhandled exception", exc_info=e) return jsonify({"error": "Internal server error"}), 500 ``` > **核心建议**:服务端开发中应**积极而谨慎**地使用异常处理。异常机制是保障系统健壮性的重要工具,但需结合业务场景合理设计处理策略[^1][^3]。 §§相关问题§§ 1. 如何设计服务端的全局异常拦截机制? 2. 在微服务架构中,异常处理与熔断机制如何协同工作? 3. 哪些异常适合在服务端转换为HTTP状态码? 4. 为什么Python的`asyncio`服务中异常处理需要特殊设计?</think>### 服务端开发中异常处理的最佳实践 在服务端开发中,**推荐合理使用 `try-except` 异常处理机制**,但需遵循特定原则。以下是关键建议和注意事项: #### ✅ 推荐使用场景 1. **资源边界操作** 在涉及外部资源(数据库、网络、文件)的边界处必须使用异常处理: ```python # 数据库操作示例(参考引用[1]) try: cursor.execute("UPDATE users SET status=? WHERE id=?", (status, user_id)) if cursor.rowcount == 0: # 需显式检查影响行数 raise NoRowsAffected("更新失败") except (MySQLException, NoRowsAffected) as e: logger.error(f"数据库操作失败: {e}") rollback_transaction() ``` 2. **第三方服务交互** gRPC/WebSocket等通信必须处理连接异常(参考引用[2][3]): ```python # gRPC服务端异常处理(参考引用[3]) try: response = await grpc_service.process_request(request) except DEADLINE_EXCEEDED: return Status(code=StatusCode.DEADLINE_EXCEEDED) except RESOURCE_EXHAUSTED: return Status(code=StatusCode.RESOURCE_EXHAUSTED) ``` 3. **关键业务逻辑** 对核心业务流程进行保护性封装: ```python def process_order(order_data): try: validate_order(order_data) # 可能抛出ValidationError charge_payment(order_data) # 可能抛出PaymentError update_inventory(order_data) # 可能抛出InventoryError except BusinessException as e: handle_business_error(e) raise HTTPException(status_code=400, detail=str(e)) ``` #### ⚠️ 使用注意事项 1. **避免过度捕获** - 禁止空的 `except:` 或 `except Exception:` - 优先捕获具体异常类(如 `MySQLException` 而非 `Exception`) 2. **异常层级设计** 建立业务异常体系提升可维护性: ```mermaid graph TD BaseException --> ServiceException ServiceException --> DatabaseError ServiceException --> NetworkError DatabaseError --> MySQLException NetworkError --> GRPCError ``` 3. **性能敏感区域** - 高频循环内部避免 `try` 块(每秒万次以上调用) - 改用预先检查模式(LBYL): ```python # 替代方案:预先验证 if not is_valid(input_data): # 避免用异常做流程控制 return ErrorResponse(...) ``` 4. **资源清理保障** 使用上下文管理器确保资源释放: ```python with database_connection_pool.get_connection() as conn: try: conn.execute(query) except DatabaseError: handle_error() # 连接会在__exit__中自动关闭 ``` #### 📊 异常处理策略对比 | 场景 | 推荐方式 | 不推荐方式 | |---------------------|-----------------------------|------------------------| | 数据库操作 | `try` + 影响行数检查[^1] | 仅依赖异常 | | 网络超时 | 显式捕获 `DEADLINE_EXCEEDED`[^3] | 异常捕获 | | 业务校验 | 条件检查(LBYL) | 用异常做流程控制 | | 资源释放 | 上下文管理器 | 手动 try-finally | > **核心原则**:服务端应**在系统边界处严格处理异常**,在核心逻辑层适度使用,避免用异常替代正常业务逻辑。关键是要区分*预期错误*(用错误码/返回体处理)和*意外异常*(用异常机制处理)[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值