手撕简易mvc框架的仿@ResponseBody和@ResponseView
思路:
- 前端所有请求,*.do均由
DispatcherServlet
进行拦截过滤 - 在DispatcherServlet中,它的初始化会加载配置在全局参数中的
contentConfigLocation下的application.properties
文件(application.properties里面配置了n个请求的方法类型【类似是@Controller注解的值,不过是全限定名】) - 在初始化中调用
HandlerMapping的Load方法
,将能被请求的方法(解析注解类型等)通过反射封装成一个映射对象(包含对象、方法、注解类型【映射对象,每个对象封装了一个方法,用于处理请求】)放进一个集合中(该集合为方法中的静态类型,k为注解的值、v为映射对象mapping) - 初始化完成后在DispatcherServlet中执行
service
方法(调取service()⽅法,service()判断客户端的请求⽅式,如果是get,则执⾏doGet(),如果是post则执⾏doPost().),那么直接执行service就可以不用理会请求方式 - 在
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