最近在看《深入浅出Spring Boot2.x》,把学习的知识点汇总下,这样就不容易忘记。

Springboot可以理解为Spring的注解版,所以学习Springboot 从学习Spring开始。

Spring两个核心理念:
一个是控制反转(Inversion of Control,IoC)
一个是面向切面编程(Aspect Oriented Programming,AOP)。

下面先来学习下控制反转:

一、IoC

IoC:一种通过描述来生成或者获取对象的技术,IoC容器是Spring的核心。

在Spring中把每一个需要管理的对象称为Spring Bean(简称Bean),
而Spring管理这些Bean的容器,被我们称为Spring IoC容器(或者简称IoC容器)。

IoC容器需要具备两个基本的功能
•通过描述管理Bean,包括发布和获取Bean;
•通过描述完成Bean之间的依赖关系

BeanFactory:一个顶级容器接口,所有的IoC容器都需要实现此接口。

ApplicationContext:是BeanFactory的子接口之一,通过继承上级接口,进而继承BeanFactory接口,但是在BeanFactory的基础上,
扩展了消息国际化接口(MessageSource)、环境可配置接口(EnvironmentCapable)、应用事件发布接口(ApplicationEventPublisher)和资源模式解析接口(ResourcePatternResolver),

AnnotationConfigApplicationContext:一个基于注解的IoC容器,通过注解来装配Bean到Spring IoC容器中。

二、装配:

对于扫描装配而言使用的注解是@Component@ComponentScan。

@Component:标明哪个类被扫描进入Spring IoC容器,
@ComponentScan:标明采用何种策略去扫描装配Bean。

@ComponentScan的属性

1.basePackages:定义扫描的包名,在没有定义的情况下,它只会扫描当前包和其子包下的路径;

2.basePackageClasses:定义扫描的类;其中还有includeFilters和excludeFilters,包名可以采用正则式去匹配。

3.includeFilters:是定义满足过滤器(Filter)条件的Bean才去扫描。

4.excludeFilters:是排除过滤器条件的Bean, 它们都需要通过一个注解@Filter去定义,它有一个type类型,可以定义为注解或者正则式等类型。classes定义注解类,pattern定义正则式类。

例子:
@ComponentScan(“com.springboot.chapter3.")
@ComponentScan(basePackages = {“com.springboot.chapter3.pojo”})
@ComponentScan(basePackageClasses = {User.class})
@ComponentScan(basePackages = "com.springboot.chapter3.
”,
excludeFilters = {@Filter(classes = {Service.class})})

@SpringBootApplication也注入了@ComponentScan,探索其源码:

package org.springframework.boot.autoconfigure;
/**imports**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
// 自定义排除的扫描类
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    // 通过类型排除自动配置类
    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
    Class<?>[] exclude() default {};

    // 通过名称排除自动配置类
    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
    String[] excludeName() default {};

    // 定义扫描包
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    // 定义被扫描的类
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

通过它就能够定义扫描哪些包。但是需要注意,它提供的exclude和excludeName两个方法是对于其内部的自动配置类才会生效的。

三、依赖注入:

依赖注入:对象的引用,依赖关系的管理交给容器来做,反转指的是责任的反转。主要通过注解@Autowired实现。

@Autowired
1.注入的机制最基本的一条是根据类型(by type),回顾IoC容器的顶级接口BeanFactory,可以知道IoC容器是通过getBean方法获取对应Bean的,而getBean又支持根据类型(by type)或者根据名称(by name)。

2.首先根据类型找到对应的Bean,如果对应类型的Bean不是唯一的,那么它会根据其属性名称和Bean的名称进行匹配。如果匹配得上,就会使用该Bean;如果还无法匹配,就会抛出异常。

3.@Autowired是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null,需要配置@Autowired属性required为false,例如:@Autowired(required = false)

4.配合@Primary使用,它是一个修改优先权的注解,它告诉Spring IoC容器,当发现有多个同样类型的Bean时,请优先使用我进行注入。

5.配合@Quelifier使用,它的配置项value需要一个字符串去定义,与@Autowired组合在一起,通过类型和名称一起找到Bean。
会调用BeanFactory接口中的方法: T getBean(String name, Class requiredType) throws BeansException;
通过它就能够按照名称和类型的结合找到对象了。

四、生命周期

Bean的生命周期:Bean定义、Bean的初始化、Bean的生存期和Bean的销毁4个部分。

Bean定义过程大致如下:
1.Spring通过我们的配置,如@ComponentScan定义的扫描路径去找到带有@Component的类,这个过程就是一个资源定位的过程

2.一旦找到了资源,那么它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始化Bean,也就没有Bean的实例,它有的仅仅是Bean的定义

3.然后就会把Bean定义发布到Spring IoC容器中。此时,IoC容器也只有Bean的定义,还是没有Bean的实例生成。

完成了这3步只是一个资源定位并将Bean的定义发布到IoC容器的过程,还没有Bean实例的生成,更没有完成依赖注入。

在默认的情况下,Spring会继续去完成Bean的实例化和依赖注入,这样从IoC容器中就可以得到一个依赖注入完成的Bean。但是,有些Bean会受到变化因素的影响,这时我们倒希望是取出Bean的时候完成初始化和依赖注入,换句话说就是让那些Bean只是将定义发布到IoC容器而不做实例化和依赖注入,当我们取出来的时候才做初始化和依赖注入等操作。

初始化Bean
ComponentScan中还有一个配置项lazyInit,只可以配置Boolean值,且默认值为false,也就是默认不进行延迟初始化,因此在默认的情况下Spring会对Bean进行实例化和依赖注入对应的属性值,可断点验证下。
配置懒加载
@ComponentScan(basePackages = “com.springboot.chapter3.*”, lazyInit = true)

如果仅仅是实例化和依赖注入还是比较简单的,还不能完成进行自定义的要求。为了完成依赖注入的功能,Spring在完成依赖注入之后,还提供了一系列的接口和配置来完成Bean初始化的过程。

Spring在完成依赖注入后,还会进行下列流程来完成它的生命周期:

spring boot flowable 驳回 spring boot in action_spring


图中描述的是整个IoC容器初始化Bean的流程,有两点需要注意:

•这些接口和方法是针对什么而言的。在没有注释的情况下的流程节点都是针对单个Bean而言的,但是BeanPostProcessor是针对所有Bean而言的。

•即使你定义了ApplicationContextAware接口,但是有时候并不会调用,这要根据你的IoC容器来决定。我们知道,Spring IoC容器最低的要求是实现BeanFactory接口,而不是实现ApplicationContext接口。

对于那些没有实现ApplicationContext接口的容器,在生命周期对应的ApplicationContextAware定义的方法也是不会被调用的,只有实现了ApplicationContext接口的容器,才会在生命周期调用ApplicationContextAware所定义的setApplicationContext方法。
这个Bean也就实现了生命周期中单个Bean可以实现的所有接口,并且可以通过注解@PostConstruct定义了初始化方法,通过注解@PreDestroy定义了销毁方法。

有时候Bean的定义可能使用的是第三方的类,此时可以使用注解@Bean来配置自定义初始化和销毁方法
@Bean(initMethod =“init”, destroyMethod = “destroy” )

五、使用属性文件

在Spring Boot中使用属性文件,可以采用其默认的application.properties,也可以使用自定义的配置文件。

先在Maven配置文件中加载依赖,这样Spring Boot将创建读取属性文件的上下文,有了依赖,就可以直接使用application.properties文件。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

属性文件依赖

在application.properties文件中配置属性,它是Spring Boot默认的文件。

database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456

对于属性的引用,有两种方法:
1.用Spring表达式:通过@Value注解,使用${......}这样的占位符读取配置在属性文件的内容,@Value注解,既可以加载属性,也可以加在方法上。

@Component
public class DataBaseProperties {
    @Value("${database.driverName}")
    private String driverName = null;
    @Value("${database.url}")
    private String url = null;

    ...
}

2.使用注解@ConfigurationProperties,这里在注解中配置的字符串database,将与POJO的属性名称组成属性的全限定名去配置文件里查找,这样就能将对应的属性读入到POJO当中。

@Component
@ConfigurationProperties("database")
public class DataBaseProperties {

    private String driverName = null;

    private String url = null;

	...
}

使用别的配置文件:需要使用@PropertySource去定义对应的属性文件,把它加载到Spring的上下文中,value可以配置多个配置文件。使用classpath前缀,意味着去类文件路径下找到属性文件;

ignoreResourceNotFound则是是否忽略配置文件找不到的问题,默认值为false,也就是没有找到属性文件,就会报错;这里配置为true,也就是找不到就忽略掉,不会报错。

@SpringBootApplication
@ComponentScan(basePackages = {"com.springboot.chapter3"})
@PropertySource(value={"classpath:jdbc.properties"}, ignoreResourceNotFound=true)
public class Chapter3Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter3Application.class, args);
    }
}