在学习spring中,发现一段代码 定义接口 public interface BrandDao { @Select("select * from brand where id = #{id}") //配置数据库字段和模型实体类属性映射 @Results({ @Result(column = "brand_name", property = "brandName"), @Result(column = "company_name", property = "companyName") }) Brand findById(Integer id); } 定义一个service类 @Service public class BrandService { @Autowired private BrandDao brandDao; public Brand findById(Integer id) { return brandDao.findById(id); } } 执行方法 public class App { public static void main(String[] args) { ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class); BrandService brandService = ioc.getBean(BrandService.class); Brand brand = brandService.findById(2); System.out.println(brand); } } 上面代码可以正常运行,就是BrandDao接口他并没有实现类,为什么可以通过@Autowired进行注入? PS:应该是SpringConfig代码的这段已经有bean了: //定义bean,返回MapperScannerConfigurer对象 @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.test.dao");//BrandDao接口属于这个包下 return msc; }
用mockito写单测,内部用到的对象是mock的,感觉除了提高单测覆盖率,也不能测出什么问题呀?最近刚写单测,有些不太理解,请帮忙指教一下,谢谢!
这个问题该怎么解决 "image.png" (https://wmprod.oss-cn-shanghai.aliyuncs.com/images/20241225/ade5a0243fb2cdb85f2b68bcb7cb18d4.png)
线程池200,肯定不可能满足所有情况。 系统的业务并发量、CPU数、业务处理时间等等,怎么确定我最后该配置多少tomcat线程合适? 有没有什么公式可以参考
启动springboot项目时报错, 报错信息如下: org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:157) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at cn.ethan.chatgpt.Application.main(Application.java:17) [classes/:na] Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory(ServletWebServerApplicationContext.java:206) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:154) ~[spring-boot-2.1.3.RELEASE.jar:2.1.3.RELEASE] ... 8 common frames omitted 启动具体代码如下: @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } Pom文件依赖如下: org.springframework.boot spring-boot-starter-security 2.1.3.RELEASE org.springframework.boot spring-boot-starter-web 2.7.14 org.springframework.boot spring-boot-starter-tomcat 3.1.2 io.jsonwebtoken jjwt-api 0.11.2 io.jsonwebtoken jjwt-impl 0.11.2 io.jsonwebtoken jjwt-jackson 0.11.2 commons-codec commons-codec 1.15 javax.servlet javax.servlet-api 3.1.0 provided 做过的尝试有: 1. 检查并更换@SpringBootApplication注解 2. 更新pom文件中的starter-tomcat依赖 3. 修改pom文件中的provided 经过@TNT的回答, 我将pom文件中的tomcat依赖注释, 并把starter-web和stater-security的依赖版本号设置成一致后, 问题得到解决.
1.概述之前我们在总结Spring扩展点:后置处理器时谈到了Spring Bean的生命周期和其对Spring框架原理理解的重要性,所以接下来我们就来分析一下Bean生命周期的整体流程。首先Bean就是一些Java对象,只不过这些Bean不是我们主动new出来的,而是交个Spring IOC容器创建并管理的,因此Bean的生命周期受Spring IOC容器控制,Bean生命周期大致分为以下几个阶段:Bean的实例化(Instantiation) :Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化Bean的属性赋值(Populate) :Bean实例化之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充,Bean的属性赋值就是指 Spring 容器根据BeanDefinition中属性配置的属性值注入到 Bean 对象中的过程。Bean的初始化(Initialization) :对Bean实例的属性进行填充完之后还需要执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,并且Spring高频面试题Bean的循环引用问题也是在这个阶段体现的;Bean的使用阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期,接下来Bean就可以被随心所欲地使用了。Bean的销毁(Destruction) :Bean 的销毁是指 Spring 容器在关闭时,执行一些清理操作的过程。在 Spring 容器中, Bean 的销毁方式有两种:销毁方法destroy-method和 DisposableBean 接口。项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用 Github地址:github.com/plasticene/… Gitee地址:gitee.com/plasticene3… 2.Bean生命周期详解和使用案例这里我先纠正一下在Spring扩展点后置处理器总结的描述:之前说BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor,BeanPostProcessor这三个后置处理器的调用时机都在Spring Bean生命周期中是不严谨的,按照上面我们对Bean生命周期的阶段划分,只有BeanPostProcessor作用于Bean的生命周期中,而BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor是针对BeanDefinition的,所以不属于Bean的生命周期中。BeanPostProcessor在Bean生命周期的体现如下图所示:Bean的生命周期和人的一生一样都会经历从出生到死亡,中间是一个漫长且复杂的过程,接下来我们就来整体分析一下Bean生命周期的核心流程和相关接口回调方法的调用时机,同时这里想强调一下Bean的生命周期也是面试的高频考点,对核心流程务必要掌握清楚,这里用一张流程图进行详述展示,是重点、重点、重点。根据上面的Bean生命周期核心流程做如下代码演示示例:Bean定义:@Data @AllArgsConstructor public class Boo implements InitializingBean, DisposableBean, BeanNameAware { private Long id; private String name; public Boo() { System.out.println("boo实例化构造方法执行了..."); } @PostConstruct public void postConstruct() { System.out.println("boo执行初始化@postConstruct注解标注的方法了..."); } @PreDestroy public void preDestroy() { System.out.println("boo执行初始化@preDestroy注解标注的方法了..."); } @Override public void afterPropertiesSet() throws Exception { System.out.println("boo执行InitializingBean的afterPropertiesSet()方法了..."); } @Override public void destroy() throws Exception { System.out.println("boo执行DisposableBean的destroy()方法了..."); } @Override public void setBeanName(String name) { System.out.println("boo执行BeanNameAware的setBeanName()方法了..."); } private void initMethod() { System.out.println("boo执行init-method()方法了..."); } public void destroyMethod() { System.out.println("boo执行destroy-method()方法了..."); } } 实现InstantiationAwareBeanPostProcessor:@Component public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { @Override public Object postProcessBeforeInstantiation(Class<?> BeanClass, String BeanName) throws BeansException { System.out.println("InstantiationAwareBeanPostProcessor的before()执行了...." + BeanName); return null; } @Override public boolean postProcessAfterInstantiation(Object Bean, String BeanName) throws BeansException { System.out.println("InstantiationAwareBeanPostProcessor的after()执行了...." + BeanName); return false; } } 实现BeanPostProcessor:@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object Bean, String BeanName) throws BeansException { System.out.println("BeanPostProcessor的before()执行了...." + BeanName); return Bean; } @Override public Object postProcessAfterInitialization(Object Bean, String BeanName) throws BeansException { System.out.println("BeanPostProcessor的after()执行了...."+ BeanName); return Bean; } } 执行下面的配置类测试方法:@ComponentScan(basePackages = {"com.shepherd.common.config"}) @Configuration public class MyConfig { @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod") public Boo boo() { return new Boo(); } public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); Boo boo = applicationContext.getBean(Boo.class); System.out.println("拿到boo对象了:" + boo); applicationContext.close(); } } 名为boo的Bean相关运行结果如下:beanPostProcessor的before()执行了....myConfig beanPostProcessor的after()执行了....myConfig InstantiationAwareBeanPostProcessor的before()执行了....boo boo实例化构造方法执行了... InstantiationAwareBeanPostProcessor的after()执行了....boo boo执行BeanNameAware的setBeanName()方法了... beanPostProcessor的before()执行了....boo boo执行初始化@postConstruct注解标注的方法了... boo执行InitializingBean的afterPropertiesSet()方法了... boo执行init-method()方法了... beanPostProcessor的after()执行了....boo 拿到boo对象了:Boo(id=null, name=null) boo执行初始化@preDestroy标注的方法了... boo执行DisposableBean的destroy()方法了... boo执行destroy-method()方法了... 根据控制台打印结果可以boo的相关方法执行顺序严格遵从上面流程图,同时当我们执行容器applicationContext的关闭方法close()会触发调用bean的销毁回调方法。3.浅析Bean生命周期源码实现DefaultListableBeanFactory是Spring IOC的Bean工厂的一个默认实现,IOC大部分核心逻辑实现都在这里,可关注。Bean生命周期就是创建Bean的过程,这里我们就不在拐弯抹角兜圈子,直接来到DefaultListableBeanFactory继承的AbstractAutowireCapableBeanFactory的#doCreateBean()方法,之前说过在Spring框架中以do开头的方法都是核心逻辑实现所在protected Object doCreateBean(String BeanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the Bean. // BeanWrapper 是对 Bean 的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装 Bean 的属性描述器 BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { // <1> 单例模型,则从未完成的 FactoryBean 缓存中删除 instanceWrapper = this.factoryBeanInstanceCache.remove(BeanName); } if (instanceWrapper == null) { // <2> 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化 instanceWrapper = createBeanInstance(BeanName, mbd, args); } // 包装的实例对象 Object Bean = instanceWrapper.getWrappedInstance(); // 包装的实例class类型 Class<?> BeanType = instanceWrapper.getWrappedClass(); if (BeanType != NullBean.class) { mbd.resolvedTargetType = BeanType; } // Allow post-processors to modify the merged Bean definition. // <3> 判断是否有后置处理 // 如果有后置处理,则允许后置处理修改 BeanDefinition synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, BeanType, BeanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), BeanName, "Post-processing of merged Bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // <4> 解决单例模式的循环依赖 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(BeanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching Bean '" + BeanName + "' to allow for resolving potential circular references"); } // 提前将创建的 Bean 实例加入到 singletonFactories 中 // 这里是为了后期避免循环依赖 addSingletonFactory(BeanName, () -> getEarlyBeanReference(BeanName, mbd, Bean)); } // Initialize the Bean instance. // 开始初始化 Bean 实例对象 Object exposedObject = Bean; try { // <5> 对 Bean 进行填充,将各个属性值注入,其中,可能存在依赖于其他 Bean 的属性 // 则会递归初始依赖 Bean populateBean(BeanName, mbd, instanceWrapper); // <6> 调用初始化方法 exposedObject = initializeBean(BeanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && BeanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), BeanName, "Initialization of Bean failed", ex); } } // <7> 循环依赖处理 if (earlySingletonExposure) { // 获取 earlySingletonReference Object earlySingletonReference = getSingleton(BeanName, false); // 只有在存在循环依赖的情况下,earlySingletonReference 才不会为空 if (earlySingletonReference != null) { // 如果 exposedObject 没有在初始化方法中被改变,也就是没有被增强 if (exposedObject == Bean) { exposedObject = earlySingletonReference; } // 处理依赖 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(BeanName)) { String[] dependentBeans = getDependentBeans(BeanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(BeanName, "Bean with name '" + BeanName + "' has been injected into other Beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other Beans do not use the final version of the " + "Bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register Bean as disposable. try { // <8> 注册 Bean的销毁逻辑 registerDisposableBeanIfNecessary(BeanName, Bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), BeanName, "Invalid destruction signature", ex); } return exposedObject; } 由上面代码可知,Bean的创建过程核心步骤如下:createBeanInstance(BeanName, mbd, args) 进行Bean的实例化populateBean(BeanName, mbd, instanceWrapper)进行Bean的属性填充赋值initializeBean(BeanName, exposedObject, mbd)处理Bean初始化之后的各种回调事件registerDisposableBeanIfNecessary(BeanName, Bean, mbd)注册Bean的销毁接口解决创建Bean过程中的循环依赖,Spring使用三级缓存解决循环依赖,这也是一个重要的知识点,这里不详细阐述,后面会安排接下来我们就来看看和Bean初始化阶段相关各种回调事件执行方法#initializeBean(),分析一下上面流程图的执行顺序是怎么实现的。protected Object initializeBean(final String BeanName, final Object Bean, RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { invokeAwareMethods(BeanName, Bean); return null; } }, getAccessControlContext()); } else { // 涉及到的回调接口点进去一目了然,代码都是自解释的 // BeanNameAware、BeanClassLoaderAware或BeanFactoryAware invokeAwareMethods(BeanName, Bean); } Object wrappedBean = Bean; if (mbd == null || !mbd.isSynthetic()) { // BeanPostProcessor 的 postProcessBeforeInitialization 回调,这里会执行@PostConstruct标注的方法 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, BeanName); } try { // init-methods // 或者是实现了InitializingBean接口,会调用afterPropertiesSet() 方法 invokeInitMethods(BeanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), BeanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { // BeanPostProcessor 的 postProcessAfterInitialization 回调 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, BeanName); } return wrappedBean; }至于Bean的销毁流程与Bean初始化类似,从上面的使用示例中看可以得出当容器关闭时,才会对Bean销毁方法进行调用。销毁过程是这样的。顺着close()-> doClose() -> destroyBeans() -> destroySingletons() -> destroySingleton() -> destroyBean() -> Bean.destroy(),会看到最终调用Bean的销毁方法。这里就不在展示源码细节啦,有兴趣的话自行去调试查看了解4.总结以上全部就是对Spring Bean生命周期的全面总结, Spring 的 Bean 容器机制是非常强大的,它可以帮助我们轻松地管理 Bean 对象,并且提供了丰富的生命周期回调方法,允许我们在 Bean 的生命周期中执行自己的特定操作,这对于我们平时工作使用中进行增强扩展至关重要,因此掌握Bean的生命周期是必须的。
最近在学习java,ruoyi框架模仿com.ruoyi.framework.config.MybatisConfig,我的方法会报错显示无法自动装配。找不到 'DataSource' 类型的 Bean。但他的我也没看到有写实体类这是怎么做到的? 图一是ruoyi的 "image.png" (https://wmprod.oss-cn-shanghai.aliyuncs.com/images/20241223/1c646e70f67ae75710caae702a990db3.png) 图二是我的 "image.png" (https://wmprod.oss-cn-shanghai.aliyuncs.com/images/20241223/583bfade3c7515aa11533f802c53ee06.png)
条件装配是Spring Boot一大特点,根据是否满足指定的条件来决定是否装配 Bean ,做到了动态灵活性,starter的自动配置类中就是使用@Conditional及其衍生扩展注解@ConditionalOnXXX做到了自动装配的,所以接着之前总结的 Spring Boot自动配置原理和自定义封装一个starter,今天分析一下starter中自动配置类的条件装配注解。项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用。 Github地址:github.com/plasticene/… Gitee地址:gitee.com/plasticene3…1 @Conditional@Conditional:该注解是在spring4中新加的,其作用顾名思义就是按照一定的条件进行判断,满足条件才将bean注入到容器中,注解源码如下:@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition} classes that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); } 从代码中可知,该注解可作用在类,方法上,同时只有一个属性value,是一个Class数组,并且需要继承或者实现Condition接口:@FunctionalInterface public interface Condition { boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2); } @FunctionalInterface:表示该接口是一个函数式接口,即可以使用函数式编程,lambda表达式。Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。总结:@Conditional注解通过传入一个或者多个实现了的Condition接口的实现类,重写Condition接口的matches方法,其条件逻辑在该方法之中,作用于创建bean的地方。根据上面的描述,接下来我模拟多语言环境条件装配切换不同语言的场景:语言类@Data @Builder public class Language { private Long id; private String content; } 条件:public class ChineseCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String property = environment.getProperty("lang"); if (Objects.equals(property, "zh_CN")) { return true; } return false; } } public class EnglishCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String property = environment.getProperty("lang"); if (Objects.equals(property, "en_US")) { return true; } return false; } } 配置类:@Configuration public class MyConfig { @Bean @Conditional(ChineseCondition.class) public Language chinese() { return Language.builder().id(1l).content("华流才是最屌的").build(); } @Bean @Conditional(EnglishCondition.class) public Language english() { return Language.builder().id(2l).content("english is good").build(); } public static void main(String[] args) { System.setProperty("lang", "zh_CN"); AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // 遍历Spring容器中的beanName for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } } 执行结果:chinese ,说明根据条件匹配到ChineseCondition返回true,成功注入bean。2 @Condition衍生注解2.1@ConditionalOnBean@ConditionalOnBean :当给定的在bean存在时,则实例化当前Bean,示例如下 @Bean @ConditionalOnBean(name = "address") public User (Address address) { //这里如果address实体没有成功注入 这里就会报空指针 address.setCity("hangzhou"); address.setId(1l) return new User("魅影", city); } 这里加了ConditionalOnBean注解,表示只有address这个bean存在才会实例化user实现原理如下:2.2.@ConditionalOnMissingBean@ConditionalOnMissingBean:当给定的在bean不存在时,则实例化当前Bean, 与@ConditionalOnBean相反@Configuration public class BeanConfig { @Bean(name = "notebookPC") public Computer computer1(){ return new Computer("笔记本电脑"); } @ConditionalOnMissingBean(Computer.class) @Bean("reservePC") public Computer computer2(){ return new Computer("备用电脑"); } ConditionalOnMissingBean无参的情况,通过源码可知,当这个注解没有参数时,仅当他注解到方法,且方法上也有@Bean,才有意义,否则无意义。那意义在于已被注解方法的返回值类型的名字作为ConditionalOnMissingBean的type属性的值。2.3.@ConditionalOnClass@ConditionalOnClass:当给定的类名在类路径上存在,则实例化当前Bean2.4.@ConditionalOnMissingClass@ConditionalOnMissingClass:当给定的类名在类路径上不存在,则实例化当前Bean2.5.@ConditionalOnProperty@ConditionalOnProperty:Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { // 数组,获取对应property名称的值,与name不可同时使用 String[] value() default {}; // 配置属性名称的前缀,比如spring.http.encoding String prefix() default ""; // 数组,配置属性完整名称或部分名称 // 可与prefix组合使用,组成完整的配置属性名称,与value不可同时使用 String[] name() default {}; // 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置 String havingValue() default ""; // 缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效 boolean matchIfMissing() default false; // 是否可以松散匹配,至今不知道怎么使用的 boolean relaxedNames() default true;} 通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。 如果该值为空,则返回false; 如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。 如果返回值为false,则该configuration不生效;为true则生效。当然havingValue也可以不设置,只要配置项的值不是false或“false”,都加载Bean示例代码:feign: hystrix: enabled: true fegin开启断路器hystrix: @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.hystrix.enabled") public Feign.Builder feignHystrixBuilder() { return HystrixFeign.builder(); } 结论:@Conditional及其衍生注解,是为了方便程序根据当前环境或者容器情况来动态注入bean,是Spring Boot条件装配实现的核心所在。
1.概述当下Spring Boot之所以能成为主流首选开发框架,得益于其核心思想:约定大于配置和Spring提供的基于注解配置式开发,解决了繁琐的XML文件配置问题,大大提高了开发效率。基于Spring MVC三层架构框架开发的项目中大量用到@Controller, @Service...等注解,即使这些类在不同包路径下,都能被注入到Spring容器中,然后可以相互之间进行依赖注入、使用。这时候就有一个问题了:Spring是如何将声明了@Component注解的Bean注入到Spring容器当中的呢?怎么做到bean的类定义可以随意写在不同包路径下?答案就是今天的主角@ComponentScan,该注解告诉Spring扫描那些包路径下的类,然后判断如果类使用了@Component,@Controller, @Service...等注解,就注入到Spring容器中。之前我们讲过一个注解@Component,它就是声明当前类是一个bean组件,那@ComponentScan注解顾名思义就是扫描声明了@Component注解的类,然后注入到Spring容器中的。这时候你可能会问@Controller, @Service...等注解为什么也会被扫描、注入到Spring容器中。接下来我们就看看这些注解@Controller, @Service, @Repository和@Component的关系,从这些注解的定义上来看都声明了@Component,所以都是@Component衍生注解,其作用及属性和@Component是一样,只不过是提供了更加明确的语义化,是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰@Controller:一般用于表现层的注解。@Service:一般用于业务层的注解。@Repository:一般用于持久层的注解。@RestController:是@Controller的衍生注解,主要用于前后端分离,接口返回JSON格式数据的表现层注解项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用。 Github地址:github.com/plasticene/… Gitee地址:gitee.com/plasticene3… 接下来我们就来讲讲@ComponentScan的使用和底层实现。2.@ComponentScan的使用在讲述@ComponentScan使用之前先来看看定义:@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Repeatable(ComponentScans.class)//可重复注解 public @interface ComponentScan { @AliasFor("basePackages") String[] value() default {};//基础包名,等同于basePackages @AliasFor("value") String[] basePackages() default {};//基础包名,value Class<?>[] basePackageClasses() default {};//扫描的类,会扫描该类所在包及其子包的组件。 Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;//注册为BeanName生成策略 默认BeanNameGenerator,用于给扫描到的Bean生成BeanName Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;//用于解析bean的scope的属性的解析器,默认是AnnotationScopeMetadataResolver ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;//scoped-proxy 用来配置代理方式 // no(默认值):如果有接口就使用JDK代理,如果没有接口就使用CGLib代理 interfaces: 接口代理(JDK代理) targetClass:类代理(CGLib代理) String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;//配置要扫描的资源的正则表达式的,默认是"**/*.class",即配置类包下的所有class文件。 boolean useDefaultFilters() default true;//useDefaultFilters默认是true,扫描带有@Component ro @Repository ro @Service ro @Controller 的组件 Filter[] includeFilters() default {};//包含过滤器 Filter[] excludeFilters() default {};//排除过滤器 boolean lazyInit() default false;//是否是懒加载 @Retention(RetentionPolicy.RUNTIME) @Target({}) @interface Filter {//过滤器注解 FilterType type() default FilterType.ANNOTATION;//过滤判断类型 @AliasFor("classes") Class<?>[] value() default {};//要过滤的类,等同于classes @AliasFor("value") Class<?>[] classes() default {};//要过滤的类,等同于value String[] pattern() default {};// 正则化匹配过滤 } } 从定义来看,比起之前讲的@Import注解相对有点复杂,但是不用过于担心,其大部分属性使用默认即可,我们一般只需要配置一下basePackages属性指定包扫描路径即可。下面我们来看看如何使用,我在包路径下com.shepherd.common.bean下定义如下类:@Component public class Coo { } @Repository public class Doo { } @Service public class Eoo { } @RestController public class Foo { } 然后在另一个包路径com.shepherd.common.bean1下再定义一个类:@Component public class Goo { } 最后声明一个类,使用ComponentScan注解进行包扫描:@ComponentScan("com.shepherd.common.bean") public class MyConfig { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // 遍历Spring容器中的beanName for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } } 执行结果如下:myConfig coo doo eoo foo 我们发现Goo没有注入到Spring容器中,因为我们扫描的包路径是com.shepherd.common.bean,但是它在com.shepherd.common.bean1下,所以没有被扫描到,要想被扫描到只需要指定扫描包添加路径com.shepherd.common.bean1即可@ComponentScan(basePackages = {"com.shepherd.common.bean", "com.shepherd.common.bean1"}) public class MyConfig { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // 遍历Spring容器中的beanName for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } } 执行结果如下:myConfig coo doo eoo foo goo 可以看到Goo被成功扫描、注入到Spring容器中了。从上面@ComponentScan定义看到声明了@Repeatable(ComponentScans.class),意味着该注解可以在同一个类中多次使用,这时候我想着使用两次分别指定不同的包扫描路径,解决前面Goo没有被扫描到的问题,下面的@ComponentScan多次使用等价于 @ComponentScans({@ComponentScan("com.shepherd.common.bean"), @ComponentScan("com.shepherd.common.bean1")}),代码如下:@ComponentScan("com.shepherd.common.bean") @ComponentScan("com.shepherd.common.bean1") public class MyConfig { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // 遍历Spring容器中的beanName for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } } 执行结果如下:myConfig 这时候惊奇发现指定的包路径下类都没有被扫描注入,很是纳闷不知道问题出在哪里,只能debug调试了,你会发现又来到配置后置处理器ConfigurationClassPostProcessor的#processConfigBeanDefinitions()方法,这个方法会先判断有没有配置类,没有的话不再做后续的注解解析。 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } // 判断当前bean(这里就是上面的定义的MyConfig类)是不是配置类,是的话加入配置类候选集合 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found // 配置类集合为空,直接返回,不在做后续的相关注解解析 if (configCandidates.isEmpty()) { return; } ...... } 进入到ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)方法,核心逻辑如下:Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; } 这里判断当前是不是配置类,是配置类还分是FULL模式或LITE模式,两种模式的区别之前我们总结过,请查看 @Configuration 和 @Component区别于实现原理,上面定义的MyConfig没有用@Configuration注解,所以config是null,所以接下来会进入到方法isConfigurationCandidate(metadata)发现配置类LITE模式匹配规则里面并没有包含@ComponentScans注解,所以判断当前类不是配置类,自然不会再进行后面的相关注解解析了,这也就是上面多次使用@ComponentScan扫描注入不成功的问题。上面的案例都是只简单配置@ComponentScan的basePackages()属性,当然我们也可以基于@Filter进行过滤啥的,如下面Spring Boot的启动类注解:@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { } 3.@ComponentScan的实现原理@ComponentScan的底层实现流程和之前我们分析 @Import实现原理基本一致的,都是依靠配置类后置处理器ConfigurationClassPostProcessor进行处理、解析的,核心流程图如下所示:所以我们这里直接看配置类解析器ConfigurationClassParser的解析方法doProcessConfigurationClass() protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { // 对@Component的解析处理,对@ComponentScan注解解析在下面,意味着会先跳过这里对@ComponentScan解析进行包扫描拿到生了@Component的beanDefinition,然后递归调用会再次来到这里解析@Component if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass, filter); } // Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations 解析@ComponentScan核心所在 // 这里是调用AnnotationConfigUtils的静态方法attributesForRepeatable,获取@ComponentScan注解的属性 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { // for循环,遍历componentScans,此时仅有一个componentScan,使用componentScanParser解析器来解析componentScan这个对象 for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = // componentScanParser解析器进行解析 this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed // for循环扫描到的beanDefinition信息 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } // 这里递归调用前面的配置类解析器的解析方法,也就是会再次来到doProcessConfigurationClass()这个方法,会匹配到方法一开始的对@Component解析逻辑 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations // 处理注解@import的入口方法 processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // Process any @ImportResource annotations AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; } ComponentScanAnnotationParser的parse() public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } scanner.setResourcePattern(componentScan.getString("resourcePattern")); for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } Set<String> basePackages = new LinkedHashSet<>(); String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); } 初始化ClassPathBeanDefinitionScanner扫描器,根据·@ComponentScan的属性,设置扫描器的属性,最后调用扫描器的doScan()方法执行真正的扫描工作。遍历扫描包,调用findCandidateComponents()方法根据基础包路径来找到候选的Bean。之后就是遍历扫描到的候选Bean,给他们设置作用域,生成BeanName等一系列的操作。然后检查BeanName是否冲突,添加到beanDefinitions集合当中,调用registerBeanDefinition注册Bean,将Bean的定义beanDefinition注册到Spring容器当中,方便后续注入bean。4.总结以上全部就是对@ComponentScan注解实现流程的解析,也是对使用了@Component的组件怎么注入到Spring容器的梳理,Spring Boot项目会默认扫描启动类包下面的所有组件,其自动配置原理实现中使用到了@ComponentScan注解,所以我们需要关注该注解啦。
1.概述@Import 是 Spring 基于 Java 注解配置的主要组成部分,@Import 注解提供了类似 @Bean 注解的功能,向Spring容器中注入bean,也对应实现了与Spring XML中的元素相同的功能,注解定义如下:@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration @Configuration}, {@link ImportSelector}, * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import. */ Class<?>[] value(); } 从定义上来看,@Import注解非常简单,只有一个属性value(),类型为类对象数组,如value={A.class, B.class},这样就可以把类A和B交给Spring容器管理。但是这个类对象需要细分为三种对象,也对应着@Import的三种用法如下:普通类实现了ImportSelector接口的类(这是重点~Spring Boot的自动配置原理就用到这种方式)实现了ImportBeanDefinitionRegistrar接口的类项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用。Github地址:github.com/plasticene/… Gitee地址:gitee.com/plasticene3… 下面我们就针对@Import的三种不同用法一一举例阐述说明。2.@Import的三种用法2.1 注入普通类这种方式很简单,直接上代码,首先先随便定义一个普通类:这里我定义了一个Student类@Data public class Student { private Long id; private String name; private Integer age; } 接下来就是声明一个配置类,然后使用@Import导入注入即可:@Configuration @Import({Student.class}) public class MyConfig { @Bean public Person person01() { Person person = Person.builder().id(1l).name("shepherd").age(25).build(); return person; } public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // 遍历Spring容器中的beanName for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } } 这里我用@Bean注入了一个bean,想证实一下@Import实现的功能与其类似,执行上面的main()结果如下:org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory myConfig com.shepherd.common.config.Student person01 可以看到,这里默认注入了一些Spring内部bean和我们在MyConfig中注入的bean,@Import({Student.class})把Student类注入到了Spring容器中,beanName默认为全限定类名com.shepherd.common.config.Student,而@Bean注入的默认为方法名,这也是两者的区别。2.2 实现了ImportSelector接口的类这一方式比较重要,也可以说是@Import最常用的方式,Spring Boot的自动装配原理就用到了这种方式,所以得认真学习一下。我们先来看看ImportSelector这个接口的定义:public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. * @return the class names, or an empty array if none */ String[] selectImports(AnnotationMetadata importingClassMetadata); /** * Return a predicate for excluding classes from the import candidates, to be * transitively applied to all classes found through this selector's imports. * <p>If this predicate returns {@code true} for a given fully-qualified * class name, said class will not be considered as an imported configuration * class, bypassing class file loading as well as metadata introspection. * @return the filter predicate for fully-qualified candidate class names * of transitively imported configuration classes, or {@code null} if none * @since 5.2.4 */ @Nullable default Predicate<String> getExclusionFilter() { return null; } } selectImports( )返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中。注意如果为null,要返回空数组,不然后续处理会报错空指针getExclusionFilter()该方法制定了一个对类全限定名的排除规则来过滤一些候选的导入类,默认不排除过滤。该接口可以不实现接下来我们编写一个类来实现ImportSelector接口public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.shepherd.common.config.Student", "com.shepherd.common.config.Person"}; } } 最后在配置类中使用@Import导入:@Configuration @Import({MyImportSelector.class}) public class MyConfig { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // 遍历Spring容器中的beanName for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } } 执行结果如下:org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory myConfig com.shepherd.common.config.Student com.shepherd.common.config.Person 可以看出,Spring没有把MyImportSelector当初一个普通类进行处理,而是根据selectImports( )返回的全限定类名数组批量注入到Spring容器中。当然你也可以实现重写getExclusionFilter()方法排除某些类,比如你不想注入Person类,你就可以通过这种方式操作一下即可,这里就不再展示代码案例,可以自行尝试。2.3 实现了ImportBeanDefinitionRegistrar接口的类这种方式通过描述就可以知道是通过实现ImportBeanDefinitionRegistrar将要注入的类添加到BeanDefinition注册中心,这样Spring 后续会根据bean定义信息将类注入到容器中。老规矩,我们先看看ImportBeanDefinitionRegistrar的定义:public interface ImportBeanDefinitionRegistrar { default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { registerBeanDefinitions(importingClassMetadata, registry); } default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } } 可以看到一共有两个同名重载方法,都是用于将类的BeanDefinition注入。唯一的区别就是,2个参数的方法,只能手动的输入beanName,而3个参数的方法,可以利用BeanNameGenerator根据beanDefinition自动生成beanName自定义一个类实现ImportBeanDefinitionRegistrar:public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { //使用 BeanNameGenerator自动生成beanName @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Person.class); AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry); registry.registerBeanDefinition(beanName, beanDefinition); } // 手动指定beanName // @Override // public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Student.class); // AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); // registry.registerBeanDefinition("student001", beanDefinition); // } } 注意,这里只能注入一个bean,所以只能实现一个方法进行注入,如果两个都是实现,前面的一个方法生效。将上面2.2小节的配置类变成@Import({MyImportBeanDefinitionRegistrar.class})即可,执行结果和上面一样的,这里不再展示了。3.@Import的实现原理探究@Import注解实现源码之前,不得不引出Spring中一个非常重要的类:ConfigurationClassPostProcessor,从名字可以看出它是一个BeanFactoryPostProcessor,主要用于处理一些配置信息和注解扫描。之前我们就在总结的 @Configuration 和 @Component区别于实现原理文章中讲述过该类的核心所在,感兴趣的可根据该文章链接跳转自行查看,不出意外地,@Import注解的扫描、解析也在其中,其流程图如下所示:ConfigurationClassPostProcessor既然是一个后置处理器,我们就直接从其后置处理方法入手即可,经过debug调试发现ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()是完成对@Component,@ComponentScan,@Bean,@Configuration,@Import等等注解的处理的入口方法@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId); // 处理配置类 processConfigBeanDefinitions(registry); } processConfigBeanDefinitions()处理配置类的核心逻辑:该方法比较长,碍于篇幅,我提取出有关@Import解析的核心代码,其余代码用......替代。 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); // 将配置类加入到configCandidates集合当中 for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // 配置类集合为空,直接返回,不需要做下面的解析 // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } ...... // 解析配置类,以do...while()循环方式遍历解析所有配置类的@Component,@ComponentScan,@Bean,@Configuration,@Import // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { // 解析注解入口 parser.parse(candidates); parser.validate(); ...... // 执行完配置类的解析之后,根据解析得到的configClasses转换为beanDefinition放入到Sping容器当中 // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); ...... } while (!candidates.isEmpty()); ...... } 接下来的疏通一下解析配置类的流程如下所示:ConfigurationClassParser -> parse() -> processConfigurationClass() -> doProcessConfigurationClass() 上面的方法我为了方便方便省略掉了方法参数,自己调试时候如果找不到方法,可以直接搜索方法名查找调用流程。当我们看到以都do开头的方法就看到希望了,因为熟悉Spring框架源码的人都知道在Spring底层代码中,以do开头方法一般就是核心逻辑代码实现所在。doProcessConfigurationClass()源码如下: protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass, filter); } // Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations // 处理注解@import的入口方法 processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // Process any @ImportResource annotations AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; } 上面包含对众多注解的处理,这里不一一讲述,后续我们讲到相应注解再解析相应代码片段,今天我直奔主题进入其中的解析@Import的方法processImports()private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, 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接口的类 */ if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); Predicate<String> selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter);//过滤注入的类 } if (selector instanceof DeferredImportSelector) { 延迟注入 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else {//调用selector当中的selectImports方法,得到要注入的类的全限定名 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); // 获得元类信息 Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); // 递归的处理注入的类 processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); } } /** * 如果是ImportBeanDefinitionRegistrar 则configClass.addImportBeanDefinitionRegistrar 提前放到一个map当中 */ 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 = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);//实例化 configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());//放到一个map中 } /** * 如果是普通类 */ 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), exclusionFilter); } } } 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(); } } } 该方法的核心逻辑:先进行注入集合的判空和循环依赖的检查,最后再进行遍历注入的候选集合,三种类型的类执行不同的逻辑:实现了ImportSelector接口的类,调用getExclusionFilter()方法,如果不为空,那么就进行过滤,过滤后调用selectImports()方法,得到要注入的类的全限定名。根据类全限定名,得到类元信息。然后递归的调用processImports()方法实现了ImportBeanDefinitionRegistrar接口的类,会实例化这个类,放入集合importBeanDefinitionRegistrars当中。普通类型的类(上面两个都不满足),那么就把它当作是配置类来处理,调用processConfigurationClass()方法,最终会放入到configurationClasses这个集合当中。经过一系列的递归调用,实现了ImportBeanDefinitionRegistrar接口的类,会放入到importBeanDefinitionRegistrars集合当中,其余的类都放入到configurationClasses集合当中。 之后就会回到processConfigBeanDefinitions方法,也就是执行完了ConfigurationClassParser的parse()方法。此时会执行loadBeanDefinitions将configurationClasses集合当中类加载的Spring容器当中,并且从 importBeanDefinitionRegistrars缓存当中拿到所有的ImportBeanDefinitionRegistrar并执行registerBeanDefinitions方法。4.总结根据上述分析,@Import注解还是依靠ConfigurationClassPostProcessor核心后置处理器实现的,所以这里想再次强调一下该类的重要性,要重点关注啦。