Spring IoC&DI~

一、Spring IoC&DI

1. Spring 是什么?

通过前面的学习,我们知道了Spring是一个开源框架,他让我们的开发更加简单,他支持广泛的应用场景,有着活跃而庞大的社区,这也是Spring能够长久不衰的原因,但是这个概念相对来说,还是比较抽象

spring中的核心思想是IoC和AOP

我们用一句更具体的话来概括Spring,那就是: Spring 是包含了众多工具方法的 IoC 容器

1. 什么是容器?

生活中的水杯,垃圾桶,冰箱等等这些都是容器

在编程语言中,Map,List等都是容器

2. 什么是 IoC?

IoC 是Spring的核心思想,也是常见的面试题,那什么是IoC呢?

其实IoC我们在前面已经使用了,我们在前面讲到,在类上面添加 @RestController 和

@Controller 注解,就是把这个对象交给Spring管理,Spring 框架启动时就会加载该类,把对象交给Spring管理,就是IoC思想

IoC:Inversion of Control (控制反转),也就是说 Spring 是一个控制反转的容器

什么是控制反转呢?也就是控制权反转,什么的控制权发生了反转?获得依赖对象的过程被反转了,也就是说,当需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入 (Dependency Injection,DI)就可以了,这个容器称为:IoC容器,Spring是一个IoC容器,所以有时Spring 也称为Spring 容器

2. IoC 介绍

接下来我们通过案例来了解一下什么是IoC

需求:造一辆车

1. 传统程序开发

先设计轮子(Tire),然后根据轮子的大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car),这里就出现了一个依赖关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子

java 复制代码
public class Main {

    public static void main(String[] args) {
        Car car = new Car(20);
        car.run();
    }
}


public class Car {
    private Framework framework;
    public Car(int size) {
        this.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){
        this.bottom = new Bottom(size);
        System.out.println("framework init");
     }
}

public class Bottom {
    private Tire tire;
    public Bottom(int size){
        this.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);
    }
}

2. 问题分析

这样的设计看起来没问题,但是可维护性却很低

接下来需求有了变更:随着对的车的需求量越来越大,个性化需求也会越来越多,我们需要加工多种尺寸的轮胎,那么由于依赖性,Tire之前的代码都要就行改动,耦合性高

3. 解决方案

我们尝试换一种思路,我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子,这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身,车身依赖汽车

基于以上思路,我们把调用汽车的程序示例改造一下,把创建子类的方式,改为注入传递的方式

java 复制代码
public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(20, "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("car run");
    }
}

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;

    public Tire(int size, String color) {
        this.size = size;
        System.out.println("tire init,siz="+size);
    }
}

代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何改变的,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计了

4. IoC 优势

在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire

改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car

我们发现了一个规律,通用程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了 Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了,这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转 ,也就是 IoC 的实现思想

IoC容器的优点:

解耦: 您的业务逻辑代码不再需要关心对象的实例化和依赖查找,只专注于业务实现

可维护性高: 依赖关系集中在配置中,修改依赖时不需要修改源代码

3. DI 介绍

DI: Dependency Injection(依赖注入)

容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入

程序运行时需要某个资源,此时容器就为其提供这个资源

从这点来看,依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,依赖注入是从应用程序的角度来描述,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦

上述代码中,是通过构造函数的方式,把依赖对象注入到需要使用的对象中的

IoC 是一种思想,也是目标, 而思想只是一种指导原则,最终还是要有可行的落地方案,而 DI 就属于具体的实现。所以也可以说,DI 是IoC的一种实现

二、 IoC & DI 使用

既然 Spring 是一个 IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能: 存和取

Spring 容器 管理的主要是对象,这些对象,我们称之为Bean 我们把这些对象交由Spring管理,由Spring来负责对象的创建和销毁。我们程序只需要告诉Spring,哪些需要存,以及如何从Spring中取出对象

目标:把BookDao,BookService 交给Spring管理,完成Controller层、Service层、Dao层的解耦

步骤:

  1. Service层及Dao层的实现类,交给Spring管理: 使用注解: @Component
  2. 在Controller层 和Service层 注入运行时依赖的对象: 使用注解 @Autowired
java 复制代码
@Component
public class BookDao {
    public List<BookInfo> mockData(){
        List<BookInfo> bookInfos = new ArrayList<>();
        for (int i=1; i<=15; i++){
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("图书"+ i);
            bookInfo.setBookAuthor("作者"+i);
            bookInfo.setCount(new Random().nextInt(100));
            bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
            bookInfo.setPublish("出版社"+i);
            bookInfo.setStatus(i%5==0?2:1);
            bookInfos.add(bookInfo);
        }
        return bookInfos;
    }
}


@Component
public class BookService {
    @Autowired
    private BookDao bookDao;
    public List<BookInfo> getList(){
//        BookDao bookDao = new BookDao();
        List<BookInfo> bookInfos = bookDao.mockData();
        //处理图书状态
        for (BookInfo bookInfo:bookInfos){
            //此处只考虑1和2两种情况
            if (bookInfo.getStatus()==1){
                bookInfo.setStatusCN("可借阅");
            }else {
                bookInfo.setStatusCN("不可借阅");
            }
        }
        return bookInfos;
    }
}

@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    BookService bookService;
    @RequestMapping("/getList")
    public List<BookInfo> getList() {
        //查询图书信息, 并返回
//        BookService bookService = new BookService();
        List<BookInfo> bookInfos = bookService.getList();
        return bookInfos;
    }
}

三、IoC 详解

通过上面的案例,我们已经知道了Spring IoC 和DI的基本操作,接下来我们来系统的学习Spring IoC和DI的操作

前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。也就是bean的存储

1. Bean的存储

Spring框架为了更好的服务web应用程序,提供了丰富的注解

共有两类注解类型可以实现:

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
  2. 方法注解:@Bean

@Controller(控制器存储)

java 复制代码
public class UserContraller {

    public String sayHello() {
        return "hello";
    }
}

获取bean的三种方式


@Service(服务存储)

java 复制代码
@Service
public class UserService {
    public String doservice() {
        return "do service...";
    }
}


@Repository(仓库存储)

java 复制代码
@Repository
public class UserRepository {
    public String dorepository() {
        return "dorepository";
    }
}

@Component(组件存储)

java 复制代码
@Component
public class UesrComponent {
    public String doComponent() {
        return "doComponent";
    }
}

@Configuration(配置存储)

java 复制代码
@Configuration
public class UesrConfig {
    public String doconfig() {
        return "doconfig";
    }
}

2. 为什么要这么多类注解?

这个也是和咱们前面讲的应用分层是呼应的,让程序员看到类注解之后,就能直接了解当前类的用途

• @Controller:表现层,接收请求,对请求进行处理,并进行响应

• @Servie:业务逻辑层,处理具体的业务逻辑

• @Repository:数据访问层,也称为持久层,负责数据访问操作

• @Configuration:配置层,处理项目中的一些配置信息
类注解之间的关系

其实这些注解里面都有一个注解 @Component ,说明它们本身就是属@Component 的"子类"。@Component 是一个元注解,也就是说可以注解其他类注解,如 @Controller,@Service ,@Repository 等. 这些注解被称为 @Component 的衍生注解

3. 方法注解 @Bean

类注解是添加到某个类上的, 但是存在两个问题:

  1. 使用外部包里的类, 没办法添加类注解
  2. 一个类, 需要多个对象, 比如多个数据源
    这种场景, 我们就需要使用方法注解 @Bean

1. 方法注解要配合类注解使用

java 复制代码
@ComponentScan(basePackages = "com.bit.ioc")
@Configuration
public class BeanConfig {
    @Bean({"u2", "userInfo2"})
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
}
java 复制代码
UserInfo bean2 = context.getBean(UserInfo.class);
		System.out.println(bean2);

2. 定义多个对象

java 复制代码
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan", 16);
    }
    @Bean
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
}
java 复制代码
UserInfo bean2 = context.getBean(UserInfo.class);
		System.out.println(bean2);

报错信息显示:期望只有一个匹配,结果发现了两个,userInfo,userInfo2

从报错信息中,可以看出来,@Bean 注解的bean,bean的名称就是它的方法名

接下来我们根据名称来获取bean对象

java 复制代码
UserInfo bean1 = (UserInfo) context.getBean("userInfo");
		System.out.println(bean1);
		UserInfo bean2 = (UserInfo) context.getBean("userInfo2");
		System.out.println(bean2);

3. 重命名 Bean

可以通过设置 name 属性给 Bean 对象进行重命名操作

java 复制代码
@Bean({"u2", "userInfo2"}) 
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
java 复制代码
UserInfo bean1 = context.getBean("u2", UserInfo.class);
		System.out.println(bean1);
		UserInfo bean2 = context.getBean("userInfo2", UserInfo.class);
		System.out.println(bean2);

4. 扫描路径

使用前面学习的四个注解声明的bean,一定会生效吗?

不一定,bean想要生效,还需要被Spring扫描


加上@ComponentScan(basePackages = "com.flower.ioc")

在配置类上添加 @ComponentScan 注解,该注解默认会扫描启动类所在的包下所有的配置类
把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到

四、 DI 详解

依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象,我们使用 @Autowired 这个注解,完成依赖注入的操作,简单来说,就是把对象取出来放到某个类的属性中

关于依赖注入,Spring也给我们提供了三种方式:

  1. 属性注入
  2. 构造方法注入
  3. Setter 注入

1. 属性注入

属性注入是使用 @Autowired 实现的,将 Service 类注入到 Controller 类中

java 复制代码
//属性注入
    @Autowired
    private UserService userService;
    @Autowired
    private UserRepository userRepository;
    public void sayHello(){
        userService.doService();
        userRepository.doRepository();
        System.out.println("do controller");
    }
java 复制代码
     UserController bean = context.getBean(UserController.class);
		bean.sayHello();

2. 构造方法注入

java 复制代码
    private UserService userService;
    private UserRepository userRepository;
    public UserController() {
        System.out.println("执行UserController 无参构造方法....");
    }
    public UserController(UserService userService) {
        System.out.println("执行UserController 有参构造方法1....");
        this.userService = userService;
    }
    public UserController(UserRepository userRepository) {
        System.out.println("执行UserController 有参构造方法3....");
        this.userRepository = userRepository;
    }

    @Autowired
    public UserController(UserService userService, UserRepository userRepository) {
        System.out.println("执行UserController 有参构造方法2....");
        this.userService = userService;
        this.userRepository = userRepository;
    }

若有一个构造方法,则执行该构造方法

若有多个构造方法,必须要有无参构造,否则报错,通过 @Autowired 明确指定到底使用哪个构造方法

3. Setter 注入

java 复制代码
    private UserService userService;
    private UserRepository userRepository;
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

4. 三种注入优缺点分析

属性注入

优点: 简洁,使用方便;

缺点:

1.只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现空指针异常

2.不能注入一个Final修饰的属性
构造函数注入

优点:

1.可以注入final修饰的属性

2.注入的对象不会被修改

3.依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法

4.通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的

缺点:

注入多个对象时,代码会比较繁琐
Setter注入

优点: 方便在类实例之后,重新对该对象进行配置或者注入

缺点:

1.不能注入一个Final修饰的属性

2.注入对象可能会被改变,因为setter方法可能会被多次调用, 就有被修改的风险

5. @Autowired存在问题

当同一类型存在多个bean时,使用 @Autowired 会存在问题

java 复制代码
@Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan", 16);
    }

    @Bean({"u2", "userInfo2"})
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
java 复制代码
    @Autowired
    private UserInfo u1;
    public void doService() {
        System.out.println(u1);
        System.out.println("doService");
    }

报错的原因是,非唯一的 Bean 对象

如何解决上述问题呢?Spring提供了以下几种解决方案

@Primary

@Qualifier

@Resource

1. @Primary

java 复制代码
    @Bean({"u2", "userInfo2"})
    @Primary
    public UserInfo userInfo2(){
        return new UserInfo("lisi",   17);
    }

2. @Qualifier

java 复制代码
    @Autowired
    @Qualifier("u2")
//    @Resource(name = "u2")
    private UserInfo u1;
    public void doService() {
        System.out.println(u1);
        System.out.println("doService");
    }

3. @Resource

java 复制代码
    @Resource(name = "u2")
    private UserInfo u1;
    public void doService() {
        System.out.println(u1);
        System.out.println("doService");
    }

4. 若将引入的u1改为方法名,结果会如何呢?

java 复制代码
    @Autowired
    private UserInfo userInfo;
    public void doService() {
        System.out.println(userInfo);
        System.out.println("doService");
    }
java 复制代码
@Autowired
    private UserInfo userInfo2;
    public void doService() {
        System.out.println(userInfo2);
        System.out.println("doService");
    }


由此可见,方法名也可以避免冲突
常见面试题:

@Autowird 与 @Resource的区别

@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解

@Autowired 默认是按照类型注入,而@Resource是按照名称注入. 相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean

五、总结

1. Spring, Spring Boot 和Spring MVC的关系以及区别

Spring :简单来说,Spring 是一个开发应用框架,什么样的框架呢,有这么几个标签:轻量级、一站式、模块化,其目的是用于简化企业级应用程序开发
Spring MVC :Spring MVC是Spring的一个子框架,Spring诞生之后,大家觉得很好用,于是按照MVC模式设计了一个 MVC框架(一些用Spring 解耦的组件),主要用于开发WEB应用和网络接口,所以,Spring MVC 是一个Web框架
Spring Boot : Spring Boot是对Spring的一个封装,为了简化Spring应用的开发而出现的,中小型企业,没有成本研究自己的框架,使用Spring Boot 可以更加快速的搭建框架,降级开发成本,让开发人员更加专注于Spring应用的开发,而无需过多关注XML的配置和一些底层的实现

当你创建一个现代的 Java Web 项目(使用 Spring Boot)时,实际发生的流程是:

Spring Boot 启动,扫描项目依赖。

它发现你有 spring-boot-starter-web,于是自动配置好 Spring MVC(配置 DispatcherServlet)

它初始化 Spring Framework 的容器(ApplicationContext),开始管理你的 Bean。

它启动内置的 Tomcat 服务器,监听 8080 端口

当请求进来时,Spring MVC 的 Controller 接收请求,调用由 Spring 管理的 Service 进行业务处理

Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个
MVC 框架,而Spring Boot 是基于Spring的一套快速开发整合包

2. bean 的命名

  1. 五大注解存储的bean
    前两位字母均为大写, bean名称为类名
    其他的为类名首字母小写
    通过 value属性设置 @Controller(value = "user")
  2. @Bean 注解存储的bean
    bean名称为方法名
    通过name属性设置 @Bean(name = {"u2","userInfo2"})

3. 常见面试题

  1. 三种注入方式的优缺点(参考上面)
  2. 常见注解有哪些? 分别是什么作用?
    web url映射: @RequestMapping
    参数接收和接口响应: @RequestParam, @RequestBody, @ResponseBody
    bean的存储: @Controller, @Service, @Repository, @Component, @Configuration,
    @Bean
    bean的获取: @Autowired, @Qualifier, @Resource
  3. @Autowired 和 @Resource 区别
  4. 说下你对Spring,SpringMVC,Springboot的理解(参考上面)

本期内容到此为止,喜欢的话请点个赞,谢谢观看!!!

相关推荐
CRUD酱42 分钟前
RabbitMQ是如何确保消息的可靠性的?
java·python·rabbitmq
独自破碎E1 小时前
矩阵区间更新TLE?试试二维差分
java·线性代数·矩阵
卷到起飞的数分1 小时前
20.Spring Boot原理2
java·spring boot·后端
申阳1 小时前
Day 20:开源个人项目时的一些注意事项
前端·后端·程序员
shepherd1111 小时前
一文带你掌握MyBatis-Plus代码生成器:从入门到精通,实现原理与自定义模板全解析
java·spring boot·后端
sivdead1 小时前
Agent平台消息节点输出设计思路
后端·python·agent
程序员西西1 小时前
作为开发,你真的懂 OOM 吗?实测 3 种场景,搞懂 JVM 崩溃真相
java·后端
小周在成长1 小时前
Java 内部类指南
后端
橘子编程1 小时前
仓颉语言变量与表达式解析
java·linux·服务器·开发语言·数据库·python·mysql