最近在复习servlet,发现用原生servlet处理请求时,一个servlet只能处理一个请求,有多个业务时只能写很多个servlet,虽然springmvc等框架都可以解决这个问题,而且实际开发也不会有人用原生servlet处理请求,但是还是想尝试自己写一个简单的工具解决这一麻烦。先回顾下原生servlet处理一个请求:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("loginServlet");
}
}
然而,一个Controller会有很多业务,如userController里就有login、register、loginByWX(微信扫码登陆)、checkEmail(查看邮箱是否可用)、checkUsername(查看用户名是否可用)等,加上其他controller的话servlet就要写上百个,显然是不可行的,所以我想能不能通过规范请求的url,让我后端知道该请求应该去到哪里,比如说/user/login.do就是要去userController这个类中去调用login()方法,于是我写了一个类,就叫DispatcherServlet(分发器),让他在service方法中截取url获得两个东西:我要调用哪个方法和这个方法在哪个类里:
@WebServlet("*.do")
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//为所有/*.do的请求设置编码集
req.setCharacterEncoding("utf8");
String uri = req.getRequestURI();
//uri统一格式为:/controller/operation.do,拿到要执行的方法名
int startIndex = uri.lastIndexOf("/")+1;
int endIndex = uri.indexOf(".");
//拿到要调用的方法名
String operation = uri.substring(startIndex,endIndex);
//拿到controller的名字
String beanName = uri.substring(uri.indexOf("/")+1,uri.lastIndexOf("/"));
}
}
下一步就是把这个类的对象创建以供调用方法时使用且通过反射找到这个类中的方法,我这里使用了配置文件的方式,xml内容和xml的解析方法如下:
我有5个业务模块,共5个Controller,这个xml开始有点像spring内味了,但我这不需要引入dtd等约束文件,换句话说,这里的标签就是写成”myBeans“、”myBean“甚至”abc“都没问题,关键是xml的解析
<?xml version="1.0" encoding="utf-8"?>
<beans>
<bean id="user" class="com.joker.controller.UserController"/>
<bean id="department" class="com.joker.controller.DepartmentController"/>
<bean id="article" class="com.joker.controller.ArticleController"/>
<bean id="meeting" class="com.joker.controller.MeetingController"/>
<bean id="home" class="com.joker.controller.HomeController"/>
</beans>
解析xml:
这里的核心代码就是”
Element element = (Element) node;
String id = element.getAttribute("id");“
强转Element对象后调用getAttribute()方法获得标签中我自己写的id和class中的值
@WebServlet("*.do")
public class DispatcherServlet extends HttpServlet {
private static Map<String,Object> requestMapping = new HashMap<>();
//以下静态代码块仅运行一次,在此类被加载时(第一次收到*.do请求时),根据applicationContext.xml的配置初始化uri和controller的映射关系
static {
try {
InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream("applicationContext.xml");
//解析xml文件
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document dom = documentBuilder.parse(is);
//<bean></bean>或者<bean/>中的内容拿出来,封装成com.sun.org.apache.xerces.internal.dom.DeepNodeListImpl对象
NodeList bean = dom.getElementsByTagName("bean");
for (int i = 0; i < bean.getLength(); i++) {
Node node = bean.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE){
Element element = (Element) node;
String id = element.getAttribute("id");
String className = element.getAttribute("class");
Object o = Class.forName(className).getDeclaredConstructor().newInstance();
requestMapping.put(id,o);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
做完这步,成员变量requestMapping中就存放好了5个键值对,键为"user",值就是UserController对象,键为"article",值就是ArticleController的对象。
这时,上面通过截取url获得到的信息就可以派上用场了,如/user/login.do这样的请求,通了subString()方法我得到了两个字符串,user跟login,user正是我成员变量requestMapping中的键,而login,只需要调用Class类中的getDeclaredMethod方法再把login填到参数中,就可以得到这个方法并调用。
把service方法写完:
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//为所有/*.do的请求设置编码集
req.setCharacterEncoding("utf8");
String uri = req.getRequestURI();
//uri统一格式为:/controller/operation.do,拿到要执行的方法名
int startIndex = uri.lastIndexOf("/")+1;
int endIndex = uri.indexOf(".");
//拿到要调用的方法名
String operation = uri.substring(startIndex,endIndex);
//拿到controller的名字
String beanName = uri.substring(uri.indexOf("/")+1,uri.lastIndexOf("/"));
//在加载此类时已根据在applicationContext.xml中配置的信息将所有controller创建实例,现在根据uri,找到容器中对应的controller实例
Object controllerBean = requestMapping.get(beanName);
//拿到实例后,根据uri找到需要调用的方法名,通过反射找到此方法并调用
try {
Method methodToInvoke = controllerBean.getClass().getDeclaredMethod(operation, HttpServletRequest.class, HttpServletResponse.class);
methodToInvoke.setAccessible(true);
methodToInvoke.invoke(controllerBean,req,resp);
} catch (Exception e) {
e.printStackTrace();
}
}
整个类的代码如下:
/**
* @Author:Joker
* @Date:2022/5/3 10:09
* @Description:页面分发器,根据前端请求的url地址找到对应的controller实例并invoke其对应的方法
*/
@WebServlet("*.do")
public class DispatcherServlet extends HttpServlet {
private static Map<String,Object> requestMapping = new HashMap<>();
//以下静态代码块仅运行一次,在此类被加载时(第一次收到*.do请求时),根据applicationContext.xml的配置初始化uri和controller的映射关系
static {
try {
InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream("applicationContext.xml");
//解析xml文件
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document dom = documentBuilder.parse(is);
//<bean></bean>或者<bean/>中的内容拿出来,封装成com.sun.org.apache.xerces.internal.dom.DeepNodeListImpl对象
NodeList bean = dom.getElementsByTagName("bean");
for (int i = 0; i < bean.getLength(); i++) {
Node node = bean.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE){
Element element = (Element) node;
String id = element.getAttribute("id");
String className = element.getAttribute("class");
Object o = Class.forName(className).getDeclaredConstructor().newInstance();
requestMapping.put(id,o);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//为所有/*.do的请求设置编码集
req.setCharacterEncoding("utf8");
String uri = req.getRequestURI();
//uri统一格式为:/controller/operation.do,拿到要执行的方法名
int startIndex = uri.lastIndexOf("/")+1;
int endIndex = uri.indexOf(".");
//拿到要调用的方法名
String operation = uri.substring(startIndex,endIndex);
//拿到controller的名字
String beanName = uri.substring(uri.indexOf("/")+1,uri.lastIndexOf("/"));
//在加载此类时已根据在applicationContext.xml中配置的信息将所有controller创建实例,现在根据uri,找到容器中对应的controller实例
Object controllerBean = requestMapping.get(beanName);
//拿到实例后,根据uri找到需要调用的方法名,通过反射找到此方法并调用
try {
Method methodToInvoke = controllerBean.getClass().getDeclaredMethod(operation, HttpServletRequest.class, HttpServletResponse.class);
methodToInvoke.setAccessible(true);
methodToInvoke.invoke(controllerBean,req,resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
算是一个乞丐版SpringMVC吧