6-1 框架效果介绍
当我们调用远程服务器接口,只需要定义一个接口,把需要调用的服务器接口信息用注解配置在接口上,在定义的抽象方法中也需要用注解绑定方法。
@PersonAPI(url = "http://localhost/person")
public interface IPersonAPI {
@GetMapping("/all")
Flux<Person> findAllPerson();
}
调用
IPersonAPI
的findAllPerson()
方法就相当于调用了远程服务http://localhost/person/all
6-2 设计思路
开发WebClient框架
6-3 添加用户实体类
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String personId;
private String personName;
private String gender;
private Integer age;
}
6-4 定义接口
- 获取所有用户
- 根据id查询用户信息
- 新增用户
- 根据id删除用户
@PersonAPI(url = "http://localhost/person")
public interface IPersonAPI {
@GetMapping("/all")
Flux<Person> findAllPerson();
@GetMapping("/id")
Mono<Person> findByPersonId(@RequestParam String personId);
@PostMapping
Mono<Person> create(@RequestBody Mono<Person> person);
@DeleteMapping
Mono<Void> deletePerson(@RequestParam String personId);
}
增加注解类
用来设置请求远程服务的url;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonAPI {
String url() default "";
}
6-5 请求远程服务信息实体类
用来存放请求远程服务的信息,比如URL、请求方法、请求参数类型、请求参数、请求体
;
import lombok.Data;
import org.springframework.http.HttpMethod;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Mono;
import java.util.Map;
@Data
public class RequestMethodInfo {
/**
* 请求URL
* */
private String detailUrl;
/**
* 请求方法类型
* */
private HttpMethod requestMethod;
/**
* 请求参数
* */
private Map<String,Object> requestParam;
/**
* @RequestParam 的参数
* */
private MultiValueMap<String,String> queryParam;
/**
* 请求体
* */
private Mono requestBody;
/**
* 请求体类型
* */
private Class<?> requestType;
/**
*判断返回类型是Mono还是Flux,如果是Flux isFluxFlag=true,否则为false
* */
private boolean isFluxFlag;
/**
* 返回类型
* */
private Class<?> returnType;
}
服务器信息实体类
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ServerInfo {
//远程URL信息
private String remoteUrl;
}
6-6 容器注册
将
IPersonAPI
注册到FactoryBean
,使用JDK
代理
@SpringBootApplication
public class WebfluxclientApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxclientApplication.class, args);
}
@Bean
ProxyCreator jdkProxyCreat(){
return new JDKProxyCreator();
}
@Bean
FactoryBean<IPersonAPI> createIPersonAPI(ProxyCreator proxyCreator){
return new FactoryBean<IPersonAPI>() {
/**
* 返回代理对象
* */
@Override
public IPersonAPI getObject() throws Exception {
return (IPersonAPI) proxyCreator.createProxy(this.getObjectType());
}
@Override
public Class<?> getObjectType() {
return IPersonAPI.class;
}
};
}
}
创建代理类接口
public interface ProxyCreator {
Object createProxy(Class<?> type);
}
使用JDK动态代理实现代理类
- 得到
IPersonAPI.java
各方法的请求方法、请求参数、请求url
等信息;- 根据服务器信息、方法信息调用
Rest
获取到结果。
@Slf4j
public class JDKProxyCreator implements ProxyCreator {
@Override
public Object createProxy(Class<?> type) {
log.info("JDKProxyCreator createProxy type" +type);
//根据接口得到API服务信息
ServerInfo serverInfo = extractServerInfo(type);
//给每个代理类一个实现
RestHandler restHandler = new WebClientRestHandler();
//初始化服务器信息(初始化webClient)
restHandler.init(serverInfo);
log.info("serverInfo " + serverInfo);
return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//根据方法和参数得到调用信息
RequestMethodInfo methodInfo = extractMethodInfo( method,args);
log.info("methodInfo " + methodInfo);
//调用Rest
return restHandler.invokeRest( methodInfo);
}
/**根据方法定义和调用参数得到调用的相关信息*/
private RequestMethodInfo extractMethodInfo(Method method, Object[] args) {
Annotation [] annotations = method.getAnnotations();
RequestMethodInfo requestMethodInfo = new RequestMethodInfo();
for (Annotation annotation : annotations) {
String requestDetailUrl = null;
HttpMethod httpMethod = null;
if(annotation instanceof GetMapping){
/**
* 不应该使用((GetMapping) annotation).value()[0],假如在方法上使用的是
* @GetMapping 而不是 @GetMapping("/"),就会导致数组越界报空指针。
* */
requestDetailUrl = Stream.of(((GetMapping) annotation).value())
.filter(e-> e != null&&!"".equals(e))
.findAny()
.orElse("");
httpMethod = HttpMethod.GET;
}else if(annotation instanceof DeleteMapping){
requestDetailUrl = Stream.of(((DeleteMapping) annotation).value())
.filter(e-> e != null&&!"".equals(e))
.findAny()
.orElse("");
httpMethod = HttpMethod.DELETE;
}else if(annotation instanceof PostMapping){
requestDetailUrl = Stream.of(((PostMapping) annotation).value())
.filter(e-> e != null&&!"".equals(e))
.findAny()
.orElse("");
httpMethod = HttpMethod.POST;
}
requestMethodInfo.setDetailUrl(requestDetailUrl);
requestMethodInfo.setRequestMethod(httpMethod);
}
Parameter[] parameters = method.getParameters();
Map<String,Object> requestParam = new HashMap<>();
MultiValueMap<String,String> multiValueMap = new LinkedMultiValueMap();
for (int i = 0; i < parameters.length; i++) {
RequestParam annotation = parameters[i].getAnnotation(RequestParam.class);
if(annotation != null){
String paramName = Optional.ofNullable(annotation.value()).filter(s -> s != null && !"".equals(s)).orElse(parameters[i].getName());
requestParam.put(paramName,args[i]);
multiValueMap.add(paramName, (String)args[i]);
}
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
if(requestBody != null){
requestMethodInfo.setRequestBody((Mono<?>) args[i]);
Type[] actualTypeArguments = ((ParameterizedType) parameters[i].getParameterizedType()).getActualTypeArguments();
requestMethodInfo.setRequestType((Class<?>) actualTypeArguments[0]);
}
}
requestMethodInfo.setRequestParam(requestParam);
requestMethodInfo.setQueryParam(multiValueMap);
//判断返回类型是Mono还是Flux,如果是Mono isMonoFlag=true,否则为false
//isAssignableFrom:判断类型是否是某个类的子类
//instanceof :判断实例是否是某个类的子类
boolean assignableFrom = method.getReturnType().isAssignableFrom(Flux.class);
requestMethodInfo.setFluxFlag(assignableFrom);
//获取返回类型
//获取实际类型: getActualTypeArguments
//java.lang.reflect.Method.getGenericReturnType()方法返回一个Type对象,该对象表示此Method对象表示的方法的正式返回类型
//https://www.yiibai.com/javareflect/javareflect_method_getgenericreturntype.html
//从一个泛型类型中获取第一个泛型参数的类型类
Type[] aClass = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments();
//设置返回类型
requestMethodInfo.setReturnType((Class<?>)aClass[0]);
return requestMethodInfo;
}
});
}
private ServerInfo extractServerInfo(Class<?> type) {
//PersonAPI personAPI = type.getAnnotation(PersonAPI.class);
Annotation [] annotations = type.getAnnotations();
for (Annotation annotation : annotations) {
if(annotation instanceof PersonAPI){
PersonAPI personAPI = (PersonAPI) annotation;
return ServerInfo.builder().remoteUrl(personAPI.url()).build();
}
}
return null;
}
}
6-7 使用Rest实现远程调用
有可能需要使用别的调用方式,比如
RestTemplate
,所以最好定义接口,然后调用需要的实现类。
/**
* rest请求handler
* */
public interface RestHandler {
/**初始化服务器信息*/
void init(ServerInfo serverInfo);
/**调用Rest请求,返回接口*/
Object invokeRest(RequestMethodInfo methodInfo);
}
Handler实现类
public class WebClientRestHandler implements RestHandler {
private WebClient webClient;
@Override
public void init(ServerInfo serverInfo) {
//设置请求Url
this.webClient = WebClient.create(serverInfo.getRemoteUrl());
}
@Override
public Object invokeRest(RequestMethodInfo methodInfo) {
WebClient.RequestBodyUriSpec method = this.webClient.method(methodInfo.getRequestMethod());
WebClient.RequestBodySpec requestBodySpec = this.webClient
.method(methodInfo.getRequestMethod())
.uri(methodInfo.getDetailUrl(),methodInfo.getRequestParam())
//.body(methodInfo.getRequestParam(), )
.accept(MediaType.APPLICATION_JSON);
WebClient.ResponseSpec retrieve = null;
//https://stackoverflow.com/questions/48828603/how-to-create-request-with-parameters-with-webflux-webclient
if(methodInfo.getRequestBody() != null){
retrieve = method.uri(methodInfo.getDetailUrl())
.accept(MediaType.APPLICATION_JSON)
.body(methodInfo.getRequestBody(), methodInfo.getRequestType())
.retrieve();
}else{
//MultiValueMap<String,String> requestParam = methodInfo.getRequestParam();
//requestParam.forEach((x,y) ->{});
MultiValueMap<String,String> multiValueMap = new LinkedMultiValueMap();
//retrieve =
retrieve = method.uri(uriBuilder ->
uriBuilder.path(methodInfo.getDetailUrl()).queryParams(methodInfo.getQueryParam())
.build()).retrieve();
//retrieve = requestBodySpec.retrieve();
//retrieve = requestBodySpec.bodyValue(methodInfo.getRequestParam()).retrieve();
}
//设置请求方法类型
/*WebClient.ResponseSpec retrieve = this.webClient
.method(methodInfo.getRequestMethod())
.uri(methodInfo.getDetailUrl())
//.body(methodInfo.getRequestParam(), )
.contentType(MediaType.APPLICATION_JSON)
.body(methodInfo.getRequestBody(),methodInfo.getReturnType())
.retrieve();*/
retrieve.onStatus(
httpStatus ->{
System.out.println("httpStatus:\t" + httpStatus.value());
return HttpStatus.NOT_FOUND.value() == httpStatus.value();
}
,
err -> {
System.out.println(err);
return Mono.just(new RuntimeException("没有查询到"));
});
Object object = null;
//处理Response
if(methodInfo.isFluxFlag()){
object =retrieve.bodyToFlux(methodInfo.getReturnType());
}else{
object = retrieve.bodyToMono(methodInfo.getReturnType());
}
return object;
}
}
6-8 远程服务调用测试
- 不订阅不会实际发出请求,但是会进入代理类。
- 需要操作数据并返回
Mono
时可以使用flatMap
。
import com.whaleson.webfluxclient.api.remote.IPersonAPI;
import com.whaleson.webfluxclient.entity.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/remote")
@Slf4j
public class RemotePersonAPIController {
@Autowired
private IPersonAPI personAPI;
/*
public RemotePersonAPIController(IPersonAPI personAPI) {
this.personAPI = personAPI;
}*/
@GetMapping("/all")
public void getAllUser(){
log.info("--------- getAllUser start");
//不调用远程服务,只查看获取方法信息是否正确;
/*personAPI.create(Mono.just(Person.builder().gender("男").age(18).personName("魏大勋").build()));
personAPI.deletePerson("1111");
personAPI.findByPersonId("222");*/
log.info("所有人员信息是:");
personAPI.findAllPerson().subscribe(System.out::println);
//personAPI.findAllPerson().subscribe(System.out::println);
log.info("--------- getAllUser end");
}
@GetMapping("/add2")
public void add2(){
log.info("添加个人信息2:");
personAPI.create(Mono.just(Person.builder().gender("男").age(18).personName("魏大勋").build()));
}
@GetMapping("/add")
public void add(){
//只有订阅后才能插入数据库
log.info("添加个人信息");
personAPI.create(Mono.just(Person.builder().gender("男").age(18).personName("魏大勋").build())).subscribe(System.out::println);
}
@GetMapping("/testId")
public void test1(@RequestParam String personId){
log.info("准备根据id查个人信息{}", personId);
personAPI.findByPersonId(personId).subscribe(person -> {
log.info("找到人了,是" + personId);
}, errorMsg ->{
log.info("没有找到人呐",errorMsg.getMessage());
});
/*log.info("准备根据id删除个人信息{}", personId);
personAPI.deletePerson(personId).subscribe();*/
}
}
查看mongodb服务器数据
使用postman添加一个用户
查看mongodb服务器数据
根据personId查询一个不存在的用户
查看服务器后台结果
2021-01-28 10:57:20.140 INFO 20456 --- [ctor-http-nio-3] c.w.w.c.RemotePersonAPIController : 准备根据id查个人信息6012253abb33a8466193dc02
2021-01-28 10:57:20.142 INFO 20456 --- [ctor-http-nio-3] w.framework.proxy.JDKProxyCreator : methodInfo RequestMethodInfo(detailUrl=/id, requestMethod=GET, requestParam={personId=6012253abb33a8466193dc02}, queryParam={personId=[6012253abb33a8466193dc02]}, requestBody=null, requestType=null, isFluxFlag=false, returnType=class com.whaleson.webfluxclient.entity.Person)
httpStatus: 404
org.springframework.web.reactive.function.client.DefaultClientResponse@37f4e818
2021-01-28 10:57:20.464 INFO 20456 --- [ctor-http-nio-5] c.w.w.c.RemotePersonAPIController : 没有找到人呐