WebClient是從Spring WebFlux 5.0版本開始提供的一個非阻塞的基於響應式編程的進行Http請求的客戶端工具。它的響應式編程的基於Reactor的。WebClient中提供了標准Http請求方式對應的get、post、put、delete等方法,可以用來發起相應的請求。
所以這節內容是基於WebClient自己編寫一個類似於Feign或者Retrofit的框架
設計思路
框架搭建
定義服務器注解
/**
* 服務器相關的信息
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiServer {
String value() default "";
}
定義訪問對象
/**
* 類名可以隨意,字段需要保持一致
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String name;
private int age;
}
定義遠程訪問接口
@ApiServer("http://localhost:9090/user")
public interface IUserApi {
@GetMapping("/")
Flux<User> getAllUser();
@GetMapping("/{id}")
Mono<User> getUserById(@PathVariable("id") String id);
@DeleteMapping("/{id}")
Mono<Void> deleteUserById(@PathVariable("id") String id);
@PostMapping("/")
Mono<User> createUser(@RequestBody Mono<User> user);
}
創建代理類接口
我們使用的是JDK動態代理 通過定義接口解耦 后面可以自定義修改為使用cglib動態代理
/**
* 創建代理類接口
*/
public interface ProxyCreator {
/**
* 創建代理類
* @param type
* @return
*/
Object createProxy(Class<?> type);
}
創建遠程請求實例接口
我們通過 WebClient遠程訪問 可以自己改造為RestTemplete訪問
/**
* rest請求調用handler
*/
public interface RestHandler {
/**
* 初始化服務器信息
* @param serverInfo
*/
void init(ServerInfo serverInfo);
/**
* 調用rest請求,返回結果
* @param methodInfo
* @return
*/
Object invokeRest(MethodInfo methodInfo);
}
對應的實體類
服務器信息
/**
* 服務器信息類
*/
@Data
@Builder
// Data和Builder共同使用時必須手動添加無參和有參構造
@NoArgsConstructor
@AllArgsConstructor
public class ServerInfo {
/**
* 服務器url
*/
private String url;
}
方法調用信息
/**
* 方法調用信息類
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MethodInfo {
/**
* 請求url
*/
private String url;
/**
* 請求方法
*/
private HttpMethod method;
/**
* 請求參數(url)
*/
private Map<String, Object> params;
/**
* 請求body
*/
private Mono<?> body;
/**
* 請求body的類型
*/
private Class<?> bodyElementType;
/**
* 返回是flux還是mono
*/
private boolean returnFlux;
/**
* 返回對象的類型
*/
private Class<?> returnElementType;
}
創建遠程實例實現類
public class WebClientRestHandler implements RestHandler {
private WebClient client;
/**
* 初始化webclient
* @param serverInfo
*/
@Override
public void init(ServerInfo serverInfo) {
this.client = WebClient.create(serverInfo.getUrl());
}
/**
* 處理rest
* @param methodInfo
* @return
*/
@Override
public Object invokeRest(MethodInfo methodInfo) {
// 返回結果
Object result = null;
WebClient.RequestBodySpec request = this.client
// 請求方法
.method(methodInfo.getMethod())
// 請求url
.uri(methodInfo.getUrl(), methodInfo.getParams())
// 接收類型
.accept(MediaType.APPLICATION_JSON);
WebClient.ResponseSpec retrieve = null;
// 判斷是否帶了body
if (methodInfo.getBody() != null) {
// 發出請求
retrieve = request.body(methodInfo.getBody(), methodInfo.getBodyElementType()).retrieve();
} else {
retrieve = request.retrieve();
}
// 處理異常
retrieve.onStatus(status -> status.value() == 404,
response -> Mono.just(new RuntimeException("Not Found")));
// 處理body
if (methodInfo.isReturnFlux()) {
result = retrieve.bodyToFlux(methodInfo.getReturnElementType());
} else {
result = retrieve.bodyToMono(methodInfo.getReturnElementType());
}
return result;
}
}
創建動態代理實現類
@Slf4j
public class JDKProxyCreator implements ProxyCreator {
@Override
public Object createProxy(Class<?> type) {
log.info("createProxy: {}", type);
// 根據接口得到API服務器
ServerInfo serverInfo = extractServerInfo(type);
log.info("serverInfo: {}", serverInfo);
// 給每一個代理類創建一個實例
RestHandler handler = new WebClientRestHandler();
// 初始化服務器信息(初始化webclient)
handler.init(serverInfo);
return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 根據方法和參數得到調用信息
MethodInfo methodInfo = extractMethodInfo(method, args);
log.info("methodInfo: {}", methodInfo);
// 調用rest
return handler.invokeRest(methodInfo);
}
});
}
/**
* 根據方法定義和調用參數得到調用的相關信息
*
* @param method
* @param args
* @return
*/
private MethodInfo extractMethodInfo(Method method, Object[] args) {
MethodInfo methodInfo = new MethodInfo();
extractUrlAndMethod(method, methodInfo);
extractRequestParamAndBody(method, args, methodInfo);
// 提取返回對象的信息
extractReturnInfo(method, methodInfo);
return methodInfo;
}
/**
* 提取返回對象信息
*
* @param method
* @param methodInfo
*/
private void extractReturnInfo(Method method, MethodInfo methodInfo) {
// 返回flux還是mono
// isAssignableFrom 判斷類型是否是某個類的子類
// instanceof 判斷實例是否是某個類的子類
boolean isFlux = method.getReturnType().isAssignableFrom(Flux.class);
methodInfo.setReturnFlux(isFlux);
// 得到返回對象的實際類型
Class<?> elementType = extractElementType(method.getGenericReturnType());
methodInfo.setReturnElementType(elementType);
}
/**
* 得到反省類型的實際類型
*
* @param genericReturnType
* @return
*/
private Class<?> extractElementType(Type genericReturnType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
return (Class<?>) actualTypeArguments[0];
}
/**
* 得到請求的param和body
*
* @param method
* @param args
* @param methodInfo
*/
private void extractRequestParamAndBody(Method method, Object[] args, MethodInfo methodInfo) {
// 得到調用的參數和body
Parameter[] parameters = method.getParameters();
// 參數和值對應的map
Map<String, Object> params = new LinkedHashMap<>();
for (int i = 0; i < parameters.length; i++) {
// 是否帶 @PathVariable注解
PathVariable annoPath = parameters[i].getAnnotation(PathVariable.class);
if (annoPath != null) {
params.put(annoPath.value(), args[i]);
}
// 是否帶了 RequestBody
RequestBody annoBody = parameters[i]
.getAnnotation(RequestBody.class);
if (annoBody != null) {
methodInfo.setBody((Mono<?>) args[i]);
// 請求對象的實際類型
methodInfo.setBodyElementType(
extractElementType(parameters[i].getParameterizedType()));
}
}
methodInfo.setParams(params);
}
/**
* 得到請求的url和方法
*
* @param method
* @param methodInfo
*/
private void extractUrlAndMethod(Method method, MethodInfo methodInfo) {
// 得到請求URL和請求方法
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
// GET
if (annotation instanceof GetMapping) {
GetMapping a = (GetMapping) annotation;
methodInfo.setUrl(a.value()[0]);
methodInfo.setMethod(HttpMethod.GET);
}
// POST
else if (annotation instanceof PostMapping) {
PostMapping a = (PostMapping) annotation;
methodInfo.setUrl(a.value()[0]);
methodInfo.setMethod(HttpMethod.POST);
}
// DELETE
else if (annotation instanceof DeleteMapping) {
DeleteMapping a = (DeleteMapping) annotation;
methodInfo.setUrl(a.value()[0]);
methodInfo.setMethod(HttpMethod.DELETE);
}
// PUT
else if (annotation instanceof PutMapping) {
PutMapping a = (PutMapping) annotation;
methodInfo.setUrl(a.value()[0]);
methodInfo.setMethod(HttpMethod.PUT);
}
}
}
/**
* 提取服務器信息
*
* @param type
* @return
*/
private ServerInfo extractServerInfo(Class<?> type) {
ServerInfo serverInfo = new ServerInfo();
ApiServer annotation = type.getAnnotation(ApiServer.class);
serverInfo.setUrl(annotation.value());
return serverInfo;
}
}
啟動類注入Bean
注入動態代理對象
/**
* 創建jdk動態代理對象
* @return
*/
@Bean
ProxyCreator jdkProxyCreator(){
return new JDKProxyCreator();
}
構造IUserApi Bean工廠
@Bean
FactoryBean<IUserApi> userApi(ProxyCreator proxyCreator){
return new FactoryBean<IUserApi>(){
/**
* 返回代理對象
* @return
*/
@Override
public IUserApi getObject() throws Exception {
return (IUserApi) proxyCreator.createProxy(this.getObjectType());
}
@Override
public Class<?> getObjectType() {
return IUserApi.class;
}
};
}
測試
啟動springboot-webflux項目、springboot-webclient項目
@GetMapping("/{id}")
public void testFindAndDeleteAndCreate(@PathVariable("id") String id) {
// 創建用戶
userApi.createUser(
Mono.just(User.builder().name("kaka").age(33).build()))
.subscribe(System.out::println);
}
查看MongoDb 數據已保存