从头开始实现一个小型spring框架——实现Bean管理 IOC与DI(解决循环依赖)

写在前面

最近学习了一下spring的相关内容,所以也就想要照猫画虎地记录一下spring的框架,通过阅读这些也希望能够消除对Spring框架的恐惧,其实细心阅读也很容易理解。

项目的源码我放在了github上:源码地址

我会在这里整理文章的系列目录:

  1. 从头开始实现一个小型spring框架——手写Spring之实现SpringBoot启动
  2. 从头开始实现一个小型spring框架——手写Spring之集成Tomcat服务器
  3. 从头开始实现一个小型spring框架——控制器controller的实现
  4. 从头开始实现一个小型spring框架——实现Bean管理(IOC与DI)

一、bean管理

1.1 什么是bean?

  • bean是spring抽象出的概念
    区别于普通的对象,bean的声明周期一般较长。
  • 在整个虚拟机中都是可见的
    普通的对象仅在当前类可见,在其他类中是引用不到的。因此对bean的维护成本要高于对普通对象的维护。
  • bean一般单例存在
    正因为对bean的维护成本很高,因此bean单例存在。

1.2 bean优势

  • 运行期效率高
    bean在项目启动时初始化,使用时直接获取,提高了获取对象的性能;也减少了代码在维护上成本;减少了对象使用过后被抛弃的情况,降低虚拟机在gc时的压力。
  • 统一维护,便于管理和拓展
    不必处理链式的依赖
  • 单例存在

二、依赖注入和控制反转

很多人认为控制反转和依赖注入是相同的,其实这是一个误区。

2.1 控制反转

IOC(Inversion of Control):思想
控制反转是一种设计的原则和思想,用来降低代码之间的耦合度。

2.2 依赖注入

DI(Dependency Injection):方式
依赖注入是实现IOC的一种方式。
采用依赖注入的方式,类A中只需要定义一个B的属性,不需要通过new来获取对象,二是通过bean容器将对象new出来,并注入到A对象的引用里。

控制正转方式:

控制反转创建的方式:

三、实现

因为我们已经在上一篇文章中实现了包扫描,所以可以非常方便获取到类的信息。
获取到类的信息后,我们就要根据是否存在需要注入的依赖来实例化 Bean对象了,我们来看具体的实现:

3.1 包结构的改变

直接看下图

更改后的内容添加了对依赖注入的实现,具体有

framework模块:

  • beans包下增加
    • Autowired自定义注解:用于标记需要进行注入的域
    • Bean自定义注解:标记类为需要实例化的Bean
      test模块
  • service包增加
    • UserService :实现到controller的注入

3.2 具体实现

framework模块

Autowired标记注解

package com.qcby.beans;

import java.lang.annotation.*;

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

Bean注解

package com.qcby.beans;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
}

BeanFactory工厂

这是本次实现的核心代码,将Bean通过map的形式进行保存,提供getBean,initBean方法对Bean容器内的实例进行获取和初始化Bean容器

package com.qcby.beans;

import com.qcby.web.mvc.Controller;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author kevinlyz
 * @ClassName BeanFactory
 * @Description bean工厂
 * @Date 2019-06-09 11:09
 **/
public class BeanFactory {

    //ConcurrentHashMap保存bean
    public static Map<Class<?>,Object> classToBean = new ConcurrentHashMap<>();

    //获取bean
    public static Object getBean(Class<?> cls){
        return classToBean.get(cls);
    }

   //初始化
    public static void initBean(List<Class<?>> classList) throws Exception {
        List<Class<?>> toCreate = new ArrayList<>(classList);
       
        //循环实例化bean,判断是否存在循环依赖
        while (toCreate.size() != 0){
            int remainSize = toCreate.size();
            for (int i=0;i<toCreate.size();i++){
                if (finishCreate(toCreate.get(i))){
                    toCreate.remove(i);
                }
            }
            if (toCreate.size() == remainSize){         //陷入死循环,抛出异常
                throw new Exception("循环依赖异常");
            }
        }
    }

    //创建bean
    private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
        //不是bean直接返回true并删除
        if (!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)){
            return true;
        }

        //创建对象
        Object bean = cls.newInstance();

        //解决存在的依赖;获取域并判断是否被Autowired注解
        for (Field field : cls.getDeclaredFields()){
            if (field.isAnnotationPresent(Autowired.class)){        //存在Autowired注解
                Class<?> fieldType = field.getType();
                Object reliantBean = BeanFactory.getBean(fieldType);        //从Bean工厂内获取被依赖的bean
                if (reliantBean == null){
                    return false;
                }
                field.setAccessible(true);
                field.set(bean,reliantBean);            //为bean设置依赖域
            }
        }
        classToBean.put(cls,bean);
        return true;
    }
}

修改MiniApplication,对Bean工厂进行初始化


BeanFactory.initBean(classList);

完整代码为

package com.qcby.starter;

import com.qcby.beans.BeanFactory;
import com.qcby.core.ClassScanner;
import com.qcby.web.handler.HandlerManagger;
import com.qcby.web.server.TomcatServer;

import java.util.List;

/**
 * @author kevinlyz
 * @ClassName MiniApplication
 * @Description 框架的入口类
 * @Date 2019-06-04 19:21
 **/
public class MiniApplication {
    public static void run(Class<?> cls,String[] args){
        System.out.println("Hello mini-spring application!");
        TomcatServer tomcatServer = new TomcatServer(args);
        try {
            tomcatServer.startServer();
            List<Class<?>> classList = ClassScanner.scannClasses(cls.getPackage().getName());
            BeanFactory.initBean(classList);
            classList.forEach(it->System.out.println(it.getName()));
            HandlerManagger.resolveMappingHandler(classList);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

修改MappingHandler获取Bean的方式为从工厂中获取


 Object ctl = BeanFactory.getBean(controller);
 

完整代码为

package com.qcby.web.handler;


import com.qcby.beans.Bean;
import com.qcby.beans.BeanFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author kevinlyz
 * @ClassName MappingHandler
 * @Description 包含uri,类的方法信息,类信息和参数
 * @Date 2019-06-08 18:32
 **/
public class MappingHandler {

    private String uri;
    private Method method;
    private Class<?> controller;
    private String[] args;

    public MappingHandler(String uri, Method method, Class<?> controller, String[] args) {
        this.uri = uri;
        this.method = method;
        this.controller = controller;
        this.args = args;
    }

    public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
        String reqUri = ((HttpServletRequest)req).getRequestURI();
        if (!this.uri.equals(reqUri))
            return false;

        //相等则调用Handler的resolveMappingHandler方法,实例化并返回
        Object[] parameters = new Object[args.length];
        for(int i=0;i<args.length;i++){
            parameters[i] = req.getParameter(args[i]);
        }

        Object ctl = BeanFactory.getBean(controller);
        Object response = method.invoke(ctl,parameters);
        res.getWriter().println(response.toString());
        return true;
    }
}

test模块

添加UserService

package com.qcby.service;

import com.qcby.beans.Bean;

/**
 * @author kevinlyz
 * @ClassName UserService
 * @Description TODO
 * @Date 2019-06-09 13:37
 **/
@Bean
public class UserService {

    public String getInfo(String name){
        return name+" is handsome!!";
    }
}

修改UserController

package com.qcby.controller;

import com.qcby.beans.Autowired;
import com.qcby.service.UserService;
import com.qcby.web.mvc.Controller;
import com.qcby.web.mvc.RequestMapping;
import com.qcby.web.mvc.RequestParam;

/**
 * @author kevinlyz
 * @ClassName UserController
 * @Description TODO
 * @Date 2019-06-08 17:14
 **/
@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/test")
    public String test(@RequestParam("name") String name,@RequestParam("desc") String desc){
        System.out.println("test访问了!");
        System.out.println("name>>>>>>>"+name);
        return userService.getInfo(name);
    }
}

3.3 测试

再熟悉不过的流程了

构建:gradle build
运行:java -jar test/build/libs/test-1.0-SNAPSHOT.jar

然后在浏览器输入需要传递的参数,这里仅传输了一个那么用于测试,看到如下结果:

完事!!!
到此,我们的依赖注入也成功集成到了框架之中!

四、小结

在这一篇我们使用反射的方式对依赖注入进行了实现,增加了Bean注解用于标记一个类是否是需要管理的Bean,Autowired注解用于注解在何处注入实例化后的Bean。以及核心的实现类BeanFactory工厂,对外提供了获取bean的getBean方法和初始化工厂的initBean的方法。同时修改MappingHandler获取Bean的方式为从工厂中获取。

测试中增加了UserService并用@Bean标记,并在UserController中注入,成功完成测试。

五、补充——循环依赖问题

在这里我们要详细的解释一下循环依赖的问题。这种问题在使用filed注入和setter的形式进行注入不会存在循环依赖的问题(不是不存在,而是Spring已经进行了解决,解决的方式就是通过Spring的三级缓存。)但是通过构造器的方式进行注入的话就会存在循环依赖的问题。

采用构造器注入的方式,如我们有一个类A和类B

class A{
	private B b;
	
	public A(B b){
		this.b = b;
	}
}


class B{
	private A a;
	
	public B(A a){
		this.a = a;
	}
}

这种注入方式的话,我们会在创建bean A的使用执行构造方法,并且立即创造B的bean。在创建B的bean的时候发现构造方法需要用到A的bean,并且立即创建。此时的创建过程便产生了循环的依赖问题。

我们的mini-spring中也存在循环以来的原因是我们在加载bean容器是,也就是BeanFactory时就会进行bean的创建。类似构造器的注入,我们需要创建所有的bean对象,并把相应的域注入进去,所以在注入的时候也会存在这么一个注入的问题。

六、框架总结

到这里,我们的小型Spring框架的开发就彻底的结束了。其实项目中还存在着许多的缺陷和不足,在细节方面没有很细的雕琢,如果有大佬进行了改进和实现,欢迎在github里面提交代码呀。

  • 项目中首先对架构进行了介绍,将项目分为了framewok模块和test模块两个,分别负责框架的实现和测试,在简单的介绍了gradle的相关知识后,实现了SpringBoot的启动方式。
  • 第二篇对Web请求的流程进行了介绍和分析,分析了请求到Web服务器的传输过程。在此基础上,我们对项目中的包结构进行了扫描,并实现拦截了TestServlet中的请求,最终将Tomcat容器集成在了mini-spring框架之中。
  • 第三篇是对控制器的核心实现。我们对容器内部的处理流程做了进一步的分析,分析其中存在的问题和Spring对请求方式处理上的改进。根据Spring的实现方式,我们对mini-spring框架进行了实现,ClassScanner获取项目的包结构,使用DispatcherServlet对/下的请求进行了拦截,基于Controller、RequestMapping、RequestParam三个注解实现信息的扫描和配置。
  • 第四篇在第三篇包扫描的基础上进一步实现依赖注入,首先对控制反转和依赖注入的区别进行了分析,进而在BeanFactory中解决了容器中存在的循环依赖的问题,最终完成我们的mini-spring框架!

项目中还可以进一步完善,如Controller的参数注解的改进,依赖注入的性能,还可以结合数据持久层的框架,作进一步完善!

  • 35
    点赞
  • 148
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值