spring IOC(实现原理)

文章目录

依赖注入

**依赖注入(DI)**是Spring框架核心功能之一,它允许对象间的依赖关系通过配置或代码在运行时动态注入,而不是通过对象内部硬编码。这种方法减少了代码间的耦合,增加了灵活性和可重用性。

依赖注入强调的是对象的依赖关系由外部来管理和提供,而不是由对象自身负责创建依赖对象。通过这种方式,可以降低对象之间的耦合度。即下面的方式也属于依赖注入。

常见的依赖注入方式

  • 构造函数注入:通过类的构造函数将依赖对象传递给该类。在对象实例化时,容器会调用相应的构造函数,并传入依赖对象的实例。
java 复制代码
public class ProductService {
    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    // 业务方法
}
  • Setter 方法注入:利用类的 setter 方法将依赖对象设置到该类中。容器先创建对象实例,然后调用 setter 方法来注入依赖对象。
java 复制代码
public class UserService {
    private UserRepository userRepository;

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 业务方法
}

xml依赖注入

  • <property>

  • <list><value></value></list>

  • <map><entry key=' ' value=' '></map>

  • <array><value></value></array>

  • <set><value></value></set>

  • <props><prop key=' '>值</prop></props>

    ---------------Student类------------------
    public class Student {
    private String name;
    private Adress address;
    private String[] books;
    private List hobbies;
    private Map<String,String> card;
    private Set games;
    private String wife;
    private Properties info;
    //getter,setter,无参and全参构造
    }
    ---------------Teacher类------------------
    public class Teacher {
    private String name;
    private int age;
    //getter,setter,无参and全参构造
    }
    ---------------applicationContext.xml------------------












    复制代码
      <bean id="student" class="com.luxiya.Student">
          <property name="name" value="luxiya"/>
          <property name="address" ref="adress"/>
          <property name="books">
              <array>
                  <value>java</value>
                  <value>python</value>
              </array>
          </property>
          <property name="hobbies">
              <list>
                  <value>打篮球</value>
                  <value>打游戏</value>
              </list>
          </property>
          <property name="card">
              <map>
                  <entry key="身份证" value="123456789"/>
                  <entry key="银行卡" value="123456789"/>
              </map>
          </property>
          <property name="games">
              <set>
                  <value>LOL</value>
                  <value>DOTA</value>
              </set>
          </property>
          <property name="wife">
              <null/>
          </property>
          <property name="info">
              <props>
                  <prop key="name">luxiya</prop>
                  <prop key="age">18</prop>
              </props>
          </property>
      </bean>

    ---------------测试类------------------
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = (Student) context.getBean("student");
    System.out.println(student.toString());
    Teacher teacher = (Teacher) context.getBean("teacher");
    System.out.println(teacher.toString());
    Teacher teacher1 = (Teacher) context.getBean("teacher1");
    System.out.println(teacher1.toString());
    Teacher teacher2 = (Teacher) context.getBean("teacher2");
    System.out.println(teacher2.toString());

控制反转

​ 在传统的程序设计中,当一个对象(A)需要使用另一个对象(B)的功能时,通常会在对象 A 内部主动创建 对象 B 的实例,即对象 A 对对象 B 的创建和生命周期管理具有控制权。而在控制反转的思想下,对象 A 所依赖的对象 B 的创建、实例化以及依赖关系的建立,不再由对象 A 自身负责,而是由一个外部的容器(比如 Spring 容器)来负责管理。这样一来,对象 A 获得依赖对象 B 的过程被反转了,控制权从对象 A 转移到了外部容器,这就是 "控制反转" 名称的由来。

​ IoC 容器负责创建对象、管理对象的生命周期,并在适当的时候将对象注入到需要它们的其他对象中。应用程序中的各个组件(对象)只需要关注自身的业务逻辑,而不需要关心依赖对象的创建和获取过程。容器会根据配置信息(如 XML 配置文件或注解)来确定对象之间的依赖关系,并自动完成对象的实例化和注入。

  • XML 配置:在 Spring 早期,XML 是配置 Bean 的主要方式。通过编写 XML 文件,**将 Bean 的定义信息(如 Bean 的类名、依赖关系、属性值等)与 Bean 的实现类分离开来。**这样做的好处是可以在不修改实现类代码的情况下,修改 Bean 的配置信息,便于对系统进行维护和扩展。但缺点是 XML 文件可能会变得冗长复杂,增加了配置的难度和维护成本。
  • 注解配置 :随着 Spring 框架的发展,注解配置逐渐流行起来。使用注解(如 @Component@Service@Repository@Autowired 等)可以直接在实现类中定义 Bean 的信息和依赖关系,将 Bean 的定义和实现合二为一。这种方式减少了大量的 XML 配置,使得代码更加简洁,开发效率更高,实现了所谓的 "零配置"(或者说减少了显式配置)。

相关

ApplicationContext 接口,工厂的接口,使用该接口可以获取到具体的 Bean 对象。该接口下有两个具体的实现类。

ClassPathXmlApplicationContext,加载类路径下的 Spring 配置文件。(注:ClassPathXmlApplicationContext在读取application.xml文件时,就会自动创建bean实例)

FileSystemXmlApplicationContext,加载本地磁盘下的 Spring 配置文件。

Spring 框架的 Bean管理的配置文件方式

  1. id 属性

id 属性为每个 Bean 提供了一个唯一的标识符。

  1. class 属性

class 属性指定了 Bean 对象的全限定类名。在这个例子中,com.example.service.UserService 就是 UserService 类的完整路径,Spring 容器会根据这个路径来创建相应的 Bean 实例。

  1. scope 属性
  • singleton(单例) :这是 scope 属性的默认值。当 scope 设置为 singleton 时,Spring 容器只会创建该 Bean 的一个实例,并且在整个应用程序的生命周期中都使用这个实例。
  • prototype(多例) :当 scope 设置为 prototype 时,每次从 Spring 容器中获取该 Bean 时,容器都会创建一个新的实例。
  1. init - methoddestroy - method 属性
  • init - method :当 Bean 被加载到 Spring 容器时,会调用 init - method 属性指定的方法。
  • destroy - method :当 Bean 从 Spring 容器中移除时,会调用 destroy - method 属性指定的方法。
xml 复制代码
    <!-- 定义一个单例的 UserService Bean -->
    <bean id="userServiceSingleton" class="com.example.service.UserService"
          scope="singleton"
          init-method="init"
          destroy-method="destroy">
    </bean>

    <!-- 定义一个多例的 UserService Bean -->
    <bean id="userServicePrototype" class="com.example.service.UserService"
          scope="prototype">
    </bean>
-----------------测试------------------
public class UserService {

    // 初始化方法
    public void init() {
        System.out.println("UserService 初始化");
    }

    // 销毁方法
    public void destroy() {
        System.out.println("UserService 销毁");
    }
}

实例化Bean的三种方式

无参构造器实例化

这是 Spring 中最基本的实例化 Bean 的方式,Spring 容器会调用 Bean 类的无参构造器(如果没有指定其他构造器)或者指定的有参构造器来创建 Bean 对象。

xml 复制代码
public class UserService {
    public UserService() {
        System.out.println("UserService实例化");
    }
}
-------------application.xml--------------
<bean id="userService" class="com.luxiya.demo.UserService"/>
-------------测试类--------------
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");

静态工厂方法实例化

使用一个静态工厂方法来创建 Bean 对象。静态工厂方法是类中的一个静态方法,该方法会返回一个对象实例。Spring 容器会调用这个静态工厂方法来获取 Bean 对象。

复制代码
public class StaticFactory {
    public static UserService getUserService()
    {
        System.out.println("静态工厂创建");
        return new UserService();
    }
}
-------------application.xml--------------
    <!-- 静态工厂-->
    <bean id="uSstatic" class="com.luxiya.demo.StaticFactory" factory-method="getUserService"/>
-------------测试类--------------
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("uSstatic");

实例工厂方法实例化

使用一个实例工厂的方法来创建 Bean 对象。首先需要创建一个工厂类的实例,然后调用该实例的方法来创建 Bean 对象。

复制代码
-------------application.xml--------------
<!--动态工厂-->
<bean id="dFactory" class="com.luxiya.demo.DFactory"/>
<bean id="useDF" factory-bean="dFactory" factory-method="getUserService"/>
-------------测试类--------------
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("useDF");

静态和动态对比

  • 静态工厂:静态工厂方法相对固定,因为它是类的静态方法,不能根据不同的实例状态进行不同的处理。通常适用于创建一些通用的、不依赖于实例状态的对象。不依赖于工厂类的实例。
  • 动态工厂:动态工厂方法可以根据工厂类实例的不同状态来创建不同的 Bean 对象,具有更高的灵活性。例如,工厂类可以有不同的属性值,根据这些属性值在实例工厂方法中创建不同类型或配置的 Bean 对象。依赖于工厂类的实例。

注解

常用注解

@Component 普通的类

@Controller 表现层@Service 业务层

@Repository 持久层

依赖注入常用的注解

@Value 用于注入普通类型(String,int,double 等类型)

@Autowired 默认按类型进行自动装配(引用类型)

@Qualifier 和@Autowired 一起使用,强制使用名称注入

@Resource Java 提供的注解,也被支持。使用 name 属性,按名称注入

对象生命周期(作用范围)注解

@Scope 生命周期注解,取值 singleton(默认值,单实例)和 prototype(多

例)

初始化方法和销毁方法注解(了解)

@PostConstruct 相当于 init-method

@PreDestroy 相当于 destroy-method

复制代码
@Service
public class TeacherService {
    public TeacherService() {
        System.out.println("TeacherService初始化");
    }
}

-----------application.xml----------
<!--开启注解扫描-->
<context:component-scan base-package="com.luxiya.demo"/>
-----------------测试类--------------
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
TeacherService teacherService = (TeacherService) context.getBean("teacherService");

纯注解

纯注解的方式是微服务架构开发的主要方式,所以也是非常的重要。纯注解的目的是替换掉所有的配置文件。但是需要编写配置类。

常用的注解总结

@Configuration 声明是配置类

@ComponentScan 扫描具体包结构的。也可以扫描多个包@ComponentScan(value ={"com.qcbyjy.demo4","com.qcbyjy.demo3"})

@Import 注解 Spring 的配置文件可以分成多个配置的,编写多个配置类。用于导入其他配置类

@Bean 注解 只能写在方法上,表明使用此方法创建一个对象,对象创建完成保存到 IOC 容器中

复制代码
------------demo包下的配置类--------------
@Configuration
@ComponentScan(basePackages = "com.luxiya.demo")
@Import(SpringConfig2.class)
public class SpringCofig {
    @Bean(name="dataSource")
    public DataSource createDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///spring_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
}
-------------------基础测试--------------
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringCofig.class);
        Student student = (Student) context.getBean("student");
-------------demo2包下的配置类----------------
@Configuration
@ComponentScan(basePackages = "com.luxiya.demo2")
public class SpringConfig2 {
}
//demo2包下
@Data
@Component
public class Course {
    @Value("java")
    private String name;

    public Course()
    {
        System.out.println("Course初始化");
    }
}
//demo包下
@Component
@Data
public class Student {
    @Value("luxiya")
    private String name;

    public Student() {
        System.out.println("Student实例化");
    }
}
------------测试@Import注解-------------
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringCofig.class);
        Course course = (Course) context.getBean("course");
        
------------测试@Bean注解-------------

                ApplicationContext context = new AnnotationConfigApplicationContext(SpringCofig.class);
//        Student student = (Student) context.getBean("student");
//        Course course = (Course) context.getBean("course");
        // 从应用上下文中获取 DataSource 对象
        DataSource dataSource = context.getBean("dataSource", DataSource.class);

        // 验证 DataSource 对象是否为 DruidDataSource 类型
        if (dataSource instanceof DruidDataSource) {
            DruidDataSource druidDataSource = (DruidDataSource) dataSource;
            System.out.println("Druid DataSource 配置信息:");
            System.out.println("Driver Class Name: " + druidDataSource.getDriverClassName());
            System.out.println("URL: " + druidDataSource.getUrl());
            System.out.println("Username: " + druidDataSource.getUsername());
            System.out.println("Password: " + druidDataSource.getPassword());
        }

其它问题

为什么p 命名空间方式需要无参构造

原因:在 Spring 中,当你使用属性注入(如你给出的 p 命名空间方式)时,Spring 容器在创建 Bean 实例的过程中,默认情况下会尝试使用无参构造函数来实例化对象,之后再通过 setter 方法进行属性注入。

Spring 容器在创建 Bean 实例时,主要有以下几个步骤:

  1. 实例化对象:Spring 需要先创建 Bean 对应的 Java 对象实例,一般情况下,它会优先尝试使用无参构造函数来创建对象。这是因为无参构造函数可以简单直接地创建对象,不需要额外的参数信息。
  2. 属性注入 :在对象实例创建完成后,Spring 会根据配置(如 p 命名空间指定的属性值)调用对象的 setter 方法将属性值注入到对象中。

所以,当你使用 p 命名空间进行属性注入时,Spring 首先会尝试调用无参构造函数来创建 Teacher 对象,若类中没有无参构造函数,就会抛出 No default constructor found 这样的异常。

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json