在学习 @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) 复制代码
怎么办呢?解决办法有二种:
@ComponentScan("com.**") @Import
方法一简单粗暴,看似没啥毛病,但这是建立在你知道 bean
对象的大概包路径的基础上的,第三方的jar包中的 bean
可并不是都是以 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
的外层,否则,这两个配置类就会被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 // 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" }; } } 复制代码
打开浏览器,调用接口,得到如下返回,证明 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) { // 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
对象列表。
其实一般在项目上,我们实在是用不到上面的注解。有时候知识我们学会了,但是我们总想不出一种应用场景来将技术给用上,好烦。其实并不是这样的,了解技术的来龙去脉,久而久之会给我们带来很多能力,比如编写更加优秀的代码,更容易看懂框架源码,框架上手快,bug解决速度快,牛逼吹起来会更有逼格。
但是,脱离需求,技术可能意义不是很大,接到一个需求,我们可以动动脑,看下这个需求能不能用上,就好比下面这张购物车实现图:
像这些商品数量的操作,我们完全可以使用redis的相关操作来实现,你却非要给我建一张表来存储,当然不是不可以,只是缓存更简单,更高效罢了。以用户id为key,商品id作为field,使用redis哈希这种数据结构即可解决。
小编觉得先不急着实现需求,可以先多动动脑筋,看看有什么技术点可以用到,再动手写代码。