spring IOC(实现原理)
文章目录
- 依赖注入
- 控制反转
- 相关
- Spring 框架的 Bean管理的配置文件方式
- 实例化Bean的三种方式
- 无参构造器实例化
- 静态工厂方法实例化
- 实例工厂方法实例化
- 静态和动态对比
- 注解
- 常用注解
- 纯注解
- 其它问题
- 为什么p 命名空间方式需要无参构造
依赖注入
**依赖注入(DI)**是Spring框架核心功能之一,它允许对象间的依赖关系通过配置或代码在运行时动态注入,而不是通过对象内部硬编码。这种方法减少了代码间的耦合,增加了灵活性和可重用性。
依赖注入强调的是对象的依赖关系由外部来管理和提供,而不是由对象自身负责创建依赖对象。通过这种方式,可以降低对象之间的耦合度。即下面的方式也属于依赖注入。
常见的依赖注入方式:
- 构造函数注入:通过类的构造函数将依赖对象传递给该类。在对象实例化时,容器会调用相应的构造函数,并传入依赖对象的实例。
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// 业务方法
}
- Setter 方法注入:利用类的 setter 方法将依赖对象设置到该类中。容器先创建对象实例,然后调用 setter 方法来注入依赖对象。
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<String> hobbies;
private Map<String,String> card;
private Set<String> 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="adress" class="com.luxiya.Adress">
<property name="address" value="空中花园"/>
</bean>
<!-- 需要相应的构造函数-->
<bean id="teacher" class="com.luxiya.Teacher" c:name="luxiya" c:age="18"/>
<!-- 需要相应的构造函数-->
<bean id="teacher1" class="com.luxiya.Teacher">
<constructor-arg name="name" value="luxiya-constructor"/>
<constructor-arg name="age" value="188"/>
</bean>
<!-- 需要空参构造-->
<bean id="teacher2" class="com.luxiya.Teacher" p:name="zhihuiguan" p:age="19">
</bean>
<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管理的配置文件方式
id
属性
id
属性为每个 Bean 提供了一个唯一的标识符。
class
属性
class
属性指定了 Bean 对象的全限定类名。在这个例子中,com.example.service.UserService
就是 UserService
类的完整路径,Spring 容器会根据这个路径来创建相应的 Bean 实例。
scope
属性
singleton
(单例):这是scope
属性的默认值。当scope
设置为singleton
时,Spring 容器只会创建该 Bean 的一个实例,并且在整个应用程序的生命周期中都使用这个实例。prototype
(多例):当scope
设置为prototype
时,每次从 Spring 容器中获取该 Bean 时,容器都会创建一个新的实例。
init - method
和destroy - method
属性
init - method
:当 Bean 被加载到 Spring 容器时,会调用init - method
属性指定的方法。destroy - method
:当 Bean 从 Spring 容器中移除时,会调用destroy - method
属性指定的方法。
<!-- 定义一个单例的 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 对象。
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 实例时,主要有以下几个步骤:
- 实例化对象:Spring 需要先创建 Bean 对应的 Java 对象实例,一般情况下,它会优先尝试使用无参构造函数来创建对象。这是因为无参构造函数可以简单直接地创建对象,不需要额外的参数信息。
- 属性注入:在对象实例创建完成后,Spring 会根据配置(如
p
命名空间指定的属性值)调用对象的setter
方法将属性值注入到对象中。
所以,当你使用 p
命名空间进行属性注入时,Spring 首先会尝试调用无参构造函数来创建 Teacher
对象,若类中没有无参构造函数,就会抛出 No default constructor found
这样的异常。