一、发现问题
不知道有没有小伙伴曾经或现在还依然跟我一样,一直迷惑于 Spring MVC 框架是怎么实现只要添加一个 @Controller 就能把普通的类变成 Servlet 的?或者说怎么就把一个请求转进了普通的一个类里的?为什么我的类就必须继承 HttpServlet 还必须配置 @WebServlet 注解或配置进 web.xml ?如果是我,我该怎么实现?带着这几个疑问,我们一起来梳理一下解决思路。
二、思路分析
-
HttpServlet是不是必须继承的?答:当然是必须的。因为这是
web容器(Tomcat)与请求处理类之间的桥梁。 -
HttpServlet是每个请求处理类都必须实现吗?答:不一定。如果我们是通过传统的一个请求对应一个
Servlet的方式来处理请求,那么这些Servlet就必须都实现HttpServlet。但是如果我们构建一个继承自HttpServlet类的DispatcherServlet,然后通过这个请求分发类去调用散布在不同Java类中的某个可以处理当前请求的方法即可。这样一来就不用每个请求处理类都继承HttpServlet了。 -
构建一个继承自
HttpServlet类的DispatcherServlet进行请求分发听着是可以,但是要怎么分发出去呢?答:首先在处理请求前创建一个容器,缓存下每个
URL所对应的处理方法及其相关信息。这样一来,当我们接收到请求后,可根据URL找到对应的处理方法信息,这样我们就能通过反射来调用处理方法,实现请求分发。
三、代码实现
(1)编写核心模块 DispatcherServlet
MyDispatcherServlet类中重写了三个方法doGet、doPost、init, 请求处理前的准备工作自然就放在了init方法中,而分发请求进行处理的工作放在doGet、doPost中任一方法即可。init方法中主要包含了五个步骤:- 读取配置文件。
- 扫描所有需要交由
IoC容器进行实例化的类,即带有:@Service和@Controller注解的类。 - 实例化所有
IoC容器中的类。 - 属性注入。为带有
@AutoWired注解的属性进行赋值。 - 记录请求与处理器(处理方法)间的映射关系。
doPost方法中,主要的处理逻辑:- 第一步,由于该示例中加入了
@Security注解进行处理方法的权限登记,所以在找到对应请求处理器后的第一件事就是鉴权。 - 第二步,组装处理器反射调用时用到的参数。
- 第三步,考虑到处理器的参数可能会用到
HTTPServletRequest和HTTPServletResponse这两个对象,所以在这步做的简单的处理。 - 第四步,通过反射执行处理器。
- 第一步,由于该示例中加入了
MyDispatcherServlet
import com.idol.framework.builder.ConfigBuilder;
import com.idol.framework.factory.BeanFactory;
import com.idol.framework.handler.Handler;
import com.idol.framework.handler.HandlerMapping;
import com.idol.framework.util.ScanUtil;
import org.apache.commons.lang3.StringUtils;
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.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* @author Supreme_Sir
* @version 1.0
* @className MyDispatcherServlet
* @description 自定义请求分发处理器
* @date 2020/10/31 13:52
**/
public class MyDispatcherServlet extends HttpServlet {
private BeanFactory beanFactory = BeanFactory.getInstance();
private HandlerMapping handlerMapping = HandlerMapping.getInstance();
@Override
public void init(ServletConfig config) throws ServletException {
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
try {
// 加载配置文件
Properties configProperties = ConfigBuilder.doLoadConfig(contextConfigLocation);
// 扫描注解
List<String> classPathList = ScanUtil.doScan((String) configProperties.getProperty("scanPackage"),
new ArrayList<String>());
// 初始化相关 Bean
beanFactory.doCreateBeans(classPathList);
// 实现依赖注入
beanFactory.doAutoWired();
// 构造处理器映射器——HandlerMapping,以建立 URL 与处理方法间的映射关系
handlerMapping.initHandlerMapping();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("my mvc 初始化完成");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
Handler matchedHandler = handlerMapping.getMatchedHandler(req);
if(matchedHandler == null) {
resp.getWriter().write("对应的请求处理器没有被找到~!");
return;
}
String name = req.getParameter("name");
if (!matchedHandler.getUserPermission().contains(name)) {
resp.getWriter().write("对不起,您没有访问该网络地址的权限~!");
return;
}
int paramCount = matchedHandler.getMethod().getParameters().length;
Object[] args = new Object[paramCount];
/*
说明:
web 容器(Tomcat)考虑到如果一个 URL 请求如下时:http://localhost:8080/demo/query?name=lisi&name=zhangsan
即同一个参数名多次出现的情况可能存在,所以就将同一个参数名下的值封装进一个数组中(["lisi", "zhangsan"])
(当然如果参数名只出现一次也会封装进字符串数组),因此 parameterMap 的 value 泛型为 String[]。
*/
Map<String, String[]> parameterMap = req.getParameterMap();
for (Map.Entry entry : parameterMap.entrySet()) {
Map<String, Integer> indexMapping = matchedHandler.getParamIndexMapping();
if (!indexMapping.containsKey(entry.getKey())) {
continue;
}
Integer index = indexMapping.get(entry.getKey());
args[index] = StringUtils.join((String[]) entry.getValue(), ",");
}
// 判断是否需要 HttpServletRequest 对象作为方法参数
if (matchedHandler.isNeedRequest()) {
Integer requestIndex = matchedHandler.getParamIndexMapping().get("HttpServletRequest");
args[requestIndex] = req;
}
// 判断是否需要 HttpServletResponse 对象作为方法参数
if (matchedHandler.isNeedResponse()) {
Integer responseIndex = matchedHandler.getParamIndexMapping().get("HttpServletResponse");
args[responseIndex] = resp;
}
try {
matchedHandler.getMethod().invoke(matchedHandler.getObject(), args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doPost(req, resp);
}
}
(2)框架使用
说明:@Controller、@RequestMapping()、@AutoWired 这三个注解和 Spring MVC 中的注解使用方式相似,@Security() 注解中的值表示可以访问当前处理器的用户名,如果有多个,使用逗号进行分隔即可。
DemoController
import com.idol.framework.annotation.AutoWired;
import com.idol.framework.annotation.Controller;
import com.idol.framework.annotation.RequestMapping;
import com.idol.framework.annotation.Security;
import com.idol.website.service.IDemoService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Supreme_Sir
* @version 1.0
* @className DemoController
* @description
* @date 2020/10/31 22:37
**/
@Controller
@RequestMapping("/demo")
public class DemoController {
@AutoWired
private IDemoService demoService;
@RequestMapping("/query")
@Security({"zhangSan"})
public void getName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
resp.getWriter().write("查询用户名:" + demoService.getName(name));
return;
}
@RequestMapping("/insert")
@Security({"liSi"})
public void insertName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
resp.getWriter().write("添加用户名:" + demoService.getName(name));
return;
}
@RequestMapping("/delete")
@Security({"wangWu"})
public void deleteName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
resp.getWriter().write("删除用户名:" + demoService.getName(name));
return;
}
@RequestMapping("/modify")
@Security({"zhaoLiu"})
public void modifyName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
resp.getWriter().write("修改用户名:" + demoService.getName(name));
return;
}
}
源码
--------------------- 哪来的天生优秀,都是一步一个坑踩过来的。 ---------------------
本文围绕自定义MVC框架展开,先提出关于框架实现的疑问,如普通类如何处理请求等。接着进行思路分析,探讨是否必须继承特定类及请求分发方式。最后给出代码实现,包括编写核心模块DispatcherServlet及框架使用说明,还提及相关注解使用。
492

被折叠的 条评论
为什么被折叠?



