前置条件:
创建一个Spring项目,有Spring框架的支持
Spring中启动类的@SpringBootApplication实现了包扫描。在Spring项目构建时,Spring会扫描所在目录下的包,扫描包下@Component注解管理的Bean对象,将Bean对象添加到IOC容器中。
好处是什么呢?
在项目中如果使用到这个Bean对象,可以直接通过依赖注入来使用,而不是需要一个一个new出来。
通过下述代码,我们在操作数据库的时候,可以直接通过@Autowired依赖注入来调用Mapper的方法,非常的方便。
// @Mapper注解是@Component注解的拓展,同样可以实现标记Bean对象的作用
@Mapper
public interface DeptLogMapper {
// 实现操作后插入日志到数据库
@Insert("insert into dept_log(create_time, description) values (#{createTime}, #{description})")
void insert(DeptLog deptLog);
}
@Service
public class DeptLogServiceImpl implements DeptLogService {
// 依赖注入
@Autowired
DeptLogMapper deptLogMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
但是通常启动类只能扫描当前包下的Bean对象,如果是第三方的Bean对象,又该如何添加到IOC容器中注册为Bean对象呢?
假如我想这个第三方包中的Country和Province导入。是否直接在类上添加@Component注解就可以呢?
而通常第三方的包都是只读文档,所以这种方法是局限性很大的。
通过@Bean实现第三方扫描Bean对象
我们测试一下在启动类中使用@Bean注解
@SpringBootApplication
public class SpringbootRegisterApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringbootRegisterApplication.class, args);
Country country = context.getBean(Country.class);
System.out.println(country);
}
@Bean
public Country country() {
return new Country();
}
}
我们通过@Bean注解获得Country函数的返回值,将返回值添加到IOC容器中,注册为Bean对象。
通过getBean来获得
查看Bean对象(控制台右边),发现country已经被注册了。
但是在启动类注册Bean不符合单一功能原则。启动类就是启动用的,而不应该掺杂注册的功能。
// 配置类
@Configuration
public class CommonConfig {
@Bean
public Country country() {
return new Country();
}
// 可以通过参数的形式,访问之前注册的Bean
@Bean
public Province province(Country country) {
System.out.println(country);
return new Province();
}
}
// 启动类
@SpringBootApplication
@Import(CommonConfig.class)
public class SpringbootRegisterApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringbootRegisterApplication.class, args);
Country country = context.getBean(Country.class);// 通过类名获取
System.out.println(country);
System.out.println(context.getBean("province"));// 通过名字获取,默认为类名
}
}
新建一个CommonConfig使用@Configuration注解
通过Import导入,获取Configuration中的Bean对象注册。
这样就不用在启动类中写Bean了。
但是如果我要导入很多的Bean呢?这样会造成在启动类中Import一个很长的都是类名的数组。代码看起来十分臃肿。
可以通过重写ImportSelector中的selectImports方法来集中注册
// ImportSelector
public class CommonImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return String[]{"config.CommonConfig"};
}
}
// 启动类
@SpringBootApplication
@Import(CommonImportSelector.class)
public class SpringbootRegisterApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringbootRegisterApplication.class, args);
Country country = context.getBean(Country.class);
System.out.println(country);
System.out.println(context.getBean("province"));
}
}
现在又出现了一个问题,要导入的Bean都在CommonImportSelector 中,这会使得代码耦合,如果要增加还需要去修改CommonImportSelector
所以不妨直接把所有要注册的Bean都添加到配置文件中
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 {
try {
br.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return imports.toArray(new String[0]);
}
}
这样的话,只需要修改配置文件就可以了。
还有个更加优雅的写法
自定义一个注释
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(CommonImportSelector.class)
public @interface EnableCommonConfig {
}
在启动类添加这个Enable注释
@SpringBootApplication
@EnableCommonConfig
public class SpringbootRegisterApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringbootRegisterApplication.class, args);
Country country = context.getBean(Country.class);
System.out.println(country);
System.out.println(context.getBean("province"));
}
}
启动类的导入其实就是这样写的。