Spring IOC详解

一,什么是耦合

1、耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。

2、在软件工程中,对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高,维护成本越高,因此对象的设计应使类和构件之间的耦合最小。

程序的耦合

我们创建一个基于三层架构的项目Spring

新建三层架构的包(持久层)dao,(业务层)service,(表现层)web

/**
 * 持久层实现类
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser(){
        System.out.println("insert into tb_user......");
    }
}
/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {
    //硬编码:此处有依赖关系
    private UserDao userDao = new UserDaoImpl();

    public void addUser(){
        userDao.addUser();
    }
}
/**
 * 模拟表现层
 */
public class Client {
    public static void main(String[] args) {
        //硬编码:此处有依赖关系
        UserService userService = new UserServiceImpl();
        userService.addUser();
    }
}

        上边的代码service层在依赖dao层的实现类,此时如果更改dao了层的实现类或此时没有dao层实现类,编译将不能通过。这里就是因为程序之间的高度耦合导致的,我们需要用到IOC来解耦

二,什么是IOC

        IOC (Inverse of Control)即控制反转:由ioc容器来创建依赖对象,程序只需要从IOC容器获取创建好的对象

原来:

        我们在获取对象时,都是采用new的方式。是主动的。

现在:

我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。

这种被动接收的方式获取对象的思想就是控制反转,它是spring框架的核心之

三,IOC(工厂模式)解耦:

  1. 把所有的dao和service对象使用配置文件配置起来

  2. 当服务器启动时读取配置文件

  3. 把这些对象通过反射创建出来并保存在容器中

  4. 在使用的时候,直接从工厂拿

工厂模式方法一

我们创建一个BeanFactory_v1工厂

/**
 * bean工厂
 */
public class BeanFactory_v1 {

    /**
     * 获得UserServiceImpl对象
     * @return
     */
    public static UserService getUserService(){
        return new UserServiceImpl();
    }

    /**
     * 获得UserDaoImpl对象
     * @return
     */
    public static UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

在UserServiceImpl类中

public class UserServiceImpl implements UserService {
    //高耦合:硬编码(类与接口有很高的依赖性)
    //private UserDao UserDao = new UserDaoImpl();

    //工厂1
    private UserDao userDao=BeanFactory_v1.getUserDao();
    
    @Override
    public void addUser() {
        userDao.addUser();
    }
}

 在Client类中

public class Client {
    public static void main(String[] args) {
        //高耦合:硬编码(类与接口有很高的依赖性)
        //UserService userService = new UserServiceImpl();

        //工厂1
        UserService userService= BeanFactory_v1.getUserService();
        userService.addUser();
       
    }
}

问题:我们在开发中会有很多个service和dao,此时工厂类就要添加无数个方法。

工厂模式方法二

配置要使用的dao和service

在resources中配置bean.properties

UserDao=com.by.dao.UserDaoImpl
UserService=com.by.service.UserServiceImpl

我们创建一个BeanFactory_v2工厂

//问题:我们在开发中会有很多个service和dao,此时工厂类就要添加无数个方法。
public class BeanFactory_v2 {
    public static Object getBean(String propertiesKey) {
        try {
            /***********1、读取beans.properties***********/
            //不能使用:项目发布后没有src目录
            //InputStream inputStream=new FileInputStream("src\\main\\resources\\beans.properties");
            InputStream inputStream = BeanFactory_v2.class.getClassLoader().getResourceAsStream("beans.properties");
            //Properties:继承了Hashtable,作用是读取*.properties文件
            Properties properties = new Properties();
            properties.load(inputStream);
            /*************2、根据配置文件中的value值创建(使用反射)对象************/
            return Class.forName(properties.getProperty(propertiesKey)).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        UserService userService = (UserService) BeanFactory_v2.getBean("userService");
        userService.addUser();
    }
}

 在UserServiceImpl类中

public class UserServiceImpl implements UserService {
    //高耦合:硬编码(类与接口有很高的依赖性)
    //private UserDao UserDao = new UserDaoImpl();

    //工厂1
   //private UserDao userDao=BeanFactory_v1.getUserDao();

    //工厂2
    private UserDao userDao= (UserDao) BeanFactory_v2.getBean("UserDao");

    @Override
    public void addUser() {
        userDao.addUser();
    }
}

 在Client类中

public class Client {
    public static void main(String[] args) {
        //高耦合:硬编码(类与接口有很高的依赖性)
        //UserService userService = new UserServiceImpl();

        //工厂1
        //UserService userService= BeanFactory_v1.getUserService();

        //工厂2
        UserService userService = (UserService) BeanFactory_v2.getBean("UserService");

        userService.addUser();
        for (int i = 0; i < 5; i++) {
            UserService userServicei = (UserService) BeanFactory_v2.getBean("UserService");
            System.out.println(userServicei);
        }
    }
}

测试结果

问题:1.每次都会创建新的对象 2.获得对象时才读取配置文件

工厂模式方法三

我们创建一个BeanFactory_v3工厂

//问题:1.每次都会创建新的对象   2.获得对象时才读取配置文件
public class BeanFactory_v3 {
    private static Map<String,Object> iocMap=new HashMap<String, Object>();
    static {
        try {
            //解决2.获得对象时才读取配置文件
            //InputStream inputStream=new FileInputStream("src\\\\main\\\\resources\\\\beans.properties");
            InputStream inputStream = BeanFactory_v3.class.getClassLoader().getResourceAsStream("beans.properties");
            //Properties:继承了Hashtable,作用是读取*.properties文件
            Properties properties = new Properties();
            properties.load(inputStream);
            //解决1.每次都会创建新的对象:在静态代码块创建对象并装到Map中,每次调用getBean()都直接从Map中获取对象而无需再重新创建了
            Set<Object> keySet=properties.keySet();
            for (Object key : keySet) {
                String propertiesValue = properties.getProperty((String) key);
                iocMap.put((String) key,Class.forName(propertiesValue).newInstance());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Object getBean(String propertiesKey){
        return iocMap.get(propertiesKey);
    }

    public static void main(String[] args) {
        UserService userService = (UserService) BeanFactory_v3.getBean("UserService");
        userService.addUser();
    }
}

 在UserServiceImpl类中

public class UserServiceImpl implements UserService {
    //高耦合:硬编码(类与接口有很高的依赖性)
    //private UserDao UserDao = new UserDaoImpl();
    //工厂1
   //private UserDao userDao=BeanFactory_v1.getUserDao();
    //工厂2
    //private UserDao userDao= (UserDao) BeanFactory_v2.getBean("UserDao");
    //工厂3
    private UserDao userDao= (UserDao) BeanFactory_v3.getBean("UserDao");
    @Override
    public void addUser() {
        userDao.addUser();
    }
}

 在Client类中

public class Client {
    public static void main(String[] args) {
        //高耦合:硬编码(类与接口有很高的依赖性)
        //UserService userService = new UserServiceImpl();

        //工厂1
        //UserService userService= BeanFactory_v1.getUserService();

        //工厂2
        //UserService userService = (UserService) BeanFactory_v2.getBean("UserService");

        //工厂3
        UserService userService = (UserService) BeanFactory_v3.getBean("UserService");
        userService.addUser();
        for (int i = 0; i < 5; i++) {
            UserService userServicei = (UserService) BeanFactory_v3.getBean("UserService");
            System.out.println(userServicei);
        }
    }
}

测试结果 

工厂模式解耦项目结构

四,Spring的IOC解决程序耦合      

        在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。

        所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。

创建工程Spring_IOC_Xml

添加项目依赖

<dependencies>
        <!-- Spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
    </dependencies>

 注意:Jar包彼此存在依赖,只需引入最外层Jar即可由Maven自动将相关依赖Jar引入到项目中。

spring-context依赖

核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。

  • spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。BeanFactory属于延时加载,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。

  • spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。与BeanFactory不同,ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。

  • spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。

/**
 * 持久层实现类
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser(){
        System.out.println("insert into tb_user......");
    }
}
/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {
    //此处有依赖关系
    private UserDao userDao = new UserDaoImpl();

    public void addUser(){
        userDao.addUser();
    }
}

在resources中配置applicationContext.xml

(注意:命名无限制,约定俗成命名有:spring-context.xml、applicationContext.xml、beans.xml )

<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--
        把service和dao交给spring的工程去创建,并装到IOC容器中:
            id="userDao" :map的key,必须保持唯一,用于获取对象
            class="com.by.dao.UserDaoImpl":全类名,是map的value,用于通过反射创建对象
    -->
    <bean id="userDao" class="com.by.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.by.service.UserServiceImpl">
</beans>
/**
 * 模拟表现层
 */
public class Client {
    public static void main(String[] args) {
        //是带有IOC容器(map)的工厂
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        /**************************测试ioc***************************/
        //UserDao userDao = (UserDao) ac.getBean("userDao"); //强转
        //UserDao userDao = ac.getBean(UserDao.class); //遍历map
        UserDao userDao = ac.getBean("userDao", UserDao.class);
        userDao.addUser();

        UserService userService = ac.getBean("userService", UserService.class);
        userService.addUser();

        //spring工厂帮我们创建的对象是否是单例?
        for (int i = 0; i < 5; i++) {
            UserService userServicei = ac.getBean("userService", UserService.class);
            System.out.println(userServicei);
        }

    }
}

测试结果

问题:service层仍然耦合

DI(依赖注入)

什么时DI

        Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
        我们的程序在编写时,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。通过容器只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。
        那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

DI原理


        所谓的依赖注入其实就是通过反射给成员变量赋值,这里的成员变量就是我们所说的依赖。比如TestController当中有个TestService对象,他需要依赖TestService对象当中的方法,那么TestService就是TestController的依赖。

构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。具体代码如下

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;
    private String msg;

    public UserServiceImpl(UserDao userDao, String msg) {
        this.userDao = userDao;
        this.msg = msg;
    }

    @Override
    public void addUser(){
        System.out.println(userDao+"---------"+msg);
        userDao.addUser();
    }
}

配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--
        把service和dao交给spring的工程去创建,并装到IOC容器中:
            id="userDao" :map的key,必须保持唯一,用于获取对象
            class="com.by.dao.UserDaoImpl":全类名,是map的value,用于通过反射创建对象
    -->
    <bean id="userDao" class="com.by.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.by.service.UserServiceImpl">
        <!--
            构造方法注入:要求必须提供构造方法
                 ==给谁赋值:==
                    name="userDao":构造方法中的参数名称
                    index="0":构造方法中的索引位置
                  ==赋什么值:==
                    ref="userDao":bean标签的id,注入的是ioc容器中的bean对象
                    value="你好":注入的是基本数据类型和String类型
        -->
        <constructor-arg name="userDao" ref="userDao"></constructor-arg>
        <constructor-arg index="1" value="你好"></constructor-arg>
    </bean>
</beans>

Client类

public class Client {
    public static void main(String[] args) {
        //是带有IOC容器(map)的工厂
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        /**************************测试ioc***************************/
        /*//UserDao userDao = (UserDao) ac.getBean("userDao"); //强转
        //UserDao userDao = ac.getBean(UserDao.class); //遍历map
        UserDao userDao = ac.getBean("userDao", UserDao.class);
        userDao.addUser();

        UserService userService = ac.getBean("userService", UserService.class);
        userService.addUser();

        //spring工厂帮我们创建的对象是否是单例?
        for (int i = 0; i < 5; i++) {
            UserService userServicei = ac.getBean("userService", UserService.class);
            System.out.println(userServicei);
        }*/

        /**************************测试DI***************************/
        UserService userService = ac.getBean("userService", UserService.class);
        userService.addUser();
    }
}

测试结果

set方法注入

顾名思义,就是在类中提供需要注入成员的set方法。具体代码如下:

配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--
        把service和dao交给spring的工程去创建,并装到IOC容器中:
            id="userDao" :map的key,必须保持唯一,用于获取对象
            class="com.by.dao.UserDaoImpl":全类名,是map的value,用于通过反射创建对象
    -->
    <bean id="userDao" class="com.by.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.by.service.UserServiceImpl" scope="prototype" init-method="init" destroy-method="destroy">
        <!--
           构造方法注入:要求必须提供构造方法
                ==给谁赋值:==
                   name="userDao":构造方法中的参数名称
                   index="0":构造方法中的索引位置
                 ==赋什么值:==
                   ref="userDao":bean标签的id,注入的是ioc容器中的bean对象
                   value="你好":注入的是基本数据类型和String类型

       -->
<!--        <constructor-arg name="userDao" ref="userDao"></constructor-arg>-->
<!--        <constructor-arg name="msg" value="你好"></constructor-arg>-->
<!--
            set方法注入:要求必须提供set方法
                ==给谁赋值:==
                name="userDao":pojo的属性名,userDao====>setUserDao
                ==赋什么值:==
                    ref="userDao":bean标签的id,注入的是ioc容器中的bean对象
                    value="set注入":注入的是基本数据类型和String类型
         -->
        <property name="userDao" ref="userDao"></property>
       <property name="msg" value="set注入"></property>

</beans>

测试结果

 自动注入

不用在配置中 指定为哪个属性赋值,由spring自动根据某个 "原则" ,在工厂中查找一个bean并为属性注入值。具体代码如下:

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void addUser(){
        userDao.addUser();
    }
}

配置applicationContext.xml 

方式一: byType

<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--2、把对象交给spring来创建-->
    <bean id="userDao" class="com.by.dao.UserDaoImpl"></bean>
        <!--autowire="byType":按照类型自动注入值-->
    <bean id="userService" class="com.by.service.UserServiceImpl" autowire="byType">
    </bean>

方式二: byName

<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--2、把对象交给spring来创建-->
    <bean id="userDao" class="com.by.dao.UserDaoImpl"></bean>
    <!--autowire="byName":按照名称自动注入值-->
    <bean id="userService" class="com.by.service.UserServiceImpl" autowire="byName">
    </bean>
</beans>

相关推荐

  1. SpringIoC原理

    2024-01-05 12:24:02       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-05 12:24:02       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-05 12:24:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-05 12:24:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-05 12:24:02       20 阅读

热门阅读

  1. 面试算法92:翻转字符

    2024-01-05 12:24:02       32 阅读
  2. Go语言断言和类型查询

    2024-01-05 12:24:02       38 阅读
  3. 16.Linux Bash Shell通过`read`命令读取用户输入

    2024-01-05 12:24:02       43 阅读
  4. python 堆栈

    2024-01-05 12:24:02       27 阅读
  5. Vue当中的observable是什么?怎么用

    2024-01-05 12:24:02       31 阅读
  6. UI自动化Selenium 页面窗口window定位切换

    2024-01-05 12:24:02       35 阅读
  7. DevOps(8)

    DevOps(8)

    2024-01-05 12:24:02      37 阅读