问题涉及的源码分析:
点下Download Sources,把源码注释下载进来
。new Spring容器时,调用了refresh方法:
refresh方法:(IoC容器的加载,就是在refresh方法里实现的)
new ApplicationContext() --> refresh() --> finishBeanFactorylnitialization (循环所有的BeanDefinition ,通过BeanFactory.getBean()生成所有的Bean), 这个循环结束之后所有的bean也就创建完了。而在循环结束后,还有一段代码,就是扩展的关键:
即这个实例实现了SmartInitializingSingleton接口,这就是实现的扩展点之一
。再看refresh源码,finishBeanFactorylnitialization下面还有个finishRefresh方法:
这里发布了一个ContextRefreshedEvent的事件,我们只要创建个监听器,监听这个事件
,也可以完成扩展。
答案:
方式一
:实现SmartInitializingSingleton接口方式二
:要创建个监听器,监听ContextRefreshedEvent事件看下效果,以上即Bean加载完成后输出一句测试的话:
答案:
BeanDefinition里装着Bean的一些信息,看创建IoC容器的源码中的refresh方法的源码:
答案:
所以Bean的生产顺序是由BeanDefinition的注册顺序决定的,当然依赖关系也会影响Bean的创建顺序。
那BeanDefinition的注册顺序由什么决定?
主要由注解(或配置)的解析顺序来决定,由源码看到解析顺序为:
从上下文拿一下BeanDefinitionNames,是个String[]
,有序,输出下:
按照上面这个注册顺序,以及Spring中后面的Bean覆盖前面的Bean,还可以这么应用:有个用@Component注册的Bean,我想让他失效并替换成另一个,则可以用@Bean再写一个
。
答案:
有三种方式给Spring容器提供配置的元数据:
标签
+ @Component答案:
对于XML
:
${}
<property name="password" value="${mysql.password}"></property>
对于JavaConfig
:
@ComponentScan
@PropertySource("classpath:db.properties")
@Value("${}")
以上是应用层的区别,最后,从源码和流程上来说:大方向都是创建Spring容器(同一个接口,不同的实现类),然后加载、解析配置,再注册为BeanDefinition后交给Bean工厂生产。
答案:
这四个注解其实是一个注解,@Controller、@Service、@Repository的源注解都是@Component
之所以分为三种,是因为开发一个项目,常使用三层架构,即控制层、业务层、数据返回层,提供这三个注解用于不同层,提高代码的阅读性。
答案:
四种:
第一种
:直接指定类(如果是配置类,就会按照配置类来解析,如果是个普通类,则直接解析成Bean)
@Import(UserMapper.class)
@Import(JavaConfig.class) //此时,JavaConfig这个配置类中的Bean也会生效
第二种:
导入一个实现了ImportSelector这个接口的类,这种方式可以一次性注册多个,返回一个String[ ],每一个值是完整类路径
public class MyImportSelector implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata){
//可以以字符串数组的形式注册多个Bean
//字符串必须是类的完整类名
return new String[]{"com.llg.UserMapper","com.llg.Role"};
}
}
@Configuration
@Import(MyImportSelector.class)
public class MainConfig{
}
但注意
,这个时候的Bean创建不是正常的IoC加载流程,使用@Import,之前的一个postProcessBeanFactory在所有Beanfinition注册完之后修改这个Import的Bean的信息就会报错
:
第三种
:导入一个实现ImportBeanDefinitionRegistrar的接口的类,这种也可以一次导入多个,是通过BeanDefinitionRegistry来动态注册BeanDefinition,因此这种方式是没有上面在所有Beanfinition注册完之后修改这个Import的Bean的信息就会报错
的这个情况的。
第四种
:这种其实是第二种的衍生,第二种的接口有个子接口,叫延迟导入选择器,也可以一次导入多个Bean,和ImportSelector的区别是DeferredImportSelector的顺序更靠后