Spring IOC与工厂模式

PS:本文内容较为硬核,需要对java的面向对象、反射、类加载器、泛型、properties、XML等基础知识有较深理解。

(一)简单介绍

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

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

(二)“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实现) 存放生产完的对象

(三)真正的Spring IOC

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

 

我们来看看执行过程:

1.读取Bean配置信息放入Bean定义注册表
2.根据Bean注册表实例化Bean
3.将实例化之后的bean实例放入Bean缓存池(HashMap实现)
4.应用程序通过类名从Bean缓存池中取出Bean实例
看了这么多,我们还是来看看具体如何使用spring IOC容器来创建对象吧。首先,就像上面我的那个山寨IOC一样,我们要先来编写XML文件,XML比properties要复杂,不过好在我们暂时还用不到那么复杂的部分。首先先去官网的文档里面找一段模板抄下来:

 
<?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容器取出对象即可。那么这个容器在哪?现在当然是只能我们手动创建这个容器,不然他也不会凭空产生对吧。

 

我们来看一眼这个接口继承图,我们只需要关注两个,一个是里面的一个顶级接口——BeanFactory,另一个是最底下的ApplicationContext接口。BeanFactory是简单容器,他实现了容器的基本功能,典型方法如 getBean、containsBean等。ApplicationContext是应用上下文,他在简单容器的基础上,增加上下文的特性。我们开发时一般都是使用ApplicationContext接口,因为他的功能比BeanFactory更强大。当然,“应用上下文”这个名字可能有点奇怪,不过我们只需要记得他就是那个spring IOC容器接口就行。接口有了,接下来就是要找实现类。

ApplicationContext有好多的实现类,我们就挑一个最常用的讲——ClassPathXmlApplicationContext。

 

我们先来看一眼他的名字: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的细节和一些其他功能。最后放一张截图,不清楚项目结构的可以看一眼。

 

应聘阿里,字节跳动,美团必须掌握的Spring IOC与工厂模式的更多相关文章

  1. 应聘阿里,字节跳动美团90%会问到的JVM面试题! 史上最全系列!

    Java 内存分配 • 寄存器:程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码.• 静态域:static 定义的静态成员.• 常量池:编译时被确定并保存在 .class 文件中的(f ...

  2. 凭借着这份Spring面试题,我拿到了阿里,字节跳动美团的offer!

      一般问题 1.1. 不同版本的 Spring Framework 有哪些主要功能?   1.2. 什么是 Spring Framework? Spring 是一个开源应用框架,旨在降低应用程序开发 ...

  3. 面试阿里,字节,美团必看的Spring的Bean管理详解

    IOC容器 工厂只负责创建对象,而Spring当然不仅仅是一个对象工厂,其核心是一个对象容器,其具备控制反转的能力,所以也称为IOC容器. 帮助我们存放对象,并且管理对象,包括:创建.销毁.装配,这样 ...

  4. 我把阿里、腾讯、字节跳动、美团等Android性能优化实战整合成了一个PDF文档

    安卓开发大军浩浩荡荡,经过近十年的发展,Android技术优化日异月新,如今Android 11.0 已经发布,Android系统性能也已经非常流畅,可以在体验上完全媲美iOS. 但是,到了各大厂商手 ...

  5. 多次面试被拒,‘宅家苦修’30天,终获美团offer(含字节跳动/阿里/腾讯等大厂面试题整理)

    背景:双非渣本. 今年由于疫情,上半年一直在家里.2月份本来无忧无虑,呆在家里不给国家添乱的时候,发现身边的同学找到了大厂的offer.心里开始有点慌张.本来想在3月份如果能回到学校,就开始考研之路, ...

  6. 被字节跳动、小米、美团面试官问的AndroidFramework难倒了? 这里有23道面试真题,助力成为offer收割机!

    目录 1.Android中多进程通信的方式有哪些?a.进程通信你用过哪些?原理是什么?(字节跳动.小米)2.描述下Binder机制原理?(东方头条)3.Binder线程池的工作过程是什么样?(东方头条 ...

  7. 工作三年终于社招进字节跳动!字节跳动,阿里,腾讯Java岗面试经验汇总

    前言 我大概我是从去年12月份开始看书学习,到今年的6月份,一直学到看大家的面经基本上百分之90以上都会,我就在5月份开始投简历,边面试边补充基础知识等.也是有些辛苦.终于是在前不久拿到了字节跳动的o ...

  8. 从字节跳动离职后,拿到探探、趣头条、爱奇艺、小红书、15家公司的 offer【转】

    前言 博主目前从事Android开发3年,前两年一直在抖音工作.我这篇文章并不是简单的描述一些面试中的题,或者总结一些Android的知识,而是想记录我整个的想法和准备的过程,以及一些心得体会,让大家 ...

  9. 5年Android程序员面试字节跳动两轮后被完虐,请查收给你的面试指南

    大家应该看过很多分享面试成功的经验,但根据幸存者偏差的理论,也许多看看别人面试失败在哪里,对自己才更有帮助. 最近跟一个朋友聊天,他准备了几个月,刚刚参加完字节跳动面试,第二面结束后,嗯,挂了- 所以 ...

随机推荐

  1. 字节跳动2020Java面经,你离高薪就只差一片面试题了

    前言 经历了惨痛的春招与秋招之后,也积攒了一些面经,希望能对大家有所帮助.由于字数限制需要答案的可以关注GZH[程序员空间] 免费领取完整版PDF 其他 什什么是幂等?什什么情况下需要考虑幂等?你怎么 ...

  2. CodeForces 1093F Vasya and Array

    题意 给一个长度为 \(n\) 的整数序列 \(a\),其中 \(a_i\) 要么为 \(-1\),要么为 \(1\sim k\) 中的整数. 求出将所有 \(-1\) 替换为 \(1\sim k\) ...

  3. CSS动画之动画模块

    过渡模块和动画模块的区别:过渡动画需要人为的去触发这个反应,而动画模块不需要人为相同点:都是动画:都是系统新增的属性:都是要满足三要素才有动画效果三要素:1.告诉系统执行哪个动画,要写出动画名称:an ...

  4. 虚拟化下Centos7 扩容根分区

    查看分区大小和挂载情况 用到的命令df.lsblk [root@localhost ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/ ...

  5. UbuntuStudio20.04安装教程(双系统安装,windows10已安装)

    硬件和系统: acer4750(原i3换i7,加固态硬盘200多G,原机械硬盘500G由光驱改装,内存由2G增加为6G)2010年购买3300,性价比高,硬件升级后2020年不过时 windows10 ...

  6. python socket 二进制

    问题 在工作中经常会用到socket传输数据,例如客户端给服务器发送数据(双方约定了数据格式),在交测之前,自己用python写个接受数据的server,解析下拼成的数据格式是否正确.用python写 ...

  7. 简单入门Rabbitmq

    什么是RabbitMQ RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写.支持多种客户端,如:Python.Ruby..NET.Java.JMS.C.PHP.ActionScr ...

  8. Docker(12)- docker run 命令详解

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 作用 创建一个新的容器并运行一个 ...

  9. Docker(10)- docker create 命令详解

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 作用 创建一个新的容器但不启动它 ...

  10. 数据治理方案技术调研 Atlas VS Datahub VS Amundsen

    数据治理意义重大,传统的数据治理采用文档的形式进行管理,已经无法满足大数据下的数据治理需要.而适合于Hadoop大数据生态体系的数据治理就非常的重要了. ​ 大数据下的数据治理作为很多企业的一个巨大的 ...