通过手写SpringMVC理解IOC和DI

Demo结构和说明

1.整体项目结构和环境

通过手写SpringMVC理解IOC和DI_第1张图片

整个demo分为两个模块:应用模块(com.clf.demo包下);手写mvc框架模块(com.clf.mvcframework包下)

环境:jdk1.8, idea,tomcat8.5

创建项目为maven工程的web项目,创建过程可以参考博客

整个项目的源码链接

2.编码流程说明

首先在mvc模块下对常用的几个注解进行自定义:

通过手写SpringMVC理解IOC和DI_第2张图片

在demo中进行常规的应用搭建

实现Controller层

@MyController
@MyRequestMapping("/demo")
public class DemoController {

    @MyAutowired
    private DemoService demoService;

    @MyRequestMapping("/query")
    public void query(HttpServletRequest request,
                      HttpServletResponse response,
                      @MyRequestParam("/name") String name){
        String result = demoService.get(name);
        try {
            PrintWriter writer = response.getWriter();
            writer.write(result);
            if (writer != null){
                writer.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

实现Service层:

@MyService
public class DemoServiceImpl implements DemoService {
    @Override
    public String get(String name) {
        return "success, My name is " + name;
    }
}

然后实现自定义的DispatcherServlet实现配置信息的加载,容器的初始化和依赖的注入等功能。

SpringMVC的创建的主要阶段

通过手写SpringMVC理解IOC和DI_第3张图片

配置阶段

1.配置web.xml添加需要的初始化参数



    Web Application
    
        clfmvc
        com.clf.mvcframework.servlet.ClfDispatcherServlet
        
            
            contextConfigLocation
            application.properties
        
        1
    
    
        clfmvc
        /*
    

2.修改pom.xml配置依赖



    4.0.0

    com.clf.mvcframework
    MySpringMVC
    1.0-SNAPSHOT
    
        UTF-8
        1.8
        1.8
        1.8
    
    
    
        
            javax.servlet
            servlet-api
            2.5
        
    

3.创建properties文件

在resource文件夹下创建application.properties文件

添加应用扫描的包路径,我的整个应用模块在demo包下(具体根据自己的项目结构进行创建):

初始化阶段

首先创建一个自定义的DispatcherServlet继承HttpServlet,然后实现init()方法。

初始化阶段主要包括五个流程:

1.加载配置文件

2.解析配置文件,扫描应用相关的类

3.初始化所有相关的类,并保存到IOC容器中

4.完成自动化的依赖注入,DI

5.创建HandlerMapping将url和method建立对应的关系

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("======================SpringMVC is     initializing=====================");
        /**
         * 1.加载配置文件
         */
        System.out.println("获取初始参数:" + config.getInitParameter("contextConfigLocation"));
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        /**
         * 2.解析配置文件,扫描所有相关的类
         */
        //获取到properties中的scanPackage=com.clf.demo
        doScanner(contextConfig.getProperty("scanPackage"));
        /**
         * 3.初始化所有相关的类.并且保存到IOC容器中
         */
        doInstance();
        /**
         * 4.完成自动化的依赖注入,DI
         */
        doAutowired();
        /**
         * 5.创建HandlerMapping将url和method建立对应关系
         */
        initHandlerMapping();
    }

doLoadConfig():

根据初始化参数获取对应properties文件路径

private void doLoadConfig(String contextConfigLocation) {
        //从类路径下去取得properties
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            //加载所有配置信息
            contextConfig.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

doSacnner():

通过扫描包路径将包下所有的类文件的className存入list集合中,便于后面对类进行遍历读取类名进行bean的实例化

private void doScanner(String scanPackage) {
        //将 com.clf.demo 格式转换成 /com/clf/demo
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replace(".", "/"));
        //拿到文件目录
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            //目录下可能还有多级子包,需要进行递归判断
            if (file.isDirectory()){
                //如果是子文件夹,则进行继续扫描
                doScanner(scanPackage + "." + file.getName());
            }else {
                if (!file.getName().endsWith(".class")){
                    continue;
                }
                //如果拿到的是一个文件,则将对应的路径放到容器中
                String className = (scanPackage + "." + file.getName().replace(".class", "").trim());
                classNames.add(className);
            }
        }
    }

doInstance():

扫描自定义的注解进行bean的实例化并存放到ioc容器中

private void doInstance() {
        //如果list为空, 则没有扫描到任何东西,直接返回
        if (classNames.isEmpty()){ return; }
        try {
            //对扫描到的类进行反射
            for (String className : classNames) {
                //根据className获取到对应的类
                Class clazz = Class.forName(className);
                //并不是所有的手写spring类都需要反射,只对加入自定义注解的进行反射
                if (clazz.isAnnotationPresent(MyController.class)){
                    //获取类的类名,而不包括包名,同时将获取到的类名首字母小写
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, clazz.newInstance());
                }else if (clazz.isAnnotationPresent(MyService.class)){
                    //1.类名首字母小写
                    //2.自定义命名优先
                    MyService service = clazz.getAnnotation(MyService.class);
                    String beanName = service.value();
                    //判断是否有自定义beanName
                    if ("".equals(beanName)){
                        //没有自定义的命名就使用类名首字母小写
                        beanName = lowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    //3.用接口的全称作为key, 用接口的实现类的实例作为值
                    Class[] interfaces = clazz.getInterfaces();
                    for (Class i : interfaces) {
                        //如果存在一个接口的多个实例抛出异常
                        if (ioc.containsKey(i.getName())){
                            throw new Exception("The beanName is exists");
                        }
                        ioc.put(i.getName(), instance);
                    }
                }else {
                    continue;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

doAutowired():

对每个bean的成员属性进行注入,即使用了@Autowired的成员变量

private void doAutowired() {
        if (ioc.isEmpty()){ return; }
        for (Map.Entry entry : ioc.entrySet()) {
            //获取当前类的所有的属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            //遍历属性
            for (Field field : fields) {
                //判断是否是需要进行依赖注入的属性
                if (!field.isAnnotationPresent(MyAutowired.class)){ return; }
                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                //获取自定的注入的beanName
                String beanName = autowired.value();
                if ("".equals(beanName)){
                    //获取到接口的全称
                    beanName = field.getType().getName();
                }
                //强制赋值
                field.setAccessible(true);
                try {
                    //从ioc获取实例进行注入
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    continue;
                }

            }
        }
    }

initHandlerMapping():

将每一个url和需要访问的方法进行映射存入handlerMapping中,保证保证在进行请求url时容器可以根据请求的ur获取Method进行反射调用方法。

private void initHandlerMapping() {
        if (ioc.isEmpty()){ return; }
        for (Map.Entry entry : ioc.entrySet()) {
            Class clazz = entry.getValue().getClass();
            //如果不是一个Controller直接跳过
            if (!clazz.isAnnotationPresent(MyController.class)){ continue; }
            String baseUrl = "";
            //判断是否拥有RequestMapping注解
            if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
                //获取Controller上的baseUrl
                baseUrl = requestMapping.value();
            }
            Method[] methods = clazz.getMethods();
            //遍历类的方法
            for (Method method : methods) {
                //如果没有MyRequestMapping直接跳过
                if (!method.isAnnotationPresent(MyRequestMapping.class)) { continue; }
                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                //获取完整的url,同时使用正则将多个 "/" 替换成只有一个
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped: " + url + "," + method);
            }
        }
    }

运行阶段

运行的主要流程:

1.前端根据url发送请求

2.请求后后端通过自定义的DispatcherServlet获取request

3.通过request获取当前访问的url

4.handlerMapping实际上就是一个Map,分别存放url和对应的Method方法

5.通过获取method对象来获取对应的Class,再由Class名称获取原始的beanName,通过ioc容器由beanName获取bean对象进行注入

    /**
     * 自定义一个HandlerMapping
     */
    private Map handlerMapping = new HashMap();

6.将方法返回的信息写入response

运行结果

启动成功:

访问不存在的url

通过手写SpringMVC理解IOC和DI_第4张图片

请求url:/demo/query

通过手写SpringMVC理解IOC和DI_第5张图片

 

你可能感兴趣的:(Spring)