手写一个MVC框架
目的:
提前建立框架的认识
框架都是别人编写好的代码,我们为了便于项目的编写,也可以实现一个简易的框架。本框架参考SpringMVC。
当我们要实现一个项目的时候,会需要实现很多功能,如下图。
以我们现在的认知就是一个功能对应一个Servlet,但这样写起来会很麻烦,代码也会非常冗余。
所以当我们注册或者登陆的时候,是否都可以去找一个类?如下图。
MVC是什么?
M:Model,模型层
V:View,视图层
C:Control,控制器层
23种设计模式中并没有MVC设计模式,但是现在MVC模式已经广泛应用,MVC模式是23种模式中几种模式的变形和整合。
框架构成![image-20211014193306337](https://img-blog.csdnimg.cn/img_convert/e69d022be0f6f9059fdfcac89d3989fc.png)
所有代码的详细解释都写了注释,请细细品味。
DispatcherServlet
这个servlet是所有用户请求的入口,一个servlet处理所有请求,根据映射器所指向的某个类的方法,然后调用这个方法,来处理每一个请求。方法处理完之后,都会有相应的结果产生,随后将结果返回给用户。
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 前端控制器,也称为中央控制器或者核心控制器。
* 它就相当于 mvc 模式中的 c
*/
@WebServlet(name = "DispatcherServlet")
public class DispatcherServlet extends HttpServlet {
/**
* 初始化方法,加载配置文件
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
// 通过路径加载输入流进来,获取配置文件里面所存储的每一个键值对
String path = config.getInitParameter("contentConfigLocation");
InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);
HandlerMapping.load(is);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取用户请求的uri
String uri = req.getRequestURI();
HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri);
if (mapping == null){
resp.sendError(404, "自定义MVC:映射地址不存在" + uri);
return;
}
Object obj = mapping.getObject();
Method method = mapping.getMethod();
Object result = null;
try {
// 调用invoke()方法实现动态代理
result = method.invoke(obj, req, resp);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
switch (mapping.getType()){// 判断mapping里面的类型
case TEXT:
resp.getWriter().write((String)result);
break;
case VIEW:
resp.sendRedirect((String)result);
break;
}
}
}
HandlerMapping
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;
/**
* 映射器(包含了大量的网址与方法的对应关系)
*/
public class HandlerMapping {
// 将所有使用了@ResponseBody,@ResponseView这两个注解的封装成一个Map对象
private static Map<String, MVCMapping> data = new HashMap<>();
/**
* 获取集合中网址所对应的方法
* @param uri
* @return
*/
public static MVCMapping get(String uri){
return data.get(uri);
}
/**
* 加载配置文件的方法
* @param is
*/
public static void load(InputStream is){
Properties ppt = new Properties();
try {
ppt.load(is);
} catch (IOException e) {
e.printStackTrace();
}
// 获取配置文件中描述的一个个的类
Collection<Object> values = ppt.values();
for (Object cla : values) {
String className = (String) cla;
try {
// 加载配置文件中描述的每一个类
Class c = Class.forName(className);
// 创建这个类的对象
Object obj = c.getConstructor().newInstance();
// 获取这个类的所有方法
Method[] methods = c.getMethods();
for (Method m:methods) {
// 获取这个方法的所有注解
Annotation[] as = m.getAnnotations();
if(as != null){
for(Annotation annotation : as){
if(annotation instanceof ResponseBody){
// 说明此方法,用于返回字符串给客户端
MVCMapping mapping = new MVCMapping(obj, m, ResponseType.TEXT);
Object o = data.put(((ResponseBody) annotation).value(), mapping);
if(o != null){
// 存在了重复的请求地址
throw new RuntimeException("请求地址重复:"+((ResponseBody) annotation).value());
}
}else if(annotation instanceof ResponseView){
// 说明此方法,用于返回界面给客户端
MVCMapping mapping = new MVCMapping(obj, m, ResponseType.VIEW);
Object o = data.put(((ResponseView) annotation).value(), mapping);
if(o != null){
// 存在了重复的请求地址
throw new RuntimeException("请求地址重复:"+((ResponseView) annotation).value());
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 映射对象,每一个对象封装了一个方法,用于处理请求
*/
public static class MVCMapping{
private Object object;
private Method method;
private ResponseType type;
public MVCMapping() {
}
public MVCMapping(Object object, Method method, ResponseType type) {
this.object = object;
this.method = method;
this.type = type;
}
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 getType() {
return type;
}
public void setType(ResponseType type) {
this.type = type;
}
}
}
@ResponseBody
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* 注解的作用:
* 被此注解添加的方法,会被用于处理请求。
* 方法返回的内容,会以文字形式返回到客户端
*/
public @interface ResponseBody {
String value();
}
@ResponseView
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* 注解的作用:
* 被此注解添加的方法, 会被用于处理请求。
* 方法返回的内容,会直接重定向
*/
public @interface ResponseView {
String value();
}
ResponseType
/**
* 枚举类
* 描述相应的类型是文字类型还是视图类型
*/
public enum ResponseType {
TEXT, VIEW;
}
web.xml
<?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_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>com.lyc.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><!--所有以*.do结尾的请求都经过DispatcherServlet处理-->
</servlet-mapping>
</web-app>
application.properties
# 在这里配置每一个用于处理请求的类,每一个类中可能包含0-n个用于处理请求的方法
user=com.lyc.test.UserController
Controller测试类
import com.lyc.mvc.ResponseBody;
import com.lyc.mvc.ResponseView;
public class UserController {
/**
* 登录方法 返回文本类型
* @return
*/
@ResponseBody("/login.do")
public String login(HttpServletRequest request, HttpServletResponse response){
// 因为返回的是中文,所以在浏览器上会显示乱码
// 乱码的处理通过过滤器来完成,没必要加入到框架里,框架里如果定好了编码格式会导致它不灵活
return "登录成功";
}
/**
* 注册方法 返回视图类型
* @return
*/
@ResponseView("/reg.do")
public String reg(HttpServletRequest request, HttpServletResponse response){
return "success.jsp";
}
}
success.jsp
<%--
Created by IntelliJ IDEA.
User: 成成子
Date: 2021/10/14
Time: 19:12
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
注册成功
</body>
</html>
运行截图
login.do请求 返回""文本
reg.do请求 跳转view页面