@Import
在学习@Import
这个注解时,小编在想一个问题,这个注解的做用是导入一个配置Configuration
类,那到底什么地方会用到它呢?想到咱们工程中也不会使用这个注解去导入配置呀,咱们都是新建一个类xxxxxxConfiguration.java
,而后直接在类里边把全部的Bean
组件啥的都给声明了,下面的代码咱们感受似曾相识,哈哈。java
/**
* 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封装咱们本身的Starterredis
我讲的到底有没有道理呢?让咱们来作个实验。UserConfig
用于配置User
对象,它位于com.example
包下,DemoApplication.java
位于com.example.demo
包下,此时SpringBoot
是无法扫描到UserConfig
并注入User
对象的。spring
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)
复制代码
怎么办呢?解决办法有二种:缓存
@ComponentScan("com.**")
注解一句话搞定@Import
注解引入方法一简单粗暴,看似没啥毛病,但这是创建在你知道bean
对象的大概包路径的基础上的,第三方的jar包中的bean
可并非都是以com
开头命名的,这就尴尬了。 在上面的路径结构基础上,咱们在DemoApplication.java
中加入@Import(UserConfig.class)
这个注解便可解决问题。springboot
另外,@Import
至关于Spring xml配置文件中的<import />
标签。bash
ImportSelector
@Import
注释是让咱们导入一组指定的配置类--@Configuration
修饰的类,类名一旦指定,将所有被解析。相反,ImportSelector
将容许咱们根据条件动态选择想导入的配置类,换句话说,它具备动态性。ImportSelector
使用时,咱们要建立一个类实现ImportSelector
接口,并重写其中的String[] selectImports(AnnotationMetadata importingClassMetadata);
方法。数据结构
假设咱们想实现这样一个功能,咱们建立一个CustomImportSelector
类,当使用CustomImportSelector
的元素是类时,咱们返回UserConfig
配置类,当使用CustomImportSelector
的元素是类时,咱们返回StudentConfig
配置类。app
UserConfig
和
StudentConfig
在
DemoApplication
的外层,不然,这两个配置类就会被spring默认解析到了。
/**
*
* @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
// 一、很明显,这里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" };
}
}
复制代码
打开浏览器,调用接口,获得以下返回,证实Student
没有被注入成为bean,而User
成功被注入框架
注解在Spring启动过程当中在哪里被解析? Spring源码版本:5.1.6.RELEASE
小编粗略debug了下源码,这2个注解的解析过程统一在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) {
// 一、若是该配置类被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);
}
}
// 二、若是该配置类被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());
}
// 三、若是该配置类被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
对象列表。
其实通常在项目上,咱们实在是用不到上面的注解。有时候知识咱们学会了,可是咱们总想不出一种应用场景来将技术给用上,好烦。其实并非这样的,了解技术的前因后果,长此以往会给咱们带来不少能力,好比编写更加优秀的代码,更容易看懂框架源码,框架上手快,bug解决速度快,牛逼吹起来会更有逼格。
可是,脱离需求,技术可能意义不是很大,接到一个需求,咱们能够动动脑,看下这个需求能不能用上,就比如下面这张购物车实现图:
像这些商品数量的操做,咱们彻底可使用redis的相关操做来实现,你却非要给我建一张表来存储,固然不是不能够,只是缓存更简单,更高效罢了。以用户id为key,商品id做为field,使用redis哈希这种数据结构便可解决。
小编以为先不急着实现需求,能够先多动动脑筋,看看有什么技术点能够用到,再动手写代码。