大白话聊框架设计(入门篇) | 第四章:简单实现IOC容器
文章目录
- **1.简单实现IOC容器**
- **2.简单介绍IOC容器**
- **3.创建Inject注解**
- **4.创建IocContrainer**
- **5.初始化IOC容器**
- **6.改造原有New对象方式**
- **6.1 DispatchFilter改造后完整代码**
- **6.2 ActionMapper改造后的完整代码**
- **7.最终工程目录结构**
- **8.测试改造后的项目**
- **8.1 启动tomcat**
- **8.2 在浏览器输入URL**
- **8.3 输出结果**
1.简单实现IOC容器
【思考】
我们都知道创建对象可以通过new方式创建,但是为了降低代码耦合度和提高代码扩展性,我们希望由框架来帮我们管理对象的生命周期和帮我们注入对象,该怎么实现呢?
接下来我们带着以下几个问题去进入下一章教程:
- 容器什么时候帮我们注入对象?
- 容器如何去扫描对应的属性并注入?
- 为什么需要使用IOC容器?
- 谁去帮我们加载IOC容器?
- 什么是IOC容器
- 如何实现IOC容器?
- IOC容器需要多少个实例?
2.简单介绍IOC容器
Java程序员都知道:java程序中的每个业务逻辑至少需要两个或以上的对象来协作完成,通常,每个对象在使用他的合作对象时,自己均要使用像new object() 这样的语法来完成合作对象的申请工作。你会发现:对象间的耦合度高了。而IOC的思想是:容器来实现这些相互依赖对象的创建、协调工作。对象只需要关系业务逻辑本身就可以了。从这方面来说,对象如何得到他的协作对象的责任被反转了(IOC、DI)
回顾之前我们事先的DispatchFilter中的doFilter方法
ublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {//filter执行过滤器System.out.println("filter执行");//根据请求找到对应的mapping类ActionMapper actionMapper = new ActionMapper();ActionMapping mapping =actionMapper.findMapping(request);if(mapping!=null){WebExecutor executor = new WebExecutor();//执行用户请求webExecutor.execute(request,response,mapping);}else {//非框架处理部分,继续执行其他环节chain.doFilter(request, response);}}
由上面代码可以看出,dispatchFileter每次接受请求,都会去创建一个ActionMapper和WebExecutor对象,直接使用关键字类new一个对象,这样有什么坏处呢?假如针对不同的请求会有不同的ActionMapper实现类,那么在这里就需要针对每个ActionMapping的实现都得new一个,这样会导致当前模块直接与New的对象耦合了,不利于以后的扩展,而且也违背了Java推荐的面向接口面向抽象编程。
IOC带来的好处?
先从IOC说起,这个概念其实是从我们平常new一个对象的对立面来说的,我们平常使用对象的时候,一般都是直接使用关键字类new一个对象,那这样有什么坏处呢?其实很显然的,使用new那么就表示当前模块已经不知不觉的和new的对象耦合了,而我们通常都是更高层次的抽象模块调用底层的实现模块,这样也就产生了模块依赖于具体的实现,这样与我们Java中提倡的面向接口面向抽象编程是相冲突的,而且这样做也带来系统的模块架构问题。很简单的例子,我们在进行数据库操作的时候,总是业务层调用DAO层,当然我们的DAO一般都是会采用接口开发,这在一定程度上满足了松耦合,使业务逻辑层不依赖于具体的数据库DAO层。但是我们在使用的时候还是会new一个特定数据库的DAO层,这无形中也与特定的数据库绑定了,虽然我们可以使用抽象工厂模式来获取DAO实现类,但除非我们一次性把所有数据库的DAO写出来,否则在进行数据库迁移的时候我们还是得修改DAO工厂类。
那我们使用IOC能达到什么呢?IOC,就是DAO接口的实现不再是业务逻辑层调用工厂类去获取,而是通过容器(比如spring)来自动的为我们的业务层设置DAO的实现类。这样整个过程就反过来,以前是我们业务层主动去获取DAO,而现在是DAO主动被设置到业务逻辑层中来了,这也就是反转控制的由来。通过IOC,我们就可以在不修改任何代码的情况下,无缝的实现数据库的换库迁移,当然前提还是必须得写一个实现特定数据库的DAO。我们把DAO普遍到更多的情况下,那么IOC就为我们带来更大的方便性,比如一个接口的多个实现,我们只需要配置一下就ok了,而不需要再一个个的写工厂来来获取了。这就是IOC为我们带来的模块的松耦合和应用的便利性。
说白了其实就是由我们平常的new转成了使用反射来获取类的实例。
现在相信大家对IOC也有大概了解了,接下来在下一章我们带着问题思考下怎么去设计一个IOC容器:IOC容器怎么用?什么时候用?如何帮我们去创建和注入对象?
根据以往使用框架经验,我们对象注入都是框架去帮我们实现的,我们不需要理会引用的对象是来自哪个实现类,我们只需要声明其接口或者抽象类,调用接口方法即可,由框架去帮我们去负责找到对应的实现类注入。
private ActionMapper actionMapper;private boolean flag;
我们在当前模块声明这样一行代码,框架怎么帮我们找到哪些才是真正想注入的属性呢?很明显,flag这种基本类型并不是我们想要框架帮我们去注入的,我们只是想要ActionMapper的实现类,假如我们没有一个标识去让框架识别,那么框架就会把当前模块所有声明的属性都帮我们去实例化并注入,这并不是我们想要的。那这时候我们可以对需要让框架注入的属性加上一个注解,去告诉框架那些需要框架帮我们管理的。
3.创建Inject注解
我们使用Inject注解去告诉框架哪些属性是可以让其帮我们注入的
【思路】
后面框架会通过扫描所有@Inject标注的属性,然后帮我们去创建和注入对象
/*** Created by liangyh on 2016/11/24.* Email:10856214@163.com*/@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Inject {}
4.创建IocContrainer
IOC容器的主要职责是管理对象生命周期、扫描并注入对象,为了让IOC容器能够找到需要被注入的属性,前面章节我们实现了Inject注解,那么想需要IOC容器帮忙注入的属性,只需要在其上面加上inject注解即可,IOC容器就可以帮我们注入
@Inject
private ActionMapper actionMapper;
接下来我们开始动手去实现一个IOC容器
【思路】
- 对外提供一个方法去获取容器实例
- 提供一个注入方法用于注入对象
- 提供一个加载方法用于加载class与其实例到容器
全局对象的生命周期都交给IOC容器,为了保证所有模块获取的IOC容器都是同一个,因此IOC容器应该只能有一个实例,在这里我们可以使用懒汉模式,等待真正有人调用的时候,才去创建容器实例,并且我们不能让其他模块去实例化这个类,因此我们还需要把构造函数私有化。
/*** Ioc容器* Created by liangyh on 2016/11/24.* Email:10856214@163.com*/public class IocContrainer {private static IocContrainer INSTANCE;private IocContrainer() {}/*** 获取Ioc容器实例(懒汉模式)* @return*/public static synchronized IocContrainer getInstance() {if (null == INSTANCE) {INSTANCE = new IocContrainer();}return INSTANCE;}}
IOC容器需要对所有带有inject注解的属性都进行注入,因为应该对外提供一个injct方法,用于注入对象的。首先会获取当前传入对象的所有声明属性,然后判断它上面有没有Inject注解,如果存在的话,则利用反射对当前属性进行加载并注入到当前传入对象中。因为有些属性可能是使用private进行修饰的,所以我们还需要破解其访问权限,还有一点需要注意的,就是当前对象其属性本身可能也存在需要注入的属性,因为在这里我们还是需要利用递归算法,一层层去注入,直到所有属性及其自身属性都被一起注入。
我们需要声明一个context用于存放对象的字节码及其实例的映射,因为加载对象其实就是把对象存放到内存中
private static Map<Class,Object> context = new HashMap<Class,Object>();
接下来我们实现inject和loadClass方法,用于注入对象和类的加载
public void inject(Object obj) {//获取所有声明属性Field[] fields = obj.getClass().getDeclaredFields();for(Field field : fields){if(field.isAnnotationPresent(Inject.class)){//加载实例Object fieldObj = loadClass(field.getType());//暴力破解属性的访问权限field.setAccessible(true);//对目标对象的属性进行注入try {field.set(obj,fieldObj);} catch (IllegalAccessException e) {e.printStackTrace();}}}}/*** 类加载* @param clazz* @return*/private Object loadClass(Class clazz){//创建实例Object obj = context.get(clazz);if(obj!=null){return obj;}try {//创建实例obj = clazz.newInstance();//把实例与其字节码映射存放在内存中context.put(clazz,obj);//继续注入当前对象其属性inject(obj);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return obj;}
上面loadClass方法中之所以要先判断context里是否已经存在当前字节码的实例,是为了避免重复创建。loadClass方法里之所以再调用一次inject方法,是为了注入当前传入的属性其自身带有的属性,简单来说就是这个属性本身也是一个对象,该对象自身也有被inject注解标识的属性,因为也需要注入,这里运用了递归思想。
IOC容器完整代码如下:
/*** Ioc容器* Created by liangyh on 2016/11/24.* Email:10856214@163.com*/public class IocContrainer {private static IocContrainer INSTANCE;private static Map<Class,Object> context = new HashMap<Class,Object>();private IocContrainer() {}/*** 获取Ioc容器实例(懒汉模式)* @return*/public static synchronized IocContrainer getInstance() {if (null == INSTANCE) {INSTANCE = new IocContrainer();}return INSTANCE;}public void inject(Object obj) {//获取所有声明属性Field[] fields = obj.getClass().getDeclaredFields();for(Field field : fields){if(field.isAnnotationPresent(Inject.class)){//加载实例Object fieldObj = loadClass(field.getType());//暴力破解属性的访问权限field.setAccessible(true);//对目标对象的属性进行注入try {field.set(obj,fieldObj);} catch (IllegalAccessException e) {e.printStackTrace();}}}}/*** 类加载* @param clazz* @return*/private Object loadClass(Class clazz){//创建实例Object obj = context.get(clazz);if(obj!=null){return obj;}try {//创建实例obj = clazz.newInstance();//把实例与其字节码映射存放在内存中context.put(clazz,obj);//继续注入当前对象其属性inject(obj);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return obj;}}
5.初始化IOC容器
IOC容器初始化是在项目启动的时候,因此在本教程,IOC容器初始化会放在DispatchFilter的init方法中,当项目启动的时候tomcat就会调用DispatchFilter中的init方法(前面我们已经在web.xml中配置),因此这个时候IOC容器就会被创建,而且会开始对当前filter用到的属性进行注入,DispatchFilter的init方法如下:
public void init(FilterConfig filterConfig) throws ServletException {//创建IOC容器//为了保证全局只使用一个ioc容器,这里使用单例模式创建对象IocContrainer ioc = IocContrainer.getInstance();//注入属性ioc.inject(this);}
6.改造原有New对象方式
前几章我们用到的DispatchFilter和ActionMapper两个类都是使用New方式把其依赖的模块实例引用进来,在本章我们需要使用我们注解方式,改成通过IOC容器来帮我们实现注解。
1.DispatchFilter依赖ActionMapper和WebExecutor两个类,它改造前引用方式如下:
//根据请求找到对应的mapping类ActionMapper actionMapper = new ActionMapper();
WebExecutor executor = new WebExecutor();
现在我们通过框架来帮我们将这两个类的实例注入,改造后代码如下:
@Injectprivate ActionMapper actionMapper;@Injectprivate WebExecutor webExecutor;
2.ActionMapper依赖WebConfig这个类,改造前引用方式如下:
//从配置文件获取所有规则匹配映射WebConfig webConfig = new WebConfig();
同样,我们通过框架来帮我们将这个类的实例注入,改造后代码如下:
@Injectprivate WebConfig webConfig;
6.1 DispatchFilter改造后完整代码
*** 核心请求分发过滤器* Created by liangyh on 2016/9/21.* Email:10856214@163.com*/public class DispatchFilter implements Filter {@Injectprivate ActionMapper actionMapper;@Injectprivate WebExecutor webExecutor;public void init(FilterConfig filterConfig) throws ServletException {//创建IOC容器//为了保证全局只使用一个ioc容器,这里使用单例模式创建对象IocContrainer ioc = IocContrainer.getInstance();//注入属性ioc.inject(this);}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {//filter执行过滤器System.out.println("filter执行");//根据请求找到对应的mapping类//ActionMapper actionMapper = new ActionMapper();ActionMapping mapping =actionMapper.findMapping(request);if(mapping!=null){//WebExecutor executor = new WebExecutor();//执行用户请求webExecutor.execute(request,response,mapping);}else {//非框架处理部分,继续执行其他环节chain.doFilter(request, response);}}public void destroy() {}}
6.2 ActionMapper改造后的完整代码
/*** Created by liangyh on 2016/11/19.* Email:10856214@163.com*/public class ActionMapper {@Injectprivate WebConfig webConfig;public ActionMapping findMapping(ServletRequest request) {ActionMapping actionMapping = new ActionMapping();//把请求实体类型转换成HttpServletRequestHttpServletRequest req = (HttpServletRequest) request;//从请求实体中获取UriString uri = req.getRequestURI();//根据Uri地址截取用户请求的方法名String methodName = uri.substring(req.getContextPath().length() + 1);//从配置文件获取所有规则匹配映射// WebConfig webConfig = new WebConfig();Map<String, Mapping> mappings = webConfig.getMappings();//根据请求方法名找到匹配的映射类Mapping mapping = mappings.get(methodName);//根据通配符查找mappingif (mapping == null) {//遍历所有mappings里的所有keyIterator<String> iter = mappings.keySet().iterator();while (iter.hasNext()) {String matchKey = iter.next();//匹配当前请求方法,如匹配则返回对应的mappingactionMapping = matchMapping(methodName, matchKey, mappings);if (actionMapping != null) {return actionMapping;}}} else {return convertActionMapping(methodName, mapping);}return actionMapping;}/*** 转换ActionMapping** @param methodName* @param mapping*/private ActionMapping convertActionMapping(String methodName, Mapping mapping) {//把mapping转换成对应的actionMapping返回ActionMapping actionMapping = new ActionMapping();actionMapping.setClassName(mapping.getClassName());actionMapping.setMethodName(methodName);actionMapping.setResult(mapping.getResult());return actionMapping;}/*** 匹配映射* <pre>* methodName:helloPersonAction* matchKey: *PersonAction* mapping:new Mapping("com.eshare.action.PersonAction","{1}","SUCCESS.jsp")* </pre>** @param methodName 方法名* @param matchKey 匹配关键字* @param mappings 映射配置* @return*/private ActionMapping matchMapping(String methodName, String matchKey, Map<String, Mapping> mappings) {String regexKey = matchKey;//查看当前关键字是否包含通配符*if (matchKey.contains("*")) {//对通配符替换成正则表达式regexKey = matchKey.replaceAll("\\*", "(\\.\\*)");}//把关键字编译成正则表达式实例Pattern pattern = Pattern.compile(regexKey);Matcher matcher = pattern.matcher(methodName);Mapping mapping;//通过正则表达式与方法名相匹配if (matcher.find()) {//匹配上则通过关键字去查找对应的mappingmapping = mappings.get(matchKey);//框架使用者配置的方法参数名称String configMethod = mapping.getMethodName();//请求实际的处理方法String method;//配置的方法是否有括号,如果存在,则是一个占位符if (configMethod.contains("{")) {//把所有括号去掉configMethod = configMethod.replaceAll("\\{", "").replaceAll("\\}", "");//把数字转换成索引int index = Integer.valueOf(configMethod);//通过matcher的group方法将请求方法与占位符匹配替换method = matcher.group(index);} else {//假如配置方法参数不存在占位符,则直接是最后的执行方法名method = configMethod;}//把解析出来的处理方法名存储到mapping中,替换原来的占位符return convertActionMapping(method, mapping);}return null;}}
7.最终工程目录结构
8.测试改造后的项目
8.1 启动tomcat
8.2 在浏览器输入URL
我的上下文路径是eshare
http://localhost:8080/eshare/sayHelloPersonAction
8.3 输出结果
大白话聊框架设计(入门篇) | 第四章:简单实现IOC容器相关推荐
- 大白话聊框架设计(入门篇) | 第三章:通配符匹配Mapping实现
文章目录 **1.通配符匹配Mapping实现** **2.重构ActionMapper** **3.修改WebConfig配置** **4.新增Mapping构造方法** **5.新增PersonA ...
- 大白话聊框架设计(入门篇) | 第一章:Filter实现框架拦截
文章目录 前言 1Filter实现框架拦截 1.1配置自定义Filter 1.2创建一个Filter 1.3创建一个ActionMapping 1.4创建一个ActionMapper 1.5创建一个W ...
- 大白话聊框架设计(入门篇) | 第二章:规则匹配Mapping实现
文章目录 **1.规则匹配Mapping实现** **2.重构ActionMapper** **2.1 改造前** **2.2 改造后** **3.创建一个Mapping** **4.创建一个WebC ...
- C语言入门——《明解C语言》入门篇第四章练习
新开C语言专栏整理一下学C的艰苦之路,先从看书+视频开始. 整理了<明解C语言>入门篇第四章练习的"参考答案",记录一下学习的过程.直接上代码. 练习4-1 int m ...
- 视频教程-Unity客户端框架设计PureMVC篇视频课程(上)-Unity3D
Unity客户端框架设计PureMVC篇视频课程(上) 二十多年的软件开发与教学经验IT技术布道者,资深软件工程师.具备深厚编程语言经验,在国内上市企业做项目经理.研发经理,熟悉企业大型软件运作管理过 ...
- 视频教程-Unity客户端框架设计PureMVC篇视频课程(下)-Unity3D
Unity客户端框架设计PureMVC篇视频课程(下) 二十多年的软件开发与教学经验IT技术布道者,资深软件工程师.具备深厚编程语言经验,在国内上市企业做项目经理.研发经理,熟悉企业大型软件运作管理过 ...
- Android入门篇(四):自动拨打电话、手动拨打电话
Android入门篇(四):自动拨打电话.手动拨打电话 一.前言 最近在做的项目需要用到自动拨号的这一功能,17年写了一个,最近拿出来用发现不能使用了,后面查资料据说是因为Android 6(api2 ...
- CC2530入门篇————实现四盏灯全亮
CC2530入门篇--实现四盏灯全亮 宏定义引脚 初始化引脚 亮灯 宏定义引脚 #include<iocc2530.h> //四个引脚分别对应板子上四个小灯 #define LED1 P1 ...
- R语言学习笔记——入门篇:第一章-R语言介绍
R语言 R语言学习笔记--入门篇:第一章-R语言介绍 文章目录 R语言 一.R语言简介 1.1.R语言的应用方向 1.2.R语言的特点 二.R软件的安装 2.1.Windows/Mac 2.2.Lin ...
最新文章
- 你当年没玩好的《愤怒的小鸟》,AI现在也犯难了
- Mysql 时间操作(当天,昨天,7天,30天,半年,全年,季度)
- HDMI_VGA_CBVS同时显示
- html认识数字游戏大全,认识数字小游戏,就是这么简单!为孩子收藏
- android 蓝牙传输分包,彻底掌握Android多分包技术(一)
- 蓝桥杯单片机stc15f2k61s2矩阵按键中断扫描代码
- arduino 温度调节器_Arduino用温湿度传感器控制继电器,为什么点了串口助手才能运行,拔掉usb线,直接外界9V电源却用不了...
- byte初始化并赋值_一位数组的定义、赋值和初始化.note
- Android mainfests手记
- DHCP:(3)思科防火墙ASA上部署DHCP服务以及DHCP中继
- python开发一个PC屏幕监控软件(2000块的道德底线)
- 【存储知识】存储基础知识(存储设备、HBA卡、硬盘接口类型、存储特性指标)
- 阿里巴巴Java开发手册认证考试题库
- 51单片机 八音盒设计
- 【Python与数据分析实验报告】Pandas数据分析基础应用
- linux系统子接口配置文件,linux配置子接口
- 文件服务器文件夹,共享文件夹及权限迁移
- scanf函数、冒泡排序和不定长度数组的使用 —— malloc
- EP Henry推出世界首批用二氧化碳固化的Solidia Concrete制造的预制产品
- SpringSecurity(Web权限方案)
热门文章
- linux读写红外线设备,Linux中的红外线设备(转)
- pywintypes.com_error: (-2147221008, ‘尚未调用 CoInitialize。‘, None, None)
- 下面没有了......
- 不得不服!Chrome 灵魂插件!
- ChatGLM-6B介绍
- 几十年后咱们死了 QQ号怎么办?
- c#连接字符串形式访问微软共享文件夹sharefile
- 【知识分享】儿童编程学习规划(Scratch/Python/C++)
- LeetCode 27.移除元素
- 全网最全15家银行提额秘籍