概述

1、在项目中是如何使用Spring的?

2、将Bean注册进IOC容器相关的细节

IOC使用细节

Bean组件注册细节

在Spring容器的底层,最重要的功能就是IOC和DI,也就是控制反转和依赖注入。 DI和IOC它俩之间的关系是DI不能单独存在,DI需要在IOC的基础上来完成。
之前我们学习Spring的时候是通过编写XML配置文件的方式来将我们的组件注册进ioc进容器的,但这种编写XML配置文件的方式,既要用一个bean标签表示一个组件,又要用一个property组件给某个属性赋值,不仅繁琐,而且还很容易出错,稍有不慎就会导致编写的应用程序各种报错,要是在eclipse中没有安装spring插件,排查半天你也不一定会发现是在XML文件配置因为某一个字母出错而导致整个项目报错!另外,每个项目都编写大量的XML文件来配置Spring,也大大增加了项目维护的复杂度,往往很多个项目的Spring XML文件的配置大部分是相同的,只有很少量的配置不同,这也造成了配置文件上的冗余。而要是使用了Spring注解驱动来开发进行组件注册,就不需要书写繁琐的xml文件了。
在Spring内部,所有的Bean组件都会放到IOC容器中,组件之间的关系通过IOC容器来自动装配,也就是我们所说的依赖注入。接下来,我们就使用注解的方式来完成容器中组件的注册、管理及依赖、注入等功能。

注册单个Bean组件进IOC容器

XML文件方式注册单个组件

这种方式不用多说,直接将要注册进容器的bean对象信息用标签声明在配置文件中,等到 IOC容器加载完之后就可以通过ClassPathXmlApplicationContext获取到这个bean了;

注解方式注册单个组件

使用注解的方式将bean注册进容器的话,需要用到@Configuration注解以及@Bean注解搭配使用。具体的做法是创建一个类并用@Configuration注解在类上声明它是一个配置类;然后在方法声明一个@Bean注解,表示这个方法的作用是将方法的返回值作为一个bean组件注册进IOC容器,而且该方法的方法名就是该组件在IOC容器中的名称,也就是使用方法名这个字符串来作为获取容器中bean对象的key;
IOC容器的顶层接口是BeanFactory,ClassPathXmlApplicationContext是他的一个子实现类,我们使用配置文件的方式获取注册进容器中的bean使用可以使用该实现类来获取;而现在使用注解的方式将组件注册进容器的话,就不能使用上面这个实现类去获取了,因为没有配置文件;AnnotationConfigApplicationContext是IOC容器顶层接口的另一个实现,使用注解注册的组件我们可以使用它来获取;
这里有一个细节: 我们在使用注解方式向Spring的IOC容器中注入JavaBean时,如果没有在@Bean注解中明确指定bean的名称,那么就会使用当前方法的名称来作为bean的名称;如果在@Bean注解中明确指定了bean的名称,那么就会使用@Bean注解中指定的名称来作为bean的名称。

批量注册多个Bean组件进IOC容器

我们之前使用Spring的XML方式注册组件的时候,可以使用使用XML文件配置包扫描的方式批量的将某个包下的类全部添加进ioc容器,而实际的项目开发中,我们更多的也是使用Spring的包扫描功能对项目中的包进行扫描,凡是在指定的包或其子包中的类上标注了@Repository、@Service、@Controller、@Component注解的类都会被扫描到,并将这个类注入到Spring容器中。
Spring包扫描功能可以使用XML配置文件进行配置,也可以使用@ComponentScan注解的方式进行设置,使用@ComponentScan注解进行设置比使用XML配置文件来配置要简单的多。

XML配置文件方式批量注册组件进容器

使用XML配置文件的方式去配置包的扫描,需要在Spring的XML配置文件中的beans根节点中引入相应的的标签,然后在该标签内的属性配置一下要扫描的包,这样配置完之后,这个包类以及这个包下的子包中的类只要是标注了@Repository、@Service、@Controller、@Component注解的类都会被注册进IOC容器;

注解方式批量注册组件

使用注解的方式配置包扫描的话,配置文件也换成配置类方便一些;我们需要用注解的方式达到像使用XML文件一样的配置包扫描效果,就只需要在我们的配置类(标注了@Configration注解的累)上添加@ComponentScan注解,并加上一个value属性将扫描的包指定为要扫描的包路径,同时要确保该路径下的要注册进IOC容器的类上标注了@Repository、@Service、@Controller、@Component注解。

但是仅仅是这样的话,这些批量注册进容器中的类只是创建了对象,而对象中的属性还没有被赋值;这个时候DI属性注入的作用的出来了,不过要搭配@Autowired注解使用,在标注了@Repository、@Service、@Controller、@Component注解的类内部属性上标注@Autowired注解,到时候创建这个bean时就会去容器中找到bean名字与标注了@Autowired注解的属性名相同的bean组件进入注入,这样bean对象的属性值也就有了;

指定包扫描时的过滤规则

我们按住CTRL点开ComponentScan注解类并查看其源码就可以发现里面有两个Filter,就是我们在XML文件方式配置包扫描的两种过滤规则:

  • includeFilters:扫描时只包括哪些类
  • excludeFilters:扫描时要排除哪些类

includeFilters与excludeFilters的写法就是写一些Filter数组进去,它的值有以下几种规则:
FilterType.ANNOTATION :按照注解排除
FilterType.ASPECTJ :接口表达式
FilterType.ASSIGNABLE_TYPE:按照类型排除
FilterType.REGEX:按照正则表达示
配置方式:
image.png
表示按照注解的方式排除标注了Controller以及Service这两种注解的类;
另一个类似;

将Bean组件注册进IOC容器时设置Bean组件的作用域

Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,并将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象。如果每次从Spring容器中获取对象时,都要创建一个新的实例对象,那么该如何处理呢?此时就需要使用@Scope注解来设置组件的作用域了。
而使用@Scope注解来设置组件的作用域就相当于我们在XML配置文件中为bean标签设置scope属性,
image.png

懒加载模式

Spring在启动时,默认会将单实例的bean进行实例化,并加载到Spring容器中去。也就是说,单实例bean默认是在Spring容器启动的时候创建对象,并且还会将对象加载到Spring容器中。如果说我们不想要某个单实例的bean在容器创建的时候跟着容器进行创建,而是进行延迟加载,即在容器创建的时候先不创建这个bean组件而是等到需要用到该bean组件的时候再创建(同时要保证它是单实例的),那么该如何处理呢?此时,就需要使用到@Lazy注解了。
这种延迟加载的的机制也叫懒加载模式, 即是在Spring容器启动的时候,先不创建组件对象,在第一次使用(获取)bean的时候再来创建对象,并进行一些初始化。
image.png

按照条件将Bean组件注册进IOC容器

Spring不仅支持在解配置类中的方法使用@Bean注解注册组件,而且Spring还支持使用 @Conditional注解按照条件向IOC容器中注册bean,满足条件的bean就会被注册到IOC容器中,不满足条件的bean就不会被注册到IOC容器中。
这个Condition它是一个接口。也就是说我们使用@Conditional注解时它的参数可以是一个个的实现Condition接口的类,所以我们可以写一个实现类来实现Spring提供的Condition接口,将这个类写在@Conditional注解的参数上,就会按照我们写的实现类的条件进行检查类了。
在springboot的底层里,用到了狠多的Conditional注解
20201129173723444.png

使用@Import注解给容器导入组件

  1. 我们自己写的类,自然是可以通过包扫描+给组件标注注解(@Controller@Servcie@Repository@Component)的形式将其注册到IOC容器中,但这种方式比较有局限性,局限于我们自己写的类,比方说我们自己写的类,我们当然能把以上这些注解标注上去了。 <br /> 通过包扫描+给组件标注注解(@Controller@Servcie@Repository@Component)的形式自然可以将我们写的类注册到IOC容器中,毕竟我们能够自己添加(@Controller@Servcie@Repository@Component)等注解在类上,但是 如果不是我们自己写的类,比如说我们在项目中会经常引入一些第三方的类库,我们需要将这些第三方类库中的类注册到Spring容器中,该怎么办呢?我们又不能直接在人家的源码上加上注解,所以此时,我们就可以使用@Bean@Import注解将这些类快速的导入Spring容器中。 <br /> 所以到目前为止我们有以下几种方式将一个类注册进ioc容器中:
  • 包扫描+给组件标注注解(@Controller、@Servcie、@Repository、@Component),但这种方式比较有局限性,局限于我们自己写的类
  • @Bean注解,通常用于导入第三方包中的组件
  • @Import注解,快速向Spring容器中导入一个组件
  • 还有一种是按条件装配

这个Import注解有三种用法:
Import注解的第一种用法就是在配置类上加上该@Import注解,然后将要导入容器中的类作为参数,就可以将类作为bean组件添加进容器;
image.png
Import注解的源码中可以看出它可以配合 Configuration、ImportSelector以及ImportBeanDefinitionRegistrar来使用;
这个ImportSelector接口有一个selectImports方法,返回值是一个类的全类名数组,作用是将包含在该数组的类添加入容器,故我们可以写一个实现类去实现该接口去自定义需要导入的组件;只不过在容器中bean组件的名称是类的全类名;
image.png
第三种用法是配合ImportBeanDefinitionRegistrar来将组件添加进容器,因为BeanDefinitionRegistry是一个注册类接口,我们可以通过它来给容器注册bean。
具体的做法是定义一个实现类来实现ImportBeanDefinitionRegistrar接口,重写其中的注册bean的定义信息方法;然后在配置类的Import注解参数上加上这个实现类即可;

使用FactoryBean向容器中注册bean

FactoryBean是一个工厂bean,它跟普通的bean(比如自己写的person类)的区别是普通的bean导入容器,容器会调用普通bean的无参构造方法来创建一个对象 ;而这个工厂bean(它也是一个接口)跟普通bean不一样,容器会调用它的getObject方法来返回一个对象,将该对象添加进容器中。
使用这种方式将bean注册进容器的话,需要实现FactoryBean接口的getObject方法,该方法的返回值就是要注册进容器的bean;最后不要忘记将这个FactoryBean接口的实现类注册进容器;

生命周期相关细节

通常意义上讲的bean的生命周期,指的是bean从创建到初始化,经过一系列的流程,最终销毁的过程。只不过,在Spring中,bean的生命周期是由Spring容器来管理的。

init初始化跟destroy销毁方法

虽然bean的生命周期是在Spring中管理的,但是我们可以自己指定bean的初始化和销毁的方法。我们指定了bean的初始化和销毁方法之后,当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。
以前在XML文件中我们要定义bean的生命周期方法可以直接在bean标签上加上init-method属性添加上初始化方法,destroy-method属性添加上销毁方法。需要注意的是,在Bean中,需要存在init属性指定的方法和destroy方法。而且Spring中还规定,这里的init()方法和destroy()方法必须是无参方法,但可以抛出异常。
使用注解的方式给bean组件添加上生命周期方法是在用@Bean注解将组件添加进容器时添加上initMethod跟destroyMethod两个属性指定的,同样需要在在Bean中,需要存在init属性指定的方法和destroy方法。
image.png
注意,没有配置懒加载的单例会在容器创建的时候创建,容器销毁的时候销毁;而多例是在使用到的时候才会创建,创建后容器关闭时才会销毁;而如果多例对象没有被使用过,那么容器创建时多例对象也不会创建,关闭容器时也不会执行销毁;

InitializingBean、DisposableBean接口

正如InitializingBean接口的名字所示,该接口用来设置初始化方法的接口,它里面只包括afterPropertiesSet方法,凡是继承该接口的JavaBean类,在bean的属性初始化后都会执行该方法。
而DisposableBean也是顾名思义,用来设置bean的销毁方法。

同样,使用该方式时,单实例对象的初始化方法与销毁方法都是随着容器的创建与销毁进行的,而多例对象是在使用时才会被创建;

@PostConstruct注解和@PreDestroy注解(可能要导包)

JDK中还提供了两个注解能够在bean创建完成并且属性赋值完成之后执行一些初始化工作和在容器销毁bean之前通知我们进行一些清理工作。
@PostConstruct注解被用来修饰一个非静态的void()方法。被@PostConstruct注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。被@PostConstruct注解修饰的方法通常在构造函数之后,init()方法之前执行。
有时候我们是会在Spring框架中使用到@PostConstruct注解的,该注解的方法在整个bean初始化中的执行顺序如下:
Constructor(构造方法)→@Autowired(属性完成注入后)→@PostConstruct(标注的方法)

BeanPostProcessor后置处理器

BeanPostProcessor是一个接口,它内部声明了两个方法:postProcessBeforeInitialization和postProcessAfterInitialization,在它的源码注释中表明这两个方法分别是在Spring容器中的bean初始化前后执行,所以Spring容器中的每一个bean对象初始化前后,都会执行BeanPostProcessor接口的实现类中的这两个方法。
也就是说,postProcessBeforeInitialization方法会在bean实例化和属性设置之后,自定义初始化方法之前被调用,而postProcessAfterInitialization方法会在自定义初始化方法之后被调用。当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行。

生命周期各种方法执行顺序

一个bean的创建流程大概有以下几个阶段:
实例化 Instantiation —> 属性赋值 Populate —> 初始化 Initialization —> 销毁 Destruction
添加上各种方法之后的执行顺序如下:
实例化 bean(构造方法) —> 属性依赖注入 Populate(set方法) —>查看bean是否实现xxxAware接口—> BeanPostProcessor的前置处理方法—> InitializingBean接口方法—>init-method方法—>初始化 Initialization —> BeanPostProcessor的后置处理方法—>DisposableBean接口方法—>destroy-method方法—>销毁 Destruction;

属性赋值

使用@Value注解为bean的属性赋值

之前我们为属性赋值的时候是在bean标签里面写一个property标签,这样就可以给相应的属性赋值了。
现在使用注解的方式,在创建的bean的时候顺便在属性上标注@Value注解给bean的属性赋值。

属性的自动装配

Autowired注解装配

@Autowired注解可以对类成员变量、方法和构造函数进行标注,完成自动装配的工作。@Autowired注解可以放在类、接口以及方法上。
@Autowired注解的特性:
1. @Autowired注解默认是优先按照类型去容器中找对应的组件,相当于是调用了如下这个方法:ioc.getBean(类名.class); 若找到相应的组件就赋值。
2. 如果找到多个相同类型的组件,就会将属性名称作为组件的id,再到IOC容器中进行查找,这时就相当于是调用了如下这个方法:ioc.getBean(“组件的id”);
3. 找不到就报错 ,倘若设置了 @Autowired注解的required =false 属性,则找不到就不进行注入,不会报错

Qualifier注解

使用Qualifier注解跟Autowired注解搭配使用,当容器中存在多个同类型组件时,可以使用指定的组件id去容器中进行查找并注入。

@Primary注解

当容器中存在多个同类型的组件时,使用Autowired注解装配的匹配规则是首先是以类型匹配,找不到再以属性名作为组件id进行匹配,若果想要以指定的组件id去匹配还得加上一个Qualifier注解;
除了这几个注解,spring还提供了一个@Primary注解,它的特性是,在多个组件存在容器的情况下,优先去注入被@Primary注解备注的组件。

AOP使用细节