1 关于Servlet
1.1 什么是Servlet
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
- 编写一个Java类,实现servlet接口。
- 把开发好的Java类部署到web服务器中。
按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet.
1.2 Servlet的工作流程
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
- Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第二步。
- 装载并创建该Servlet的一个实例对象。
- 调用Servlet实例对象的init()方法。
- 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
- WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
一般情况下,首次访问时被创建,服务器关闭时被销毁。
1.3 实现Servlet的方式
- 实现javax.servlet.Servelt接口
重写五个抽象方法
public void init(ServletConfig servletConfig)
public ServletConfig getServletConfig()
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
public void destroy()
public String getServletInfo()
其中,init,service,destroy为Servlet生命周期方法。
- 继承javax.servlet.GenericServlet类
观察第一种方式发现整个方法种并没有注入ServletConfig,一种方法是给我们自己的Servelt添加一个ServletConfig属性,在init()方法被调用后手动注入servletConfig对象到ServletConfig属性中。
由此得来了GenericServlet,我们只要继承该类,上面的工作都不需要我们去做了,并且只需要实现service方法即可,还能得到ServletConfig的诸多可调用方法。需要注意的是GenericServlet是抽象类,只有service是抽象方法,其他是空实现。继承后只需覆盖service,其他方法看情况选择性覆盖。
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
}
3.继承javax.servlet.http.HttpServlet类
这是一个更为强大的Servlet类,与HTTP协议相关,当接收到请求时,会由service方法判断请求方式,依此来选择调用doget方法或者dopost方法。
由下图可以看出HttpServlet继承了GenderServlet并将原来的ServletRequest和ServletResponse强制转换成了与HTTP协议相关的request和response。
时序图
2 Servlet处理请求
2.1 为什么要使用一个Servlet来处理多个请求?
当浏览器发送了一次请求到服务器时,servlet容器会根据请求的url-pattern找到对应的Servlet类,执行对应的doPost或doGet方法,再将响应信息返回给浏览器,这种情况下,一个具体的Servlet类只能处理对应的web.xml中配置的url-pattern请求,一个Servlet类,一对配置信息。如果业务扩展,需要三个Servlet来处理请求,就需要再加上两个具体的Servlet类,两对配置信息,如果继续向上扩展,就会写n个Servelt类,在web.xml中配置n次ServletConfig,效率非常低下,并且会浪费更多的资源.
为了避免重复的操作(多次编写配置文件,多次新建具体的Servlet类)影响效率,就衍生出一套简单的操作来提高效率,一次配置,多次使用;一个Servlet具体类,处理多个请求。
2.2 解决思路
- 根据请求的地址,截取其中的具体方法名,然后使用if-else判断匹配,再执行具体的方法。
- 根据截取出来的方法名,使用反射,来执行具体的方法。
显然第一种方法当方法很多的时候,会很繁琐。故此肯定选择利用反射的方式来解决。
2.3 BaseServlet
public abstract class BaseServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
/*
* 1. 获取参数,用来识别用户想请求的方法
* 2. 然后判断是哪一个方法,是哪一个我们就调用哪一个
*/
String methodName = req.getParameter("method");
if(methodName == null || methodName.trim().isEmpty()) {
throw new RuntimeException("您没有传递method参数!无法确定您想要调用的方法!");
}
/*
* 得到方法名称,是否可通过反射来调用方法?
* 1. 得到方法名,通过方法名再得到Method类的对象!
* * 需要得到Class,然后调用它的方法进行查询!得到Method
* * 我们要查询的是当前类的方法,所以我们需要得到当前类的Class
*/
Class c = this.getClass();//得到当前类的class对象
Method method = null;
try {
// c.getMethod(name, parameterTypes):需要传入方法名以及方法的参数类型
method = c.getMethod(methodName,
HttpServletRequest.class, HttpServletResponse.class);
} catch (Exception e) {
throw new RuntimeException("您要调用的方法:" + methodName + "(HttpServletRequest,HttpServletResponse),它不存在!");
}
/*
* 调用method表示的方法
*/
try {
String result = (String)method.invoke(this, req, resp);
/*
* 获取请求处理方法执行后返回的字符串,它表示转发或重定向的路径!
* 帮它完成转发或重定向!
*/
/*
* 如果用户返回的是字符串为null,或为"",那么我们什么也不做!
*/
if(result == null || result.trim().isEmpty()) {
return;
}
/*
* 查看返回的字符串中是否包含冒号,如果没有,表示转发
* 如果有,使用冒号分割字符串,得到前缀和后缀!
* 其中前缀如果是f,表示转发,如果是r表示重定向,后缀就是要转发或重定向的路径了!
*/
if(result.contains(":")) {
// 使用冒号分割字符串,得到前缀和后缀
int index = result.indexOf(":");//获取冒号的位置
String s = result.substring(0, index);//截取出前缀,表示操作
String path = result.substring(index+1);//截取出后缀,表示路径
if(s.equalsIgnoreCase("r")) {//如果前缀是r,那么重定向!
resp.sendRedirect(req.getContextPath() + path);
} else if(s.equalsIgnoreCase("f")) {
req.getRequestDispatcher(path).forward(req, resp);
} else {
throw new RuntimeException("你指定的操作:" + s + ",当前版本还不支持!");
}
} else {//没有冒号,默认为转发!
req.getRequestDispatcher(result).forward(req, resp);
}
} catch (Exception e) {
System.out.println("您调用的方法:" + methodName + ", 它内部抛出了异常!");
throw new RuntimeException(e);
}
}
}
当我们在使用SpringMVC的时候,一个Controller下面就可以写多个方法处理不同的请求,还能自动封装入参,而使用原生Servlet的时候则需要我们手动封装入参,还要写个BaseServelt来使一个Servelt可以处理多个请求。
嗯,框架确实减少了我们的工作量,使得我们能够专注于业务层面的工作。