Использование аннотаций @Import, ImportSelector и анализ исходного кода

Spring Boot
Использование аннотаций @Import, ImportSelector и анализ исходного кода

один,@Import

изучение@ImportКогда эта аннотация была сделана, редактор думал о проблеме.Функция этой аннотации заключается в том, чтобы импортировать конфигурациюConfigurationкласс, так где именно он используется? Думая, что наш проект не будет использовать эту аннотацию для импорта конфигурации, мы все создаем новый классxxxxxxConfiguration.java, а затем сразу поставить всеBeanВсе компоненты объявлены, и следующий код кажется нам знакомым, ха-ха.

/**
 * xx配置类,里边会有n个bean
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class CustomConfig {
    @Bean
    public Marker zuulProxyMarkerBean() {
    	return new Marker();
    }
    ......
}

Но задумывались ли вы когда-нибудь о проблеме, когда класс конфигурацииCustomConfigне в@SpringBootApplicationМожет ли он по-прежнему помещаться внутри пакета и его подпакетов? Ответ - нет. потому что это неspringbootв диапазоне сканирования по умолчанию. Подробности можно посмотретьSpringBoot инкапсулирует наш собственный Starter

То, что я говорю, имеет смысл? Давайте проведем эксперимент.UserConfigдля конфигурацииUserОбъект, который находитсяcom.exampleПакет,DemoApplication.javaродыcom.example.demoпакет, на данный моментSpringBootне могу сканироватьUserConfigи вводитьUserобъект.

UserConfig.java

/**
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class UserConfig {
    @Bean
    public User getUser() {
    	return new User();
    }
}

Использование следующей инъекции кода сообщит об ошибке:

@Autowired
private User user;
The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)

Как это сделать? Есть два решения:

  • 1. Используйте@ComponentScan("com.**")Аннотировать предложение
  • 2. Используйте@ImportИмпорт аннотаций

Первый способ простой и грубый, и вроде бы ничего плохого в нем нет, но это исходя из того, что вы знаетеbeanНа основе приблизительного пути к объекту в стороннем пакете jarbeanНо не всеcomНазвано в начале, что смущает. Основываясь на приведенной выше структуре пути, мы имеемDemoApplication.javaПрисоединяйся@Import(UserConfig.class)Эта аннотация решит проблему.

Кроме того,@ImportЭквивалент файла конфигурации Spring xml<import />Этикетка.

два,ImportSelector

@ImportАннотация позволяет нам импортировать указанный набор классов конфигурации --@ConfigurationУкрашенный класс, как только имя класса будет указано, будет проанализирован. Напротив,ImportSelectorПозволит нам динамически выбирать класс конфигурации, который мы хотим импортировать, на основе условий, другими словами, он является динамическим.ImportSelectorПри использовании мы хотим создать класс, реализующийImportSelectorинтерфейс и переопределитьString[] selectImports(AnnotationMetadata importingClassMetadata);метод.

Предположим, мы хотим реализовать такую ​​функцию, мы создаемCustomImportSelectorкласс, при использованииCustomImportSelectorКогда элемент является классом, мы возвращаемUserConfigкласс конфигурации, при использованииCustomImportSelectorКогда элемент является интерфейсом, мы возвращаемStudentConfigКласс конфигурации.

Обратите внимание на иерархию каталогов, чтобы убедиться, чтоUserConfigиStudentConfigсуществуетDemoApplicationВнешний слой, в противном случае эти два класса конфигурации будут разрешены весной по умолчанию.

/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class UserConfig {
	@Bean
	public User getUser() {
		return new User();
	}
}
/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
@Configuration
public class StudentConfig {

	@Bean
	public Student getStudent() {
		return new Student();
	}

}
@SpringBootApplication
// 1、很明显,这里CustomImportSelector修饰的是一个类,我们将会返回UserConfig
@Import(CustomImportSelector.class)
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}
/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月19日
 * @Version 1.0
 */
@RestController
public class MyController {

	@Autowired(required = false)
	private Student student;

	@Autowired(required = false)
	private User user;

	@RequestMapping("/getStudent")
	private String getStudent() {
		return "student=[" + student + "],user=[" + user + "]";
	}

}
/**
 * 
 * @Author jiawei huang
 * @Since 2019年8月26日
 * @Version 1.0
 */
public class CustomImportSelector implements ImportSelector {

	/**
	 * importingClassMetadata:被修饰的类注解信息
	 */
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {

		// 注意,自定义注解这里是拿不到的
		System.out.println(importingClassMetadata.getAnnotationTypes());

		// 如果被CustomImportSelector导入的组件是类,那么我们就实例化UserConfig
		if (!importingClassMetadata.isInterface()) {
			return new String[] { "com.example.UserConfig" };
		}

		// 此处不要返回null
		return new String[] { "com.example.StudentConfig" };
	}
}

Откройте браузер, вызовите интерфейс, get возвращает следующее, чтобы доказатьStudentне вводится как bean-компонент, аUserуспешно введен

В-третьих, поговорим о принципе

Где разрешаются аннотации во время загрузки Spring? Весенняя исходная версия:5.1.6.RELEASE

Редактор грубо отлаживает исходный код, процесс парсинга этих двух аннотаций унифицирован вConfigurationClassParser$DeferredImportSelectorGroupingHandlerв классеprocessImports()Метод реализован, и примерный исходный код метода выглядит следующим образом:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    	Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    
    if (importCandidates.isEmpty()) {
    	return;
    }
    
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    	this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
    	this.importStack.push(configClass);
        try {
        for (SourceClass candidate : importCandidates) {
            // 1、如果该配置类被ImportSelector修饰,则当成ImportSelector进行处理
        	if (candidate.isAssignable(ImportSelector.class)) {
        		// Candidate class is an ImportSelector -> delegate to it to determine imports
        		Class<?> candidateClass = candidate.loadClass();
        		ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        		ParserStrategyUtils.invokeAwareMethods(
        				selector, this.environment, this.resourceLoader, this.registry);
        		if (selector instanceof DeferredImportSelector) {
        			this.deferredImportSelectorHandler.handle(
        					configClass, (DeferredImportSelector) selector);
        		}
        		else {
        			String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
        			Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
        			processImports(configClass, currentSourceClass, importSourceClasses, false);
        		}
        	}
        	// 2、如果该配置类被ImportBeanDefinitionRegistrar修饰,则当成ImportBeanDefinitionRegistrar进行处理
        	else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
        		// Candidate class is an ImportBeanDefinitionRegistrar ->
        		// delegate to it to register additional bean definitions
        		Class<?> candidateClass = candidate.loadClass();
        		ImportBeanDefinitionRegistrar registrar =
        				BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
        		ParserStrategyUtils.invokeAwareMethods(
        				registrar, this.environment, this.resourceLoader, this.registry);
        		configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        	}
        	// 3、如果该配置类被Import修饰,则当成Import进行处理
        	else {
        		// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        		// process it as an @Configuration class
        		this.importStack.registerImport(
        				currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        		processConfigurationClass(candidate.asConfigClass(configClass));
        	}
        }
        }
        catch (BeanDefinitionStoreException ex) {
        throw ex;
        }
        catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
        		"Failed to process import candidates for configuration class [" +
        		configClass.getMetadata().getClassName() + "]", ex);
        }
        finally {
        this.importStack.pop();
        }
    }
}

С момента запуска Spring до выполнения разбора аннотаций общая ссылка вызова выглядит следующим образом:

SpringApplication-refreshContext()->AbstractApplicationContext-refresh()-postProcessBeanFactory()->PostProcessorRegistrationDelegate-invokeBeanDefinitionRegistryPostProcessors()->ConfigurationClassPostProcessor-processConfigBeanDefinitions()->ConfigurationClassParser-parse()->ConfigurationClassParser-processImports()

ConfigurationClassParserдаSpringпредоставлено для разбора@Configurationкласс конфигурации, через который вы получитеConfigurationClassсписок объектов.

4. Резюме

На самом деле, вообще в проекте мы действительно не используем приведенные выше аннотации. Иногда мы усваиваем знания, но не всегда можем придумать сценарий применения технологии, что раздражает. На самом деле это не так.Понимание тонкостей технологий со временем принесет нам много способностей, таких как написание более качественного кода, более легкое понимание исходного кода фреймворка, фреймворк быстро запускается, скорость устранения ошибок высока, а хвастовство будет более мощным.

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

Мы можем использовать связанные операции redis для реализации таких операций, как эти количества товаров, но вы должны построить мне таблицу для их хранения.Конечно, это не невозможно, но кеш проще и эффективнее. С идентификатором пользователя в качестве ключа и идентификатором продукта в качестве поля для решения проблемы можно использовать структуру данных хэша redis.

Редактор считает, что не стоит торопиться сначала реализовать требования, можно сначала мозгом посмотреть, какие технические моменты можно использовать, а потом писать код.