Spring Reactive响应式编程-WebClient框架开发
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 数据已保存