自己动手实现简易版Feign

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 paramMap = new HashMap<>(4);
        Parameter[] parameters = method.getParameters();
        for (int i=0; i

自定义FactoryBean

使用FactoryBean生成bean

/**
 * @author kun
 * @data 2022/1/16 14:50
 */
public class HttpConsumerProxyFactoryBean implements FactoryBean {

    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 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 list) {
        list.add(new MultiRequestBodyResolver());
    }

    @Override
    public void addReturnValueHandlers(List list) {

    }

    @Override
    public void configureMessageConverters(List> list) {
        MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
        LinkedList 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> list) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List list) {

    }

    @Override
    public void extendHandlerExceptionResolvers(List 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)