一、什么是 Servlet
servlet(Server Applet) 是javaEE Web的三大核心组件之一。狭义上说Servlet是java语言实现的一个接口,广义上来说是指任何实现了这个接口的类,人们通常将Servlet理解为后者。需要注意的是,servlet接口来自于web服务容器,比如tomcat,所以,servlet的运行依赖于容器的调用,自身没有main函数。
servlet的运行,需要部署在web服务容器中,有访问请求时,容器将请求分派给相应的servlet实现类进行处理。
二、Servlet 生命周期及注解
注解类,3.0 版本后可以使用注解替换web.xml文件。
public @interface WebServlet {
String name() default ""; //标识servlet的名称
String[] value() default {}; //全名匹配的访问路径,支持多个,必须"/"开头且与urlPatterns二选一,同时存在时将会报错无法启动
String[] urlPatterns() default {};//模式匹配的访问路径,支持多个,必须"/"开头且与value二选一,同时存在时将会报错无法启动
int loadOnStartup() default -1; //定义初始化时机与顺序,不配置或设置值小于0时,将在第一次请求到访时执行初始化;设置值大于0时,将伴随着容器的启动而初始化数值越小初始化越早。
WebInitParam[] initParams() default {}; //初始化参数
boolean asyncSupported() default false; //支持异步
String smallIcon() default "";//小图标
String largeIcon() default "";//大图标
String description() default "";//介绍
String displayName() default "";//展示名称
}
serlvet 接口的定义:
public interface Servlet {
//1、初始化servlet,容器启动或第一个请求进入时调用,在容器的一个生命周期里仅执行一次,通过注解的loadOnStartup值决定。对于实现类对象的创建时机总与init调用时机一致,但是前者早于后者一步
void init(ServletConfig var1) throws ServletException;
//获取servlet的配置信息,如servlet的名称,初始化参数等
ServletConfig getServletConfig();
//2、处理web请求,每个请求调用一次
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
获取servlet的所在容器的上下文信息,即全局配置环境信息
String getServletInfo();
//3、销毁,容器关闭时有容器调用
void destroy();
}
测试类:
@WebServlet(name="servletLife", displayName = "生命周期测试",loadOnStartup = -8,value = "/life")
public class ServletLife implements Servlet {
public ServletLife(){
System.out.println("完成对象的创建,但不是初始化");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("完成初始化");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("欢迎光临");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("被销毁了。。。");
}
}
测试结果:
[2022-09-09 11:28:51,441] Artifact ServletDemo:war exploded: Artifact is deployed successfully
[2022-09-09 11:28:51,441] Artifact ServletDemo:war exploded: Deploy took 1,174 milliseconds
09-Sep-2022 23:28:59.926 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [D:\myjava\tomcat8\webapps\manager]
09-Sep-2022 23:28:59.995 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [D:\myjava\tomcat8\webapps\manager] has finished in [69] ms
完成对象的创建,但不是初始化
完成初始化
欢迎光临
欢迎光临
欢迎光临
欢迎光临
欢迎光临
欢迎光临
欢迎光临
欢迎光临
D:\myjava\tomcat8\bin\catalina.bat stop
Using CATALINA_BASE: "C:\Users\tim\AppData\Local\JetBrains\IntelliJIdea2021.1\tomcat\97722028-ee40-4e44-8b69-a4b2464707b1"
Using CATALINA_HOME: "D:\myjava\tomcat8"
Using CATALINA_TMPDIR: "D:\myjava\tomcat8\temp"
Using JRE_HOME: "D:\myjava\jdk11"
Using CLASSPATH: "D:\myjava\tomcat8\bin\bootstrap.jar;D:\myjava\tomcat8\bin\tomcat-juli.jar"
NOTE: Picked up JDK_JAVA_OPTIONS: --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
09-Sep-2022 23:32:23.878 信息 [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance.
09-Sep-2022 23:32:23.878 信息 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-7070"]
09-Sep-2022 23:32:24.457 信息 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"]
09-Sep-2022 23:32:25.004 信息 [main] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
被销毁了。。。
09-Sep-2022 23:32:25.031 信息 [main] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-7070"]
09-Sep-2022 23:32:25.033 信息 [main] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["ajp-nio-8009"]
三、Servlet 的继承体系
GenericServlet是一个与Http协议无关的抽象实现类,通过继承GenericServlet,只需要实现一个service方法。而HttpServlet是一个Http协议相关的实现,在GenericServlet的基础上,根据http请求的方法(get,post,put等)定义了不同的实现,通过查看源码可以发现,在HttpServlet中的service方法已不直接处理请求,而是根据请求的方法对请求做了分发,get请求调用了doGet方法处理,post请求调用了doPost方法处理,而这些方法的形参也从ServletRequest变为了HttpServletRequest,ServletResponse变为了HttpServletResponse。
自定义Servlet时,可以通过继承Servlet、GenericServlet、HttpServlet实现。直接继承Servlet时需要实现5个方法,所以,通常的Web开发中,都建议继承HttpServlet来定义。
需要注意的是,如果通过实现Servlet或GenericServlet自定义servlet,在方法中需要通过ServletRequst获取Http协议相关的内容时,需要先向下封装成HttpServletRequest,通过后者的实例去获取到Http相关的请求信息。
GenericServlet抽象类中service方法源码:
四、Servlet 中的三大作用域
总所周知,http协议本身是无状态的,但是,用户在持续的通信中,却需要保留相关的状态信息,以便于有效的通信。所有,servlet中定义了三大作用域,Request < Session < Application(ServletContext) 分别代表了请求级别、会话级别及应用程序级别,他们保存的自定义变量将在作用域范围内有效。
请求作用域,Request级别。他的出现支持了一个请求在多个servlet中跳转,当然,这里的多个servlet必须处于同一个组件内。request.SetAttribute(key,value) 仅在这个请求作用域内有效,在request.getDispatch(“/a”).forward(req,res),跳转的过程中有效,可以用来在不同的servlet中传值。request.getAttribute(key)获取值。
// /a请求中设置变量值,跳转到另一个servlet中,同一个请求! req.setAttribute("from", "china"); req.getRequestDispatcher("/allscope").forward(req, resp); // /allscope请求 Object from = req.getAttribute("from"); System.out.println("得到一个请求作用域的变量 from=" + from);
seession,回话结束后失效,浏览关闭时回话结束。
HttpSession session = req.getSession(); //设置key-value session.setAttribute("name","tim"); //根据key 获取value session.getAttribute("name");
ServletContext 容器的一个生命周期内有效,ServletContext 实例可以通过多种方式获得。
ServletContext servletContext = req.getServletContext(); Object coutObj = servletContext.getAttribute("count"); if (coutObj != null) { int count = (int) coutObj; servletContext.setAttribute("count", ++count); System.out.println("得到一个全局作用域的变量 count=" + count); } else { servletContext.setAttribute("count", 1); }
总体来说,三大作用域都实现了不同级别的状态保存。而且,都是通过各自实例的 setAttribute getAttribute 来设置、读取值。值类型为object。
五、突破 Servlet 三大作用域
那就是将状态信息保存在cookie中,在服务端创建cookie并设置key-value,随着response返回到客户端并最终保存在客户端,客户端再次发送请求时,浏览器自动携带cookie供服务端读取。当然,cookie支持设置有效期及路径一定程度上保证其中键值对信息的安全。
功夫再高,也怕菜刀,如果用户设置浏览器禁止cookie,那就GG了。
@WebServlet("/sc")
public class SetCookie extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
Cookie cookie=new Cookie("name","tim");
cookie.setMaxAge(24*60*60);
//仅 /ServletDemo_war_exploded/sc 路径下的请求可以见本cookie
//修改cookie时,以cookie名称和路径比对,存在两者相同的,则修改,否则将新建
cookie.setPath("/ServletDemo_war_exploded/sc");
Cookie cookie2=new Cookie("age","100");
cookie2.setMaxAge(24*60*60);
resp.addCookie(cookie);
resp.addCookie(cookie2);
}
}
@WebServlet("/gc")
public class GetCookie extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
Cookie[] cookies=req.getCookies();
if(cookies!=null){
for(Cookie cookie:cookies){
System.out.println(cookie.getName()+":"+cookie.getValue());
}
}
}
}