手撕简易mvc框架的仿@ResponseBody和@ResponseView

手撕简易mvc框架的仿@ResponseBody和@ResponseView

思路:
  1. 前端所有请求,*.do均由DispatcherServlet进行拦截过滤
  2. 在DispatcherServlet中,它的初始化会加载配置在全局参数中的contentConfigLocation下的application.properties文件(application.properties里面配置了n个请求的方法类型【类似是@Controller注解的值,不过是全限定名】)
  3. 在初始化中调用HandlerMapping的Load方法,将能被请求的方法(解析注解类型等)通过反射封装成一个映射对象(包含对象、方法、注解类型【映射对象,每个对象封装了一个方法,用于处理请求】)放进一个集合中(该集合为方法中的静态类型,k为注解的值、v为映射对象mapping)
  4. 初始化完成后在DispatcherServlet中执行service方法(调取service()⽅法,service()判断客户端的请求⽅式,如果是get,则执⾏doGet(),如果是post则执⾏doPost().),那么直接执行service就可以不用理会请求方式
  5. service中获取用户请求的uri/xx.do,调用HandlerMapping.get(uri)方法获取一个MVCMapping映射对象,判断其是否为空来决定是否抛出映射地址不存在404错误,通过method的invoke方法获取一个结果(传递对象、请求和响应),再根据MVCMapping的返回类型来进行resp.getWriter().write((String)result)或者resp.senRedirect((String)result)

前端所有请求,经过DispatcherServlet:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>com.kkb.mvc.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contentConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

注意!!!application.properties路径在src下

DispatcherServlet.java

package com.kkb.mvc;

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.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;

public class DispatcherServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        /*
        * 这一块感觉就是spring中的组件扫描
        */
        String path = config.getInitParameter("contentConfigLocation");
        InputStream inputStream = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);
        HandlerMapping.Load(inputStream);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取用户请求uri
        String uri = req.getRequestURI();
        HandlerMapping.MVCMapping mvcMapping = HandlerMapping.get(uri);
        if (mvcMapping == null) {
            resp.sendError(404, "MVC:映射地址不存在" + uri);
            return;
        }
        Method method = mvcMapping.getMethod();
        Object object = mvcMapping.getObject();
        Object result = null;
        try {
            result = method.invoke(object, req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
        switch (mvcMapping.getResponseType()) {
            case TEXT:
                resp.getWriter().write((String)result);
                break;
            case VIEW:
                resp.sendRedirect((String)result);
                break;
        }
    }
}

ResponseBody.java

package com.kkb.mvc;

import java.lang.annotation.*;

/**
 * 注解的作用:
 *  被此注解添加的方法, 会被用于处理请求。
 *  方法返回的内容,会以文字形式返回到客户端
 */
@Target(ElementType.METHOD) //定义注解写在方法上
@Retention(RetentionPolicy.RUNTIME)
/*
@Retention作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中.
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略

class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期

runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
*/
@Documented       //写在文档上
public @interface ResponseBody {
    String value();
}


ResponseView.java

package com.kkb.mvc;

import java.lang.annotation.*;

/**
 * 注解的作用:
 *  被此注解添加的方法, 会被用于处理请求。
 *  方法返回的内容,会直接重定向
 */
@Target(ElementType.METHOD) //定义注解写在方法上
@Retention(RetentionPolicy.RUNTIME)
/*
@Retention作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中.
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略

class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期

runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
*/
@Documented       //写在文档上
public @interface ResponseView {
    String value();
}


ResponseType.java

package com.kkb.mvc;

public enum ResponseType {
    VIEW, TEXT,
}

HandlerMapping.java

package com.kkb.mvc;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @version 1.0
 * @Author: lyq
 * @Date: 2021/4/23
 * @Description: TODO
 **/
public class HandlerMapping {

    private static Map<String, MVCMapping> data = new HashMap<>();

    public static MVCMapping get(String uri) {
        return data.get(uri);
    }

    public static void Load(InputStream inputStream) {
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //获取配置文件中描述的一个个的类
        Collection<Object> values = properties.values();
        for (Object value : values) {
            String className = (String) value;
            try {
                //加载配置文件中的每一个类  等为每一个controller
                Class<?> aClass = Class.forName(className);
                //根据反射创建类对象 实例化
                Object o = aClass.getConstructor().newInstance();
                //获取类的所有方法  等于controller下的service【理解为框架中的@RequestMapping注解方法】
                Method[] methods = o.getClass().getMethods();
                for (Method method : methods) {
                    //每个方法上的注解
                    Annotation[] annotations = method.getAnnotations();
                    //有注解的时候
                    if (annotations != null) {
                        for (Annotation annotation : annotations) {
                            if (annotation instanceof ResponseBody) {
                                //说明此方法,用于返回字符串给客户端
                                MVCMapping mvcMapping = new MVCMapping(o, method, ResponseType.TEXT);
                                MVCMapping put = data.put(((ResponseBody) annotation).value(), mvcMapping); //map的put是有返回值的,当有覆盖出现会返回旧值
                                if (put != null) {
                                    //存在了重复的请求地址
                                    throw new RuntimeException("请求地址重复" + ((ResponseBody) annotation).value());
                                }
                            } else if (annotation instanceof ResponseView) {
                                //说明此方法,用于返回界面给客户端
                                MVCMapping mvcMapping = new MVCMapping(o, method, ResponseType.VIEW);
                                MVCMapping put = data.put(((ResponseView) annotation).value(), mvcMapping); //map的put是有返回值的,当有覆盖出现会返回旧值
                                if (put != null) {
                                    //存在了重复的请求地址
                                    throw new RuntimeException("请求地址重复" + ((ResponseBody) annotation).value());
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static class MVCMapping {
        private Object object;
        private Method method;
        private ResponseType responseType;

        public Object getObject() {
            return object;
        }

        public void setObject(Object object) {
            this.object = object;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public ResponseType getResponseType() {
            return responseType;
        }

        public void setResponseType(ResponseType responseType) {
            this.responseType = responseType;
        }

        public MVCMapping() {

        }

        public MVCMapping(Object object, Method method, ResponseType responseType) {

            this.object = object;
            this.method = method;
            this.responseType = responseType;
        }
    }
}

TestController.java

package com.kkb.controller;

import com.kkb.mvc.ResponseBody;
import com.kkb.mvc.ResponseView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @version 1.0
 * @Author: lyq
 * @Date: 2021/4/23
 * @Description: TODO
 **/
public class TestController {

    @ResponseBody("/test.do")
    public String test1(HttpServletRequest req, HttpServletResponse resp) {
        return "success";
    }

    @ResponseView("/toIndex.do")
    public String test2(HttpServletRequest req, HttpServletResponse resp) {
        return "index.jsp";
    }
}

application.properties

# 配置的controller的类路径 千万别忘!!
a1=com.kkb.controller.TestController
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值