1.IOC和DI
IOC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:
图1-1 传统应用程序示意图
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:
DI是什么
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
2.DI入门案例
项目目录
配置文件
<?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">
<!-- 导入spring的坐标spring-context,对应的版本是5.2.10.RELEASE-->
<!-- 配置bean-->
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl"></bean>
<bean id="bookService" class="com.example.service.impl.BookServiceImpl">
<!-- 配置server与dao的关系-->
<property name="bookDao" ref="bookDao"></property>
</bean>
</beans>
图例说明一下:
3.bean的基础配置
定义
在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。bean 是由Spring IoC 容器实例化、组装和管理的对象。
bean的基础配置
bean属性:
- id:给bean取名字
- class:给定义路径类名
- property:表示配置当前的属性
- name:表示配置的具体属性名称
- ref:参照哪一个bean
bean别名配置
bean作用范围配置
scope:
- singleton:单例(默认)
- property非单例
spring bean为什么是单例
Spring Bean之所以是单例的主要原因是为了提高性能和减少资源消耗。下面是几个原因:
-
性能优化:单例模式可以避免频繁创建和销毁对象的开销。当一个Bean被创建后,Spring容器会将其缓存起来,下次需要使用该Bean时,直接从缓存中获取,避免了重复创建对象的开销。
-
资源共享:单例模式可以实现资源的共享。在一个应用程序中,如果多个地方需要使用同一个Bean实例,通过单例模式可以确保这些地方使用的是同一个实例,避免了资源的重复分配和浪费。
-
依赖管理:单例模式可以方便进行依赖注入和依赖查找。在一个应用程序中,如果多个地方需要使用同一个Bean实例,通过单例模式可以方便地将该实例注入到需要使用的地方,简化了依赖管理的过程。
-
状态共享:单例模式可以实现状态的共享。在一个应用程序中,如果多个地方需要共享某个对象的状态,通过单例模式可以确保这些地方使用的是同一个对象,从而实现状态的共享。
总结起来,Spring Bean之所以是单例的主要原因是为了提高性能、减少资源消耗、方便依赖管理和实现状态共享。
4.bean的生命周期
-
实例化(Instantiation):在这个阶段,Spring容器会根据配置信息或注解创建Bean的实例。可以通过构造函数实例化Bean对象或者使用工厂方法创建。
-
属性赋值(Population):在这个阶段,Spring容器会将配置文件中或注解中定义的属性值注入到Bean实例中。可以使用依赖注入(Dependency Injection)或者自动装配(Autowiring)来实现属性赋值。
-
初始化(Initialization):在这个阶段,Spring容器会调用Bean的初始化方法,可以自定义初始化方法来执行一些特定的逻辑操作。可以使用
@PostConstruct
注解标记初始化方法。 -
使用(In Use):在这个阶段,Bean实例已经被完全创建,并且可以被其他组件或模块使用。
-
销毁(Destruction):在这个阶段,当Bean不再被使用时,Spring容器会调用Bean的销毁方法进行资源释放和清理工作。可以使用
@PreDestroy
注解标记销毁方法。
需要注意的是,Bean的生命周期是由Spring容器来管理和控制的,开发人员可以通过实现InitializingBean
和DisposableBean
接口或者使用相应的注解来自定义初始化和销毁方法。此外,还可以通过配置文件中的init-method
和destroy-method
属性来指定初始化和销毁方法的名称。
总结起来,Bean的生命周期包括实例化、属性赋值、初始化、使用和销毁这几个阶段,开发人员可以在适当的阶段进行自定义操作,以满足具体的需求。
5.依赖注入方式
在Spring框架中,有多种方式可以实现依赖注入(Dependency Injection):
-
构造函数注入(Constructor Injection):通过构造函数来注入依赖对象。在Bean的定义中,可以通过构造函数参数来指定依赖对象,Spring容器会自动解析并注入相应的依赖。
-
Setter方法注入(Setter Injection):通过Setter方法来注入依赖对象。在Bean的定义中,可以为依赖对象提供Setter方法,并在配置文件或注解中指定依赖对象的引用,Spring容器会自动调用Setter方法来注入依赖。
-
字段注入(Field Injection):通过直接将依赖对象注入到Bean的字段上。在Bean的定义中,可以使用
@Autowired
注解或@Resource
注解来标记要注入的字段,Spring容器会自动将依赖对象注入到对应的字段上。 -
接口注入(Interface Injection):通过在Bean实现接口中定义的方法来注入依赖对象。在Bean的定义中,可以实现特定的接口,并在接口方法中接受依赖对象作为参数,Spring容器会自动调用接口方法来注入依赖。
需要注意的是,以上方式可以单独使用,也可以组合使用。例如,可以通过构造函数注入必要的依赖对象,然后使用Setter方法或字段注入来注入可选的依赖对象。
另外,Spring还提供了注解方式的依赖注入,包括@Autowired
、@Resource
、@Inject
等注解,可以直接在Bean的字段、构造函数或Setter方法上使用这些注解来实现依赖注入。
总结起来,Spring框架提供了多种方式来实现依赖注入,开发人员可以根据具体的需求和情况选择合适的方式来进行依赖注入。
6.加载properties文件
在Java中加载properties文件,可以使用java.util.Properties
类来实现。下面是一个简单的示例代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class LoadPropertiesFileExample {
public static void main(String[] args) {
Properties properties = new Properties();
InputStream inputStream = null;
try {
// 加载properties文件
inputStream = new FileInputStream("config.properties");
properties.load(inputStream);
// 获取属性值
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 打印属性值
System.out.println("Username: " + username);
System.out.println("Password: " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭输入流
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上述代码中,假设我们要加载一个名为"config.properties"的properties文件。通过FileInputStream
来创建一个输入流,然后使用Properties
类的load()
方法将properties文件的内容加载到Properties
对象中。接下来,我们可以使用getProperty()
方法获取指定属性的值,并进行相应的操作。
需要注意的是,上述代码中的文件路径是相对路径,表示当前工作目录下的文件。如果properties文件不在当前工作目录下,可以使用绝对路径或者相对路径来指定文件的位置。
另外,还可以使用ClassLoader
来加载位于classpath下的properties文件,例如:
InputStream inputStream = LoadPropertiesFileExample.class.getClassLoader().getResourceAsStream("config.properties");
这样可以确保在不同的环境中都能正确加载properties文件。