スプリング自動射出タイプミスマッチ問題

バックグラウンド

マイクロサービス プロジェクトは統合およびマージする必要があり、さまざまな落とし穴が生じる

問題の説明

マージされたプロジェクトが開始された後、アプリケーション エラーの種類に一貫性がなくなります。スタックは次のとおりです。

2020-07-02 11:32:04,008 ERROR  [main] org.springframework.boot.SpringApplication:: Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'CNGetPakgOverDistanceHandler': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'stringRedisTemplate' is expected to be of type 'org.springframework.data.redis.core.StringRedisTemplate' but was actually of type 'org.springframework.data.redis.core.RedisTemplate'
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:321) ~[spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1269) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:551) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.17.RELEASE.jar:1.5.17.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) ~[spring-boot-1.5.17.RELEASE.jar:1.5.17.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) ~[spring-boot-1.5.17.RELEASE.jar:1.5.17.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) ~[spring-boot-1.5.17.RELEASE.jar:1.5.17.RELEASE]
	at com.dianwoba.wireless.fundamental.boot.WirelessSpringApplication.run(WirelessSpringApplication.java:16) ~[wireless-fundamental-1.0.0-20191205.115817-28.jar:1.0.0-SNAPSHOT]
	at com.dianwoba.order.foul.Application.main(Application.java:20) ~[classes/:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_131]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]
	at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:64) ~[?:?]
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'stringRedisTemplate' is expected to be of type 'org.springframework.data.redis.core.StringRedisTemplate' but was actually of type 'org.springframework.data.redis.core.RedisTemplate'
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:384) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:522) ~[spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:496) ~[spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:627) ~[spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:171) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87) ~[spring-beans-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:318) ~[spring-context-4.3.20.RELEASE.jar:4.3.20.RELEASE]
	... 21 more
复制代码

検出プロジェクトで使用されるカスタム redis テンプレート定義を表示します。コードは次のとおりです。

@Bean
public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, String> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    template.setDefaultSerializer(template.getStringSerializer());
    StringRedisSerializer serializer = new StringRedisSerializer();
    template.setValueSerializer(serializer);
    template.setHashValueSerializer(serializer);
    return template;
}
复制代码

彼の元のサービスはどのように始まったのですか? RedisTemplate<String, String> は StringRedisTemplate 型と互換性がありますか? いいえ、それ以外の場合、例外は報告されません。そこで、元の master ブランチに戻しました。アプリはスムーズに起動します。子供たち、疑問符がたくさんありますか?私は持っている。

問題分析

スプリング射出タイプの適合ルールは?

Spring は、Resource アノテーションを介してタイプ マッチング注入問題を注入します。注入された 2 つのデータ スプリングは、どのようなデータ スプリングに自動的に適合しますか? そうです、Java の親子型変換のように、スーパータイプとして定義されたフィールドにサブタイプを注入することはできますが、サブタイプとして定義されたフィールドにスーパータイプを自動的に注入することはできません。関連するソースコードは次のとおりです。

  1. ResourceElement 要素を含むインジェクション メタデータを作成し、コンストラクターで型を確認します (Resource アノテーションで型が指定されている場合)。
  2. プロパティに注入する Bean を取得する
// 内部类:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement
// 该元素重写了getResourceToInject方法,即获取用来要注入到field字段的bean
protected Object getResourceToInject(Object target, String requestingBeanName) {
	return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
			getResource(this, requestingBeanName));
}
// 1. 首先从jndi工厂中按照名称查找,名称优先级(Resource注解配置):lookup->mappedName->name
// 2. 其次从resourceFactory(beanFactory)中获取bean
protected Object getResource(LookupElement element, String requestingBeanName) throws BeansException {
	if (StringUtils.hasLength(element.mappedName)) {
		return this.jndiFactory.getBean(element.mappedName, element.lookupType);
	}
	if (this.alwaysUseJndiLookup) {
		return this.jndiFactory.getBean(element.name, element.lookupType);
	}
	if (this.resourceFactory == null) {
		throw new NoSuchBeanDefinitionException(element.lookupType,
				"No resource factory configured - specify the 'resourceFactory' property");
	}
	return autowireResource(this.resourceFactory, element, requestingBeanName);
}
复制代码
  1. ファクトリ (つまり、CNGetPakgOverDistanceHandler 親クラス DwdElemeOverDistanceCheckHandler が依存する StringRedisTemplate) から注入される Bean を取得します (doGetBean)。TypeConverterDelegate での型変換と一致判定:ClassUtils.isAssignableValue (requiredType, convertValue) メソッド判定
// Check if required type matches the type of the actual bean instance.
if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
	try {
        // 如果需要的话进行类型转换:TypeConverterSupport.convertIfNecessary
		return getTypeConverter().convertIfNecessary(bean, requiredType);
	}
	catch (TypeMismatchException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Failed to convert bean '" + name + "' to required type '" +
					ClassUtils.getQualifiedName(requiredType) + "'", ex);
		}
		throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
	}
}
复制代码

明らかに、このシナリオでは RedisTemplate は StringRedisTemplate の子ではありません。型不一致の例外をスローします

合併前のプロジェクトはなぜ順調にスタートできるのか?

ブレークポイントは、挿入された StringRedisTemplate インスタンスがカスタム構成 CacheConfig で定義された Bean ではないことを発見しました。代わりに、springboot 自動構成で定義された StringRedisTemplate Bean です。RedisAutoConfiguration 構成クラス

@Configuration
protected static class RedisConfiguration {

	...
	@Bean
	@ConditionalOnMissingBean(StringRedisTemplate.class)
	public StringRedisTemplate stringRedisTemplate(
			RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}
}
复制代码

2 つの構成アノテーションの構成クラスの優先順位は?

Application (springboot のソースクラス) の優先度は次のとおりです (他の Bean の優先度は同じです)。

メンバーの内部クラスまたはインターフェース ->ComponentScans ->Import ->ImportResource ->アプリケーション独自の Bean アノテーション メソッド ->アプリケーションのインターフェース ->ImportResource

つまり、org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration (アプリケーションのインポート フェーズ、SpringBootApplication アノテーションから継承された親アノテーション EnableAutoConfiguration) の優先度は、カスタム CacheConfig よりも低くする必要があります。 EnableAutoConfigurationImportSelector プロセスの処理では、Spring ファクトリから EnableAutoConfiguration の実装クラスを読み込み、インポートします。自動構成ファイルは、spring-autoconfigure-metadata メタデータ構成に基づいて並べ替え、フィルター処理、およびインポートされます。

  1. 構成構成クラスは ConfigurationClassParser.configurationClasses に収集されます
  2. ConfigurationClassBeanDefinitionReader は、ConfigurationClassParser.configurationClasses キャッシュ内の構成クラスをトラバースして読み取り、Bean 定義をファクトリに登録します。

ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass は、構成クラスに Bean 定義をロードします. beanMethod メソッドでの Bean 定義の処理ロジックを見てみましょう: loadBeanDefinitionsForBeanMethod

  1. 条件付きの判定は Bean 定義を無視します
  2. Bean 定義のエイリアスを取得し、Bean アノテーションの name 属性を読み取ります。そうでない場合は、デフォルトでメソッド名になります
  3. 既存の Bean 定義によってオーバーライドされるかどうかを決定します。ConfigurationClassBeanDefinition、ScannedGenericBeanDefinition、および bean で定義されたロールが ROLE_APPLICATION より大きい場合、書き換えが許可されます
  4. ファクトリ registerBeanDefinition に Bean 定義を登録します。既存の Bean 定義を取得し、Bean 定義のオーバーライドが許可されていない場合は例外をスローします (デフォルトで許可されています)。

まとめ

  1. まず、カスタム Bean は、springboot によって自動構成された Bean よりも優先されます。
  2. 2 番目の springboot 自動構成された redis Bean は有効にならず、条件付き判定 ConditionalOnMissingBean は、登録された Bean ステージ (loadBeanDefinitionsForBeanMethod) の条件です; 直接スキップされます。これは事実であることが証明されています。次の図を参照してください。到達した (つまり、Bean がスキップされた) レジスター):

したがって、何があっても、カスタム cacheConfig の redis Bean が有効になるはずです。つまり、エラーが報告されます。

マージ前に工場で springboot 自動構成の redis Bean を使用するのはなぜですか?

master ブランチに戻ってブレークポイントを実行し、最初にカスタム redis Bean に移動し、次に springboot に移動して redis Bean を自動的に登録しますが、スキップはありませんか? すると、問題の原因が徐々に明らかになり、問題は shouldSkip 条件判定の段階にあるはずです

// OnBeanCondition.getMatchOutcome
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
	BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
			ConditionalOnMissingBean.class);
    // 如果找到bean则返回条件不通过noMatch。查找策略为ALL,即所有
	List<String> matching = getMatchingBeans(context, spec);
	if (!matching.isEmpty()) {
		return ConditionOutcome.noMatch(ConditionMessage
				.forCondition(ConditionalOnMissingBean.class, spec)
				.found("bean", "beans").items(Style.QUOTE, matching));
	}
    // 如果未找到bean则返回条件通过
	matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
			.didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
// 创建BeanSearchSpec
BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
		Class<?> annotationType) {
	this.annotationType = annotationType;
	MultiValueMap<String, Object> attributes = metadata
			.getAllAnnotationAttributes(annotationType.getName(), true);
	collect(attributes, "name", this.names);
	collect(attributes, "value", this.types);
	collect(attributes, "type", this.types);
	collect(attributes, "annotation", this.annotations);
	collect(attributes, "ignored", this.ignoredTypes);
	collect(attributes, "ignoredType", this.ignoredTypes);
	this.strategy = (SearchStrategy) metadata
			.getAnnotationAttributes(annotationType.getName()).get("search");
	BeanTypeDeductionException deductionException = null;
	try {
		if (this.types.isEmpty() && this.names.isEmpty()) {
			addDeducedBeanType(context, metadata, this.types);
		}
	}
    ...
}
复制代码

getMatchingBeans ルックアップ Bean ルックアップ戦略 SearchStrategy が PARENTS または ANCESTORS の場合、ファクトリの親クラスを使用してルックアップします。ファクトリが null の場合、空のコレクションが返されます。つまり、ConditionalOnMissingBean 条件は、ファクトリからすべての注釈型属性を取得することによって、指定された型のすべての beanName (親子階層を含む) を取得します。このとき、cacheConfig で定義されているタイプは RedisTemplate であるため、指定されたタイプには属しません。親クラスをサブクラスに移行することはできません。type=StringRedisTemplate、現在はファクトリにのみ存在します: RedisTemplate

// BeanTypeRegistry
Set<String> getNamesForType(Class<?> type) {
	updateTypesIfNecessary();
	Set<String> matches = new LinkedHashSet<String>();
	for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) {
		if (entry.getValue() != null && type.isAssignableFrom(entry.getValue())) {
			matches.add(entry.getKey());
		}
	}
	return matches;
}
复制代码

条件付きアノテーションのignoredTypes属性で指定された型に対応するbeanNameをbeanNamesから削除アノテーション属性で指定された型のすべてのbeanNameをファクトリ(親子階層含む)から取得し、指定された同名のbeanを取得ファクトリからのアノテーション名属性 (親子階層を含む)、ファクトリの containsBean メソッドを呼び出します。名前が指定されていないため、最終的な判定条件は、カスタム cacheConfig 内の redis Bean を上書き登録し続けることです。

マージされたファクトリで springboot 自動構成された redis Bean がないのはなぜですか?

マージされたブランチは次の構成を追加したため、ここでの springboot の自動構成は失われています。

@Bean
@Primary
public StringRedisTemplate stringRedisTemplate2(RedisConnectionFactory redisConnectionFactory) {
	return new StringRedisTemplate(redisConnectionFactory);
}
复制代码

問題の要約

  1. マージする前は、プロジェクトに StringRedisTemplate タイプの Bean がなかったため、自動構成された Bean 定義がカスタム Bean 定義をオーバーライドしていました。したがって、カスタム Bean の定義が間違っていて、例外はスローされません
  2. マージ後、プロジェクトには StringRedisTemplate タイプの Bean 構成があるため、自動構成された Bean 定義はカスタム Bean 定義を上書きしません。カスタム構成タイプが間違った RedisTemplate タイプであるため、注入するときは名前で注入を一致させます。したがって、例外がスローされました

おすすめ

転載: blog.csdn.net/m0_71777195/article/details/126384171