Spring的IOC

本文深入探讨了控制反转(IOC)的设计思想,及其在Spring框架中的应用。详细讲解了IOC如何通过依赖注入(DI)降低代码耦合度,提高程序灵活性。通过实例演示了Spring如何配置和管理Bean,实现对象的创建和依赖关系的动态注入。

IOC理论推导

1、没有Spring前,代码是怎么来完成的?

要写dao层的接口和实现类,再写service层的接口和实现类

  • UserDao 接口
  • UserDaoImpl 实现类
  • UserService 业务接口
  • UserServiceImpl 业务实现类

 

-- 之前,业务层要自己去创建dao层对象,才能调到新增的dao层的方法

举例:提供获取用户数据的功能

1)先写一个UserDao接口:com.nikey.dao.UserDao

package com.nikey.dao;

public interface UserDao {
    void getUser();
}

2)再去写Dao的实现类:com.nikey.dao.UserDaoImpl

package com.nikey.dao;

public class UserDaoImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("默认获取用户的数据");
    }
}

3)然后去写UserService的接口:com.nikey.service.UserService

package com.nikey.service;

public interface UserService {
    void getUser();
}

4)最后写Service的实现类:com.nikey.service.UserServiceImpl

package com.nikey.service;

import com.nikey.dao.UserDao;
import com.nikey.dao.UserDaoImpl;

public class UserServiceImpl implements UserService {
    
    // 业务层要自己创建dao层对象
    private UserDao userDao = new UserDaoImpl();

    @Override
    // 业务层调dao层去查用户
    public void getUser() {
        userDao.getUser();
    }
}

5、测试一下:创建一个测试方法MyTest

import com.nikey.dao.UserDaoImpl;
import com.nikey.service.UserServiceImpl;

public class MyTest {
    public static void main(String[] args) {
        // 用户实际调用的是业务层,dao层他们不需要接触
        UserServiceImpl userService = new UserServiceImpl();

        userService.setUserDao(new UserDaoImpl());
        userService.getUser();
        System.out.println("-------------------");
    }
}

测试结果:service层调用dao层的方法,dao层模拟获取数据库中的数据

 

现在需求有变化了,要变成使用mysql数据库来获取用户数据,需要修改以下文件:

把Userdao的实现类增加一个,新增mysql的dao层实现类:com.nikey.dao.UserDaoMysqlImpl

package com.nikey.dao;

//增加一个mysql获取用户数据的实现
public class UserDaoMysqlImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("Mysql获取用户数据");
    }
}

去service实现类里面修改对应的实现(修改为创建UserDaoMysqlImpl的对象):com.nikey.service.UserServiceImpl

UserServiceImpl修改后:private UserDao userDao = new UserDaoMysqlImpl();

UserServiceImpl修改前:private UserDao userDao = new UserDaoImpl();

package com.nikey.service;

import com.nikey.dao.UserDao;
import com.nikey.dao.UserDaoMysqlImpl;

public class UserServiceImpl implements UserService {

    // 修改为创建需要的类型的dao层实现类(如mysql,oracle,nosql等)
    private UserDao userDao = new UserDaoMysqlImpl();
    
    @Override
    // 业务层调dao层去查用户
    public void getUser() {
        userDao.getUser();
    }
}

运行测试方法,测试结果:(底层获取用户数据的数据源有变化了,service层实现类要修改,测试方法不需要修改)

 

如果需求又变化了,要使用oracle来实现,那么修改如下内容:

1)新增oracle的dao层的实现类

2)修改service层的实现类,去创建UserDaoOracleImpl的对象

这就是service层实现类,自己主动创建对象的弊端了,如果dao层实现类有变化,每次都要修改service层的实现类;

每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .

 

-- 现在如何去解决之前的问题呢 ?

要解决这个问题,从客户端去调用需要的实现类(默认的,mysql,oracle或nosql的),service层不需要每次都修改,可以在service层实现类中增加一个方法:public void setUserDao(UserDao userDao)

利用set进行动态实现值的注入,而不是每次主动new UserDaoMysqlImpl()

com.nikey.service.UserServiceImpl

package com.nikey.service;

import com.nikey.dao.UserDao;
import com.nikey.dao.UserDaoMysqlImpl;

public class UserServiceImpl implements UserService {

//    修改为创建需要的类型的dao层实现类(如mysql,oracle,nosql等)
//    private UserDao userDao = new UserDaoMysqlImpl();

    private UserDao userDao;

    // 利用set进行动态实现值的注入,而不是每次主动new UserDaoMysqlImpl()
    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    // 业务层调dao层去查用户
    public void getUser() {
        userDao.getUser();
    }
}

测试方法修改: userService.setUserDao(new UserDaoMysqlImpl());  

让客户端去选择,要使用new UserDaoImpl(),还是new UserDaoMysqlImpl()

import com.nikey.dao.UserDaoImpl;
import com.nikey.dao.UserDaoMysqlImpl;
import com.nikey.service.UserServiceImpl;

public class MyTest {
    public static void main(String[] args) {
        // 用户实际调用的是业务层,dao层他们不需要接触
        UserServiceImpl userService = new UserServiceImpl();

        userService.setUserDao(new UserDaoImpl());
        userService.getUser();
        System.out.println("-------------------");

        userService.setUserDao(new UserDaoMysqlImpl());
        userService.getUser();
    }
}

 

在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码,如果程序代码量十分大,修改一次的成本代价十分昂贵!

  • 之前,程序是主动创建对象,控制权在程序员手上;(用户的每一个需求,都要修改代码)
  • 使用了set注入后,程序不再具有主动性,而是变成了被动接受对象,把主动权交给了调用者 ;

这种思想,从本质上解决了问题,程序员不用再去管理怎么创建对象,怎么实现了,它只负责提供一个接口,系统的耦合性大大降低,可以更专注在业务实现上,这就是IOC原型

 

之前:

逻辑代码在业务层主动权在业务层,在程序员手上,不管增加多少dao层实现类,用户拿到的都是一样的;

 

以后:

主动权在用户,用户选择去调用哪里;

 

2、IOC本质

  • 控制反转IOC(Inversion of Control),是一种设计思想;
  • DI(依赖注入)是实现IOC的一种方法;
  • 没有IOC的程序中,使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制;
  • 控制反转后,将对象创建转移给第三方,即获得依赖对象的方式反转了;

图1中,对象A,B,C,D之间互相有关系,一层调一层

图2中,IOC容器连接对象A,B,C,D,使他们之间进行了解耦,不在有强耦合性

图3中,就实现了用户想要去调谁,就指定调谁就可以了

 

上层(service层)和下层(dao层)之间设计了一个接口(setUserDao(UserDao userDao)),通过接口去调用,就没有强联系性了。

    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }
  • IOC是Spring框架的核心内容,使用多种方式完美的实现了IOC;
  • 可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC;
  • Spring容器在初始化时,先读取配置文件,根据配置文件或元数据,创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象;

 

  • 采用XML方式配置Bean的时候,Bean定义信息是和实现分离的;
  • 采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

 

控制反转,是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式;

在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

 

HelloSpring

导入Jar包

注 : spring 需要导入commons-logging进行日志记录.

1、编写一个Hello实体类

package com.nikey.pojo;

public class Hello {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}

2、编写spring文件 , 这里自己命名为beans.xml

Idea中:New-->XML Configuration File-->Spring Config-->bean.xml

 

类型 变量名 = new 类型();

Hello hello = new Hello();

  • bean 等价于对象,相当于上面Hello hello = new Hello()过程;
  • 其中,id等价于变量名hello,class等价于要 new的对象的类型(全路径名)
  • property相当于给对象中的属性设置一个值
  • 通过这种方式,来new一个对象(交给容器去做了),只要配置就可以,不需要主动去创建对象了;
<?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就是java对象 , 由Spring创建和管理-->
    <bean id="hello" class="com.nikey.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>
</beans>

3、进行测试

import com.nikey.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        //解析beans.xml文件,生成管理相应的Bean对象,配置文件可以传入多个,逗号分隔
        //获取Spring上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        //我们的对象现在都在Spring中管理了,要使用,直接去里面取出来就可以
        //getBean : 参数即为spring配置文件中bean的id .
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

测试结果:

4、思考问题:

  • Hello 对象是谁创建的 ?  ---hello 对象是由Spring创建的
  • Hello 对象的属性是怎么设置的 ?  ---hello 对象的属性是由Spring容器设置的

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的

  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

IOC是一种编程思想,由主动的编程变成被动的接收;

可以通过newClassPathXmlApplicationContext去浏览一下底层源码

 

依赖注入 : 就是利用set方法来进行注入的;

如果把Hello类中的setStr(String str)方法去掉,bean.xml就会报错,核心就是通过set方法,去完成注入设值的;

public void setStr(String str) {
        this.str = str;
    }

 

要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,即对象由Spring来创建,管理,装配

 

修改案例一

实现类的对象的创建,装配交给Spring来管理了;

在案例一中, 新增一个Spring配置文件bean-01-ioc1.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.xsd">

    <!--bean就是java对象 , 由Spring创建和管理-->
    <bean id="userDaoImpl" class="com.nikey.dao.impl.UserDaoImpl"/>
    <bean id="userDaoMysqlImpl" class="com.nikey.dao.impl.UserDaoMysqlImpl"/>
    <bean id="userDaoOracleImpl" class="com.nikey.dao.impl.UserDaoOracleImpl"/>

    <bean id="userServiceImpl" class="com.nikey.service.impl.UserServiceImpl">
        <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
        <!--引用另外一个bean , 不是用value 而是用 ref-->
        <!--ref:引用Spring容器中创建好的对象-->
        <!--value: 具体的值,基本数据类型-->
        <property name="userDao" ref="userDaoImpl"/>
    </bean>
</beans>

测试:

import com.nikey.service.impl.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyIoc1Test {
    public static void main(String[] args) {
        // 获取ApplicationContext:拿到Spring 的容器
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-01-ioc1.xml");

        // 需要什么对象,就getBean获取什么
        UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");

        // 需要获取什么类型的UserDao实现类,在bean.xml配置文件中修改property的ref
        userServiceImpl.getUser();

    }
}

 

如果需要修改UserDao的实现类为mysqloracle的实现类,只需要修改Spring配置文件bean.xml中property的ref值即可;

测试类都不需要任何修改,再运行测试类,测试结果就改变了;

修改前:<property name="userDao" ref="userDaompl"/>

修改后:<property name="userDao" ref="userDaoOracleImpl"/>

    <bean id="userServiceImpl" class="com.nikey.service.impl.UserServiceImpl">
        <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
        <!--引用另外一个bean , 不是用value 而是用 ref-->
        <!--ref:引用Spring容器中创建好的对象-->
        <!--value: 具体的值,基本数据类型-->
        <property name="userDao" ref="userDaoOracleImpl"/>
    </bean>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值