需求分析

设计一个含有IOC的简单Spring,要求含有对象注册、对象管理以及暴露给外部的获取对象功能。

项目设计

  1. 对于注册的对象用一个类BeanInfo来描述其信息,包括对象标识、全类名以及属性名与值的Map。
  2. 对于IOC容器设定一个顶层接口BeanFactory,定义通过对象标识获取对象示例的方法getBean(String id)。AbstractBeanFactory实现该接口,在该类中实现解析生成目标对象,以及获取对象方法,并在该类中添加注册器接口,以便能从注册器中读取注册的对象。
  3. 对于注册器,提供一个顶层接口SourceReader,并在其中添加加载用户注册的对象的方法loadBeans(String filePath)。本项目中使用读取XML的方式,从XML中读取出注册的对象,并把它们封装成BeanInfo放入Map中。
  4. 设定一个上下文XMLContext,继承AbstractBeanFactory,负责选择使用哪种注册方式,并决定何时加载注册的对象。

代码实现

BeanInfo类

package ex1;

import java.util.HashMap;
import java.util.Map; /**
* 该类用于描述注册在容器中的对象
*/
public class BeanInfo {
private String id; //对象ID,名字
private String type; //全类名
private Map<String, Object> properties = new HashMap<>(); //属性名与值的映射集合 public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public Map<String, Object> getProperties() {
return properties;
} public void setProperties(Map<String, Object> properties) {
this.properties = properties;
} public void addProperty(String key,Object value) {
properties.put(key,value);
}
}

BeanFactory接口

package ex1;

public interface BeanFactory {
/**
* 根据对象的名称标识来获取对象实例
* @param id 对象名称,即对象描述信息中的对象标识
* @return 指定名称的对象实例
*/
Object getBean(String id);
}

AbstractBeanFactory类

package ex1;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 最顶层的IOC实现
* 该类负责从注册器中取出注册对象
* 实现从对象描述信息转换为对象实例的过程
* 实现根据名称获取对象的方法
*/
public abstract class AbstractBeanFactory implements BeanFactory {
private String filePath; //注册文件路径
private Map<String, BeanInfo> container; //注册对象信息Map(IOC容器)
protected SourceReader reader; //对象注册读取器 public AbstractBeanFactory(String filePath) {
this.filePath = filePath;
} /**
* 抽象方法,需由子类实现,用于指定使用什么样的注册读取器
* @param reader 指定的注册读取器
*/
protected abstract void setReader(SourceReader reader); //从注册读取器中读取注册对象的信息Map
public void registerBeans() {
this.container = this.reader.loadBeans(this.filePath);
} //实现BeanFactory定义的根据名称获取指定对象的方法
@Override
public Object getBean(String id) {
BeanInfo beanInfo = this.container.get(id); //根据对象名称获取该对象的描述信息
if (beanInfo == null) {
return null;
} else {
//根据对象信息,解析并生存指定对象实例,返回给用户
return this.parseBean(beanInfo);
}
} /**
* 解析并生成对象实例
* 该方法主要通过反射完成,步骤如下:
* 1.根据类名,加载指定类,并获取该类的Class对象clazz
* 2.使用clazz实例化该类,获取一个对象,注意,这里采用无参构造方法
* 3.逐个设置对象子段的值,这里采用setter Method方式,而不是直接使用Field对象
* 4.返回对象实例
* @param beanInfo 指定对象的描述信息
* @return
*/
protected Object parseBean(BeanInfo beanInfo) {
Class clazz;
Object bean = null;
try {
clazz = Class.forName(beanInfo.getType()); //根据对象的全类名,指定类
bean = clazz.newInstance(); //使用注册对象的无参构造函数,实例化对象
Method[] methods = clazz.getMethods(); //获取所有公共方法(其实Spring获取的是所有方法,包括非公有是)
for (String property : beanInfo.getProperties().keySet()) {
//首字母大写
String name = property.toUpperCase().charAt(0) + property.toLowerCase().substring(1);
//获取属性的setter方法名称(命名规范)
String setter = "set" + name;
for (Method method : methods) {
if (method.getName().equals(setter)) {
Object value = beanInfo.getProperties().get(property);
method.invoke(bean, value); //通过反射对属性赋值
break;
}
}
} } catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return bean;
}
}

SourceReader接口

package ex1;

import java.util.Map;
/**
* 注册读取器接口
* 复制读取用户注册的对象
* 继承该接口的类可以实现多种读取方式,如从配置文件中读取,根据标注读取,从网络中读取等
*/
public interface SourceReader {
/**
* 读取用户注册的对象信息
* @param filePath 注册路径
* @return 注册对象信息Map
*/
Map<String, BeanInfo> loadBeans(String filePath);
}

XMLSourceReader类

这里实现从xml配置文件中读取。需要用到dom4j包,用来解析XML文件。本项目中XML文件放置在根目录下,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="Person" class="ex1.Person">
<property name="name" value="fang"/>
</bean>
</beans>

实现代码

package ex1;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* XML注册读取器
* 该类继承了注册读取器接口,并模拟实现了读取注册对象信息的方法
*/
public class XMLSourceReader implements SourceReader {
/**
* 实现读取注册对象信息方法
* 自己编写通过配置文件读取的实现
*/
@Override
public Map<String, BeanInfo> loadBeans(String filePath) {
BeanInfo info = new BeanInfo(); //注册对象的info
InputStream is = XMLContext.class.getClassLoader().getResourceAsStream(filePath);//获取xml文件
SAXReader reader = new SAXReader();
Map<String,BeanInfo> beanMap = new HashMap<>();
try {
Document document = reader.read(is);
Element root = document.getRootElement(); //获取根标签,这里是beans
//遍历所有bean
for(Iterator iterator = root.elementIterator("bean");iterator.hasNext();){
Element element = (Element)iterator.next();
//获取id和class
Attribute id = element.attribute("id");
Attribute clazzName = element.attribute("class");
info.setId(id.getText());
info.setType(clazzName.getText());
//遍历该bean的property
for(Iterator it=element.elementIterator("property");it.hasNext();){
Element tmp = (Element)it.next();
//获取name和value
Attribute name = tmp.attribute("name");
Attribute value = tmp.attribute("value");
info.addProperty(name.getText(),value.getText());
}
beanMap.put(id.getText(),info);
}
} catch (DocumentException e) {
e.printStackTrace();
}
return beanMap;
}
}

XMLContext类

package ex1;

public class XMLContext extends AbstractBeanFactory {
/**
* 上下文的构造方法
* 该方法中指明注册读取器
* 并在构造该方法时一次性加载注册的对象
* @param filePath
*/
public XMLContext(String filePath) {
super(filePath);
this.setReader(new XMLSourceReader());//添加注册读取器
this.registerBeans(); //加载注册的对象信息
} //设置注册读取器
@Override
protected void setReader(SourceReader reader) {
this.reader = reader;
}
}

测试类

Speakable接口

package ex1;

public interface Speakable {
void speak(String message);
}

Person类

package ex1;

public class Person implements Speakable {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public void speak(String message) {
System.out.println(this.name + " say: " + message);
}
}

Bootstrap类

package ex1;

public class Bootstrap {
public static void main(String[] args) {
BeanFactory beanFactory = new XMLContext("bean.xml");
Speakable speakable = (Speakable) beanFactory.getBean("Person");
speakable.speak("Experience One!");
}
}

运行结果

fang say: Experience One!

实现IOC功能的简单Spring框架的更多相关文章

  1. Spring框架系列(2) - Spring简单例子引入Spring要点

    上文中我们简单介绍了Spring和Spring Framework的组件,那么这些Spring Framework组件是如何配合工作的呢?本文主要承接上文,向你展示Spring Framework组件 ...

  2. Spring框架系列(3) - 深入浅出Spring核心之控制反转(IOC)

    在Spring基础 - Spring简单例子引入Spring的核心中向你展示了IoC的基础含义,同时以此发散了一些IoC相关知识点; 本节将在此基础上进一步解读IOC的含义以及IOC的使用方式.@pd ...

  3. Java开发工程师(Web方向) - 04.Spring框架 - 第2章.IoC容器

    第2章.IoC容器 IoC容器概述 abstract: 介绍IoC和bean的用处和使用 IoC容器处于整个Spring框架中比较核心的位置:Core Container: Beans, Core, ...

  4. spring-framework-中文文档一:IoC容器、介绍Spring IoC容器和bean

    5. IoC容器 5.1介绍Spring IoC容器和bean 5.2容器概述 本章介绍Spring Framework实现控制反转(IoC)[1]原理.IoC也被称为依赖注入(DI).它是一个过程, ...

  5. Spring框架源码阅读之Springs-beans(一)容器的基本实现概述(待续)

    去年通过实际框架代码的阅读,以及结合<Spring源码深度解析>和<Spring技术内幕>的阅读,对Spring框架内Bean模块有了一个整体性的认识.对此进行的总结性整理和回 ...

  6. 自定义beans.xml文件实现Spring框架

    经过一天的补习,学习文件加载,java反射,JDom等知识,到了晚上终于能够搭出一个基于配置文件的简单spring框架实现! 首先我们先看看这个问题: 下面是两副图左边是项目结构图,右边是UML图: ...

  7. Spring框架-IOC和AOP简单总结

    参考博客: https://blog.csdn.net/qq_22583741/article/details/79589910 1.Spring框架是什么,为什么,怎么用 1.1 Spring框架是 ...

  8. Spring框架的IOC核心功能快速入门

    2. 步骤一:下载Spring框架的开发包 * 官网:http://spring.io/ * 下载地址:http://repo.springsource.org/libs-release-local/ ...

  9. Spring 框架的架包分析、功能作用、优点,及jar架包简介

    Spring 框架的架包详解    Spring的作用     Spring的优势  由于刚搭建完一个MVC框架,决定分享一下我搭建过程中学习到的一些东西.我觉得不管你是个初级程序员还是高级程序员抑或 ...

随机推荐

  1. 在 vue cli3 的项目中配置双服务,模拟 ajax 分页请求

    最近安装了下vue cli3版本,与 cli 2 相比,文件少了,以前配置方法也不管用了.demo 中的大量的数据,需要做成 ajax 请求的方式来展示数据,因此,需要启动两个服务,一个用作前端请求, ...

  2. 盖洛普Q12在团队中的应用

    周五给大家做了个盖洛普Q12的分享.   分享前做了调查问卷.除了盖洛普Q12的12个问题: 1.我知道公司对我的工作要求吗? 2.我有做好我的工作所需要的材料和设备吗? 3.在工作中,我每天都有机会 ...

  3. Docker-单宿主机下的网络模式

    docker利用namespaces和cgroups实现了应用隔离和资源控制,那么网络层优势如何实现的呢?是直接使用宿主机的网卡设备,还是独立创造出自己的网络设备?以及容器如何与外界通信,下面我们通过 ...

  4. AOP 还在配置吗改用打标签模式吧!

    为什么我喜欢打标签来配置AOP 1. 配置多很混乱,代码里面很难分辨出来哪些是AOP容器(比如属性注入) 2. 对于代码生成器生成的代码里面还需要手动加到配置里面 3. 连java spring现在都 ...

  5. jeecg入门操作—字典配置

    一.字典入口 系统 管理->数据字典 二.录入字典 填写字典相关信息 三 .添加字典内容 填写字典某条记录内容 再录入几条 录入之后的字典内容 四.使用字典 1.编辑用户表单,添加用户等级字段 ...

  6. 使用栈实现队列(1)(Java)

    class MyQueue { private Stack s1; private Stack s2; public MyQueue(int size) { this.s1 = new Stack(s ...

  7. ansible-playbook(node_exporter)

    roles/node_exporter/tasks/main.yml - name: copy package copy: src=node_exporter-0.17.0.linux-amd64.t ...

  8. Python之多线程多进程

    (一)进程 1.定义 进程:就是一组资源的集合.一个程序就是一个进程. 线程是用来干活的,只有进程的话是没办法运行的,进程里其实是线程在具体干活的. import threading import t ...

  9. php框架之thinkphp

    日常开发中经常使用thinkphp5进行开发工作,总结一些使用中遇到的问题和使用的东西 1. web内置服务 V5.1.5+版本开始,增加了启动内置服务器的指令,方便测试 >php think ...

  10. Excel vba中访问ASP.NET MVC项目,记录访问时间,文件名称

    每30秒连接一次服务器,连接成功单元格变绿色,连接失败变红色,状态单元格为17行,2列 1,打开excel文件,进入vba编辑器,新建一个modules模块,在里面先写一个每30秒执行一次ConnSe ...