All Downloads are FREE. Search and download functionalities are using the official Maven repository.

feign.ReflectiveFeign Maven / Gradle / Ivy

There is a newer version: 13.5
Show newest version
/*
 * Copyright 2012-2023 The Feign Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package feign;

import static feign.Util.checkNotNull;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import feign.InvocationHandlerFactory.MethodHandler;

public class ReflectiveFeign extends Feign {

  private final ParseHandlersByName targetToHandlersByName;
  private final InvocationHandlerFactory factory;
  private final AsyncContextSupplier defaultContextSupplier;

  ReflectiveFeign(
      Contract contract,
      MethodHandler.Factory methodHandlerFactory,
      InvocationHandlerFactory invocationHandlerFactory,
      AsyncContextSupplier defaultContextSupplier) {
    this.targetToHandlersByName = new ParseHandlersByName(contract, methodHandlerFactory);
    this.factory = invocationHandlerFactory;
    this.defaultContextSupplier = defaultContextSupplier;
  }

  /**
   * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
   * to cache the result.
   */
  public  T newInstance(Target target) {
    return newInstance(target, defaultContextSupplier.newContext());
  }

  @SuppressWarnings("unchecked")
  public  T newInstance(Target target, C requestContext) {
    TargetSpecificationVerifier.verify(target);

    Map methodToHandler =
        targetToHandlersByName.apply(target, requestContext);
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class[] {target.type()}, handler);

    for (MethodHandler methodHandler : methodToHandler.values()) {
      if (methodHandler instanceof DefaultMethodHandler) {
        ((DefaultMethodHandler) methodHandler).bindTo(proxy);
      }
    }

    return proxy;
  }

  static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map dispatch;

    FeignInvocationHandler(Target target, Map dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      } else if (!dispatch.containsKey(method)) {
        throw new UnsupportedOperationException(
            String.format("Method \"%s\" should not be called", method.getName()));
      }

      return dispatch.get(method).invoke(args);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof FeignInvocationHandler) {
        FeignInvocationHandler other = (FeignInvocationHandler) obj;
        return target.equals(other.target);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return target.hashCode();
    }

    @Override
    public String toString() {
      return target.toString();
    }
  }

  private static final class ParseHandlersByName {

    private final Contract contract;
    private final MethodHandler.Factory factory;

    ParseHandlersByName(
        Contract contract,
        MethodHandler.Factory factory) {
      this.contract = contract;
      this.factory = factory;
    }

    public Map apply(Target target, C requestContext) {
      final Map result = new LinkedHashMap<>();

      final List metadataList = contract.parseAndValidateMetadata(target.type());
      for (MethodMetadata md : metadataList) {
        final Method method = md.method();
        if (method.getDeclaringClass() == Object.class) {
          continue;
        }

        final MethodHandler handler = createMethodHandler(target, md, requestContext);
        result.put(method, handler);
      }

      for (Method method : target.type().getMethods()) {
        if (Util.isDefault(method)) {
          final MethodHandler handler = new DefaultMethodHandler(method);
          result.put(method, handler);
        }
      }

      return result;
    }

    private MethodHandler createMethodHandler(final Target target,
                                              final MethodMetadata md,
                                              final C requestContext) {
      if (md.isIgnored()) {
        return args -> {
          throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
        };
      }

      return factory.create(target, md, requestContext);
    }
  }

  private static class TargetSpecificationVerifier {
    public static  void verify(Target target) {
      Class type = target.type();
      if (!type.isInterface()) {
        throw new IllegalArgumentException("Type must be an interface: " + type);
      }

      for (final Method m : type.getMethods()) {
        final Class retType = m.getReturnType();

        if (!CompletableFuture.class.isAssignableFrom(retType)) {
          continue; // synchronous case
        }

        if (retType != CompletableFuture.class) {
          throw new IllegalArgumentException("Method return type is not CompleteableFuture: "
              + getFullMethodName(type, retType, m));
        }

        final Type genRetType = m.getGenericReturnType();

        if (!(genRetType instanceof ParameterizedType)) {
          throw new IllegalArgumentException("Method return type is not parameterized: "
              + getFullMethodName(type, genRetType, m));
        }

        if (((ParameterizedType) genRetType).getActualTypeArguments()[0] instanceof WildcardType) {
          throw new IllegalArgumentException(
              "Wildcards are not supported for return-type parameters: "
                  + getFullMethodName(type, genRetType, m));
        }
      }
    }

    private static String getFullMethodName(Class type, Type retType, Method m) {
      return retType.getTypeName() + " " + type.toGenericString() + "." + m.getName();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy