从零开始实现一个简易的Java MVC框架(三)--实现IOC

Spring中的IOC
IoC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想。而DI(Dependency Injection),即依赖注入就是Ioc的一种实现方式。关于Ioc和DI的具体定义和优缺点等大家可以自行查找资料了解一下,这里就不详细赘述,总之spring的IoC功能很大程度上便捷了我们的开发工作。

在实现我们的Ioc之前,我们先了解一下spring的依赖注入,在spring中依赖注入有三种方式,分别是:

接口注入(Interface Injection)
设值方法注入(Setter Injection)
构造注入(Constructor Injection)
@Component
public class ComponentA {
    @Autowired // 1.接口注入
    private ComponentB componentB;

    @Autowired // 2.设值方法注入
    public void setComponentB(ComponentB componentB) {
        this.componentB = componentB;
    }

    @Autowired // 3.构造注入
    public ComponentA(ComponentB componentB) {
        this.componentB = componentB;
    }
}
循环依赖注入
如果只是实现依赖注入的话实际上很简单,只要利用java的反射原理将对应的属性‘注入’进去就可以了。但是必须要注意一个问题,那就是循环依赖问题。循环依赖就是类之间相互依赖形成了一个循环,比如A依赖于B,同时B又依赖于A,这就形成了相互循环。

// ComponentA
@Component
public class ComponentA {
    @Autowired
    private ComponentB componentB;
}

// ComponentB
@Component
public class ComponentB {
    @Autowired
    private ComponentA componentA;
}
那么在spring中又是如何解决循环依赖问题的呢,我们大致说一下原理。

如果要创建一个类,先把这个类放进‘正在创建池‘中,通过反射等创建实例,创建成功的话就把这个实例放入创建池中,并移除‘正在创建池‘中的这个类。每当实例中有依赖需要注入的话,就从创建池中找对应的实例注入进去,如果没有找到实例,则先创建这个依赖。

利用了这个正在创建的中间状态缓存,让Bean的创建的时候即使有依赖还没有实例化,可以先把Bean放进这个中间状态,然后跑去创建那个依赖,假如那个依赖的类又依赖与这个Bean,那么只要在‘正在创建池‘中再把这个Bean拿出来,注入到这个依赖中,就可以保证Bean的依赖能够实例化完成。再回头来把这个依赖注入到Bean中,那么这个Bean也实例化完成了,就把这个Bean从‘正在创建池‘移到‘创建完成池‘中,就解决了循环依赖问题。

虽然spring巧妙的避免了循环依赖问题,但是事实上构造注入是无法避免循环依赖问题的。因为在实例化ComponentA的构造函数的时候必须得到ComponentB的实例,但是实例化ComponentB的构造函数的时候又必须有ComponentA的实例。这两个Bean都不能通过反射实例化然后放到‘正在创建池‘,所以无法解决循环依赖问题,这时候spring就会主动抛出BeanCurrentlyInCreationException异常避免死循环。

* 注意,前面讲的这些都是基于spring的单例模式下的,如果是多例模式会有所不同,大家有兴趣可以自行了解。

实现IOC
现在可以开始实现IOC功能了

增加注解

先在zbw.ioc包下创建一个annotation包,然后再创建一个Autowired的注解。这个注解的Target只有一个ElementType.FIELD,就是只能注解在属性上。意味着我们目前只实现接口注入的功能。这样可以避免构造注入造成的循环依赖问题无法解决,而且接口注入也是用的最多的方式了。如果想要实现设值方式注入大家可以自己去实现,实现原理几乎都一样。

package com.zbw.ioc.annotation;

import ...

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

实现IOC类

package com.zbw.ioc;

import ...

@Slf4j
public class Ioc {

    /**
     * Bean容器
     */
    private BeanContainer beanContainer;

    public Ioc() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 执行Ioc
     */
    public void doIoc() {
        for (Class<?> clz : beanContainer.getClasses()) { //遍历Bean容器中所有的Bean
            final Object targetBean = beanContainer.getBean(clz);
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) { //遍历Bean中的所有属性
                if (field.isAnnotationPresent(Autowired.class)) {// 如果该属性被Autowired注解,则对其注入
                    final Class<?> fieldClass = field.getType();
                    Object fieldValue = getClassInstance(fieldClass);
                    if (null != fieldValue) {
                        ClassUtil.setField(field, targetBean, fieldValue);
                    } else {
                        throw new RuntimeException("无法注入对应的类,目标类型:" + fieldClass.getName());
                    }
                }
            }
        }
    }

    /**
     * 根据Class获取其实例或者实现类
     */
    private Object getClassInstance(final Class<?> clz) {
        return Optional
                .ofNullable(beanContainer.getBean(clz))
                .orElseGet(() -> {
                    Class<?> implementClass = getImplementClass(clz);
                    if (null != implementClass) {
                        return beanContainer.getBean(implementClass);
                    }
                    return null;
                });
    }

    /**
     * 获取接口的实现类
     */
    private Class<?> getImplementClass(final Class<?> interfaceClass) {
        return beanContainer.getClassesBySuper(interfaceClass)
                .stream()
                .findFirst()
                .orElse(null);
    }

}

在知道IOC的原理之后发现其实真的是非常简单,这里用了几十行的代码就实现了IOC的功能。

首先在Ioc类构造的时候先获取到我们之前已经单例化的BeanContainer容器。

然后在doIoc()方法中就是正式实现IOC功能的了。

遍历在BeanContainer容器的所有Bean
对每个Bean的Field属性进行遍历
如果某个Field属性被Autowired注解,则调用getClassInstance()方法对其进行注入
getClassInstance()会根据Field的Class尝试从Bean容器中获取对应的实例,如果获取到则返回该实例,如果获取不到,则我们认定该Field为一个接口,我们就调用getImplementClass()方法来获取这个接口的实现类Class,然后再根据这个实现类Class在Bean容器中获取对应的实现类实例。
测试用例

为了测试我们的Ioc和之前写的BeanContainer编写正确,我们写一下测试用例测试一下。

先在pom.xml添加junit的依赖

<properties>
    ...
    <junit.version>4.12</junit.version>
</properties>
<dependencies>
    ...
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>
然后在test包下添加DoodleController、DoodleService、DoodleServiceImpl三个类方便测试

// DoodleController
package com.zbw.bean;
@Controller
@Slf4j
public class DoodleController {
    @Autowired
    private DoodleService doodleService;

    public void hello() {
        log.info(doodleService.helloWord());
    }
}

// DoodleService
package com.zbw.bean;
public interface DoodleService {
    String helloWord();
}

// DoodleServiceImpl
package com.zbw.bean;
@Service
public class DoodleServiceImpl implements DoodleService{
    @Override
    public String helloWord() {
        return "hello word";
    }
}
再编写IocTest的测试用例

package com.zbw.ioc;

import ...

@Slf4j
public class IocTest {
    @Test
    public void doIoc() {
        BeanContainer beanContainer = BeanContainer.getInstance();
        beanContainer.loadBeans("com.zbw");
        new Ioc().doIoc();
        DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
        controller.hello();
    }
}

看到在DoodleController中输出了DoodleServiceImpl的helloWord()方法里的字符串,说明DoodleController中的DoodleService已经成功注入了DoodleServiceImpl。那么我们的IOC的功能也完成了。

源码地址:doodle

原文地址:从零开始实现一个简易的Java MVC框架(三)--实现IOC

原文地址:https://www.cnblogs.com/piwefei/p/12071853.html

时间: 2025-01-02 14:39:43

从零开始实现一个简易的Java MVC框架(三)--实现IOC的相关文章

如何设计一个易用的MVC框架

导言 把一件简单的事情做复杂很容易,把一件复杂的事情做简单却不易.在计算机的世界里, 冯.诺依曼把复杂的电脑简化为:存储器,控制器,运算器和I/O设备; 丹尼斯·里奇把晦涩的汇编语言简化为258页的<C程序设计语言>; 詹姆斯高斯林把繁琐的跨平台编码简化为256条字节码指令: 对我们大部分人而言,把简单的事情做简单就足够了. 关于框架 框架是对某一类共通业务的封装,框架设计应该遵循几个基本的原则:1 易用性 2 稳定性3 扩展性,框架从来都是给别人用 的,框架的学习成本与他的复杂度成正比,如果

使用EF Code First搭建一个简易ASP.NET MVC网站,允许数据库迁移

本篇使用EF Code First搭建一个简易ASP.NET MVC 4网站,并允许数据库迁移. 创建一个ASP.NET MVC 4 网站. 在Models文件夹内创建Person类. public class Person { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } 在Controls文件夹内创建PersonControlle

依赖注入[4]: 创建一个简易版的DI框架[上篇]

本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度对依赖注入进行了深入论述,为了让读者朋友能够更好地理解.NET Core的依赖注入框架的设计思想和实现原理,我们创建了一个简易版本的DI框架,也就是我们在前面文章中多次提及的Cat.我们会上下两篇来介绍这个被称为为Cat的DI框架,上篇介绍编程模型,下篇关注设计实现.[源代码从这里下载] 目录一.DI容器的层

依赖注入[5]: 创建一个简易版的DI框架[下篇]

为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]>中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的设计和实现. 目录一.服务注册:ServiceRegistry 二.DI容器:Cat 三.扩展方法 一.服务注册:ServiceRegistry 由于作为DI容器的Cat对象总是利用预先添加到服务注册来提供对应的服务实例,所以服务注册至关重要.如下

Java MVC框架性能比较

- by zvane 现在各种MVC框架很多,各框架的优缺点网络上也有很多的参考文章,但介绍各框架性能方面差别的文章却不多,本人在项目开发中,感觉到采用了struts2框架的项目访问速度,明显不如原来采用了struts1框架的项目快,带着这些疑惑,我对各类MVC框架的做了一个简单的性能分析比较,其结果应该说是基本符合预期的,可供大家参考. 测试环境:CPU:酷睿2 T5750,内存:DDR2-667 2G,Web容器:Tomcat6.0,最大线程数设置为1000,操作系统:WinXP-sp3 测

聊一个自己写的MVC框架

也有个一周没有更新博客了,其实我没有偷懒,因为之前一直在看Spring源码,所以想着去写一个类Spring的框架,也没有给自己定什么高的要求,简单实现MVC.AOP.IOC等功能就行.现在这个框架基本上已经成型了,自己也测试过了,因此拿出来和大家分享一下. 我本文就不写标题了,因为自己的思路是跟着代码走的,所以可能说着说着MVC就跳到DI那一块了.首先我在开始的时候,也是跟随着大部分人的思路,先从DispatcherServlet入手.因为为DispatcherServlet是和用户交互的.和D

一个简单的Python MVC框架(4)

前面都是准备,这里是整个Web Mvc框架的核心地方,稍微多介绍一下.核心类是一个叫Dspth的模块.这里我没有处理路由,一个是不太熟,另外一个是主要是体会架构.我用的路由规则如下:1)用URL的地址参数进行路由,有两个参数,一个是ctr,表示控制类,一个是act表示执行的方法2)所有执行方法都约定参数格式如下self,Environ,CGI,CGITB,Form,Cookies,SessionId,*Args当然,真正做框架的时候,可以把Environ,CGI,CGITB,Form,Cooki

java 集合框架(三)Collection

Collection是集合框架的根接口,一个集合代表一组对象,我们称之为元素.不同的集合具有不同的特性,比如有的集合可以有重复元素,有的不可以,有的可以排序,有的不可排序,如此等等,而Collection作为集合的根接口,它规范定义了集合的通用方法,一个集合我们可以看作一个在内存中的小型数据库,而数据库的常用操作无外乎"增删改查",Collection中的方法也大体是这些类型操作. Java集合框架中没有直接实现Collection的子类,而是通过一系列子接口,比如List,Set,Q

简单的JAVA MVC框架模式--Java-servlet-JavaBean

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码 此框架模式是一个简单的解决个人所得税计算的业务逻辑 servlet import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import ja