Spring 的 IOC 容器管理对象示例
IOC 控制反转
IOC—Inversion of Control,即 “控制反转”,不是什么技术,而是一种设计思想。
在 Java 开发中,IOC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
所谓 IOC,对于 Spring 框架来说,就是由 Spring 来负责控制对象的生命周期和对象间的关系。
Spring 所倡导的开发方式就是如此,所有的类都会在 Spring 容器中登记。所有的类的创建、销毁都由 Spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 Spring 控制,所以这叫控制反转。
理解 IOC
如何理解好 IOC 呢?理解好 IOC 的关键是要明确:
谁控制谁,控制什么?
传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IOC 是有专门一个容器来创建这些对象,即由 IOC 容器来控制对 象的创建;谁控制谁?当然是 IOC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
为何是反转,哪些方面反转了?
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
对象示例
public class HelloWorld {
private String userName;
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void sayHello(){
System.out.println("Hello,"+userName);
}
}
传统方式加载对象
HelloWorld helloWorld=new HelloWorld();
helloWorld.setUserName("周杰伦");
helloWorld.sayHello();
IOC 容器加载对象
新建 IOC 配置文件
在 resources 目录下新建一个 IOC 的配置文件 applicationContext.xml,等下需要通过这个配置文件去创建 IOC 容器。
<?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">
<bean id="helloWorld" class="com.jueee.bean.HelloWorld">
<property name="userName" value="林俊杰"></property>
</bean>
</beans>
IOC 普通方式加载
- BeanFactory:表示 Spring IOC 容器,专门生产 bean 对象的工厂,负责配置,创建和管理 bean
- bean:被 Spring IOC 容器管理的对象都是 bean,可以理解为 Spring 下皆为 bean
//1.从classpath路径去寻找配置文件,加载我们的配置
Resource resources= new ClassPathResource("applicationContext.xml");
//2.加载配置文件之后,创建IOC容器
BeanFactory factory=new XmlBeanFactory(resources);
//3.从Spring IOC容器中获取指定名称的对象
HelloWorld helloWorld= (HelloWorld) factory.getBean("helloWorld");
//--------------------IOC结束了---------------------
helloWorld.sayHello();
IOC 注解方式加载
- 先在测试类头上加个 @ContextConfiguration,意思是找到 Spring 容器,classpath 就是 resource 目录
- @Autowired:表示自动按照类型去 Spring 容器中找到对应的 Bean 对象,然后自动注入
@SpringBootTest
@ContextConfiguration("classpath:applicationContext.xml")
public class HelloWorldTest2 {
@Autowired
private HelloWorld helloWorld;
@Test
public void testHelloIOCNB(){
helloWorld.sayHello();
}
}
IOC 加载工作原理
IOC 的工作原理,使用到的技术有两个,反射和内省。
如下示例所示:
String className = "com.jueee.bean.HelloWorld";
//--------------------模拟IOC开始了-------------------
//1.使用反射创建对象
Class clzz = Class.forName(className);
Constructor con = clzz.getConstructor();
con.setAccessible(true);//设置构造器可访问性为true
Object obj = con.newInstance();
//2.使用内省机制获取所有的属性名称
BeanInfo beanInfo = Introspector.getBeanInfo(clzz, Object.class);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
String propertyName = pd.getName();
if ("userName".equals(propertyName)) {
pd.getWriteMethod().invoke(obj, "陈奕迅");
}
}
HelloWorld helloWorld = (HelloWorld) obj;
//--------------------模拟IOC结束了---------------------
helloWorld.sayHello();
IOC 的容器类型
BeanFactory
//--------------------IOC开始了-------------------
//1.从classpath路径去寻找配置文件,加载我们的配置
Resource resources= new ClassPathResource("applicationContext.xml");
//2.加载配置文件之后,创建IOC容器
BeanFactory factory=new XmlBeanFactory(resources);
//3.从Spring IOC容器中获取指定名称的对象
HelloWorld helloWorld= (HelloWorld) factory.getBean(HelloWorld.class);
//--------------------IOC结束了---------------------
helloWorld.sayHello();
ApplicationContext
ApplicationContext 这个其实是 BeanFactory 的一个子接口。
//--------------------IOC开始了-------------------
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("上面的代码已经创建Bean对象了,下面的获取Bean,获取已有的Bean");
HelloWorld helloWorld= ctx.getBean("helloWorld",HelloWorld.class);
//--------------------IOC结束了---------------------
helloWorld.sayHello();
加载 Bean 的方式
根据 Bean 对象在容器中的 id 来获取。当有两个 id 重复时,就会报错。
HelloWorld helloWorld= (HelloWorld) factory.getBean("helloWorld");
根据类型获取 Bean。当两个 bean 的 id 不一样,class 一样的时候,还是会报错,报类找到的 Bean 不是唯一的
HelloWorld helloWorld= (HelloWorld) factory.getBean(HelloWorld.class);
根据 id + 类型来获取 Bean,推荐方式。
HelloWorld helloWorld= ctx.getBean("helloWorld",HelloWorld.class);
Bean 的作用域
singltton: 单例,在 IOC 容器中的 Bean 实例,都是唯一的
<bean id="helloWorld1" class="com.jueee.bean.HelloWorld" scope="singleton"></bean>
prototype: 多例,在 IOC 容器中的 Bean,每次都返回一个新的对象
<bean id="helloWorld2" class="com.jueee.bean.HelloWorld" scope="prototype"></bean>
配置文件的 import 导入
我们的 applicationContext.xml 里面写的是 Bean,当项目里面多个需要控制反转的配置,如果都写在一个 xml 文件里,太大,也太乱。
所以我们可以分开写每个包里面写个自己的 xml,然后 applicationContext.xml 直接 import 导入就可以了。
<!--导入其他的配置文件-->
<import resource="classpath:HelloWorld.xml"></import>
Bean 的初始化和销毁
构建 Bean
public class MyDataSource { public void open(){ System.out.println(this + " 初始化"); } public void dowork(){ System.out.println(this + " 工作"); } public void close(){ System.out.println(this + " 销毁"); } }
配置 xml
<bean id="myDataSource" class="com.jueee.bean.MyDataSource" init-method="open" destroy-method="close"></bean>
普通模式
MyDataSource myDataSource=new MyDataSource(); myDataSource.open(); myDataSource.dowork(); myDataSource.close();
输出:
com.jueee.bean.MyDataSource@4ae33a11 初始化 com.jueee.bean.MyDataSource@4ae33a11 工作 com.jueee.bean.MyDataSource@4ae33a11 销毁
IOC 模式
@SpringBootTest @ContextConfiguration("classpath:applicationContext4.xml") public class MyDataSourceTest { @Autowired private MyDataSource myDataSource; //IOC容器的方式 @Test public void test2(){ myDataSource.dowork(); } }
输出(自动进行创建和销毁):