@MapperScan("org.net5ijy.cloud.*") public class DemoApplication {}
在Controller层使用@Autowired注入Service
public interface LoginService {} @Service public class LoginServiceImpl implement LoginService {} @Autowired private LoginService loginService;
在使用Service方法时抛出statement找不到的异常。
那么为什么?
-
-
为什么@Autowired注入的是Mybatis的代理Bean,而不是impl实现类Bean
-
要回答第一个问题,首先要了解一下MapperScan注解的实现原理。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { /** * Base packages to scan for MyBatis interfaces. * Note that only interfaces with at least one method will be * registered; concrete classes will be ignored. */ String[] basePackages() default {}; // Other fields }
这个注解由mybatis-spring提供,配合MapperScannerRegistrar和MapperScannerConfigurer,可以实现编码方式为Mapper接口创建代理,并注册到Spring容器。
Use this annotation to register MyBatis mapper interfaces when using Java Config. It performs when same work as MapperScannerConfigurer via MapperScannerRegistrar.
Either basePackageClasses() or basePackages() (or its alias value()) may be specified to define specific packages to scan. Since 2.0.4, If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.
Indicates one or more component classes to import — typically @Configuration classes.
Provides functionality equivalent to the <import/> element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register(java.lang.Class<?>...)).
@Bean definitions declared in imported @Configuration classes should be accessed by using @Autowired injection. Either the bean itself can be autowired, or the configuration class instance declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly navigation between @Configuration class methods.
May be declared at the class level or as a meta-annotation.
If XML or other non-@Configuration bean definition resources need to be imported, use the @ImportResource annotation instead.
文中的第二段中,允许import一个@Configuration类、ImportSelector的实现类、ImportBeanDefinitionRegistrar的实现类。
ImportBeanDefinitionRegistrar接口:
Interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary.
Along with @Configuration and ImportSelector, classes of this type may be provided to the @Import annotation (or may also be returned from an ImportSelector).
该接口定义了两个重载的registerBeanDefinitions方法,可以向Spring容器注册BeanDefinition:
public interface ImportBeanDefinitionRegistrar { default void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { registerBeanDefinitions(importingClassMetadata, registry); } default void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,在registerBeanDefinitions方法中,他将MapperScannerConfigurer类注册到了Spring容器中,而MapperScannerConfigurer类实现了BeanDefinitionRegistryPostProcessor接口,这个接口的postProcessBeanDefinitionRegistry方法会在bean definition registry初始化完成后被调用,此时所有的bean definition已经加载完毕,但是bean还没有初始化。
而MapperScannerConfigurer就是在这个postProcessBeanDefinitionRegistry方法中扫描所有的Mapper接口并且注册FactoryBean bean definition的。
@Import的实现原理、MapperScannerRegistrar和MapperScannerConfigurer如何被调用不在本文的讨论范围内,我们只要知道以下两点就可以继续分析下去:
所以就可以直接看MapperScannerConfigurer类的postProcessBeanDefinitionRegistry方法实现,看一下他是如何扫描Mapper接口的。
BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and registers them as MapperFactoryBean. Note that only interfaces with at least one method will be registered; concrete classes will be ignored.
The basePackage property can contain more than one package name, separated by either commas or semicolons.
This class supports filtering the mappers created by either specifying a marker interface or an annotation. The annotationClass property specifies an annotation to search for. The markerInterface property specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that match either criteria. By default, these two properties are null, so all interfaces in the given basePackage are added as mappers.
在文档的第一段,可以看到这个类会在basePackages下递归查找Mapper接口,然后将他们注册为MapperFactoryBean(他实现了FactoryBean接口),他只会扫描接口(至少要有一个方法)而不会扫描类。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } // 设置过滤器 scanner.registerFilters(); // 扫描 scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
ClassPathMapperScanner.scan方法:
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); // 扫描 doScan(basePackages); if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }
ClassPathMapperScanner.doScan方法:
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { // ... } else { // 这里也比较重要,内部会将MapperFactoryBean注册到bean definition registry中 // 后面详细说明 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
super.doScan方法:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // 配置的basePackages只有一个元素,所以for循环只会循环一次 for (String basePackage : basePackages) { // 在basePackage下面扫描所有的接口 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils .processCommonDefinitionAnnotations( (AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils .applyScopedProxyMode( scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 注册bean definition registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } /* Set<BeanDefinition> candidates = findCandidateComponents(basePackage); 这行代码比较重要,就是在basePackage下面扫描接口,创建BeanDefinition registerBeanDefinition(definitionHolder, this.registry); 这行代码将BeanDefinition注册到bean definition registry */
findCandidateComponents方法:
public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { // 走的是这个分支 return scanCandidateComponents(basePackage); } }
scanCandidateComponents方法:
/* 参数basePackage就是配置的mapper scan包 */ private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; // 这里会把basePackage包和子包下面的所有class文件都找到 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); // 遍历所有的class文件 for (Resource resource : resources) { if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); // 此处几乎不会过滤掉任何class // 所以只要是在basePackage包和其子包下面的class都会被扫描 // 然后封装BeanDefinition一并返回 if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { // 加到集合等待返回 candidates.add(sbd); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException("", ex); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("", ex); } return candidates; } /* 内部细节就不再详细记录了,确实很复杂 */
super.doScan方法执行完毕之后,又回到ClassPathMapperScanner.doScan方法:
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { // ... } else { // 回到这里 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
processBeanDefinitions方法:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // 重新设置beanClass // this.mapperFactoryBeanClass是org.mybatis.spring.mapper.MapperFactoryBean // 他是MapperFactoryBean的FactoryBean的实现 // Spring在创建了FactoryBean实现类的实例之后,会调用他的T getObject()方法获取真实的Bean // 然后将这个Bean放入容器 // MapperFactoryBean内部会为Mapper接口创建Proxy // 多数的接口扫描的框架都是利用Spring FactoryBean + Proxy方式实现的 // MapperFactoryBean内部创建代理的逻辑不在本文讨论范围,暂时省略 definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); // 以下设置sqlSessionFactory和sqlSessionTemplate的代码在多数情况下都不会执行,因为没有配置 boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { LOGGER.warn(""); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { LOGGER.warn(""); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); } }
到此为止,Mapper扫描逻辑就完成了。
所以,我们就可以回答第一个疑问了:为什么MapperScan注解会扫描Service接口,并没有让他扫描呀?
因为我们在@MapperScan注解配置的basePackages包含了Service接口所在包,Mybatis在doScan时把这些接口也当成了Mapper接口,做了扫描,并且将BeanDefinition注册到了Spring容器。
要回答第二个和第三个问题,需要连接@Autowired注解的原理。
@Autowired注解是使用AutowiredAnnotationBeanPostProcessor类实现的。
他是InstantiationAwareBeanPostProcessor接口的实现类。
InstantiationAwareBeanPostProcessor接口:
/* Subinterface of BeanPostProcessor that adds a before-instantiation callback, and a callback after instantiation but before explicit properties are set or autowiring occurs. Typically used to suppress default instantiation for specific target beans, for example to create proxies with special TargetSources (pooling targets, lazily initializing targets, etc), or to implement additional injection strategies such as field injection. */ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { return null; } default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { return true; } default PropertyValues postProcessProperties( PropertyValues pvs, Object bean, String beanName) throws BeansException { return null; } @Deprecated default PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { return pvs; } }
通常用于修改特定Bean的默认初始化行为,例如创建代理、依赖注入等。
其中postProcessProperties(PropertyValues pvs, Object bean, String beanName)方法被用来实现@Autowired依赖注入。
以下为简单的容器初始化和创建Bean流程:
-
启动类run方法
-
SpringApplication.refresh方法
-
AbstractApplicationContext.refresh方法
-
AbstractApplicationContext.finishBeanFactoryInitialization方法
-
DefaultListableBeanFactory.preInstantiateSingletons方法
-
AbstractBeanFactory.doGetBean方法
-
AbstractAutowireCapableBeanFactory.createBean(String, RootBeanDefinition, Object[])方法
-
AbstractAutowireCapableBeanFactory.doCreateBean方法
-
AbstractAutowireCapableBeanFactory.populateBean方法
在AbstractAutowireCapableBeanFactory.populateBean方法中有依赖注入的代码实现:
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) { // ... PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // 这里调用postProcessProperties方法做依赖注入 // 可以使用debug方式跟踪进去 PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck( bw, mbd.allowCaching); } pvsToUse = ibp.postProcessPropertyValues( pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } // ... }
public PropertyValues postProcessProperties( PropertyValues pvs, Object bean, String beanName) { // 获取依赖注入元信息,包括目标Bean的类型,需要注入的Field信息 InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { // 这里是依赖注入的核心逻辑 metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "", ex); } return pvs; }
public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable { Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { for (InjectedElement element : elementsToIterate) { // 遍历,逐一注入 element.inject(target, beanName, pvs); } } }
element.inject方法:
protected void inject(Object bean, String beanName, PropertyValues pvs) { Field field = (Field) this.member; Object value; // 如果是第一次进来,都是false if (this.cached) { value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); Set<String> autowiredBeanNames = new LinkedHashSet<>(1); TypeConverter typeConverter = beanFactory.getTypeConverter(); try { // 从容器里面获取依赖Bean // 后续详细说明 value = beanFactory .resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(...); } synchronized (this) { if (!this.cached) { Object cachedFieldValue = null; if (value != null || this.required) { cachedFieldValue = desc; registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch( autowiredBeanName, field.getType())) { cachedFieldValue = new ShortcutDependencyDescriptor( desc, autowiredBeanName, field.getType()); } } } this.cachedFieldValue = cachedFieldValue; this.cached = true; } } } if (value != null) { // 反射注入 ReflectionUtils.makeAccessible(field); field.set(bean, value); } }
public Object resolveDependency( DependencyDescriptor descriptor, String requestingBeanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) { 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) { // 去获取依赖Bean result = doResolveDependency( descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } }
doResolveDependency方法:
public Object doResolveDependency( DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) { InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); try { // 与核心流程无关,暂时略过 Object shortcut = descriptor.resolveShortcut(this); if (shortcut != null) { return shortcut; } // 与核心流程无关,暂时略过 Class<?> type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(strVal, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); try { return converter .convertIfNecessary(value, type, descriptor.getTypeDescriptor()); } catch (UnsupportedOperationException ex) { return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()): converter.convertIfNecessary( value, type, descriptor.getMethodParameter())); } } // 与核心流程无关,暂时略过 Object multipleBeans = resolveMultipleBeans( descriptor, beanName, autowiredBeanNames, typeConverter); if (multipleBeans != null) { return multipleBeans; } // 从这里开始,是关键的逻辑 // 从容器里面查找与注入类型匹配的Bean,从我们的编码方式可以确定有两个: // 一个是loginService -> Mybatis代理Bean // 一个是loginServiceImpl -> 我们编写的实现类Bean Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (isRequired(descriptor)) { // 如果没有找到且依赖是必须的,就抛出一个未找到依赖的错 raiseNoMatchingBeanFound( type, descriptor.getResolvableType(), descriptor); } return null; } String autowiredBeanName; Object instanceCandidate; if (matchingBeans.size() > 1) { // 如果找到了多个Bean // 就尝试获取一个最匹配的Bean autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { // 如果没有找到Bean // 则抛出一个expected single matching bean but found ${N}的错 return descriptor.resolveNotUnique( descriptor.getResolvableType(), matchingBeans); } else { return null; } } instanceCandidate = matchingBeans.get(autowiredBeanName); } else { // We have exactly one match. // 正好找到一个 Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); autowiredBeanName = entry.getKey(); instanceCandidate = entry.getValue(); } if (autowiredBeanNames != null) { autowiredBeanNames.add(autowiredBeanName); } // 如果此时Bean还没有初始化 // 需要使用Spring容器初始化一下 // 就是调用的beanFactory.getBean(beanName)方法,这样就回到了Spring的创建Bean的流程 // 不在本文讨论范围,略过 if (instanceCandidate instanceof Class) { instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); } Object result = instanceCandidate; if (result instanceof NullBean) { if (isRequired(descriptor)) { raiseNoMatchingBeanFound( type, descriptor.getResolvableType(), descriptor); } result = null; } if (!ClassUtils.isAssignableValue(type, result)) { throw new BeanNotOfRequiredTypeException( autowiredBeanName, type, instanceCandidate.getClass()); } // 返回 return result; } finally { ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); } } protected String determineAutowireCandidate( Map<String, Object> candidates, DependencyDescriptor descriptor) { Class<?> requiredType = descriptor.getDependencyType(); // 如果有Primay的Bean,就直接使用他 String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); if (primaryCandidate != null) { return primaryCandidate; } // 优先级判断 String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); if (priorityCandidate != null) { return priorityCandidate; } for (Map.Entry<String, Object> entry : candidates.entrySet()) { String candidateName = entry.getKey(); Object beanInstance = entry.getValue(); // ||左侧为false // ||右侧尝试匹配field名和BeanName,如果匹配就使用这个Bean // 在我们的场景下,返回的就是Mybatis的代理Bean了 if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || matchesBeanName(candidateName, descriptor.getDependencyName())) { return candidateName; } } return null; }
为什么@Autowired注入的是Mybatis的代理,而不是impl实现类Bean?为什么@Autowired注入时Spring容器里面有两个Service的Bean,却没有抛出发现了两个Bean的错?
因为在发现容器里面存在某个接口的多个Bean时,就尝试使用Primary、优先级、Bean名称去做精确的匹配,如果匹配到了就是这个Bean来注入,如果没有找到才抛出expected single matching bean but found ${N}的错。
在本例中,Spring容器中存在两个LoginService接口的Bean:一个是loginService -> Mybatis代理Bean,另一个是loginServiceImpl -> 我们编写的实现类Bean。而待注入的field名称与Mybatis代理Bean的名称一致,所以就使用了这个代理Bean来注入。
如何解决这个问题
使用@Resource("loginServiceImpl")方式注入
@Resource("loginServiceImpl") private LoginService loginService;
@Autowired @Qualifier("loginServiceImpl") private LoginService loginService;
或者
@Autowired private LoginService loginServiceImpl;
把@MapperScan注解的扫描范围改到精确的Mapper包
这种方式其实是最好的。
@MapperScan("org.net5ijy.cloud.mapper") public class DemoApplication {}