《B站-Spring源码解析》学习笔记(一)——组件管理

视频地址:https://www.bilibili.com/video/BV1oW41167AV
对应代码Git库地址:https://github.com/whh306318848/spring-annotation.git

  1. Spring中所有的组件都放在IOC容器中,组件之间的关系通过容器进行自动装配(DI)即依赖注入;
  2. 以前是通过xml方式配置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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="person" class="com.atguigu.bean.Person">
        <property name="age" value="18"></property>
        <property name="name" value="zhangsan"></property>
    </bean>
</beans>

在代码中通过new一个ClassPathXmlApplicationContext对象(返回的是ApplicationContext的IOC容器)使用配置文件的方式构造Bean,构造函数的参数是配置文件的路径(基于resources目录的相对路径)

// 使用配置文件的方式构造一个Bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 用id获取
Person bean = (Person) applicationContext.getBean("person");
System.out.println(bean);

通过ApplicationContext的getBean方法以Bean的id作为参数,获取在容器中注册的Bean对象
3. 现在可以通过配置类的方式注册对象,使用@Configuration注解告诉Spring某个类是配置类,作用与xml配置文件一样
4. 在配置类中通过给function添加@Bean注解(适用于给容器中导入第三方组件或类),实现向容器中注册Bean,其作用与xml配置文件中的bean标签一样

@Bean(value = "person") // 通过value指定id
public Person person01() {
    return new Person("lisi", 20);
}

4.1. 方法的返回值类型即是注册Bean的类型
4.2. 默认情况下方法名是Bean的id,也可以通过@Bean注解的value参数手动设置Bean的id
4.3. 在代码中通过new一个AnnotationConfigApplicationContext对象(返回的是ApplicationContext的IOC容器)使用配置类的方式构建Bean,构造函数的参数是配置类的类型

// 使用配置类的方式构造一个Bean
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
// 用类型获取
Person bean = applicationContext.getBean(Person.class);

通过ApplicationContext的getBean方法以Bean的类型作为参数,获取在容器中注册的Bean对象
5. 在实际开发中,使用包扫描(适用于自己写的类导入到容器中)的情况比较多,包扫描的配置有两种,分别是配置文件配置和注解方式配置
5.1. 若使用配置文件方式,需要在配置文件中添加context:component-scan标签

<context:component-scan base-package="com.atguigu"></context:component-scan>

其中base-package的值指定了包扫描的范围,意思是扫描该包下的所有组件,只要标注了@Controller、@Service、@Repository、@Component这四个注解中任何一个注解,都会被自动扫描加进容器中
5.2. 使用注解方式配置包扫描时,需要用到@ComponentScan注解,该注解的value参数用于指定要扫描的包,其作用与配置文件的base-package值一样

@ComponentScan(value = "com.atguigu")

5.3. 通过getBeanDefinitionNames方法可以获取到容器中所有的bean的名称

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
    System.out.println(name);
}

5.4. 可以通过@ComponentScan注解的excludeFilter属性设置需要被排除的类,其值是一个@Filter数组
5.5. @Filter有两个常用属性分别是type和classes,type用于标注过滤规则,总共有五个,分别是:
– ANNOTATION(按照注解进行过滤)
– ASSIGNABLE_TYPE(按照给定的类型过滤,包含其子类、实现类等)
– ASPECTJ(按照ASPECTJ表达式过滤,不常用)
– REGEX(按照正则表达式过滤)
– CUSTOM(按照自定义规则规律,自定义规则需要实现TypeFilter接口)
classes是一个类型数组

@ComponentScan(value = "com.atguigu", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
})

5.6. 当配置includeFilters规则时,需要先将@ComponentScan注解的useDefaultFilters参数设置为false,否则默认的包扫描规则就是扫描所有的注解
5.7. 如果使用的是JDK 8及以上版本,@ComponentScan是一个重复组件,可以在配置类上定义多个@ComponentScan注解,用以配置多个包扫描规则
5.8. 考虑到兼容性问题,如果有多个包扫描规则,建议使用@ComponentScans注解实现多个包扫描策略,@ComponentScans注解只有一个value参数,其值是@ComponentScan数组

// 配置多个包扫描规则
@ComponentScans(value = {
        @ComponentScan(value = "com.atguigu", includeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}),
                @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
        }, useDefaultFilters = false)   // 配置包扫描的范围
})

5.9. @Filter注解中,若type为FilterType.CUSTOM,则classes的值必须是实现了TypeFilter接口的类,其中有一个match方法,用于自定义过滤规则,指定报名下的每一个类,都会被扫描到并由match方法进行匹配,match方法有两个参数分别是MetadataReader和MetadataReaderFactory,metadataReader的值是“当前正在扫描的类的信息”,metadataReaderFactory的值是“其他任何类(不止当前类)的信息”,其返回值是boolean型,若当前类需要被过滤(包含或排除),则返回true,反之则返回false

public class MyTypeFilter implements TypeFilter {
    /***
     * @param metadataReader 读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory 获取到其他任何类(不止当前类)的信息
     * @return 若该类被过滤,则返回true,反之则返回false
     **/
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 获取当前正在扫描的类的类信息,如类型、实现了什么接口等
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前类资源(类的路径等)
        Resource resource = metadataReader.getResource();
        // 获取类名(含包名)
        String className = classMetadata.getClassName();
        System.out.println("--->" + className);
        if (className.contains("er")) {
            return true;
        }
        return false;
    }
}
  1. 在IOC容器中,默认Bean都是单实例的,无论从容器中获取到少次相同类型的Bean,其实返回的都是同一个对象,我们可以通过@Scope注解指定每个Bean的作用范围
    6.1. @Scope注解的value取值范围包括4个

– prototype:多实例,IOC容器启动时并不会去调用方法创建对象放在容器中,而是每次获取的时候都会调用方法创建对象
– singleton:单实例(默认值),IOC容器启动会调用方法创建对象到IOC容器中,以后每次获取就是直接从容器(map.get())中拿
– request:同一次请求创建一个实例
– session:同一个session创建一个实例

@Scope("prototype")
@Bean("person")
public Person person() {
    System.out.println("给容器中添加Person...");
    return new Person("张三", 25);
}

6.2. 当Bean是单实例时,在IOC容器创建好之后,Bean实例马上就会被创建,以后每次获取,都是从容器中取相同的Bean实例
6.3. 当Bean是多实例时,是在每次获取对象的时候,IOC容器才会创建Bean实例并返回
7. 当Bean是单实例时,可以通过@Lazy注解设置懒加载,即首次获取对象时,再创建Bean实例,而不是在IOC容器创建好之后,马上创建Bean实例

@Lazy
@Bean("person")
public Person person() {
    System.out.println("给容器中添加Person...");
    return new Person("张三", 25);
}
  1. Spring可以按照条件给容器中注册Bean实例,通过@Conditional注解实现,@Conditional注解的值是实现Condition接口的类的数组,该注解可以标注在类上,也可以标注在方法上
    8.1 Condition接口中定义了一个matches方法,该方法有两个值ConditionContext表示“能使用的上下文(环境信息)”,AnnotatedTypeMetadata表示“当前标注了Condition注解的注释信息”
/**
 * @author: wuhaohua
 * @date: Created in 2020/12/28 17:53
 * @description: 判断是否Linux操作系统
 */
public class LinuxCondition implements Condition {
    /***
     * @param conditionContext 判断能使用的上下文(环境)
     * @param annotatedTypeMetadata 当前标注了Condition注解的注释信息
     * @throws
     * @Author: wuhaohua
     * @Date: 2020/12/28
     * @Description:
     **/
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 1、能获取到ioc使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 2、获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 3、获取到环境信息
        Environment environment = conditionContext.getEnvironment();
        // 4、获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();

        String property = environment.getProperty("os.name");
        if (property.contains("Linux")) {
            return true;
        }
        return false;
    }
}
/**
 * @author: wuhaohua
 * @date: Created in 2020/12/28 17:54
 * @description: 判断是否Windows操作系统
 */
public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 1、能获取到ioc使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 2、获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 3、获取到环境信息
        Environment environment = conditionContext.getEnvironment();
        // 4、获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();

        String property = environment.getProperty("os.name");

        if (property.contains("Windows")) {
            return true;
        }
        return false;
    }
}

8.2. 在定义好Condition的实现类后,在需要控制的Bean注册方法上增加相应的注解

/***
 * @Conditional({Condition}):按照一定的条件进行判断,满足条件给容器中注册bean
 * 如果系统是Windows,就给容器中注册("bill")
 * 如果系统是Linux,就给容器中注册("linus")
 **/
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person01() {
    return new Person("Bill Gates", 62);
}

@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person02() {
    return new Person("Linus", 48);
}

8.3. 当@Conditional注解标注在类上时,满足condition条件,这个类中配置的所有bean注册才会生效
9. 使用@Import也可以快速的给容器中导入一个组件,将其标注在配置类上,其值是一个类型数组,被赋值的类会自动注册到IOC容器中,注册的Bean的id默认是组件的全类名(即含包名的类名)

@Configuration
// 导入组件,id默认是组件的全类名(含包名)
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig2 {
    //......
}

9.1. 除了直接将类写在@Import注解中,也可以通过实现ImportSelector接口的方式导入其他类,实现ImportSelector接口中的selectImports方法,该方法只有一个参数AnnotationMetadata,其值是当前标注@Import注解的类(即MainConfig2类)的所有信息,该方法的返回值是要导入到容器中的组件全类名数组

/**
 * @author: wuhaohua
 * @date: Created in 2020/12/28 19:59
 * @description: 自定义逻辑返回需要导入的组件
 */
public class MyImportSelector implements ImportSelector {
    /***
     * @param annotationMetadata 当前标注@Import注解的类的所有信息
     * @return 要导入到容器中的组件全类名
     * @throws
     * @Author: wuhaohua
     * @Date: 2020/12/28
     * @Description:
     **/
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {

        //方法不要返回null值,否则会报空指针异常
        return new String[]{"com.atguigu.bean.Blue", "com.atguigu.bean.Yellow"};
    }
}

9.2. 除了ImportSelector还能通过实现ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法可以自定义给容器中注册bean(手动注册bean到容器中,在SpringBoot源码中用的比较多),该方法有两个值,分别是AnnotationMetadata(当前类的所有注解及其他信息)和BeanDefinitionRegistry(bean定义的注册类,所有bean的定义都在这注册,可以通过这个变量给容器注册一个bean),主要通过操作BeanDefinitionRegistry进行组件添加,并且可以指定Bean的名字

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /***
     * @param importingClassMetadata 当前类的所有注解及其他信息
     * @param registry bean定义的注册类,所有bean的定义都在这注册,可以通过这个变量给容器注册一个bean
     *                 把所有需要添加到容器中的bean:调用BeanDefinitionRegistry.registerBeanDefinition手工注册进来
     * @Author: wuhaohua
     * @Date: 2020/12/29 
     * @Description: 
     **/
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean red = registry.containsBeanDefinition("com.atguigu.bean.Red");
        boolean blue = registry.containsBeanDefinition("com.atguigu.bean.Blue");
        if (red && blue) {
            // 指定Bean的定义信息,即bean的类型、bean的scope等等
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            // 指定bean名
            registry.registerBeanDefinition("rainBow", beanDefinition);
        }
    }
}
  1. 使用Sprig提供的FactoryBean(工厂Bean)也可以向容器中注册Bean,区别于普通的Bean,当普通的Bean注册到容器时,容器会调用其无参构造函数创建一个对象注册在容器中,而FactoryBean是一个接口,容器会调用其getObject方法,将其返回的对象注册到容器中(也会调用其getObjectType和isSingleton方法)
    10.1. 若要使用FactoryBean,需要自定义类并实现FactoryBean接口,接口上的泛型代表着需要注册到容器的类
/**
 * @author: wuhaohua
 * @date: Created in 2020/12/29 15:31
 * @description: 创建一个Spring定义的FactoryBean
 */
public class ColorFactoryBean implements FactoryBean<Color> {
    /***
     * @return 需要添加到容器中的Color对象
     * @Author: wuhaohua
     * @Date: 2020/12/29 
     * @Description: 返回一个Color对应,这个对象会被添加到容器中
     **/
    @Override
    public Color getObject() throws Exception {
        System.out.println("ColorFactoryBean getObject...");
        return new Color();
    }

    /***
     * @return Bean的类型
     * @Author: wuhaohua
     * @Date: 2020/12/29
     * @Description: Bean的类型
     **/
    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    /***
     * @return 若返回true,这个bean是单例的,在容器中只保存一份,若返回false,每次获取时都会创建一个新的bean
     * @Author: wuhaohua
     * @Date: 2020/12/29
     * @Description: 控制该bean是否是单实例
     **/
    @Override
    public boolean isSingleton() {
        return false;
    }
}

然后需要在配置类中将自定义的FactoryBean实现类作为Bean对象添加到容器中

@Bean
public ColorFactoryBean colorFactoryBean() {
    return new ColorFactoryBean();
}

10.2. 这里需要注意,虽然从代码层面看起来装配的是ColorFactoryBean,但实际上装配的是ColorFactoryBean的getObject方法创建的Color对象,若想获取到ColorFactoryBean本身的话,需要在获取的beanId前面增加一个&前缀

@Test
public void testImport() {
    printBeans(applicationContext);
    Blue bean = applicationContext.getBean(Blue.class);
    System.out.println(bean);

    // 工厂Bean获取的是调用getObject创建的对象
    Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
    System.out.println("bean的类型" + colorFactoryBean.getClass());

    Object colorFactoryBean3 = applicationContext.getBean("&colorFactoryBean");
    System.out.println("bean的类型" + colorFactoryBean3.getClass());
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据