dubbo參數(shù)校驗(yàn)ValidationFilter使用與說明
org.apache.dubbo.rpc.Filter
核心功能
- 攔截RPC調(diào)用流程
Filter是Dubbo框架中實(shí)現(xiàn)攔截邏輯的核心接口,作用于服務(wù)消費(fèi)者和提供者的作業(yè)鏈路,支持在方法調(diào)用前后插入自定義邏輯。如參數(shù)校驗(yàn)、異常處理、日志記錄等。
- 擴(kuò)展性機(jī)制
Dubbo通過SPI擴(kuò)展機(jī)制動(dòng)態(tài)加載Filter實(shí)現(xiàn)類,構(gòu)建鏈?zhǔn)秸{(diào)用結(jié)構(gòu),每個(gè)Filter通過Invoke方案?jìng)鬟f調(diào)用上下文,最終執(zhí)行目標(biāo)方法。
實(shí)現(xiàn)機(jī)制
- 責(zé)任鏈模式
Provider端Filter鏈在服務(wù)暴露時(shí)通過FilterChainBuilder#buildInvokerChain方法構(gòu)建,基于SPI配置按優(yōu)先級(jí)排序,形成多層攔截邏輯。
- SPI加載規(guī)則
Filter實(shí)現(xiàn)類需要在META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter文件中聲明,并通過@Activate注解配置激活條件(如服務(wù)端/消費(fèi)端)
- 動(dòng)態(tài)加載
Filter鏈在服務(wù)初始化階段動(dòng)態(tài)生成,通過ExtensionLoader加載所有激活的Filter實(shí)例,并按順序包裝成調(diào)用鏈。
常見內(nèi)置Filter實(shí)現(xiàn)
| Filter名稱 | 功能描述 | 適用端 |
|---|---|---|
| ExceptionFilter | 統(tǒng)一處理服務(wù)端異常,將非受檢異常封裝為RuntimeException返回客戶端 | Provider |
| ValidationFilter | 基于JSR303標(biāo)準(zhǔn)校驗(yàn)接口參數(shù)合法性 | Both |
| AccessLogFilter | 記錄服務(wù)調(diào)用日志,指定輸出到指定文件 | Provider |
| TimeoutFilter | 監(jiān)控方法執(zhí)行超時(shí),觸發(fā)超時(shí)中斷邏輯 | Provider |
| GenericFilter | 處理泛化調(diào)用的序列化與反序列化 | Both |
自定義Filter實(shí)現(xiàn)步驟
- 實(shí)現(xiàn)Filter接口
import com.alibaba.fastjson2.JSON;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 使用@Activate注解指定Filter生效場(chǎng)景
// order屬性控制執(zhí)行順序,值越小,優(yōu)先級(jí)越高
@Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER}, order = 10001)
public class CustomFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(CustomFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
logger.info("invoker invoked method {} {} {} {}",
invocation.getMethodName(),
JSON.toJSONString(invocation.getObjectAttachments()),
invocation.getAttributes(),
JSON.toJSONString(invocation.getArguments()));
Result result = invoker.invoke(invocation);
logger.info("invoker invoked result {}", JSON.toJSONString(result));
return result;
}
}
- 聲明SPI擴(kuò)展
在resources/META-INF/dubbo目錄下創(chuàng)建配置文件org.apache.dubbo.rpc.Filter,添加自定義Filter類路徑:
consumer=com.doudou.demo.filter.CustomFilter
ValidationFilter
Dubbo的ValidationFilter是基于JSR303標(biāo)準(zhǔn)實(shí)現(xiàn)的參數(shù)校驗(yàn)組件,主要用于服務(wù)消費(fèi)者和服務(wù)提供者兩端,確保接口調(diào)用時(shí)參數(shù)的合法性。
核心特性
作用機(jī)制
- 通過
@Activate注解激活,默認(rèn)作用于消費(fèi)者和服務(wù)者兩端,執(zhí)行順序?yàn)?0000。 - 在請(qǐng)求處理前攔截參數(shù),利用JSR303標(biāo)準(zhǔn)的注解進(jìn)行校驗(yàn),校驗(yàn)失敗時(shí)拋出異常中斷流程。
依賴配置
需要引入validation-api和hibernate-validator依賴包
<!-- Bean Validation API -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- Hibernate Validator實(shí)現(xiàn) -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId> <!-- 適配EL表達(dá)式 -->
<version>5.0.0-M1</version>
</dependency>
使用
API
@Setter
@Getter
public class UserDTO implements Serializable {
@NotBlank(message = "用戶名不能為空")
private String username;
@Min(value = 18, message = "年齡必須大于18歲")
private Integer age;
@Email(message = "郵箱格式不合法")
private String email;
}
public class BaseResult<T> {
// 處理是否正確結(jié)束
private boolean success;
// 異常編碼
private Integer errorCode;
// 異常描述
private String errorMsg;
// dubbo接口返回的結(jié)果
private T data;
}
public interface UserService {
BaseResult<String> registerUser(UserDTO userDTO);
}
服務(wù)提供者
@DubboService(validation = "true")
public class UserServiceImpl implements UserService {
@Override
public BaseResult<String> registerUser(UserDTO userDTO) {
return BaseResult.success("用戶注冊(cè)成功:" + userDTO.getUsername());
}
}
public class ParameterVerificationResultFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(ParameterVerificationResultFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result = invoker.invoke(invocation);
// 處理出現(xiàn)異常
if (result.hasException()) {
Throwable exception = result.getException();
// 是由參數(shù)校驗(yàn)失敗拋出的異常
if (exception instanceof ConstraintViolationException) {
List<String> errors = ((ConstraintViolationException) exception).getConstraintViolations().stream()
.map(v -> v.getPropertyPath() + ": " + v.getMessage())
.collect(Collectors.toList());
logger.info("---------------2---------------");
logger.error(errors.toString());
logger.info("---------------3---------------");
// 將錯(cuò)誤信息封裝到返回結(jié)果中
return AsyncRpcResult.newDefaultAsyncResult(BaseResult.fail(400, errors.toString()), invocation);
}
}
return result;
}
}
META-INF/dubbo/org.apache.dubbo.rpc.Filter
# 參數(shù)校驗(yàn)過濾器 validation=org.apache.dubbo.validation.filter.ValidationFilter # 校驗(yàn)結(jié)果處理過濾器 parameterVerification=com.doudou.filter.ParameterVerificationResultFilter
application.yml
dubbo: provider: filter: validation,parameterVerification
服務(wù)消費(fèi)方
@RestController
public class UserServiceController {
@DubboReference(validation = "true")
private UserService userService;
@PostMapping("/test")
public BaseResult<String> test(@RequestBody UserDTO userDTO) {
return userService.registerUser(userDTO);
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
// 處理RpcException異常
@ExceptionHandler(RpcException.class)
public ResponseEntity<BaseResult> handleValidationException(RpcException rpcException) {
return ResponseEntity.badRequest().body(BaseResult.fail(403, rpcException.getLocalizedMessage()));
}
}
源碼解析
org.apache.dubbo.validation.filter.ValidationFilter
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 判斷是否需要進(jìn)行參數(shù)驗(yàn)證
if (needValidate(invoker.getUrl(), invocation.getMethodName())) {
try {
// 通過url中的validation屬性值獲取驗(yàn)證器
Validator validator = validation.getValidator(invoker.getUrl());
if (validator != null) {
// 獲取到驗(yàn)證器時(shí),進(jìn)行參數(shù)驗(yàn)證
validator.validate(
invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
}
} catch (RpcException e) {
// RpcException 異常直接拋出
throw e;
} catch (Throwable t) {
// 非RpcException,封裝到結(jié)果中返回
return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
}
}
// 執(zhí)行下一個(gè)過濾器的處理
return invoker.invoke(invocation);
}
/**
* 是否需要進(jìn)行參數(shù)驗(yàn)證
*/
private boolean needValidate(URL url, String methodName) {
return validation != null
&& !methodName.startsWith("$")
&& ConfigUtils.isNotEmpty(url.getMethodParameter(methodName, VALIDATION_KEY))
&& !"false".equalsIgnoreCase(url.getParameter(VALIDATION_KEY));
}
org.apache.dubbo.validation.support.AbstractValidation
@Override
public Validator getValidator(URL url) {
// 使用url作為存儲(chǔ)驗(yàn)證器map集合的的key
String key = url.toFullString();
// 從容器中獲取驗(yàn)證器
Validator validator = validators.get(key);
// 判斷驗(yàn)證器是否已經(jīng)存在
if (validator == null) {
// 如果不存在,則創(chuàng)建
validators.put(key, createValidator(url));
validator = validators.get(key);
}
return validator;
}
org.apache.dubbo.validation.support.jvalidation.JValidation
@Activate(onClass = "javax.validation.Validation")
public class JValidation extends AbstractValidation {
/**
* Return new instance of {@link JValidator}
* @param url Valid URL instance
* @return Instance of JValidator
*/
@Override
protected Validator createValidator(URL url) {
// 創(chuàng)建一個(gè)Dubbo框架默認(rèn)的校驗(yàn)器
return new JValidator(url);
}
}
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java JSch遠(yuǎn)程執(zhí)行Shell命令的方法
本文主要介紹了Java JSch遠(yuǎn)程執(zhí)行Shell命令,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
SpringBoot修改內(nèi)置tomcat版本的操作步驟
生產(chǎn)環(huán)境使用的外部部署Tomcat還是內(nèi)置Tomcat由于版本安全漏洞,往往需要升級(jí)到指定的安全版本,本文演示一下SpringBoot升級(jí)內(nèi)置的Tomcat版本,感興趣的小伙伴跟著小編一起來看看吧2024-07-07
java根據(jù)當(dāng)前時(shí)間獲取yyyy-MM-dd?HH:mm:ss標(biāo)準(zhǔn)格式的時(shí)間代碼示例
在Java中可以使用java.time包中的LocalDateTime類和DateTimeFormatter類來獲取并格式化當(dāng)前時(shí)間為yyyy-MM-dd?HH:mm:ss的格式,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10
使用IntelliJ IDEA調(diào)式Stream流的方法步驟
本文主要介紹了使用IntelliJ IDEA調(diào)式Stream流的方法步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
關(guān)于Android觸摸事件分發(fā)的原理詳析
觸摸事件分發(fā)機(jī)制一直以來都是Android中比較重要的一大塊,自定義view,各種復(fù)雜的自定義手勢(shì)交互都與觸摸事件分發(fā)機(jī)制關(guān)系密,下面這篇文章主要給大家介紹了關(guān)于Android觸摸事件分發(fā)原理的相關(guān)資料,需要的朋友可以參考下2022-01-01
SpringBoot整合kafka遇到的版本不對(duì)應(yīng)問題及解決
這篇文章主要介紹了SpringBoot整合kafka遇到的版本不對(duì)應(yīng)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Flink流處理引擎零基礎(chǔ)速通之?dāng)?shù)據(jù)的抽取篇
今天不分享基礎(chǔ)概念知識(shí)了,來分享一個(gè)馬上工作需要的場(chǎng)景,要做數(shù)據(jù)的抽取,不用kettle,想用flink。實(shí)際就是flink的sql、table層級(jí)的api2022-05-05

