Problem overview
-
A Springboot project uses mybatis plus as the data layer framework
-
Scan Mapper interface with @ MapperScan annotation
@MapperScan("org.net5ijy.cloud.*") public class DemoApplication {}
-
Use @ Autowired to inject Service in the Controller layer
-
Service interface
public interface LoginService {}
-
Service implementation class
@Service public class LoginServiceImpl implement LoginService {}
-
Autowired injection Service
@Autowired private LoginService loginService;
-
-
Throw an exception that the statement cannot find when using the Service method
preliminary analysis
- Firstly, the scope of the basePackages package configured with the @ MapperScan annotation may be too large, causing Mybatis to scan the interface of the Service layer as Mapper
- When using @ Autowired injection, Spring does not use impl to implement the class Bean, but uses the proxy of Mybatis, resulting in the Controller layer using the proxy Bean of Mybatis
So why?
- Why did the @ MapperScan annotation scan the Service interface and not let it scan
- Why does @ Autowired inject Mybatis proxy Bean instead of impl implementation class Bean
- Why are there two Service beans in the Spring container when @ Autowired is injected, but the fault that two beans are found is not thrown
MapperScan annotation
To answer the first question, first understand the implementation principle of MapperScan annotation.
Annotation definition
@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 }
This annotation is provided by mybatis Spring. Together with mappercannerregister and mappercannerconfigurer, you can create a proxy for the Mapper interface in coding mode and register it with the Spring container.
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.
Brief description of Import 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.
In the second paragraph, you are allowed to import a @ Configuration class, an implementation class of ImportSelector, and an implementation class of ImportBeanDefinitionRegistrar.
ImportBeanDefinitionRegistrar interface:
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).
This interface defines two overloaded registerBeanDefinitions methods, which can register BeanDefinition with Spring container:
public interface ImportBeanDefinitionRegistrar { default void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { registerBeanDefinitions(importingClassMetadata, registry); } default void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
Mappercannerregister class
The mappercannerregister class implements the ImportBeanDefinitionRegistrar interface. In the registerBeanDefinitions method, it registers the mappercannerconfigurer class in the Spring container, and the mappercannerconfigurer class implements the BeanDefinitionRegistryPostProcessor interface, The postProcessBeanDefinitionRegistry method of this interface will be called after the initialization of bean definition registry. At this time, all bean definitions have been loaded, but the bean has not been initialized.
Mappercannerconfigurer scans all Mapper interfaces and registers factorybean bean bean definition in this postProcessBeanDefinitionRegistry method.
@The implementation principle of Import and how mappercannerregister and mappercannerconfigurer are called are beyond the scope of this article. We can continue to analyze as long as we know the following two points:
- First, the mappercannerregister class will be import ed, and its registerBeanDefinitions will be called. At this time, he will register a mappercannerconfigurer into the bean definition registry
- Secondly, mappercannerconfigurer implements the BeanDefinitionRegistryPostProcessor interface. Its postProcessBeanDefinitionRegistry method will be called after the initialization of bean definition registry is completed to scan all Mapper interfaces
Therefore, you can directly look at the implementation of the postProcessBeanDefinitionRegistry method of the mappercannerconfigurer class to see how it scans the Mapper interface.
Mappercannerconfigurer class
summary
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.
In the first paragraph of the document, you can see that this class will recursively find Mapper interfaces under basePackages, and then register them as mapperfactorybean (he implements the FactoryBean interface). He will only scan interfaces (at least one method) instead of classes.
Mapper scan
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)); } // catalog filter scanner.registerFilters(); // scanning scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
Classpathmappercanner.scan method:
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); // scanning doScan(basePackages); if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }
Classpathmappercanner.doscan method:
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { // ... } else { // It is also important here. Internally, MapperFactoryBean will be registered in bean definition registry // It will be explained in detail later processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
super.doScan method:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // The configured basePackages have only one element, so the for loop will only loop once for (String basePackage : basePackages) { // Scan all interfaces under 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); // Register bean definition registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } /* Set<BeanDefinition> candidates = findCandidateComponents(basePackage); This line of code is more important, that is, scan the interface under the basePackage and create the BeanDefinition registerBeanDefinition(definitionHolder, this.registry); This line of code registers the BeanDefinition to the bean definition registry */
findCandidateComponents method:
public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { // It's this branch return scanCandidateComponents(basePackage); } }
scanCandidateComponents method:
/* The parameter basePackage is the configured mapper scan package */ private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; // All class files under the basePackage package and sub package will be found here Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); // Traverse all class files for (Resource resource : resources) { if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); // Almost no class es are filtered out here // Therefore, all class es under the basePackage package and its sub packages will be scanned // Then encapsulate the BeanDefinition and return it together if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { // Add to collection and wait for return candidates.add(sbd); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException("", ex); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("", ex); } return candidates; } /* The internal details are no longer recorded in detail. It's really complicated */
After the execution of the super.doScan method, return to the classpathmappercanner.doscan method:
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { // ... } else { // Back here processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
processBeanDefinitions method:
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); // Reset beanClass // This.maperfactorybeanclass is org.mybatis.spring.mapper.maperfactorybean // It is the implementation of MapperFactoryBean // After Spring creates an instance of the FactoryBean implementation class, it will call its T getObject() method to get the real Bean // Then put the Bean into the container // MapperFactoryBean will create a Proxy for Mapper interface internally // Most interface scanning frameworks are implemented using Spring FactoryBean + Proxy // The logic of creating an agent inside MapperFactoryBean is beyond the scope of this article and will be omitted for the time being definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); // The following code to set sqlSessionFactory and sqlSessionTemplate will not be executed in most cases because it is not configured 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); } }
At this point, Mapper scan logic is complete.
Solved the first question
Therefore, we can answer the first question: Why did MapperScan annotation scan the Service interface and not let it scan?
Because the basePackages configured in @ MapperScan annotation contain the packages of Service interfaces, Mybatis took these interfaces as Mapper interfaces during doScan, scanned them, and registered the BeanDefinition into the Spring container.
@Autowired annotation principle
To answer the second and third questions, you need to connect the principles of the @ Autowired annotation.
@The Autowired annotation is implemented using the AutowiredAnnotationBeanPostProcessor class.
AutowiredAnnotationBeanPostProcessor class
It is the implementation class of the instantiaawarebeanpostprocessor interface.
Instantiaawarebeanpostprocessor interface:
/* 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; } }
It is usually used to modify the default initialization behavior of a specific Bean, such as proxy creation, dependency injection, etc.
The postProcessProperties(PropertyValues pvs, Object bean, String beanName) method is used to implement @ Autowired dependency injection.
Where do I call the postProcessProperties method
The following is a simple container initialization and Bean creation process:
- Start class run method
- SpringApplication.refresh method
- AbstractApplicationContext.refresh method
- AbstractApplicationContext.finishBeanFactoryInitialization method
- DefaultListableBeanFactory.preInstantiateSingletons method
- AbstractBeanFactory.doGetBean method
- AbstractAutowireCapableBeanFactory.createBean(String, RootBeanDefinition, Object []) method
- AbstractAutowireCapableBeanFactory.doCreateBean method
- AbstractAutowireCapableBeanFactory.populateBean method
There is code implementation of dependency injection in AbstractAutowireCapableBeanFactory.populateBean method:
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; // The postProcessProperties method is called here for dependency injection // You can use debug to trace it 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; } } } // ... }
postProcessProperties method implementation
postProcessProperties method
public PropertyValues postProcessProperties( PropertyValues pvs, Object bean, String beanName) { // Obtain dependency injection meta information, including the type of target Bean and the Field information to be injected InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { // Here is the core logic of dependency injection metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "", ex); } return pvs; }
InjectionMetadata.inject method
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) { // Traverse, inject one by one element.inject(target, beanName, pvs); } } }
element.inject method:
protected void inject(Object bean, String beanName, PropertyValues pvs) { Field field = (Field) this.member; Object value; // If it's the first time you come in, it's all 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 { // Get the dependent Bean from the container // Subsequent details 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) { // Reflection injection ReflectionUtils.makeAccessible(field); field.set(bean, value); } }
beanFactory.resolveDependency method
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 { // The previous branches have little to do with the problems to be solved in this paper, so they are omitted for the time being // Take this branch Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { // To get dependent beans result = doResolveDependency( descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } }
doResolveDependency method:
public Object doResolveDependency( DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) { InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); try { // It has nothing to do with the core process and is omitted for the time being Object shortcut = descriptor.resolveShortcut(this); if (shortcut != null) { return shortcut; } // It has nothing to do with the core process and is omitted for the time being 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())); } } // It has nothing to do with the core process and is omitted for the time being Object multipleBeans = resolveMultipleBeans( descriptor, beanName, autowiredBeanNames, typeConverter); if (multipleBeans != null) { return multipleBeans; } // Starting here is the key logic // Find the Bean matching the injection type from the container. From our coding method, we can determine that there are two: // One is loginservice - > mybatis proxy Bean // One is loginserviceimpl - > the implementation class Bean we wrote Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (isRequired(descriptor)) { // If it is not found and the dependency is necessary, an error of not finding the dependency is thrown raiseNoMatchingBeanFound( type, descriptor.getResolvableType(), descriptor); } return null; } String autowiredBeanName; Object instanceCandidate; if (matchingBeans.size() > 1) { // If more than one Bean is found // Just try to get the most matching Bean autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { // If Bean is not found // An error of expected single matching bean but found ${N} is thrown return descriptor.resolveNotUnique( descriptor.getResolvableType(), matchingBeans); } else { return null; } } instanceCandidate = matchingBeans.get(autowiredBeanName); } else { // We have exactly one match. // Just found one Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); autowiredBeanName = entry.getKey(); instanceCandidate = entry.getValue(); } if (autowiredBeanNames != null) { autowiredBeanNames.add(autowiredBeanName); } // If the Bean is not initialized at this time // You need to use the Spring container to initialize it // Is to call the beanfactory. Get Bean (beanname) method, which returns to Spring's process of creating beans // Beyond the scope of this article, skip 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 return result; } finally { ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); } } protected String determineAutowireCandidate( Map<String, Object> candidates, DependencyDescriptor descriptor) { Class<?> requiredType = descriptor.getDependencyType(); // If you have Primay's Bean, use it directly String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); if (primaryCandidate != null) { return primaryCandidate; } // Priority judgment 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 on the left // ||Try to match the field name and BeanName on the right. If they match, use this Bean // In our scenario, the proxy Bean of Mybatis is returned if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || matchesBeanName(candidateName, descriptor.getDependencyName())) { return candidateName; } } return null; }
Problem solving
Why does @ Autowired inject Mybatis proxy instead of impl implementation class Bean? Why are there two Service beans in the Spring container when @ Autowired is injected, but the fault that two beans are found is not thrown?
Because there are multiple interfaces in the discovery container Bean Try using Primary,Priority Bean Name to make an exact match. If it matches, this is it Bean To inject, and throw it if it is not found expected single matching bean but found ${N}My fault. In this case, Spring There are two in the container LoginService Interfaced Bean: One is loginService -> Mybatis agent Bean,The other is loginServiceImpl -> The implementation class we write Bean. And to be injected field Name and Mybatis agent Bean The name of the proxy is the same, so this proxy is used Bean To inject.
How to solve this problem
Inject using @ Resource("loginServiceImpl")
@Resource("loginServiceImpl") private LoginService loginService;
Inject using @ Autowired + @Qualifier
@Autowired @Qualifier("loginServiceImpl") private LoginService loginService;
perhaps
@Autowired private LoginService loginServiceImpl;
Change the scanning range of @ MapperScan annotation to the exact Mapper package
This is actually the best way.
@MapperScan("org.net5ijy.cloud.mapper") public class DemoApplication {}