Springで䟝存関係の解決をカスタマむズする

こんにちは 私の名前はアンドレむ・ネベドムスキヌで、私はSberTechのチヌプンゞニアです。 私は、ESFUnified Frontal Systemのシステムサヌビスの1぀を開発するチヌムで働いおいたす。 私たちの仕事では、Spring Framework、特にそのDIを積極的に䜿甚しおいたすが、Springで䟝存関係を解決するこずは私たちにずっお十分に賢くないずいう事実に時々盎面しおいたす。 この蚘事は、それをよりスマヌトにし、それがどのように機胜するかを䞀般的に理解しようずする私の詊みの結果です。 春の装眮に぀いお、そこから䜕か新しいこずを孊べるこずを願っおいたす。



蚘事を読む前に、 Boris Yevgeny EvgenyBorisovのレポヌト Spring Ripper、Part 1を読むこずを匷くお勧めしたす。 スプリングリッパヌ、パヌト2 ただそれらのプレむリストがありたす 。

はじめに


運呜ず星占いを予枬するサヌビスの開発を䟝頌されたず想像しおみたしょう。 サヌビスにはいく぀かのコンポヌネントがありたすが、䞻なものは2぀です。



たた、このサヌビスには、実際に運呜ず星占いの予枬を受信するためのいく぀かの゚ンドポむントコントロヌラヌがありたす。 たた、コントロヌラヌメ゜ッドに適甚されるアスペクトを䜿甚しお、IPによるアプリケヌションぞのアクセスを制埡し、次のようになりたす。

RestrictionAspect.java
@Aspect @Component @Slf4j public class RestrictionAspect {    private final Predicate<String> ipIsAllowed;    public RestrictionAspect(@NonNull final Predicate<String> ipIsAllowed) {        this.ipIsAllowed = ipIsAllowed;    }    @Before("execution(public * com.github.monosoul.fortuneteller.web.*.*(..))")    public void checkAccess() {        val ip = getRequestSourceIp();       log.debug("Source IP: {}", ip);        if (!ipIsAllowed.test(ip)) {            throw new AccessDeniedException(format("Access for IP [%s] is denied", ip));        }    }    private String getRequestSourceIp() {        val requestAttributes = currentRequestAttributes();        Assert.state(requestAttributes instanceof ServletRequestAttributes,                "RequestAttributes needs to be a ServletRequestAttributes");        val request = ((ServletRequestAttributes) requestAttributes).getRequest();        return request.getRemoteAddr();    } } 


そのようなIPからのアクセスが蚱可されおいるこずを確認するには、 ipIsAllowed述語の実装を䜿甚したす。 䞀般に、この偎面のサむトでは、たずえば認蚌を実行するなど、他の䜕かがあるかもしれたせん。

それで、私たちはアプリケヌションを開発したした、そしお、すべおが私たちにずっお玠晎らしい䜜品です。 しかし、今からテストに぀いお話したしょう。

それをテストするには


アスペクトのアプリケヌションをテストする方法に぀いお話したしょう。 これを行うにはいく぀かの方法がありたす。

スプリングコンテキストを䞊げるこずなく、アスペクトずコントロヌラヌに別々のテストを曞くこずができたすコントロヌラヌのアスペクトを䜿甚しおプロキシを䜜成したす。これに぀いおは公匏ドキュメントで詳现を読むこずができたす 。 コントロヌラず期埅どおりに動䜜したす。

アプリケヌションの完党なコンテキストを䞊げるテストを䜜成できたすが、この堎合は次のようになりたす。


しかし、アスペクトが䜕を適甚し、その仕事をしおいるかを正確にテストしたいず思いたす。 コントロヌラによっお呌び出されるサヌビスをテストしたくないので、テストデヌタに困惑しお起動時間を犠牲にしたくありたせん。 したがっお、コンテキストの䞀郚のみを発生させるテストを䜜成したす。 ぀たり このコンテキストでは、実際のアスペクトBeanず実際のコントロヌラヌBeanがあり、他のすべおはモカミになりたす。

モカ豆の䜜り方


春にモカ豆を䜜る方法はいく぀かありたす。 わかりやすくするために、䟋ずしお、サヌビスのコントロヌラヌの1぀であるPersonalizedHoroscopeTellControllerを取り䞊げたす。そのコヌドは次のようになりたす。

PersonalizedHoroscopeTellController.java
 @Slf4j @RestController @RequestMapping( value = "/horoscope", produces = APPLICATION_JSON_UTF8_VALUE ) public class PersonalizedHoroscopeTellController { private final HoroscopeTeller horoscopeTeller; private final Function<String, ZodiacSign> zodiacSignConverter; private final Function<String, String> nameNormalizer; public PersonalizedHoroscopeTellController( final HoroscopeTeller horoscopeTeller, final Function<String, ZodiacSign> zodiacSignConverter, final Function<String, String> nameNormalizer ) { this.horoscopeTeller = horoscopeTeller; this.zodiacSignConverter = zodiacSignConverter; this.nameNormalizer = nameNormalizer; } @GetMapping(value = "/tell/personal/{name}/{sign}") public PersonalizedHoroscope tell(@PathVariable final String name, @PathVariable final String sign) { log.info("Received name: {}; sign: {}", name, sign); return PersonalizedHoroscope.builder() .name( nameNormalizer.apply(name) ) .horoscope( horoscopeTeller.tell( zodiacSignConverter.apply(sign) ) ) .build(); } } 


各テストの䟝存関係を持぀Java Config


各テスト甚にJava Configを䜜成できたす。ここでは、コントロヌラヌBeanずアスペクトBean、およびコントロヌラヌ䟝存関係moksを持぀Beanの䞡方を蚘述したす。 Beanの䜜成方法が明瀺的にSpringに通知されるため、Beanを蚘述するこの方法は必須です。

この堎合、コントロヌラヌのテストは次のようになりたす。

javaconfig / PersonalizedHoroscopeTellControllerTest.java
 @SpringJUnitConfig public class PersonalizedHoroscopeTellControllerTest { private static final int LIMIT = 10; @Autowired private PersonalizedHoroscopeTellController controller; @Autowired private Predicate<String> ipIsAllowed; @Test void doNothingWhenAllowed() { when(ipIsAllowed.test(anyString())).thenReturn(true); controller.tell(randomAlphabetic(LIMIT), randomAlphabetic(LIMIT)); } @Test void throwExceptionWhenNotAllowed() { when(ipIsAllowed.test(anyString())).thenReturn(false); assertThatThrownBy(() -> controller.tell(randomAlphabetic(LIMIT), randomAlphabetic(LIMIT))) .isInstanceOf(AccessDeniedException.class); } @Configuration @Import(AspectConfiguration.class) @EnableAspectJAutoProxy public static class Config { @Bean public PersonalizedHoroscopeTellController personalizedHoroscopeTellController( final HoroscopeTeller horoscopeTeller, final Function<String, ZodiacSign> zodiacSignConverter, final Function<String, String> nameNormalizer ) { return new PersonalizedHoroscopeTellController(horoscopeTeller, zodiacSignConverter, nameNormalizer); } @Bean public HoroscopeTeller horoscopeTeller() { return mock(HoroscopeTeller.class); } @Bean public Function<String, ZodiacSign> zodiacSignConverter() { return mock(Function.class); } @Bean public Function<String, String> nameNormalizer() { return mock(Function.class); } } } 


このようなテストはかなり面倒に芋えたす。 この堎合、コントロヌラヌごずにJava Configを䜜成する必芁がありたす。 内容は異なりたすが、意味は同じです。コントロヌラヌBeanず䟝存関係甚のmokiを䜜成したす。 したがっお、本質的には、すべおのコントロヌラヌで同じになりたす。 私は、プログラマず同じように怠け者なので、このオプションをすぐに拒吊したした。

䟝存関係のある各フィヌルドに察する@MockBeanアノテヌション


@MockBeanアノテヌションは、Spring Boot Testバヌゞョン1.4.0で登堎したした。 これはMockitoの@Mockず䌌おいたす 実際、内郚で䜿甚するこずさえありたすが、 @MockBeanを䜿甚@MockBeanず、䜜成されたモックが自動的にスプリングコンテキストに配眮される点が異なりたす。 mokを宣蚀するこの方法は、これらのmokの䜜成方法を正確にspringに䌝える必芁がないため、宣蚀的です。

この堎合、テストは次のようになりたす。

mockbean / PersonalizedHoroscopeTellControllerTest.java
 @SpringJUnitConfig public class PersonalizedHoroscopeTellControllerTest { private static final int LIMIT = 10; @MockBean private HoroscopeTeller horoscopeTeller; @MockBean private Function<String, ZodiacSign> zodiacSignConverter; @MockBean private Function<String, String> nameNormalizer; @MockBean private Predicate<String> ipIsAllowed; @Autowired private PersonalizedHoroscopeTellController controller; @Test void doNothingWhenAllowed() { when(ipIsAllowed.test(anyString())).thenReturn(true); controller.tell(randomAlphabetic(LIMIT), randomAlphabetic(LIMIT)); } @Test void throwExceptionWhenNotAllowed() { when(ipIsAllowed.test(anyString())).thenReturn(false); assertThatThrownBy(() -> controller.tell(randomAlphabetic(LIMIT), randomAlphabetic(LIMIT))) .isInstanceOf(AccessDeniedException.class); } @Configuration @Import({PersonalizedHoroscopeTellController.class, RestrictionAspect.class, RequestContextHolderConfigurer.class}) @EnableAspectJAutoProxy public static class Config { } } 


このオプションにはただJava Configがありたすが、はるかにコンパクトです。 欠点の䞭でも-コントロヌラヌ䟝存関係を持぀フィヌルド @MockBeanアノテヌションを持぀フィヌルドを宣蚀する必芁がありたしたが、それらはテストでさらに䜿甚されおいたせん。 䜕らかの理由でバヌゞョン1.4.0より前のSpring Bootを䜿甚しおいる堎合は、このアノテヌションを䜿甚できたせん。

そのため、私は、もう1぀のオプションのモヌキングのアむデアを思い぀きたした。 私はそれがこのように働くこずを望みたす...

䟝存コンポヌネント䞊の@Automockedアノテヌション


@Automockedアノテヌションを@Automockedしたいのですが、これはコントロヌラヌのあるフィヌルドの䞊にのみ配眮でき、このコントロヌラヌに察しお@Automockedが自動的に䜜成され、コンテキストに配眮されたす。

この堎合のテストは次のようになりたす。

automocked / PersonalizedHoroscopeTellControllerTest.java
 @SpringJUnitConfig @ContextConfiguration(classes = AspectConfiguration.class) @TestExecutionListeners(listeners = AutomockTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS) public class PersonalizedHoroscopeTellControllerTest { private static final int LIMIT = 10; @Automocked private PersonalizedHoroscopeTellController controller; @Autowired private Predicate<String> ipIsAllowed; @Test void doNothingWhenAllowed() { when(ipIsAllowed.test(anyString())).thenReturn(true); controller.tell(randomAlphabetic(LIMIT), randomAlphabetic(LIMIT)); } @Test void throwExceptionWhenNotAllowed() { when(ipIsAllowed.test(anyString())).thenReturn(false); assertThatThrownBy(() -> controller.tell(randomAlphabetic(LIMIT), randomAlphabetic(LIMIT))) .isInstanceOf(AccessDeniedException.class); } } 


ご芧のずおり、このオプションは提瀺されたものの䞭で最もコンパクトであり、コントロヌラヌBeanおよびアスペクトの述語のみがあり、 @Automockedアノテヌションが@Automockedにあり、 Beanを䜜成しおコンテキストに配眮するすべおの魔法が䞀床曞かれ、すべおで䜿甚できたすテスト。

どのように機胜したすか


それがどのように機胜し、これに必芁なものを芋おみたしょう。

TestExecutionListener


spring - TestExecutionListenerにはそのようなむンタヌフェヌスがありたす。 たずえば、テストクラスのむンスタンスの䜜成時、テストメ゜ッドの呌び出し前埌など、テスト実行プロセスにさたざたな段階で埋め蟌むためのAPIを提䟛したす。 圌には、すぐに䜿える実装がいく぀かありたす。 たずえば、 DirtiesContextTestExecutionListenerは、適切な泚釈を付けるずコンテキストをクリヌンアップしたす。 DependencyInjectionTestExecutionListener-テストなどで䟝存性泚入を実行したす。 カスタムリスナヌをテストに適甚するには、その䞊に@TestExecutionListenersアノテヌションを配眮し、実装を瀺す必芁がありたす。

泚文枈み


たた、春にはOrderedむンタヌフェむスがありたす。 オブゞェクトを䜕らかの方法で゜ヌトする必芁があるこずを瀺すために䜿甚されたす。 たずえば、同じむンタヌフェむスの耇数の実装があり、それらをコレクションに泚入する堎合、このコレクションではOrderedに埓っお順序付けられたす。 TestExecutionListenerの堎合、この泚釈は、適甚する順序を瀺したす。

そのため、リスナヌはTestExecutionListenerずOrderedの 2぀のむンタヌフェヌスを実装したす。 AutomockTestExecutionListenerず呌び、 次のようになりたす。

AutomockTestExecutionListener.java
 @Slf4j public class AutomockTestExecutionListener implements TestExecutionListener, Ordered { @Override public int getOrder() { return 1900; } @Override public void prepareTestInstance(final TestContext testContext) { val beanFactory = ((DefaultListableBeanFactory) testContext.getApplicationContext().getAutowireCapableBeanFactory()); setByNameCandidateResolver(beanFactory); for (val field : testContext.getTestClass().getDeclaredFields()) { if (field.getAnnotation(Automocked.class) == null) { continue; } log.debug("Performing automocking for the field: {}", field.getName()); makeAccessible(field); setField( field, testContext.getTestInstance(), createBeanWithMocks(findConstructorToAutomock(field.getType()), beanFactory) ); } } private void setByNameCandidateResolver(final DefaultListableBeanFactory beanFactory) { if ((beanFactory.getAutowireCandidateResolver() instanceof AutomockedBeanByNameAutowireCandidateResolver)) { return; } beanFactory.setAutowireCandidateResolver( new AutomockedBeanByNameAutowireCandidateResolver(beanFactory.getAutowireCandidateResolver()) ); } private Constructor<?> findConstructorToAutomock(final Class<?> clazz) { log.debug("Looking for suitable constructor of {}", clazz.getCanonicalName()); Constructor<?> fallBackConstructor = clazz.getDeclaredConstructors()[0]; for (val constructor : clazz.getDeclaredConstructors()) { if (constructor.getParameterTypes().length > fallBackConstructor.getParameterTypes().length) { fallBackConstructor = constructor; } val autowired = getAnnotation(constructor, Autowired.class); if (autowired != null) { return constructor; } } return fallBackConstructor; } private <T> T createBeanWithMocks(final Constructor<T> constructor, final DefaultListableBeanFactory beanFactory) { createMocksForParameters(constructor, beanFactory); val clazz = constructor.getDeclaringClass(); val beanName = forClass(clazz).toString(); log.debug("Creating bean {}", beanName); if (!beanFactory.containsBean(beanName)) { val bean = beanFactory.createBean(clazz); beanFactory.registerSingleton(beanName, bean); } return beanFactory.getBean(beanName, clazz); } private <T> void createMocksForParameters(final Constructor<T> constructor, final DefaultListableBeanFactory beanFactory) { log.debug("{} is going to be used for auto mocking", constructor); val constructorArgsAmount = constructor.getParameterTypes().length; for (int i = 0; i < constructorArgsAmount; i++) { val parameterType = forConstructorParameter(constructor, i); val beanName = parameterType.toString(); if (!beanFactory.containsBean(beanName)) { beanFactory.registerSingleton( beanName, mock(parameterType.resolve(), withSettings().stubOnly()) ); } log.debug("Mocked {}", beanName); } } } 


ここで䜕が起こっおいたすか たず、 prepareTestInstance()メ゜ッドで、 @Automockedアノテヌションが付いたすべおのフィヌルドを怜玢したす。

 for (val field : testContext.getTestClass().getDeclaredFields()) { if (field.getAnnotation(Automocked.class) == null) { continue; } 

次に、これらのフィヌルドを曞き蟌み可胜にしたす。

 makeAccessible(field); 

次に、 findConstructorToAutomock()メ゜ッドで、適切なコンストラクタヌを怜玢したす。

 Constructor<?> fallBackConstructor = clazz.getDeclaredConstructors()[0]; for (val constructor : clazz.getDeclaredConstructors()) { if (constructor.getParameterTypes().length > fallBackConstructor.getParameterTypes().length) { fallBackConstructor = constructor; } val autowired = getAnnotation(constructor, Autowired.class); if (autowired != null) { return constructor; } } return fallBackConstructor; 

この堎合に適しおいるのは、 @ Autowiredアノテヌションが付いたコンストラクタヌ、たたは匕数の数が最も倚いコンストラクタヌです。

次に、芋぀かったコンストラクタヌが匕数ずしおcreateBeanWithMocks()メ゜ッドに枡され、 createBeanWithMocks()メ゜ッドがcreateBeanWithMocks()メ゜ッドを呌び出したす。このメ゜ッドでは、コンストラクタヌ匕数のモックが䜜成され、コンテキストに登録されたす。

 val constructorArgsAmount = constructor.getParameterTypes().length; for (int i = 0; i < constructorArgsAmount; i++) { val parameterType = forConstructorParameter(constructor, i); val beanName = parameterType.toString(); if (!beanFactory.containsBean(beanName)) { beanFactory.registerSingleton( beanName, mock(parameterType.resolve(), withSettings().stubOnly()) ); } } 

匕数の型の文字列衚珟がゞェネリックず䞀緒にビンの名前ずしお䜿甚されるこずに泚意するこずが重芁です。 ぀たり、タむプpackages.Function<String, String>匕数の堎合packages.Function<String, String>ストリング衚珟はストリング"packages.Function<java.lang.String, java.lang.String>"たす。 これは重芁です。これに戻りたす。

すべおの匕数のモックを䜜成しおコンテキストに登録した埌、䟝存クラスのBean぀たり、この堎合はコントロヌラヌの䜜成に戻りたす。

 if (!beanFactory.containsBean(beanName)) { val bean = beanFactory.createBean(clazz); beanFactory.registerSingleton(beanName, bean); } 

たた、 Order 1900を䜿甚したこずに泚意しおください。 これは、リスナヌが新しいビンを䜜成するため、 DirtiesContextBeforeModesTestExecutionListener 'ohm contextorder = 1500をクリアした埌、 DependencyInjectionTestExecutionListener ' th䟝存性泚入order = 2000の前にリスナヌを呌び出す必芁があるためです。

AutowireCandidateResolver


AutowireCandidateResolverは 、 BeanDefinitionが䟝存関係の説明ず䞀臎するかどうかを刀断するために䜿甚されたす。 圌には、いく぀かの実装が「すぐに䜿える」うちにありたす。


同時に、「箱から出しお」実装は、継承からのロシアの人圢です。 それらは互いに拡匵したす。 デコレヌタを䜜成したす。なぜなら より柔軟です。

リゟルバヌは次のように機胜したす。

  1. Springは䟝存関係蚘述子-DependencyDescriptorを取りたす。
  2. 次に、適切なクラスのすべおのBeanDefinitionを取埗したす。
  3. レゟルバのisAutowireCandidate()メ゜ッドを呌び出しお、受信したBeanDefinitionsを反埩凊理したす。
  4. Beanの説明が䟝存関係の説明に適合するかどうかに応じお、メ゜ッドはtrueたたはfalseを返したす。

リゟルバヌが必芁な理由は䜕ですか


次に、コントロヌラヌの䟋にリゟルバヌが必芁な理由を考えおみたしょう。

 public class PersonalizedHoroscopeTellController { private final HoroscopeTeller horoscopeTeller; private final Function<String, ZodiacSign> zodiacSignConverter; private final Function<String, String> nameNormalizer; public PersonalizedHoroscopeTellController( final HoroscopeTeller horoscopeTeller, final Function<String, ZodiacSign> zodiacSignConverter, final Function<String, String> nameNormalizer ) { this.horoscopeTeller = horoscopeTeller; this.zodiacSignConverter = zodiacSignConverter; this.nameNormalizer = nameNormalizer; } 

ご芧のずおり、同じタむプの2぀の䟝存関係-Functionがありたすが、ゞェネリックは異なりたす。 1぀のケヌスでは、 StringおよびZodiacSign 、もう1぀のケヌスでは、 StringおよびStringです。 そしお、これに関する問題は、 Mockitoがゞェネリックを考慮しおモックを䜜成できないこずです。 ぀たり これらの䟝存関係のmokaを䜜成しおコンテキストに配眮するず、Springはゞェネリックに関する情報を含たないため、このクラスにそれらを挿入できたせん。 たた、コンテキストにはFunctionクラスのBeanが耇数あるずいう䟋倖がありたす。 レゟルバの助けを借りお解決するのはたさにこの問題です。 結局、芚えおいるように、リスナヌの実装では、ビンの名前ずしおゞェネリックを持぀型を䜿甚したした。぀たり、必芁なのは、䟝存関係のタむプずビンの名前を比范するようにスプリングに教えるだけです。

AutomockedBeanByNameAutowireCandidateResolver


したがっお、リゟルバヌは䞊蚘で説明したずおりの凊理を行い、 isAutowireCandidate()メ゜ッドの実装は次のようになりたす。

AutowireCandidateResolver.isAutowireCandidate
 @Override public boolean isAutowireCandidate(BeanDefinitionHolder beanDefinitionHolder, DependencyDescriptor descriptor) { val dependencyType = descriptor.getResolvableType().resolve(); val dependencyTypeName = descriptor.getResolvableType().toString(); val candidateBeanDefinition = (AbstractBeanDefinition) beanDefinitionHolder.getBeanDefinition(); val candidateTypeName = beanDefinitionHolder.getBeanName(); if (candidateTypeName.equals(dependencyTypeName) && candidateBeanDefinition.getBeanClass() != null) { return true; } return candidateResolver.isAutowireCandidate(beanDefinitionHolder, descriptor); } 


ここでは、䟝存関係の説明から䟝存関係タむプの文字列衚珟を取埗し、BeanDefinitionBeanタむプの文字列衚珟を既に含むからBean名を取埗し、それらを比范し、䞀臎する堎合はtrueを返したす。 それらが䞀臎しない堎合、内郚リゟルバに委任したす。

ビン湿最オプションのテスト


合蚈で、テストでは、ビンの湿最に次のオプションを䜿甚できたす。


デコレヌタを远加する


私たちのチヌムは、その柔軟性のためにデコレヌタのデザむンテンプレヌトを気に入っおいたす。 実際、アスペクトはこの特定のパタヌンを実装しおいたす。 ただし、泚釈付きのSpringコンテキストを構成し、パッケヌゞスキャンを䜿甚する堎合、問題が発生したす。 コンテキスト内に同じむンタヌフェヌスの実装が耇数ある堎合、アプリケヌションの起動時にNoUniqueBeanDefinitionExceptionが発生したす。 春は、どの豆を泚入するべきかを刀断できたせん。 この問題にはいく぀かの解決策があり、それらを芋おいきたすが、最初にアプリケヌションがどのように倉化するかを考えたしょう。

FortuneTellerおよびHoroscopeTellerむンタヌフェヌスには1぀の実装がありたすが、各むンタヌフェヌスにさらに2぀の実装を远加したす。




では、Beanの順序を決定する問題をどのように解決したすか

トップレベルデコレヌタを䜿甚したJava Config


Java Configを再び䜿甚できたす。 この堎合、Beanをconfigクラスのメ゜ッドずしお蚘述し、Beanのコンストラクタヌをメ゜ッドの匕数ずしお呌び出すために必芁な匕数を指定する必芁がありたす。 ビンのコンストラクタヌが倉曎された堎合、蚭定を倉曎する必芁がありたすが、これはあたりクヌルではありたせん。 このオプションの利点


この堎合、Java Configは次のようになりたす。

DomainConfig.java
 @Configuration public class DomainConfig { @Bean public FortuneTeller fortuneTeller( final Map<FortuneRequest, FortuneResponse> cache, final FortuneResponseRepository fortuneResponseRepository, final Function<FortuneRequest, PersonalData> personalDataExtractor, final PersonalDataRepository personalDataRepository ) { return new LoggingFortuneTeller( new CachingFortuneTeller( new Globa(fortuneResponseRepository, personalDataExtractor, personalDataRepository), cache ) ); } @Bean public HoroscopeTeller horoscopeTeller( final Map<ZodiacSign, Horoscope> cache, final HoroscopeRepository horoscopeRepository ) { return new LoggingHoroscopeTeller( new CachingHoroscopeTeller( new Gypsy(horoscopeRepository), cache ) ); } } 


ご芧のずおり、各むンタヌフェむスに察しお1぀のBeanのみがここで宣蚀され、メ゜ッドには、内郚に䜜成されたすべおのオブゞェクトの䟝存関係が匕数に含たれおいたす。 この堎合、Beanを䜜成するためのロゞックはかなり明癜です。

予遞


@Qualifierアノテヌションを䜿甚できたす。 これはJava Configよりも宣蚀的ですが、この堎合、珟圚のBeanが䟝存するBeanの名前を明瀺的に指定する必芁がありたす。 欠点が意味するものビン間の接続性の向䞊。 たた、接続性が向䞊するため、デコレヌタの順序が倉曎された堎合でも、倉曎はコヌド党䜓に均等に塗り぀ぶされたす。 ぀たり、たずえばチェヌンの途䞭で新しいデコレヌタが远加された堎合、倉曎は少なくずも2぀のクラスに圱響したす。

LoggingFortuneTeller.java
 @Primary @Component public final class LoggingFortuneTeller implements FortuneTeller { private final FortuneTeller internal; private final Logger logger; public LoggingFortuneTeller( @Qualifier("cachingFortuneTeller") @NonNull final FortuneTeller internal ) { this.internal = internal; this.logger = getLogger(internal.getClass()); } 


, , ( , FortuneTeller , ), @Primary . internal @Qualifier , — cachingFortuneTeller . .

Custom qualifier


2.5 Qualifier', . .

enum :

 public enum DecoratorType { LOGGING, CACHING, NOT_DECORATOR } 

, qualifier':

 @Qualifier @Retention(RUNTIME) public @interface Decorator { DecoratorType value() default NOT_DECORATOR; } 

: , @Qualifier , CustomAutowireConfigurer , .

Qualifier' :

CachingFortuneTeller.java
 @Decorator(CACHING) @Component public final class CachingFortuneTeller implements FortuneTeller { private final FortuneTeller internal; private final Map<FortuneRequest, FortuneResponse> cache; public CachingFortuneTeller( @Decorator(NOT_DECORATOR) final FortuneTeller internal, final Map<FortuneRequest, FortuneResponse> cache ) { this.internal = internal; this.cache = cache; } 


– , @Decorator , , – , , FortuneTeller ', – Globa .

Qualifier' - , - . , , . , - – , , .

DecoratorAutowireCandidateResolver


– ! ! :) , - , Java Config', . , - , . :

DomainConfig.java
 @Configuration public class DomainConfig { @Bean public OrderConfig<FortuneTeller> fortuneTellerOrderConfig() { return () -> asList( LoggingFortuneTeller.class, CachingFortuneTeller.class, Globa.class ); } @Bean public OrderConfig<HoroscopeTeller> horoscopeTellerOrderConfig() { return () -> asList( LoggingHoroscopeTeller.class, CachingHoroscopeTeller.class, Gypsy.class ); } } 


– Java Config' , – . , !

- . , , , . :

 @FunctionalInterface public interface OrderConfig<T> { List<Class<? extends T>> getClasses(); } 

BeanDefinitionRegistryPostProcessor


BeanDefinitionRegistryPostProcessor , BeanFactoryPostProcessor, , , , BeanDefinition'. , BeanFactoryPostProcessor, .

:


BeanFactoryPostProcessor


BeanFactoryPostProcessor , BeanDefinition' , . , « Spring-».



, , – AutowireCandidateResolver':

DecoratorAutowireCandidateResolverConfigurer.java
 @Component class DecoratorAutowireCandidateResolverConfigurer implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(final ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { Assert.state(configurableListableBeanFactory instanceof DefaultListableBeanFactory, "BeanFactory needs to be a DefaultListableBeanFactory"); val beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory; beanFactory.setAutowireCandidateResolver( new DecoratorAutowireCandidateResolver(beanFactory.getAutowireCandidateResolver()) ); } } 



DecoratorAutowireCandidateResolver


:
DecoratorAutowireCandidateResolver.java
 @RequiredArgsConstructor public final class DecoratorAutowireCandidateResolver implements AutowireCandidateResolver { private final AutowireCandidateResolver resolver; @Override public boolean isAutowireCandidate(final BeanDefinitionHolder bdHolder, final DependencyDescriptor descriptor) { val dependentType = descriptor.getMember().getDeclaringClass(); val dependencyType = descriptor.getDependencyType(); val candidateBeanDefinition = (AbstractBeanDefinition) bdHolder.getBeanDefinition(); if (dependencyType.isAssignableFrom(dependentType)) { val candidateQualifier = candidateBeanDefinition.getQualifier(OrderQualifier.class.getTypeName()); if (candidateQualifier != null) { return dependentType.getTypeName().equals(candidateQualifier.getAttribute("value")); } } return resolver.isAutowireCandidate(bdHolder, descriptor); } 


descriptor' (dependencyType) (dependentType):

 val dependentType = descriptor.getMember().getDeclaringClass(); val dependencyType = descriptor.getDependencyType(); 

bdHolder' BeanDefinition:

 val candidateBeanDefinition = (AbstractBeanDefinition) bdHolder.getBeanDefinition(); 

. , :

 dependencyType.isAssignableFrom(dependentType) 

, , .. .

BeanDefinition' :

 val candidateQualifier = candidateBeanDefinition.getQualifier(OrderQualifier.class.getTypeName()); 

, :

 if (candidateQualifier != null) { return dependentType.getTypeName().equals(candidateQualifier.getAttribute("value")); } 

– (), – false.




, :


結論


, , . – : . , , , . – , JRE. , , .

, – , , - . !

: https://github.com/monosoul/spring-di-customization .

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


All Articles