02 Spring_IOC 控制反转

目录

一、SpringIOC_控制反转思想

二、SpringIOC_自定义对象容器

三、SpringIOC_Spring实现IOC

四、SpringIOC_Spring容器类型

 1.容器接口(BeanFactory、ApplicationContext)

五、SpringIOC_对象的创建方式

1.使用无参构造方法

2.使用工厂类的普通方法构建对象

3.使用工厂类的静态方法构建对象

4.知识点整理:

六、SpringIOC_对象的五种创建策略

七、SpringIOC_对象的销毁时机:

八、SpringIOC_声明周期方法

九、SpringIOC_获取Bean对象的方式

1.通过id/name获取

2.通过类型获取

3.通过 id/name+类型 获取

4.整体代码段:


一、SpringIOC_控制反转思想

 1.IOC介绍:

IOC(Inversion of Control) :程序将创建对象的权利交给框架。

之前在开发过程中,对象实例的创建是由调用者管理的,代码如下:
public interface StudentDao {
    // 根据id查询学生
    Student findById(int id);
 } 

 public class StudentDaoImpl implements StudentDao{ 
    @Override
    public Student findById(int id) {
        // 模拟从数据库查找出学生
        return new Student(1,"百战程序员","北 京");
   }
 }

 public class StudentService {
    public Student findStudentById(int id){
        // 此处就是调用者在创建对象
        StudentDao studentDao = new StudentDaoImpl();
        return studentDao.findById(1);
   }
}

2.上述写法中有两个缺点:

(1)浪费资源: StudentService 调用方法时即会创建一个对象,如果不断调用方法则会创建大量StudentDao 对象。
(2)代码耦合度高:假设随着开发,我们创建了 StudentDao 另一个更加完善的实现类StudentDaoImpl2 ,如果在 StudentService中想使用StudentDaoImpl2 ,则必须修改源码。

IOC思想是将创建对象的权利交给框架,框架会帮助我们创建对象,分配对象的使用,控制权由程序代码转移到了框架中,控制权发生了反转,这就是SpringIOC 思想。 IOC 思想可以完美的解决以上两个问题。

二、SpringIOC_自定义对象容器

注意:(不需要记代码,只是模拟IOC思想,帮助理解)

接下来我们通过一段代码模拟 IOC 思想。创建一个集合容器,先将对象创建出来放到容器中,需要使用对象时,只需要从容器中获取对象即可,而不需要重新创建,此时容器就是对象的管理者。

 1.创建实体类

public class Student {
    private int id;
    private String name;
    private String address;
 // 省略getter/setter/构造方法/tostring
}

2.创建Dao接口和实现类

public interface StudentDao {
    // 根据id查询学生
    Student findById(int id);
}
public class StudentDaoImpl implements StudentDao{
    @Override
    public Student findById(int id) {
        // 模拟从数据库查找出学生
        return new Student(1,"百战程序员","北京");
   }
}
public class StudentDaoImpl2 implements StudentDao{
    @Override
    public Student findById(int id) {
        // 模拟根据id查询学生
        System.out.println("新方法!!!");
        return new Student(1,"百战程序员","北京");
   }
}

3.创建配置文件bean.properties,该文件中定义管理的对象

studentDao=com.itbaizhan.dao.StudentDaoImpl

4.创建容器管理类,该类在类加载时读取配置文件,将配置文件中配置的对象全部创建并放入容器中。

//容器类,负责管理对象,在类加载时读取配置文件并创建对象
public class Container {
    static Map<String,Object> map = new HashMap();

//    读取配置文件并创建对象
    static {
        InputStream is = Container.class.getClassLoader().getResourceAsStream("bean.properties");
        Properties properties = new Properties();
        try{
            properties.load(is);
        }catch (IOException e) {
            e.printStackTrace();
        }

//        遍历配置文件中的所有对象
        Enumeration<Object> keys = properties.keys();
        while (keys.hasMoreElements()){
            String key = keys.nextElement().toString();
            String value = properties.getProperty(key);
//            创建对象
            try {
                Object o = Class.forName(value).newInstance();
//                  把对象放入集合当中
                map.put(key,o);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
//    从容器中获取对象
    public static Object getBean(String key){
        return map.get(key);
    }
}

5.创建Dao对象的调用者StudentService

public class StudentService {
    public Student findStudentById(int id){
//        从容器中获取对象
        StudentDao studentDao = (StudentDao) Container.getBean("studentDao");
        System.out.println(studentDao.hashCode());
        return studentDao.findById(id);
    }
}

6.测试StudentService

public class Test {
    public static void main(String[] args) {
        StudentService studentService = (StudentService) Container.getBean("studentService");
        System.out.println(studentService.findStudentById(1));
        System.out.println(studentService.findStudentById(1));
    }
}

7.测试结果

8.测试结论:

(1)StudentService从容器中每次拿到的都是同一个StudentDao对象,节约了资源。

studentDao=com.itbaizhan.dao.StudentDaoImpl2
(2)如果想使用StudentDaoImpl2 对象,只需要修改bean.properties的内容即可,无需修改Java 源码。

 (3)在IOC思想中,将创建好的对象保存到“容器中”

三、SpringIOC_Spring实现IOC

 接下来我们使用Spring实现IOCSpring内部也有一个容器用来管理对象。

 1.创建Maven工程,引入依赖

<dependencies>
        <!--spring核心模块-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.13</version>
        </dependency>

        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
</dependencies>

2.创建POJO类、Dao类和接口

public class Student {
    private int id;
    private String name;
    private String address;
 // 省略getter/setter/构造方法/tostring
}
public interface StudentDao {
    // 根据id查询学生
    Student findById(int id);
}
public class StudentDaoImpl implements StudentDao{
    @Override
    public Student findById(int id) {
        // 模拟从数据库查找出学生
        return new Student(1,"百战程序员","北京");
   }
}

3.编写xml配置文件,配置文件中配置需要Spring帮我们创建的对象

<?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">
    <!--配置spring对象容器  id:为对象起名字  class:传入要配置的类对象的全类名 -->
    <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl"></bean>
</beans>

4.测试从Spring容器中获取对象

public class TestContainer {
    @Test
    public void t1(){
//        创建Spring容器
        /**
         * ApplicationContext:容器接口
         * ClassPathXmlApplicationContext:容器接口实现类,该类可以从项目中读取配置文件,传入相对路径
         * FileSystemXmlApplicationContext:容器接口实现类,该类从磁盘中读取配置文件,传入绝对路径
         */
         ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\txxiaoer\\Desktop\\SSM框架Demo\\springdemo\\spring_ioc1\\src\\main\\resources\\bean.xml");
//        从容器中获取对象
        StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao");
        StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao");
        System.out.println(studentDao1.hashCode());
        System.out.println(studentDao2.hashCode());
        System.out.println(studentDao1.findById(1));
    }
}

5.测试结果

 6.知识点整理:

(1)创建的studentDao1和studentDao2两个对象是同一个对象,并可以调用方法。

(2)Spring配置文件中,通过配置标签“<bean>”配置容器中的对象

(3)Spring配置文件中,<bean>标签的<id>属性为配置“对象名”,<class>属性可以配置“对象类型”。

四、SpringIOC_Spring容器类型

 1.容器接口(BeanFactory、ApplicationContext

(1)BeanFactory:BeanFactory是Spring容器中的顶层接口,它可以对Bean对象进行管理。

(2)ApplicationContext:ApplicationContext是BeanFactory的子接口。它除了继承BeanFactory的所有功能外,还添加了对国际化、资源访问、事件传播等方面的良好支持。(一般ApplicationContext用的比较多)

2.ApplicationContext有以下三个常用实现类:

(1) ClassPathXmlApplicationContext:该类可以从项目中读取配置文件,传入想对路径
(2) FileSystemXmlApplicationContext:该类可以从磁盘中读取配置文件,传入绝对路径
(3) AnnotationConfigApplicationContext:使用该类不读取配置文件,而是会读取注解

3.代码演示如:

public class TestContainer {
    @Test
    public void t1(){
//        创建Spring容器
        /**
         * ApplicationContext:容器接口
         * ClassPathXmlApplicationContext:容器接口实现类,该类可以从项目中读取配置文件,传入相对路径
         * FileSystemXmlApplicationContext:容器接口实现类,该类从磁盘中读取配置文件,传入绝对路径
         */
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\txxiaoer\\Desktop\\SSM框架Demo\\springdemo\\spring_ioc1\\src\\main\\resources\\bean.xml");
//        从容器中获取对象
        StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao");
        StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao");
        System.out.println(studentDao1.hashCode());
        System.out.println(studentDao2.hashCode());
        System.out.println(studentDao1.findById(1));
    }
}

五、SpringIOC_对象的创建方式

Spring 会帮助我们创建 bean ,那么它底层是调用什么方法进行创建的呢?

1.使用无参构造方法

Spring默认使用类的空参构造方法创建bean:
// 假如类没有空参构造方法,将无法完成bean的创建
public class StudentDaoImpl implements StudentDao{

// 设置一个有参的构造方法,默认去除了无参的构造方法
    public StudentDaoImpl(int a){}
    
    @Override
    public Student findById(int id) {
        // 模拟根据id查询学生
        return new Student(1,"百战程序员","北 京");
   }
}

 如上,IDEA会报红提示没有无参构造方法,不能通过编译。

2.使用工厂类的普通方法构建对象

Spring 可以调用工厂类的方法创建 bean
(1)创建工厂类,工厂类提供创建对象的方法:
public class StudentDaoFactory {
    public StudentDao getStudentDao(){

//return new StudentDaoImpl(1):返回StudentDaoImpl对象,利用有参构造方法  
        return new StudentDaoImpl(1);
   }
}

(2)在配置文件中配置创建bean的方式为工厂方式。

<!--2.利用普通工厂构建对象-->
        <!--id:为对象起名字(对象名)  class:传入要配置的类对象的全类名 -->
    <bean id="studentDaoFactory" class="com.itbaizhan.dao.StudentDaoFactory"></bean>
        <!--factory-bean:传入上面<bean>标签中<id>的对象名    factory-method:传入StudentFactory类中的方法名(获取对象的那个方法)-->
    <bean id="studentDao" factory-bean="studentDaoFactory" factory-method="getStudentDao"></bean>

3.使用工厂类的静态方法构建对象

Spring 可以调用工厂类的静态方法创建 bean
1
(1)创建工厂类,工厂提供创建对象的静态方法。
public class StudentDaoFactory2 {
    public static StudentDao getStudentDao(){
        return new StudentDaoImpl(1);
    }
}

(2)在配置文件中配置创建bean的方式为工厂静态方法。

<!--3.利用静态工厂构建对象-->
        <!--id:为对象起名字(对象名)  class:传入要配置的类对象的全类名   factory-method:传入工厂类的静态方法名-->
        <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoFactory2" factory-method="getStudentDao"></bean>

4.知识点整理:

(1)Spring不可以使用“有参构造方法”创建bean对象;可以使用“空参构造方法”“工厂方法”“静态工厂方法”创建bean对象。

(2)Spring调用工厂类的普通方法创建bean对象需要配置“工厂对象和被创建的对象”

六、SpringIOC_对象的五种创建策略

 Spring通过配置 <bean> 中的 scope 属性设置对象的创建策略,共有五种创建策略(其中singleton和prototype为两种常用的创建策略):

1.singleton:

单例,默认策略。整个项目只会创建一个对象,通过 <bean> 中的 lazy-init 属性可以设置单例对象的创建时机:

lazy-init="false"(默认) 立即创建,在容器启动时会创建配置文件中的所有Bean 对象。
lazy-init="true" 延迟创建,第一次使用 Bean 对象时才会创建。

(1)配置单例策略:

<!--配置spring对象容器  id:为全类名起名字  class:传入要配置的类的全类名
                        scope:创建策略(singleton:单例,整个项目只创建一个对象   prototype:多例,每次从容器中获取时都会创建对象)
                        lazy-init:false(默认)立即创建    true:第一次使用Bean对象时才会创建-->
    <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2" scope="singleton" lazy-init="false" ></bean>
<!--    <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean>-->

(2)测试单例策略:

public class StudentDaoImpl2 implements StudentDao{
    public StudentDaoImpl2(){
        System.out.println("创建了StudentDao!");
    }
}
    @Test
    public void t2(){
//        创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
//        StudentDao studentDao = new StudentDaoImpl2();等同于下面的创建方式,下面的创建方式是从Spring容器中拿到对象
//        从容器中获取对象
        StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao");
        StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao");
        StudentDao studentDao3 = (StudentDao) ac.getBean("studentDao");
        System.out.println(studentDao1.hashCode());
        System.out.println(studentDao2.hashCode());
        System.out.println(studentDao3.hashCode());
    }

(3)测试结果:

 (4)结论:

lazy-init="false"(默认):立即创建,在容器启动时会创建配置文件中的所有Bean对象。

2.prototype

多例,每次从容器中获取时都会创建新的对象。

(1)配置多例策略:

<!--配置多例策略-->
    <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2" scope="prototype" ></bean>

(2)测试多例策略:

    @Test
    public void t2(){
//        创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
//        StudentDao studentDao = new StudentDaoImpl2();等同于下面的创建方式,下面的创建方式是从Spring容器中拿到对象
//        从容器中获取对象
        StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao");
        StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao");
        StudentDao studentDao3 = (StudentDao) ac.getBean("studentDao");
        System.out.println(studentDao1.hashCode());
        System.out.println(studentDao2.hashCode());
        System.out.println(studentDao3.hashCode());
    }

(3)测试结果:

 (4)结论:

每次从容器中获取时都会创建新的对象。

3.request :每次请求创建一个对象,只在 web 环境有效。
4.session :每次会话创建一个对象,只在 web 环境有效。
5.gloabal-session :一次集群环境的会话创建一个对象,只在 web
环境有效。

七、SpringIOC_对象的销毁时机:

 对象的创建策略不同,销毁时机也不同:

1.singleton :对象随着容器的销毁而销毁。(因为整个项目中只有一个对象,并且存在容器当中,如果对象被随意地销毁的话,再想获取对象就获取不到了,所以正常情况下单例的对象不会销毁,只会随着容器的销毁而销毁)
2.prototype :使用 JAVA 垃圾回收机制销毁对象。(每次获取对象时都会创建新的对象,不存在容器当中,不然容器中要存很多这种对象。所以多例对象也不由Spring来销毁,因为太多了,所以直接使用Java的垃圾回收机制销毁对象,什么时候对象没用了就将其销毁。)
3.request :当处理请求结束, bean 实例将被销毁。
4.session :当 HTTP Session 最终被废弃的时候, bean 也会被销毁掉。
5.gloabal-session :集群环境下的 session 销毁, bean 实例也将被销毁。

八、SpringIOC_声明周期方法

 Bean对象的生命周期包含创建——使用——销毁,Spring可以配置Bean对象在创建和销毁时自动执行的方法:

1.定义声明周期方法

public class StudentDaoImpl2 implements StudentDao{

    //创建时自动执行的方法
    public void init(){
        System.out.println("创建StudentDao!!!");
    }

    //销毁时自动执行的方法
    public void destroy(){
        System.out.println("销毁StudentDao");
    }

//    构造方法在创建对象的时候被调用
    public StudentDaoImpl2(){
        System.out.println("创建了StudentDao!");
    }
}

2.配置声明周期方法

 <!--init-method:创建时执行的方法,一般放一些资源(传入方法名)
            destroy-method:销毁时执行的方法,一般关闭一些资源(传入方法名) -->
    <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2" scope="singleton"
          init-method="init" destroy-method="destroy" ></bean>

3.测试方法

    @Test
    public void t3(){
//        创建Spring容器
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
//        销毁Spring容器,
//        ClassPathXmlApplicationContext才有销毁容器的方法,ApplicationContext引用没有销毁容器的方法
        ac.close();
    }

4.测试结果

 5.知识点整理:

(1)Spring中, <bean> 中配置“init-method ”属性可以定义创建对象时执行的方法,一般放入一些资源

(2)在Spring中, <bean> 中配置“destroy-method ”属性可以定义销毁对象时执行的方法,一般放入一些资源关闭

九、SpringIOC_获取Bean对象的方式

Spring 有多种获取容器中对象的方式:

1.通过id/name获取

(1)配置文件
<bean name="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean> 

<bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean>

(2)获取对象

StudentDao studentDao = (StudentDao) ac.getBean("studentDao");

2.通过类型获取

(1)配置文件

<bean name="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean>

(2)获取对象

StudentDao studentDao2 = ac.getBean(StudentDao.class);

3.通过 id/name+类型 获取

虽然使用类型获取不需要强转,但如果在容器中有一个接口的多个实现类对象,则获取时会报错,此时需要使用类型+id/name 获取:
(1)配置文件
<bean name="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean> 
<bean name="studentDao1" class="com.itbaizhan.dao.StudentDaoImpl"></bean>

(2)获取对象

//传入id/name属性值+类型
StudentDao studentDao2 = ac.getBean("studentDao",StudentDao.class);

4.整体代码段:

(1)配置文件:

<bean name="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean> 
<bean name="studentDao1" class="com.itbaizhan.dao.StudentDaoImpl"></bean>

(2)测试方法中获取对象

@Test
    public void t4(){
//        创建Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
//       第一种方式:根据name获取对象(需要进行类型强转)
        StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao");
        System.out.println(studentDao1);
//        第二种方式:根据类型获取对象(不需要进行类型强转,因为传入的就是确切的类型)
//        这种方式有个问题:整个容器中同类型对象有可能有多个,这种情况就很难分辨了。
//        如:StudentDao接口有多个实现类,这时就无法分辨具体要创建哪个实现类的对象了
        StudentDao studentDao2 = ac.getBean(StudentDao.class);
        System.out.println(studentDao2);
//        第三种方式:根据name/id+类型获取对象
        StudentDao studentDao3 = ac.getBean("studentDao",StudentDao.class);
        System.out.println(studentDao3);

    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值