ps:本文为作者学习笔记技术参考意义不大
文章目录
一. 自动配置工作流程
自动配置是springboot技术非常好用的核心因素,前面学习了这么多种技术的整合,每一个都离不开自动配置。不过在学习自动配置的时候,需要你对spring容器如何进行bean管理的过程非常熟悉才行,所以这里需要先复习一下有关spring技术中bean加载相关的知识。方式方法很多,逐一快速复习一下,查漏补缺。不过这里需要声明一点,这里列出的bean的加载方式仅仅应用于后面课程的学习,并不是所有的spring加载bean的方式。跟着我的步伐一种一种的复习,他们这些方案之间有千丝万缕的关系,顺着看完,你就懂自动配置是怎么回事了。
1. 准备工作
首先我们先要创建好一个基础的java模块,在POM文件中添加下面的依赖信息:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
创建一个BookService接口,以及其四个实现类,定义方法分别输出不同的内容:
public interface BookSerivce {
void check();
}
public class BookServiceImpl1 implements BookSerivce {
@Override
public void check() {
System.out.println("book service 1..");
}
}
// 四个实现类以此类推
二. Bean的加载方式
1. 方式一:xml方式
创建一个Spring的配置文件,在其中使用xml的方式定义bean:
定义方式
注意定义第三方的bean时要确保第三方的依赖包导入到项目中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--xml方式声明自己开发的bean-->
<bean id="cat" class="com.itheima.bean.Cat"/>
<bean class="com.itheima.bean.Dog"/>
<!--xml方式声明第三方开发的bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
<bean class="com.alibaba.druid.pool.DruidDataSource"/>
<bean class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>
获取方式
public class App1 {
public static void main(String[] args) {
// applicationCOntext1.xml Spring的配置文件文件名
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationCOntext1.xml");
// Object cat = ctx.getBean("cat"); // 用id拿
// System.out.println(cat);
// Dog dog = ctx.getBean(Dog.class); // 用类型拿
// System.out.println(dog);
String[] names = ctx.getBeanDefinitionNames(); // 拿到的所有bean的名称
for (String name : names) {
System.out.println(name);
}
}
}
如果有多个相同的bean打印出来的bean类路径会以#数字递增的方式区分:
2. 方式二:注解方式结合配置文件
上一种定义的方式过于繁琐,不如使用注解来定义bean来的方便可以使用@Component注解直接电话已,还有三个衍生注解也有相同的效果:
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层bean定义
定义方式
如果想将该类定义为bean只需要在类上面添加注解就可以了:
@Component("tom") // 注解value的值就是bena的id
public class Cat {
public Cat(){
}
int age;
public Cat(int age){
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
'}';
}
}
这样的方式那么第三方的类应该怎么去定义呢?
@Configuration // 标注为配置类 这个也附带加载为bena的效果
public class DbConfig {
@Bean
public DruidDataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
但是这样的方式需要指定bean的扫描路径:
applicationCOntext2.xml内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--指定加载bean的位置,多个包可以用逗号隔开-->
<context:component-scan base-package="com.itheima.bean,com.itheima.config"/>
</beans>
获取方式
public class App2 {
public static void main(String[] args) {
// 定义扫描路劲的配置文件名
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationCOntext2.xml");
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
3. 方式三:注解结合配置类
上一种定义方式任然存在一个问题,那就是配置文件已经存在,现在这种方法就是使用类来代替这个配置文件。
定义方式
首先我们创建一个全新的配置类SpringConfig3内容如下:
// 代替spring的配置文件
@ComponentScan({
"com.itheima.bean","com.itheima.config"}) // 扫描的配置组件的
public class SpringConfig3 {
}
获取方式
public class App3 {
public static void main(String[] args) {
// 没有配置文件了 直接加载配置类
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
4. 方式四:@Import注解
精准制导的加载方式,使用@Import注解就可以解决你的问题。它可以加载所有的一切,只需要在注解的参数中写上加载的类对应的.class即可。有人就会觉得,还要自己手写,多麻烦,不如扫描好用。对呀,但是他可以指定加载啊,好的命名规范配合@ComponentScan可以解决很多问题,但是@Import注解拥有其重要的应用场景。有没有想过假如你要加载的bean没有使用@Component修饰呢?这下就无解了,而@Import就无需考虑这个问题。
定义方式
这个Dog类和Cat都不需要被@Component注解标注
@Import({
Dog.class,Cat.class})
public class SpringConfig4 {
}
处了导入类,还可以直接导入别的配置类,这样配置类中的bena也会被加载进去。
@Import(DbConfig.class)
public class SpringConfig4 {
}
且配置类也不需要注解:
public class DbConfig {
// 也会被加载
@Bean
public DruidDataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
获取方式
public class App4 {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
5. 方式五:编程形式
前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载,下面这种方式就比较特殊了,可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。
定义方式
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//上下文容器对象已经初始化完毕后,手工加载bean
ctx.register(Mouse.class);
}
}
其实这种方式坑还是挺多的,比如容器中已经有了某种类型的bean,再加载会覆盖。
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//上下文容器对象已经初始化完毕后,手工加载bean
// 参数一: id
// 参数二: 类的字节码
// 参数三: 类的有参构造器参数
ctx.registerBean("tom", Cat.class,0);
ctx.registerBean("tom", Cat.class,1);
ctx.registerBean("tom", Cat.class,2);
System.out.println(ctx.getBean(Cat.class));
}
}
获取方式
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//上下文容器对象已经初始化完毕后,手工加载bean
// 参数一: id
// 参数二: 类的字节码
// 参数三: 类的有参构造器参数
ctx.registerBean("tom", Cat.class,0);
ctx.registerBean("tom", Cat.class,1);
ctx.registerBean("tom", Cat.class,2);
System.out.println(ctx.getBean(Cat.class));
}
}
6. 方式六:ImportSelector接口
在方式五种,我们感受了bean的加载可以进行编程化的控制,添加if语句就可以实现bean的加载控制了。但是毕竟是在容器初始化后实现bean的加载控制,那是否可以在容器初始化过程中进行控制呢?答案是必须的。实现ImportSelector接口的类可以设置加载的bean的全路径类名,记得一点,只要能编程就能判定,能判定意味着可以控制程序的运行走向,进而控制一切。
定义方式
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// System.out.println("================");
// System.out.println("提示:"+metadata.getClassName()); // 加载的是哪个bean描述的就是哪个
// System.out.println(metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));// 加载这个类的 配置文件有没有这个注解 输出布尔值
// Map<String, Object> attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");
// System.out.println(attributes); // 拿到很多这个注解的值
// System.out.println("================");
//各种条件的判定,判定完毕后,决定是否装在指定的bean
boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
if(flag){
return new String[]{
"com.itheima.bean.Dog"}; // 返回的是要注册bean的全类名
}
return new String[]{
"com.itheima.bean.Cat"}; // 要定义的bean的全路径
}
}
@Configuration
//@ComponentScan(basePackages = "com.itheima")
@Import(MyImportSelector.class)
public class SpringConfig6 {
}
获取方式
public class App6 {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
System.out.println("----------------------");
}
}
7. 方式七:ImportBeanDefinitionRegistrar接口
方式六中提供了给定类全路径类名控制bean加载的形式,如果对spring的bean的加载原理比较熟悉的小伙伴知道,其实bean的加载不是一个简简单单的对象,spring中定义了一个叫做BeanDefinition的东西,它才是控制bean初始化加载的核心。BeanDefinition接口中给出了若干种方法,可以控制bean的相关属性。说个最简单的,创建的对象是单例还是非单例,在BeanDefinition中定义了scope属性就可以控制这个。如果你感觉方式六没有给你开放出足够的对bean的控制操作,那么方式七你值得拥有。我们可以通过定义一个类,然后实现ImportBeanDefinitionRegistrar接口的方式定义bean,并且还可以让你对bean的初始化进行更加细粒度的控制,不过对于新手并不是很友好。忽然给你开放了若干个操作,还真不知道如何下手。
定义方式
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
// 参数一: 和上个方法的一样
// 参数二:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 创建的bean BookServiceImpl2.class
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
// bookService 是bean的id
}
}
@Import(MyRegistrar.class)
public class SpringConfig7 {
}
获取方式
public class App7 {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig7.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
System.out.println("----------------------");
}
}
8. 方式八:BeanDefinitionRegistryPostProcessor接口的类
定义方式
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
// 参数一: 和上一种方法一样
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
// 正常情况那边拿到的是最后一个bean 但是MyPostProcessor.class 配置的会优先拿到
@Import({
BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
public class SpringConfig8 {
}
获取方式
public class App8 {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class);
BookSerivce bookService = ctx.getBean("bookService", BookSerivce.class);// 按照名字和接口拿
bookService.check();
}
}
三. 其他
1. 工厂bean
首先我们先创建一个Dog类:
public class Dog {
}
接着我们可以创建这个Dog类对应的工厂类:
import org.springframework.beans.factory.FactoryBean;
// 工厂bean 这个泛型就是要创建的bean的类型
public class DogFactoryBean implements FactoryBean<Dog> {
// 实现这个工厂bean造出来的对象是什么
@Override
public Dog getObject() throws Exception {
Dog d = new Dog();
return d;
}
// 造出来的是什么类型的
@Override
public Class<?> getObjectType() {
return Dog.class;
}
@Override
public boolean isSingleton() {
return true;
// 返回true的话造出的就是同一个bean
// 返回false的话造成的就不是同一个bean
// 这就是单例 和 非单例
}
}
配置类内容:
// 代替spring的配置文件
@ComponentScan({
"com.itheima.bean","com.itheima.config"}) // 扫描的配置组件的
public class SpringConfig3 {
@Bean // id就是这个方法的名字
public DogFactoryBean dog(){
return new DogFactoryBean(); // 获取bean的时候 获取的还是一个dog对象因为这是个工厂类
}
}
2. 注解格式导入XML格式配置的bean
由于早起开发的系统大部分都是采用xml的形式配置bean,现在的企业级开发基本上不用这种模式了。但是如果你特别幸运,需要基于之前的系统进行二次开发,这就尴尬了。新开发的用注解格式,之前开发的是xml格式。这个时候可不是让你选择用哪种模式的,而是两种要同时使用。spring提供了一个注解可以解决这个问题,@ImportResource,在配置类上直接写上要被融合的xml配置文件名即可,算的上一种兼容性解决方案,没啥实际意义。
我们创建一个配置类:
@Configuration
@ImportResource("applicationContext1.xml") // 将配置文件中的bean也加载到这个配置类
public class SpringConfig32 {
}
3. proxyBeanMethods属性
前面的例子中用到了@Configuration这个注解,当我们使用AnnotationConfigApplicationContext加载配置类的时候,配置类可以不添加这个注解。但是这个注解有一个更加强大的功能,它可以保障配置类中使用方法创建的bean的唯一性。 为@Configuration注解设置proxyBeanMethods属性值为true即可,由于此属性默认值为true,所以很少看见明确书写的,除非想放弃此功能。
// proxyBeanMethods = true 获取bean的时候获取到的都是同一个bean
// proxyBeanMethods = false 获取bean的时候获取的是不同的bean
@Configuration(proxyBeanMethods = true)
public class SpringConfig33 {
@Bean
public Cat cat(){
return new Cat();
}
}