文章目录
- Q1、如何在Spring创建完所有的Bean之后做扩展?
- Q2、Spring容器启动时,为什么先加载BeanFactoryPostProcess?
- Q3、Bean的生产顺序是由什么决定的?
- Q4、Spring有哪几种配置方式
- Q5、JavaConfig是如何替代spring.xml的?
- Q6、@Component、@Controller、@Service、@Repository有何区别?
- Q7、@Import可以有几种用法?
Q1、如何在Spring创建完所有的Bean之后做扩展?
问题涉及的源码分析:
点下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加载完成后输出一句测试的话:
Q2、Spring容器启动时,为什么先加载BeanFactoryPostProcess?
答案:
- 因为BeanDefinition会在IoC容器加载的时候先注册,而BeanFactoryPostProcess是所有BeanDefinition注册完后做扩展用的,所以要先加载
- 其次,Ioc容器的加载,即创建Bean,这就需要解析配置类(xml的bean标签、@Component...),负责解析的类也实现了BeanFactoryPostProcessor
Q3、Bean的生产顺序是由什么决定的?
BeanDefinition里装着Bean的一些信息,看创建IoC容器的源码中的refresh方法的源码:
答案:
所以Bean的生产顺序是由BeanDefinition的注册顺序决定的,当然依赖关系也会影响Bean的创建顺序。
java
那BeanDefinition的注册顺序由什么决定?
主要由注解(或配置)的解析顺序来决定,由源码看到解析顺序为:
- @Configuration
- @Component(当然它还有@Order来细分,这里不细分)
- @Import的一类
- @Bean
- 使用BeanDefinitionRegistryPostProcessor注册的
从上下文拿一下BeanDefinitionNames,是个String[]
,有序,输出下:
按照上面这个注册顺序,以及Spring中后面的Bean覆盖前面的Bean,还可以这么应用:有个用@Component注册的Bean,我想让他失效并替换成另一个,则可以用@Bean再写一个
。
Q4、Spring有哪几种配置方式
答案:
有三种方式给Spring容器提供配置的元数据:
- XML配置文件 :从Spring诞生开始用,spring.xml中用
<bean>
标签 - 基于注解 :从Spring2.5+开始,spring.xml
<component-scan base-package=""/>
+ @Component - 基于JavaConfig:从Spring3.0开始,使用@Configuration代替xml,使用@Bean代替Bean标签
Q5、JavaConfig是如何替代spring.xml的?
答案:
对于XML
:
- 创建Spring容器是new ClassPathXmlApplicationContext("a.xml")
- 有一个spring.xml文件
- 配置一个Bean是用bean标签,scope、lazy属性
- 扫描包用
<component-scan base-package=""/>
- 引入外部的属性配置文件用
<property-placeHolder resource="/xx.properties">
- 引入配置文件后,使用其属性给Bean属性赋值为
${}
java
<property name="password" value="${mysql.password}"></property>
- 指定其他配置文件用
<import resource="">
对于JavaConfig
:
- 创建Spring容器是new AnnotationConfigApplicationContext(javaConfig.class)
- 有一个配置类,被@Configuration注解修饰
- 配置一个Bean是用@Bean注解,属性也用注解@Scope、@Lazy
- 扫描包用
@ComponentScan
- 引入外部的属性配置文件用
@PropertySource("classpath:db.properties")
- 使用配置文件中的值为
@Value("${}")
- 指定其他用@Import(配置类)
以上是应用层的区别,最后,从源码和流程上来说:大方向都是创建Spring容器(同一个接口,不同的实现类),然后加载、解析配置,再注册为BeanDefinition后交给Bean工厂生产。
Q6、@Component、@Controller、@Service、@Repository有何区别?
答案:
这四个注解其实是一个注解,@Controller、@Service、@Repository的源注解都是@Component
之所以分为三种,是因为开发一个项目,常使用三层架构,即控制层、业务层、数据返回层,提供这三个注解用于不同层,提高代码的阅读性。
Q7、@Import可以有几种用法?
答案:
四种:
第一种
:直接指定类(如果是配置类,就会按照配置类来解析,如果是个普通类,则直接解析成Bean)
java
@Import(UserMapper.class)
@Import(JavaConfig.class) //此时,JavaConfig这个配置类中的Bean也会生效
第二种:
导入一个实现了ImportSelector这个接口的类,这种方式可以一次性注册多个,返回一个String[ ],每一个值是完整类路径
java
public class MyImportSelector implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata){
//可以以字符串数组的形式注册多个Bean
//字符串必须是类的完整类名
return new String[]{"com.llg.UserMapper","com.llg.Role"};
}
}
java
@Configuration
@Import(MyImportSelector.class)
public class MainConfig{
}
但注意
,这个时候的Bean创建不是正常的IoC加载流程,使用@Import,之前的一个postProcessBeanFactory在所有Beanfinition注册完之后修改这个Import的Bean的信息就会报错
:
第三种
:导入一个实现ImportBeanDefinitionRegistrar的接口的类,这种也可以一次导入多个,是通过BeanDefinitionRegistry来动态注册BeanDefinition,因此这种方式是没有上面在所有Beanfinition注册完之后修改这个Import的Bean的信息就会报错
的这个情况的。
第四种
:这种其实是第二种的衍生,第二种的接口有个子接口,叫延迟导入选择器,也可以一次导入多个Bean,和ImportSelector的区别是DeferredImportSelector的顺序更靠后