/* * 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(); } } }