博客结尾附有此篇博客的全部代码!!!
一、认识IOC
Spring IoC(Inversion of Control,控制反转)是Spring框架的核心概念之一,它是一种设计原则,用于实现对象之间的解耦和依赖关系管理。
什么是控制反转呢?
举个例子:以上面造车为例,汽车Run()起来需要依赖于车身(Framework),而车身依赖于底盘(Bottom),底盘依赖于轮胎(Tire)
代码实现:
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}
public class Car {
private Framework framework;
public Car() {
Framework framework=new Framework();
System.out.println("Car init...");
}
public void run(){
System.out.println("Car is running");
}
}
public class Framework {
private Bottom bottom;
public Framework(){
Bottom b = new Bottom();
System.out.println("Framework init...");
}
}
public class Bottom {
private Tire tire;
public Bottom(){
Tire tire = new Tire();
System.out.println("Bottom init...");
}
}
public class Tire {
private String color;
private Integer size;
public Tire() {
this.color = "彩色";
this.size = 17;
System.out.println("color:"+color+",size:"+size);
}
}
这段代码我们发现,它的耦合性太高了,如果我们想要自己设置轮胎大小和颜色,我们就需要将整个程序的代码改一遍。
public class Bottom {
private Tire tire;
public Bottom(String color,Integer size){
Tire tire = new Tire(color,size);
System.out.println("Bottom init...");
}
}
public class Tire {
private String color;
private Integer size;
public Tire(String color, Integer size) {
System.out.println("color:"+color+",size:"+size);
}
}
解决方案:
这里我们可以将轮胎,底盘和车身外包给其他公司,他们根据客户的需要制造出零件,我们负责组装。
实现代码:我们将创建子类的方式,改为传递注入
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();
}
}
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 is running");
}
}
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 String color;
private Integer size;
public Tire(String color, Integer size ) {
System.out.println("color:"+color+",size:"+size);
}
}
通过实现程序我们发现:类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由当前类来控制。
这样的话, 即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。
Spring IOC的核心思想:将对象的创建和依赖关系的管理从代码中分离出来,交给一个专门的外部容器(如Spring容器)来完成
二、认识DI
DI:Dependency Injection(依赖注⼊)
DI是实现IoC(Inversion of Control,控制反转)的一种具体技术手段。它的核心思想是通过外部机制(如框架或容器)将依赖关系注入到对象中,而不是由对象自己去创建或查找依赖。这样可以降低对象之间的耦合度,提高代码的可维护性和可测试性。
依赖注入的核心思想是:由外部机制(如Spring容器)负责创建依赖对象(如B),并将依赖对象注入到需要它的对象(如A)中
三、详解IOC
Spring 是⼀个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:
• 存
• 取
Spring 容器管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由Spring来负责对象的创建和销毁.我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象。
3.1 Bean的存储
Bean在上面我们也说了,就是Spring管理起来的对象。
实现将对象交给Spring管理,
共有两类注解类型可以:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- ⽅法注解:@Bean.
3.2 @Controller(控制器存储)
@Controller
public class ControllerDemo {
public void controllerMethod() {
System.out.println("controllerMethod");
}
}
3.3 获取Bean的方法
从Spring容器中获取Bean对象:
- 先获取Spring上下⽂对象
- 从Spring上下⽂中获取对象
Spring提供的方法:
传Bean对象名注意:
- 如果Bean对象名是大驼峰,则改成小驼峰 (ControllerMethod–>controllerMethod)
- 如果Bean 对象前两个单词是大写的,则Bean对象名不变(COntrollerMethod–>COntrollerMethod)
通过类名获得
public static void main(String[] args) {
//获取Spring上下文对象
ConfigurableApplicationContext context =
SpringApplication.run(SpringIocApplication.class, args);
//从Spring上下文获取Bean对象
ControllerDemo bean = context.getBean(ControllerDemo.class);
bean.controllerMethod();
}
通过Bean对象名称
ControllerDemo bean1 = (ControllerDemo)context.getBean("controllerDemo");
bean1.controllerMethod();
返回的Bean 对象类型是Object,这里需要自己强转一下。
演示一下类名前两个字母都为大写:
HElloController bean3 = (HElloController)context.getBean("HElloController");
bean3.controllerMethod2();
当将类名第二个字母改为小写会报错误:
通过Bean对象名称和类名
ControllerDemo bean2 = context.getBean("controllerDemo", ControllerDemo.class);
bean2.controllerMethod();
3.4 @Service(服务存储)
@Service
public class ServiceDemo {
public void ServiceMethod() {
System.out.println("Service Method");
}
}
ServiceDemo bean =(ServiceDemo) context.getBean("serviceDemo");
bean.ServiceMethod();
3.5 @Repository(仓库存储)
@Repository
public class RepositoryDemo {
public void RepositoryMethod() {
System.out.println("RepositoryMethod");
}
}
RepositoryDemo bean2 =(RepositoryDemo) context.getBean("repositoryDemo");
bean2.RepositoryMethod();
3.6 @Component(组件存储)
@Component
public class ComponentDemo {
public void ComponentMethod() {
System.out.println("ComponentMethod");
}
}
ComponentDemo bean1 = context.getBean(ComponentDemo.class);
bean1.ComponentMethod();
3.7 @Configuration(配置存储)
@Configuration
public class ConfigurationDemo {
public void ConfigurationMethod(){
System.out.println("ConfigurationMethod");
}
}
ConfigurationDemo bean3 = context.getBean(ConfigurationDemo.class);
bean3.ConfigurationMethod();
3.8 五大类注释的对比
- @Controller代表控制层。接收参数返回响应,控制层一定要使用@Controller
- @Service代表服务层。专门用于标记服务层组件的注解。通常用于业务逻辑层。
- @Repository代表数据层。专门用于标记数据访问层组件的注解。通常用于DAO(数据访问对象)层。
- @Configuration代表配置层。
- @Component代表组件层。通用的注解,用于将类标记为Spring管理的组件。
这五大注解都是Spring的组件(组件(Component)是一个泛指,它通常用于描述任何由Spring IoC容器管理的对象)
3.9 ⽅法注解 @Bean
类注解是添加到某个类上的, 但是存在两个问题:
- 使⽤外部包⾥的类, 没办法添加类注解
package com.thirdparty;
//这个是第三方库,你不能进行修改
public class ThirdPartyService {
public void performService() {
System.out.println("Service performed by ThirdPartyService");
}
}
//用过Bean注解将第三方库中ThirdPartyService实例交给Spring管理
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.thirdparty.ThirdPartyService;
@Configuration
public class ThirdPartyConfig {
@Bean
public ThirdPartyService thirdPartyService() {
return new ThirdPartyService();
}
}
- ⼀个类, 需要多个对象, ⽐如多个数据源
当一个应用需要连接多个数据源时,可能需要同一个接口的多个实现。如果使用类注解,Spring容器会为每个标记了相同注解的类创建一个Bean,但无法区分这些Bean。
@Configuration
public class BeanDemo {
@Bean
public User user1(){
User user=new User();
user.setName("张三");
user.setAge(18);
return user;
}
}
User bean = context.getBean(User.class);
System.out.println(bean);
@Bean注解需要搭配五大类注解使用,否则会出现错误!
@Configuration
public class BeanDemo {
@Bean
public User user1(){
User user=new User();
user.setName("张三");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user=new User();
user.setName("lisi");
user.setAge(20);
return user;
}
}
@Configuration
public class BeanDemo {
@Bean(name = "user1")
public User user1(){
User user=new User();
user.setName("张三");
user.setAge(18);
return user;
}
@Bean(value = "user2")
public User user2(){
User user=new User();
user.setName("lisi");
user.setAge(20);
return user;
}
}
四、扫描路径
使用@ComponentScan注解:
当我们将启动类放到demo2包下,发现刚才执行的代码报错:
这里我们通过@ComponentScan指定Spring扫描com.abcdefg.springioc这个路径下的包。
五、DI详解
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象。
简单来说:就是将实例的对象取出,放入到某个类的属性中。
创建依赖注入的三种方法:
- 属性注⼊(Field Injection)
- 构造⽅法注⼊(Constructor Injection)
- Setter 注⼊(Setter Injection)
@Autowired注解是实现依赖注入 的一种常用方式。
5.1 属性注入
@Service
public class Demo1 {
public void demo1() {
System.out.println("demo1");
}
}
@Controller
public class Demo2 {
//属性注入
@Autowired
private Demo1 demo1;
public void demo2() {
demo1.demo1();
System.out.println("demo2");
}
}
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ConfigurableApplicationContext context =
SpringApplication.run(SpringIocApplication.class, args);
Demo2 bean = context.getBean(Demo2.class);
bean.demo2();
}
}
5.2 构造方法注入
@Controller
public class Demo1 {
private String msg;
private String msg2;
public Demo1() {
System.out.println("无参的demo1");
}
public Demo1(String msg, String msg2) {
this.msg = msg;
this.msg2 = msg2;
System.out.println("两个参数的demo1");
}
public Demo1(String msg) {
this.msg = msg;
System.out.println("一个参数的demo1");
}
public void demo1() {
System.out.println("demo1");
}
}
@Controller
public class Demo2 {
private Demo1 demo1;
@Autowired
public Demo2(Demo1 demo1) {
this.demo1 = demo1;//无参的demo1这段话会在这里打印出来
}
public void demo2() {
System.out.println("Demo2");
demo1.demo1();
}
}
运行结果会发现,虽然我们给了三个构造函数,但是@Autowired还是会调用无参的构造函数。
当我们注掉无参的构造函数,这段代码就会报错!
如果我们想使用有参的构造函数,则需要给我们想使用的构造函数前面加上@Autowired,他就会调用我们想使用的构造函数。
这里的话,上述代码就不能作为演示了,因为msg和msg2是我们定义的基本数据类型,@Autowired不支持基本数据类型注入。
所以这里我用已经注入到Spring容器中的类来演示。
//构造方法注入
private ControllerDemo controller;
private ComponentDemo component;
public Demo1() {}
@Autowired
public Demo1(ControllerDemo controller, ComponentDemo component) {
this.controller = controller;
this.component = component;
}
public Demo1(ControllerDemo controller) {
this.controller = controller;
}
public void demo1(){
System.out.println("demo1");
controller.controllerMethod();
component.ComponentMethod();
}
5.3 setter方法注入
private ControllerDemo controller;
private ComponentDemo component;
@Autowired
public void setController(ControllerDemo controller) {
this.controller = controller;
}
@Autowired
public void setComponent(ComponentDemo component) {
this.component = component;
}
public void demo1(){
System.out.println("demo1");
controller.controllerMethod();
component.ComponentMethod();
}
5.4 @Autowired存在问题
- 属性注⼊
优点:
- 简洁,使⽤⽅便;
缺点:
- 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现NPE(空指针异常)
- 不能注⼊⼀个Final修饰的属性
- 构造函数注⼊(Spring 4.X推荐)
优点:
- 可以注⼊final修饰的属性
- 注⼊的对象不会被修改
- 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
- 通⽤性好,构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
缺点:
- 注⼊多个对象时, 代码会⽐较繁琐
- Setter注⼊(Spring 3.X推荐)
优点:
- ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
缺点:
- 不能注⼊⼀个Final修饰的属性
- 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤,就有被修改的⻛险
解决方法:
- @Primary
- @Qualifier
- @Resource
当我们注入对象User对象:
@Configuration
public class BeanDemo {
@Bean(name = {"user1","hahh"})
public User user1(){
User user=new User();
user.setName("张三");
user.setAge(18);
return user;
}
@Bean(value = "user2")
public User user2(){
User user=new User();
user.setName("lisi");
user.setAge(20);
return user;
}
}
@Service
public class Demo2 {
@Autowired
private User user;
public void demo2(){
System.out.println("demo2");
user.toString();
}
}
修改方法:
- 加入@Primary注解(package org.springframework.context.annotation)
- 使用@Qualifier注解(package org.springframework.beans.factory.annotation)
- 使用@Resource(package jakarta.annotation)
@Autowird 与 @Resource的区别
- @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
- @Autowired 默认是按照类型注入,而@Resource是按照名称注入.相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如name设置,根据名称获取 Bean.