目录
4.1、GenericServlet&HttpServlet
一、servlet简述
java servlet是一个对请求做出响应的对象。当浏览器向服务器发出请求后,web服务器接收到请求后查看是否为servlet,如果是则将请求传递给servlet容器,servlet容器找到对应的servlet对象,调用该对象的service方法来处理请求,产生响应,然后返回给浏览器。tomcat是一个servlet容器,但是它内部集成了web服务器。一个servlet容器可以含有多个web应用,一个web应用含有多个servlet,对应关系如下图所示:
二、servlet生命周期
servlet的生命周期由servlet容器管理,生命周期包含的步骤如下:
- 加载相应servlet类
- 创建servlet实例
- 调用init方法
- 调用service方法
- 调用destroy方法
步骤1、2、3只会执行一次,仅当servlet被加载时执行。默认第一次请求该servlet时才会加载servlet,但是可以配置web.xml文件来强制它在容器启动时加载。步骤4会被执行多次,每来一个请求会被执行一次。步骤5在servlet被卸载时会被执行,只执行一次。
三、容器执行过程
当容器启动时会加载web应用,同时创建ServletContext。ServletContext定义了一些和servlet容器交流的方法,比如得到其他文件的资源、转发请求或写日记。当请求来时会先找和URL匹配的filter过滤器来先预处理请求,然后再把请求传递给相应的servlet,但是如果servlet没有加载则先加载,对servlet初始化过程中会创建ServletConfig的对象,并传给servlet。可以在web.xml中为每一个servlet配置初始参数,可以通过ServletConfig的对象获取。在创建ServletConfig时会将ServletContext放入ServletConfig中,因此通过SerlvetConfig可以获得ServletContext。总之有了servlet并初始化后,则调用它的service方法处理请求,然后返回响应结果。其中请求信息HttpRequest获得,响应结果用HttpResponse表示。容器在放回结果给浏览器时还要将响应结果给filter过滤器进行处理,最后才返回结果。也就是说过滤器可以在servlet处理前后进行预处理和后处理。
上述有点乱、复杂,但是已经讲到了几乎所有要用到的类,过滤器、请求、响应、servlet、ServletConfig、ServletConfig。下面将会详细介绍。
四、相关类
4.1、GenericServlet&HttpServlet
这是一个通用的,协议无关的servlet,同时也是个抽象类,需要重写service方法。在该类的初始化中会被传入ServletConfig对象。
public class SimpleServlet extends GenericServlet {
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
String yesOrNoParam = request.getParameter("param");
if("yes".equals(yesOrNoParam) ){
response.getWriter().write(
"<html><body>You said yes!</body></html>");
}
if("no".equals(yesOrNoParam) ){
response.getWriter().write(
"<html><body>You said no!</body></html>");
}
}
}
上面对参数进行了判断,输出不同的响应结果。
HttpServlet继承于GenericServlet,与http协议有关,不同的http请求方式对应不同的方法,比如想要响应get和post方式的请求,只需重写doGet和doPost方法即可。
public class SimpleHttpServlet extends HttpServlet {
protected void doGet( HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
protected void doPost( HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("GET/POST response");
}
}
4.2、HttpRequest
HttRequest是和http协议相关的类,代表请求。继承于ServletRequest,比ServletRequest多了些协议相关的方法。
通过getParameter可以获得请求参数,比如String param1 = request.getParameter("param1");获得参数param1的值,该参数可以通过get或者post方法传给服务器。
通过getHeader方法可以获得http中相应头部的信息,比如String contentLength = request.getHeader("Content-Length");获得http请求体重的字节长度。
通过getInputStream可以获得字节流,读入二进制数据,通过getReader获得字符流,读入字符数据。
通过getSession方法可以获得会话信息。还有其他的方法使用,比如获得ServletConfig、getServletContext。
4.3、HttpResponse
HttpResponse是和http协议相关的类,代表响应。继承于ServletResponse。通过getWriter获得PrintWrite对象,可写入数据到响应结果中。通过setHeader可以设置响应头部,比如设置Content-Type告诉浏览器接下来返回的是HTML,response.setHeader("Content-Type", "text/html");通过getOutputStream可以写入二进制数据。通过sendRedirect可以让浏览器重定向。
4.4、HttpSession
Session代表会话,每一个会话对应一个用户,可以在用户多次访问web应用不同网页的信息时维护该用户的信息,同一个会话范围内的servlet都可以访问到会话信息。通过HttpSession可以绑定一些对象到会话上,允许用户在多次访问中保持信息一致性。会话可以通过cookies或者重写url实现。setAttribute绑定对象,getAttribute得到已绑定对象。setMaxInactiveInterval设置最大的访问间隔,也就是最多会保存会话信息多久。
4.5、RequestDispatcher
RequestDispatcher可以让你在一个servlet中调用另一个servlet,可以通过请求的getRequestDispatcher方法获得。含有两个方法:forward和include,分别对应着转发和包含的功能。
forward:将请求传发另一个servlet之前不能关闭流(或提交输出),否则会抛出异常,请求转发后会清空输出流,就是说转发之前写入的数据都被抛弃(测试了下,转发之后对response的输出无效)。因此该方法多用于对请求进行预处理,使之更适合目标servlet。传入的两个参数必须是之前的request和response,不能更换。
include:主要用于将其他servlet输出的内容包含进来。将请求转发给目标servlet后,目标servlet对response(响应)请求头、响应的状态的修改都会忽略,因此只会得到目标servlet内容的输出。因此此方法很适合将其他servlet的内容包含进来。
4.6、ServletContext
很多中方法可以获得ServletContext,比如通过请求的getServiceContext方法。该类可以与容器交流,主要可以存入对象到ServletContext中,由于ServletContext有着和web应用一样的生命周期且存放在容器中,因此所有servlet都可以在整个应用生命周期中访问该对象。
4.7、ServletConfig
servlet初始化时被提供的,可以通过它获得servlet对应的初始化参数,该参数由web.xml配置。
注意:对象可以存入到HttpSession和ServletContext中,然后可以在对应的范围内共享数据,但是由于HttpSession和ServletContext都是存在容器内存中的,因此在集群的服务器组中,却不能通过这两个类共享数据,因为每一个服务器都有一个容器,也就是说数据可能会出现多份。因此可以选择将共享数据存入到数据库中。
五、web.xml配置
容器是通过读入web应用的web.xml文件得到该应用所有的信息的,包括servlet的配置信息。只有在web.xml中配置了servlet,容器才是知道有该servlet,知道怎么使用该servlet。
5.1、映射servlet
通过web.xml,可以将servlet映射到某一个URL上,因此用户可以通过该URL来访问servlet。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.luo.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/servlet1</url-pattern>
</servlet-mapping>
</web-app>
通过servlet元素为一个servlet的设置名字,通过该名字在servlet-mapping中配置URL映射。通过[域名]/[应用名]/servlet1就可以访问HelloServlet类的对象了。
注意,后面的servlet的URL映射可以覆盖之前的servlet。
5.2、配置servlet初始参数
<servlet>
<servlet-name>controlServlet</servlet-name>
<servlet-class>com.jenkov.butterfly.ControlServlet</servlet-class>
<init-param>
<param-name>myParam</param-name>
<param-value>paramValue</param-value>
</init-param>
</servlet>
配置了一个参数myParam,值为paramValue。因此可以通过ServletConfig获取参数值了。
public class SimpleServlet extends GenericServlet {
protected String myParam = null;
public void init(ServletConfig servletConfig) throws ServletException{
this.myParam = servletConfig.getInitParameter("myParam");
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
response.getWriter().write("<html><body>myParam = " +
this.myParam + "</body></html>");
}
}
5.3、Servlet Load-on-Startup
servlet默认第一次被访问时初始化,但是可以配置Load-on-Startup元素强制它初始化。
<servlet>
<servlet-name>controlServlet</servlet-name>
<servlet-class>com.jenkov.webui.ControlServlet</servlet-class>
<init-param><param-name>container.script.static</param-name>
<param-value>/WEB-INF/container.script</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
此时,ControlServlet会随着容器启动而启动,而里面的1代表着启动的顺序。如果是正整数,则值越小越先加载,如果值小于0或者没有设置该元素时,只有第一次请求时才会初始化。(貌似值为0,也是可以随容器启动而启动的,没有测试它的启动优先级,文档上也没有说值为0时会怎么样。。)
5.4、配置应用初始参数
可以给servlet配置初始参数,自然也可以给应用配置初始参数。
<context-param>
<param-name>myParam</param-name>
<param-value>the value</param-value>
</context-param>
然后通过ServletContext的getInitParameter方法获得该参数值。
六、cookies
设置cookies
Cookie cookie = new Cookie("myCookie", "myCookieValue");
response.addCookie(cookie);
读取cookies
Cookie[] cookies = request.getCookies();
String userId = null;
for(Cookie cookie : cookies){
if("uid".equals(cookie.getName())){
userId = cookie.getValue();
}
}
设置过期时间
Cookie cookie = new Cookie("uid", "123");
cookie.setMaxAge(24 * 60 * 60); // 24 hours.
response.addCookie(cookie);
删除cookies
Cookie cookie = new Cookie("uid", "");
cookie.setMaxAge(0);
response.addCookie(cookie);
七、过滤器
过滤器会拦截http到servlet的请求,能够在不改变servlet源代码的基础上添加新的功能。多个过滤器拦截一个servlet的请求时会构成一个链(chain),这些过滤器顺序执行。执行过程如下:
请求-->filter1-->filter2-->servlet-->filter2-->filter1。
过滤器实现:
public class MyFilter implements Filter{
public void init(FilterConfig arg0) throws ServletException {}
public void destroy() {}
private static int count=0;
private int number=count++;
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
PrintWriter writer=resp.getWriter();
writer.write("filter"+number+"<br/>");
chain.doFilter(req, resp);//sends request to next resource
writer.write("filter"+number+"<br/>");
}
}
web.xml配置
<filter>
<filter-name>f1</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
<filter>
<filter-name>f2</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>f1</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>f2</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
会拦截所有请求。
结果:
八、监听器
监听器可以监听到web应用中发生的事件,并作出反应。一个有三种层次的servlet事件:servlet context级别的事件、session级别的事件、请求级别的事件。每种级别都分为两类事件:声明周期变化,属性变化。下面是事件分类和接口对应的表格:
Event Category | Event Descriptions | Java Interface |
---|---|---|
Servlet context lifecycle changes | Servlet context creation, at which point the first request can be serviced Imminent shutdown of the servlet context | javax.servlet. ServletContextListener |
Servlet context attribute changes | Addition of servlet context attributes Removal of servlet context attributes Replacement of servlet context attributes | javax.servlet. ServletContextAttributeListener |
Session lifecycle changes | Session creation Session invalidation Session timeout | javax.servlet.http. HttpSessionListener |
Session attribute changes | Addition of session attributes Removal of session attributes Replacement of session attributes | javax.servlet.http. HttpSessionAttributeListener |
request lifecycle changes | a ServletRequest is about to come into scope of the web application. a ServletRequest is about to go out of scope of the web application. | javax.servlet. ServletRequestEvent |
request attribute changes | This is the event class for notifications of changes to the attributes of the servlet request in an application. | javax.servlet. ServletRequestAttributeEvent |
监听类需要实现上面的某个接口,然后在web.xml中的listener元素中声明这个监听器,比如下面声明了几个监听器:
<web-app>
<display-name>MyListeningApplication</display-name>
<listener>
<listener-class>com.acme.MyConnectionManager</listenerclass>
</listener>
<listener>
<listener-class>com.acme.MyLoggingModule</listener-class>
</listener>
<servlet>
<display-name>RegistrationServlet</display-name>
...
</servlet>
</web-app>
九、并发
当多个请求给同一个servlet时会出现并发执行service方法的情况。想让线程安全,必须遵循一些原则:
- service方法中尽量不要访问成员变量,除非成员变量是线程安全的。
- 在service方法中不要重新赋值给成员变量,如果必须赋值,则需要用同步块(synchronized)进行同步。
- 规则1、2都使用与静态变量
- 局部变量是线程安全的,但是局部变量指向的对象不一定是线程安全的。
public class SimpleHttpServlet extends HttpServlet {
// Not thread safe, static.
protected static List list = new ArrayList();
// Not thread safe
protected Map map = new HashMap();
// Thread safe to access object, not thread safe to reassign variable.
protected Map map = new ConcurrentHashMap();
// Thread safe to access object (immutable), not thread safe to reassign variable.
protected String aString = "a string value";
protected void doGet( HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Not thread safe, unless the singleton is 100% thread safe.
SomeClass.getSomeStaticSingleton();
// Thread safe, locally instantiated, and never escapes method.
Set set = new HashSet();
}
}
参考
教程:http://tutorials.jenkov.com/java-servlets/index.html
详细的web.xml配置:https://docs.oracle.com/cd/E24329_01/web.1211/e21049/web_xml.htm#WBAPP502
api 文档:https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html
过滤器和监听器:https://docs.oracle.com/cd/B14099_19/web.1012/b14017/filters.htm#i1000654
监听器和例子:https://www.journaldev.com/1945/servletcontextlistener-servlet-listener-example