当前位置: 首页 > news >正文

SpringIoC和DI

文章目录

    • OCP开闭原则
    • DIP(依赖倒置原则)
    • IOC(控制反转)
    • 依赖注入DI
      • 基于XML配置Bean
        • set注入
        • 构造注入
      • 使用注解存储bean
        • @Controller
        • 方法注解@Bean
        • 扫描路径
        • 依赖注入
        • 三种注入方式优缺点分析

引入


当我们写了一个程序,遵循SpringMVC三层架构,表现层调用业务逻辑层,业务逻辑层调用持久层,持久层返回数据给Dao层,最后返回到表现层,层与层之间接口,降低耦合度,我们通过new对象调用方法

如果我们需求改变就需要改动原来运行没问题的程序,如果是银行业务,涉及到钱的,后面需要对系统扩展,新增业务,需要改动,改完就需要测试,是很麻烦的事情,违背了OCP开闭原则和DIP原则


OCP开闭原则

OCP是软件七大开发原则中最基本的一个原则:开闭原则

开:对扩展开放

闭:对修改关闭

OCP是七大开发中最基本,最核心的,其他的六个原则都是为这个原则服务的

OCP的核心是什么?

只要在扩展系统功能时,没有修改以前写好的代码,那么就是符合OCP原则的

反之,进行系统功能扩展时,修改了之前的程序,之前所有的程序都需要进行改动,违背了OCP原则

DIP(依赖倒置原则)

像上面的图片

UserController依赖了具体的UserServiceImplForMySQL

UserServiceImplForMySQL依赖了具体的UserDaolmplForMySQL

目前来说:上是依赖下的。

凡是上依赖下的,都违背了依赖倒置原则。

举例子:造⼀辆车

传统的设计思路是先根据轮子大小设计底盘,根据底盘设计车身,再根据车身设计整个汽车

上依赖下,不符合依赖倒置原则,一旦需求发生变化,需要改动整个调用链上的所有代码

什么叫做符合依赖倒置原则?

上不再依赖下了,表示符合依赖倒置原则。

依赖倒置的原则的核心是什么?

倡导面向接口编程,面向抽象编程,不要面向具体编程。比如说new对象new了具体的实现类,我们希望的是存在接口类,而不需要具体的实现类,具体的是写死的

目的:降低程序的耦合度,提高扩展力


怎么解决上面的问题呢?

控制反转思想入场

IOC(控制反转)

什么是控制反转?

把对象创建的控制权交出去,把对象和对象之间的维护权交出去

通过这种思想,我们造车创建对象的顺序是以下这种方式

那么谁去管理呢?

Spring框架

Spring框架实现了控制反转IoC思想

Spring是一个实现了IoC思想的容器

依赖注入DI是控制反转思想的具体体现

Spring官方介绍

  1. Spring是一个轻量级的控制反转(loC)和面向切面(AOP)的容器框架。
  2. Spring最初的出现是为了解决EJB臃肿的设计,以及难以测试等问题。
  3. Spring为简化开发而生,让程序员只需关注核心业务的实现,尽可能的不再关注非业务逻辑代码(事务控制,安全日志等)。

Spring是怎么实例化对象的?

默认情况下,Spring会通过反射机制,调用类的无参构造方法来实例化对象

实现原理是

Class clazz = Class. forName(“//类路径”);

Object obj = clazz. newInstance();

Spring把创建好的Bean存储到什么数据结构中?

Map<String,Object>

Spring 会管理很多Bean对象,id里的是唯一表示,bean的id不能重复,class里的是类的路径,value对应的是存储的类的对象

SpringIoC的底层是怎么实现的?

ApplicationContext接口的超级父接口是:BeanFactory(翻译为Bean工厂,就是能够生产Bean对象的一个工厂对象)定义了获取Bean的方法,而具体的实现类如DefaultListableBeanFactory负责实际的创建过程。ApplicationContext在此基础上增加了更多的企业级功能,比如事件传播、资源访问等。

ApplicationContext是一次性加载并初始化所有的Bean对象,而BeanFactory是需要那个才去加载那个,因此更加轻量

BeanFactory是IoC容器的顶级接口。

Spring的IoC容器底层实际上使用了:工厂模式。

什么是工厂模式?

工厂模式是一种创建型设计模式,用于将对象的创建逻辑封装起来,客户端无需直接实例化具体对象,从而提高代码的灵活性和降低了代码的耦合度

Spring底层的IoC是怎么实现的?

总结:XML解析 +工厂模式 +反射机制

依赖注入DI

注入的常见两种方式

  1. set注入:执行set方法给属性赋值
  2. 构造方法注入:执行构造方法给属性赋值

对于依赖注入的理解:对象A和对象B之间的关系,通过注入的手段来维护

基于XML配置Bean

set注入

必须提供一个set方法。

添加依赖

<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.2.3</version>
    </dependency>
    <!--        Junit依赖单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.19.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j2-impl</artifactId>
        <version>2.19.0</version>
    </dependency>
</dependencies>

配置XML文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

定义类

package service;
import dao.UserDao;

public class UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void printUser(){
        userDao.print();
    }
}

Spring容器会调用这个set方法,来给userDao属性赋值。

自己写一个set方法,不使用IDEA工具生成的。不符合javabean规范。

命名规范:使用IDEA生成的setter方法

package impl;

import dao.UserDao;

public class UserDaoImpl implements UserDao {
    @Override
    public void print() {
        System.out.println("打印UserDaoImpl的信息");
    }
}
package dao;

public interface UserDao {
    void print();
}

xml配置Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--定义userDao-->
    <bean id="userDao" class="impl.UserDaoImpl"/>
    <!--定义userService-->
    <bean id="userService" class="service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

bean标签的两个重要属性:

id:是这个bean的身份证号,不能重复,是唯一的标识。

class:必须填写类的全路径,全限定类名。 (带包名的类名)

想让Spring调用对应的set方法,需要配置property标签

property标签 :Setter方法注入,支持简单类型或引用类型

name属性怎么指定?去掉set,把剩下的单词首字母变小写

ref为引用,ref后面指定的是要注入的bean的id

Test类

public class UserDaoTest {
    @Test
    public void test(){
        ApplicationContext context=new ClassPathXmlApplicationContext("userDao.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.printUser();
    }
}

ApplicationContext 为应用上下文—Spring容器。

ApplicationContext 是一个接口。

ApplicationContext 接口下有很多实现类。其中有一个实现类叫做:ClassPathXmlApplicationContext

ClassPathXmLApplicationContext 专门从类路径当中加载spring配置文件的一个Spring上下文对象。

ApplicationContext context=new ClassPathXmlApplicationContext(“userDao.xml”);

这行代码只要执行:就相当于启动了Spring容器,解析userDao. xml文件,并且实例化所有的bean对象,放到spring容器当中。

输出信息

构造注入

提供构造方法

public class UserService {
    private UserDao userDao;
     public UserService() {
    }
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void printUser(){
        userDao.print();
    }
}

xml配置

<bean id="userService" class="service.UserService">
        <constructor-arg index="0" ref="userDao"/>
    </bean>

constructor-arg标签注入依赖 ref指定要注入的bean的id

当构造方法有多个参数,可以使用index下标

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--定义userDao-->
    <bean id="userDao" class="impl.UserDaoImpl"/>
    <!--定义bookDao-->
    <bean id="bookDao" class="impl.BookDaoImpl"/>
    <!--定义userService-->
    <bean id="userService" class="service.UserService">
        <constructor-arg index="0" ref="userDao"/>
        <constructor-arg index="1" ref="bookDao"/>
    </bean>
</beans>

也可以使用name

<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>

要注意构造方法对应的参数名字不要写错

注意:使用构造注入,类中一定要有个无参构造方法,如果没有会报错。必须按照类中定义的顺序进行注入,否则会抛出BeanCreationException类型不匹配异常

注入内部Bean

在property标签中嵌套bean标签——内部Bean

<!--   声明bean-->
    <bean id="orderDaoBean" class="dao.OrderDao"></bean>
    <bean id="orderDaoService" class="service.OrderService">
        <!--内部bean-->
        <property name="orderDao">
            <bean class="dao.OrderDao"></bean>
        </property>
    </bean>

注入简单类型

使用value属性

创建实体类

public class User {
    private int age;
    private String username;
    public void setUsername(String username) {
        this.username = username;
    }


    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", username='" + username + '\'' +
                '}';
    }
}
<bean id="userBean" class="model.user">
    <property name="username" value="zhangsan"/>
    <property name="age" value="15"/>
</bean>

注入集合类型

Spring框架支持注入集合类型的注入

假设我们的实体类包括数组类型、List类型、Map类型、Set类型等

Book类

@Data
@AllArgsConstructor//有参构造
@NoArgsConstructor//无参构造
public class Book {
    private String bookName;
    private String author;
    private Integer price;
}

添加lombok依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.36</version>
</dependency>

Student类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String UserName;
    private Book[] books;
    private List<Book> bookList;
    private Map<String,Book> bookMap;
}

XML配置

<bean id="book1" class="model.Book"></bean>
<bean id="book2" class="model.Book"></bean>
<bean id="book3" class="model.Book"></bean>
<bean id="book4" class="model.Book"></bean>
<bean id="student" class="model.Student">
    <property name="userName" value="zhangsan"></property>
    <!--数组注入-->
    <property name="books">
        <array>
            <ref bean="book1"></ref>
            <ref bean="book2"></ref>
            <ref bean="book3"></ref>
            <ref bean="book4"></ref>
        </array>
    </property>
    <!--List注入-->
    <property name="bookList">
      <list>
          <ref bean="book1"></ref>
          <ref bean="book2"></ref>
      </list>
    </property>
    <!--set注入-->
    <property name="bookSet">
        <set>
            <ref bean="book3"></ref>
            <ref bean="book4"></ref>
        </set>
    </property>
    <!--Map注入-->
    <property name="bookMap">
        <map>
           <entry key="01" value-ref="book1"></entry>
           <entry key="02" value-ref="book2"></entry>
           <entry key="03" value-ref="book3"></entry>
           <entry key="04" value-ref="book4"></entry>
        </map>
    </property>
</bean>

测试类

public class StudentTest {
    @Test
    public void test(){
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("set.xml");
        Student student =(Student) applicationContext.getBean("student");
        System.out.println(student);
    }
}

使用注解存储bean

把对象交给Spring IOC容器管理,可以加上类注解@Controller、@Service、@Repository、@Component、@Configuration.

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

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

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

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

程序应用分层调用流程:

使用@Bean获取对象

@Controller

使用@Controller存储bean

@Controller
public class HController {
    public void print(){
        System.out.println("do HController");
    }
}

通过ApplicationContext上下文管理Bean

@SpringBootApplication
public class IocDemoApplication {
    public static void main(String[] args) 
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
        HController hController=context.getBean(HController.class);
        hController.print();
}

HController类

@Controller
public class HController {
    public void print(){
        System.out.println("do HController");
    }
}

不加@Controller会报找不到bean的cuow

Bean的命名规范:命名约定使用Java标准约定作为实例字段名

bean名称以小写字母开头,然后使用驼峰式大小写.

特殊情况,当有多个字符并且第一个和第二个字符都是大写时,将保留原始的大小写.比如HController

@Service、@Repository、@Component、@Configuration使用方法类似

注意:使用@Service注解时应不能发起请求,报404

未加 @Servie注解

未加 @Component注解

未加 @Configuration注解

未加 @Repository注解

方法注解@Bean

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

1. 使用外部包里的类,没办法添加类注解

2. ⼀个类,需要多个对象,比如多个数据源

方法注解的使用

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

@Bean注解的bean,bean的名称就是它的方法名

定义多个对象

@Component
public class StrudentComponent {
    @Bean
    public Student s1(){
        return new Student("zhangsan",10);
    }

    @Bean()
    public Student s2(){
        return new Student("lisi",10);
    }
}

期望只有⼀个匹配,结果发现了两个

设置name属性给Bean对象进⾏重命名操作

扫描路径

通过注解@ComponentScan 来配置扫描路径.(不推荐使用)

推荐做法: 把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到

依赖注入

使用@Autowired 实现的

属性注入

@Autowired
private UserService userService;//属性注入

构造器注入

@Autowired
public HelloController(UserService userService, UserConfig userConfig) {
        this.userService = userService;
        this.config = userConfig;
    }

setter方法注入

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

使用@Value
注入基本类型、字符串或配置文件中的属性

@Value("${app.timeout:100}")
private int timeout;

@Resource(按名称注入)

@Component
public class StrudentComponent {
 @Bean("s5")
    public Student s2(){
        return new Student("lisi",10);
    }
}
@Service
public class UserService {
    @Resource(name="s5")
    private Student s3;
}

Test类

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
      ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
      Student bean=(Student)context.getBean("s5");
        System.out.println(bean);
      }
}

当存在多个同类型 Bean 时,按名称指定具体 Bean

使用注解@Qualifier

@Component
public class StrudentComponent {
     @Bean
    public Student s1(){
        return new Student("zhangsan",10);
    }
    
    @Bean("s5")
    public Student s2(){
        return new Student("lisi",10);
    }

}
@Service
public class UserService {
    @Qualifier("s5")
    @Autowired
    private Student s3;
}

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

@Component
public class StrudentComponent {

    @Primary
    @Bean
    public Student s1(){
        return new Student("zhangsan",10);
    }

    @Bean
    public Student s2(){
        return new Student("lisi",10);
    }
三种注入方式优缺点分析

在 Spring 依赖注入中,共有三种主要方式:属性注入(字段注入)、构造函数注入和 Setter 注入。每种方式各有其适用场景和优缺点,以下是详细分析。

一、属性注入(字段注入)

优点:
简洁方便
代码量少,直接在字段上添加注解即可完成注入,适合快速开发。

缺点:
强耦合于 IoC 容器
只能在 Spring 等 IoC 容器中使用,脱离容器后字段无法初始化,直接使用会抛出NullPointerException。

无法注入 final 修饰的字段
final 字段必须在构造方法中初始化,而属性注入的时机在对象实例化之后,导致编译错误。

测试困难
需依赖 Spring 容器进行单元测试,或通过反射手动注入依赖。

二、构造函数注入(Spring 4.X 推荐)

优点:
支持 final 字段
依赖在构造方法中初始化,允许字段声明为 final,确保对象不可变。

依赖完全初始化
对象创建时即完成所有依赖注入,避免使用过程中出现未初始化的NullPointerException。

代码健壮性高
依赖关系通过构造方法显式声明,强制调用方提供必要依赖,减少隐含错误。

通用性强
不依赖特定框架,纯 Java 语法实现,更换框架时无需修改注入逻辑。

解决循环依赖
Spring 优先通过构造方法解决循环依赖,若无法解决会直接抛出异常,避免运行时问题。

缺点:
代码冗余
当依赖较多时,构造方法参数列表较长,可通过 Lombok 的 @RequiredArgsConstructor 简化

三、Setter 注入(Spring 3.X 推荐)
通过 @Autowired 标注在 Setter 方法上实现依赖注入。

优点:
灵活性高
允许在对象创建后重新注入或修改依赖,适用于动态配置场景。

缺点:
无法注入 final 字段
与属性注入相同,Setter 方法无法初始化 final 字段。

依赖可能被修改
Setter 方法可被多次调用,导致依赖对象被意外覆盖,破坏对象状态一致性。

依赖初始化时机不确定
依赖可能在对象部分初始化后注入,若在初始化逻辑中提前使用依赖,可能引发NullPointerException。

相关文章:

  • Debian/Ubuntu的networking的`/etc/network/interfaces`配置文件,如何禁用ipv6的route路由网关?
  • nvidia 各 GPU 架构匹配的 CUDA arch 和 CUDA gencode
  • 边缘检测技术现状初探1
  • Java全栈面试宝典:线程机制与Spring IOC容器深度解析
  • 167. 两数之和 II - 输入有序数组 leetcode
  • UML之扩展用例
  • open3d教程 (三)点云的显示
  • 【蓝桥杯每日一题】4.1
  • SQLyog一款被遗忘的MySQL管理利器深度解析
  • 强化终端安全防线防范屏幕共享诈骗
  • SAP SCC安装配置
  • 【蓝桥杯速成】| 18.完全背包(练习室)
  • Excel去掉单元格里面的换行的方法
  • 优艾智合-西安交大具身智能机器人研究院公布人形机器人矩阵
  • Tiktok矩阵运营中使用云手机的好处
  • ChatBI产品形态深度解析:嵌入式BI与大模型的场景化融合实践
  • Mysql之Redo log(Red log of MySQL)
  • 黑白彩色相机成像原理
  • 嘿嘿,好久不见
  • Maven核心配置文件深度解析:pom.xml完全指南
  • java做房屋拍卖网站/网站维护费用一般多少钱
  • 企业为什么要做网站 作用是什么/seo优化平台
  • 桥东企业做网站/信息流广告文案
  • ui设计的网站/seo排名优化表格工具
  • 网站两侧广告代码/北京网站制作400办理多少钱
  • 做网站哪些/网络信息发布平台