你好 我叫Andrey Nevedomsky,我是SberTekh的首席工程师。 我在一个团队中工作,该团队开发ESF(统一正面系统)的系统服务之一。 在我们的工作中,我们积极使用Spring框架,尤其是它的DI,并且不时遇到这样一个事实,即解决Spring中的依赖关系对我们来说不够聪明。 本文是我尝试使其变得更智能并大体上了解其工作原理的结果。 希望您可以从中学习有关弹簧装置的新知识。

在阅读本文之前,我强烈建议您阅读
Boris Yevgeny
EvgenyBorisov的报告:
Spring Ripper,第1部分 ;
弹簧松土器,第2部分 。 仍然有一个
播放列表 。
引言
假设我们被要求开发一种用于预测命运和星座的服务。 我们的服务有几个组成部分,但对我们来说主要是两个:
- Globa,将实现FortuneTeller界面并预测命运;

- 吉普赛人,它将实现HoroscopeTeller界面并创建星座。

同样,在我们的服务中,实际上将有多个端点(控制器)用于获得算命和星座。 而且,我们还将使用将应用于控制器方法的方面来控制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
谓词的某些实现。 通常,在此方面的站点上,可能会有其他一些授权。
因此,我们开发了该应用程序,一切对我们来说都很好。 但是,让我们现在谈论测试。
怎么测试呢?
让我们谈谈如何测试方面的应用。 我们有几种方法可以做到这一点。
您可以为方面和控制器编写单独的测试,而无需提高spring上下文(这只会为控制器提供方面的代理,您可以在官方
文档中阅读更多有关此内容的
信息 ),但是在这种情况下,我们将
无法准确测试将
哪些方面正确应用于控制器和工作完全符合我们的期望 ;
您可以编写测试以提高应用程序的完整上下文,但是在这种情况下:
- 运行测试将需要很长时间,因为 所有垃圾箱都将升起;
- 我们将需要准备有效的测试数据,这些数据可以通过容器之间的整个调用链,而不会同时抛出NPE。
但是,我们想准确测试方面已应用并正在执行的工作。 我们不想测试由控制器调用的服务,因此不想被测试数据所困扰并牺牲启动时间。 因此,我们将编写测试,仅在其中提出部分上下文。 即 在我们的上下文中,将有一个真正的Aspect Bean和一个真正的Controller Bean,其他所有东西都是mokami。
如何制作摩卡豆?
有几种方法可以在春季制作摩卡豆。 为了清楚起见,以一个示例为例,我们采用服务的一个控制器
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以及具有控制器依赖项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
,创建的模拟将自动放置在spring上下文中。 这种声明mok的方法将是声明性的,因为我们不必确切地告诉spring如何创建这些mok。
在这种情况下,测试将如下所示:
模拟bean / 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
批注的字段),即使在测试中不再使用它们。 好吧,如果由于某种原因您使用的Spring Boot版本低于1.4.0,那么您将无法使用此注释。
因此,我想到了另一种替代方案。 我希望它以这种方式工作...
通过@Automocked注释依赖组件
我希望我们拥有
@Automocked
批注,该批注只能与控制器一起放在字段上方,然后会自动为该控制器创建moki并将其放置在上下文中。
这种情况下的测试如下所示:
自动模拟/ 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对其进行排序。 就TestExecutionListener而言,此注释指示应按顺序应用它们。
因此,我们的监听器将实现2个接口:
TestExecutionListener和
Ordered 。 我们称其为
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()
方法,该方法又调用
createMocksForParameters()
方法,在该方法中,将为构造函数参数创建
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()) ); } }
重要的是要注意,参数类型的字符串表示形式(以及泛型)将用作bin的名称。 也就是说,对于类型为
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上下文(order = 1500)之后并在
DependencyInjectionTestExExcutionListener '依赖项注入(order = 2000)之前调用我们的侦听器,因为我们的侦听器会创建新的bin。
AutowireCandidateResolver
AutowireCandidateResolver用于确定
BeanDefinition是否与依赖项描述
匹配 。 他具有“开箱即用”的几种实现,其中包括:
同时,“开箱即用”的实现是从继承的俄罗斯娃娃,即 他们彼此扩大。 我们将编写一个装饰器,因为 它更加灵活。
解析器的工作方式如下:
- Spring需要一个依赖项描述符-DependencyDescriptor ;
- 然后,它采用适当类的所有BeanDefinition ;
- 迭代接收到的BeanDefinitions,调用解析器的
isAutowireCandidate()
方法。
- 根据bin的描述是否与依赖项的描述匹配,该方法返回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; }
如您所见,它具有两个相同类型的依赖项
-Function ,但具有不同的泛型。 在一种情况下,使用
String和
ZodiacSign ,在另一种情况下使用
String和
String 。 而且问题在于,
Mockito无法考虑泛型来创建moks 。 即 如果我们为这些依赖关系创建mokas并将它们放在上下文中,那么Spring将无法将它们注入到此类中,因为它们将不包含有关泛型的信息。 我们将看到一个例外,即上下文中有多个
Function类bean。 正是这个问题,我们将在解析器的帮助下解决。 毕竟,您还记得,在侦听器的实现中,我们使用了具有泛型的类型作为bin的名称,这意味着我们所需要做的就是教导spring将依赖类型与bin的名称进行比较。
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。 如果它们不匹配,它将委派给内部解析器。
测试箱润湿选项
总体而言,在测试中,我们可以使用以下选项进行箱式润湿:
- Java Config-必不可少的,很繁琐的,带有样板,但也许信息量尽可能大;
@MockBean
是声明性的,不如Java Config笨重,但是仍然会以字段形式出现一些小的样板,这些字段具有测试本身未使用的依赖项;
@Automocked
+自定义解析器-测试和样板中的最少代码,但范围可能很窄,仍然需要编写。 但是,如果要确保弹簧正确地创建代理,可能会非常方便。
添加装饰器
我们的团队
喜欢Decorator设计模板的灵活性。 实际上,各个方面都实现了这种特定模式。 但是,如果您使用批注配置spring上下文并使用程序包扫描,则会遇到问题。 如果您在上下文中具有同一接口的多个实现,则在应用程序
启动时 ,将发生
NoUniqueBeanDefinitionException异常 ,即 春天将无法弄清楚应该注入哪种豆。 这个问题有几种解决方案,然后我们将研究它们,但是首先让我们弄清楚我们的应用程序将如何变化。
现在,
FortuneTeller和
HoroscopeTeller接口具有一个实现,我们将为每个接口添加2个实现:

- 缓存...-缓存装饰器;
- Logging ...是日志装饰器。
那么,如何解决确定豆类顺序的问题呢?
带有顶级装饰器的Java Config
您可以再次使用Java Config。 在这种情况下,我们将把bean描述为config类的方法,并且必须指定调用bean的构造函数所需的参数作为该方法的参数。 由此可以得出,如果bin的构造函数发生变化,我们将不得不更改配置,这不是很酷。 此选项的优点:
- 装饰器之间的连接性将较低,因为 它们之间的连接将在配置中进行描述,即 他们对彼此一无所知;
- 装饰器顺序的所有更改都将集中在一个位置-配置。
在我们的例子中,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 ) ); } }
如您所见,对于每个接口,这里仅声明一个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, .
:
- BeanDefinition';
- BeanDefinition' , OrderConfig '. , .. BeanDefinition' ;
- , OrderConfig ', BeanDefinition', , () .
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.
, :
- Java Config – , , , ;
@Qualifier
– , - ;
- Custom qualifier – , Qualifier', ;
- - – , , , .
结论
, , . – : . , , , . – , JRE. , , .
, – , , - . 感谢您的阅读!
所有资源均可在以下网址获得:https://github.com/monosoul/spring-di-customization。