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);
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章