SpringIOC

SpringIOC

       IOC(Inversion of Control) ,所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器(比如SpringIOC容器)负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓的反转。在传统编程中,当我们创建一个对象时,需要在程序显示的new一个,这时对象的创建及维护的控制权在我们自己手中,而在Spring框架中,则不需要我们手动new,这时对对象的控制权转移到了SpringIOC容器中,当我们需要对象时,直接从容器中获取即可,至于对象怎么来的,我们无需关心,因为SpringIOC容器为我们做了这些事,就像我们洗衣服时,我们需要自己做抹肥皂,搓衣服、漂洗及拧干等动作,而当洗衣机出现之后,这些工作全部交由洗衣机这个容器来做,我们只需插上电源,按下启动开关即可,至于洗衣服是如何以及何时放洗衣粉,洗衣服、漂洗、脱水,我们不需要知道,我们只关心,当洗衣机停止工作后,就表示我们的衣服已经洗干净了,SpringIOC容器就相当于这个洗衣机一样,只不过洗衣机装的是衣服,而容器装的是对象,在Spring中把这一个一个的对象称为bean,那么这些bean放在什么地方呢?答案是配置文件中,当我们的容器启动之后,首先会读取配置文件中各个bean,并对其进行实例化,配置等,配置文件格式像下面这样,我们称这种实例化bean的方式为类构造器实例化bean,通常我们需要被实例化的bean提供一个默认的构造器。

<?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-2.5.xsd">
 
  <bean id="..." class="...">
 
<!-- collaborators and configuration for this bean go here -->
 
  </bean>
 
  <bean id="..." class="...">
 
<!-- collaborators and configuration for this bean go here -->
 
  </bean> 
 
<!-- more bean definitions go here --> 
 
</beans>

      可以看出SpringIOC容器可以包含一个或多个bean每个bean都有一个或多个id。这些id在当前IoC容器中必须唯一。如果一个bean有多个id,那么其他的id在本质上将被认为是别名。当基于这种配置文件的方式来配置bean时,将通过id或name属性来指定bean标识符。不过为一个bean提供一个name并不是必须的,如果没有指定,那么容器将为其生成一个惟一的name。bean的class需要填写完整类名,即包名+类名的格式。bean的命名采用标准的Java命名约定,即小写字母开头,首字母大写间隔的命名方式。如accountManager、 accountService 、userDao及loginController,等等。假设现在我们已经配置好了bean,那么该如何获取这些已经定义好的bean呢,前面讲过这些bean都放在了SpringIOC容器,这时我们就需要实例化容器(实例化容器的过程也即启动容器的过程),就像我们的洗衣机一样,如果我们只把衣服放进洗衣机而没有接通电源按下启动开关洗衣机是不会帮我们洗的,实例化容器的过程非常简单,可以看到,ClassPathXmlApplicationContext 传递的是一个数组,我们可以同时启动多个容器。
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"beans.xml"});

采用这种方式实例化容器,需要将spring的配置文件放到当前项目的classpath路径下,classpath路径指的是当前项目的src目录,该目录是java源文件的存放位置。除了这种方式,我们也可以在文件系统路径下寻找配置文件来实例化容器

ApplicationContext ctx = new FileSystemXmlApplicationContext(new String[]{“d:\\beans.xml“});

同样Spring的配置文件也可以指定多个,可以通过String数组传入。我们通常使用的是第一种方式来启动容器。很多时候,由于Spring需要管理和配置的东西比较多,如果都放在一个配置文件中,配置文件会变的比较大,同时不方便与维护,一般好的做法是按照功能模块将Spring配置文件分开,例如:DAO层配置到一个spring-dao.xml配置文件中,Service层配置到spring-service.xml文件中,Struts的action配置到spring-action.xml文件中,然后通过下面的办法将这些分散的配置文件组合起来:

在一个作为Spring总配置文件中的<bean>元素定义之前,通过<import>元素将要引入的spring其他配置文件引入,例如:

<beans>
       <import resource=”spring-dao.xml”/>
       <import resource=”spring-service.xml”/>
       <import resource=”spring-action.xml”/>
       ……
       <bean>
       </bean>
       ……
</beans>
这样我们只需引入一个配置文件即可,启动了容器之后,我们就可以在代码中获取我们所需的bean了,获取bean的方式同样也很简单,像下面这样

ctx.getBean("bean");
这里的bean指的是配置文件中bean的id值,这时我们就可以调用这个类来为我们工作了。在SpringIOC容器还可以为bean设置别名,就像我们除了有一个身份证上面的名字之外,通常还会有一个小名,像下面这样为bean配置一个别名toName,这时就可以用toName来引用id名为fromName的bean。

<bean id="fromName" class="...">
<alias name="fromName" alias="toName"/>
则下面的两行代码的效果是一样的

ctx.getBean("fromName");
ctx.getBean("toName");
通过一个简单的例子感受一下SpringIOC功能,首先我们有一个bean

清单1:
public class Person {
    public void say() {
        System.out.println("Hello Spring");
    }
}
接下来编写Spring的配置文件,通过<bean />标签来让SpringIOC容器来管理我们定义的Person bean
<?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-2.5.xsd">
  <bean id="person" class="org.spring.Person">
  </bean>
</beans>
编写一个测试用例
清单2:
public class IOCTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = (Person) context.getBean("person");
        person.say();
    }
}
这里用到了Spring核心类库,需要在工程中导入Spring的核心包spring.jar,该jar在dist文件夹目录下,运行test方法,测试控制台出现如下错误
java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
at org.springframework.context.support.AbstractApplicationContext.<init>(AbstractApplicationContext.java:146)
at org.springframework.context.support.AbstractRefreshableApplicationContext.<init>(AbstractRefreshableApplicationContext.java:84)
at org.springframework.context.support.AbstractRefreshableConfigApplicationContext.<init>(AbstractRefreshableConfigApplicationContext.java:59)
at org.springframework.context.support.AbstractXmlApplicationContext.<init>(AbstractXmlApplicationContext.java:58)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:136)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
可以看出引起错误的原因是缺少了日志包,这时我们引入lib\jakarta-commons\commons-logging.jar包,再次运行test方法,测试用例完美运行,控制台输出Hello Spring。在这里我们并没有显示的new一个person类,而是从容器中拿出一个bean直接就可以用了。

实例化Bean的方式

1、使用类构造器实例化

前面实例化bean的方式就叫类构造器实例化,只要在配置文件中指定bean的class属性即可,同时需要bean提供一个默认的构造器
<bean id="person" class="org.spring.Person">

2、使用静态工厂方法实例化

当采用静态工厂方法创建bean时,除了需要指定class 属性外,还需要通过factory-method属性来指定创建bean实例 的工厂方法。Spring将调用此方法返回实例对象,在此例中, createStudentBean()必须是一个static方法。
public class Student {
    
}
public class StudentStaticFactory {
    public static Student createStudentBean() {
        return new Student();
    }
}
<bean id="student" class="org.spring.initbean.StudentStaticFactory" factory-method="createStudentBean" />

3、使用实例工厂方法实例化

与使用静态工厂方法实例化类似,用来进行实例化的非静态实例工厂方法位于另外一个bean中,容器将调用该bean的工厂方法来创建一个新的bean实例。为使用此机制,class属性必须为空,而factory-bean 属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,该工厂bean的工厂方法本身必须通过factory-method属性来设定。
public class Student {
    
}
public class StudentInstanceFactory {
    public Student createStudentBean() {
        return new Student();
    }
}
<bean id="studentInstanceFactory" class="org.spring.initbean.StudentInstanceFactory" />
<bean id="student" factory-bean="studentInstanceFactory" factory-method="createStudentBean" />

Bean的延迟加载:
       默认情况下,Spring在启动时将所有作用域为singleton的bean提前进行实例化。提前实例化意味着作为初始化的一部分,ApplicationContext会自动创建并配置所有的singleton bean,通常情况下这是件好事,因为这样在配置中有任何错误能立即发现。但是假设该bean中有一个集合的属性,并且这个集合中还含有大量的数据,则Spring容器启动时需要加载大量的数据到内存中,比较不好的情况是我们根本不需要用到这些数据,却要加载这些数据,导致占用了大量的内存,有时候这种默认的处理可能并不适合我们,如果不想让一个singleton bean在容器启动时被提前初始化,则可以将该bean设置为延迟实例化,这种方式在bean第一次被用到时才初始化,延迟初始化将通过<bean/>元素中的lazy-init属性来进行控制,如将上述中的person bean设置为延迟初始化
<bean id="person" class="org.spring.Person" lazy-init="true" />
配置之后Person bean在执行context.getBean("person");时才会被初始化,而不是容器启动就进行初始化
如果想对所有bean都应用延迟初始化,可以在根节点beans设置default-lazy-init="true",如下:
<beans  default-lazy-init="true"   ...>
那么如何知道该bean是什么时候创建的呢,我们在Person类中显示地添加一个默认的构造器
清单3:
public class Person {
    public Person() {
        System.out.println("构造器");
    }
    
    public void say() {
        System.out.println("Hello Spring");
    }
}
测试代码
public class IOCTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");        
    }
}
这段代码表示初始化Spring容器,也即启动容器,执行代码之后控制台并没有打印任何信息,而当代码修改如下时
public class IOCTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = (Person) context.getBean("person");
    }
}
控制打印出构造器字符串,可知为bean设置了延迟加载之后,只有在第一次获取bean时,容器才帮我们实例化bean,延迟加载的缺陷是spring容器启动的时候,我们无法检验出错误。

Bean的作用域

       前面提到Spring默认在启动时将所有作用域为singleton的bean提前进行实例化,这里提到bean的作用域范围,在Spring框架中,支持五种作用域,其中有三种只能用在基于web的Spring ApplicationContext,
singleton:在每个Spring IoC容器中一个bean定义对应一个对象实例。
prototype:一个bean定义对应多个对象实例。
request:在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的SpringApplicationContext情形下有效。
session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global session:在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。
bean的作用域通过<bean />标签的scope属性来配置

singleton作用域:

       当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。换言之,当把一个bean定义设置为singlton作用域时,Spring IoC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例。


prototype作用域:

       prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。下图演示了Spring的prototype作用域。请注意,通常情况下,DAO不会被配置成prototype,因为DAO通常不会持有任何会话状态,因此应该使用singleton作用域。


需要注意的是,spring不能对一个prototype bean的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。这就意味着清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。当spring容器中的bean的作用域为prototype时,则不管配置文件中的lazy-init为default、false还是true,在获取bean时才为bean创建对象。

下面再次利用上面的例子感受一下这两种作用域,这里为了方便阅读,重新贴代码,并在此基础上进行改造
首先当bean的作用为singleton时
<bean id="person" class="org.spring.Person" scope="singleton"/>
在测试代码中获取两次获取person bean,并测试这两个bean是否为同一个
清单4:
public class IOCTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = (Person) context.getBean("person");
        Person person2 = (Person) context.getBean("person");
//        person.say();
        System.out.println(person == person2);
    }
}
清单5:
Person类
public class Person {
    public Person() {
        System.out.println("构造器");
    }
    
    public void say() {
        System.out.println("Hello Spring");
    }
}
运行清单4,控制台输出:
构造器
true
说明我们两次获取到的person是同一个实例,从只打印一次构造器也可以知道person只初始化了一次

接下来我们把scope作用域设置为prototype
<bean id="person" class="org.spring.Person" scope="prototype"/>
这时控制台打印的结果为:
构造器
构造器
false
结果打印了false,并且调用了两次构造器,可以得知prototype作用域的bean在每次调用该bean时,都会被实例化一次。

init方法与destroy方法

       有时候我们需要在Spring初始化bean或销毁bean(容器关闭时)时,作一些处理工作,spring可以在创建和拆卸bean的时候调用bean的两个生命周期方法。例如我们人在吃饭之前都需要洗手,吃完饭后需要擦一下嘴巴,则这时person类为
public class Person {
    public Person() {
        System.out.println("构造器");
    }
    
    /*public void say() {
        System.out.println("Hello Spring");
    }*/
    
    public void init() {
        System.out.println("washing......");
    }
    
    public void eat() {
        System.out.println("eatting......");
    }
    
    public void destroy() {
        System.out.println("wape mouth.....");
    }
}

在配置文件中指定init-methoddestroy-method,如
<bean id="person" class="org.spring.Person" init-method="init" destroy-method="destroy" scope="singleton"/>
初始化的方法会在构造器执行完毕之后执行,而销毁容器的工作需要我们显示调用destroy方法
清单6:
public class IOCTest {
    @Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = (Person) context.getBean("person");
        person.eat();
        context.destroy();
    }
}

执行代码清单6:
构造器
washing......
eatting......
wape mouth.....

注意:destory-method在scope=singleton有效,这个读者可以自行测试一下,最后一句wape mouth.....将不会打印。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值