02 Spring_IOC 控制反转

矫情吗;* 2023-09-28 17:16 83阅读 0赞

目录

一、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_控制反转思想

97d8565c25ab46f5a55414963beab1df.png

1.IOC介绍:

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

之前在开发过程中,对象实例的创建是由调用者管理的,代码如下:

  1. public interface StudentDao {
  2. // 根据id查询学生
  3. Student findById(int id);
  4. }
  5. public class StudentDaoImpl implements StudentDao{
  6. @Override
  7. public Student findById(int id) {
  8. // 模拟从数据库查找出学生
  9. return new Student(1,"百战程序员","北 京");
  10. }
  11. }
  12. public class StudentService {
  13. public Student findStudentById(int id){
  14. // 此处就是调用者在创建对象
  15. StudentDao studentDao = new StudentDaoImpl();
  16. return studentDao.findById(1);
  17. }
  18. }

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

(1)浪费资源: StudentService 调用方法时即会创建一个对象,如果不断调用方法则会创建大量StudentDao 对象。

(2)代码耦合度高:假设随着开发,我们创建了 StudentDao 另一个更加完善的实现类StudentDaoImpl2 ,如果在 StudentService中想使用StudentDaoImpl2 ,则必须修改源码。

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

二、SpringIOC_自定义对象容器

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

9af32b10aa47484ba19f164c135c8a26.png

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

ee2ddb313909477f866b00a97e533986.png

1.创建实体类

  1. public class Student {
  2. private int id;
  3. private String name;
  4. private String address;
  5. // 省略getter/setter/构造方法/tostring
  6. }

2.创建Dao接口和实现类

  1. public interface StudentDao {
  2. // 根据id查询学生
  3. Student findById(int id);
  4. }
  5. public class StudentDaoImpl implements StudentDao{
  6. @Override
  7. public Student findById(int id) {
  8. // 模拟从数据库查找出学生
  9. return new Student(1,"百战程序员","北京");
  10. }
  11. }
  12. public class StudentDaoImpl2 implements StudentDao{
  13. @Override
  14. public Student findById(int id) {
  15. // 模拟根据id查询学生
  16. System.out.println("新方法!!!");
  17. return new Student(1,"百战程序员","北京");
  18. }
  19. }

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

  1. studentDao=com.itbaizhan.dao.StudentDaoImpl

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

  1. //容器类,负责管理对象,在类加载时读取配置文件并创建对象
  2. public class Container {
  3. static Map<String,Object> map = new HashMap();
  4. // 读取配置文件并创建对象
  5. static {
  6. InputStream is = Container.class.getClassLoader().getResourceAsStream("bean.properties");
  7. Properties properties = new Properties();
  8. try{
  9. properties.load(is);
  10. }catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. // 遍历配置文件中的所有对象
  14. Enumeration<Object> keys = properties.keys();
  15. while (keys.hasMoreElements()){
  16. String key = keys.nextElement().toString();
  17. String value = properties.getProperty(key);
  18. // 创建对象
  19. try {
  20. Object o = Class.forName(value).newInstance();
  21. // 把对象放入集合当中
  22. map.put(key,o);
  23. } catch (InstantiationException e) {
  24. e.printStackTrace();
  25. } catch (IllegalAccessException e) {
  26. e.printStackTrace();
  27. } catch (ClassNotFoundException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. // 从容器中获取对象
  33. public static Object getBean(String key){
  34. return map.get(key);
  35. }
  36. }

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

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

6.测试StudentService

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

7.测试结果

f9ba11eaec6d4d128a5111d35e370283.png

8.测试结论:

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

  1. studentDao=com.itbaizhan.dao.StudentDaoImpl2

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

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

三、SpringIOC_Spring实现IOC

34355c11365f4d5c8af0a9a5ec2616ad.png

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

fc08c30137094b218ef9a6312445c66d.png

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

  1. <dependencies>
  2. <!--spring核心模块-->
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-context</artifactId>
  6. <version>5.3.13</version>
  7. </dependency>
  8. <!--junit-->
  9. <dependency>
  10. <groupId>junit</groupId>
  11. <artifactId>junit</artifactId>
  12. <version>4.12</version>
  13. <scope>test</scope>
  14. </dependency>
  15. </dependencies>

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

  1. public class Student {
  2. private int id;
  3. private String name;
  4. private String address;
  5. // 省略getter/setter/构造方法/tostring
  6. }
  7. public interface StudentDao {
  8. // 根据id查询学生
  9. Student findById(int id);
  10. }
  11. public class StudentDaoImpl implements StudentDao{
  12. @Override
  13. public Student findById(int id) {
  14. // 模拟从数据库查找出学生
  15. return new Student(1,"百战程序员","北京");
  16. }
  17. }

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!--配置spring对象容器 id:为对象起名字 class:传入要配置的类对象的全类名 -->
  7. <bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl"></bean>
  8. </beans>

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

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

5.测试结果

2703ed2309444ecab9f6db41bb1150ad.png

6.知识点整理:

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

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

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

四、SpringIOC_Spring容器类型

bcbdc442677845e2a18e6a9c3ea3684f.png

1.容器接口(BeanFactory、ApplicationContext

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

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

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

(1) ClassPathXmlApplicationContext:该类可以从项目中读取配置文件,传入想对路径

(2) FileSystemXmlApplicationContext:该类可以从磁盘中读取配置文件,传入绝对路径

(3) AnnotationConfigApplicationContext:使用该类不读取配置文件,而是会读取注解

3.代码演示如:

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

五、SpringIOC_对象的创建方式

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

1.使用无参构造方法

Spring默认使用类的空参构造方法创建bean:

  1. // 假如类没有空参构造方法,将无法完成bean的创建
  2. public class StudentDaoImpl implements StudentDao{
  3. // 设置一个有参的构造方法,默认去除了无参的构造方法
  4. public StudentDaoImpl(int a){}
  5. @Override
  6. public Student findById(int id) {
  7. // 模拟根据id查询学生
  8. return new Student(1,"百战程序员","北 京");
  9. }
  10. }

edfbeae970d9475097fc9c4f6f0076a3.png

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

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

Spring 可以调用工厂类的方法创建 bean :

(1)创建工厂类,工厂类提供创建对象的方法:

  1. public class StudentDaoFactory {
  2. public StudentDao getStudentDao(){
  3. //return new StudentDaoImpl(1):返回StudentDaoImpl对象,利用有参构造方法
  4. return new StudentDaoImpl(1);
  5. }
  6. }

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

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

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

Spring 可以调用工厂类的静态方法创建 bean :

1

(1)创建工厂类,工厂提供创建对象的静态方法。

  1. public class StudentDaoFactory2 {
  2. public static StudentDao getStudentDao(){
  3. return new StudentDaoImpl(1);
  4. }
  5. }

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

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

4.知识点整理:

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

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

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

d382febdde674c20bd6b86b90aea6a27.png

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

1.singleton:

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

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

lazy-init=”true” 延迟创建,第一次使用 Bean 对象时才会创建。

(1)配置单例策略:

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

(2)测试单例策略:

  1. public class StudentDaoImpl2 implements StudentDao{
  2. public StudentDaoImpl2(){
  3. System.out.println("创建了StudentDao!");
  4. }
  5. }
  6. @Test
  7. public void t2(){
  8. // 创建Spring容器
  9. ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
  10. // StudentDao studentDao = new StudentDaoImpl2();等同于下面的创建方式,下面的创建方式是从Spring容器中拿到对象
  11. // 从容器中获取对象
  12. StudentDao studentDao1 = (StudentDao) ac.getBean("studentDao");
  13. StudentDao studentDao2 = (StudentDao) ac.getBean("studentDao");
  14. StudentDao studentDao3 = (StudentDao) ac.getBean("studentDao");
  15. System.out.println(studentDao1.hashCode());
  16. System.out.println(studentDao2.hashCode());
  17. System.out.println(studentDao3.hashCode());
  18. }

(3)测试结果:

5cbd1e546b5647ce8c8c574e9bef5bfd.png

(4)结论:

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

2.prototype:

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

(1)配置多例策略:

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

(2)测试多例策略:

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

(3)测试结果:

513a909e27bb4ba49b81a338fe45149b.png

(4)结论:

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

3.request :每次请求创建一个对象,只在 web 环境有效。

4.session :每次会话创建一个对象,只在 web 环境有效。

5.gloabal-session :一次集群环境的会话创建一个对象,只在 web

环境有效。

七、SpringIOC_对象的销毁时机:

c8bce726466341da83bea6db3ee7c628.png

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

1.singleton :对象随着容器的销毁而销毁。(因为整个项目中只有一个对象,并且存在容器当中,如果对象被随意地销毁的话,再想获取对象就获取不到了,所以正常情况下单例的对象不会销毁,只会随着容器的销毁而销毁)

2.prototype :使用 JAVA 垃圾回收机制销毁对象。(每次获取对象时都会创建新的对象,不存在容器当中,不然容器中要存很多这种对象。所以多例对象也不由Spring来销毁,因为太多了,所以直接使用Java的垃圾回收机制销毁对象,什么时候对象没用了就将其销毁。)

3.request :当处理请求结束, bean 实例将被销毁。

4.session :当 HTTP Session 最终被废弃的时候, bean 也会被销毁掉。

5.gloabal-session :集群环境下的 session 销毁, bean 实例也将被销毁。

八、SpringIOC_声明周期方法

05d4517f283d4f878c4086f8d87bedad.png

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

1.定义声明周期方法

  1. public class StudentDaoImpl2 implements StudentDao{
  2. //创建时自动执行的方法
  3. public void init(){
  4. System.out.println("创建StudentDao!!!");
  5. }
  6. //销毁时自动执行的方法
  7. public void destroy(){
  8. System.out.println("销毁StudentDao");
  9. }
  10. // 构造方法在创建对象的时候被调用
  11. public StudentDaoImpl2(){
  12. System.out.println("创建了StudentDao!");
  13. }
  14. }

2.配置声明周期方法

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

3.测试方法

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

4.测试结果

3f55672bfe0f41b7840057ce0f8ab3e8.png

5.知识点整理:

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

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

九、SpringIOC_获取Bean对象的方式

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

1.通过id/name获取

(1)配置文件

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

(2)获取对象

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

2.通过类型获取

(1)配置文件

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

(2)获取对象

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

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

虽然使用类型获取不需要强转,但如果在容器中有一个接口的多个实现类对象,则获取时会报错,此时需要使用类型+id/name 获取:

(1)配置文件

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

(2)获取对象

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

4.整体代码段:

(1)配置文件:

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

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

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

发表评论

表情:
评论列表 (有 0 条评论,83人围观)

还没有评论,来说两句吧...

相关阅读

    相关 spring—控制IOC

    IOC即为Inversion of Control,中文名称为控制反转。它是一种设计模式,用于降低代码耦合度,提高代码可维护性和可扩展性。 在面向对象编程中,对象之间的依赖关

    相关 spring Ioc(控制)

    简述: spring的核心有两部分:ioc和aop (1)ioc:控制反转,之前调用一个类中的不是静态的方法,创建类的对象 new 类,再调用。现在使用spring的ioc

    相关 控制

            控制反转(Inversion of Control,缩写为IoC),是[面向对象编程][Link 1]中的一种设计原则,可以用来减低计算机代码之间的[耦合度][

    相关 Spring框架 IOC控制

    对象之间的依赖由容器建立,避免对象之间依赖由对象自身建立。可以用减少代码之间的耦合度。其中最常见的方式叫做依赖注入。 当A类中用到了B类的对象b,在没有IOC时需要在A的代