Spring Framework 源码学习笔记(一)

3,561 阅读7分钟

Chapter 01 Spring基础及组件使用

Section 01 - Spring是什么?

  Spring致力于J2EE的各种解决方案,而不仅仅专注于某一层解决方案。可以说Spring是企业应用开发的“一站式”选择, Spring贯穿于表现层、业务层、持久层,然而Spring并不想取代那些已经有的框架,而是以高度的开放性,与这些已有的框架进行整合。

  Spring早期使用最多的是IoC容器及DI依赖注入,通过在application.xml文件中配置bean标签,将项目中的组件或对象交给IoC容器管理,当初始化IoC容器时,Bean会被创建或者实例化,这就导致一个巨大项目中需要配置非常多的bean标签,维护起来非常繁琐。

Spring 核心体系

  • Spring Core:即,Spring核心,它是框架最基础的部分,提供IOC和依赖注入特性
  • Spring Context:即,Spring上下文容器,它是BeanFactory功能加强的一个子接口
  • Spring Web:它提供Web应用开发的支持
  • Spring MVC:它针对Web应用中MVC思想的实现
  • Spring DAO:提供对JDBC抽象层,简化了JDBC编码,同时,编码更具有健壮性。
  • Spring ORM:它支持用于流行的ORM框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO的整合等等。

Spring常用组件

image.png

Section 02 - XML与注解注册Bean

创建maven项目, 添加依赖 添加entity包,新增实体类Person

public class Person {
    private String name;
    private Integer age;
    //省略getter/setter/toString
}

xml配置方式注册Bean

在resource目录下新建beans.xml配置文件

<?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">

    <bean id="person" class="com.citi.entity.Person">
        <property name="name" value="Peter"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>

在test pacakge下新建SingleBeanContainerTest测试类,加入main方法,调用getBeanByXml()方法,测试从容器中获取bean

public static void getBeanByXml(){
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
    Person person = (Person) context.getBean("person");
    System.out.println(person);
}

控制台打印

image.png

使用注解方式注册Bean 新建config包,增加配置类BeanConfig

@Configuration
public class BeanConfig {

    @Bean
    public Person person(){
        Person person = new Person();
        person.setName("Stark");
        person.setAge(40);
        return person;
    }
}

@Configuration:标识为一个配置类,等同于配置文件 @Bean:给容器注册一个Bean,类型为返回值的类型,bean id为返回名 SingleBeanContainerTest中新增方法,在main方法中执行

public static void getBeanByAnno(){
    ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
    Person person = (Person) context.getBean("person");
    System.out.println(person);

    String[] beanNamesForType = context.getBeanNamesForType(Person.class);
    for (String s : beanNamesForType) {
        System.out.println("Bean id:" + s);
    }
}

控制台输出

image.png

修改BeanConfig中@Bean注解下的方法的方法名为stark

@Bean
public Person stark(){
    Person person = new Person();
    person.setName("Stark");
    person.setAge(40);
    return person;
}

再次运行SingleBeanContainerTest的mian方法,控制台报错No bean named 'person' available

image.png 这是因为代码中获取的bean的id为person,而配置了中@Bean注解下的方法名也就是bean的id为stark,所以出现报错,更改getBean("person")为getBean("stark"),再次执行,获取成功

image.png 除了修改@Bean注解下的方法名来定义Bean的id,也可以通过@Bean("thor")的方式来定义Bean的名称

image.png

IoC容器的数据结果为Map,注册Bean是就是往Map里面添加数据,使用put("key",value),key为Bean的id,value就是对象,

Section 03 - XML与注解批量注册Bean

@CompponmentScan 代替@Bean 分别新增controller,service,dao三个package,并增加响应的类,通过@Controller,@Service,@Repository三个注解将三个类声明为Bean

@Controller
public class PersonController {
}
@Service
public class PersonService {
}
@Repository
public class PersonDao {
}

修改BeanConfig,增加@ComponentScan注解,将com.citi包下面的所有Bean都扫描到BeanConfig这个配置类中,就相当于XML配置文件中有许多bean标签,好处是不用一个个写bean标签,通过一个注解可以扫描所有的Bean

@Configuration
@ComponentScan(basePackages = "com.citi")
public class BeanConfig {

}

测试IoC容器是否实例化扫描到的Bean,新增一个ComponentScanTest测试类

public class ComponentScanTest {

    @Test
    public void getBeansByScan(){
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

控制台输出如下,自定义的三个类,包括BeanConfi本身都被容器实例化

image.png

entity包中的Person类没有被实例化,是因为Person类上没有添加注解,也即是没有配置bean标签,所以没有注册到容器中,也就没有被实例化,给Person实体类添加一个@Component注解,标识为一个Bean,再次执行测试方法,控制台打印结果如下,Person类被实例化

image.png @ComponentScan源码

image.png includeFilters()的使用

image.png includeFilters()和excludeFilters()返回都是一个Filter数组

FilterType是一个枚举类,默认是ANNOTATION,注解方式

image.png

修改BeanConfig代码,增加includeFilters(),只扫描com.citi包下面的@Controller,@Service注解标识的Bean,一定不要忘记useDefaultFilters = false

@Configuration
@ComponentScan(basePackages = "com.citi",
        includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
        },
        useDefaultFilters = false)
public class BeanConfig {

}

执行测试,控制台输出只包含了@Controller,@Service注解的Bean,@Componet和@Repository注解标识的类并没有被容器管理

image.png

Plus:为什么使用includeFilters时,要配置useDefaultFilters = false?

image.png 当useDefaultFilters = true时,进入到registerDefaultFilters()方法中

image.png 该方法中将@Component注解的Bean都加入到includeFilters中,而@Controller,@Service,@Repository都是基于@Component的注解,所有useDefaultFilters = true会导致设置是扫描@Controller和@Service注解的Bean不生效

使用excludeFilters(),将BeanConfig中includeFilters改为excludeFilters(),并且useDefaultFilters = true,执行测试,可以看出只包含了除了@Controller,@Service标注的Bean之外的其他Bean的实例化对象 image.png

使用FilterType枚举类中的ASSIGNABLE_TYPE进行过滤Bean,修改BeanConfig为如下,表示触PersonController和PersonService之外的其他Bean

@Configuration
@ComponentScan(basePackages = "com.citi",
        excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersonController.class, PersonService.class})
        },
        useDefaultFilters = true)
public class BeanConfig {

}

image.png

使用自定义的FilterType,在config包下新增CustTypeFilter,继承TypeFilter

public class CustTypeFilter implements TypeFilter {

    /**
     * @param metadataReader 读取到当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @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);

        // 过滤条件,return true表示过滤掉符合这个条件的Bean
        if (className.contains("service")){
            return true;
        }
        return false;
    }
}

image.png

除了@ComponentScan之外还可以添加@ComponentScans,也就是多个@ComponentScan

image.png

使用了两个@ComponentScan注解,一个是去除自定义的Bean,即bean id包含“service”字符串的Bean,另外一个使用Annotation 类型的Filter,去除@Controller注解标记的Bean

@ComponentScans({@ComponentScan(basePackages = "com.citi",
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {CustTypeFilter.class})
        },
        useDefaultFilters = true),
        @ComponentScan(basePackages = "com.citi",
                excludeFilters = {
                        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
                },
                useDefaultFilters = true)})

public class BeanConfig {

}

这里控制台应该输出包含除了PeresonController和PersonService外的实例化对象,根据控制台打印可以看出配置的两个@ComponentScan都没有生效,这里还有待继续探索,

image.png

Section 04 - Bean的单实例和多实例

IoC容器不管是使用注解还是xml方式,默认都是单实例的,IoC容器初始化的时候就会被创建,多实例仅当bean被使用的时候才会创建 修改BeanConfi为

@Configuration
public class BeanConfig {

        @Bean
        public Person person(){
                Person person = new Person();
                person.setName("Stark");
                person.setAge(40);
                return person;
        }
}

新增测试类SingleOrMultInstanceTest,执行testSingleBean()方法

public class SingleOrMultiInstanceTest {

    @Test
    public void testSingleBean(){
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        Person person = (Person) context.getBean("person");
        Person person1 = (Person) context.getBean("person");
        System.out.println("是否为单实例:" + (person == person1));
    }
}

查看输出结果,通过比较从容器中获取的两个bean是否为一个bean,默认是单实例

image.png

@Scope注解可以配置Bean为单实例或者多实例 @Scope源码

image.png

image.png

修改BeanConfig

@Configuration
public class BeanConfig {

        @Bean
        @Scope("prototype")
        public Person person(){
                Person person = new Person();
                person.setName("Stark");
                person.setAge(40);
                return person;
        }
}

执行测试,此时已经为多实例

image.png

Section 05 - 懒加载@Lazy

修改BeanConfig,增加@Lazy注解,懒加载指针多单实例Bean,因为单实例是在容器初始化是就创建对象,增加@Lazy注解后,当调用getBean()获取实例化对象时容器才会实例化Bean 首先将BeanConfig中@Scope注解注释

@Configuration
public class BeanConfig {

        //@Lazy
        @Bean
        //@Scope("prototype")
        public Person person(){
                System.out.println("Bean被实例化");
                Person person = new Person();
                person.setName("Stark");
                person.setAge(40);
                return person;
        }
}

新增LazyLoadTest,testLazyLoad()方法中只包含容器初始化的代码,执行该方法

public class LazyLoadTest {

    @Test
    public void testLazyLoad(){
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        //Person person = (Person) context.getBean("person");
        //System.out.println(person);
    }
}

查看控制台,打印了Bean被实例化,说明容器初始化是就执行了Bean实例化操作

image.png

放开BeanConfig中的@Lazy注解,再次执行,Bean没有被实例化

image.png

在LazyLoadTest类中的testLazyLoad()方法中增加获取Bean的代码

public class LazyLoadTest {

    @Test
    public void testLazyLoad(){
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        Person person = (Person) context.getBean("person");
        System.out.println(person);
    }
}

再次执行,结合上一次的执行结果,说明增加了@Lazy注解后,容器初始化是不在实例化Bean,而是在getBean时才会实例化Bean

image.png