文章目录

  • **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方式创建,但是为了降低代码耦合度和提高代码扩展性,我们希望由框架来帮我们管理对象的生命周期和帮我们注入对象,该怎么实现呢?
接下来我们带着以下几个问题去进入下一章教程:

  1. 容器什么时候帮我们注入对象?
  2. 容器如何去扫描对应的属性并注入?
  3. 为什么需要使用IOC容器?
  4. 谁去帮我们加载IOC容器?
  5. 什么是IOC容器
  6. 如何实现IOC容器?
  7. 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容器

【思路】

  1. 对外提供一个方法去获取容器实例
  2. 提供一个注入方法用于注入对象
  3. 提供一个加载方法用于加载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容器相关推荐

  1. 大白话聊框架设计(入门篇) | 第三章:通配符匹配Mapping实现

    文章目录 **1.通配符匹配Mapping实现** **2.重构ActionMapper** **3.修改WebConfig配置** **4.新增Mapping构造方法** **5.新增PersonA ...

  2. 大白话聊框架设计(入门篇) | 第一章:Filter实现框架拦截

    文章目录 前言 1Filter实现框架拦截 1.1配置自定义Filter 1.2创建一个Filter 1.3创建一个ActionMapping 1.4创建一个ActionMapper 1.5创建一个W ...

  3. 大白话聊框架设计(入门篇) | 第二章:规则匹配Mapping实现

    文章目录 **1.规则匹配Mapping实现** **2.重构ActionMapper** **2.1 改造前** **2.2 改造后** **3.创建一个Mapping** **4.创建一个WebC ...

  4. C语言入门——《明解C语言》入门篇第四章练习

    新开C语言专栏整理一下学C的艰苦之路,先从看书+视频开始. 整理了<明解C语言>入门篇第四章练习的"参考答案",记录一下学习的过程.直接上代码. 练习4-1 int m ...

  5. 视频教程-Unity客户端框架设计PureMVC篇视频课程(上)-Unity3D

    Unity客户端框架设计PureMVC篇视频课程(上) 二十多年的软件开发与教学经验IT技术布道者,资深软件工程师.具备深厚编程语言经验,在国内上市企业做项目经理.研发经理,熟悉企业大型软件运作管理过 ...

  6. 视频教程-Unity客户端框架设计PureMVC篇视频课程(下)-Unity3D

    Unity客户端框架设计PureMVC篇视频课程(下) 二十多年的软件开发与教学经验IT技术布道者,资深软件工程师.具备深厚编程语言经验,在国内上市企业做项目经理.研发经理,熟悉企业大型软件运作管理过 ...

  7. Android入门篇(四):自动拨打电话、手动拨打电话

    Android入门篇(四):自动拨打电话.手动拨打电话 一.前言 最近在做的项目需要用到自动拨号的这一功能,17年写了一个,最近拿出来用发现不能使用了,后面查资料据说是因为Android 6(api2 ...

  8. CC2530入门篇————实现四盏灯全亮

    CC2530入门篇--实现四盏灯全亮 宏定义引脚 初始化引脚 亮灯 宏定义引脚 #include<iocc2530.h> //四个引脚分别对应板子上四个小灯 #define LED1 P1 ...

  9. R语言学习笔记——入门篇:第一章-R语言介绍

    R语言 R语言学习笔记--入门篇:第一章-R语言介绍 文章目录 R语言 一.R语言简介 1.1.R语言的应用方向 1.2.R语言的特点 二.R软件的安装 2.1.Windows/Mac 2.2.Lin ...

最新文章

  1. 你当年没玩好的《愤怒的小鸟》,AI现在也犯难了
  2. Mysql 时间操作(当天,昨天,7天,30天,半年,全年,季度)
  3. HDMI_VGA_CBVS同时显示
  4. html认识数字游戏大全,认识数字小游戏,就是这么简单!为孩子收藏
  5. android 蓝牙传输分包,彻底掌握Android多分包技术(一)
  6. 蓝桥杯单片机stc15f2k61s2矩阵按键中断扫描代码
  7. arduino 温度调节器_Arduino用温湿度传感器控制继电器,为什么点了串口助手才能运行,拔掉usb线,直接外界9V电源却用不了...
  8. byte初始化并赋值_一位数组的定义、赋值和初始化.note
  9. Android mainfests手记
  10. DHCP:(3)思科防火墙ASA上部署DHCP服务以及DHCP中继
  11. python开发一个PC屏幕监控软件(2000块的道德底线)
  12. 【存储知识】存储基础知识(存储设备、HBA卡、硬盘接口类型、存储特性指标)
  13. 阿里巴巴Java开发手册认证考试题库
  14. 51单片机 八音盒设计
  15. 【Python与数据分析实验报告】Pandas数据分析基础应用
  16. linux系统子接口配置文件,linux配置子接口
  17. 文件服务器文件夹,共享文件夹及权限迁移
  18. scanf函数、冒泡排序和不定长度数组的使用 —— malloc
  19. EP Henry推出世界首批用二氧化碳固化的Solidia Concrete制造的预制产品
  20. SpringSecurity(Web权限方案)

热门文章

  1. linux读写红外线设备,Linux中的红外线设备(转)
  2. pywintypes.com_error: (-2147221008, ‘尚未调用 CoInitialize。‘, None, None)
  3. 下面没有了......
  4. 不得不服!Chrome 灵魂插件!
  5. ChatGLM-6B介绍
  6. 几十年后咱们死了 QQ号怎么办?
  7. c#连接字符串形式访问微软共享文件夹sharefile
  8. 【知识分享】儿童编程学习规划(Scratch/Python/C++)
  9. LeetCode 27.移除元素
  10. 全网最全15家银行提额秘籍