Spring Reactive响应式编程-WebClient框架开发

WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。

所以这节内容是基于WebClient自己编写一个类似于Feign或者Retrofit的框架

设计思路

image

框架搭建

定义服务器注解

/** * 服务器相关的信息 */ @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); }

访问:http://localhost:8080/1
image

查看MongoDb 数据已保存
image

源码下载地址:https://gitee.com/javaming/springboot-webclient


__EOF__

本文作者mpy
本文链接https://www.cnblogs.com/mpyidudu/p/15504683.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   狻猊的主人  阅读(992)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
点击右上角即可分享
微信分享提示