📚自研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、视图渲染结果会返回给客户端浏览器显示。
附源码地址: 欢迎大家指点 ,共同进步 共同学习!!!