@Lazy アノテーションが無限ループを解消できるのはなぜですか?

以下の内容はSpring6.0.4をベースにしています。

前回の記事で、 Brother Song は、すべての循環依存関係が Spring で解決できるわけではなく、一部の循環依存関係はデフォルトでは Spring で解決できないことについて話しました。あまり詳しくない友達は、先に前の記事を読んでください。

上記記事前段の事例を例に挙げると、構築方法においてお互いのBeanを注入し合うというのは現時点では完全に無限ループとなっており、果たしてこの無限ループに解決策はあるのでしょうか?

Springではそれを解決する方法が提供されていますが、解決されていないようです。なぜそう言えるのかは、この記事を読めばわかります。

1. @レイジー

この記事のタイトルにあるように、前の記事で述べた自動解決できない 3 つの循環依存関係は、@Lazyアノテーションを追加することですべて解決できます。

コンストラクターインジェクションの場合は次のようになります。

@Service
public class AService {

    BService bService;

    @Lazy
    public AService(BService bService) {
        this.bService = bService;
    }

    public BService getbService() {
        return bService;
    }
}
@Service
public class BService {
    AService aService;
    
    @Lazy
    public BService(AService aService) {
        this.aService = aService;
    }

    public AService getaService() {
        return aService;
    }
}

@Lazyアノテーションは、AService または BService、あるいはその両方のコンストラクターに追加できます。

追加後、プロジェクトを再度開始しますが、エラーは発生しません。問題は解決されたようですが、まだほとんど意味がありません。皆さん、私のスタートアップ コードを見てください。

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
AService aService = ctx.getBean(AService.class);
BService bService = ctx.getBean(BService.class);
System.out.println("aService.getClass() = " + aService.getClass());
System.out.println("bService.getClass() = " + bService.getClass());
System.out.println("aService.getbService().getClass() = " + aService.getbService().getClass());
System.out.println("bService.getaService().getClass() = " + bService.getaService().getClass());

最終的な印刷結果は次のようになります。

AService と BService から取得した Bean はすべて通常のプロキシ化されていないオブジェクトであることが友人にわかりますが、実際、元のコードはプロキシ化する必要がありません。ただし、AService の BService と BService の AService は両方ともプロキシ オブジェクトなので、AService の BService は Spring コンテナから取得した BService と一致する必要があり、BService の AService も Spring コンテナで取得した AService と一致する必要がありますが、実際にはこの 2 つは同じではありません。

しかし、これは簡単に理解できます。なぜ Spring が行き詰まりを解くことができるかというと、AService と BService によって注入された Bean はオリジナルの Bean ではなく、プロキシ Bean であるからです。AService に注入された BService はプロキシ オブジェクトです。同様に、BService に注入された AService もプロキシ オブジェクトです。

冒頭でこの問題は Spring で解決されたが解決されていないと述べたのはこのためです。

実際、これは@Lazyアノテーションの動作原理です。名前を見てください。このアノテーションが付けられたオブジェクトは遅延してロードされます。実際、このアノテーションが付けられたオブジェクトはプロキシ オブジェクトを自動的に生成します。

前の記事で説明した他の 2 つの問題も@Lazyアノテーションによって解決できます。コードは次のとおりです。

@Service
@Scope("prototype")
public class AService {
    @Lazy
    @Autowired
    BService bService;

}
@Service
@Scope("prototype")
public class BService {
    @Lazy
    @Autowired
    AService aService;
}

ここで @Lazy は問題を解決するために 1 つだけを必要としますが、両方を追加することもできます。

@Async アノテーションの場合は、@Lazy アノテーションによっても解決できます。

@Service
public class AService {
    @Autowired
    @Lazy
    BService bService;

    @Async
    public void hello() {
        bService.hello();
    }

    public BService getbService() {
        return bService;
    }
}
@Service
public class BService {
    @Autowired
    AService aService;

    public void hello() {
        System.out.println("xxx");
    }

    public AService getaService() {
        return aService;
    }
}

このようにして、循環依存関係を解消することができます。

一言で言えば、@Lazy アノテーションは中間プロキシ層を確立することで循環依存関係を解消します。

2. 原理分析

次に、@Lazy アノテーション処理のソースコードを解析してみましょう。

@Autowired が全体の処理フローの前の部分と前の記事にどのように変数を挿入したのかを説明するため、この部分のソース コードを最初から分析するつもりはありません。紹介する内容は同じですので、詳しくない方は@Autowiredを先に読んでいただくことをお勧めします。一つの記事。ここでは、友人に属性挿入のプロセスを確認してもらうために、この記事の要約を借用します。

  1. Bean を作成する場合、元の Bean が作成された後、populateBean メソッドが呼び出され、Bean のプロパティが設定されます。
  2. 次に、postProcessAfterInstantiation メソッドを呼び出して、ポストプロセッサを実行する必要があるかどうかを判断します。実行する必要がない場合は、直接戻ります。
  3. postProcessProperties メソッドを呼び出して、さまざまなポストプロセッサの実行をトリガーします。

  1. 手順 3 のメソッドで、findAutowiringMetadata を呼び出します。これにより、さらに buildAutorwiringMetadata メソッドがトリガーされ、@Autowired、@Value、および @Inject アノテーションを含むプロパティまたはメソッドが検索され、それらが InjectedElement としてカプセル化されて返されます。
  2. 属性注入のために InjectedElement#inject メソッドを呼び出します。

  1. 次に、resolvedCachedArgument メソッドを実行して、必要な Bean オブジェクトをキャッシュから検索します。
  2. キャッシュに Bean が存在しない場合は、resolveFieldValue メソッドを呼び出してコンテナ内で Bean を見つけます。
  3. 最後に、makeAccessible メソッドと set メソッドを呼び出して属性の割り当てを完了します。

ステップ 7 では、resolveFieldValue メソッドを呼び出して Bean を解決し、@Lazy アノテーションの関連ロジックがこのメソッドで処理されます ( @Autowired がどのように変数を注入するかに対応します。この記事のセクション 3.2 )。

solveFieldValue メソッドは、最終的には、resolveDependency メソッドまで実行されます。

@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
		@Nullable Set<string> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
	if (Optional.class == descriptor.getDependencyType()) {
		return createOptionalDependency(descriptor, requestingBeanName);
	}
	else if (ObjectFactory.class == descriptor.getDependencyType() ||
			ObjectProvider.class == descriptor.getDependencyType()) {
		return new DependencyObjectProvider(descriptor, requestingBeanName);
	}
	else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
		return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
	}
	else {
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
				descriptor, requestingBeanName);
		if (result == null) {
			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		}
		return result;
	}
}

このメソッドでは、最初に、挿入されたプロパティのタイプが Optional、ObjectFactory、または JSR-330 のアノテーションであるかどうかを判断します。ここではここではいないので、最後の分岐を取ります。

最後の else では、まず getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary メソッドを呼び出して、Bean オブジェクトを遅延ロードする必要があるかどうかを確認し、ここで @Lazy アノテーションが処理されます。遅延ロードが可能な場合、このメソッドの戻り値は null ではなく、直接返すことができ、doResolveDependency メソッドを実行する必要はありません。

ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary:

@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
	return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}

見てみましょう。このメソッドはまず isLazy を呼び出して遅延読み込みが必要かどうかを判断し、必要に応じて buildLazyResolutionProxy メソッドを呼び出して遅延読み込みオブジェクトを構築します。そうでない場合は、直接 null を返します。

protected boolean isLazy(DependencyDescriptor descriptor) {
	for (Annotation ann : descriptor.getAnnotations()) {
		Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
		if (lazy != null &amp;&amp; lazy.value()) {
			return true;
		}
	}
	MethodParameter methodParam = descriptor.getMethodParameter();
	if (methodParam != null) {
		Method method = methodParam.getMethod();
		if (method == null || void.class == method.getReturnType()) {
			Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
			if (lazy != null &amp;&amp; lazy.value()) {
				return true;
			}
		}
	}
	return false;
}

この判定メソッドは主に、現在のクラス内の各種パラメータに @Lazy アノテーションが含まれているかどうか、メソッド、プロパティ、クラス名に @Lazy アノテーションが含まれているかどうかを確認し、あれば true を返し、そうでない場合は false を返します。

buildLazyResolutionProxy メソッドを見てみましょう。

private Object buildLazyResolutionProxy(
		final DependencyDescriptor descriptor, final @Nullable String beanName, boolean classOnly) {
	BeanFactory beanFactory = getBeanFactory();
	final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;
	TargetSource ts = new TargetSource() {
		@Override
		public Class<!--?--> getTargetClass() {
			return descriptor.getDependencyType();
		}
		@Override
		public boolean isStatic() {
			return false;
		}
		@Override
		public Object getTarget() {
			Set<string> autowiredBeanNames = (beanName != null ? new LinkedHashSet&lt;&gt;(1) : null);
			Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);
			if (target == null) {
				Class<!--?--> type = getTargetClass();
				if (Map.class == type) {
					return Collections.emptyMap();
				}
				else if (List.class == type) {
					return Collections.emptyList();
				}
				else if (Set.class == type || Collection.class == type) {
					return Collections.emptySet();
				}
				throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
						"Optional dependency not present for lazy injection point");
			}
			if (autowiredBeanNames != null) {
				for (String autowiredBeanName : autowiredBeanNames) {
					if (dlbf.containsBean(autowiredBeanName)) {
						dlbf.registerDependentBean(autowiredBeanName, beanName);
					}
				}
			}
			return target;
		}
		@Override
		public void releaseTarget(Object target) {
		}
	};
	ProxyFactory pf = new ProxyFactory();
	pf.setTargetSource(ts);
	Class<!--?--> dependencyType = descriptor.getDependencyType();
	if (dependencyType.isInterface()) {
		pf.addInterface(dependencyType);
	}
	ClassLoader classLoader = dlbf.getBeanClassLoader();
	return (classOnly ? pf.getProxyClass(classLoader) : pf.getProxy(classLoader));
}

このメソッドはプロキシ オブジェクトを生成するために使用されます。プロキシ オブジェクト TargetSource はここで構築されます。その getTarget メソッドでは、doResolveDependency が実行されてプロキシされたオブジェクトを取得します (doResolveDependency の取得ロジックについては、「@Autowired はどのように変数を挿入しましたか?」の記事を参照してください)。getTarget メソッドは必要な場合にのみ呼び出されますしたがって、 @Lazy アノテーションが行うことは、Bean の各属性に値を注入するときに、Spring コンテナで注入されたオブジェクトを見つけることです。今回は、それを探すのではなく、まずプロキシ オブジェクトをその上に配置し、必要に応じて Spring コンテナに移動してそれを見つけます。

フォローアップ ロジックについてはこれ以上は言いませんが、@Autowired に変数を挿入する方法を参照してください。たった 1 つの記事です。

さて、私の友人たちは、@Lazy アノテーションが Spring の循環依存関係をどのように解決するかを理解しました ~ 解決されましたが、回避できる場合は、毎日の開発で回避する必要があります ~ </string></string>

RustDesk 1.2: Flutterを使用してデスクトップ版を書き換え、 deepinで告発されたWaylandをサポート V23は2023年に最も需要の多いWSL 8プログラミング言語への適応に成功: PHPは好調、C/C++需要は鈍化 ReactはAngular.jsの瞬間を経験している? CentOS プロジェクトは「誰にでもオープン」であると主張 MySQL 8.1 および MySQL 8.0.34 が正式にリリース Rust 1.71.0 安定版 リリース
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/lenve/blog/10089610