SpringBoot2.0不容错过的新特性 WebFlux响应式编程【第6章】webflux客户端声明式restclient框架开发讲解

6-1 框架效果介绍

当我们调用远程服务器接口,只需要定义一个接口,把需要调用的服务器接口信息用注解配置在接口上,在定义的抽象方法中也需要用注解绑定方法。

@PersonAPI(url = "http://localhost/person")
public interface IPersonAPI {

    @GetMapping("/all")
    Flux<Person> findAllPerson();
}

调用IPersonAPIfindAllPerson()方法就相当于调用了远程服务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        : 没有找到人呐
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值