一、Bean是怎么装配的?
1、bean扫描
在之前的ssm中,spring要么用标签的形式来扫描包,要么使用注解@ComponentScan来扫描
但是在Springboot中,启动类上默认有一个注解@SpringBootApplication,里面就包含了@ComponentScan
注意,springboot默认扫描启动类所在的包及其子包。
也就是说,如果你给启动类也单独放进了一个目录或是把启动类放进了别的包下,那么程序就无法正常运行
但如果你非要给启动类创建一个单独的目录存放,那么就把@ComponentScan单独拿出来。
@ComponentScan(basePackages = "指定包")
但是尽量别这么做
2、bean注册
学习spring的时候就提到过
问:如果我将第三方的jar包(不是自定义的)注入到ioc容器中,那么这些注解还可以使用吗?
答:
不能,spring提供了两个注解来解决这个问题
@Bean
@Import
@Bean
使用@Bean需要在启动类中注册
但是不建议使用这种方式,启动类就让他起到一个启动入口就行了,最好别给他添加太多功能
如果要注册第三方bean,建议在配置类中集中注册
@Confiugration是为了标识是一个配置类,注意要放在启动类的同包或子包下才能使用
问:return回去的这个对象的名称是什么?
答:
默认方法名是bean对象的名称,在@Bean注解中修改
问:如果方法内部要使用ioc中已经存在的bean对象怎么办?
答:
@Import
我们先写一个配置类,里面用@Bean注册几个bean
在上面说过,配置类注意要放在启动类的同包或子包下才能使用,如果并不想放在同包下,那么就需要使用@Import引入了
在启动类上添加@Import注解其底层是一个数组列表,所以如果有多个配置类的话用{}括起来就行了
不过我们更多还是使用ImportSelector接口实现
写一个接口实现类,然后将一开始写的配置类放进去。不过类太多这样书写也不方便,我们在实际开发中有更清晰的设计,就是将这些配置类统一放在一个配置文件中。resources是管理配置文件的目录,将这些配置类的名称放进去
public class CommonImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//读取配置文件的内容
List<String> imports = new ArrayList<>();
InputStream is = CommonImportSelector.class.getClassLoader().
getResourceAsStream("common.imports");
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = null;
try{
while((line = br.readLine()) != null){
imports.add(line);
}
}catch (IOException e){
throw new RuntimeException(e);
}finally {
if (br != null){
try {
br.close();
}catch (IOException e){
throw new RuntimeException(e);
}
}
}
return imports.toArray(new String[0]);
}
}
然后在启动类上@Import的就不是配置类.class了,而是ImportSelector的实现类.class
3、合并注解
上面的例子中,启动类上会有很多个注解,这样看起来很繁琐,不美观。我们想更有逼格一点。
我们之前就说过,启动类的注解@SpringBootApplication就是多个注解组合到一起的,那么我们也可以手动将多个注解组合到一起
Ctrl + 鼠标左键点击@SpringBootApplication注解查看底层
其中最重要的是上最上面的两个注解
@Target:表示当前注解可以在类上使用
@Retention:表示当前注解可以在运行时保存
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({CommonImportSelector.class})
public @interface EnableCommonConfig {
}
最终的启动类如下
二、注册条件
大部分情况下,我们最好别直接将数据写在java代码中,而是写在配置文件中方便修改。但是这样就又有了问题
数据的使用我们在上一篇博客中已经学过了使用@Value来获取配置文件中的数据。但是当数据不存在的时候就会报错,导致程序停止运行,这是我们在开发中所不希望看到的。因此springboot提供了设置注册生效的注解@Conditional。
不过注解本体使用起来较为麻烦,所以我们最多的还是使用他的几个子类
注解 | 说明 |
@ConditionalOnProperty | 配置文件中存在对应属性,才声明该bean |
@ConditionalOnMissingBean | 当不存在当前类型的bean时,才声明该bean |
@ConditionalOnClass | 当前环境存在指定的这个类时,才声明该bean |
@ConditionalOnPropetry( prefix = "配置文件中的要使用的数据的前缀" , name = {"数据1","数据n"})
假设现在并没有配置country的信息
那么在我们在进行getbean的时候就会报错,提示这里不存在该对象
三、自动配置原理
遵循约定大约配置的原则,在boot程序启动后,起步依赖中的一些bean对象会自动注入到ioc容器中。
我们之前的步骤并不是完全自动装配,我们除了手动装配bean之外,还需要自己导入到启动类里
先看源码,启动类@SpringBootAutoConfigApplication中组合了@Configuration(说明启动类其实也是一个配置类)、@EnableAutoConfigration
翻源码翻到最后看到一个@ConditionalOnClass,里面一个DispatcherServlet.class。也就是说,若环境中没有DispatcherServlet这个类,那么springboot便不会开启自动装配。而这个类存在于springboot的web起步依赖中,也就是说只有引入springboot的web依赖,我们才可以使用完全自动装配
springboot自动配置的原理