Spring IoC

前言:

我们介绍下Spring.
通过前⾯的学习, 我们知道了Spring是⼀个开源框架, 他让我们的开发更加简单. 他⽀持⼴泛的应⽤场景, 有着活跃⽽庞⼤的社区, 这也是Spring能够⻓久不衰的原因.
这么说可能还是很抽象.用一句话概括就是Spring就是一个包含了众多工具和方法的IoC容器.
所谓的容器,现实生活中很多,类似冰箱就是一个容器,里面放了很多食物
在我们之前掌握知识来看:LIst 也是一个容器,里面可以存放很多不同类型的数据.

1.什么是IoC

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

也就是"控制权"反转.就像是我们在JavaSE中,每一个对象都需要自己来new,但是把创建对象的任务交给了容器,我们只是需要的时候将所需对象注入进目标对象中即可.这个容器就称为"IoC容器",我们将所需对象通过注解加到目标对象中就称为"依赖注入".

"控制反转"是一种思想,而"依赖注入"是这种思想的实现形式.但是不是只有这一种实现方式,Spring用的是依赖注入

1.2IoC介绍

传统的程序开发

就类似造车:

先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最 后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦

代码实现:

java 复制代码
//轮胎
public class Tire {
    private int size;
    private String color;
 
    public Tire(int size) {
        System.out.println("tire size:"+size);
    }
}
 
//底盘
public class Bottom {
    private Tire tire;
 
    public Bottom(int size) {
        tire = new Tire(size);
        System.out.println("tire init...");
    }
}
 
//框架
public class Framework {
    private Bottom bottom;
 
    public Framework(int size) {
        bottom =  new Bottom(size);
        System.out.println("bottom init....");
    }
}
 
//汽车
public class Car {
    private Framework framework;
 
    public Car(int size) {
        framework = new Framework(size);
        System.out.println("framework init...");
    }
 
    public void run() {
        System.out.println("car run...");
    }
}
 
//启动类
public class Main {
    public static void main(String[] args)
    {
        Car car = new Car(10);
        car.run();
    }
}

我们通过上述代码就模拟好了造车.

但是如果我们要在上述代码修改一个变量,那么所有的类就会跟着改变

牵一发而动全身

代码的耦合性太高了,根本不利于我们维护代码,可读性也大大降低

解决方案:
我们尝试换⼀种思路, 我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计 底盘,最后根据底盘来设计轮⼦. 这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝, ⻋⾝依赖汽⻋
这就类似我们打造⼀辆完整的汽⻋, 如果所有的配件都是⾃⼰造,那么当客⼾需求发⽣改变的时候, ⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那 么即使是轮胎的尺⼨发⽣变变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃⾝是不需要出⼒的

如何实现:
改用传递对象的方式,即使下级类修改,也没关系

1.2.1IoC实现造车

java 复制代码
//bottom
public class Bottom {
    private Tire tire;
 
    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("tire init...");
    }
}
 
 
 
//Car
public class Car {
    private Framework framework;
 
    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("framework init...");
    }
 
    public void run() {
        System.out.println("car run...");
    }
}
 
 
//Framework
public class Framework {
    private Bottom bottom;
 
    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("bottom init....");
    }
}
 
 
 
//Tire
public class Tire {
    private int size;
    private String color;
 
    public Tire(int size, String color) {
        System.out.println("tire size:"+size+",color:"+color);
    }
}
 
 
 
//Main
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();
    }
}


从上面我们可以看到
传统方式是创建Car再去找需求的依赖,是一个从下到上的结构

而IOC的方式是将对象依赖注入,是改进之后的控制权反转

这里我们可以看出IOC思维的优势

1.减少了类之间的耦合度

2.便于对象的集中管里

2什么是DI

DI: Dependency Injection(依赖注⼊)
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
上面IoC方式造车的例子就是"依赖注入"
某个对象想使用某个资源,我们直接注入给他

3.IoC详解

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

3.1Bean的存储

存储Bean我们需要使用两类注解

类注解:@Controller,@Service,@Component,@Repository,@Configuration

方法注解:@Bean

在学习这些注解之前我们要先学会使用传统方式,来获取Bean.这里我们使用

复制代码
ApplicationContext来获取

ApplicationContext 翻译过来就是: Spring 上下⽂
因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下文.
关于上下⽂的概念
在计算机领域, 上下⽂这个概念, 咱们最早是在学习线程时了解到过, ⽐如我们应⽤进⾏线程切换的时 候,切换前都会把线程的状态信息暂时储存起来,这⾥的上下⽂就包括了当前线程的信息,等下次该 线程⼜得到CPU时间的时候, 从上下⽂中拿到线程上次运⾏的信息
这个上下⽂, 就是指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前运⾏的环境

java 复制代码
@SpringBootApplication
public class SpringIoc3Application {

    public static void main(String[] args) {
        ApplicationContext context=SpringApplication.run(SpringIoc3Application.class, args);
    }

}

获取对象bean的方式:
ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext 获取bean对象的功能, 是⽗ 类BeanFactory提供的功能.

java 复制代码
public interface BeanFactory {
 
 //以上省略...
 
 // 1. 根据bean名称获取bean
 Object getBean(String var1) throws BeansException;
 // 2. 根据bean名称和类型获取bean
 <T> T getBean(String var1, Class<T> var2) throws BeansException;
 // 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean
 Object getBean(String var1, Object... var2) throws BeansException;
 // 4. 根据类型获取bean
 <T> T getBean(Class<T> var1) throws BeansException;
 // 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的bean
 <T> T getBean(Class<T> var1, Object... var2) throws BeansException;
 
 //以下省略...
}

Bean的命名约定:

在Spring官方文档中指出,如果是类注解以名字来访问,就要注意名字必须和对象名保持一致,并且在getBean()方法中名字的第一个首字母要小写,特殊情况下,对象名前两个字母都是大写的情况下直接使用原名,不需要小写.

使用方法注解的时候.直接使用方法名即可.
⽐如
类名: UserController, Bean的名称为: userController
类名: AccountManager, Bean的名称为: accountManager
类名: AccountService, Bean的名称为: accountService
前两个字母都是大写
⽐如
类名: UController, Bean的名称为: UController
类名: AManager, Bean的名称为: AManager

3.1.1@Controller(控制器存储)

接下来我们使用上述传统方式来获取Bean并观察结果

首先我们要将UserController对象用@Controller注解存放到IoC容器中

这样我们就把这个类放到了Spring中.

接下来我们使用getBean()方法来获取UserController对象

java 复制代码
@SpringBootApplication
public class SpringIoc3Application {

    public static void main(String[] args) {
        ApplicationContext context=SpringApplication.run(SpringIoc3Application.class, args);
        UserController bean = context.getBean(UserController.class);//通过类型扫描UserController
        bean.say();
        UserController userController = (UserController) context.getBean("userController");
        userController.say();
        UserController bean1 = context.getBean("userController", UserController.class);
        bean1.say();
    }

}

最后我们看到结果成功输出也就是获取到了UserController对象.

地址一样,说明是一个对象

我们获取对象的功能,是Application的父类BeanFactory的功能.

3.1.2@Service(服务存储)

3.1.3 @Repository(仓库存储)

3.1.4 @Component(组件存储)

3.1.5@Configuration(配置存储)

3.2为什么使用这么多类注解

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

@Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.

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

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

@Configuration:配置层. 处理项⽬中的⼀些配置信息

3.3 ⽅法注解 @Bean

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

  1. 使⽤外部包⾥的类, 没办法添加类注解
  2. ⼀个类, 需要多个对象, ⽐如多个数据源
    这种场景, 我们就需要使⽤⽅法注解 @Bean
    代码案例1:这里我们将类注解先注释掉.
java 复制代码
//@Configuration
public class UserConfig {
    public void say(){
        System.out.println("hi,UserConfig");
    }
    @Bean
    public User user(){
        return new User("张三");
    }
}

这里出现了报错

错误内容是没有名为"userConfig"的bean可用.

原因是我们没有加上类注解,切记@Bean要搭配类注解使用

加上类注解

java 复制代码
@Configuration
public class UserConfig {
    public void say(){
        System.out.println("hi,UserConfig");
    }
    @Bean
    public User user(){
        return new User("张三");
    }
}

代码案例2:定义多个对象,使用类的类型扫描

这里我们定义两个User对象并且都通过@Bean注解添加到容器中

java 复制代码
@Bean
    public User user(){
        return new User("张三");
    }
    @Bean
    public User user1(){
        return new User("李四");
    }

通过类的类型扫描

这里出现了报错,通过类的类型扫描.此时容器中有两个User对象,我们根据类型获取对象,此时Spring不知道你要获取哪个对象,所以报错了.

解决办法:用类的名字扫描

3.4扫描路径

我们把启动类放到其他的目录下面

再次启动程序

为什么会出错呢?
为什么没有找到bean对象呢?
使⽤五⼤注解声明的bean,要想⽣效, 还需要配置扫描路径, 让Spring扫描到这些注解
也就是通过 @ComponentScan 来配置扫描路径.

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

那为什么前⾯没有配置 @ComponentScan注解也可以呢?
@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解
@SpringBootApplication 中了.默认扫描的范围是SpringBoot启动类所在包及其⼦包
在配置类上添加 @ComponentScan 注解, 该注解默认会扫描该类所在的包下所有的配置类

ApplicationContext VS BeanFactory(常⻅⾯试题)

继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和
ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽
ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外, 它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.

从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽
BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)

4.DI(依赖注入)详解

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象.
在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作.
简单来说, 就是把对象取出来放到某个类的属性中.
在⼀些⽂章中, 依赖注⼊也被称之为 "对象注⼊", "属性装配", 具体含义需要结合⽂章的上下⽂来理解.
关于依赖注⼊, Spring也给我们提供了三种⽅式:

  1. 属性注⼊(Field Injection)
  2. 构造⽅法注⼊(Constructor Injection)
  3. Setter 注⼊(Setter Injection

4.1属性注入

4.2构造方法注入

我们可以看到,只有一个构造方法的时候即使不加@Autowired也可以获取数据

但是,我们要是加一个空的构造方法看看效果

我们看下报错信息,也是我们的老朋友了,就是空指针异常.为什么会空指针异常呢?

因为程序启动的时候会首先调用无参数的构造方法,如果没有会调用我们写的,但是两个都有的话就会调用无参数的,此时UserService并没有真正new对象,去调用UserService的say()方法就会出现空指针异常

解决办法:就是在想要注入的构造方法中添加@Autowired注解

4.3Setter注入

java 复制代码
@Controller
public class UserController3 {
 //注⼊⽅法3: Setter⽅法注⼊
 private UserService userService;
 @Autowired
 public void setUserService(UserService userService) {
 this.userService = userService;
 }
 public void sayHi(){
 System.out.println("hi,UserController3...");
 userService.sayHi();
   }
}

4.4三种注入优缺点分析


1.属性注⼊
优点: 简洁,使⽤⽅便;
缺点:
只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指
针异常)
不能注⼊⼀个Final修饰的属性
2.构造函数注⼊(Spring 4.X推荐)
优点:
可以注⼊final修饰的属性
注⼊的对象不会被修改
依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法
是在类加载阶段就会执⾏的⽅法.
通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
缺点:
注⼊多个对象时, 代码会⽐较繁琐
3.Setter注⼊(Spring 3.X推荐)
优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
缺点:
不能注⼊⼀个Final修饰的属性
注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险

4.5@Autowired存在的问题

当我们new两个User对象的时候,我们使用@Autowired注入User对象会出现报错.

如果注入对象的名和创建的对象名字不一样就会报错.

解决办法:

1.使用@Primary

在有两个对象的时候可以在其中一个对象上加上@Primary.作用是默认选择,如果有两个对象并且对象名不同会使用加上@Primary的默认对象

2.使用 @Qualifie

在注解后边括号指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称

3.@Resource

使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称


@Autowird 与 @Resource的区别(面试题)
@Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
@Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说, @Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。

相关推荐
嘤国大力士7 分钟前
C++11&QT复习 (七)
java·c++·qt
松树戈12 分钟前
Java常用异步方式总结
java·开发语言
weisian15112 分钟前
Java常用工具算法-3--加密算法2--非对称加密算法(RSA常用,ECC,DSA)
java·开发语言·算法
小李同学_LHY26 分钟前
三.微服务架构中的精妙设计:服务注册/服务发现-Eureka
java·spring boot·spring·springcloud
非ban必选1 小时前
spring-ai-alibaba第四章阿里dashscope集成百度翻译tool
java·人工智能·spring
非ban必选1 小时前
spring-ai-alibaba第五章阿里dashscope集成mcp远程天气查询tools
java·后端·spring
遥不可及~~斌1 小时前
@ComponentScan注解详解:Spring组件扫描的核心机制
java
高林雨露1 小时前
Java 与 Kotlin 对比示例学习(三)
java·kotlin
极客先躯2 小时前
高级java每日一道面试题-2025年3月22日-微服务篇[Nacos篇]-Nacos的主要功能有哪些?
java·开发语言·微服务
爱喝醋的雷达2 小时前
Spring SpringBoot 细节总结
java·spring boot·spring