Spring Cloud OpenFeign 实现原理解析

1、什么是Feign

Feign 是一个声明式 HTTP 调用客户端,Feign 的目标是使编写Java HTTP客户端更加容易。Feign 的声明编程调用思想来自 Retrofit ,Retrofit 在 Android 端十分流行,Feign 借鉴了 Retrofit 的编程理念。

Feign 支持以下几种 HTTP 客户端 Apache Httpclient , Square OkHttp , HttpURLConnection。

2、什么是 Spring Cloud OpenFeign

Spring Cloud 中服务与服务之间的调用都是通过 HTTP 完成的,而 Spring Cloud OpenFeign 就是基于 Fegin 的封装,与Spring Boot 结合实现开箱即用,来实现服务之间内部调用的客户端。

3、Feign 实现原理

以Fegin官方提供的示例(https://github.com/OpenFeign/feign),来解析其实现的原理。如下,获取指定 Github 仓库的所有贡献者:

 /**
     * 定义声明式接口
     */
    interface GitHub {
        /**
         * 声明调用方式 GET 和 调用地址,获取指定仓库贡献者列表
         */
        @RequestLine("GET /repos/{owner}/{repo}/contributors")
        List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository);

        class Contributor {
            String login;
            int contributions;
        }
    }

    public static void main(String[] args) {
        //Feign客户端初始化
        GitHub github = Feign.builder()
                .decoder(new GsonDecoder())
                .logLevel(Logger.Level.FULL)
                .target(GitHub.class, "https://api.github.com");
        //获取并打印feign的贡献者列表。
        List<GitHub.Contributor> contributors = github.contributors("OpenFeign", "feign");
        contributors.forEach(contributor -> System.out.println(contributor.login + " (" + contributor.contributions + ")"));
    }

从示例中可以看到,Feign客户端初始化包括三个部分,Feign 默认配置初始化、设置自定义配置、生成代理对象。

在这里插入图片描述

3.1、Feign.builder() 初始化构造器

根据默认值初始化 Feign 构造器

public static Builder builder() {
    return new Builder();
  }

public static class Builder {

    //请求拦截器,拦截每一个请求,添加或修改请求属性
    private final List<RequestInterceptor> requestInterceptors =
        new ArrayList<RequestInterceptor>();
    //日志级别,默认关闭
    private Logger.Level logLevel = Logger.Level.NONE;
    //定义接口上有效的注释和值。
    private Contract contract = new Contract.Default();
    //客户端实例化。空参数表示使用默认值。
    private Client client = new Client.Default(null, null);
    //默认重试规则
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    //编码器
    private Encoder encoder = new Encoder.Default();
    //解码器
    private Decoder decoder = new Decoder.Default();
    //生成查询参数,eg: "/uri?name={name}&number={number}"
    private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
    //错误解码处理器
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    //控制请求的参数,比如:超时时间
    private Options options = new Options();
    //反射控制工厂
    private InvocationHandlerFactory invocationHandlerFactory =
        new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;

    。。。。。。省略。。。。。。。。
}

3.2、Feign.target() 生成代理对象

 public <T> T target(Class<T> apiType, String url) {
      return target(new HardCodedTarget<T>(apiType, url));
    }

    public <T> T target(Target<T> target) {
      //调用JDK动态代理生成接口代理类
      return build().newInstance(target);
    }

    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
     // 注入依赖配置项
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
    
    public class ReflectiveFeign extends Feign {
      @Override
      public <T> T newInstance(Target<T> target) {
       //根据 Contract 约定,解析接口方法上的注解,建立方法名与对应处理器的映射关系
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        //收集方法对象与对应方法处理器的映射关系
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
        //收集默认方法处理器
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
        for (Method method : target.type().getMethods()) {
          if (method.getDeclaringClass() == Object.class) {
            continue;
          } else if (Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
          } else {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
          }
        }
        // 使用JDK动态代理为接口生成代理对象,实际业务处理交给 InvocationHandler 处理,实质就是调用相应的 MethodHandler 
        InvocationHandler handler = factory.create(target, methodToHandler);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
            new Class<?>[] {target.type()}, handler);
    
        for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
          defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
      }
    }
    
    
    // InvocationHandler 实现代理类 ,Map<Method, MethodHandler>  
  static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    //根据接口注解生成的方法对象与方法处理器的映射对象
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> 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();
      }

      //根据调用的方法从 Map<Method, MethodHandler> 中获取对应的 MethodHandler,实现http的调用
      return dispatch.get(method).invoke(args);
    }
  }

在这里插入图片描述

target 方法通过使用JDK动态代理 Proxy.newProxyInstance,为FeginClient接口生成代理对象处理器 FeignInvocationHandler。最后通过调用 FeignInvocationHandler 中的 invoke 方法,
从之前解析出的 methodToHandler 映射关系中获取真正的代理实现类,执行具体的业务逻辑。

3.3、如何解析 FeginClient 接口中定义的方法

那么是如何解析 Github.contributors 方法的注解呢。 Feign中提供一个 Contract 解析契约(协议),如果没有指定,默认使用 BaseContract 中的实现。

在这里插入图片描述

3.3.1、默认Contract 实现

abstract class BaseContract implements Contract {

    /**
     * @param targetType {@link feign.Target#type() type} of the Feign interface.
     * @see #parseAndValidateMetadata(Class)
     */
    @Override
    public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
      //不支持参数化类型
      checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",
          targetType.getSimpleName());
      //只支持单继承接口
      checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",
          targetType.getSimpleName());
      if (targetType.getInterfaces().length == 1) {
        checkState(targetType.getInterfaces()[0].getInterfaces().length == 0,
            "Only single-level inheritance supported: %s",
            targetType.getSimpleName());
      }
      Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
      for (Method method : targetType.getMethods()) {
        if (method.getDeclaringClass() == Object.class ||
            (method.getModifiers() & Modifier.STATIC) != 0 ||
            Util.isDefault(method)) {
          continue;
        }
        MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
        checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s",
            metadata.configKey());
        result.put(metadata.configKey(), metadata);
      }
      return new ArrayList<>(result.values());
    }
    
     /**
     * Called indirectly by {@link #parseAndValidateMetadata(Class)}.
     */
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
      MethodMetadata data = new MethodMetadata();
      data.targetType(targetType);
      data.method(method);
      data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
      data.configKey(Feign.configKey(targetType, method));

      //类注解解析
      if (targetType.getInterfaces().length == 1) {
        processAnnotationOnClass(data, targetType.getInterfaces()[0]);
      }
      processAnnotationOnClass(data, targetType);

      //方法注解解析
      for (Annotation methodAnnotation : method.getAnnotations()) {
        processAnnotationOnMethod(data, methodAnnotation, method);
      }
      if (data.isIgnored()) {
        return data;
      }
      checkState(data.template().method() != null,
          "Method %s not annotated with HTTP method type (ex. GET, POST)",
          data.configKey());
      Class<?>[] parameterTypes = method.getParameterTypes();
      Type[] genericParameterTypes = method.getGenericParameterTypes();

      Annotation[][] parameterAnnotations = method.getParameterAnnotations();
      int count = parameterAnnotations.length;
      for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        if (parameterAnnotations[i] != null) {
          isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }

        if (isHttpAnnotation) {
          data.ignoreParamater(i);
        }

        if (parameterTypes[i] == URI.class) {
          data.urlIndex(i);
        } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
          if (data.isAlreadyProcessed(i)) {
            checkState(data.formParams().isEmpty() || data.bodyIndex() == null,
                "Body parameters cannot be used with form parameters.");
          } else {
            checkState(data.formParams().isEmpty(),
                "Body parameters cannot be used with form parameters.");
            checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
            data.bodyIndex(i);
            data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
          }
        }
      }

      if (data.headerMapIndex() != null) {
        checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()],
            genericParameterTypes[data.headerMapIndex()]);
      }

      if (data.queryMapIndex() != null) {
        if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
          checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
        }
      }

      return data;
    }
    。。。。。。。。。。。。。。省略。。。。。。。。。。。。
}

3.3.2、SpringMvcContract 基于Spring MVC的协议规范

Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成请求协议解析,客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的 API,就可以使用面向接口的编码方式对接服务。

Spring Cloud 没有基于Spring MVC 全部注解来做Feign 客户端注解协议解析,有一些注解并不支持,如 @GetMapping,@PutMapping 等,仅支持使用 @RequestMapping 等。

基于 Spring MVC 注解规范解析类上的注解

@Override
	protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
		if (clz.getInterfaces().length == 0) {
			RequestMapping classAnnotation = findMergedAnnotation(clz,
					RequestMapping.class);
			if (classAnnotation != null) {
				// Prepend path from class annotation if specified
				if (classAnnotation.value().length > 0) {
					String pathValue = emptyToNull(classAnnotation.value()[0]);
					pathValue = resolve(pathValue);
					if (!pathValue.startsWith("/")) {
						pathValue = "/" + pathValue;
					}
					data.template().uri(pathValue);
				}
			}
		}
	}

基于 Spring MVC 注解规范解析方法上的注解

@Override
	protected void processAnnotationOnMethod(MethodMetadata data,
			Annotation methodAnnotation, Method method) {
		if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
				.annotationType().isAnnotationPresent(RequestMapping.class)) {
			return;
		}

		RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
		// HTTP Method
		RequestMethod[] methods = methodMapping.method();
		if (methods.length == 0) {
			methods = new RequestMethod[] { RequestMethod.GET };
		}
		checkOne(method, methods, "method");
		data.template().method(Request.HttpMethod.valueOf(methods[0].name()));

		// path
		checkAtMostOne(method, methodMapping.value(), "value");
		if (methodMapping.value().length > 0) {
			String pathValue = emptyToNull(methodMapping.value()[0]);
			if (pathValue != null) {
				pathValue = resolve(pathValue);
				// Append path from @RequestMapping if value is present on method
				if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
					pathValue = "/" + pathValue;
				}
				data.template().uri(pathValue, true);
			}
		}

		// produces
		parseProduces(data, method, methodMapping);

		// consumes
		parseConsumes(data, method, methodMapping);

		// headers
		parseHeaders(data, method, methodMapping);

		data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());
	}

3.2、MethodHandler

MethodHandler 有两个实现类:DefaultMethodHandler 和 SynchronousMethodHandler,feign 最终的http请求就是在同步方法处理器(SynchronousMethodHandler)中完成的。

MethodHandler 请求处理逻辑

在这里插入图片描述

 @Override
  public Object invoke(Object[] argv) throws Throwable {
     //请求参数封装为 RequestTemplate
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    //重试处理器 
    Retryer retryer = this.retryer.clone();
    // 重试逻辑
    while (true) {
      try {
          //请求返回
        return executeAndDecode(template);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

  Object executeAndDecode(RequestTemplate template) throws Throwable {
   // 根据不同参数构建请求模板,并执行请求拦截器生成最终Request
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    //返回响应信息
    Response response;
    long start = System.nanoTime();
    try {
      //通过 client 完成请求,Apache Httpclient、Square OkHttp 或 HttpURLConnection 。默认是 HttpURLConnection。
      response = client.execute(request, options);
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      //处理正常返回
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
           //解码操作
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
     //异常处理
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

 //解码操作
  Object decode(Response response) throws Throwable {
    try {
      return decoder.decode(response, metadata.returnType());
    } catch (FeignException e) {
      throw e;
    } catch (RuntimeException e) {
      throw new DecodeException(e.getMessage(), e);
    }
  }

3.3、最终的HTTP请求

feign 中默认的http请求,使用 HttpURLConnection 实现,每一次请求都会创建一个连接。这里可以使用连接池来优化该问题,配置中可以使用 HttpClient 或者 OKHttp。

在这里插入图片描述

 class Default implements Client {

    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;

    /**
     * Null parameters imply platform defaults.
     */
    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
      this.sslContextFactory = sslContextFactory;
      this.hostnameVerifier = hostnameVerifier;
    }

    @Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
    }

4、Spring Cloud 中的 OpenFeign

在spring cloud中,如果要启用Fegin,需要在启动类中添加注解 @EnableFeignClients。从该注解源码中我们可以看到,引入了类 @Import(FeignClientsRegistrar.class)。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] defaultConfiguration() default {};

    Class<?>[] clients() default {};

}

@Import(FeignClientsRegistrar.class) 导入FeignClientsRegistrar,扫描带有注解 @FeignClient 的类注入到容器。

FeignClientsRegistrar 继承 ImportBeanDefinitionRegistrar 完成对含有 @FeignClient 的类的注册;注意只是Bean注册,没有初始化,Spring 启动过程分注册和初始化,初始化通过getBean()初始化;

ImportBeanDefinitionRegistrar.registerBeanDefinitions 通过 ClassPathScanningCandidateComponentProvider 扫描加了 @FeignClient 的类。

FeignClientFactoryBean 继承了 FactoryBean , FactoryBean#getObject() 返回的就是我们的 Feign 的代理对象,最后这个代理对象被注入到了 Spring 容器中, 通过 @Autowired 可以直接注入使用。

@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 注册 EnableFeignClients 的配置类
		registerDefaultConfiguration(metadata, registry);
		// 注册有 FeignClient 的接口
		registerFeignClients(metadata, registry);
	}

	private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 扫描配置注解中配置范围内的 @FeignClient
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// 验证被注解的类是否是接口
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					// 注入IOC 容器
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));

					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

    // 注册 FeignClient
	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

		boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
																// null

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章