The Autowired injection Service becomes the Mapper proxy of biaomidou

Keywords: Java Spring Spring Boot Spring Cloud

Problem overview

  • A Springboot project uses mybatis plus as the data layer framework

  • Scan Mapper interface with @ MapperScan annotation

    public class DemoApplication {}
  • Use @ Autowired to inject Service in the Controller layer

    • Service interface

      public interface LoginService {}
    • Service implementation class

      public class LoginServiceImpl implement LoginService {}
    • Autowired injection Service

      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

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


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) {

  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  if (StringUtils.hasText(lazyInitialization)) {

  // catalog filter

  // scanning
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

Classpathmappercanner.scan method:

public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    // scanning

	if (this.includeAnnotationConfig) {

	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

  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 = 
			String beanName = 
                this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			if (candidate instanceof AnnotatedBeanDefinition) {
                    	(AnnotatedBeanDefinition) candidate);
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = 
                    new BeanDefinitionHolder(candidate, beanName);
				definitionHolder = AnnotationConfigUtils
                    	scopeMetadata, definitionHolder, this.registry);
                // 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 = 
        // Traverse all class files
		for (Resource resource : resources) {
			if (resource.isReadable()) {
				try {
					MetadataReader metadataReader = 
                    // 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);
						if (isCandidateComponent(sbd)) {
                            // Add to collection and wait for return
				} 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

  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

    // 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.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)) {
          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) {
          new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      if (explicitFactoryUsed) {
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;

    if (!explicitFactoryUsed) {

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;

	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 = 
                            	bw, mbd.allowCaching);
					pvsToUse = ibp.postProcessPropertyValues(
                        pvs, filteredPds, bw.getWrappedInstance(), beanName);
					if (pvsToUse == null) {
				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);
		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) &&
                                    autowiredBeanName, field.getType())) {
							cachedFieldValue = new ShortcutDependencyDescriptor(
									desc, autowiredBeanName, field.getType());
				this.cachedFieldValue = cachedFieldValue;
				this.cached = true;
	if (value != null) {
        // Reflection injection
		field.set(bean, value);

beanFactory.resolveDependency method

public Object resolveDependency(
    DependencyDescriptor descriptor, String requestingBeanName,
	Set<String> autowiredBeanNames, TypeConverter typeConverter)  {

	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 = 
	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()):
                            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
                    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) {

        // 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)) {
                    type, descriptor.getResolvableType(), descriptor);
			result = null;
		if (!ClassUtils.isAssignableValue(type, result)) {
			throw new BeanNotOfRequiredTypeException(
                autowiredBeanName, type, instanceCandidate.getClass());
        // return
		return result;
	} finally {

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")

private LoginService loginService;

Inject using @ Autowired + @Qualifier

private LoginService loginService;


private LoginService loginServiceImpl;

Change the scanning range of @ MapperScan annotation to the exact Mapper package

This is actually the best way.

public class DemoApplication {}

Posted by tiki on Mon, 27 Sep 2021 03:40:49 -0700