自研springmvc小框架

📚自研SpringMvc框架

🍊初衷

        让了解Servlet生命周期

        自定去定义和模仿一个SpringMvc框架

        明白Servlet生命周期到底和springmvc有什么关系

🍊02、准备工作

        新建一个servlet工程

        导入依赖

        定义/src/main/webapp目录

        定义/src/main/webapp/WEB-INF/classes目录

        定义/src/main/webapp/WEB-INF/web.xml文件

        定义启动类AppMain.java

🍊03、面向servlet开发的话

                每个业务都有一个servlet,这个非常繁琐的。不利于管

很多东西需要自己的处理和管理。也很麻烦。不通用。

        webwork---struts1---

        struts2(@Scope(prototype))+struts.xml--->springmvc

        orm---jdbctemplate-----hibernate mybatis

        ssm---springboot--ssm---springcloud       

🍊04、架构说明

 

 

        比较复杂的是我们需要在MVC框架中创建一个接收所有请求的 Servlet,通常我们把它命名为DispatcherServlet,

特点:

        它总是映射到/(为什么springmvc的映射的路径是一个/

呢?

        告诉你,所有的请求都由我的控制和转发,根据不同的Controller的方法定义的@GetMaping

或 @PostMapping的Path决定调用哪个方法,获得方法返回的ModelAndView后,渲染模板,写入

 HttpServletResponse 即完成了整个MVC的处理

知识点:

Servlet的生命周期

init方法 --- 初始化 -- 收集所有的servlet

service方法 ----执行 根据请求方式进入doPost/doGet

🍊05、整体的架构步骤

1.初始化 将所有的controller加入到对应Mapping中

2.确定模板引擎

3.处理请求

4.处理参数

5.处理转发

6.处理静态资源

7.处理异步处理

8.处理异常

9.处理参数注入

10.aop完成拦截器

🍊06、初始化

package com.kuangstudy.framework.core;


import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.kuangstudy.framework.anno.GetMapping;
import com.kuangstudy.framework.anno.PostMapping;
import com.kuangstudy.framework.engine.FreemakerEngine;
import com.kuangstudy.framework.reflect.ClazzUtils;
import com.kuangstudy.framework.view.ModelAndView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


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 javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {
    // 日记管理和处理
    private final Logger logger = LoggerFactory.getLogger(getClass());
    // 存放Get请求的路由
    private Map<String, GetDispatcher> getMappings = new ConcurrentHashMap<>();
    // 存放Post请求的路由
    private Map<String, PostDispatcher> postMappings = new ConcurrentHashMap<>();
    // 指定package并自动扫描:
    private List<Class<?>> controllers = Lists.newArrayList();
    // 确定模板引擎
    private FreemakerEngine viewEngine;
    // 需要注入到请求方法中的参数
    private static final Set<Class<?>> supportedGetParameterTypes = Sets.newHashSet(int.class,
            long.class, boolean.class,float.class,double.class,Map.class,
            String.class, HttpServletRequest.class, HttpServletResponse.class, HttpSession.class);
    private static final Set<Class<?>> supportedPostParameterTypes = Sets.newHashSet(HttpServletRequest.class,
            HttpServletResponse.class, HttpSession.class);


    /**
     * 当Servlet容器创建当前Servlet实例后,
     * 会自动调用init(ServletConfig)方法
     */
    @Override
    public void init() throws ServletException {
        // 1: 加载controller配置包
        initController();
        // 2: 处理映射请求
        initMapping();
        // 3: 确定视图模板
        initModelView();
    }


    public void initController() {
        Set<Class<?>> classes = ClazzUtils.getClasses("com.kuangstudy.controller");
        controllers = classes.stream().collect(Collectors.toList());
        logger.info("controllers {}...", controllers);
        logger.info("init {}...", getClass().getSimpleName());
    }


    public void initMapping() {
        // 2: 数据格式处理器
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 3:依次处理每个Controller:
        for (Class<?> controllerClass : controllers) {
            try {
                Object controllerInstance = controllerClass.getConstructor().newInstance();
                // 依次处理每个Method:
                for (Method method : controllerClass.getMethods()) {
                    if (method.getAnnotation(GetMapping.class) != null) {
                        // 处理@Get:
                        if (method.getReturnType() != ModelAndView.class && method.getReturnType() != void.class) {
                            throw new UnsupportedOperationException(
                                    "Unsupported return type: " + method.getReturnType() + " for method: " + method);
                        }
                        for (Class<?> parameterClass : method.getParameterTypes()) {
                            if (!supportedGetParameterTypes.contains(parameterClass)) {
                                throw new UnsupportedOperationException(
                                        "Unsupported parameter type: " + parameterClass + " for method: " + method);
                            }
                        }
                        // 对方法的参数进行处理
                        String[] parameterNames = Arrays.stream(method.getParameters()).map(p -> p.getName()).toArray(String[]::new);
                        // 获取注解上的路径
                        String path = method.getAnnotation(GetMapping.class).value();
                        logger.info("Found GET: {} => {}", path, method);
                        // 将路由参数统一放入到路由映射中
                        this.getMappings.put(path, new GetDispatcher(controllerInstance, method, parameterNames, method.getParameterTypes()));
                    } else if (method.getAnnotation(PostMapping.class) != null) {
                        // 处理@Post:
                        if (method.getReturnType() != ModelAndView.class && method.getReturnType() != void.class) {
                            throw new UnsupportedOperationException(
                                    "Unsupported return type: " + method.getReturnType() + " for method: " + method);
                        }
                        Class<?> requestBodyClass = null;
                        for (Class<?> parameterClass : method.getParameterTypes()) {
                            if (!supportedPostParameterTypes.contains(parameterClass)) {
                                if (requestBodyClass == null) {
                                    requestBodyClass = parameterClass;
                                } else {
                                    throw new UnsupportedOperationException("Unsupported duplicate request body type: "
                                            + parameterClass + " for method: " + method);
                                }
                            }
                        }
                        String path = method.getAnnotation(PostMapping.class).value();
                        logger.info("Found POST: {} => {}", path, method);
                        this.postMappings.put(path, new PostDispatcher(controllerInstance, method,
                                method.getParameterTypes(), objectMapper));
                    }
                }
            } catch (ReflectiveOperationException e) {
                logger.error("出现解析和加载错误....");
                e.printStackTrace();
            }
        }
    }




    public void initModelView() {
        // 创建ViewEngine:
        //this.viewEngine = new ViewEngine(getServletContext());
        this.viewEngine = new FreemakerEngine(getServletContext());
    }




}

06、静态资源处理

        什么是静态资源呢

  • 就是不需要servlet去处理和转发的资源,比如:html 、css 、js 、images等其他的

  • 因为静态不需要任何逻辑,直接返回即可。你只要放入在webapp的目录即可。就可以访问

那为什么在很多框架中springmvc, nodejs express等,都需要对静态资源额外处理呢?

  • 因为静态资源访问,也是请求,而DispatcherSerlvet处理是所有的请求。静态资源请求会也进行处理,所以这样就会有问题。所以我们对其静态资源进行排除。什么动态资源呢?

        什么动态资源呢?

        就是说需要被servlet处理的请求,就动态请求

        

package com.kuangstudy.core.filter;


import javax.servlet.ServletContext;
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.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;


@WebServlet(urlPatterns = { "/favicon.ico", "/static/*" })
public class FileServlet extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 读取当前请求路径:
        ServletContext ctx = req.getServletContext();
        // RequestURI包含ContextPath,需要去掉:
        String urlPath = req.getRequestURI().substring(ctx.getContextPath().length());
        // 获取真实文件路径:
        String filepath = ctx.getRealPath(urlPath);
        if (filepath == null) {
            // 无法获取到路径:
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }


        Path path = Paths.get(filepath);
        if (!path.toFile().isFile()) {
            // 文件不存在:
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        // 根据文件名猜测Content-Type:
        var mime = Files.probeContentType(path);
        if (mime == null) {
            mime = "application/octet-stream";
        }
        resp.setContentType(mime);
        // 读取文件并写入Response:
        OutputStream output = resp.getOutputStream();
        try (InputStream input = new BufferedInputStream(new FileInputStream(filepath))) {
            input.transferTo(output);
        }
        output.flush();
    }
}

07、处理请求

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        if (method.equalsIgnoreCase("get")) {
            this.doGet(req, resp);
        } else if (method.equalsIgnoreCase("post")) {
            this.doPost(req, resp);
        }
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI();
        GetDispatcher dispatcher = this.getMappings.get(requestURI);
        try {
            // 1: 执行请求:http://localhost:8085/login
            // 2:根据/login得到: GetDispatcher
            //    - obj = LoginController
            //    - method = toLogin
            //    - parameterNamess = null
            //    - parameterClasses = null
            //3: 反射执行方法 LoginController.toLogin();
            Object obj =  dispatcher.method.invoke(dispatcher.obj,dispatcher.parameterClasses);
            ModelAndView modelAndView = (ModelAndView) obj;
            System.out.println(modelAndView.viewName);
            System.out.println(modelAndView.model);
            // 执行引擎---freemaker去做后续工作
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }




@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost执行进来了.....");
        String requestURI = req.getRequestURI();
        PostDispatcher dispatcher = this.postMappings.get(requestURI);
        try {
            // 这里就是具体方法的执行的位置 dispatcher.obj =IndexController
            ModelAndView modelAndView =  dispatcher.invoke(req,resp);
            System.out.println(modelAndView.viewName);
            System.out.println(modelAndView.model);
            // 执行引擎---freemaker去做后续工作
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

08、视图返回 -- 模板渲染

  • freemarker (用freemarker)

  • thymeleaf

  • voclity

  • beetl

  • pebbletemplates

  • jsp(抛弃)

package com.kuangstudy.core.viewengine;


import com.kuangstudy.core.view.ModelAndView;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;


import javax.servlet.ServletContext;
import java.io.IOException;
import java.io.Writer;


public class FreemakerEngine {




    private Configuration configuration;


    /**
     * ServletContext 当前执行应用程序上下文:application
     * @param application
     */
    public FreemakerEngine(ServletContext application) {
        try {
            configuration = new Configuration(Configuration.VERSION_2_3_28);
            // 指定模板文件从何处加载的数据源,这里设置成一个文件目录。
            //d://tomcat/ksd-web-newmvc/src/main/webapp//WEB-INF/templates/login.html
            configuration.setServletContextForTemplateLoading(application, "/WEB-INF/templates");
            // 指定模板如何检索数据模型,这是一个高级的主题了… // 但先可以这么来用:
            configuration.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_28));
            // 设置编码
            configuration.setDefaultEncoding("UTF-8");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    public void render(ModelAndView mv, Writer writer) throws IOException {
        try {
            Template temp = configuration.getTemplate(mv.viewName);
            temp.process(mv.model, writer);
        } catch (TemplateException ex) {
            ex.printStackTrace();
        }
    }
}

SpringMVC工作原理:

  1、  用户发送请求至前端控制器DispatcherServlet。
        2、  DispatcherServlet收到请求调用HandlerMapping处理器映射器。
        3、  处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
        4、  DispatcherServlet调用HandlerAdapter处理器适配器。
        5、  HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
        6、  Controller执行完成返回ModelAndView。
        7、  HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
        8、  DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
        9、  ViewReslover解析后返回具体View。
        10、 DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
        11、视图渲染结果会返回给客户端浏览器显示。

附源码地址: 欢迎大家指点 ,共同进步  共同学习!!!

https://gitee.com/dandingshang/spingmvc.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值