一、发现问题
不知道有没有小伙伴曾经或现在还依然跟我一样,一直迷惑于 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;
}
}
源码
--------------------- 哪来的天生优秀,都是一步一个坑踩过来的。 ---------------------