1.IoC&DI简介
1.1 Spring是什么
- Spring 是一个包含了众多⼯具⽅法的 IoC 容器
- Spring两大核心思想:IOC和AOP(AOP后续博文)
- 容器是⽤来容纳某种物品的(基本)装置
比如说:
list/map 装数据的容器
tomcat 装web的容器
Spring容器,装的是对象,对象这个词,在Spring的范围内,称为bean
1.2 IoC
- IoC思想:把对象交给Spring管理
在类上⾯添加 @RestController 和 @Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类,这就是IoC思想
- IoC: 控制反转, 也就是说 Spring 是⼀个"控制反转"的容器
什么是控制反转呢? 也就是控制权反转.
什么的控制权发⽣了反转? 获得依赖对象的过程被反转了
也就是说, 当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊就可以了.
这个容器称为:IoC容器. Spring是⼀个IoC容器, 所以有时Spring 也称为Spring 容器.
⽐如招聘, 企业的员⼯招聘,⼊职, 解雇等控制权, 由⽼板转交给给HR(⼈⼒资源)来处理
1.2.1 案例
造一辆车:
1.2.1.1 传统
public class Main {
public static void main(String[] args) {
Car car = new Car(19);
car.run();
}
}
public class Car {
private Framework framework;
public Car(int size) {
framework = new Framework(size);
System.out.println("car init...");
}
public void run(){
System.out.println("car run...");
}
}
public class Framework {
private Bottom bottom;
public Framework(int size) {
bottom = new Bottom(size);
System.out.println("framework init...");
}
}
public class Bottom {
private Tire tire;
public Bottom(int size) {
tire = new Tire(size);
System.out.println("bottom init...");
}
}
public class Tire {
private int size;
public Tire(int size) {
this.size = size;
System.out.println("tire init...size " + size);
}
}
这样的设计看起来没问题,但是可维护性却很低.
当最底层代码改动之后,整个调⽤链上的所有代码都需要修改.
程序的耦合度⾮常⾼.
1.2.1.2 改进
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(17,"red");
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
public class Car {
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
System.out.println("car init");
}
public void run(){
System.out.println("run init...");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("framework init...");
}
}
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("bottom init...");
}
}
public class Tire {
private int size;
private String color;
public Tire(int size,String color) {
this.size = size;
this.color = color;
System.out.println("tire init...size "+ size +",color "+color);
}
}
此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修改任何代码,这样就完成了程序的解耦
1.3 IoC &DI使⽤
既然Spring是⼀个IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能:
•存
•取
@Autowired告诉Spring,从容器中取出这个对象,赋值给当前对象的属性
那么如何存呢?
@Component让Spring帮我们存
2. IOC详解
2.1 Bean的存储
类注解:@Controller、@Service、@Repository、@Component、@Configuration
⽅法注解:@Bean
2.1.1 @Controller(控制器存储)
@Controller
public class UserController {
public void doController(){
System.out.println("do Controller...");
}
}
public static void main(String[] args) {
//Spring上下文
//返回的就是Spring的运行环境
ApplicationContext context = SpringApplication.run(Application.class, args);
UserController bean = context.getBean(UserController.class);
bean.doController();
}
2.1.2 @Service(服务存储)
@Service
public class UserService {
public void doService(){
System.out.println("do Service...");
}
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
//根据名称和类型
UserService bean3 = context.getBean("userService",UserService.class);
//根据名称,需要进行类型转换
UserService bean2 = (UserService) context.getBean("userService");
bean2.doService();
//根据类名
UserService bean1 = context.getBean(UserService.class);
bean1.doService();
}
2.1.3 @Repository(仓库存储)
@Repository
public class UserRepository {
public void doRepository(){
System.out.println("do doRepository...");
}
}
2.1.4 @Component(组件存储)
@Component
public class UserComponent {
public void doComponent(){
System.out.println("do Component...");
}
}
2.1.5 @Configuration(配置存储)
@Configuration
public class UserConfiguration {
public void doConfiguration(){
System.out.println("do Configuration...");
}
}
2.1.6 修改BeanName
2.1.7 ApplicationContext 与 BeanFactory(常⻅⾯试题)
- 继承关系和功能⽅⾯来说:
Spring容器有两个顶级的接⼝:BeanFactory和 ApplicationContext
其中BeanFactory提供了基础的访问容器的能⼒,⽽ ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了BeanFactory的所有功能之外, 它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.
- 从性能⽅⾯来说:
ApplicationContext是⼀次性加载并初始化所有的Bean对象,⽽BeanFactory 是需要那个才去加载那个(懒加载),因此更加轻量.(空间换时间)
2.1.8 为什么要有这么多类注解
这个也是和应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途
这和每个省/市都有⾃⼰的⻋牌号是⼀样的. ⻋牌号都是唯⼀的,标识⼀个⻋辆的.但是为什么还需要设置不同的⻋牌开头呢. 这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地
2.1.9 注解间关系
这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"⼦类"
既然这样,那可以使用@Component 代替其它其它注解嘛?
下面来测试以下@Controller与其它注解:
使用@Controller:
使用@Service或@Component:
这表明了程序的入口,只能使用@Controller,不能使用其它.
虽然@Component可以代替其它注解,但是并不推荐代替.
注意:
这五个注解只能加在类上,并且只能加在自己的代码上
2.2. 方法注解@Bean
2.2.1 问题引入
下面先看一段代码:
由此可以看出,使用五大注解不管从容器中取多少次对象,取的都是同一个
①那如果我们需要一个场景:对于一个类,定义多个对象时,比如数据库操作,定义多个数据源,该怎么办?
②并且使⽤外部包⾥的类, 没办法添加类注解
上述两种情况通过@Bean解决
2.2.2 ⽅法注解要配合类注解使⽤
下面假设为第三方包
然后来获取
表明@Bean必须搭配五大注解使用
2.2.3 定义多个对象
此时加上注解@Configuration再来运行:
先通过类名获取:
此时都是这个类型,不知道获取哪个
那就使用名称或者类名+名称获取:
获取成功
2.2.4 传递参数
此时报错
修改代码定义一个叫name的String类型对象
再看下面代码:
这说明:
如果需要的@Bean的类型,对应的对象只有一个,直接赋值
如果有多个,通过名称去匹配
2.2.5 重命名Bean
也可以
一个Bean可以有多个名称,但是一个名称只能对应一个Bean
2.3 扫描路径
SpringBoot特点:约定大于配置,扫描路径就是其中一个体现
默认扫描路径为:启动类所在的目录及其子孙目录
@ComponentScan可以制定扫描路径,l例如:
如果没有指定,默认就是该注解所在类的目录及其子孙目录
3. DI详解
汽车模型代码中, 是通过构造函数的⽅式, 把依赖对象注⼊到需要使⽤的对象中的
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊
3.1 属性注入
注意:
①属性注入以类型进行匹配,与注入的属性名称无关
②如果一个类型存在多个对象时,优先匹配名称,匹配不上就报错
此时注入两个:
结果为:
既然说与注入的属性名称无关,那就修改:
此时找不到了
③无法注入一个Final修饰的属性
1.定义时就进行赋值
2.构造方法中进行赋值
3.2 构造方法注入
如果只有一个构造方法,@Autowired可以省略
如果存在多个构造函数时,需要加@Autowired,注明使用哪个构造函数
下面来解释:
①有无参的时候,优先使用无参的构造函数,没有进行赋值,空指针异常
②没有无参的时候,识别不了用哪个
③所以要告诉Spring用哪个
3.3 Setter注入
此时报错
Setter注入需要加@Autowired
3.4 三种注入方式的优缺点
优点 | 缺点 | |
属性注入 |
|
|
构造函数注入 |
|
|
Setter注入 |
|
|
3.5 @Autowired存在的问题
当程序中同一个类型有多个对象时,使用@AutoWired会报错(一些情况下)
3.5.1 解决1
属性名和需要使用的对象名保持一致
3.5.2 解决2@Primary(不推荐)
使用@Primary注解标识默认的对象
3.5.3 解决3@Qualifier
使用@Qualifier
3.5.4 解决4@Resource
使用@Resource
3.6 @Autowired和@Resource区别
- @Autowired是spring框架提供的注解,⽽@Resource是JDK提供的注解
- @Autowired默认是按照类型注⼊,如果一个类型存在多个对象时,优先匹配名称,匹配不上就报错
- @Resource是按照名称注⼊.相⽐于@Autowired 来说, @Resource ⽀持更多的参数设置,例如name设置,根据名称获取Bean