• 实现一个简易的Spring MVC


    申明:仅作为个人学习笔记,原文请访问: 手写Spring MVC

    如果需要源码,请点击上面的链接。

    总体思路:

    1、Spring MVC是通过注册DispacherServlet,并配置url-pattern为/* 来接管所有的请求。同样的,如果我们需要实现Mvc框架,也需要在web.xml中注册一个自定义的servlet:

    <servlet>
        <servlet-name>xmvc</servlet-name>
        <servlet-class>com.ph.xspring.servlet.XDispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <!--you can't use classpath*: -->
          <param-value>application.properties</param-value>
        </init-param>
      </servlet>
      <servlet-mapping>
        <servlet-name>xmvc</servlet-name>
        <url-pattern>/*</url-pattern>
      </servlet-mapping>

    2、实现MVC的功能,还需要配置一些常用的注解,例如@RequestMapping, @Controller等等。下面列举一个自定义的注解

    @Target({ElementType.TYPE}) //限定注解的使用位置
    @Retention(RetentionPolicy.RUNTIME) //注解的保留政策,如果想要通过反射来获取注解信息,需要在内存中保留注解信息,只能用runtime
    @Documented  //用于制作文档
    public @interface XController {
        String value()default "";
    }

    3、接着要实现XDispacherServlet中的内容,在这个Servlet内读取配置文件、初始化参数、IOC容器初始化、依赖注入等等。这些列初始化的功能,都需要重写HttpServlet中的init方法,在init方法中实现。

    4、初始化完成后,就需要接收并处理浏览器传过来的请求了,将service(GET,POST为例)方法中的请求交给doDispacher来处理,并返回结果。

    下面来解析XDispacherServlet中的内容:

    1、静态变量

    public class XDispatcherServlet extends HttpServlet {
        /*
        属性配置文件
         */
        private Properties contextConfig = new Properties();
        private List<String> classNamelist = new ArrayList<>();
    
        /**
         * IOC容器
         */
        Map<String ,Object>iocMap = new HashMap<String,Object>();
        Map<String, Method>handlerMapping = new HashMap<String, Method>();

    首先是上面几个变量,在下面的init方法中,需要对上述变量进行初始化。

    contextConfig用于表示配置文件,例如applicationContext

    classNameList用来存放用户指定包下的所有java类的字节码的名称,用于后面通过反射来获取类信息。

    iocMap用来表示IOC容器,通过注解或者xml文件注入进来的对象(本文仅通过注解注入),会存放在此。

    handlerMapping用来存放url与对应方法之间的映射关系,例如 在controller中 ”/login“  路径与 Login()方法之间是对应的,通过访问/login,即可调用Login()方法。

    2、初始化

            //1.加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
    
            //2.扫描相关的类
            doScanner(contextConfig.getProperty("scan-package"));
    
            //3.初始化IOC容器,将所有的相关类实例保存到IOC容器中
            doInstance();
    
            //4.依赖注入
            doAutowired();
    
            //5.初始化HandlerMapping
            initHandlerMapping();

    初始化有上述五个步骤,下面会一 一说明。

    2.1、加载配置文件

        private void doLoadConfig(String contextConfigLocation) {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
    
            try {
                //保存在内存
                contextConfig.load(inputStream);
                System.out.println("[INFO-1] property file has been saved in contextConfig.");
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(null != inputStream){
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    需要传入文件名,该文件放在resources目录下,我在该文件配置了所需要扫描的包(下面一个步骤用到),如下

    scan-package=com.ph

    2.2、扫描相关类

           private void doScanner(String scanPackage) {
    
            URL resourcePath = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.","/"));
            System.out.println("INFO-2   "+resourcePath);
            if(resourcePath == null){
                return;
            }
            File classPath = new File(resourcePath.getFile());
            for(File file: classPath.listFiles()){
                if(file.isDirectory()){
                    System.out.println("[INFO-2] {" + file.getName() + "} is a directory.");
                    //子目录递归
                    doScanner(scanPackage + "." + file.getName());
                }else{
                    if(!file.getName().endsWith(".class")){
                        System.out.println("[INFO-2] {" + file.getName() + "} is not a class file.");
                        continue;
                    }
                    String className = (scanPackage+"." +file.getName()).replace(".class","");
                   
                    classNamelist.add(className);
                    System.out.println("[INFO-2] {" + className + "} has been saved in classNameList.");
                }
            }
    
        }

    将指定包下所有字节码文件,添加到classNameList中。

    2.3、初始化IOC容器

        private void doInstance() {
            if(classNamelist.isEmpty()){
                return;
            }
            try {
                for(String className: classNamelist){
                    Class<?>clazz = Class.forName(className);
    
                    if(clazz.isAnnotationPresent(XController.class)){
                        String beanName = toLowerFirstCase(clazz.getSimpleName());
                        Object instance = clazz.newInstance();
    
                        //保存在ioc容器
                        iocMap.put(beanName, instance);
                        System.out.println("[INFO-3] {" + beanName + "} has been saved in iocMap.");
    
                    }else if(clazz.isAnnotationPresent(XService.class)){
                        String beanName = toLowerFirstCase(clazz.getSimpleName());
    
                        //如果注解包含自定义名称
                        XService xService = clazz.getAnnotation(XService.class);
                        if("".equals(xService.value())){
                            beanName = xService.value();
                        }
    
                        Object instance = clazz.newInstance();
                        iocMap.put(beanName, instance);
                        System.out.println("[INFO-3] {" + beanName + "} has been saved in iocMap.");
    
                        //找类的接口(把该类所依赖的接口全都注入)
                        for(Class<?> i :clazz.getInterfaces()){
                            if(iocMap.containsKey(i.getName())){
                                throw new Exception("The Bean Name is Exist.");
                            }
                            iocMap.put(i.getName(), instance);
                            System.out.println("[INFO-3] {" + i.getName() + "} has been saved in iocMap.");
                        }
                    }
                }
            }catch(Exception e){
    
            }
        }

    这里的注入都是通过添加注解来实现的,那么需要判断哪些类被添加了注解(如@Service,@Controller等),如果有这些注解,则需要将这个类注入到容器中。对应的就是在iocMap中添加这个类就行了。如果在注解中指定了value,那么可以将这个value值作为bean的名称,否则就取这个类的类名(将类名的首字母替换为小写)。

    2.4、依赖注入

     private void doAutowired() {
            if(iocMap.isEmpty())
                return;
            for(Map.Entry<String, Object>entry : iocMap.entrySet()){
                //获取每一个class里的字段
                Field[]fields = entry.getValue().getClass().getDeclaredFields();
    
                for(Field field: fields){
                    if(!field.isAnnotationPresent(XAutowired.class)){
                        continue;
                    }
                    //如果该字段上有注解 @Autowired
                    System.out.println("[INFO-4] Existence XAutowired.");
    
                    //获取注解对应的类
                    XAutowired xAutowired = field.getAnnotation(XAutowired.class);
                    String beanName = xAutowired.value().trim();
    
                    //获取XAutowired注解的值
                    if("".equals(beanName)){
                        System.out.println("[INFO] xAutowired.value() is null");
                        //如果注解没有值,则取该字段的字段名
                        beanName = field.getType().getName();
                    }
    
                    //只要加了注解,都需要加载,不管是private 还是protect
                    field.setAccessible(true);
    
                    try{
                        //entry.getValue()获取的是类对象, icoMap.get(beanName)获取的是需要被注入的类的对象
                        //理解起来就是,给controller中被注入的那个字段赋值。
                        field.set(entry.getValue(), iocMap.get(beanName));
                        System.out.println("[INFO-4] field set {" + entry.getValue() + "} - {" + iocMap.get(beanName) + "}.");
                    }catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }

    所谓依赖注入,在这里就是指的是解析@Autowired注解。@Autowired标注在变量上,我们需要从IOC容器中,取出对应的实例,赋给这个变量。

    例如:

        @XAutowired
        XTestService testService;

    需要再在iocMap容器中,找到XTestService的实例,将其赋给testService这个变量,那么在controller中,就可以调用testService这个服务。

    2.5、初始化url与方法的映射

        //5、初始化handlerMapping
        private void initHandlerMapping() {
            if(iocMap.isEmpty()){
                return;
            }
            for(Map.Entry<String, Object> entry : iocMap.entrySet()){
                Class<?>clazz = entry.getValue().getClass();
                //必须要有controller标签
                if(!clazz.isAnnotationPresent(XController.class)){
                    continue;
                }
                String baseUrl = "";
                if(clazz.isAnnotationPresent(XRequestMapping.class)){
                    XRequestMapping xRequestMapping = clazz.getAnnotation(XRequestMapping.class);
                    baseUrl = xRequestMapping.value();
                }
    
                for(Method method: clazz.getMethods()){
                    if(!method.isAnnotationPresent(XRequestMapping.class)){
                        continue;
                    }
                    XRequestMapping xRequestMapping = method.getAnnotation(XRequestMapping.class);
                    String url = ("/" + baseUrl + "/" + xRequestMapping.value()).replaceAll("/+","/");
                    handlerMapping.put(url, method);
                    System.out.println("[INFO-5] handlerMapping put {" + url + "} - {" + method + "}.");
                }
            }
        }

    将@XRequestMapping注解中的value值与所在的方法对应起来,当有请求来的时候,可以通过请求的url来调用相应的方法。

    3、处理请求

    初始化之后,就可以处理浏览器发来的请求了。

    @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //7、运行阶段
            try {
                doDispach(req, resp);
            } catch (Exception e) {
                e.printStackTrace();
                resp.getWriter().write("500 Exception Detail:
    " + Arrays.toString(e.getStackTrace()));
            }
        }
        private void doDispach(HttpServletRequest request, HttpServletResponse response) throws InvocationTargetException, IllegalAccessException {
            String url = request.getRequestURI();
            String contextPath = request.getContextPath();
    
            url = url.replaceAll(contextPath, "").replaceAll("/+","/");
            System.out.println("[INFO]request url ----> "+url);
            if(!(this.handlerMapping.containsKey(url))){
                try {
                    response.getWriter().write("404 NOT FOUND");
                    return;
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return;
            }
    
            //获取url对应的方法
            Method method = this.handlerMapping.get(url);
            System.out.println("[INFO]method ----> "+method);
    
            String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
            System.out.println("[INFO]iocMap.get(beanName)->" + iocMap.get(beanName));
    
            //第一个参数是获取方法,后面的参数,多个参数直接加,按顺序对应
            method.invoke(iocMap.get(beanName),request, response);
            System.out.println("[INFO] method.invoke put {" + iocMap.get(beanName) + "}.");
        }

    通过url,来调用相应的方法,传入 request和response对象。

    4、编写测试类

    @XController
    @XRequestMapping("/test")
    public class TestController {
    
        @XAutowired
        XTestService testService;
    
        @XRequestMapping("/getUserInfo")
        public void  getUserInfo(HttpServletRequest request, HttpServletResponse response){
            String userInfo = testService.getUserInfo();
            try {
                response.getWriter().write(userInfo);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    }
    @XService
    public class XTestServiceImpl implements XTestService {
        @Override
        public String getUserInfo() {
            return "admin:123";
        }
    }

    5、配置tomcat并启动,浏览器中访问:

    http://localhost:8080/test/getUserInfo
  • 相关阅读:
    android broadcast小结
    初学设计模式【7】单例模式——sington
    android之Service总结
    现代软件开发实现六面体
    牛顿迭代法求根
    实现顺序表的各种基本运算
    公共的分页类,包含jsp页面
    多项式拟合+高斯消元解方程组
    差商表及牛顿插值
    C#.NET 获取拨号连接 宽带连接
  • 原文地址:https://www.cnblogs.com/phdeblog/p/13232447.html
Copyright © 2020-2023  润新知