1. 程式人生 > 其它 >Spring Reactive響應式程式設計-WebClient框架開發

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);
    }

訪問:http://localhost:8080/1

檢視MongoDb 資料已儲存

原始碼下載地址:https://gitee.com/javaming/springboot-webclient