Java Web开发中,理解Servlet是非常重要的。ServletConfig和ServletContext是在Servlet开发中需要知道的两个重要的概念,这里分别介绍。
1、ServletConfig
ServletConfig是针对单个servlet的配置,可以通过它配置对应servlet用到的参数。
1.1 上手
有时候,在开发的时候,我们希望能在web.xml
文件中为Servlet配置一些参数,在Servlet中能够读取。这样,就避免了在java代码中写死参数,每次修改都得重新编译。
首先,需要在web.xml
中配置相关待读取的参数,比如这里,我们想配置下应用管理员的一些信息,如下。注意,这些参数是配置在servlet标签的子标签中的。
<servlet>
<servlet-name>ServletConfigTest</servlet-name>
<servlet-class>com.web.test.ServletConfigTest</servlet-class>
<init-param>
<param-name>administratorName</param-name>
<param-value>PaopaoYu</param-value>
</init-param>
<init-param>
<param-name>administratorEmail</param-name>
<param-value>PaopaoYuIsACat@163.com</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ServletConfigTest</servlet-name>
<url-pattern>/ServletConfig.Test</url-pattern>
</servlet-mapping>
在Servlet中,可以通过getServletConfig()方法,得到一个ServletConfig实例的引用,调用相关的方法可以得到这些参数的值。如下,是Servlet的代码。
package com.web.test;
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.io.PrintWriter;
/**
* Created by chengxia on 2018/12/3.
*/
public class ServletConfigTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("The administrator infomation as below:<br/>");
java.util.Enumeration e = getServletConfig().getInitParameterNames();
while (e.hasMoreElements()){
out.println("<br/>Init parameter for servlet, name = " + e.nextElement());
}
out.println("<br/>Administrator Name: " + getServletConfig().getInitParameter("administratorName"));
out.println("<br/>Administrator Email: " + getServletConfig().getInitParameter("administratorEmail"));
out.flush();
out.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
启动tomcat服务器之后,访问http://localhost:8080/ServletConfig.Test
的效果如下:
1.2 容器处理ServletConfig的机制
Tomcat容器在初始化一个servlet时,会为这个servlet创建一个唯一的ServletConfig。容器从web.xml
文件读取相关的servlet初始化参数,并将其交给ServletConfig,然后,把ServletConfig传递给servlet的init()方法。
这样,我们在编写servlet的代码时,就能够很方便的通过调用getServletConfig()方法来获得ServletConfig实例的引用了。
这里,可能会有一个疑问。我们经常需要重写Servlet类的initi()方法,而这个方法是没有参数的。容器是如何将ServletConfig传递给init()方法的呢?实际上,我们实现的Servlet的父类中有两个init()方法:一个是init(ServletConfig);一个是init()。前者会调用后者,所以,在一般的需求中只需要覆盖后者,即可达到初始化的效果。如果非要覆盖init(ServletConfig)方法,最好先调用super.init(ServletConfig)
,这样能够确保不会漏掉想关的初始化操作。
1.3 Jsp中如何获得Servlet初始化参数
如果要在Jsp中获得上面配置的Servlet初始化参数,可以在用RequestDispatcher将请求转发给jsp时,设置请求属性。如下。
web.xml
参数配置:
<servlet>
<servlet-name>ServletConfig2Jsp</servlet-name>
<servlet-class>com.web.test.ServletConfig2Jsp</servlet-class>
<init-param>
<param-name>administratorName</param-name>
<param-value>PaopaoYu</param-value>
</init-param>
<init-param>
<param-name>administratorEmail</param-name>
<param-value>PaopaoYuIsACat@163.com</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ServletConfig2Jsp</servlet-name>
<url-pattern>/ServletConfig.2jsp</url-pattern>
</servlet-mapping>
ServletConfig2Jsp.java
的Java代码:
package com.web.test;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by chengxia on 2018/12/3.
*/
public class ServletConfig2Jsp extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("administratorName",getServletConfig().getInitParameter("administratorName"));
request.setAttribute("administratorEmail",getServletConfig().getInitParameter("administratorEmail"));
RequestDispatcher view = request.getRequestDispatcher("adminInfo.jsp");
view.forward(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
adminInfo.jsp
代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Administrator Infomition</title>
</head>
<body>
<h1>Administrator Infomation:</h1>
<%
out.print("<h2>Name: " + request.getAttribute("administratorName") + "</h2>");
out.print("<h2>Email: " + request.getAttribute("administratorEmail") + "</h2>");
%>
<h2></h2>
</body>
</html>
启动Tomcat服务器之后的运行效果:
上面是将信息分别从ServletConfig中取出之后,设置到请求对象中的。其实也可以直接将ServletConfig实例设置为Request对象的属性。如下:
ServletConfig2Jsp.java
java代码:
package com.web.test;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by chengxia on 2018/12/3.
*/
public class ServletConfig2Jsp extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("servletConfig",getServletConfig());
RequestDispatcher view = request.getRequestDispatcher("adminInfo.jsp");
view.forward(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
adminInfo.jsp
jsp代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Administrator Infomition</title>
</head>
<body>
<h1>Administrator Infomation2:</h1>
<%
out.print("<h2>Name: " + ((ServletConfig)request.getAttribute("servletConfig")).getInitParameter("administratorName") + "</h2>");
out.print("<h2>Email: " + ((ServletConfig)request.getAttribute("servletConfig")).getInitParameter("administratorEmail") + "</h2>");
%>
<h2></h2>
</body>
</html>
这样的运行效果一样。
2、ServletContext
上面的ServletConfig中的参数,只能够被其所配置的servlet访问到。如果希望所配置的参数,能够被应用中的各个servlet和jsp访问到。这时候,就需要用到ServletContext。
2.1 上手
类似地,在web.xml
中配置好参数,在servlet中调用getServletContext()方法就能够访问到,如下。
web.xml
配置文件中增加配置。注意,这里的参数是配置在web-app
的子节点中的。如下。
<context-param>
<param-name>administratorName</param-name>
<param-value>PaopaoYu</param-value>
</context-param>
<context-param>
<param-name>administratorEmail</param-name>
<param-value>PaopaoYuIsACat@163.com</param-value>
</context-param>
<servlet>
<servlet-name>ServletContextTest</servlet-name>
<servlet-class>com.web.test.ServletContextTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletContextTest</servlet-name>
<url-pattern>/ServletContext.Test</url-pattern>
</servlet-mapping>
ServletContextTest.java
java代码:
package com.web.test;
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.io.PrintWriter;
/**
* Created by chengxia on 2018/12/3.
*/
public class ServletContextTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("The administrator infomation from ServletContext as below:<br/>");
out.println("<br/>Administrator Name: " + getServletContext().getInitParameter("administratorName"));
out.println("<br/>Administrator Email: " + getServletContext().getInitParameter("administratorEmail"));
out.flush();
out.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
实际上,ServletConfig对象中,也有ServletContext的引用,所以有时候,也会通过getServletConfig().getServletContext().getInitParameter("administratorName")
来获取上下文参数。
启动Tomcat服务器之后,运行效果如下:
2.2 Tomcat容器处理ServletContext的机制
整个Web应用只有一个ServletContext,Web应用中的所有部分都能够访问它。在Web应用启动的时候,Tomcat容易会读取web.xml
配置文件,对其中的每个<context-param>
创建key-value
对。然后,容器会创建ServletContext的一个实例,并为其提供上下文初始化参数各个key-value
对的引用。Tomcat容器创建的这个ServletContext实例能够被该Web应用中部署的各个servlet和jsp访问。
注意,这里说的每一个Web应用只有一个ServletContext,是指只有一个jvm的情况。在分布式环境中,每个jvm有一个ServletContext。这一点在分布式环境中,需要考虑到。
2.3 ServletContext设置监听
ServletContext能够被应用的所有部分访问,而web.xml
文件中配置的参数都只是字符串。如果我们需要用这些参数,构造对应的对象结构放在ServletContext中,,就需要为ServletContext设置监听。在ServletContext初始化的时候,执行对应的初始化方法,在其中构造相应的对象,然后,将其设置为ServletContext的属性,放在ServletContext中。
设置监听的方法比较简单。只需要新建一个类,实现ServletContextListener接口,然后按需重写接口中的contextInitialized(ServletContext event)
方法或者contextDestroyed(ServletContext event)
方法即可。
下面是一个利用上下文参数在监听函数中构造对象的简单例子。
首先,写一个Cat类,用来作为监听函数中构造对象的类:
package com.web.comp;
/**
* Created by chengxia on 2018/12/31.
*/
public class Cat {
private String name;
private String ownerName;
public Cat(String name, String ownerName) {
this.name = name;
this.ownerName = ownerName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOwnerName() {
return ownerName;
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
}
实现一个监听类:
package com.web.comp;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* Created by chengxia on 2018/12/31.
*/
public class CatServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext sc = servletContextEvent.getServletContext();
String name = sc.getInitParameter("catName");
String ownerName = sc.getInitParameter("catOwnerName");
Cat cat = new Cat(name, ownerName);
sc.setAttribute("cat", cat);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
写一个测试类servlet:
package com.web.test;
import com.web.comp.Cat;
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.io.PrintWriter;
/**
* Created by chengxia on 2018/12/3.
*/
public class ServletContextListenerTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("The cat infomation from ServletContext Attribute as below:<br/>");
Cat c = (Cat)getServletContext().getAttribute("cat");
out.println("<br/>Cat Name: " + c.getName());
out.println("<br/>Cat Owner's Name: " + c.getOwnerName());
out.flush();
out.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
最后,在配置文件web.xml
中,除了配置相关的servlet及其映射、上下文参数,还要配置监听。如下。
<listener>
<listener-class>com.web.comp.CatServletContextListener</listener-class>
</listener>
<context-param>
<param-name>catName</param-name>
<param-value>Paopao</param-value>
</context-param>
<context-param>
<param-name>catOwnerName</param-name>
<param-value>ChengXia</param-value>
</context-param>
<servlet>
<servlet-name>ServletContextListenerTest</servlet-name>
<servlet-class>com.web.test.ServletContextListenerTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletContextListenerTest</servlet-name>
<url-pattern>/ServletContextListener.Test</url-pattern>
</servlet-mapping>
启动Tomcat之后运行的效果如下:
ServletContextListener.Test运行效果
2.4 上下文属性作用域不是线程安全的
当每次请求都会有一个线程来处理,当多个线程同时访问和设置上下文作用域中的属性时会有各种因为线程安全导致的问题。
这时候不能像下面一样对get或者post服务方法进行加锁同步:
protected synchronized void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Test Context Attribute:<br/>");
getServletContext().setAttribute("foo", 22);
getServletContext().setAttribute("bar", 42);
getServletContext().getAttribute("foo");
getServletContext().getAttribute("bar");
out.flush();
out.close();
}
而应该对上下文加锁:
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("Test Context Attribute:<br/>");
synchronized (getServletContext()) {
getServletContext().setAttribute("foo", 22);
getServletContext().setAttribute("bar", 42);
getServletContext().getAttribute("foo");
getServletContext().getAttribute("bar");
}
out.flush();
out.close();
}
同样地,会话属性也不是线程安全的,需要对HttpSession对象同步来保护会话操作。但是,请求属性是线程安全的,因为它根本不涉及多线程。
3、监听和属性扩展
前面的例子中,用到了监听和属性这两个概念。接下来展开讲下。
3.1 监听
生命周期里的每一个重要时刻,都会有一个监听者在监听。除了上下文事件,还可以监听于上下文属性,servlet请求和属性,以及Http会话和会话属性相关的事件。下面是几个例子。
- ServletContextAttributeListener:监听Web应用上下文中是否增加、删除或者替换了一个属性。
- HttpSessionListener:跟踪会话活动。
- ServletRequestListener:监听请求到来,以便建立日志。
- ServletRequestAttributeListener:监听请求属性是否有增加、替换或者删除。
- HttpSessionAttributeListener:监听会话属性是否被删除、替换或者新增。
- HttpSessionBindingListener:有一个属性类,如果希望这个类型的对象在绑定到一个会话,或者从一个会话删除时得到通知。应使用这个监听。
- ServletContextListener:监听应用上下文创建或者撤销。
3.2 属性
前面在请求分派和ServletContext的内容中都提到了属性。实际上,属性就是一个对象可以设置(也叫做绑定)到另外3个Servlet API对象中的任何一个,包括ServletContext、HttpServletContext和HTTPSession。可以将属性,简单理解为一个映射实例对象中的key-value
对,key是一个String,value是一个Object。
网友评论