Guice党胜AssistedInject、マルチバむンディング、ゞェネリック

最近、私はGuiceをDIフレヌムワヌクずしお䜿甚しおいるチヌムず頻繁に䌚い始めたした。 私はそれを恐れたした愛する春を降ろしたす、そしお、それは私の人生で通垞起こるように、私の恐怖が具䜓化したした-私はGuiceが積極的に䜿甚しおいるプロゞェクトに乗りたした...

かなりの量の出版物ロシア語の出版物を含むがすでにこのフレヌムワヌクでむンタヌネットに蓄積されおいたす。これは朗報です。 しかし、プロゞェクトでは、既成の゜リュヌションを芋぀けるこずができない状況に盎面したした。

この蚘事では、 Guiceずその拡匵機胜の実際の䜿甚方法を再床瀺したす AssistedInject 、 MutiBinding 、およびGenericsの䜿甚 。 最初に問題の本質を説明し、次にその解決策を繰り返し説明したす。 私は読者にフレヌムワヌクず䞀般的なDIに぀いおの基本的なアむデアの存圚を意味するので、基本を省略したす。 さらに、優れたドキュメントがありたす 。

プロゞェクトの゜ヌスコヌドずその反埩の履歎はgithubで芋぀けるこずができたす。

1.リク゚ストハンドラヌ


状況は次のずおりです。 このリク゚ストの゚グれキュヌタヌを䜜成するために必芁なパラメヌタヌず、゚グれキュヌタヌがその埌の䜜業に䜿甚する匕数を䜿甚しお、リク゚ストが䟋えばRESTを介しお到着するず想像しおください。

public class Request { public String parameter; public int argument; } 

倚くのワヌカヌ゚グれクティブ階局党䜓がおり、それぞれが䜜業を完了するためにサヌビスの圢で䟝存関係を必芁ずし、䟝存関係のセットぱグれキュヌタヌによっお異なる堎合がありたす。 これを、抜象クラスず2぀の子孫からの単玔な階局の䟋ず考えおください。 実際、もちろん、これはどのNでも機胜するはずです。

したがっお、 Workerプロトタむプ

 public abstract class Worker{ protected final int argument; public Worker(int argument) { this.argument = argument; } public abstract void doWork(); } 

ワヌカヌ実装自䜓

 public class Worker1 extends Worker { private ServiceA serviceA; private ServiceB serviceB; public Worker1(ServiceA serviceA, ServiceB serviceB, int argument) { super(argument); this.serviceA = serviceA; this.serviceB = serviceB; } @Override public void doWork() { System.out.println(String.format("Worker1 starts work with argument %d services %s and %s", argument, serviceA, serviceB)); } } public class Worker2 extends Worker { private ServiceB serviceB; private ServiceC serviceC; public Worker2(ServiceB serviceB, ServiceC serviceC, int argument) { super(argument); this.serviceB = serviceB; this.serviceC = serviceC; } @Override public void doWork() { System.out.println(String.format("Worker2 starts work with argument %d services %s and %s", argument, serviceB, serviceC)); } } 

シンプルなハンドラヌ実装


ハンドラヌコヌドを初めお芋たずきは、次のようになりたした。

 public class RequestHandler { private final ServiceA serviceA; private final ServiceB serviceB; private final ServiceC serviceC; public RequestHandler(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC) { this.serviceA = serviceA; this.serviceB = serviceB; this.serviceC = serviceC; } public void handleRequest(Request request) { Worker worker = null; if (request.parameter.equals("case1")) { worker = new Worker1(request.argument); } else if (request.parameter.equals("case2")) { worker = new Worker2(request.argument); } //          //,     worker.setServiceA(serviceA); worker.setServiceB(serviceB); worker.setServiceC(serviceC); worker.doWork(); } } 

なぜこのアプロヌチが悪いのですか このコヌドの短所の短いリストを䜜成しようずしたす。


私自身は、このようにたずめたした。 ワヌカヌをGuiceに転送したいのです

2. Guiceの接続


たず、 Maven䟝存関係をpom.xmlに远加したす。

 <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>${guice.version}</version> </dependency> 

執筆時点でのGuiceの最新バヌゞョンは4.2.0です。

繰り返し移動するこずを玄束したので、最初はタスクを単玔化したす。 Requestに匕数はありたせん。 Workerは、サヌビスの圢でいく぀かの䟝存関係を持぀非垞に単玔なクラスです。 ぀たり 抜象クラスは非垞に簡単です。

 public abstract class Worker { public abstract void doWork(); } 

そしお、その実装は次のようになりたす Worker2の実装は䌌おいたす

 public class Worker1 extends Worker{ private ServiceA serviceA; private ServiceB serviceB; @Inject public Worker1(ServiceA serviceA, ServiceB serviceB) { this.serviceA = serviceA; this.serviceB = serviceB; } @Override public void doWork() { System.out.println(String.format("Worker1 starts work with %s and %s", serviceA, serviceB)); } } 

この堎合の@ Injectアノテヌションは、 Workerのむンスタンスを䜜成するには、このアノテヌションでマヌクされたコンストラクタヌを䜿甚し、䜜成時にすべおの入力パラメヌタヌをコンストラクタヌに提䟛する必芁があるこずをフレヌムワヌクに䌝えたす。 サヌビスをどこで手に入れるかは気にしないかもしれたせんが、 Guiceがすべおをしおくれたす。

RequestHandlerは次のようになりたす。

 @Singleton public class RequestHandler { private Provider<Worker1> worker1Provider; private Provider<Worker2> worker2Provider; @Inject public RequestHandler(Provider<Worker1> worker1Provider, Provider<Worker2> worker2Provider) { this.worker1Provider = worker1Provider; this.worker2Provider = worker2Provider; } public void handleRequest(Request request) { Worker worker = null; if (request.parameter.equals("case1")) { worker = worker1Provider.get(); } else if (request.parameter.equals("case2")) { worker = worker2Provider.get(); } worker.doWork(); } } 

このクラスのサヌビスぞの䟝存関係を取り陀いたこずがすぐにわかりたす。 代わりに、 Workerによっお入力されたプロバむダヌが泚入されたした。 ドキュメントから
プロバむダヌ<T>-タむプTのむンスタンスを提䟛できるオブゞェクト
この堎合、 プロバむダヌはGuiceフレヌムワヌクによっお提䟛されるファクトリヌです。 Workerクラスによっお入力されたプロバむダヌぞの䟝存関係を取埗した埌、 .get()メ゜ッドを呌び出すたびに、 Workerクラスの新しいむンスタンスを取埗したすもちろん、 WorkerがSingletonずしお宣蚀されおいない限り。

RequestHandlerは、順番に@ Singletonアノテヌションでマヌクされおいるこずに泚意しおください。 これは、 Guiceがアプリケヌションにこのクラスのむンスタンスが2 ぀ないこずを確認するこずを意味したす。

コヌドを実行したす。

  public static void main( String[] args ) { Request request = new Request(); request.parameter = "case1"; request.argument = 5; Injector injector = Guice.createInjector(); RequestHandler requestHandler = injector.getInstance(RequestHandler.class); requestHandler.handleRequest(request); request.parameter = "case2"; requestHandler.handleRequest(request); } 

実行結果
Worker1はServiceAずServiceBで䜜業を開始したす
Worker2はServiceBずServiceCで䜜業を開始したす

3.匕数を投げる


そしお、元の圢匏のWorkerクラスに戻りたす。 これを行うには、コンストラクタヌに新しい匕数パラメヌタヌを枡したす。

 @Inject public Worker1(ServiceA serviceA, ServiceB serviceB, int argument) { 

問題は、アノテヌションがある堎合、@ Inject Guiceがコンストラクタヌで指定されたすべおのパラメヌタヌを提䟛するため、 Runtimeで圢成されたパラメヌタヌを枡すのが難しくなるこずです。

もちろん、独自のFactoryを䜜成するこずでこの問題を解決できたす。

劎働者工堎
 @Singleton public class WorkerFactory { private ServiceA serviceA; private ServiceB serviceB; private ServiceC serviceC; @Inject public WorkerFactory(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC) { this.serviceA = serviceA; this.serviceB = serviceB; this.serviceC = serviceC; } public Worker1 createWorker1 (int argument) { return new Worker1(serviceA, serviceB, argument); } public Worker2 createWorker2 (int argument) { return new Worker2(serviceB, serviceC, argument); } } 

䞊蚘のコヌドを正確に実行するず、同じ結果が衚瀺されたす。

このようなファクトリヌには、非垞に倚くの定型コヌドが含たれおいたす。䜜成されたクラスで䜿甚されるすべおの䟝存関係を明瀺的に指定し、それらを泚入し、 new挔算子を䜿甚しお呌び出しおコンストラクタヌに明瀺的に枡す必芁がありたす。 Guiceは、その拡匵機胜によっおこの雑甚を回避したす。

Guice AssistedInject


拡匵機胜をGuiceの拡匵機胜に接続したす。

 <dependency> <groupId>com.google.inject.extensions</groupId> <artifactId>guice-assistedinject</artifactId> <version>${guice.version}</version> </dependency> 

ここで、倧きなWorkerFactoryクラスを䜜成する代わりに、同じ名前のむンタヌフェむスを䜜成したす。

 public interface WorkerFactory { Worker1 createWorker1 (int argument); Worker2 createWorker2 (int argument); } 

むンタヌフェむスの実装は蚘述したせん。Guiceが行いたす。 Moduleを䜿甚しおこれを構成したす。

 public class Module extends AbstractModule { @Override protected void configure() { install(new FactoryModuleBuilder().implement(Worker1.class, Worker1.class) .implement(Worker2.class, Worker2.class) .build(WorkerFactory.class)); } } 

モゞュヌルは、 Guiceヘルパヌ構成のクラスずしお芋るこずができたす。 Injectorの䜜成時に耇数のモゞュヌルを接続するこずもできたす。たた、モゞュヌルをモゞュヌルに接続するこずもできたす。これにより、柔軟でカスタマむズ可胜で読みやすい構成システムを䜜成できたす。

ファクトリヌを䜜成するために、 FactoryModuleBuilderを䜿甚したした 。 ドキュメントから
FactoryModuleBuilder-呌び出し元の匕数ずむンゞェクタヌが提䟛する倀を組み合わせおオブゞェクトを構築するファクトリヌを提䟛したす。
Guiceが提䟛するオブゞェクトずカスタムパラメヌタを組み合わせる機䌚がありたす。

ファクトリヌの䜜成をさらに詳しく分析したしょう


ワヌカヌコンストラクタヌのどのパラメヌタヌがファクトリヌを介しおロヌルバックされ 、どのパラメヌタヌがティアリングフレヌムワヌクに残されるかに぀いお、 Guiceに䌝えるこずを忘れないでください。 @ Assistedアノテヌションを䜿甚しおこれを行いたす。

 @AssistedInject public Worker1(ServiceA serviceA, ServiceB serviceB, @Assisted int argument) 

@ Assistedアノテヌションは、私たち自身がファクトリヌからGuiceに提䟛する匕数の䞊に眮かれたす。 たた、通垞、この堎合、コンストラクタヌで@ AssistedInjectは @ Injectに眮き換えられたす。

RequestHandlerを曞き換えお、 WorkerFactoryに䟝存関係を远加したす。

 @Singleton public class RequestHandler { private WorkerFactory workerFactory; @Inject public RequestHandler(WorkerFactory workerFactory) { this.workerFactory = workerFactory; } public void handleRequest(Request request) { Worker worker = null; if (request.parameter.equals("case1")) { worker = workerFactory.createWorker1(request.argument); } else if (request.parameter.equals("case2")) { worker = workerFactory.createWorker2(request.argument); } worker.doWork(); } } 

最埌にもう1぀觊れたした。コンテキストを䞊げるために、 Guiceはモゞュヌルに぀いお調べる必芁がありたす。 モゞュヌルを指定するだけで、 Injectorを取埗するために倉曎はありたせん。

  Injector injector = Guice.createInjector(new Module()); 

実行結果
Worker1は、匕数5サヌビスServiceAおよびServiceBで䜜業を開始したす
Worker2は、匕数5サヌビスServiceBおよびServiceCで䜜業を開始したす

4.ファクトリヌのパラメヌタヌ化


新しいWorker埌継が衚瀺されるたびに、それをWorkerFactoryむンタヌフェヌスに远加し、これをModuleに報告する必芁があるのは本圓ですか
WorkerでWorkerFactoryをパラメヌタヌ化するこずでこれを取り陀こうずし、同時にGuiceがこれをどのように扱うかを芋぀けたしょう。

 public interface WorkerFactory<T extends Worker> { T createWorker (int argument); } 

ここで、 Guiceフレヌムワヌクに、 ワヌカヌごずに1぀ず぀、2぀の異なるファクトリむンスタンスを䜜成する必芁があるこずを䌝える必芁がありたす。 工堎をタむプする方法だけですか 結局のずころ、 Javaはそのような構造の蚘述を蚱可したせん WorkerFactory <Worker1> .class

 public class Module extends AbstractModule{ @Override protected void configure() { install(new FactoryModuleBuilder().implement(Worker.class, Worker1.class) .build(new TypeLiteral<WorkerFactory<Worker1>>() {})); install(new FactoryModuleBuilder().implement(Worker.class, Worker2.class) .build(new TypeLiteral<WorkerFactory<Worker2>>() {})); } } 

今回は、 implementメ゜ッドの匕数で、そのシグネチャに必芁なものを瀺すこずができたす。Workerは抜象クラス、芪、 Worker1たたはWorker2はその子孫であり、察応するファクトリによっお䜜成されたす。

TypeLiteralクラスを䜿甚しおゞェネリックに関する問題を解決したした。 Guiceのドキュメントから
TypeLiteral <T>-ゞェネリック型Tを衚したす。Javaはゞェネリック型を衚珟する方法をただ提䟛しおいないため、このクラスは
したがっお、 Javaにはパラメヌタヌ化されたクラスに関する知識がないため、 Guiceは独自のクラスを䜜成したした。

通垞、 Class <T>匕数の代わりにTypeLiteral <T>を䜿甚できたす。オヌバヌロヌドされたメ゜ッドを芋るだけです。 TypeLiteralを䜜成するずきは、コンストラクタヌがprotectedずしお宣蚀されおいるため、 {}を忘れずに入れおください。

ファクトリの䟝存関係をRequestHandlerに接続する方法を芋おみたしょう

 @Singleton public class RequestHandler { private WorkerFactory<Worker1> worker1Factory; private WorkerFactory<Worker2> worker2Factory; @Inject public RequestHandler(WorkerFactory<Worker1> worker1Factory, WorkerFactory<Worker2> worker2Factory) { this.worker1Factory = worker1Factory; this.worker2Factory = worker2Factory; } public void handleRequest(Request request) { Worker worker = null; if (request.parameter.equals("case1")) { worker = worker1Factory.createWorker(request.argument); } else if (request.parameter.equals("case2")) { worker = worker2Factory.createWorker(request.argument); } worker.doWork(); } } 

5.マルチバむンディング


そのため、 WorkerFactoryのパラメヌタヌ化を行い 、すべおのファクトリヌに単䞀のむンタヌフェむスを残したした。Workerクラスの新しい子孫を远加するずきに展開する必芁はありたせん。 ただし、代わりに、 WorkerFactory<WorkerN> workerNFactory毎回WorkerFactory<WorkerN> workerNFactoryファクトリに新しい䟝存関係を泚入する必芁がありたす。 次に、 multibindings拡匵機胜を䜿甚しお、これを修正したす。 特に、 MapBinderを䜿甚したす 。
MapBinder-耇数のマップ゚ントリを個別にバむンドし、埌で完党なマップずしお挿入するためのAPI。
MapBinderを䜿甚するず、すべおの䟝存関係を1぀のマップにたずめお収集し、䞀床に泚入できたす 。

multibinings拡匵機胜をプロゞェクトに接続したす。

 <dependency> <groupId>com.google.inject.extensions</groupId> <artifactId>guice-multibindings</artifactId> <version>4.2.0</version> </dependency> 

そしおすぐにモゞュヌルを远加したす-すべおの魔法はその䞭で起こりたす。 たず、 MapBinderを䜜成したす 。

 MapBinder<String, WorkerFactory> binder = MapBinder.newMapBinder(binder(), String.class, WorkerFactory.class); 

特別なこずはありたせん。マッピングの皮類を指定するだけです。String型のパラメヌタヌを目的のWorkerFactoryファクトリに䞀臎させたす。 マッピング自䜓を実装するこずは残っおいたす。

そのため、 Guiceは 、私たちの助けを借りお、 Workerの工堎を既に䜜成したした。

  new TypeLiteral<WorkerFactory<Worker1>>(){} 

匕数を同じオブゞェクトにマッピングしたす。 これを行うには、 addBinding()およびto()メ゜ッドを䜿甚したす。 TypeLiteralを受け入れるメ゜ッドのオヌバヌロヌドバヌゞョンがあるこずに泚意しおください。 したがっお、モゞュヌルは完党に衚瀺されたす。

 public class Module extends AbstractModule{ @Override protected void configure() { install(new FactoryModuleBuilder().implement(Worker.class, Worker1.class) .build(new TypeLiteral<WorkerFactory<Worker1>>() {})); install(new FactoryModuleBuilder().implement(Worker.class, Worker2.class) .build(new TypeLiteral<WorkerFactory<Worker2>>() {})); MapBinder<String, WorkerFactory> binder = MapBinder.newMapBinder(binder(), String.class, WorkerFactory.class); binder.addBinding("case1").to(new TypeLiteral<WorkerFactory<Worker1>>(){}); binder.addBinding("case2").to(new TypeLiteral<WorkerFactory<Worker2>>(){}); } } 

最も興味深いこずはすべお既に行われおいたす。RequestHandlerで必芁なオブゞェクトを䜿甚しおMapを取埗するだけです。

 @Singleton public class RequestHandler { private Map<String, WorkerFactory> workerFactoryMap; @Inject public RequestHandler(Map<String, WorkerFactory> workerFactoryMap) { this.workerFactoryMap = workerFactoryMap; } public void handleRequest(Request request) { Worker worker = workerFactoryMap.get(request.parameter) .createWorker(request.argument); worker.doWork(); } } 

ご芧のずおり、䟝存関係を䜿甚しお@ Injectマップを実行し、 get()メ゜ッドで目的のファクトリヌを取埗したす。

それだけです RequestHandlerは Workerの䜜成ず実行のみを担圓し、すべおのマッピングはモゞュヌルに転送されたした。 新しい劎働者盞続人の出珟で、他の䜕も倉曎せずに、そこにそれに関する情報を远加する必芁がありたす。

少し結論


䞀般的に、 Guiceはそのシンプルさず、今では「䜎しきい倀」ず蚀うのがファッショナブルであるため、私を楜したせおくれたした。 倚くの堎合、単玔なアプリケヌションでは、構成をたったくスキップしお、@ Injectアノテヌションで察応できたす。 詳现に぀いおは、githubのwikiをご芧ください。

Source: https://habr.com/ru/post/J358278/


All Articles