(SSH学习笔记一)Spring之IOC容器的认识

IOC容器是Spring框架中的核心功能之一,还有一个就是AOP(面向切面编程)。

IOC全称Inversion of Control,也就是控制反转。那么问题来了:是对什么的控制反转?之前是如何控制,现在又是怎样控制的呢?带着这些问题,我们往下走。

搭建一个简单的项目:

项目结构:

(SSH学习笔记一)Spring之IOC容器的认识

在Service提供了一个create方法:

public class IocServiceImpl implements IocService {

    @Override
    public void create(String name) {
        System.out.println("service create:"+name);
    }
    
}

现在,我们想在action里面调用这个方法,怎么做呢?

我们要调用一个类中的非静态方法,首先需要获取这个类的对象,然后通过这个对象来调用里面的方法。

在认识Spring之前,我们一般都是直接通过new的方式来获取对象:

public class IocAction {

    public static void main(String[] args) {
        IocService iocService = new IocServiceImpl();
        iocService.create("spring");
    }
}

输出:

(SSH学习笔记一)Spring之IOC容器的认识

或者也可以通过工厂来获取对象:

public class MyObjectFactory {

    public static IocService getIcoService() {
        return new IocServiceImpl();
    }
}

-------------------------------

public class IocAction {

    public static void main(String[] args) {
        IocService iocService = MyObjectFactory.getIcoService();
        iocService.create("spring");
    }
}

同样也可以正常调用service中的方法。

上面获取的对象都有一个共同点就是:对象由程序本身创建并管理,也就是对象的生命周期由程序本身来控制。

IOC的简单使用

IOC恰恰相反,它提供了一个统一管理对象的容器,将所有程序调用的对象集中起来统一管控,程序只需要跟IOC容器说一声需要什么什么什么,然后IOC将创建好的对象提供给程序,直观上给人的感觉就是角色发生了反转,也就是所谓的控制反转。简单实现以下:

将需要的jar包放在lib目录下:

spring-beans-4.3.13.RELEASE.jar
spring-context-4.3.13.RELEASE.jar
spring-context-support-4.3.13.RELEASE.jar
spring-core-4.3.13.RELEASE.jar
spring-expression-4.3.13.RELEASE.jar

新建一个Spring配置文件applicationContext.xml放于src目录下:

applicationContext.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
       <!--装配管理的对象类-->
       <bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl"></bean>
</beans>

public class IocAction {

    public static void main(String[] args) {
        //装载配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        IocService iocService = (IocService) ac.getBean("iocService");
        iocService.create("spring");
    }
}

启动,报错:

(SSH学习笔记一)Spring之IOC容器的认识

缺少日志jar包,引入commons-logging-1.2.jar,重新启动,正常运行:

(SSH学习笔记一)Spring之IOC容器的认识

上面就是IOC的简单调用对象方式,在这里,需要思考一个问题:IOC提供的对象是在状态配置文件的时候实例化的呢还是调用的时候实例化的呢?

验证方式很简单,既然是实例化,肯定会走的一个方法就是类的构造方法,验证如下:

public class IocServiceImpl implements IocService {

    public IocServiceImpl() {
        //实例化类对象后执行
        System.out.println("IocService create!");
    }
    
    @Override
    public void create(String name) {
        System.out.println("service create:"+name);
    }
    
}
 

然后再IocAction中修改下:

public class IocAction {

    public static void main(String[] args) {
        //装载配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("-------------分割线------------");
        IocService iocService = (IocService) ac.getBean("iocService");
        iocService.create("spring");
    }
}

我们只需要看下构造方法输出的内容在分割线上还是下方即可确定对象创建的时间。执行:

(SSH学习笔记一)Spring之IOC容器的认识

从上面的输出结果可以看出来,对象实例化是在调用之前,也就是在装载配置文件时实例化的。这种实现方式有个问题就是:随着程序调用的对象越来越多,配置文件中配置越来越多的对象,如果都在装载配置文件时给其实例化,程序启动时会越来越慢。

lazy-init 延迟加载

如何让对象在调用的时候实例化而不是装载配置文件的时候实例化呢?这就需要用到bean里面的一个属性lazy-init:

<bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl" lazy-init="true"></bean>

按上修改后,执行:

(SSH学习笔记一)Spring之IOC容器的认识

可以看出来,对象改为在调用的时候实例化了。这种放在bean标签内的懒加载方式,又存在另一个不是问题的问题:如果我所有的类对象都让其延迟加载,总不能一个个都给加上这个属性吧?spring肯定不会不考虑这一点,在<beans>这个大标签上,也有一个属性:default-lazy-init

<beans default-lazy-init="true" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
       
       <bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl"></bean>
</beans>

执行结果:

(SSH学习笔记一)Spring之IOC容器的认识

上面简单介绍了IOC的基本机制以及对象的使用,

scope作用域

现在再思考一个问题:IOC容器创建的对象是单例的还是多例呢?

验证下:

public class IocAction {

    public static void main(String[] args) {
        //装载配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        IocService iocService = (IocService) ac.getBean("iocService");
        IocService iocService2 = (IocService) ac.getBean("iocService");
        System.out.println(iocService == iocService2);
        System.out.println(iocService.equals(iocService2));
    }
}

执行结果:(==比较值,equals比较的是内存地址)

true
true

由此可见,IOC默认提供的对象是单例模式。 如果不想要单例模式,希望每次调用时都是一个新的对象,怎么做呢?这就需要bean的另一个属性:scope(作用域),默认是单例模式,即scope="singleton"。另外scope还有prototype、request、session、global session作用域。

(1)scope="prototype"多例,每次调用都获取一个新的对象

<bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl" scope="prototype"></bean>

上面的执行结果变为:

false
false

(2)scope="request" 在同一个请求里面单例,即在同一个请求里面无论对一个类访问多少次,都用同一个对象;

(3)scope="session" 在同一个会话内单例,即在同一个会话里面无论对一个类访问多少次,都用同一个对象;

(4)scope="global session" 在同一个全局会话内单例

init-method与destory-method

这两个是bean中的初始化调用方法以及销毁时调用方法。现在先思考两个问题:初始化方法的调用顺序在构造方法调用之前还是之后?destory-method的调用是在对象销毁之前还是之后?

对于第二个问题,我们很清楚一点,对象都销毁了还怎么调用destory方法?也没法测试,需要个人理解下。

对于第一个问题,通过下面代码来测试下:

<bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl" init-method="init"></bean>

public class IocServiceImpl implements IocService {

    public IocServiceImpl() {
        //实例化类对象后执行
        System.out.println("IocService create!");
    }
    
    public void init() {
        System.out.println("IocService init!");
    }
    @Override
    public void create(String name) {
        System.out.println("service create:"+name);
    }
    
}
执行结果:

IocService create!
IocService init!
service create:spring

可见初始化方法在构造方法之后调用,在内部其他方法被调用之前。

方法执行顺序:

构造方法>初始化方法>其他供外部调用方法>destory-method方法>销毁对象

parent

bean里面的parent属性指的是当子bean没有指明class而父bean指明了之后,那么子bean的class就跟父bean的class一样;子bean中必须包含父bean的全部属性,且子bean可以获得父bean中的所有属性的值。

依赖注入(DI)

依赖注入(DI)是与IOC容器配合使用的,IOC容器创建好程序需要的对象,DI(依赖注入)将对象注入进需要用它的地方。

依赖注入的方式有三种:Setter注入跟Constructor(构造器)注入以及静态工厂注入

参考:https://blog.csdn.net/huyuyang6688/article/details/52022311

Setter注入

顾名思义,是通过set方法将对象注入到需要的地方,见代码:

配置文件内容:

<bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl">
               <property name="iocDao" ref="iocDao"></property>
       </bean>
       <bean id="iocDao" class="com.sts.javaIoc.dao.impl.IocDaoImpl">
 </bean>

Action内容:

public class IocAction {

    public static void main(String[] args) {
        //装载配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        IocServiceImpl iocService =ac.getBean(IocServiceImpl.class);
        iocService.create("spring");
    }
}

service层:

public class IocServiceImpl implements IocService {

    private IocDao iocDao;
    
    public void setIocDao(IocDaoImpl iocDao) {
        this.iocDao = iocDao;
    }
    
    public IocServiceImpl() {
        //实例化类对象后执行
        System.out.println("IocService create!");
    }
    
    
    public void init() {
        System.out.println("IocService init!");
    }
    @Override
    public void create(String name) {
        iocDao.create(name);
    }
    
}

dao层:

public class IocDaoImpl implements IocDao {
    
    public IocDaoImpl() {
        System.out.println("IocDaoImpl create");
    }
    
    @Override
    public void create(String name) {
        System.out.println("IocDaoImpl name:"+name);
    }
}

执行输出:

IocService create!
IocDaoImpl create
IocDaoImpl name:spring

在service层中需要调用dao层对象iocDao,可以按照action中的方式,装载配置文件通过bean名字或类型来获取,但是这种方式就会有一个问题:每个类需要调用其他类对象,都要重新装载一遍配置文件,每次装载配置文件每个类都得提供实例化对象,会浪费很多内存空间,而是用注入的方式,比如当前使用的Setter注入,通过set方法来获取对象,从头到尾只装载了一次配置文件。

setter注入方法名为配置文件中的name值首字母大写前面加上set,其他名字均不可找到。

构造器注入

构造器注入是通过类的构造器来实例化对象:

配置文件修改:

<bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl">
               <!-- <property name="iocDao" ref="iocDao"></property> -->
               <constructor-arg name="iocDao" ref="iocDao"></constructor-arg>
       </bean>
       <bean id="iocDao" class="com.sts.javaIoc.dao.impl.IocDaoImpl">
       </bean>

service修改:

public class IocServiceImpl implements IocService {

    private IocDao iocDao;
    
    public IocServiceImpl(IocDao iocDao) {
        this.iocDao = iocDao;
    }
    
    
    public void init() {
        System.out.println("IocService init!");
    }
    @Override
    public void create(String name) {
        iocDao.create(name);
    }
    
}
执行结果:

IocDaoImpl create
IocDaoImpl name:spring

当构造器有多个参数时,注入值如下:

配置文件:

<bean id="iocService" class="com.sts.javaIoc.service.impl.IocServiceImpl">
               <!-- <property name="iocDao" ref="iocDao"></property> -->
               <constructor-arg name="iocDao" ref="iocDao"></constructor-arg>
               <constructor-arg name="age" value="10"></constructor-arg>
       </bean>
       <bean id="iocDao" class="com.sts.javaIoc.dao.impl.IocDaoImpl">
       </bean>

service:

public class IocServiceImpl implements IocService {

    private IocDao iocDao;
    
    private int age;
    
    public IocServiceImpl(IocDao iocDao , int age) {
        this.iocDao = iocDao;
        this.age = age;
    }
    
    @Override
    public void create(String name) {
        System.out.println(iocDao);
        System.out.println(age);
    }
    
}
输出:

IocDaoImpl create
[email protected]
10

总结

IOC容器是Spring中非常重要的一部分,它的出现大大减少了代码编写量,降低了类之间的耦合性,减少了开发时间,加快了开发进度。