Spring IoC&DI
- [1.IoC & DI介绍](#1.IoC & DI介绍)
- 2.IoC详解
- [3. DI详解](#3. DI详解)
- [4. Spring, Spring Boot 和Spring MVC的关系以及区别](#4. Spring, Spring Boot 和Spring MVC的关系以及区别)
1.IoC & DI介绍
Spring是什么?
前面将的Spring 是一个开源框架,让开发更加简单,支持很多应用场景.........
简单来说,Spring是包含众多工具方法的IoC容器
IoC容器是什么?
容器:就是可以存放东西,生活中容器用来存放东西,Tomcat就是Web容器
IOC(Inversion of Control,控制反转),也就是Spring中"控制反转"的容器
控制反转 :就是控制权发生了反转,以前是谁使用(new 新对象)谁来管理,这里是同一让Spring管理对象,可以解决对象依赖混乱、耦合度高的问题
https://juejin.cn/post/7541032822355083315
案例:制造一辆汽车
原始思路:先设计轮子( Tire),根据轮子设计地盘(Bottom ),根据地盘设计车身(Framework ),最后根据车身设计整个车(Car )

java
public class Tire {
private int size = 17;
public Tire(){
System.out.println("Tire init" + "size:" + size);
}
}
java
public class Bottom {
private Tire tire;
public Bottom(){
this.tire = new Tire();
System.out.println("Bottom init");
}
}
java
public class Framework {
private Bottom bottom;
public Framework(){
this.bottom = new Bottom();
System.out.println("Framework init");
}
}
java
public class Car {
private Framework framework;
public Car(){
this.framework = new Framework();
System.out.println("Car init");
}
public void run(){
System.out.println("car run");
}
}
java
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}

此时汽车依赖于车身设计,车身设计依赖地盘,地盘依赖于轮胎,这样耦合度太高,导致维护性较低,如果这里对其轮胎设计可以传入对应尺寸,此时四个类都需要修改






此时这里只是给轮胎的构造方法添加了一个size参数,导致这里其关联的所有类的构造方法都进行了修改,因为需要这个size参数,这样耦合度非常高 不利于后期维护
这里可以换一种思路,先设计汽车的大概样子,根据汽车样子设计车身,根据车身设计地盘,根据地盘设计轮子,这里将依赖关系进行了反转
上面相当于一个汽车的所有配件都由自己来构造,这里相当于使用代理工厂来解决,告诉其轮胎尺寸,工厂会做好,我们无需关心

java
public class Tire {
private int size;
public Tire(int size){
this.size = size;
System.out.println("Tire init......" + "size:" + size);
}
}
java
public class Bottom {
private Tire tire;
public Bottom(Tire tire){
this.tire = tire;
System.out.println("Bottom init");
}
}
java
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom){
this.bottom = bottom;
System.out.println("Frameword inint......");
}
}
java
public class Car {
private Framework framework;
public Car(Framework framework){
this.framework = framework;
System.out.println("Car init......");
}
public void run(){
System.out.println("car run");
}
}
java
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(17);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
这里不关心如何构造,只需要给他成品就行(对象),此时如果Tire类添加一些参数只需要修改这个类,及其使用即可,这样就起到了解耦的作用

IoC容器就是做上面这些工作
IoC容器优点
资源不是由使用资源的双方进行管理,而是第三方进行同一管理,这样资源就容易管理,降低了使用资源双方依赖程度(耦合度)
1.资源集中管理:IoC容器帮助管理一些资源(对象),需要的时直接从IoC容器中直接取即可
2.创建实例不需要要了解细节,降低资源双方依赖关系,降低耦合度
DI介绍
DI:Dependency Injection(依赖注⼊)
依赖注入 :程序运行过程期间,动态的为应用程序提供运行时候所依赖的资源
程序需要什么资源,容器为其提供这个资源
可以将依赖注入DI当作为IoC控制反转的一种实现,通过依赖注入的方式实现对象之间的解耦


使用之前已将将对象创建(注入)了,直接使用即可

2.IoC详解
IoC控制反转,将对象的控制权交给Spring的IoC容器,由IoC容器创建及管理对象
也叫Bean存储
Bean存储
将对象交给Spring中 IoC容器管理,需要使用@Component 注解
当然也有
类注解 :@Controller、@Service、@Repository、@Component@Configuration
⽅法注解:@Bean
java
@RestController
public class UserController {
public void hello(){
System.out.println("UserController");
}
}
java
//启动类
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserController bean = context.getBean(UserController.class);
System.out.println(bean);
}
}

这里启动类是有返回值的
ApplicationContext就是Spring上下文,对象都是由Spring管理,先获取到Spring上下文

如果把这里
@RestController注解移除,再进行获取对象,会出现在Bean中找不到的错误

java
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
这里可以根据名称获取对象,bean的名称,Spring管理对象也会给管理的对象起一个名字,可以使用对应名字进行查找

Bean命名采用了Java命名的标准,使用小写字母开头,驼峰命名
例如:类名: UserController, Bean的名称为: userController
但是如果多个字符中第一个和第二个字符都是大写,则保持原本的大小写
例如:类名: USerController, Bean的名称为:USerController
这个方法可以找出知道其类名对应的Bean名

java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
System.out.println(Introspector.decapitalize("USerController"));
System.out.println(Introspector.decapitalize("UserController"));
}
}
结果如下

java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
//使用类名称
UserController bean = context.getBean(UserController.class);
System.out.println(bean);
//使用bean名称
UserController userController = (UserController) context.getBean("userController");
System.out.println(userController);
//使用bean名称 + 类名
UserController userController1 = context.getBean("userController",UserController.class);
System.out.println(userController1);
}
}
这里获取的都是同一个对象

上面使用ApplicationContext获取对应对象,但是使用BeanFactory也可以


ApplicationContext 和 BeanFactory的区别
1.关系:ApplicationContext 其实属于BeanFactory的子类,继承了其所有功能但是又新增类一些功能,支持国际化、资源访问等等
2.性能方便:ApplicationContext 是一次性将所有类都加载出来并初始化所有Bean对象,而BeanFactory是需要那个才去加载
java
@Controller
public class UserController {
public void controller(){
System.out.println("Controller......");
}
}
java
@Service
public class UserService {
public void service(){
System.out.println("service......");
}
}
java
@Component
public class UserComponent {
public void userComponnet(){
System.out.println("Componnet......");
}
}
java
@Configuration
public class UserConfig {
public void userConfig(){
System.out.println("Configuration......");
}
}
java
@Repository
public class UserRepository {
public void repository(){
System.out.println("Repository......");
}
}
java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserController userController = context.getBean(UserController.class);
userController.controller();
UserService userService = context.getBean(UserService.class);
userService.service();
UserComponent userComponent = context.getBean(UserComponent.class);
userComponent.userComponnet();
UserConfig userConfig = context.getBean(UserConfig.class);
userConfig.userConfig();
UserRepository userRepository = context.getBean(UserRepository.class);
userRepository.repository();
}
}

为什么要这么多类注解呢?
@Controller:控制层,接收数据,对请求进行处理,并进行响应
@Servie:业务逻辑层,处理具体业务逻辑
@Repository:数据访问层(持久层),负责数据访问操作
@Configuration:配置层,处理项目中的一些配置信息
@Component:一些组件信息
每个类对应主要功能不一样,这样根据注解不同,看到对应注解就知道这个类是干嘛的

这几个注解其实都是@Component的"子类",其作为元注解,上面这四个是其衍生注解
方法注解Bean
类注解是添加到某个类上,但是无法解决
1.使用外部包中的类,没办法添加类注解
2.一个类中有多个对象
这样就需要使用@Bean注解
java
@NoArgsConstructor//无参构造
@AllArgsConstructor//全部参数构造
@Data
public class UserInfo {
private String name;
private int age;
}
java
@Configuration
public class BeanConfig {
//创建一个UserInfo对象
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
}
java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
//获取UserInfo对象
UserInfo bean = context.getBean(UserInfo.class);
System.out.println(bean);
}
}
理想情况下,会加载BeanConfig 类,并创建一个UserInfo对象
但是这里出现了未定义UserInfo对象的异常

是因为其@Configuration创建的UserInfo并没有放到Spring容器中,这里在其创建的方法上加上@Bean注解
java
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
}
这样就可以获取到了

如果这里有多个呢?
java
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
@Bean
public UserInfo userInfo2(){
return new UserInfo("lisi",17);
}
}
这里依旧使用上面进行获取UserInfo对象
java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
//获取UserInfo对象
UserInfo bean = context.getBean(UserInfo.class);
System.out.println(bean);
}
}
这里出现了异常,因为这里UserInfo匹配到了两个,此时这里通过类型名称进行获取多个对象会出现问题,此时这里有userInfo,userInfo2两个对象,其bean名称就是方法名,这里通过类型就不行了,此时需要使用bean名称进行获取
NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.UserInfo' available: expected single matching bean but found 2: userInfo,userInfo2

通过bean名进行获取
java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserInfo bean = (UserInfo) context.getBean("userInfo");
System.out.println(bean);
UserInfo bean2 = (UserInfo) context.getBean("userInfo2");
System.out.println(bean2);
}
}
这样可以正常获取

这里也可以对其bean进行重命名
java
一个名字
@Bean("重命名")
@Bean(name = "重命名")
//多个名字
@Bean({"重命名","重命名2"})
@Bean(name = {"重命名","重命名2"})
java
@Configuration
public class BeanConfig {
@Bean(name = {"u1","user1"})
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
@Bean("u2")
public UserInfo userInfo2(){
return new UserInfo("lisi",17);
}
}
java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserInfo bean = context.getBean("u1", UserInfo.class);
UserInfo bean2 = context.getBean("user1", UserInfo.class);
UserInfo bean3 = context.getBean("u2", UserInfo.class);
System.out.println(bean);
System.out.println(bean2);
System.out.println(bean3);
}
}

扫描路径

此时将其启动类移动位置,这里他们不在同一个包中,此时启动进行扫描的时候就找不到这里的BeanConfig中的内容

这里是通过@ComponentScan进行扫描,因此这里可以通过这个注解进行配置

java
//配置扫描路径
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserInfo bean = context.getBean("u1", UserInfo.class);
UserInfo bean2 = context.getBean("user1", UserInfo.class);
UserInfo bean3 = context.getBean("u2", UserInfo.class);
System.out.println(bean);
System.out.println(bean2);
System.out.println(bean3);
}
}
这里就可以扫描到了

通常不使用这个配置扫描路径,通常是将其放到同一个包中 ,这样所有的bean都可以扫描到

3. DI详解
依赖注入是一个过程,IoC容器创建Bean时,会提供所有的依赖的资源(对象),这里通过使用这个@Autowired注解完成相对应操作
java
1. 属性注入(Field Injection)
2. 构造方法注⼊(Constructor Injection)
3. Setter 注⼊(Setter Injection)


此时这里没有从Bean中获取对象,此时可以使用@Autowired属性注入
属性注入
java
@Configuration
public class BeanConfig {
@Bean(name = {"u1","user1"})
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
}
java
@Service
public class UserService {
//获取到Bean中对象
@Autowired
private UserInfo userInfo;
public void service(){
System.out.println(userInfo);
System.out.println("service......");
}
}
java
@Controller
public class UserController {
// 获取到Bean中对象
@Autowired
private UserService userService;
public void controller(){
userService.service();
System.out.println("Controller......");
}
}
java
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
UserController bean = context.getBean(UserController.class);
bean.controller();
}
}
构造方法注入
有对应参数的构造方法加上@Autowired,其会自动调用
java
@Controller
public class UserController {
// @Autowired
// private UserService userService;
private UserService userService;
public UserController(){
System.out.println("执行UserController 无参构造");
}
@Autowired
public UserController(UserService userService){
System.out.println("执行UserController 有参构造");
this.userService = userService;
}
public void controller(){
userService.service();
System.out.println("Controller......");
}
}
多个方法需要在使用的方法上加上 @Autowired

如果只有一个构造方法可以不加上这个注解

如果有多个,并且都没有加@Autowired注解,其会默认执行无参构造

因此这里userService没有正确初始化,执行了无参构造没有初始化


如果都加上@Autowired注解,会直接编译出错


Setter注入
java
@Controller
public class UserController {
//属性注入
// @Autowired
// private UserService userService;
// private UserService userService;
//
//
//类注入
// public UserController(){
// System.out.println("执行UserController 无参构造");
// }
// @Autowired
// public UserController(UserService userService){
// System.out.println("执行UserController 有参构造");
// this.userService = userService;
// }
//setter注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void controller(){
userService.service();
System.out.println("Controller......");
}
}


这里不加 @Autowired会出错,没有初始化

三种方式对比
1.属性注入
优点 :简洁,使用方便
缺点 :只适用于IoC容器,非IoC容器不可用,并且只有当使用这个对象才会出现空指针错误
不能注入一个final修饰的变量
2.构造方法注入
优点 :可以注入final修改的、注入对象不会被修改
依赖对象使用前一定会被完全初始化,因为依赖是构造方法中执行,构造方法在类加载阶段执行
通用性好,各种框架都适用,构造方法是JDK提供的
缺点 :可能参数过多,构造方法代码较繁琐
3.Setter方法注入
优点:类实例过之后,可以重新进行对象配置/注入
缺点:不能注入一个Final修改的属性
注入对象可以使用其改变,因此修改就会经常调用可能会出现风险

这里如果添加构造方法,还不如使用构造方法进行初始化
如果直接初始化那@Autowired注解没啥意思了
存在问题
这里如果有多个UserInfo对象呢
java
@Configuration
public class BeanConfig {
@Bean(name = {"u1","user1"})
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
// @Bean("u2")
public UserInfo userInfo2(){
return new UserInfo("lisi",17);
}
}

非唯一的Bean对象 ,这里直接编译出错

可以使用下面三个注解进行解决
java
@Primary
@Qualifier
@Resource
这里建议使用@Primary注解 / @Qualifier进行解决
@Primary注解:当有相同类型的Bean注入时候,加上这个注解,是默认的
java
@Configuration
public class BeanConfig {
@Primary//默认这个Bean
@Bean(name = {"u1","user1"})
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
@Bean
public UserInfo userInfo2(){
return new UserInfo("lisi",17);
}
}

@Qualifier注解:注入之前,@Qualifier的value属性中,指定注⼊的bean
的名称
注入还是需要@Autowired注解属性注入
java
@Configuration
public class BeanConfig {
@Bean(name = {"u1","user1"})
public UserInfo userInfo(){
return new UserInfo("zhangsan",18);
}
@Bean
public UserInfo userInfo2(){
return new UserInfo("lisi",17);
}
}
java
@Service
public class UserService {
@Qualifier("u1")
@Autowired
private UserInfo userInfo;
public void service(){
System.out.println(userInfo);
System.out.println("service......");
}
}
java
@Controller
public class UserController {
//属性注入
@Autowired
private UserService userService;
public void controller(){
userService.service();
System.out.println("Controller......");
}
}

@Resource注解:通过其bean名注入,其name属性指定注入名称
java
@Service
public class UserService {
//@Qualifier("u1")
@Resource(name = "userInfo2")
@Autowired
private UserInfo userInfo;
public void service(){
System.out.println(userInfo);
System.out.println("service......");
}
}

@Autowird 与 @Resource的区别
1.来源不同: @Autowird是Spring框架提供的注解,而@Resource是JDK提供的注解
2.查找方式不同 @Autowird先通过类型进行注入再根据名称,@Resource先通过名称再通过类型进行注入,有name属性获取到对应的Bean
3.支持参数不同:@Autowird只支持一个而@Resource支持7个
4.支持的用法不同:@Autowird迟迟属性注入、构造方法注入和Setter注入而@Resource不支持使用构造方法
5.编译器IDEA提示不同:当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,而使用 @Resource 注解则不会提示错误



参考:https://developer.aliyun.com/article/1003903#comment
@Autowired 查找顺序
先查找是否有这个类型,看是否存在,存在的看是否有Qualifile有的化,根据参数进行查找bean,没找到 / 有两个但是没有使用Qualifile指定名称 / 其参数对应的bean名找不到会抛异常

4. Spring, Spring Boot 和Spring MVC的关系以及区别
Spring:Spring是一个应用开发框架,轻量级、一站式、模块化目的为了简化企业级应用开发
主要功能:管理对象,以及对象之间的依赖关系, ⾯向切⾯编程, 数据库事务管理, 数据访问, web框架⽀持等
并且Spring具有高度开放性,开发者可以使用其Spring部分东西,并且可以衔接/继承第三方框架
Spring MVC:是Spring中的一个子框架,按照MVC模式设计了一个MVC框架,是一个Web框架,主要用于Web开发和网络接口
Spring MVC是基于Spring开发,所以Spring框架集成,可以更简单、简洁的进行Web开发
Spring Boot:是对Spring封装,简化Spring应用开发诞生的,可以使用其快速搭建框架,降低开放成本,减少对底层的XML配置和底层实现,更加注重开发
Spring Boot是一个脚手架,快速开发
只是辅助简化开发,让开发更简单
例如:使用Spring Boot 开放Web项目,只需要引入Spring MVC框架即可,Web工作由Spring MVC来完成,想要数据访问,只需要引入Mybatis框架等等,需要什么可以进行引入
总结:
SpringBoot和SpringMVC都属于Spring,Spring MVC是一个MVC框架,而Spring Boot更像是一个基于Spring快速开放整合

web url映射: @RequestMapping
参数接收和接 应: @RequestParam, @RequestBody, @ResponseBody
bean的存储: @Controller, @Service,@Repository, @Component, @Configuration,
@Bean
bean的获取: @Autowired, @Qualifier, @Resource