Spring之手写SpringMVC5个注解

前言

在此之前先感谢两位大佬,第一个是我的同学,这老哥牛逼,本文出现的代码依托于他给的代码,另一位大佬是写《Spring5核心原理与30个类手写实战 》的作者,因为这个其实是从那里来的,代码其实也是那本书上的,只是这里做个记录,仔细梳理一下整个流程。

环境

环境预览

首先是我们环境准备,这个环境准备的话就是,那个先前说的那个创建 java web 项目。

之后的话就是没啥了,我们这个部分主要写的是那个SpringMVC里面的几个注解。

在这里插入图片描述
项目结构是这样的
在这里插入图片描述
在这里插入图片描述

环境测试

在这里插入图片描述
在这里插入图片描述

OK,项目,一切正常

项目搭建

接下来是搭建我们的项目

mymvcframwork搭建

包创建与注解编写

在这里插入图片描述
我们编写的是Version1
HU 表示的是我们自己敲的注解。

注解代码如下

package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

@Target({
    
    ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HUAutowired {
    
    
    String value() default "";
}

package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HUController {
    
    
    String value() default "";
}

package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

@Target({
    
    ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HURequestMapping {
    
    
    String value() default "";
}

package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

@Target({
    
    ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HURequestParam {
    
    
    String value() default "";
}

package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HUService {
    
    
    String value() default "";
}

HUDispatcherServlet

这个就是要帮助我们实现所有功能的类,里面包含了一套完整的IOC容器。

web.xml配置

我们的那个HUDispatcherServlet是基础了HttpServlet的
在这里插入图片描述
目的是我们要实现那个mvc的功能像这样
在这里插入图片描述
那么我们就必须要拿到请求权。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

  <display-name>Archetype Created Web Application</display-name>


  <servlet>
    <servlet-name>gpmvc</servlet-name>
    <servlet-class>com.huterox.mvcframework.servlet.Version1.HUDispatcherServlet</servlet-class>
    <init-param>
      <param-name>Myproperties</param-name>
      <param-value>application.properties</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<!--    tomcat启动的时候后创建执行init-->
  </servlet>
  <servlet-mapping>
    <servlet-name>gpmvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

我们代理全部请求
这样做的目的是为了让请求流都能被我们的这个HUDispatchServlet捕获然后才能做后面的处理。

工作流程

我们的工作流程大概就是这六步
在这里插入图片描述

doLoadConfig

这个其实就是获取我们的配置的IO流。
在这里插入图片描述
获取我们需要处理的包

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

doScanner

这个东西就是扫描,去把那个包下面的那个类的路径都保存起来,给下一步处理。

    private void doScanner(String scanPackage){
    
    
        // 扫描我们想要控制的那个包下面的所有类
        //存进去的是 com.huterox.test.xx(类名)
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));

        assert url != null;
        File classDir = new File(url.getFile());
        for(File file: Objects.requireNonNull(classDir.listFiles())){
    
    
            if(file.isDirectory()){
    
    
                this.doScanner(scanPackage+"."+file.getName());
            }else {
    
    
                if (!file.getName().endsWith(".class")) continue;
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(clazzName);
            }
        }
    }

doInstance

这个就是把那个上一步得到的类的路径,按照我们的规则去加入到IOC容器里面,没有打上我们注解的都拜拜。

    private void doInstance(){
    
    
        if(classNames.isEmpty()){
    
    
            return;
        }
        for(String className:classNames){
    
    
            try {
    
    
                Class<?> clazz = Class.forName(className);

               if(clazz.isAnnotationPresent(HUController.class)){
    
    

                   Object instance = clazz.newInstance();
                   //类名首字母小写
                   String beanName = toLowerFirstCase(clazz.getSimpleName());
                   IOC.put(beanName,instance);
               } else if (clazz.isAnnotationPresent(HUService.class)) {
    
    

                   HUService service = clazz.getAnnotation(HUService.class);
                   String beanName = service.value();
                   if("".equals(beanName.trim())){
    
    
                       beanName = toLowerFirstCase(clazz.getSimpleName());
                   }
                   Object instance = clazz.newInstance();
                   IOC.put(beanName,instance);

                   //把该对象实现的接口也搞进去
                   for (Class<?> i:clazz.getInterfaces()){
    
    
                       if(IOC.containsKey(i.getName())){
    
    
                           throw new Exception("该类实现的接口"+i.getName()+"已存在");
                       }
                       IOC.put(i.getName(),instance);
                   }
               }else {
    
    
                   continue;
               }

            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

这里要注意的是还有一个方法
这个就是把首字母小写的,按照标准处理bean的名字。

    private String toLowerFirstCase(String simpleName){
    
    
        //字母转换,符合javabean标准
        char[] chars = simpleName.toCharArray();
        if(67<=(int)chars[0]&&(int)chars[0]<=92)
            chars[0] +=32;
        return String.valueOf(chars);
    }

doAutowired

依赖注入嘛
这个也是有规则的,代码有注释

   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(HUAutowired.class))) continue;
                HUAutowired autowired = field.getAnnotation(HUAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName)){
    
    
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);

                try {
    
    
                    //这个就是为什么要按照标准来写首字母要小写
                    field.set(Entry.getValue(),IOC.get(beanName));
                } catch (IllegalAccessException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

    }

doInitHandlerMapping

这个玩意是用来保存URL的。就是这个
在这里插入图片描述

   private void doInitHandlerMapping() {
    
    

        if(IOC.isEmpty()) return;

        for (Map.Entry<String, Object> Entry : IOC.entrySet()) {
    
    
            Class<?> clazz = Entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(HUController.class)) continue;
            String baseURl="";
            if(clazz.isAnnotationPresent(HURequestMapping.class)){
    
    
                HURequestMapping requestMapping = clazz.getAnnotation(HURequestMapping.class);
                baseURl = requestMapping.value();
            }
            for (Method method : clazz.getMethods()) {
    
    
                if(!method.isAnnotationPresent(HURequestMapping.class))continue;
                HURequestMapping requestMapping = method.getAnnotation(HURequestMapping.class);
                String url =("/" + baseURl +"/"+ requestMapping.value()).replaceAll("/+","/");

                handlerMapping.put(url,method);
                //把url和对应的method放在一起
            }
        }
    }

doDispatch

这个就是我们最后要读取浏览器发送的url 然后匹配对应的方法,然后执行。

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    
    
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        //tomcat里面配的一开始的那个不要
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");
        System.out.println(url);
        if(!this.handlerMapping.containsKey(url)){
    
    
            resp.getWriter().write("404 Not Found!");
            return;
        }

        //获取请求参数
        Map<String,String[]> paramsMap = req.getParameterMap();

        Method method = this.handlerMapping.get(url);
//        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//        Object o = IOC.get(beanName);
//        //这部分是一些那啥参数
//        method.invoke(o,new Object[]{req,resp,params.get("name")[0]});

        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] paramValues = new Object[parameterTypes.length];
//        动态给值

        for (int i = 0; i < parameterTypes.length; i++) {
    
    
            Class<?> parameterType = parameterTypes[i];
            if(parameterType==HttpServletRequest.class){
    
    
                paramValues[i] = req;
                continue;
            }else if(parameterType==HttpServletResponse.class){
    
    
                paramValues[i] =resp;
                continue;
            }else{
    
    
                //目前只是针对四个类型类型的
                Annotation[][] pa = method.getParameterAnnotations();
                //pa[0]表示第一个参数 pa[0][0]第一个参数的第一个注解
                for(int j=0;j<pa.length;j++){
    
    
                    for(Annotation a:pa[i]){
    
    
                        if(a instanceof HURequestParam){
    
    
                            String paramName = ((HURequestParam) a).value();
                            if(!"".equals(paramName)){
    
    
                                String strings =Arrays.toString(paramsMap.get(paramName))
                                        .replaceAll("\\[|\\]","")
                                        .replaceAll("\\s","");//过滤
                                if(parameterType==String.class){
    
    

                                    paramValues[i] = strings;

                                }else if(parameterType==Integer.TYPE ||parameterType==Integer.class ){
    
    

                                    paramValues[i] = Integer.valueOf(strings);
                                }else if(parameterType==Double.TYPE ||parameterType==Double.class ){
    
    

                                    paramValues[i] = Double.valueOf(strings);
                                }else if(parameterType==Float.TYPE ||parameterType==Float.class ){
    
    
                                    paramValues[i] = Float.valueOf(strings);
                                }

                            }

                        }
                    }
                }

            }
        }
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(IOC.get(beanName),paramValues);

    }

调用

这里把完整代码都搞出来

package com.huterox.mvcframework.servlet.Version1;

import com.huterox.mvcframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class HUDispatcherServlet extends HttpServlet {
    
    

    private Map<String,Object> IOC = new HashMap<>();
    private Properties contextConfig = new Properties();
    private List<String> classNames = new ArrayList<>();
    private Map<String,Method> handlerMapping = new HashMap<>();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        this.doPost(req, resp);//保证post/get同时都能处理
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        try {
    
    
            System.out.println("hello this request will be processed by mymvcframeword ");
            this.doDispatch(req,resp);//相应的处理
        } catch (Exception e) {
    
    
            e.printStackTrace();
            resp.getWriter().write("服务器异常500:"+Arrays.toString(e.getStackTrace()));
        }
    }


    @Override
    public void init(ServletConfig config) throws ServletException {
    
    
        //初始化配置
        //1.加载配文件
        doLoadConfig(config.getInitParameter("Myproperties"));
        //2.扫描相关类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化IOC容器,实例化相关类
        doInstance();
        //4.完成DI注入
        doAutowired();
        //5.初始化HandlerMapping(配置器)
        doInitHandlerMapping();
        //6.完成~~给请求分发器,去去执行对应方法


    }


    private void doLoadConfig(String contextproperties) {
    
    
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextproperties);
        try {
    
    
            contextConfig.load(is);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            if(is!=null){
    
    
                try {
    
    
                    is.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
    private void doScanner(String scanPackage){
    
    
        // 扫描我们想要控制的那个包下面的所有类
        //存进去的是 com.huterox.test.xx(类名)
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));

        assert url != null;
        File classDir = new File(url.getFile());
        for(File file: Objects.requireNonNull(classDir.listFiles())){
    
    
            if(file.isDirectory()){
    
    
                this.doScanner(scanPackage+"."+file.getName());
            }else {
    
    
                if (!file.getName().endsWith(".class")) continue;
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(clazzName);
            }
        }
    }
    private void doInstance(){
    
    
        if(classNames.isEmpty()){
    
    
            return;
        }
        for(String className:classNames){
    
    
            try {
    
    
                Class<?> clazz = Class.forName(className);

               if(clazz.isAnnotationPresent(HUController.class)){
    
    

                   Object instance = clazz.newInstance();
                   //类名首字母小写
                   String beanName = toLowerFirstCase(clazz.getSimpleName());
                   IOC.put(beanName,instance);
               } else if (clazz.isAnnotationPresent(HUService.class)) {
    
    

                   HUService service = clazz.getAnnotation(HUService.class);
                   String beanName = service.value();
                   if("".equals(beanName.trim())){
    
    
                       beanName = toLowerFirstCase(clazz.getSimpleName());
                   }
                   Object instance = clazz.newInstance();
                   IOC.put(beanName,instance);

                   //把该对象实现的接口也搞进去
                   for (Class<?> i:clazz.getInterfaces()){
    
    
                       if(IOC.containsKey(i.getName())){
    
    
                           throw new Exception("该类实现的接口"+i.getName()+"已存在");
                       }
                       IOC.put(i.getName(),instance);
                   }
               }else {
    
    
                   continue;
               }

            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }
    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(HUAutowired.class))) continue;
                HUAutowired autowired = field.getAnnotation(HUAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName)){
    
    
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);

                try {
    
    
                    //这个就是为什么要按照标准来写首字母要小写
                    field.set(Entry.getValue(),IOC.get(beanName));
                } catch (IllegalAccessException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

    }
    private void doInitHandlerMapping() {
    
    

        if(IOC.isEmpty()) return;

        for (Map.Entry<String, Object> Entry : IOC.entrySet()) {
    
    
            Class<?> clazz = Entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(HUController.class)) continue;
            String baseURl="";
            if(clazz.isAnnotationPresent(HURequestMapping.class)){
    
    
                HURequestMapping requestMapping = clazz.getAnnotation(HURequestMapping.class);
                baseURl = requestMapping.value();
            }
            for (Method method : clazz.getMethods()) {
    
    
                if(!method.isAnnotationPresent(HURequestMapping.class))continue;
                HURequestMapping requestMapping = method.getAnnotation(HURequestMapping.class);
                String url =("/" + baseURl +"/"+ requestMapping.value()).replaceAll("/+","/");

                handlerMapping.put(url,method);
                //把url和对应的method放在一起
            }
        }
    }
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    
    
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        //tomcat里面配的一开始的那个不要
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");
        System.out.println(url);
        if(!this.handlerMapping.containsKey(url)){
    
    
            resp.getWriter().write("404 Not Found!");
            return;
        }

        //获取请求参数
        Map<String,String[]> paramsMap = req.getParameterMap();

        Method method = this.handlerMapping.get(url);
//        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//        Object o = IOC.get(beanName);
//        //这部分是一些那啥参数
//        method.invoke(o,new Object[]{req,resp,params.get("name")[0]});

        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] paramValues = new Object[parameterTypes.length];
//        动态给值

        for (int i = 0; i < parameterTypes.length; i++) {
    
    
            Class<?> parameterType = parameterTypes[i];
            if(parameterType==HttpServletRequest.class){
    
    
                paramValues[i] = req;
                continue;
            }else if(parameterType==HttpServletResponse.class){
    
    
                paramValues[i] =resp;
                continue;
            }else{
    
    
                //目前只是针对String类型的
                Annotation[][] pa = method.getParameterAnnotations();
                //pa[0]表示第一个参数 pa[0][0]第一个参数的第一个注解
                for(int j=0;j<pa.length;j++){
    
    
                    for(Annotation a:pa[i]){
    
    
                        if(a instanceof HURequestParam){
    
    
                            String paramName = ((HURequestParam) a).value();
                            if(!"".equals(paramName)){
    
    
                                String strings =Arrays.toString(paramsMap.get(paramName))
                                        .replaceAll("\\[|\\]","")
                                        .replaceAll("\\s","");//过滤
                                if(parameterType==String.class){
    
    

                                    paramValues[i] = strings;

                                }else if(parameterType==Integer.TYPE ||parameterType==Integer.class ){
    
    

                                    paramValues[i] = Integer.valueOf(strings);
                                }else if(parameterType==Double.TYPE ||parameterType==Double.class ){
    
    

                                    paramValues[i] = Double.valueOf(strings);
                                }else if(parameterType==Float.TYPE ||parameterType==Float.class ){
    
    
                                    paramValues[i] = Float.valueOf(strings);
                                }

                            }

                        }
                    }
                }

            }
        }
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(IOC.get(beanName),paramValues);

    }
    private String toLowerFirstCase(String simpleName){
    
    
        //字母转换,符合javabean标准
        char[] chars = simpleName.toCharArray();
        if(67<=(int)chars[0]&&(int)chars[0]<=92)
            chars[0] +=32;
        return String.valueOf(chars);
    }

}

测试

IOC测试

代码我们是搞出来了,但是我们需要测试我们的每一个功能。
首先是我们的测试类

package com.huterox.test;


import com.huterox.mvcframework.annotation.HUController;
import com.huterox.mvcframework.annotation.HURequestMapping;
import com.huterox.mvcframework.annotation.HURequestParam;


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

@HUController
public class TestHello {
    
    

    @HURequestMapping("/test/hello")
    public String sayHello(HttpServletRequest req, HttpServletResponse resp,
                           @HURequestParam("name") String name,
                           @HURequestParam("id") Integer id
                           ) throws IOException {
    
    

        String res = "Hello  -- "+name+"  --  "+id;
        PrintWriter writer = resp.getWriter();
        writer.write(res);
        writer.flush();
        writer.close();
        return res;
    }
}

我这里只有一个类是加入了我们的IOC容器里面,所以我们来玩玩。
在这里插入图片描述

IOC容器正常

HandlerMapping容器

我们来看看这个容器工作
在这里插入图片描述
正常

dopatch测试

接下来我们来看看我们最重要的功能
页面正常
在这里插入图片描述
控制台输出正常
在这里插入图片描述

总结

总的来说,这个还是很有意思的,不过显然在很多层面都有很多需要被考虑的点。周末的话我去图书馆把书嫖出来,再好好玩玩。

猜你喜欢

转载自blog.csdn.net/FUTEROX/article/details/123241412
今日推荐