手写Spring之简单实现ioc功能模块

前言

本人第一次写博客,不太了解markdown的书写规范,如有不妥之处敬请谅解!本篇博客中主要针对Spring中的ioc功能进行简单的实现,具体的目录如下。

ioc容器介绍

在进行具体的设计之前,首先了解一下ioc的基本知识(已经掌握的朋友们可以跳过这一部分)。
容器(container)这个词感觉已经被滥用了,什么都是容器。容器顾名思义就是装东西的东西。例如水杯是一个容器,用来装水。IoC容器就是用来装应用中的对象实例。注意我强调的是对象,这意味着同一类型的对象你可以在IoC容器中放好几个。IoC容器中,IoC的意思就是Inversion of Control(控制反转)。首先明确一下这里的控制是控制什么?这里的控制范范的说就是控制对象的生命周期,明确到一个典型点来说就是对象的实例化,再具体一点来说就是将对象依赖的其它对象传递给该对象。在一般情况下,建立一个对象都是通过new,静态方法,工厂方法等等手段。springFramework采用了一种比较高效的机制,它将实例化好的对象全部存在一个类似map结构的容器当中,每一个key都对应一个实例化对象。

// Spring ioc容器
private Map<String,Object> ioc=new HashMap<String,Object>();

配置文件配置

在功能实现之前首先将开发环境配置好,笔者使用的开发环境为idea,工具jdk8+tomcat7,建立一个web项目,完成以下的配置准备工作。

web.xml文件

在这里需要配置好自定义的一个servlet,即hongSpringDispatcherServlet,url-pattern设置为/以此拦截所有请求

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>hongSpringDispatcherServlet</servlet-name>
        <servlet-class>Spring.hongSpringDispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>applicationHong.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Map all requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>hongSpringDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

applicationHong.properties文件

该文件为模仿spring的配置文件,目前只包含了扫描器,确定扫描的包

scanPackage=Spring.mvc

自定义注解

自定义开发过程中所需要用到的注解hongAutoWired,hongController,hongRequestMapping,hongRequestParam,hongService

package Spring.annotation;

import java.lang.annotation.*;
//自动注入注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface hongAutoWired {
    public String value() default "";
}

其他注解类似,在这里就不一一贴出来了,可以在下文的github仓库中下载源代码

controller测试Demo

package Spring.mvc.controller;

import Spring.annotation.hongAutoWired;
import Spring.annotation.hongController;
import Spring.annotation.hongRequestMapping;
import Spring.mvc.service.IDemoService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@hongController
@hongRequestMapping("/demo")
public class DemoController {
    @hongAutoWired
    IDemoService demoService;
    @hongRequestMapping("/add")
    public void add(HttpServletRequest request, HttpServletResponse response,String name) throws IOException {
//        request.setCharacterEncoding("UTF-8");
        name=new String(name.getBytes("ISO-8859-1"),"UTF-8");
        String value=demoService.add(name);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write(value);
    }
    @hongRequestMapping("/delete")
    public void delete(HttpServletRequest request, HttpServletResponse response,String name) throws IOException {
        String value=demoService.delete(name);
        response.getWriter().write(value);
    }
    @hongRequestMapping("/update")
    public void update(HttpServletRequest request, HttpServletResponse response,String name) throws IOException {
        String value=demoService.update(name);
        response.getWriter().write(value);
    }
}

service测试Demo

service接口

package Spring.mvc.service;

public interface IDemoService {
    String add(String name);
    String delete(String name);
    String update(String name);
}

service具体的实现类

package Spring.mvc.service.serviceImpl;

import Spring.annotation.hongService;
import Spring.mvc.service.IDemoService;
@hongService
public class DemoService implements IDemoService {


    @Override
    public String add(String name) {
        return "添加成功from service    "+name;
    }

    @Override
    public String delete(String name) {
        return "删除成功from service    "+name;
    }

    @Override
    public String update(String name) {
        return "更新成功from service   "+name;
    }
}

好了,前期的准备工作都已经完成,那么下面开始具体的实现

ioc容器具体实现

首先给出代码实现的流程图

Created with Raphaël 2.2.0 调用init()方法 ioc容器初始化 扫描相关的类 实例化并保存至容器 DI依赖注入 初始化HandlerMapping

对流程图进行大致的讲解,首先调用init()方法加载配置文件,即找到web.xml文件中contextConfigLocation所对应的文件的位置,然后是ioc容器的初始化,创建一个map对象。再扫描相关的类,找到scanPackage需要实例化的类,将扫描到的类通过反射进行实例化,并将得到的实例保存到ioc容器中。再遍历这些实例化的类,寻找是否有需要自动注入的类,有则完成字段的注入,最后一步将测试controller中的方法和程序访问的url进行绑定,保证能够在浏览器访问到该方法。描述比较简洁,下面来看具体的实现。

自定义servlet

首先自定义一个servlet类继承HttpServlet,下面是该类需要用到的容器。

    private Properties contextConfig=new Properties();
    private List<String> classNames=new ArrayList<String>();
    private Map<String,Object> ioc=new HashMap<String,Object>();
    private Map<String,Method> handlerMapping=new HashMap<String,Method>();

重写servlet的init()方法,在该方法中定义了大致的调用流程

   public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件

        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2.解析配置文件,扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //3.初始化所有相关的类,并且将其放入到IOC容器中
        doInstance();

        //4.完成依赖注入
        doAutoWired();

        //5.初始化HandlerMapping映射
        initHandlerMapping();
}

加载配置文件

通过输入流读取web.xml中所指定的Spring配置文件,将之保存到属性容器contextConfig中

    private void doLoadConfig(String contextConfigLocation) {
        InputStream is= this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (null!=is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

解析配置文件,扫描相关的类

根据Spring配置文件所在位置进行扫描,只保留后缀名为.class结尾的文件,将文件名保存到classNames容器中,如果当前文件是文件夹,递归调用该方法,继续下一步扫描。

    private void doScanner(String scanPackage) {
        URL url=this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/"));
        File classpath=new File(url.getFile());
        for (File file : classpath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage +"."+ file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }

    }

初始化所有相关的类,并且将其放入到IOC容器中

遍历classNames容器中所保存的类的全限命名,找到对应的类,判断类名前是否有hongController或者hongService注解,有则通过反射新建一个实例,模仿Spring的方式,默认类名首字母小写作为Map的key,实例对象作为对应的value,保存到ioc容器(HashMap)中。注意接口的实例化对象就是子类的实例化对象,因此需要保证接口只有一个子类,否则将不知道如何选择子类。

    private void doInstance() {
        if (classNames.isEmpty()){return;}
        for (String className : classNames) {
            Class<?> clazz= null;
            try {
                clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(hongController.class)){
                String beanName=toLowerFirstCase(clazz.getSimpleName());
                Object instance=clazz.newInstance();
                ioc.put(beanName,instance);
                }
                else if (clazz.isAnnotationPresent(hongService.class)){
                    //1.默认的类名为首字母小写
                    String beanName=toLowerFirstCase(clazz.getSimpleName());
                    //2.自定义的beanName
                    hongService service=clazz.getAnnotation(hongService.class);
                    if (!"".equals(service.value()))
                    {
                        beanName=service.value();
                    }
                    Object instance=clazz.newInstance();
                    ioc.put(beanName,instance);
                    //3.创建接口的实例,怎么办?
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName()))
                        {
                            throw new Exception("The beanName has existed in ioc");
                        }
                        ioc.put(i.getSimpleName(),instance);
                    }
                }
                else {
                    continue;
                    }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

完成依赖注入

遍历ioc容器,完成依赖注入,本文实现的是通过字段注入,遍历中的每一个对象,通过类加载找到对象所对应的类,再遍历该类的所有字段,判断字段前是否有hongAutoWired注解,有则获取该字段的简单名,去匹配ioc容器的键值对,找寻该简单名所对应的实例对象,将该实例对象的值赋值给该字段。

    private void doAutoWired() {
        if (ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
           Field[] fields= entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(hongAutoWired.class)){continue;}

                hongAutoWired autoWired=field.getAnnotation(hongAutoWired.class);
                String beanName=autoWired.value().trim();
                if ("".equals(beanName)) {
                    beanName=field.getType().getSimpleName();
                }
//beanName这里没有问题吗?
                field.setAccessible(true);//强制访问

                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

        }

    }

初始化HandlerMapping映射

在这一步,同样是首先遍历ioc容器,找到容器对应实例所在的类,再继续寻找声明了hongController注解的类,用方法前定义的hongRequestMapping注解的值作为键,方法名作为值,保存到一个map容器中。注意:如果class前也声明了hongRequestMapping注解,则键需要class注解的值和方法注解值。

    private void initHandlerMapping() {
        if (ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz=entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(hongController.class)){continue;}

            String baseUrl="";
            if (clazz.isAnnotationPresent(hongRequestMapping.class)){
                hongRequestMapping requestMapping=clazz.getAnnotation(hongRequestMapping.class);
                baseUrl=requestMapping.value();
            }

            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(hongRequestMapping.class)){continue;}
                hongRequestMapping requestMapping=method.getAnnotation(hongRequestMapping.class);
                String url=("/"+baseUrl+"/"+requestMapping.value()).replaceAll("/+","/");//正则表达式替换任意个/为单个/
                handlerMapping.put(url,method);
                System.out.println("Mapped:" +url+":"+method);

            }

        }

    }

以上就是ioc容器的简单实现,下面我们测试一下代码

代码测试

在serlvet的doDispatch(HttpServletRequest req, HttpServletResponse resp)中方法简单处理,根据浏览器传过来的请求,取得url并解析为相对路径,通过url在handlerMapping容器中找到对应的方法,通过反射调用该方法。

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url=req.getRequestURI();
        String contextUrl=req.getContextPath();
        url=url.replaceAll(contextUrl,"").replaceAll("/+","/");
        if (!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 FileNotFound!");
            return;
        }
        Method method=handlerMapping.get(url);
        Map<String,String[]> params=req.getParameterMap();
        String beanName=toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(ioc.get(beanName),new Object[]{req,resp,params.get("name")[0]});
    }

我们运行该程序,在浏览器地址栏输入http://localhost:8080/spring/demo/add?name=“小红”
得到的页面为:
在这里插入图片描述
至此,我们完成ioc容器功能的简单实现。完整的代码可以在本人的github中获取,仓库地址为 https://github.com/Geek12580/Spring-IOC.

参考文章:
springIoC概述.
spring系列(一)——简介和IOC.

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值