Spring学习第一章 、 第一节:Spring Ioc容器

前言

上一章讲过有关IoC(Inversion of Control 控制反转)的内容,我们先来看看这指的是什么:

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

好吧,其实我看完这段话这段话也是丈二和尚摸不着头脑,如果非要用我的理解说的话(在spring中),控制反转就是将实例化对象的任务交给了IoC容器
juo个梨子~
一般情况下,我们会使用new的方法实例化一个Bean对象(实际上是POJO,Bean更好理解一些,如果想了解POJO可以参考:Bean、POJO、DAO、EJB的区别)来进行数据的传递等:

Student s1 = new Student();
s1.setName("张");
s1.setAge(20);

但是,在spring中,可能会采用XML或注解对Bean对象进行参数注入(没错就是注入,后面章节会提到哦)所以可以说spring是采用依赖注入的方式实现控制反转的,可以把对象从应用中解耦出去(个人理解)。

没错,这一章就会围绕控制反转对Spring进行学习。

正文

Spring IoC 容器

IoC 容器

首先看一些概念

IoC 容器
Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans.
通过阅读配置元数据提供的指令,容器知道对哪些对象进行实例化,配置和组装。配置元数据可以通过 XML,Java 注释或 Java 代码来表示。下图是 Spring 如何工作的高级视图。 image
Spring IoC 容器利用 Java 的 POJO 类和配置元数据来生成完全配置和可执行的系统或应用程序。
IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。

序号描述
1Spring BeanFactory 容器。 它是最简单的容器,给 DI 提供了基本的支持,它用 org.springframework.beans.factory.BeanFactory 接口来定义. BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的与 Spring 整合的第三方框架的反向兼容性的目的。 2
2Spring ApplicationContext 容器。该容器添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。该容器是由 org.springframework.context.ApplicationContext 接口定义。

ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常建议超过 BeanFactory。BeanFactory 仍然可以用于轻量级的应用程序,如移动设备或基于 applet 的应用程序,其中它的数据量和速度是显著。

Spring BeanFactory 容器

先来一个较为官方的解释(源自w3cSchool):

这是一个最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持,这个容器接口在 org.springframework.beans.factory.BeanFactor 中被定义。BeanFactory 和相关的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍旧保留在 Spring 中,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。
在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。
在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory。

额。。。依旧是说了一大堆==,无法形象的表示进行理解,我们来创建一个新的Spring应用程序来体会什么是Spring容器。

  1. 首先在你的IDE中创建一个工程,并在src文件夹下新建包或文件夹,这里我们创建名为com.hello包
  2. 将Spring的库文件(.jar包)导入项目中(可以直接导入也可以使用一些自动化部署工具如:Maven、Gradle等导入)
  3. 在com.hello下创建HelloWorld.java,在src下创建Main.java
package com.hello;
public class HelloWorld {
  private String message;
  //这里需要设置set,get方法用于之后容器对对象属性进行注入
  public void setMessage(String message){
   this.message  = message;
  }
  public void getMessage(){
   System.out.println("Message : " + message);
  }
}
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class Main {
  public static void main(String[] args) {
     //实例化XmlBeanFactory容器,会利用 ClassPathResource() API 去加载在路径 CLASSPATH 下可用的 bean 配置文件,初始化创建并初始化配置文件中的对象
     XmlBeanFactory factory = new XmlBeanFactory
                            (new ClassPathResource("ioc.xml"));
     //通过配置文件中的 bean ID 来返回一个真正的对象,该对象最后可以用于实际的对象。一旦得到这个对象,你就可以利用这个对象来调用任何方法。
     HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
     obj.getMessage();
  }
}

4.在src下创建容器的配置文件ioc.xml

<?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-3.0.xsd">
   <!--
   id是用于之后ioc容器获取,而property是指使用set方法注入依赖关系(之后详细提及)
   -->
   <bean id="helloWorld" class="com.tutorialspoint.HelloWorld">
       <property name="message" value="Hello World!"/>
   </bean>

</beans>

最后运行得到的结果应该是:

message: Hello World!

Spring ApplicationContext 容器

一些赘述(比较官方的东西以后来看没准有不同的体会):

Spring ApplicationContext 容器
Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文。
Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:

  • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
  • ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
  • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

之前已经对BeanFactory进行了示例,在导包等步骤没有其他区别,我们只看在容器初始化 的Main方法上的区别:
FileSystemXmlApplicationContext

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class MainApp {
   public static void main(String[] args) {
      ApplicationContext context = new FileSystemXmlApplicationContext
            ("C:workspace/HelloSpring/src/ioc.xml");
      HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
      obj.getMessage();
   }
}

ClassPathXmlApplicationContext:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
package com.hello.HelloWorld;

public class MainApp {
   public static void main(String[] args) {
      ApplicationContext ac = new ClassPathXmlApplicationContext(
						"scope.xml");
	//获得对象(默认情况下,是同一个对象)
	HelloWorld hello = ac.getBean("helloWorld",HelloWorld.class);
      hello.getMessage();
   }
}

发现有什么不同嘛?再看一遍:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
   public static void main(String[] args) {
      ApplicationContext context = 
             new ClassPathXmlApplicationContext("Beans.xml");
      HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
      obj.getMessage();
   }
}

这些栗子应该能找到其中的规律吧,那么最后一个就总结一下,之后会有专门的章节讲WebApplicationContext。

XmlWebApplicationContext:从Web系统中的XML文件来载入Bean定义的信

ServletContext servletContext = request.getSession().getServletContext();    
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);

配置WebApplicationContext的两种方法:

  1. 利用Listener接口来实现
<listener>

       <listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

<context-param>

       <param-name>contextConfigLocation</param-name>

       <param-value>classpath:applicationContext</param-value>

</context-param>
  1. 利用Servlet接口来实现
<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:applicationContext</param-value>

</context-param>
<Servlet>

       <servlet-name>context</servlet-name>

       <servlet-class>

           org.springframework.web.context.ContextLoaderServlet

       </servlet-class>

</servlet>

Spring Bean

Spring Bean 定义

被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的,例如,已经在先前章节看到的,在 XML 的表单中的 定义。

配置元数据(创建对象)
注意!接下来的配置文件xml会以不同的名称表现在不同章节,该章节的xml文件名与该章节的容器启动时加载的xml文件名将会相同,

  1. 使用无参构造器创建对象
package ioc; 
public class B{
    public B(){
        //给类添加无参构造器,虽然默认有该构造器,这里作为说明。
    }
}
<!-- 使用无参构造器创建对象-->
<beans>
<bean id="b1" class="ioc.B"/>
</beans>

class:这个属性是强制性的,并且指定用来创建 bean 的 bean 类。(使用全限定名)
id:指定bean的名称,唯一(会通过id获得对象)。

//启动spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取对象
B b1 = (B)ac.getBean("b1");//因为返回的是Object类,所以需要强转
B b1 = ac.getBean("b1",B.class)//指定返回类型,不需要强转

这样就可以进行无参构造器方式的创建对象了!
2. 使用静态工厂方法创建对象:

<bean id="cal1" 
class="java.util.Calendar"
factory-method="getInstance">
</bean>

factory-mothod:调用工厂方法实例化对象
(前提:该方法是一个静态方法。)

进行获取:


ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

Calendar cal1 = ac.getBean("cal1",B.class)
  1. 使用实例工厂方法创建对象
<bean id="time1" 
	 factory-bean="cal1"
	 factory-method="getTime"
	 ></bean>

哇,没有指定类就可以啦?
原因:spring容器此时可以调用cal1对象的getTime方法创建对象

Spring Bean作用域

首先我们写一个实例:(为了区别)

<bean id="s1" class="scope.ScopeBean"/>
//启动spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
//获得对象(默认情况下,是同一个对象)
ScopeBean s1 = ac.getBean("s1",ScopeBean.class);
ScopeBean s2 = ac.getBean("s1",ScopeBean.class);
//测试s1s2是否为同一个对象
System.out.println(s1 == s2);

返回值是true!

怎么会这样呢?如何创建多个实例呢?这就要提到Spring Bean的作用域了

Bean 的作用域
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。

如果为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。

如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。

此时就提到了Spring 框架支持以下五个作用域,分别为singleton、prototype、request、session和global session,5种作用域。

  1. singleton

嗯。。。你没看错就是单例的,要知道单例模式的对象在生产和工作出现是很频繁的,所以spring也提供了产生单例对象的方法

现在我们先创建一个单例对象

<bean id="s1" class="scope.ScopeBean" scope="singleton"/>
ApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
ScopeBean s1 = ac.getBean("s1",ScopeBean.class);
ScopeBean s2 = ac.getBean("s1",ScopeBean.class);
System.out.println(s1 == s2);

哈哈,现在发现了什么嘛,spring不指定scope时,其实默认情况下创建的对象是单例的

那就意味着:如果将创建Bean时的scope属性设定为singleton或者不进行设定,那么容器在获取对象时会创建同一个对象!
2. prototype

刚才提到的singleton情况,只需要把scope属性设定为prototype,就可以在每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
还等什么,快去试试吧!

  • ++以下作用域只适用于WebApplicationContext环境++
  1. request

每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境

  1. session

同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境

  1. global-session

一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境

Spring Bean生命周期

Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁

  1. 定义
  2. 初始化(分配资源)
  3. 使用
  4. 销毁(释放资源)

Spring中有初始化方法和销毁方法来控制Spring Bean的生命周期

初始化方法和销毁方法
package scope;
public class MessageBean {
    public MessageBean() {
		System.out.println("MessageBean()");
}
	public void init() {
		System.out.println("init()");
	}
	public void sendMsg{
	    System.out.println("sendMsg()");
	}
	public void destory() {
		System.out.println("destory()");
	}
}

看一个示例(模拟有上述方法)

<bean id="mb1" class="scope.MessageBean"
	init-method="init"
	destroy-method="destory"> </bean>

init-methoddestroy-method分别在配置文件中设置调用的初始化方法销毁方法,(在Bean初始化和销毁时会自动执行初始化和销毁方法)
调用该方法的sendMsg并进行关闭容器就可以演示效果:

//测试生命周期
//AbstractApplicationContext是ApplicationContext的子接口,ClassPathXmlApplicationContext实现了ApplicationContext,只有AbstractApplicationContext中才有关闭容器的方法。
AbstractApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
	MessageBean m1 = ac.getBean("mb1",MessageBean.class) ;
	m1.sendMsg();
	ac.close();
  1. 初始化回调

org.springframework.beans.factory.InitializingBean 接口指定一个单一的方法:

void afterPropertiesSet() throws Exception;

可以简单的实现该接口进行初始化

public class ExampleBean implements InitializingBean {
   public void afterPropertiesSet() {
      // do some initialization work
   }
}
<bean id="exampleBean" 
      class="examples.ExampleBean"
      init-method="init"/>
  1. 销毁回调

org.springframework.beans.factory.DisposableBean 接口指定一个单一的方法:

void destroy() throws Exception;

同理:

public class ExampleBean implements DisposableBean {
   public void destroy() {
      // do some destruction work
   }
}
<bean id="exampleBean"
         class="examples.ExampleBean"
         destroy-method="destroy"/>

大佬说的一些细节0.0:

如果你在非 web 应用程序环境中使用 Spring 的 IoC 容器;例如在丰富的客户端桌面环境中;那么在 JVM 中你要注册关闭 hook。这样做可以确保正常关闭,为了让所有的资源都被释放,可以在单个 beans 上调用 destroy 方法。
建议你不要使用 InitializingBean 或者 DisposableBean 的回调方法,因为 XML 配置在命名方法上提供了极大的灵活性。

Spring Bean延迟加载

直接上栗子了?:

ApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
		//spring容器启动会自动将单例bean创建好
		//可设置延迟加载,在bean中(懒加载)

为了方便演示,从作用域一节开始,xml没有进行改变。
!!!为啥我啥都没调显示有对象创建了呢?

Spring在启动时,会检查到作用域为单例的Bean并自动实例化
而延迟加载可以取消这一操作,等调用时再进行实例化:

<bean id="mb1" class="scope.MessageBean"
	init-method="init"
	destroy-method="destory"
	lazy-init="true">
	<!--这是个缺省的singleton Bean-->

指定是否延迟加载,值为true时延迟加载,缺省值为false。

Spring Bean后置处理器

赘述 x N (=_=)

Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。
BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。
你可以配置多个 BeanPostProcessor 接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。
BeanPostProcessor 可以对 bean(或对象)实例进行操作,这意味着 Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 接口进行它们的工作。
ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。

package scope;
public class MessageBean {
    private String message;
    public MessageBean() {
		this.message  = message;
}
	public void init() {
		System.out.println("init()");
	}
	public void sendMsg{
	    System.out.println("sendMsg()");
	}
	public void destory() {
		System.out.println("destory()");
	}
}

这是实现 BeanPostProcessor 的非常简单的例子,它在任何 bean 的初始化的之前和之后输入该 bean 的名称。你可以在初始化 bean 的之前和之后实现更复杂的逻辑,因为你有两个访问内置 bean 对象的后置处理程序的方法。

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitMessage implements BeanPostProcessor {
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("BeforeInitialization : " + beanName);
      return bean;  // you can return any other object as well
   }
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("AfterInitialization : " + beanName);
      return bean;  // you can return any other object as well
   }
}
AbstractApplicationContext ac = new ClassPathXmlApplicationContext("scope.xml");
	MessageBean m1 = ac.getBean("mb1",MessageBean.class) ;
	m1.sendMsg();
	ac.close();

演示之后就明白了!

引用

本文有自己的代码,同时也参考了很多博客和教程:
Spring概述:https://www.w3cschool.cn/wkspring/pesy1icl.html
Ioc:https://baike.baidu.com/item/控制反转/1158025?fr=aladdin

本文用于个人学习和分享,多处借鉴,如有不足之处请指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值