Коллекция ловушек: аномалия недопустимого связанного оператора Mybaits
Коллекция ловушек: аномалия недопустимого связанного оператора Mybaits
Mybaits Неверный связанный оператор

введение

Неверный оператор привязки — относительно распространенное исключение в Mybaits:

Язык кода:javascript
копировать
org.apache.ibatis.binding.bindingexception: 
invalid bound statement (not found): 
com.dhy.testMapper.query

Независимо от конкретной причины этого исключения, окончательный анализ показывает, что интерфейс преобразователя и соответствующий XML-файл не были успешно связаны.

Существует множество причин, по которым интерфейс преобразователя и XML не могут быть успешно связаны, но большинство причин относительно легко найти. В этом разделе я хотел бы поделиться причиной, которую не так-то легко найти:

  • Эту причину можно сформулировать в одном предложении:
    • В сценарии конфигурации с несколькими источниками данных, если имеется несколько MapperScannerConfigurers и их диапазоны сканирования перекрываются, привязка может завершиться неудачно.
    • Ошибка привязки зависит от порядка выполнения нескольких MapperScannerConfigurers.

Как настроить Mybaits в сценарии с несколькими источниками данных

Предположим, у нас есть два источника данных, один — DataSourceA, а другой — DataSourceB, тогда мы можем настроить его вот так в среде Mybaits-Spring:

Язык кода:javascript
копировать
    @Bean
    public SqlSessionFactory sqlSessionFactoryOfDataSourceA(@Qualifier("datasourceA") DataSource dataSourceA) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSourceA);
        sessionFactoryBean.setConfigLocation(configLocation);
        sessionFactoryBean.setTypeAliasesPackage("com.dhy.mapper");
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:dhy/mapper/*.xml");
        sessionFactoryBean.setMapperLocations(resources);
        return sessionFactoryBean.getObject();
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurerOfDataSourceA() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryOfDataSourceA");
        mapperScannerConfigurer.setBasePackage("com.dhy.mapper");
        return mapperScannerConfigurer;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryOfDataSourceB(@Qualifier("datasourceB") DataSource dataSourceB) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSourceB);
        sessionFactoryBean.setConfigLocation(configLocation);
        sessionFactoryBean.setTypeAliasesPackage("com.dhy.mapper.b");
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:dhy/mapper/*.xml");
        sessionFactoryBean.setMapperLocations(resources);
        return sessionFactoryBean.getObject();
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurerOfDataSourceB() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryOfDataSourceB");
        mapperScannerConfigurer.setBasePackage("com.dhy.mapper.b");
        return mapperScannerConfigurer;
    }
  • SqlSessionFactory имеет следующие две основные функции:
    • CreateSqlSession,И используйте SqlSession для выполнения операторов SQL и управления ядром интерфейса транзакций.
    • Управление информацией о конфигурации. Конфигурация, связи конфигурации, включая информацию о подключении к данным.,Файл сопоставления Конфигурация,Отношения сопоставления объектов и т. д.
  • Основная функция MapperScannerConfigurer — сканирование и регистрация интерфейса Mapper.

Благодаря приведенной выше информации о конфигурации мы можем наблюдать, что диапазоны сканирования MapperScannerA и MapperScannerB перекрываются, то есть MapperScannerA также будет сканировать интерфейс картографа MapperScannerB:

Фактически, по умолчанию mybaits-spring автоматически вводит MapperScanner в IOC. Этот сканер Mapper отличается от двух сканеров, которые мы настроили выше. По умолчанию он запускается на основе собственного диапазона сканирования пакета SpringBoot, рекурсивно сканирует и получает. все Интерфейс, отмеченный аннотацией @Mapper:

Два представленных выше сканера рекурсивно получают все классы по указанному пути к пакету, независимо от того, добавлена ​​ли аннотация @Mapper.

  • Сканер по умолчанию автоматически добавляется классом конфигурации MybatisAutoConfiguration.
Язык кода:javascript
копировать
/**
 Auto-Configuration for Mybatis. 
 Contributes a SqlSessionFactory and a SqlSessionTemplate. 
 If org.mybatis.spring.annotation.MapperScan is used, 
 or a configuration file is specified as a property, 
 those will be considered, 
 otherwise this auto-configuration will attempt to register mappers 
 based on the interface definitions in or under the root auto-configuration package.
 */
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
  ... 
  //Если мы не предоставляем SqlSessionFactory, он будет автоматически внедрен по умолчанию
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    ...
  }
  //Если мы не предоставляем SqlSessionTemplate , один из них будет автоматически введен по умолчанию
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
       ...
  }

  /**
   MapperScan ultimately ends up creating instances of MapperFactoryBean. 
   If MapperScan is used then this auto-configuration is not needed. 
   If it is _not_ used, however, then this will bring in a bean registrar and automatically register components based on the same component-scanning path as Spring Boot itself.
   */
  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

}

MybatisAutoConfiguration будет работать, когда у нас нет Конфигурация sqlSessionFactory и sqlSessionTemplate.,Автоматически помогите нам внедрить,И его внутренний статический класс MapperScannerRegistrarNotFoundConfiguration.,Если мы не используем аннотации MapperScan для указания пути сканирования пакета вручную, мы поможем нам импортировать класс AutoConfiguredMapperScannerRegistrar, который отвечает за сканирование на основе собственного диапазона сканирования пакета SpringBoot.

Язык кода:javascript
копировать
  /**
   This will just scan the same base package as Spring Boot does. 
   If you want more power, you can explicitly use org.mybatis.spring.annotation.MapperScan but this will get typed mappers working correctly, out-of-the-box, similar to using Spring Data JPA repositories.
   */
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private BeanFactory beanFactory;
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      logger.debug("Searching for mappers annotated with @Mapper");
      //сканер картографа, предоставленный mybaits 
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }
        //Получаем путь сканирования пакета текущего проекта SpringBoot
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        ...
        //Настраиваем фильтр на основе аннотации
        scanner.setAnnotationClass(Mapper.class);
        //зарегистрироваться Процесс сканирования пакет Фильтровать ожидаемый результат, установленный пользователем
        //После того, как мы настроили аннотацию выше, она будет инкапсулирована как AnnotationTypeFilter и зарегистрирована в сканере.
        scanner.registerFilters();
        //Сканирование пакетов
        scanner.doScan(StringUtils.toStringArray(packages));
      }...
    }
    ...
  }

Процесс сканирования пакетов

Вызовите метод сканирования класса ClassPathMapperScanner, предоставленный mybaits, для завершения обработки пакета пакета:

ClassPathMapperScanner наследует ClassPathBeanDefinitionScanner. Сканирование пакетов и возможность фильтрации по фильтру во время процесса сканирования уже предусмотрены Spring.

Язык кода:javascript
копировать
  /**
   * Calls the parent search that will search and register all the candidates. Then the registered objects are post
   * processed to set them as MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //Вызов метода doScan ClassPathBeanDefinitionScanner для завершения сканирования пакета и фильтрации фильтра.
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      //mybaits отвечает за обработку определения компонента
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

Для AutoConfiguredMapperScannerRegistrar набор результатов, полученный в результате вышеуказанной фильтрации, представляет собой все классы, помеченные @Mapper, в указанном наборе путей к пакету.

После того, как Mybaits завершит фильтрацию с помощью сканера пакетов Spring, следующим шагом будет дальнейшая обработка квалифицированного BeanDefintion:

При сканировании пакетов получаются интерфейсы сопоставителей, но в конечном итоге mybaits необходимо сгенерировать прокси-объекты для этих интерфейсов, и тип объекта Bean, окончательно введенный пользователем, также должен быть прокси-объектом. Таким образом, тип преобразователя, который фактически внедряется в контейнер с помощью mybaits, — это FactoryBean, то есть его метод getObject отвечает за создание прокси-объекта для текущего интерфейса преобразователя и последующий его возврат.

Язык кода:javascript
копировать
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      ...
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      //Добавляем параметры конструктора для MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      //Добавляем сопоставление отношений внедрения атрибутов
      definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
      //Устанавливаем фактический тип текущего компонента MapperFactoryBeanClass
      definition.setBeanClass(this.mapperFactoryBeanClass);
      //Добавляем сопоставление отношений внедрения атрибутов
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      ...
      //Установили ли мы свойство sqlSessionFactoryBeanName для ClassPathMapperScanner
      // Установка этого атрибута указывает, что интерфейс картографа, сканируемый текущим сканером, в конечном итоге будет передан этому sqlSessionFactory для управления.
      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        //Если установлено, добавляем сопоставление атрибутов
        definition.getPropertyValues().add("sqlSessionFactory",
            //RuntimeBeanReference будет заменен фактическим экземпляром sqlSessionFactoryBean на этапе внедрения свойств компонента.
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        //Или мы напрямую указываем экземпляр объекта sqlSessionFactory, тогда мы можем напрямую заменить RuntimeBeanReference фактическим экземпляром bean-компонента
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
      
      //Если указан sqlSessionTemplate, это также возможно.
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }
     
      //Если ни sqlSessionTemplate, ни sqlSessionFactory не указаны, то этот параметр поддерживает автоматическое внедрение.
      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      //Устанавливаем, нужно ли отложенной загрузке текущего bean-компонента
      definition.setLazyInit(lazyInitialization);
      ...
       //Настройки области
      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }
      ...
    }
  }

МетодprocessBeanDefinitions в основном определяет соответствующие отношения сопоставления внедрения зависимостей для текущего MapperFactoryBean, что эквивалентно ручной спецификации propertyValues.

В дополнение к спецификации кодирования вручную мы также можем указать отношения отображения внедрения зависимостей следующими способами:

  • xmlСпецификация файла конфигурации
  • @AutoWired и другой указанный метод аннотации
  • Метод @Bean заранее вручную устанавливает соответствующие значения атрибутов.,Это экономит шаги внедрения зависимостей.

Еще один момент, на который всем необходимо обратить внимание, это то, что если мы не укажем beanName или экземпляр bean-компонента sqlSessionFactory или sqlSessionTemplate для текущего ClassPathMapperScanner, то сканер включит автоматическое внедрение в соответствии с типом для текущего bean-компонента:

  • Что такое автоматический впрыск?
    • Мы можем вручную указать внедрение с помощью методов, упомянутых выше. отношения отображения зависимостей, так что на этапе populateBean Spring завершает соответствующие внедрения в соответствии с отношениями отображения, указанными постпроцессором bean-компонента, и BeanWrapper в соответствии с отношениями отображения, которые мы установили в соответствии с отношениями отображения, указанными в методе аннотации и сопоставлении. связь, указанная в коллекции propertyValues ​​в текущем BeanDefintion. зависимостей
    • Постпроцессор использует внедрение отражения, а BeanWrapper использует метод установки для завершения внедрения зависимостей.
    • Если отношения отображения, указанные методом аннотации и методом propertyValues, перекрываются, то propertyValues ​​имеет более высокий приоритет.
    • Как и конфигурация отношений сопоставления внедрения зависимостей в xml, соответствующие отношения сопоставления внедрения зависимостей будут сохранены в коллекции propertyValues ​​соответствующего beanDefintion.

Автоматическое внедрение означает, что если мы не укажем вручную отношение отображения внедрения зависимостей с помощью вышеуказанного метода, Spring автоматически попытается выполнить внедрение зависимостей для нашего текущего объекта bean-компонента:

  • Автоматический впрыск делится на два метода: впрыск по типу и впрыск по названию.

Логика вышеописанного процесса отражена в методе populateBean класса AbstractAutowireCapableBeanFactory, который является этапом внедрения атрибутов компонента:

Язык кода:javascript
копировать
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		...
		//Получаем существующую коллекцию PropertyValues ​​в beanDefintion ---в коллекции могут быть разработки картографирование зависимостей
		//Например: Мы передаем текущий bean-компонент через xmlConfiguration или вручную обрабатываем коллекцию PropertyValues, как указано выше.
		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
		//Получаем режим автоматического внедрения текущего компонента — по умолчанию он не включен
		int resolvedAutowireMode = mbd.getResolvedAutowireMode();
		//Если для текущего компонента включен режим автоматического внедрения, определите, следует ли внедрять по имени или по типу.
		//Например: Если режим автоматического внедрения включен и автоматическое внедрение выполняется в соответствии с типом, Spring попытается автоматически внедрить каждое свойство текущего компонента.
		//Если определенные атрибуты могут быть автоматически успешно внедрены в соответствии с типом, то создаем соответствующую реализацию зависимостей Отображение отношений---propertyValue
		//Затем добавляем propertyValue в существующую коллекцию PropertyValues ​​текущего bean-компонента
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
			// Add property values based on autowire by name if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}
			// Add property values based on autowire by type if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}
        //Постпроцессор обрабатывает логику внедрения сопоставления зависимостей, указанную в режиме аннотации, и опускает ее.
		...
        //Наконец, используем BeanWrapper для взаимодействия с модулем преобразования типов Spring
        //По реализации, сохраненной в коллекции PropertyValues отображение взаимосвязей зависимостей, завершение внедрения на основе метода установки зависимостей
		if (pvs != null) {
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}

Проблема 1: однотипные конфликты экземпляров bean-компонентов, вызванные автоматическим внедрением

Как упоминалось выше, без указания sqlSessionTemplate или sqlSessionFactory для ClassPathMapperScanner ClassPathMapperScanner включит режим автоматического внедрения в соответствии с типом для каждого сканируемого MapperFactoryBean:

  • Поскольку в данный момент в контейнере есть два bean-компонента типа SqlSessionFactory, хотя имена bean-компонентов различаются, поскольку они автоматически внедряются в соответствии с типом, исключение будет выдано, если во время будет найдено более одного экземпляра bean-компонента одного и того же типа. автоматический впрыск в зависимости от типа.

Конечно, предпосылкой возникновения этой проблемы является то, что автоматически внедренный ClassPathMapperScanner сканирует интерфейс картографа, отмеченный аннотацией @Mapper.

Есть две идеи решения этой проблемы:

  • Не отмечайте @Mapperаннотацию на Mapperинтерфейсе
  • Отключить автоматическое внедрение ClassPathMapperScanner.,Вы можете попробовать использовать @MapperScanаннотацию, чтобы указать путь сканирования пакета.,Это отключит автоматическое внедрение ClassPathMapperScanner.

Информацию о реализации аннотации @MapperScan см. в дополнительном примечании 2.

Конечно, когда эта проблема возникает при нормальных обстоятельствах, вы можете пометить один из bean-компонентов аннотацией @Primary, чтобы сообщить Spring, что при появлении нескольких потенциальных bean-компонентов приоритет должен отдаваться bean-компоненту, отмеченному аннотацией @Primary.


Проблема 2. Пути сканирования сканеров перекрываются, в результате чего сканеры с низким приоритетом не могут сканировать интерфейс сопоставителя по соответствующему пути к пакету.

В сценарии конфигурации с несколькими источниками данных мы можем одновременно настроить несколько ClassPathMapperScanner и SqlSessionFactory, которые отвечают за сканирование интерфейсов сопоставителя по разным путям пакетов.

Интерфейсы сопоставителя в разных путях пакетов принадлежат разным источникам данных, поэтому они управляются разными SqlSessionFactory:

Как показано на рисунке выше, сканер A и сканер B сканируют разные пути пакетов соответственно, но путь, сканируемый сканером A, перекрывает путь сканера B, и поскольку сканер A имеет приоритет над сканером B, это приведет к тому, что устройство B не сможет сканировать. интерфейс сопоставления соответствующего пакета. Почему это?

Это связано с тем, что метод doScan класса ClassPathBeanDefinitionScanner регистрирует bean-компоненты, сканированные каждым сканером. Если bean-компонент, сканируемый текущим сканером, уже существует в контейнере, текущий сканер пропустит его обработку:

Язык кода:javascript
копировать
	/**
	 Check the given candidate's bean name, determining whether the corresponding bean definition needs to be registered or conflicts with an existing definition.
	 */
	protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
		if (!this.registry.containsBeanDefinition(beanName)) {
			return true;
		}
		BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
		BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
		if (originatingDef != null) {
			existingDef = originatingDef;
		}
		//Определения двух bean-компонентов одинаковы, то есть они совместимы друг с другом
		if (isCompatible(beanDefinition, existingDef)) {
			return false;
		}
		//Если два bean-компонента имеют одинаковое имя, но определения bean-компонента несовместимы, будет выброшен аномальный объект, сообщающий об ошибке, что beanName сопоставлен с двумя разными bean-компонентами.
		throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
				"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
				"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
	}
  • Поскольку путь сканирования сканера A перекрывает путь сканирования сканера B, а сканер A имеет приоритет над B, при выполнении сканера B набор компонентов-кандидатов, который он сканирует, оказывается пустым.
  • В настоящее время, поскольку интерфейс сопоставителя, который изначально должен был управляться SqlSessionFactoryB, был забран сканером A и передан SqlSessionFactoryA для управления, при вызове метода сопоставителя будет выдано исключение недопустимого связанного оператора!

Есть две идеи для решения проблемы, с которой мы столкнулись выше:

  • Пусть сканер B выполнит первым,То есть сканер B, заводской постпроцессор, должен выполняться до запуска A.,Рассмотрите возможность настройки CustomMapperScannerConfigurer.,Затем позвольте ему наследовать Orderинтерфейс или напрямую отметить @Orderannotation в своем классе.
  • Просто установите приоритет CustomMapperScannerConfigurer выше, чем A.

Отметка аннотации @Order в методе @Bean влияет на порядок загрузки компонента. Не заблуждайтесь!

Идея 2 состоит в том, чтобы предотвратить перекрытие пути, сканируемого сканером A, с путем, сканируемого сканером B:

Также обратите внимание на то, чтобы отключить автоматически внедряемый сканер по умолчанию или не отмечать аннотацию @Mapper в интерфейсе картографа.


Дополнительное примечание 1: MapperScannerConfigurer

MapperScannerConfigurer — это, по сути, постпроцессор фабричного компонента Bean, используемый для добавления некоторых дополнительных определений BeanDefinitions в контейнер после завершения инициализации подготовки BeanFactory.

Его основной метод — postProcessBeanDefinitionRegistry:

Язык кода:javascript
копировать
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    //В соответствии со значением, которое мы установили для MapperScannerConfigurer, затем установите его в ClassPathMapperScanner середина
    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));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    //зарегистрироватьсяFilter,Затем запустите сканирование пакет, путь сканирования пакета — это тот, который мы установили в методе @Bean середина
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

Дополнительное примечание 2: Принцип аннотации MapperScan

Основная роль аннотации MapperScan — импорт MapperScannerRegistrar в контейнер. MapperScannerRegistrar наследует интерфейс ImportBeanDefinitionRegistrar и отвечает за регистрацию дополнительных beanDefinitions в контейнере.

Тип компонента, зарегистрированный MapperScannerRegistrar в контейнере, — это не что иное, как MapperScannerConfigurer, представленный выше. Атрибуты в аннотации MapperScan соответствуют атрибутам в классе конфигурации MapperScannerConfigurer один к одному:

Язык кода:javascript
копировать
@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     //Получаем все атрибуты аннотации середина
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    //Регистрация MapperScannerConfigurer
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
    // Тип bean-компонента, который необходимо зарегистрировать, — MapperScannerConfigurer.
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    //Если вы указали сканирование и фильтрацию пакетов по аннотации, то настроим по умолчанию, будет собираться весь интерфейс под указанным пакетом.
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }
    
    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }
    //Указан ли путь сканирования пакета в аннотациисередина
    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));
    //Если путь сканирования пакета не указан, выбирается путь сканирования пакета по умолчанию.
    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
      builder.addPropertyValue("defaultScope", defaultScope);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    // for spring-native
    builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
  • Укажите beanName MapperScannerConfigurer, внедренного в контейнер.
  • Путь к пакету по умолчанию — это имя пакета класса, который в данный момент аннотирован @MapperScan.
Язык кода:javascript
копировать
  private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
    return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
  }

  private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
    return ClassUtils.getPackageName(importingClassMetadata.getClassName());
  }
boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose