美文网首页
自己动手实现简易版Feign

自己动手实现简易版Feign

作者: 请不要问我是谁 | 来源:发表于2022-02-05 14:17 被阅读0次

    Feign的功能

    主要功能是让开发人员只使用简单注解就能像调用一般RPC一样调用HTTP请求,在此基础上,框架扩展了原SpringMVC参数只能为一个类的功能。

    基本思路

    代理实现->bean的注册->多参数实现

    代码实现

    注解定义

    需要定义三个注解。
    第一个“HttpConsumer”,用于消费端注册服务。

    /**
     * 标注需要代理为http请求发送
     *
     * @author kun
     * @data 2022/1/15 17:50
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface HttpConsumer {
        /**
         * 域名
         *
         * @return  域名
         */
        String domain();
    
        /**
         * 端口,默认80
         *
         * @return  端口号
         */
        String port() default "80";
    }
    

    第二个“HttpRequest”,用于服务提供方定义服务。

    /**
     * 用于标记可以通过http访问的服务
     *
     * @author kun
     * @data 2022/1/15 17:55
     */
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface HttpRequest {
        /**
         * http请求url
         *
         * @return  url
         */
        String value();
    }
    

    第三个“MultiRequestBody”,用于实现多变量传参。

    /**
     * 标注方法参数
     *
     * @author kun
     * @data 2022/1/15 17:57
     */
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MultiRequestBody {
        /**
         * 解析参数时用到JSON中的key
         *
         * @return  JSON格式参数
         */
        String value();
    }
    

    动态代理

    创建代理的目的是将方法转化为http调用。

    /**
     * HttpConsumer注解代理类
     *
     * @author kun
     * @data 2022/1/15 19:30
     */
    public class HttpConsumerInterceptor implements MethodInterceptor {
    
        private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
    
        private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    
        private static final String HTTP_HEAD = "http://";
    
        private final Class<?> proxyKlass;
    
        private final HttpDomain httpDomain;
    
        private final OkHttpClient okHttpClient;
    
        public HttpConsumerInterceptor(Class<?> proxyKlass, HttpDomain httpDomain, OkHttpClient okHttpClient) {
            this.proxyKlass = proxyKlass;
            this.httpDomain = httpDomain;
            this.okHttpClient = okHttpClient;
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            HttpRequest httpRequest = method.getAnnotation(HttpRequest.class);
            if (Objects.isNull(httpRequest)) {
                throw new IllegalStateException("method[" + method.getName() + "] must annotated with @HttpRequest!");
            }
            HttpRequest klassAnnotation = proxyKlass.getAnnotation(HttpRequest.class);
            String namespace = Objects.isNull(klassAnnotation) ? null : klassAnnotation.value();
            String url = getUrl(httpRequest.value(), namespace);
            Request request = buildRequest(method, args, url);
            Call call = okHttpClient.newCall(request);
            Response response = call.execute();
            ResponseBody body = response.body();
            if (Objects.isNull(body)) {
                return null;
            }
            byte[] bytes = body.bytes();
            String res = new String(bytes, StandardCharsets.UTF_8);
            if (StringUtils.isEmpty(res)) {
                return null;
            }
            try {
                return OBJECT_MAPPER.readValue(res, method.getReturnType());
            } catch (Throwable t) {
                Map<?, ?> map = OBJECT_MAPPER.readValue(res, Map.class);
                Object err = map.get("error");
                if (Objects.nonNull(err)) {
                    throw new RuntimeException(err.toString());
                }
                throw new RuntimeException(t);
            }
        }
    
        private String getUrl(String requestPath, String namespace) {
            if (Objects.isNull(namespace)) {
                return HTTP_HEAD + httpDomain.getDomain() + ":" + httpDomain.getPort() + "/" + requestPath;
            }
            return HTTP_HEAD + httpDomain.getDomain() + ":" + httpDomain.getPort() + "/" + namespace + "/" + requestPath;
        }
    
        private Request buildRequest(Method method, Object[] args, String url) throws JsonProcessingException {
            Request.Builder builder = new Request.Builder();
            Map<String, Object> paramMap = new HashMap<>(4);
            Parameter[] parameters = method.getParameters();
            for (int i=0; i<parameters.length; i++) {
                Parameter parameter = parameters[i];
                MultiRequestBody multiRequestBody = parameter.getAnnotation(MultiRequestBody.class);
                if (Objects.isNull(multiRequestBody)) {
                    throw new IllegalStateException("method[" + method.getName() + "] param must annotated with @MultiRequest!");
                }
                paramMap.put(multiRequestBody.value(), args[i]);
            }
            // 将参数构建为一个大JSON
            String param = OBJECT_MAPPER.writeValueAsString(paramMap);
            RequestBody requestBody = RequestBody.create(param, JSON);
            builder.post(requestBody);
            builder.url(url);
            return builder.build();
        }
    }
    

    自定义FactoryBean

    使用FactoryBean生成bean

    /**
     * @author kun
     * @data 2022/1/16 14:50
     */
    public class HttpConsumerProxyFactoryBean implements FactoryBean<Object> {
    
        private final Class<?> proxyKlass;
    
        private final HttpDomain httpDomain;
    
        private final OkHttpClient okHttpClient;
    
        public HttpConsumerProxyFactoryBean(Class<?> proxyKlass, HttpDomain httpDomain, OkHttpClient okHttpClient) {
            this.proxyKlass = proxyKlass;
            this.httpDomain = httpDomain;
            this.okHttpClient = okHttpClient;
        }
    
        @Override
        public Object getObject() {
            Enhancer enhancer = new Enhancer();
            if (proxyKlass.isInterface()) {
                enhancer.setInterfaces(new Class[]{proxyKlass});
            } else {
                enhancer.setSuperclass(proxyKlass);
            }
            HttpConsumerInterceptor httpConsumerInterceptor = new HttpConsumerInterceptor(proxyKlass, httpDomain, okHttpClient);
            enhancer.setCallback(httpConsumerInterceptor);
            return enhancer.create();
        }
    
        @Override
        public Class<?> getObjectType() {
            return proxyKlass;
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    }
    

    注册bean

    使用BeanDefinition方式注册bean

    /**
     * @author kun
     * @data 2022/1/16 13:59
     */
    @EnableConfigurationProperties(HttpConsumerProperties.class)
    public class HttpConsumerPostProcessor implements BeanClassLoaderAware, EnvironmentAware, BeanFactoryPostProcessor, ApplicationContextAware {
    
        private static final Logger logger = LoggerFactory.getLogger(HttpConsumerPostProcessor.class);
    
        private ClassLoader classLoader;
    
        private ApplicationContext context;
    
        private ConfigurableEnvironment environment;
    
        private ConfigurableListableBeanFactory beanFactory;
    
        private final Map<String, BeanDefinition> httpClientBeanDefinitions = new HashMap<>(4);
    
        private OkHttpClient okHttpClient;
    
        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            this.classLoader = classLoader;
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
            this.beanFactory = configurableListableBeanFactory;
            this.okHttpClient = buildOkHttpClient(environment);
            postProcessBeanFactory(beanFactory, (BeanDefinitionRegistry) beanFactory);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.context = applicationContext;
        }
    
        @Override
        public void setEnvironment(Environment environment) {
            this.environment = (ConfigurableEnvironment) environment;
        }
    
        private OkHttpClient buildOkHttpClient(ConfigurableEnvironment environment) {
            HttpConsumerProperties properties = BinderUtils.bind(environment, HttpConsumerProperties.PREFIX, HttpConsumerProperties.class);
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            ThreadPoolExecutor executor = new ThreadPoolExecutor(properties.getCoreThreads(), properties.getMaxThreads(),
                    properties.getKeepAliveTime(), TimeUnit.SECONDS, new SynchronousQueue<>(), Util.threadFactory("OkHttp Custom Dispatcher", false));
            Dispatcher dispatcher = new Dispatcher(executor);
            dispatcher.setMaxRequests(properties.getMaxRequests());
            dispatcher.setMaxRequestsPerHost(properties.getMaxRequests());
            builder.dispatcher(dispatcher);
            builder.connectTimeout(properties.getConnectTimeOut(), TimeUnit.SECONDS);
            builder.readTimeout(properties.getReadTimeOut(), TimeUnit.SECONDS);
            builder.writeTimeout(properties.getWriteTimeOut(), TimeUnit.SECONDS);
            builder.connectionPool(new ConnectionPool(properties.getMaxIdleConnections(), properties.getConnectionKeepAliveTime(), TimeUnit.SECONDS));
            return builder.build();
        }
    
        private void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
            for (String beanName : beanFactory.getBeanDefinitionNames()) {
                BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
                String beanClassName = definition.getBeanClassName();
                // 当用@Bean 返回的类型是Object时,beanClassName是null
                if (Objects.isNull(beanClassName)) {
                    continue;
                }
                Class<?> clazz = ClassUtils.resolveClassName(definition.getBeanClassName(), this.classLoader);
                ReflectionUtils.doWithFields(clazz, this::parseElement, this::annotatedWithHttpConsumer);
            }
            for (String beanName : httpClientBeanDefinitions.keySet()) {
                if (context.containsBean(beanName)) {
                    throw new IllegalArgumentException("[HttpConsumer Starter] Spring context already has a bean named " + beanName
                     + ", please change @HttpConsumer field name.");
                }
                registry.registerBeanDefinition(beanName, httpClientBeanDefinitions.get(beanName));
                logger.info("registered HttpConsumer factory bean \"{}\" in spring context.", beanName);
            }
        }
    
        private void parseElement(Field field) {
            Class<?> interfaceClass = field.getType();
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException("field [" + field.getName() + "] annotated with @HttpConsumer must be interface!");
            }
            HttpConsumer httpConsumer = AnnotationUtils.getAnnotation(field, HttpConsumer.class);
            HttpDomain httpDomain = HttpDomain.from(httpConsumer);
            // 支持占位符${}
            httpDomain.setDomain(beanFactory.resolveEmbeddedValue(httpDomain.getDomain()));
            httpDomain.setPort(beanFactory.resolveEmbeddedValue(httpDomain.getPort()));
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                    .rootBeanDefinition(HttpConsumerProxyFactoryBean.class)
                    .addConstructorArgValue(interfaceClass)
                    .addConstructorArgValue(httpDomain)
                    .addConstructorArgValue(okHttpClient);
            BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
            beanDefinition.setPrimary(true);
            beanDefinition.setAutowireCandidate(true);
            httpClientBeanDefinitions.put(field.getName(), beanDefinition);
        }
    
        private boolean annotatedWithHttpConsumer(Field field) {
            return field.isAnnotationPresent(HttpConsumer.class);
        }
    }
    

    多参数适配

    主要是多SpringMVC的参数进行配置

    /**
     * 参数解析
     *
     * @author kun
     * @data 2022/1/15 21:06
     */
    public class MultiRequestBodyResolver implements HandlerMethodArgumentResolver {
    
        private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    
        private static final String JSON_REQUEST_BODY = "JSON_REQUEST_BODY";
    
        @Override
        public boolean supportsParameter(MethodParameter methodParameter) {
            return methodParameter.hasParameterAnnotation(MultiRequestBody.class);
        }
    
        @Override
        public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
            String requestBody = getRequestBody(nativeWebRequest);
            OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
            JsonNode rootNode = OBJECT_MAPPER.readTree(requestBody);
            if (StringUtils.isEmpty(rootNode)) {
                throw new IllegalArgumentException("requestBody must not empty!");
            }
            MultiRequestBody multiRequestBody = methodParameter.getParameterAnnotation(MultiRequestBody.class);
            if (Objects.isNull(multiRequestBody)) {
                throw new IllegalArgumentException("param must annotated with @MultiRequestBody!");
            }
            String key = !StringUtils.isEmpty(multiRequestBody.value()) ? multiRequestBody.value() : methodParameter.getParameterName();
            JsonNode value = rootNode.get(key);
            if (Objects.isNull(value)) {
                return null;
            }
            Class<?> paramType = methodParameter.getParameterType();
            return OBJECT_MAPPER.readValue(value.toString(), paramType);
        }
    
        /**
         *
         * 获取请求体的JSON字符串
         */
        private String getRequestBody(NativeWebRequest webRequest) {
            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            String jsonBody = (String) webRequest.getAttribute(JSON_REQUEST_BODY, NativeWebRequest.SCOPE_REQUEST);
            if (!StringUtils.isEmpty(jsonBody)) {
                return jsonBody;
            }
            try (BufferedReader reader = servletRequest.getReader()) {
                jsonBody = IOUtils.toString(reader);
                webRequest.setAttribute(JSON_REQUEST_BODY, jsonBody, NativeWebRequest.SCOPE_REQUEST);
                return jsonBody;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    /**
     * JSON解析数据
     *
     * @author kun
     * @data 2022/1/15 21:03
     */
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
    
        }
    
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
    
        }
    
        @Override
        public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
    
        }
    
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
    
        }
    
        @Override
        public void addFormatters(FormatterRegistry formatterRegistry) {
    
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry interceptorRegistry) {
    
        }
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
    
        }
    
        @Override
        public void addCorsMappings(CorsRegistry corsRegistry) {
    
        }
    
        @Override
        public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
    
        }
    
        @Override
        public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
    
        }
    
        /**
         * 参数解析器
         *
         * @param list  解析器
         */
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
            list.add(new MultiRequestBodyResolver());
        }
    
        @Override
        public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
    
        }
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
            MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
            LinkedList<MediaType> mediaTypes = new LinkedList<>();
            mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
            jacksonConverter.setSupportedMediaTypes(mediaTypes);
            jacksonConverter.setDefaultCharset(StandardCharsets.UTF_8);
            list.add(jacksonConverter);
        }
    
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
    
        }
    
        @Override
        public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
    
        }
    
        @Override
        public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
    
        }
    
        @Override
        public Validator getValidator() {
            return null;
        }
    
        @Override
        public MessageCodesResolver getMessageCodesResolver() {
            return null;
        }
    }
    

    编写为Spring starter

    /**
     * 服务调用方生效
     *
     * @author kun
     * @data 2022/1/16 15:36
     */
    @ConditionalOnClass(OkHttpClient.class)
    public class HttpClientAuthConfig {
    
        @Bean
        @ConditionalOnMissingBean
        public HttpConsumerPostProcessor httpConsumerPostProcessor() {
            return new HttpConsumerPostProcessor();
        }
    }
    
    /**
     * 服务提供方生效
     *
     * @author kun
     * @data 2022/1/16 15:40
     */
    @ConditionalOnClass(WebMvcConfigurer.class)
    public class SpringMvcConfigurerAutoConfig {
    
        @Bean
        @ConditionalOnMissingBean
        public WebMvcConfig webMvcConfig() {
            return new WebMvcConfig();
        }
    
        @Bean
        @ConditionalOnMissingBean
        public HttpRequestValidator httpRequestValidator() {
            return new HttpRequestValidator();
        }
    }
    

    测试

    服务提供方

    /**
     * @author kun
     * @data 2022/1/22 14:25
     */
    @HttpRequest("demo")
    public interface DemoHttpService {
    
        @HttpRequest("checkSuccess")
        String checkSuccess(@MultiRequestBody("param1") String param1, @MultiRequestBody("param2") String param2);
    }
    

    服务调用方

    /**
     * @author kun
     * @data 2022/1/22 14:37
     */
    @Configuration
    public class HttpConfig {
    
        @HttpConsumer(domain = "localhost", port = "8080")
        private DemoHttpService demoHttpService;
    }
    
    /**
     * @author kun
     * @data 2022/1/22 14:40
     */
    @Component
    public class DemoConsumer {
        @Resource
        private DemoHttpService demoHttpService;
    
        public String checkSuccess() {
            return demoHttpService.checkSuccess("param1", "param2");
        }
    }
    

    完整代码地址

    https://github.com/wanggangkun/myFeign

    相关文章

      网友评论

          本文标题:自己动手实现简易版Feign

          本文链接:https://www.haomeiwen.com/subject/iiiqkrtx.html