IOC与DI #
Spring的IOC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)是Spring框架的核心概念,用于实现松耦合和可维护的应用程序。
- IOC(控制反转)意味着将对象的创建、组装和管理的控制权从应用程序代码转移到框架(Spring)中。传统的编程方式中,对象之间的依赖关系通常通过对象自己创建和管理其他对象实例来实现。而在Spring中,对象的创建和组装由Spring容器来完成,通过IOC容器,对象的依赖关系得以解耦,使得应用程序更加灵活、可扩展和可维护。
- DI(依赖注入)是IOC的一种实现方式。它通过在对象之间注入依赖关系,即通过构造函数、属性的Setter方法或接口的实现来向对象提供它所依赖的其他对象。这样,对象不再负责自己创建和管理依赖对象,而是通过IOC容器将依赖关系注入进来。
基本概念 #
- 容器(Container):Spring的IOC容器是一个负责创建、装配和管理对象的运行环境。它是Spring框架的核心部分,可以通过XML配置文件、注解或Java代码进行配置。
- Bean:在Spring中,对象被称为Bean。Bean是由Spring容器创建、装配和管理的对象实例。
- 配置元数据(Configuration Metadata):Spring使用配置元数据来描述和定义Bean及其依赖关系。配置元数据可以使用XML文件、注解或Java代码进行定义。
- 装配(Assembly):装配是指将Bean之间的依赖关系建立起来,使它们能够协同工作。Spring提供了多种装配方式,如构造函数注入、属性注入和接口注入。
- 注入(Injection):注入是指将依赖对象实例注入到目标对象中。Spring通过DI实现注入,即通过构造函数、属性的Setter方法或接口的实现将依赖关系注入到对象中。
- 生命周期管理(Lifecycle Management):Spring容器负责管理Bean的生命周期,包括实例化、初始化、使用和销毁。通过回调方法和生命周期接口,开发人员可以在Bean的生命周期中插入自定义逻辑。
底层原理 #
- 读取配置:Spring容器会读取配置文件(如XML配置文件)或注解来获取Bean定义和依赖关系的信息。
- 创建Bean实例:Spring容器根据Bean定义使用反射机制实例化Bean对象。
- 属性注入:Spring容器根据配置中的依赖关系,将所需的依赖对象注入到目标Bean中,可以通过构造函数注入、Setter方法注入或接口实现注入来实现依赖注入。
- 初始化和生命周期管理:Spring容器对Bean实例进行初始化,包括调用初始化方法、注册销毁回调等。Spring提供了多种方式来管理Bean的生命周期。
- 提供Bean:Spring容器将创建好的Bean对象提供给应用程序,应用程序可以通过容器来获取所需的Bean实例。
主要技术 #
Spring在实现IOC时运用了一些常见的设计模式,如工厂模式、单例模式和依赖注入模式等。这些设计模式帮助Spring实现了对象的创建、组装和管理,提供了灵活、可扩展的编程模型。
基于XML配置文件管理Bean #
创建对象 #
<bean id="userBean" class="org.example.bean.UserBean"></bean>
在Spring配置文件中,使用bean标签,添加相应的属性,实现对象的创建
id:对象的唯一表示,一般为类名并首字母小写class:创建对象所在类的全类名name:和id作用相同,只不过name属性可以添加特殊符号如/,id属性不可以
创建对象的时候,默认执行无参的构造
注入属性 #
注入属性-value #
方式一:Setter方法注入 #
实际上就是Spring在创建对象后,通过调用相应的Setter完成属性的注入
1、bean类中,创建属性,以及对应的Setter
public class Book {
private String bookName;
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
2、在applicationContext.xml配置文件中,先配置对象的创建,再配置属性
<bean id="book" class="org.example.bean.Book">
<!-- 使用property标签,完成属性注入
name:表示需要注入的属性
value:表示注入属性的值,如果注入的是对象,那么使用ref
-->
<property name="bookName" value="西游记"></property>
</bean>
方式二:通过有参构造注入 #
1、创建类,并提供有参构造
public class Person {
private String personName;
public Person() {
}
public Person(String personName) {
this.personName = personName;
}
}
2、在Spring配置文件中进行配置
<bean id="person" class="org.example.bean.Person">
<!-- 使用constructor-arg标签,完成使用有参构造属性注入
name:需要注入的属性,可以换成index,按照在有参构造中的属性的索引注入
value:注入属性的值,如果注入的是对象,那么使用ref
-->
<constructor-arg name="personName" value="lucy"></constructor-arg>
</bean>
方式三:P命名空间注入 #
实际上是对于Setter注入的一种简化,底层使用的还是Setter
1、配置文件添加P命名空间xmlns:p="http://www.springframework.org/schema/p"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
2、在bean标签中,进行操作
<bean id="book" class="org.example.bean.Book" p:bookName="西游记"></bean>
property标签 #
在Spring的XML配置文件中,<property>标签用于定义Bean的属性注入。通过<property>标签,可以将值或引用注入到Bean的属性中。
设置空值 #
<property name="bookName">
<null/>
</property>
值中包含特殊符号 #
例如特殊符号<,可以使用如下两种方式注入值
方式一,使用转义字符
<property name="bookName" value="<lucy>"></property>
方式二,使用CDATA
<property name="bookName">
<value>
<![CADATA[<lucy>]]>
</value>
</property>
注入属性-bean #
外部bean #
只需要将相关的对象,添加在配置文件中,由Spring创建,然后通过ref属性指定beanId,进行相应的属性(对象)注入,需要在调用类中,添加被调用类对象为属性,并且提供setter,例如,UserService中,需要调用UserDao
public class UserDao {
public void add(){
System.out.println("open in UserDao -- add");
}
}
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void showAdd(){
userDao.add();
}
}
<bean id="userDao" class="org.example.dao.UserDao"></bean>
<bean id="userService" class="org.example.service.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
内部bean、级联赋值 #
例如有Student、School两个实体类,Student实体类中有School属性,那么可以通过内部bean的方式进行注入,也可以通过外部bean,内部bean方式如下
public class School {
private String schoolName;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
}
public class Student {
private String studentName;
private School school;
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}
<bean id="student" class="org.example.bean.Student">
<property name="studentName" value="lucy"></property>
<property name="school">
<bean id="school" class="org.example.bean.School">
<property name="schoolName" value="清华大学"></property>
</bean>
</property>
</bean>
自动注入 #
用于自动装配Bean的依赖项。它指定了如何解析Bean的依赖关系,常见值如下
no(默认值):不自动装配依赖项。需要手动通过<property>或<constructor-arg>标签显式地注入依赖项。byName:按照属性名称自动装配依赖项。Spring会查找与属性名称相同的Bean,并将其注入到相应的属性中。byType:按照属性类型自动装配依赖项。Spring会查找与属性类型兼容的Bean,并将其注入到相应的属性中。如果存在多个兼容的Bean,将抛出异常。constructor:通过构造函数自动装配依赖项。Spring会根据构造函数的参数类型查找与之兼容的Bean,并自动将其注入到构造函数中。default:会根据情况自动确定使用哪种自动装配方式。
public class Family {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Family{" +
"user=" + user +
'}';
}
}
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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"
>
<!-- 创建bean -->
<bean id="user" class="top.ygang.springdemo.User">
<property name="name" value="grady"/>
</bean>
<!-- 创建bean,并使用autowire注入属性 -->
<bean id="family" class="top.ygang.springdemo.Family" autowire="byType"/>
</beans>
作用域 #
使用bean标签的scope属性进行指定Bean的作用域,控制Bean的生命周期和可见性。
singleton:默认值,单例模式,表示在整个应用程序中只创建一个Bean实例。prototype:每次请求时都创建一个新的Bean实例,每次在获得才会被创建,每次创建都是新的对象。request:在每个HTTP请求中创建一个新的Bean实例(仅适用于Web应用)。session:在每个HTTP会话中创建一个新的Bean实例(仅适用于Web应用)。
<bean scope="singleton" ... />
延迟初始化 #
使用bean标签的lazy-init属性指定Bean的延迟初始化。当设置为true时,Bean将在第一次使用时才被初始化,而不是在应用程序启动时立即初始化。
<bean lazy-init="true" ... />
depends-on #
使用bean标签的depends-on属性指定Bean依赖的其他Bean的名称。它确保在当前Bean实例化之前,指定的Bean已经被实例化。
<bean depends-on="userRepository" ... />
基于注解的方式管理Bean #
| 注解 | 描述 |
|---|---|
@Component |
通常用于普通Java类身上,表示这是一个需要被Spring容器进行管理的组件 |
@Controller |
用于描述表现(控制)层controller的类,这个类需要被Spring容器进行管理,效果等同于@Component |
@Service |
用于描述业务层service的类,这个类需要被Spring容器进行管理,效果等同于@Component |
@Repository |
用于描述持久层mapper(dao)的类,这个类需要被Spring容器进行管理,效果等同于@Component |
@Configuration |
声明配置类,效果等同于@Component |
@scope |
声明该类的作用范围,例如@Scope("prototype") |
@Bean |
在配置类的方法上添加注解,方法返回的对象将被注册为bean |
@Value |
注入外部配置文件的值,例如@Value("${property.name}") |
@Qualifier |
指定具体的依赖对象,和@Autowired一起使用,通过指定bean的名称或标识符来精确注入依赖对象,例如:@Autowired @Qualifier("myBean") |
@PostConstruct |
在需要执行初始化操作的方法上添加注解,在构造函数执行之后执行初始化操作 |
@PreDestroy |
在需要执行清理操作的方法上添加注解,在bean销毁之前执行清理操作 |
@Autowired |
自动装配,由Spring框架提供,按照类型进行自动装配,找不到会抛出异常,可以设置属性required=false找不到也不会抛异常,更具有Spring的特性和功能,例如支持@Qualifier注解进行更精确的依赖注入,可以应用于类的构造方法、字段、Setter方法和任意方法上 |
@Resource |
自动装配,由Java EE提供,先按照名称进行装配,找不到再按类型,可以应用于类的字段、Setter方法和任意方法上,但是不支持构造方法注入 |
context:component-scan #
以上注解要使用的前提条件,必须在XML配置文件中开启注解扫描,指定要扫描的基础包,可以使用逗号分隔多个包名
<context:component-scan base-package="top.ygang.controller,top.ygang.service" />
标签指定了要扫描的基础包为top.ygang。Spring将在该包及其子包下扫描所有被注解标记的组件,并将其注册到容器中。
过滤条件,可以使用<context:include-filter>和<context:exclude-filter>来设置扫描的过滤条件
<context:component-scan base-package="top.ygang">
<!-- 包含所有以org.springframework.stereotype.Service注解标记的类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<!-- 排除所有以Impl结尾的类 -->
<context:exclude-filter type="regex" expression=".*Impl$"/>
</context:component-scan>
context:annotation-config #
<context:annotation-config />
用于启用基于注解的配置和自动装配的支持,让Spring能够自动解析和处理@Autowired、@Resource等注解,从而实现依赖注入(2.5后的版本没必要加了)
在Spring2.5版本之前,还用来开启声明周期注解的使用@PostConstruct和@PreDestroy,从Spring 2.5版本开始,这些生命周期注解的支持被默认启用,无需额外的配置。
@Value #
注意:使用@Value注解时,通常需要将其应用于被Spring管理的组件(例如使用@Component、@Service、@Repository等注解标识的类)中,以便Spring能够扫描到该组件,并在实例化过程中处理@Value注解。
用于将值注入到类的字段、方法参数或构造函数参数中。它可以用于注入简单类型的值,如字符串、数字,也可以用于注入复杂类型,如对象、集合等
@Value("grady")
private String name;
// 使用Autowired注解可以在Spring容器创建当前类实例的时候自动调用,并且注入参数
@Autowired
public void setName(@Value("${name}")String name) {
this.name = name;
}
也可以从配置文件中读取,Spring内置了解析.properties、.yaml\yml的文件解析器,用来解析配置文件,需要在Spring配置文件中开启属性文件解析器,多个配置文件可以使用逗号,进行分隔,也可以使用通配符*来指定匹配,例如*.yml
<context:property-placeholder location="1.properties,2.yml" />
my.name=grady
my:
name: grady
@Value("${my.name}")
private String name;
Bean的生命周期 #
在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。
在Spring框架中,每个Bean都有其生命周期,即它在容器中的创建、初始化和销毁过程中经历的一系列阶段。Spring框架中的Bean生命周期涉及多个阶段,包括实例化、依赖注入、初始化、使用和销毁。下面是Spring Bean的生命周期的详细解释:
- 实例化:
- 当Spring容器启动时,根据配置文件或注解扫描等方式,创建Bean的实例。可以通过构造方法或工厂方法来实例化Bean。
- 依赖属性注入:
- 在实例化Bean后,Spring容器会检查Bean的依赖关系,并将所需的依赖注入到Bean中。依赖注入可以通过构造方法注入、Setter方法注入或字段注入来完成。
- Aware接口回调:
- 如果Bean实现了Spring的Aware接口(如
BeanNameAware、BeanFactoryAware、ApplicationContextAware等),Spring容器会调用相应的回调方法,使Bean能够获取与Spring容器相关的信息。
- 如果Bean实现了Spring的Aware接口(如
- 初始化前回调(BeanPostProcessor):
- Spring容器会调用注册的
BeanPostProcessor接口实现类的postProcessBeforeInitialization()方法,可以在Bean初始化之前对Bean进行一些定制和处理
- Spring容器会调用注册的
- 初始化:
- 在属性注入完成后,Spring容器会调用Bean的初始化方法。Bean的初始化方法可以通过两种方式来定义:
- 使用
@PostConstruct注解标记的方法。 - 实现
InitializingBean接口,并实现其中的afterPropertiesSet()方法。
- 使用
- 在属性注入完成后,Spring容器会调用Bean的初始化方法。Bean的初始化方法可以通过两种方式来定义:
- 初始化后回调(BeanPostProcessor):
- Spring容器会调用注册的
BeanPostProcessor接口实现类的postProcessAfterInitialization()方法,可以在Bean初始化之后对Bean进行一些定制和处理
- Spring容器会调用注册的
- 使用Bean:
- 初始化完成后,Bean可以被应用程序使用。在这个阶段,Bean会执行业务逻辑,提供所需的功能。
- 销毁:
- 在销毁阶段,Spring容器会调用Bean的销毁方法。Bean的销毁方法可以通过两种方式来定义:
- 使用
@PreDestroy注解标记的方法。 - 实现
DisposableBean接口,并实现其中的destroy()方法。
- 使用
- 需要注意的是,Bean的销毁阶段只有在Spring容器正常关闭时才会触发,或者在Web应用程序中,当Servlet容器销毁时才会触发。
- 在销毁阶段,Spring容器会调用Bean的销毁方法。Bean的销毁方法可以通过两种方式来定义:
代码体现 #
Spring配置文件
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
>
<!-- 开启注解扫描 -->
<context:component-scan base-package="top.ygang"/>
<!-- 开启注解支持 -->
<context:annotation-config/>
</beans>
Bean
@Component
public class User implements ApplicationContextAware, InitializingBean, DisposableBean {
private String name;
public User(){
System.out.println("Bean实例化:constructor");
}
public String getName() {
return name;
}
@Autowired
public void setName(@Value("grady") String name) {
System.out.println("Bean依赖注入:@Autowired");
this.name = name;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("Aware调用:setApplicationContext");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Bean初始化:afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("Bean销毁:destroy");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
BeanPostProcessor
@Configuration
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化前回调:postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化后回调:postProcessAfterInitialization");
return bean;
}
}
main方法
public static void main(String[] args) {
System.out.println("容器启动:ClassPathXmlApplicationContext");
ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User bean = applicationContext.getBean(User.class);
System.out.println("使用Bean");
System.out.println(bean);
System.out.println("容器关闭:ConfigurableApplicationContext.close()");
applicationContext.close();
}
输出结果
容器启动:ClassPathXmlApplicationContext
Bean实例化:constructor
Bean依赖注入:@Autowired
Aware调用:setApplicationContext
初始化前回调:postProcessBeforeInitialization
Bean初始化:afterPropertiesSet
初始化后回调:postProcessAfterInitialization
使用Bean
User{name='grady'}
容器关闭:ConfigurableApplicationContext.close()
Bean销毁:destroy