SpringBoot WebFlux实践

420 阅读3分钟

1、导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

2、编写接口测试webflux与传统servlet例子并通过日志打印的时间来测试两者区别

@Slf4j
@RestController
public class TestController {

    
    @GetMapping("/webflux")
    public Mono<String> webflux(){
        log.info("webflux start");
        Mono<String> res = Mono.fromSupplier(this::createStr);
        log.info("webflux end");
        return res;
    }



    @GetMapping("/servlet")
    public String servlet(){
        log.info("servlet start");
        String res = createStr();
        log.info("servlet end");
        return res;
    }

    @SneakyThrows
    private String createStr() {
        TimeUnit.SECONDS.sleep(5);
        return "hello,world";
    }
}

3、Mono与Flux的区别

Flux表示的是包含0-N个元素的异步序列。Mono表示的是包含0-1个元素的异步序列,可通过修改produces属性来支持SSE服务器推送,具体如以下例子所示:

/**
 * Mono 返回0-1个元素
 * @return
 */
@GetMapping("/webflux")
public Mono<String> webflux(){
    log.info("webflux start");
    Mono<String> res = Mono.fromSupplier(this::createStr);
    log.info("webflux end");
    return res;
}
/**
 * Flux 返回0-n个元素
 * @return
 */
@GetMapping(value = "/webflux2",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> webflux2(){
    return Flux.fromStream(IntStream.range(1,10).mapToObj(i->{
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Flux dat-"+i;
    }));
}

4、使用WebClient编写远程接口调用模拟OpenFeign框架

4.1、编写调用注解及公共API接口

@Target(ElementType.TYPE)//作用范围:类、接口、 枚举
@Retention(RetentionPolicy.RUNTIME)//程序运行时获得
@Documented
public @interface ApiServer {

    String value() default "";
}

@ApiServer("localhost:8080/webflux")
public interface CommonApi {

    @DeleteMapping("/{id}")
    Mono<Void> delete(@PathVariable("id") Long id);

    @PutMapping("/update")
    Mono<User> update(@RequestBody Mono<User> user);

    @PostMapping("/add")
    Mono<User> add(@RequestBody Mono<User> user);

    @GetMapping("/{id}")
    Mono<User> getUser(@PathVariable("id") Long id);

    @GetMapping("/get-all")
    Flux<User> getAllUser();
}


@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Long id;
    private String name;
    private Integer age;
}

4.2、创建公共接口调用工厂、调用的服务器数据结构、调用的方法信息数据结构

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ServerInfo {

    private String url;

}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MethodInfo {

    //请求url
    private String url;
    //请求的方法
    private HttpMethod method;
    //请求方法参数
    private Map<String, Object> params;
    //请求body
    private Mono<?> body;
    //是否Flux类型返回
    private Boolean isFlux;
    //返回的对象类型
    private Class<?> returnObjectType;
}

@Component
public class CommonApiFactory implements FactoryBean<CommonApi> {

    @Autowired
    private ProxyCreator proxyCreator;

    public CommonApiFactory(ProxyCreator proxyCreator){
        this.proxyCreator=proxyCreator;
    }

    @Override
    public CommonApi getObject() throws Exception {
        return (CommonApi) proxyCreator.createProxy(this.getObjectType());
    }

    @Override
    public Class<?> getObjectType() {
        return CommonApi.class;
    }

}

4.3、编写请求处理器及实现

public interface RestHandler {

    /**
     * 调用rest请求并返回结果
     * @param methodInfo
     * @return
     */
    Object invokeRest(MethodInfo methodInfo);

    /**
     * 初始化服务器信息
     * @param serverInfo
     */
    void init(ServerInfo serverInfo);
}

public class WebClientRestHandler implements RestHandler {

    private WebClient webClient;

    /**
     * 初始化webClient
     *
     * @param serverInfo
     */
    @Override
    public void init(ServerInfo serverInfo) {
        this.webClient = WebClient.create(serverInfo.getUrl());
    }

    /**
     * @param methodInfo
     * @return
     */
    @Override
    public Object invokeRest(MethodInfo methodInfo) {
        //响应数据
        Object res=null;
        ResponseSpec request = this.webClient.method(methodInfo.getMethod())
                .uri(methodInfo.getUrl())
                //接收类型
                .accept(MediaType.APPLICATION_JSON)
                //发起请求
                .retrieve();
        //判断body类型
        if (methodInfo.getIsFlux()) {
            res= request.bodyToFlux(methodInfo.getReturnObjectType());
        } else {
            res= request.bodyToMono(methodInfo.getReturnObjectType());
        }

        return res;
    }
}

4.4、创建Jdk动态代理初始化服务器信息和调用的方法信息

public interface ProxyCreator {

    Object createProxy(Class<?> type);

}

@Slf4j
@Component
public class JdkProxyCreator implements ProxyCreator {

    @Override
    public Object createProxy(Class<?> type) {
        log.info("createProxy:" + type);
        //根据接口得到API服务器信息
        ServerInfo serverInfo = getServerInfo(type);
        log.info("serverInfo:" + serverInfo);
        RestHandler handler = new WebClientRestHandler();

        //初始化服务器信息、webClient
        handler.init(serverInfo);
        /**
         * 参数1:类加载器
         * 参数2:代理的接口
         * 参数3:自定义的代理方法
         */
        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 = getMethodInfo(method, args);
                log.info("methodInfo:" + methodInfo);
                //调用rest
                return handler.invokeRest(methodInfo);
            }
        });
    }

    private MethodInfo getMethodInfo(Method method, Object[] args) {
        MethodInfo methodInfo = new MethodInfo();
        //得到请求url和请求方法
        getUrlAndMethod(method, methodInfo);
        //得到调用的参数和body
        getParamsAndBody(method, args, methodInfo);
        //得到返回的对象信息
        getReturnInfo(method, methodInfo);
        return methodInfo;
    }

    private void getReturnInfo(Method method, MethodInfo methodInfo) {
        //判断返回的是flux还是mono
        methodInfo.setIsFlux(method.getReturnType().isAssignableFrom(Flux.class));
        //得到返回对象类型(泛型)
        methodInfo.setReturnObjectType(getObjectType(method.getGenericReturnType()));
    }

    private Class<?> getObjectType(Type genericReturnType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        return (Class<?>) actualTypeArguments[0];
    }

    private void getParamsAndBody(Method method, Object[] args, MethodInfo methodInfo) {
        Map<String, Object> params = new LinkedHashMap<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //参数是否有@PathVariable
            PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                params.put(pathVariable.value(), args[i]);
            }
            //参数是否有@RequestBody
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                methodInfo.setBody((Mono<?>) args[i]);
            }
        }
        methodInfo.setParams(params);
    }

    private void getUrlAndMethod(Method method, MethodInfo methodInfo) {
        for (Annotation annotation : method.getAnnotations()) {
            //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);
            } else {
                log.error("未知的请求类型:{}", annotation.annotationType());
            }
        }
    }

    private ServerInfo getServerInfo(Class<?> type) {
        ApiServer anno = type.getAnnotation(ApiServer.class);
        ServerInfo serverInfo = ServerInfo.builder().url(anno.value()).build();
        return serverInfo;
    }
}

5.测试

@GetMapping("/")
public void test() {
    //测试信息获取是否正确
    Mono<User> add = commonApi.add(Mono.just(User.builder().id(1L).name("123").age(18).build()));
    Mono<User> user = commonApi.getUser(1L);
    Flux<User> allUser = commonApi.getAllUser();
    Mono<User> update = commonApi.update(Mono.just(User.builder().id(2L).name("123").age(18).build()));
    Mono<Void> delete = commonApi.delete(1L);
}