学习记录之Spring-框架
Spring框架的作用
-主要解决了创建对象和管理对象的问题。
-在使用Spring框架后,当需要某个对象时,直接通过Spring获取此对象即可。
-Spring可以保证类的对象的唯一性,使得各组件出现依赖关系时,不会创建多个相同的对象。
-由于Spring会创建很多个对象并管理起来,开发者需要时直接获取即可,所以,Spring也通常被称之为“Spring容器”。
通过Spring创建并管理对象--组件扫描
Spring提供了组件扫描的机制,它可以扫描指定的包,并查找此包及其子孙包下是否存在组件类,判定组件类的标准是类上是否存在组件注解,基础的组件注解有:
- `@Component`
- `@Controller`
- `@Service`
- `@Repository`
在Spring框架的解释中,以上4个注解是完全等效的,只是语义不同。所以,当某个类需要被Spring框架创建对象时,需要:
1. 确保这个类在组件扫描的范围之内
2. 确保这个类添加了组件注解
提示:在Spring Boot项目中,默认就已经配置好了组件扫描,扫描的根包就是创建项目时已存在的包。
通过Spring创建并管理对象--@Bean方法
在Spring框架的使用中,如果某个类添加了`@Configuration`注解,则此类将是“配置类”。
注意:配置类也必须在组件扫描的范围内。
在配置类中,可以自行添加一些方法,在方法上添加`@Bean`注解,则此方法会被Spring自动调用,且方法返回的对象也会被Spring管理。
在同一个配置类中,允许存在多个`@Bean`方法。
关于方法的声明:
- 访问权限:应该是`public`
- 返回值类型:你希望Spring管理的对象的类型
- 方法体:应该正确的返回与声明匹配的对象
- 方法名称:自定义
所以,当某个类需要被Spring框架管理对象时,需要:
- 在配置类中添加`@Bean`方法,使用此方法返回需要被管理的对象
如何选择创建并管理对象的方式
仅自定义的类型可以使用组件扫描的方式,当然,自定义的类型也可以使用`@Bean`方法的做法,但是不推荐。
非自定义的类型不可以使用组件扫描的方式(因为你没有办法在这些类型上添加组件注解),只能使用`@Bean`方法的做法。
自动装配
当某个属性需要值时,或被Spring调用的方法需要参数值时,Spring框架会自动从容器中查找符合的对象,自动为属性或方法的参数赋值。在属性上,添加`@Autowired`注解,即可使得Spring尝试自动装配。
假设在项目中存在:
- `IBrandRepository`
- `BrandRepositoryImpl`
- `IBrandService`
- `BrandServiceImpl`
- `BrandController`
其依赖关系是:在`Service`组件中需要使用到`Repository`,在`Controller`组件中需要使用到`Service`。要实现以上目标,需要:
- 以上各类和接口都应该在组件扫描的根包或其子孙包下
- `IBrandRepository`、`IBrandService`不需要添加注解,而`BrandRepositoryImpl`应该添加`@Repository`注解,`BrandServiceImpl`添加`@Service`注解,`BrandController`添加`@Controller`注解
- 在`BrandServiceImpl`中声明`BrandRepositoryImpl`类型的变量(暂时声明为`public`)
- 【测试】在没有添加`@Autowired`之前,此变量的值为`null`
- 【测试】当已经添加`@Autowired`之后,此变量的值为`BrandRepositoryImpl`类型的对象
- 使得`BrandRepositoryImpl`实现`IBrandRepository`接口
- 【测试】将`BrandServiceImpl`中,原本声明为`BrandRepositoryImpl`类型的变量,改为`IBrandRepository`类型,再次观察,此变量的值不变
- 在`BrandController`中声明`BrandServiceImpl`类型的变量
- 【测试】在没有添加`@Autowired`之前,此变量的值为`null`
- 【测试】当已经添加`@Autowired`之后,此变量的值为`BrandServiceImpl`类型的对象使得`BrandServiceImpl`实现`IBrandService`接口
- 【测试】将`BrandController`中,原本声明为`BrandServiceImpl`类型的变量,改为`IBrandService`类型,再次观察,此变量的值不变
组件扫描:
在Spring框架中,通过`@ComponentScan`注解,即可配置组件扫描
在Spring Boot项目中,默认即存在的类(有`main()`方法的那个类)上添加了`@SpringBootApplication`注解,此注解的源代码中包含`@ComponentScan`。
在Spring Boot项目中,无论是执行默认即存在的类的`main()`方法,还是执行带`@SpringBootTest`注解的类中的测试方法,都会加载整个项目的配置,所以,组件扫描是已启动的。
在`@SpringBootApplication`的声明上有`@ComponentScan`,则`@SpringBootApplication`具有`@ComponentScan`的效果。
在`@SpringBootApplication`的声明上有`@ComponentScan`,则`@ComponentScan`可称之为`@SpringBootApplication`的**元注解**。
关于@Autowired注解与@Resource注解的区别
在使用Spring框架时,在类的属性上使用`@Autowired`或`@Resource`都可以实现自动装配!
关于这2个注解,`@Autowired`是Spring框架定义的注解,`@Resource`注解是`javax`包中注解!
关于`@Autowired`的装配机制是:
1. 先根据类型在Spring容器中查找匹配的对象,看看有多少个
2. 如果匹配类型的对象的数量为0,即没有,取决于`@Autowired`的`required`属性
1. 如果`required=true`,则装配失败,在加载Spring时就会报错
2. 如果`required=false`,则放弃自动装配,需要自动装配的属性的值为`null`,加载Spring时并不会报错,但在后续的使用中,可能会出现NPE
3. 如果匹配类型的对象有且仅有1个,则直接装配
4. 如果匹配类型的对象的数量超过1个(2个或更多个),将尝试根据名称来装配
1. 如果存在名称匹配的对象,则成功装配
2. 如果不存在名称匹配的对象,则装配失败,在加载Spring时就会报错
关于`@Resource`的装配机制是先尝试根据名称来装配,如果成功,则装配完成,如果失败,会尝试根据类型来装配,如果仍失败,则装配失败!
从最终的体验来看,这2个注解都可以实现相同的效果,使用`@Autowired`能成功装配的,使用`@Resource`也可以,使用`@Autowired`无法装配的,使用`@Resource`也无法装配。
在使用方面,`@Autowired`和`@Resource`也存在不同:
- `@Resource`仅在属性上可以实现自动装配
- `@Autowired`可以添加在属性上、方法上、构造方法上,用于实现自动装配的效果
事实上,在专业的开发工具中(例如IntelliJ IDEA),是不建议在属性上使用`@Autowired`来实现自动装配的,因为它们认为这是不安全的做法,如果Spring没有被正常加载,则此属性将不会被自动装配,则属性的值为`null`,如果没有使用其它手段解决此问题,在使用过程中就会出现NPE!
- 通常,并不存在Spring没有被正常加载的情况,则以上问题基本上不会出现,即使出现,也是非常小概率事件
- 在某些测试中,确实可能不加载Spring环境,则存在以上关心的NPE风险
通常,这些开发工具会建议使用构造方法来注入属性的值,例如,当在属性上使用`@Autowired`时,IntelliJ IDEA会提示:
以上提示的意思是:字段的注入是不推荐的
当尝试使用构造方法注入时,代码大概是:
以上做法可以实现与传统在属性上添加`@Autowired`完全相同的效果!这种做法是推荐的,因为无论是否加载Spring,要想创建此类的对象,就必须调用构造方法,如果以上构造方法是当前类中唯一的构造方法,就必须传入值,则不会出现NPE问题(除非故意传`null`值)。
以上使用构造方法可以成功为属性赋值,也源自于Spring的自动装配机制,因为自动装配指的是:**当某个属性需要值时,或被Spring调用的方法(通常是配置类中的@Bean方法,或构造方法)需要参数值时,Spring框架会自动从容器中查找符合的对象,自动为属性或方法的参数赋值。**
但是,使用构造方法为属性注入值是比较麻烦的,特别是需要装配的属性发生变化时,构造方法需要一并调整,甚至,在某些类中,需要注入的属性较多,则构造方法需要较多参数,与常规代码的设计也是不符的!
总的来说,使用`@Autowired`语法简洁,需要调整代码时也非常方便,但是,存在NPE风险(概率非常低),而使用构造方法则非常安全,不会出现NPE,但是,语法相对麻烦,且调整代码也很麻烦!由于使用`@Autowired`时出现NPE的概率太低了,所以,在一般的开发实践中,仍是使用`@Autowired`居多!
另外,关于Spring调用构造方法:
如果类中没有显式的定义构造方法,则JAVA编译器会在编译期添加默认构造方法,且Spring会自动调用
如果类中有且仅有1个构造方法,无论此构造方法是有参数的,还是无参数的,Spring都会自动尝试调用
如果类中有多个构造方法,Spring会尝试调用带`@Autowired`注解的构造方法(应该只有1个构造方法添加了此注解),如果所有构造方法都没有`@Autowired`注解,则Spring会尝试调用默认的构造方法(即无参数的构造方法),如果所有构造方法都没有`@Autowired`注解且都有参数,则Spring无法调用
自动装配的名称
当Spring尝试实现自动装配时,会从Spring容器中查找合适的对象,关于“合适”的判定标准,首先,类型必须匹配,如果匹配类别的Bean有多个,必须保证名称匹配才可以正确装配。
关于名称匹配:被自动装配的属性名称,与Bean的名称相同。
关于Bean的名称:如果类名的第1个字母是大写的,且第2个字母是小写的,则Bean的名称是将类名首字母改为小写,例如`BrandRepositoryImpl`的Bean名称就是`brandRepositoryImpl`,否则,Bean名称就是类名。
关于名称,还可以使用注解来指定名称,例如:
或者:
Spring Bean的作用域
Spring管理的对象默认都是**单例**的,可以使用`@Scope("prototype")`使之“**非单例**”。
单例:单一实例,具体表现为:在某一时间点,此类的对象最多只有1个。
需要注意:Spring Bean的特性与通过设计模式中的单例模式创建的对象相同,但是,Spring框架并没有使用单例模式,所以,在描述时,把Spring Bean描述为单例模式是不对的。
当Spring管理的对象是单例状态时,默认是**预加载**的(加载Spring环境时就会创建此类的对象,类似于设计模式中的单例模式的饿汉式),也可以使用`@Lazy`注解将其配置为**懒加载**的(加载Spring环境时并不会直接创建此类的对象,当第1次尝试获取此对象时,才会创建对象,类似于设计模式中的单例模式的懒汉式)。
Spring Bean的生命周期
在自定义的、被Spring管理的类中,可以自定义方法,在方法上添加`@PostConstruct`注解后,此方法将变为Spring Bean生命周期中的**初始化方法**,则会在创建此类的对象之后,被Spring自动调用,还可以自定义方法,在方法上添加`@PreDestroy`注解后,此方法将变为Spring Bean生命周期中的**销毁方法**,则会在销毁对象的前一刻,被Spring自动调用。
Spring IoC与DI
IoC:Inversion of Control,即:控制反转,表现为:交对象的控制权(创建权力、管理权力)交给框架。
DI:Dependency Injection,即:依赖注入,当前类中的代码需要通过另一个类的执行来实现,则当前类依赖于另一个类,例如Controller依赖Service,Service依赖Mapper,Spring可以通过自动装配等机制为依赖项赋值,由于在编写的源代码中并没有使用到赋值符号(`=`),所以,这个行为叫作“注入”。
Spring框架通过DI完善的实现了IoC,所以,DI是一种实现手段,IoC是需要实现的目标。
关于Spring框架的异常
无此Bean的异常,在异常信息中会提示到底是缺少哪个Bean,例如以下提示的`cn.tedu.csmall.server.controller.CategoryController`,然后,再结合你的配置来检查!
创建对象的方式只有2种,要么是组件扫描,要么是`@Bean`方法,检查对应的代码即可。
如果提示的类型并不是自已配置的,且应该是正常的可用的,可考虑为依赖项错误的问题,
不唯一的Bean的异常,当尝试自动装配时,匹配类型的Bean超过1个