Spring IoC&DI

目录

[1. Spring 是什么?](#1. Spring 是什么?)

[2. 什么是 IoC?](#2. 什么是 IoC?)

[3. IoC 详解](#3. IoC 详解)

[3.1 Bean的存储](#3.1 Bean的存储)

[3.1.1 类注解](#3.1.1 类注解)

[3.1.2 方法注解](#3.1.2 方法注解)

[3.2 重命名Bean](#3.2 重命名Bean)

[4. DI详解](#4. DI详解)

[4.1 依赖注入](#4.1 依赖注入)

[4.1.1 属性注入](#4.1.1 属性注入)

[4.1.2 构造方法注入](#4.1.2 构造方法注入)

[4.1.3 setter方法注入](#4.1.3 setter方法注入)

[4.1.4 三种注入优缺点分析](#4.1.4 三种注入优缺点分析)

[4.2 @Autowired存在的问题](#4.2 @Autowired存在的问题)

[4.2.1 使用@Primary注解](#4.2.1 使用@Primary注解)

[4.2.2 使用@Qualifier注解](#4.2.2 使用@Qualifier注解)

​编辑

[4.2.3 使用@Resource注解](#4.2.3 使用@Resource注解)

[4.3 案例](#4.3 案例)

[5. 扫描路径](#5. 扫描路径)


1. Spring 是什么?

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

用⼀句更具体的话来概括Spring,那就是: Spring 是包含了众多工具方法的 IoC 容器,工具如StringUtils,容器装的是对象(Spring管理的对象称为bean,和javase的bean不一样)。而SpringBoot在Spring上进行了封装(Spring指的是Spring framework)。

2. 什么是 IoC?

在类上面添加@RestController和@Controller等注解,就是把这个对象交给Spring管理,Spring 框架启动时就会加载该类。把对象交给Spring管理,就是IoC思想。

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

Control即为控制权,这里指的是bean的控制权。传统开发模式中是使用方去创建(new)对象,对象的使用权在对象的使用方手里。而在Spring中,对象的控制权交给了Spring,由Spring创建、销毁对象,这就是控制反转。程序中只需要依赖注入(Dependency Injection,DI)就可以了。这个容器称为:IoC容器。Spring是⼀个IoC容器,所以有时Spring 也称为Spring 容器。

控制反转是⼀种思想, 在生活中也是处处体现。

  • 比如自动驾驶, 传统驾驶方式, 车辆的横向和纵向驾驶控制权由驾驶员来控制, 现在交给了驾驶自动化系统来控制, 这也是控制反转思想在生活中的实现.
  • 比如招聘, 企业的员工招聘,⼊职, 解雇等控制权, 由老板转交给HR(人力资源)来处理
    传统方式
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...");
    }
}

public class Framework {
    private Bottom bottom;
    public Framework() {
        this.bottom = new Bottom();
        System.out.println("framework init...");
    }
}

public class Bottom {
    private Tire tire;
    public Bottom()
    {
        this.tire = new Tire();
    }
}


public class Tire {
    private int size = 20;
    public Tire()
    {
        System.out.println("Tire()");
    }
}


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

问题分析

这样的设计看起来没问题,但是可维护性却很低。接下来需求有了变更: 随着对的车的需求量越来越大, 个性化需求也会越来越多,需要加⼯多种尺寸的轮胎。那这个时候就要对上面的程序进行修改了,修改后的代码如下所示:

修改之后, 其他调⽤程序也会报错, 需要继续修改:


解决方案

在上面的程序中, 我们是根据轮⼦的尺寸设计的底盘,轮子的尺寸⼀改,底盘的设计就得修改。同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改, 也就是整个设计几乎都得改我们尝试换⼀种思路, 我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身,车身依赖汽车。

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...");
    }

}

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)
    {
        System.out.println("tire init...");
    }
}

public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(20);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}

IoC容器具备以下优点:

资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。

第⼀,资源集中管理,实现资源的可配置和易管理。

第⼆,降低了使用资源双方的依赖程度,也就是我们说的耦合度。

  1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等),我们需要使用时,只需要从IoC容器中去取就可以了。

  2. 我们在创建实例的时候不需要了解其中的细节, 降低了使用资源双方的依赖程度, 也就是耦合度。Spring 就是⼀种IoC容器,帮助我们来做了这些资源管理。

3. IoC 详解

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

3.1 Bean的存储

Spring框架为了更好的服务web应用程序, 提供了更丰富的注解。共有两类注解类型可以实现:

3.1.1 类注解

@Controller、@Service、@Repository、@Component、@Configuration。

  • @Controller:控制层, 接收请求, 对请求进行处理, 并进行响应。
  • @Service:业务逻辑层, 处理具体的业务逻辑。
  • @Repository:数据访问层,也称为持久层,负责数据访问操作。
  • @Configuration:配置层,处理项目中的⼀些配置信息。
  • @Component:剩下的无法分到上面含义的就用这个注解。其他四个注解是@Component的衍生注解。

上述的注解都帮我们new了一个对象并存入了容器(单例),后续可以通过属性注入@Autowired使用这些对象(Bean)。

这些类注解起到相同的功能,之所以用这么多个,让程序员看到类注解之后,就能直接了解当前类的用途,也是和咱们前面讲的应用分层是呼应的。

如何使用这些对象?

3.1.2 方法注解

@Bean

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

  1. 使用外部包里的类,没办法添加类注解
  2. ⼀个类, 需要多个对象,比如多个数据源(数据库)

这种场景, 就需要使用方法注解 @Bean

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

在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中。

java 复制代码
@Component
public class BeanConfig {
@Bean
    public User user(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
     return user;
    }
}

不配合使用类注解会出现下面问题:

同一个类定义多个对象

java 复制代码
@Component
public class BeanConfig {
    @Bean
    public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
    return user;
    }
    @Bean
    public User user2(){
        User user = new User();
        user.setName("lisi");
        user.setAge(19);
    return user;
    }
}

定义了多个对象的话, 我们根据类型获取对象会报错:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        //从Spring上下⽂中获取对象
        User user = context.getBean(User.class);
        //使⽤对象
        System.out.println(user);
    }
}

报错信息显示: 期望只有⼀个匹配, 结果发现了两个, user1, user2

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

应该根据名称来获取bean对象。

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
    //获取Spring上下⽂对象
    ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
    //根据bean名称, 从Spring上下⽂中获取对象
    User user1 = (User) context.getBean("user1");
    User user2 = (User) context.getBean("user2");
    System.out.println(user1);
    System.out.println(user2);
    }
}

3.2 重命名Bean

通过设置 name 属性给 Bean 对象进⾏重命名操作(只有⼀个名称时, {}也可以省略)

java 复制代码
@Bean(name = {"u1","user1"})
public User user1(){
    User user = new User();
    user.setName("zhangsan");
    user.setAge(18);
    return user;
}

使用u1 就可以获取到 User 对象了

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
    //获取Spring上下⽂对象
    ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
    //从Spring上下⽂中获取对象
    User u1 = (User) context.getBean("u1");
    //使⽤对象
    System.out.println(u1);
    }
}

为什么不要直接用new?这样用有什么好处?

用了@Bean后,该对象由Spring管理, 所有的类里面都可以用到@Bean修饰的这个对象。

4. DI详解

DI: Dependency Injection(依赖注入)

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

IoC:可以理解为存对象

DI:可以理解为取对象

4.1 依赖注入

依赖注入有属性注入、构造方法注入、setter注入。

4.1.1 属性注入

属性注入是使用@Autowired 实现的。

java 复制代码
// 属性注入
@Autowired
private UserService userService;

4.1.2 构造方法注入

构造方法注入是在类的构造方法中实现注入。

想增加一个有参的构造函数,如果构造函数上不加上@Autowired就会报错。

java 复制代码
//构造方法注入
private UserService userService;
private UserInfo userInfo;

public UserController() {
    System.out.println("无参构造方法");
}

@Autowired
public UserController(UserService userService, UserInfo userInfo) {
    System.out.println("有参构造方法");
    this.userInfo = userInfo;
    this.userService = userService;
}

加了@Controller,由Spring管理,有多个构造函数时,它默认调用无参的。当有多个构造函数时不想Spring调用无参的构造函数,想给Spring指定默认的构造函数时,在想默认使用的构造函数上使用@Autowired,则可将其设置为默认的构造函数。

  • 当存在多个构造方法时,需要使用@Autowired指定默认的构造方法

  • 如果只存在一个构造方法,@Autowired可以省略
    Spring管理的对象需要一个参数时,Spring会自己从容器里面找对应的参数并把它作为参数传入。交给Spring管理的对象,

  • 如果有多个参数,这个参数可以自己指定;

  • 如果未指定,Spring会根据名称或者类型(类型得一致,指定了名称就类型和名称都满足,没指定名称就类型一致就行;如果没指定名称有多个相同类型的,则....),从容器中查找对象并注入进来。

第一个像容器中存入 String name = "test";

第二个像容器中存入 UserInfo userInfo2 = new UserInfo(name,19)

4.1.3 setter方法注入

Setter注入和属性的Setter方法实现类似,只不过在设置set方法的时候需要加上 @Autowired 注
解。

java 复制代码
//setter方法注入
private UserService userService;

@Autowired
public void setUserService(UserService userService) {
    this.userService = userService;
}

4.1.4 三种注入优缺点分析

属性注入

优点:

  • 简洁,使用方便;

缺点:

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

  • 不能注入一个Final修饰的属性
    构造函数注入(Spring 4.X推荐)
    优点:

  • 可以注入final修饰的属性

  • 注入的对象不会被修改

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

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

缺点:

  • 注入多个对象时, 代码会比较繁琐
    Setter注入(Spring 3.X推荐)
    优点:

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

缺点:

  • 不能注入⼀个Final修饰的属性。
  • 注入对象可能会被改变, 因为setter方法可能会被多次调用,就有被修改的风险。

4.2 @Autowired存在的问题

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

  • 一个类型只有一个对象时,直接注入这个对象。
  • 一个类型有多个名称对象时,按照名称注入对象。
  • 一个类型有多个名称对象时,但没有名称对应的对象时,会报错,无法启动程序。

这个时候需要使用@Primary指定一个为默认的才行 或者 使用@Qulifier("名称")设定默认 或者 @Resourse指定

Spring提供了以下几种解决方案:

  • @Primary
  • @Qualifier
  • @Resource

4.2.1 使用@Primary注解

当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

java 复制代码
@Component
public class BeanConfig {
    @Primary //指定该bean为默认bean的实现
    @Bean("u1")
    public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("lisi");
        user.setAge(19);
        return user;
    }
}

4.2.2 使用@Qualifier注解

指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。@Qualifier注解不能单独使用,必须配合@Autowired使用。

java 复制代码
@Controller
public class UserController {
    @Qualifier("user2") //指定bean名称
    @Autowired
    private User user;

    public void sayHi(){
        System.out.println("hi,UserController...");
        System.out.println(user);
    }
}

4.2.3 使用@Resource注解

是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

java 复制代码
@Controller
public class UserController {
    @Resource(name = "user2")
    private User user;

    public void sayHi(){
        System.out.println("hi,UserController...");
        System.out.println(user);
    }
}

@Resource与@AutoWired的区别

1.@Autowired是根据类型匹配,@Resource是根据名称匹配

  • 准确说@Autowired优先按照类型匹配,如果同类型有多个对象,按照名称匹配。@Resource其实也是先类型匹配。
  • @Autowired和@Resource基本原则都是根据类型匹配,但是@Autowired不能指定名称。

2.@Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解

4.3 案例

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

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

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

步骤:

  1. Service层及Dao层的实现类,交给Spring管理: 使用注解: @Component
  2. 在Controller层和Service层注入运行时依赖的对象: 使用注解 @Autowired

1.把BookDao 交给Spring管理, 由Spring来管理对象

java 复制代码
@Component
public class BookDao {
/**
* 数据Mock 获取图书信息
*
* @return
*/
public List<BookInfo> mockData() {
    List<BookInfo> books = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        BookInfo book = new BookInfo();
        book.setId(i);
        book.setBookName("书籍" + i);
        book.setAuthor("作者" + i);
        book.setCount(i * 5 + 3);
        book.setPrice(new BigDecimal(new Random().nextInt(100)));
        book.setPublish("出版社" + i);
        book.setStatus(1);
        books.add(book);
    }
    return books;
  }
}

2.把BookService 交给Spring管理, 由Spring来管理对象

java 复制代码
@Component
public class BookService {
    private BookDao bookDao = new BookDao();
    public List<BookInfo> getBookList() {
        List<BookInfo> books = bookDao.mockData();
        for (BookInfo book : books) {
            if (book.getStatus() == 1) {
                book.setStatusCN("可借阅");
            } else {
                book.setStatusCN("不可借阅");
            }
        }
    return books;
    }
}
  1. 删除创建BookDao的代码, 从Spring中获取对象
java 复制代码
@Component
public class BookService {
    @Autowired
    private BookDao bookDao;
    public List<BookInfo> getBookList() {
        List<BookInfo> books = bookDao.mockData();
        for (BookInfo book : books) {
            if (book.getStatus() == 1) {
            book.setStatusCN("可借阅");
        } else {
            book.setStatusCN("不可借阅");
          }
        }
        return books;
    }
}
  1. 删除创建BookService的代码, 从Spring中获取对象
java 复制代码
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    private BookService bookService;
    @RequestMapping("/getList")
    public List<BookInfo> getList(){
        //获取数据
        List<BookInfo> books = bookService.getBookList();
        return books;
    }
}

5. 扫描路径

默认扫描的范围是SpringBoot启动类所在包及其子包。把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到。

修改扫描路径

  1. 通过@ComponentScan("包名")可以修改扫描路径。
  2. 也可以写多个扫描路径。@ComponnentScan({"com.neu.edu.cn","com.edu"})。
相关推荐
郝学胜-神的一滴25 分钟前
Spring Boot Actuator 保姆级教程
java·开发语言·spring boot·后端·程序人生
剪刀石头布啊43 分钟前
数据口径
前端·后端·程序员
剪刀石头布啊1 小时前
http状态码大全
前端·后端·程序员
jiangxia_10241 小时前
面试系列:什么是JAVA并发编程中的JUC并发工具类
java·后端
用户1512905452201 小时前
踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录
前端·后端
A_氼乚1 小时前
JVM运行时数据区相关知识,这篇文档会勘正你的许多理解!(本周会补上更详细的图式)
后端
斜月1 小时前
Springboot 项目加解密的那些事儿
spring boot·后端
草莓爱芒果1 小时前
Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)
java·spring boot·算法
慕y2742 小时前
Java学习第九十三部分——RestTemplate
java·开发语言·学习
旋风菠萝2 小时前
设计模式---单例
android·java·开发语言