首页 > 其他分享 >Spring IOC与工厂模式

Spring IOC与工厂模式

时间:2024-03-15 11:36:35浏览次数:29  
标签:对象 spring 工厂 public Bean Spring IOC 我们

1. 简单介绍

在讲Spring IOC之前,有必要先来聊一下工厂模式(Factory Pattern)。工厂模式可将Java对象的调用者从被调用者的实现逻辑中分离出来。工厂模式是Java中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

这么讲可能有点抽象,简单的说就是以后我们不用自己new对象了,对象的实例化都交给工厂来完成,我们需要对象的时候直接问工厂拿一个就行,一会我们会来看一个例子。在这里有一点要说明,spring IOC与工厂模式并不是完全相同的,最大的不同在于普通的工厂模式内部还是使用new来创建对象,但是spring IOC是用反射来创建对象,这么做有什么好处呢?

2. "new" VS "反射"

下面我们来看一个工厂模式例子。为了演示方便,我将所有的类和接口都写在一起:

public interface Shape {
   void draw();
}

public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

public class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}
public class ShapeFactory {
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
}

我们来看一下这段代码干了啥。我们看这个ShapeFactory,里面有个getShape方法,输入图形的名字我们就能获得相应图形的对象。这就是所谓的工厂,有了这个工厂之后,我们想用什么图形的对象,直接调用getShape方法就能获得了。这样使用这些对象的类就可以和这些图形类解耦。但是我们很容易发现,现在工厂能生产三个不同的对象,如果我们要加一个新的对象到工厂中,是非常麻烦的,我们要修改代码然后重新编译,就好比现实中的工厂突然想要加一条新生产线是很麻烦的一样。于是我们肯定要寻求改进,这就孕育了spring IOC。

spring IOC的思想与工厂模式基本是一样的,只是创建对象的方式从“new”变成了反射,这就带来了很大的灵活性。不过,现在阅读spring源码还为时过早,于是我自己写了一个简单的例子来模拟spring IOC的基本原理。

首先,如果我们要用反射创建对象,全类名是必不可少的(反射不太记得的朋友请好好复习一下反射),然后我们还需要一个类名,用来告诉工厂我们需要哪个对象(就像上面getShape方法传入的参数shapeType一样),这个名字可以随便取,但是不能重复。这样我们就有了创建对象的两个要素,然后我们需要一个key-value对把这两个关联起来。然后就形成了这样一个模式:我们传入类名,工厂去查询key-value对,找到对应的全类名,然后通过全类名利用反射创建对象,再返回给我们。是不是很简单呢?

话不多说,我们先来创建这个key-value对,也就是所谓的配置文件,spring中用的是XML,我这里为了简化就用properties吧,原理都是一样的:

//文件名:bean.properties
circle=com.demo.Circle
rectangle=com.demo.Rectangle
square=com.demo.Square

配置文件有了之后,我们来写我们的BeanFactory。

//文件名:BeanFactory.java
package com.demo;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class BeanFactory {
    //配置对象(类比spring IOC容器中的Bean定义注册表)
    private static final Properties props;
    //保存创建好的对象的容器,与类名组成key-value对(类比spring IOC容器中的Bean缓存池)
    private static Map<String, Object> beans;
    static {
        props = new Properties();
        //通过类加载器读入配置文件
        InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            //加载配置文件到配置对象
            props.load(in);
            //初始化容器
            beans = new HashMap<>();
            Enumeration<Object> keys = props.keys();
            //循环遍历配置对象中的所有的类名(key)
            while (keys.hasMoreElements()){
                String key = keys.nextElement().toString();
                //通过类名拿到全类名(value)
                String beanPath = props.getProperty(key);
                //利用全类名反射创建对象
                Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
                //将对象放入容器中
                beans.put(key, value);
            }
        } catch (IOException e) {
            throw new ExceptionInInitializerError("初始化properties失败!程序终止!");
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    public static Object getBean(String beanName){
        //从容器中获取对象
        return beans.get(beanName);
    }
}

另外三个类和一个接口依旧沿用上面那个工厂模式的例子的,本案例所有java文件都位于com.demo包下。我们来仔细看看这个BeanFactory(我自己写的山寨版,模仿spring IOC基本功能),首先我们先抓住核心,核心就是里面的Map<String, Object> beans,这个东西直接对标了spring IOC容器中的Bean缓存池,用来存放创建好的对象,用Map是为了可以直接通过类名取到对应的对象。然后我们来看看这些对象是如何生产出来的:

Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();

很显然,反射就在这一句上,我们通过类的全类名来创建了对象,全类名来自于我们的Properties对象,也就是读取我们的配置文件产生的对象,对标spring IOC容器中的Bean定义注册表。现在你应该已经明白了这个配置文件的作用,他就像我们给工厂的一张生产单,上面写了我们需要生产的对象。而Bean缓存池相当于工厂的仓库,用来存储生产完的对象,等待被取出。而我们定义的Bean实现类(就是上面的那些Circle、Square之类的)相当于图纸,告诉工厂这些对象是什么样,应该如何去生产。我们来总结一下:

模块 功能
Bean实现类 说明如何生产
Bean定义注册表 说明需要生产哪些
Bean缓存池(HashMap实现) 存放生产完的对象

3. 真正的Spring IOC

看完了我写的“山寨”IOC,我们再来画个图看一看真正的spring IOC的结构执行过程,其实与我写的基本是一致的。
image
我们来看看执行过程:

  1. 读取Bean配置信息放入Bean定义注册表
  2. 根据Bean注册表实例化Bean
  3. 将实例化之后的bean实例放入Bean缓存池(HashMap实现)
  4. 应用程序通过类名从Bean缓存池中取出Bean实例

看了这么多,我们还是来看看具体如何使用spring IOC容器来创建对象吧。首先,就像上面我的那个山寨IOC一样,我们要先来编写XML文件,XML比properties要复杂,不过好在我们暂时还用不到那么复杂的部分。首先先去官网的文档里面找一段模板抄下来:
image

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

这就是spring配置文件的模板了,上面定义了一些XML文件的约束,也就是我们在那些XML标签里能写啥。我们以后的开发都会基于这个模板。然后,就是我们的类名和全类名组成的key-value对了,这些流程和上面都是完全一样的,只是写法有所不同,我们把该写的都写上(注意我的写法)

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="circle" class="com.demo.Circle"/>
    <bean id="square" class="com.demo.Square"/>
    <bean id="rectangle" class="com.demo.Rectangle"/>
</beans>

很显然,这个id就相当于类名,这个class就相当于全类名,是不是和我的山寨版基本一样?

当然,既然我们是在使用框架,所以那些繁琐的工作都不需要我们去做了,也就是说我们不需要去关心对象是如何创建如何管理的,这些都由spring帮我们完成了。我们要做的就是直接问spring IOC容器取出对象即可。那么这个容器在哪?现在当然是只能我们手动创建这个容器,不然他也不会凭空产生对吧。
image
我们来看一眼这个接口继承图,我们只需要关注两个,一个是里面的一个顶级接口——BeanFactory,另一个是最底下的ApplicationContext接口。BeanFactory是简单容器,他实现了容器的基本功能,典型方法如 getBean、containsBean等。ApplicationContext是应用上下文,他在简单容器的基础上,增加上下文的特性。我们开发时一般都是使用ApplicationContext接口,因为他的功能比BeanFactory更强大。当然,“应用上下文”这个名字可能有点奇怪,不过我们只需要记得他就是那个spring IOC容器接口就行。接口有了,接下来就是要找实现类。

ApplicationContext有好多的实现类,我们就挑一个最常用的讲——ClassPathXmlApplicationContext。
image
我们先来看一眼他的名字:ClassPathXmlApplicationContext。翻译为:类路径XML应用上下文,嗯,这个名字更奇怪了。其实,他就是一个只能读取类路径下的XML文件作为配置文件的应用上下文实现类。那我再举一个例子:FileSystemXmlApplicationContext,他是干嘛的?嗯,他是文件系统应用上下文,也就是说他可以读取磁盘任意位置(需要有读权限)的XML作为配置文件。

这样我们就可以实例化我们的IOC容器了:

package com.demo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
    	//构造函数参数为配置文件名称
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        Shape circle = (Shape) applicationContext.getBean("circle");
        circle.draw();
    }
}

这样我们就拿到了容器里面的对象,是不是和我的山寨版基本一样呢?读到这里,你应该已经理解了spring IOC的原理了,后面我会更新新的文章,分析spring IOC的细节和一些其他功能。最后放一张截图,不清楚项目结构的可以看一眼。
image

本文转载原文章地址:https://blog.csdn.net/weixin_43907422/article/details/105933374

标签:对象,spring,工厂,public,Bean,Spring,IOC,我们
From: https://www.cnblogs.com/lymf/p/18075055

相关文章

  • 猫头虎分享已解决Bug | org.springframework.dao.DuplicateKeyException异常的正确解
    博主猫头虎的技术世界......
  • Spring Schedule定时任务看这一篇就够了
    SpringSchedule 是指Spring框架提供的定时任务调度功能。SpringSchedule允许开发者在应用程序中便捷地创建和管理定时任务,比如按固定频率执行某些操作,或者按照cron表达式设定复杂的调度规则。SpringSchedule功能的依赖直接或间接地包含在 spring-boot-starter 家族中的相......
  • Java基于 Springboot+Vue 的招生管理系统,前后端分离
    博主介绍:✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌......
  • SMUSpring天梯赛1
    补题1:龙龙送外卖题意:做法:思维--遍历方式,从输入的点往外卖点遍历,或标记过的点。回溯的时候更新深度!//到达了最后一个送货点之后不用返回根结点.那么之前到达的点都是要折返点,那么就最后才送最深点节点。  //还有就是如果在去节点8点时候,途径了节点2,那么这个时候去节点......
  • springboot自动配置
    首先,你在pom文件里引入的很多第三方jar里都有一个文件 META-INF/spring.factories,这个文件里的内容和关系到能否自动配置,那有的jar为啥没有,是不需要SpringBoot来自动配置吗,这个我们后面再说。    先来看一下redissonstarter的/META-INF/spring.factories是怎么写的,......
  • 在Docker上传我们自己的镜像(以springboot项目为例)
    首先确定好在我们的centOS服务器上已经安装并配置好docker配置自己的springboot镜像并运行获取springboot的jar包mavenclean--》mavenuepackage --》复制target目录下生成的jar包在服务器选择一个文件夹上传jar包,我这里选用的文件夹叫做/opt/dockertest在jar包的同一......
  • springboot3+vue3(十一)springboot多环境开发
    在开发中我们往往会遇到,本地环境、测试环境、生产环境分别一套配置。如数据库连接,端口号等配置各不相同的问题。 1、多文件配置    2、多文件分组配置如果配置文件有很多的配置信息几百行的情况,为了方便维护我们可以根据功能的情况进行分组拆分。如:服务器相关配......
  • Spring Task 定时任务框架 以及cron表达式
    一:SpringTask介绍SpringTask是spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。定位:定时任务框架作用:定时自动执行某段Java代码应用场景:·信用卡每月还款提醒·银行贷款每月还款提醒·火车票售票系统处理未支付订单·入职纪念日为用户......
  • springboot3/14
    在做系统首页配置时,#注意点,所有页面的静态资源都需要使用thymeleaf去接管;@{}页面国际化在项目中使用国际化我们需要配置i18n文件如果需要在项目中进行按钮自动切换功能,需要自己去定义一个国际化组件LocaleResolver配置完成后,记得将写好的组件配置到spring容器中@Bean在网页......
  • 在工厂项目中,我是用这个读取PLC数据的
    ApachePLC4X软件介绍ApachePLC4X旨在创建一组库,以统一的方式与工业级可编程逻辑控制器(PLCs)进行通信。目前,支持以下语言:JavaGoC(尚未可用)Python(尚未可用)C#(.Net)(已废弃)功能特点PLC4X设计目标之一是为开发人员提供简化的API,隐藏底层通信细节,以便与各种......