自己動手實現(xiàn)簡易版Feign

Feign的功能

主要功能是讓開發(fā)人員只使用簡單注解就能像調用一般RPC一樣調用HTTP請求,在此基礎上,框架擴展了原SpringMVC參數(shù)只能為一個類的功能。

基本思路

代理實現(xiàn)->bean的注冊->多參數(shù)實現(xiàn)

代碼實現(xiàn)

注解定義

需要定義三個注解。
第一個“HttpConsumer”,用于消費端注冊服務。

/**
 * 標注需要代理為http請求發(fā)送
 *
 * @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”,用于實現(xiàn)多變量傳參。

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

動態(tài)代理

創(chuàng)建代理的目的是將方法轉化為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]);
        }
        // 將參數(shù)構建為一個大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);
    }
}

多參數(shù)適配

主要是多SpringMVC的參數(shù)進行配置

/**
 * 參數(shù)解析
 *
 * @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解析數(shù)據(jù)
 *
 * @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) {

    }

    /**
     * 參數(shù)解析器
     *
     * @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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring官網(wǎng)的Feign文檔[https://cloud.spring.io/spring-cloud-sta...
    丶含光閱讀 1,123評論 0 3
  • Java繼承關系初始化順序 父類的靜態(tài)變量-->父類的靜態(tài)代碼塊-->子類的靜態(tài)變量-->子類的靜態(tài)代碼快-->父...
    第六象限閱讀 2,261評論 0 9
  • 1-Java基礎 1.1-String和StringBuffer區(qū)別,為什么是可變的,不可變的 String 類中...
    楊慶祥閱讀 1,035評論 0 0
  • Q1:IoC 是什么? IoC 即控制反轉,簡單來說就是把原來代碼里需要實現(xiàn)的對象創(chuàng)建、依賴反轉給容器來幫忙實現(xiàn),...
    Java程序員石頭閱讀 275評論 0 1
  • 疫情現(xiàn)在比較穩(wěn)定了,小區(qū)樓下每天該遛狗的遛狗、該買菜的買菜、該逛街的逛街。然而我眉頭一皺,現(xiàn)在還是得緊繃神經(jīng)、嚴守...
    小漢同學閱讀 562評論 0 2

友情鏈接更多精彩內容