作为Web应用
没有servlet能独立存在。当前现代Web应用中,许多之间都在一起协作共同完成一个目标。
Web容器模型
3.1 对于servlet和ServletContext初始化参数:编写servlet代码访问初始化参数,创建部署描述文件元素来声明初始化参数。
3.2 对于基本的servlet属性作用域(请求、会话和上下文):编写servlet代码来增加、获取和删除属性;给定一种使用场景,明确属性适当地作用域;明确与各个作用域有关的多线程问题。
3.3 描述Web容器请求模型的元素:过滤器、过滤链、请求和响应包装器,以及Web资源(servlet或JSP页面)。
3.4 描述请求、会话和Web应用的Web容器生命周期事件模型;为每个作用域生命周期创建和配置监听者类;创建和配置作用域属性监听者类;给定一个场景,明确要使用的适当的属性监听者。
3.5 描述RequestDispatcher机制;编写servlet代码来创建一个请求分派器;编写servlet代码转发或包含目标资源;明确容器向目标资源提供的额外的请求作用域属性。
不用硬编码写在servlet类中
解决之道分2步:
配置DD文件
<init-param>
<param-name>adminEmail</param-name>
<param-value>自己的邮箱</param-value>
</init-param>
在servlet中
getServletConfig().getInitParameter("adminEmail");
这里指出,在servlet初始化(init)之前不能使用servlet初始化参数,因为此时还不是一个“真正的servlet”,并且只初始化一次。
ServletConfig的UML
测试ServletConfig
项目路径如下:
DD文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<servlet>
<servlet-name>BeerParamTests</servlet-name>
<servlet-class>com.example.TestInitParams</servlet-class>
<init-param>
<param-name>adminEmail</param-name>
<param-value>likewecare@wickedlysmart.com</param-value>
</init-param>
<init-param>
<param-name>mainEmail</param-name>
<param-value>blooper@wickedlysmart.com</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>BeerParamTests</servlet-name>
<url-pattern>/Tester.do</url-pattern>
</servlet-mapping>
</web-app>
Servlet文件如下:
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class TestInitParams extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("test init parameters<br>");
java.util.Enumeration e = getServletConfig().getInitParameterNames();
while(e.hasMoreElements()){
out.println("<br>param name = "+e.nextElement()+"<br>");
}
out.println("main email is "+getServletConfig().getInitParameter("mainEmail"));
out.println("<br>");
out.println("admin email is "+getServletConfig().getInitParameter("adminEmail"));
}
}
编译连接:
放置部署环境下:
运行结果:
针对应用的初始化参数ServletContext(上下文)
DD文件:
<context-param>
<param-name>adminEmail</param-name>
<param-value>clientheaderror@wickedlysmart.com</param-value>
</context-param>
servlet代码:
getServletContext().getInitParameter("adminEmail");
ServletConfig与SevletContext区别
上下文初始化参数是<web-app ···></web-app>
的孩子元素,应用于整个Web应用。
Servlet初始化参数是<servlet></servlet>
的孩子元素,应用于相应的Servlet。
这样,也说明了每个servlet有一个ServletConfig;每个Web应用有一个ServletContext。
这里注意:分布式应用中,每个JVM都有一个ServletContext。
ServletContext的UML
有两种不同方式得到ServletContext
- getServletConfig().getServletContext()
- this.getServeltContext()
监听者ServletContextListener
在应用为客户提供服务之前运行的一些代码。
例如,我们需要一个单独的对象,它能做到:
上下文初始化(应用正在得到部署)时得到通知
- 从ServletContext得到上下文初始化参数
- 使用初始化参数查找建立一个数据库连接
- 把数据库连接存储为一个属性,使得Web应用的各个部分都能访问
上下文撤销(应用取消部署或结束)时得到通知
- 关闭数据库连接
ServletContextListener的UML
一个简单的ServletContextListener例子(思路)
在这里例子中,我们要把String初始化参数转换为一个真正的对象——一个Dog
Dog例子:
- 监听者对象向ServletContextEvent对象请求应用ServletContext对象的一个引用。
- 监听者使用这个ServletContext引用得到“breed”的上下文初始化参数,这是一个String,表示狗的品种。
- 监听者使用这个狗品种String构造一个Dog对象。
- 监听者使用ServletContext引用在ServletContext中设置Dog属性。
- 这个Web应用的测试servlet从ServletContext得到Dog对象,并调用这个Dog的getBreed()方法。
例子实现
为了方便测试,我们将所有类放置同一包下。
开发环境:
Dog类:
package com.example;
public class Dog{
private String breed;
public Dog(String breed){
this.breed = breed;
}
public String getBreed(){
return breed;
}
}
ServletContextListener类:
package com.example;
import javax.servlet.*;
public class MyServletContextListener implements ServletContextListener{
public void contextInitialized(ServletContextEvent event){
ServletContext sc = event.getServletContext();
String dogBreed = sc.getInitParameter("breed");
Dog d = new Dog(dogBreed);
sc.setAttribute("dog",d);
}
public void contextDestroyed(ServletContextEvent event){
// nothing to do here
}
}
Servlet类:
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ListenerTester extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException , ServletException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("test context attributes set by listener<br>");
out.println("<br>");
Dog dog = (Dog) getServletContext().getAttribute("dog");
if(dog==null)
return;
out.println("Dog's breed is: " + dog.getBreed());
}
}
DD文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<servlet>
<servlet-name>
ListenerTester
</servlet-name>
<servlet-class>com.example.ListenerTester</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListenerTester</servlet-name>
<url-pattern>/ListenTest.do</url-pattern>
</servlet-mapping>
<context-param>
<param-name>breed</param-name>
<param-value>Great Dane</param-value>
</context-param>
<listener>
<listener-class>
com.example.MyServletContextListener
</listener-class>
</listener>
</web-app>
部署环境,并且启动tomcat,结果如下:
更多的监听者
监听者接口 | 场景 | 事件类型 |
---|---|---|
ServletContextAttributeListener | 你想知道一个Web应用上下文中是否增加,删除或替补了一个属性 | ServletContextAttributeEvent |
HttpSessionListener | 你想知道有多少个并发用户。也就是说,你想跟踪活动的会话 | HttpSessionEvent |
ServletRequestListener | 每次请求到来时你都想知道,以便建立日志记录 | ServletRequestEvent |
ServletRequestAttributeListener | 增加,删除或替换一个请求属性时你希望能够知道 | ServletRequestAttributeEvent |
HttpSessionBindingListener | 你有一个属性类,而且你希望这个类型的对象绑定到一个会话或者从会话删除时得到通知 | HttpSessionBindingEvent |
HttpSessionAttributeListener | 增加、删除或替补一个会话属性时你希望能够知道 | HttpSessionAttributeEvent |
ServletContextListener | 你想知道是否创建或撤销了一个上下文 | ServletContextEvent |
HttpSessionActivationListener | 你有一个属性类,而且你希望这个类型的对象在其绑定的会话迁移到另一个JVM时得到通知 | HttpSessionEvent |
这里注意最后一个不是HttpSessionActivationEvent。
属性(attribute)与参数(parameter)
* | 属性 | 参数 |
---|---|---|
类型 | 应用/上下文、请求、会话 | 应用/上下文初始化参数、请求参数、Servlet初始化参数 |
设置方法 | setAttribute(String name, Object value) | 不能设置应用和Servlet初始化参数,请求可以 |
返回类型 | Object | String |
获取方法 | getAttribute(String name) | getInitParameter(String name) |
上下文、请求和会话的作用域
上下文:
请求:
会话:
我们指出,上下文是线程不安全的,Session对于同一个客户来说也是线程不安全的。
SMT(单例Servlet模型)
解决线程不安全问题,不过影响效率。
2种方法:
- 容器创建Servlet实例池(各个实例之间线程不安全,比如实例的属性是不一样的)
- 排队(线程安全)
请求属性和请求分派
我们经常向下面这么做:
requset.setAttribute("styles",result);
request.getRequestDispatcher("result.jsp");
RequestDispatcher揭密
RequsetDispatcher的UML
获得RequestDispatcher的两种办法:
- 从ServletRequest得到RequestDispatcher
- 从ServletContext得到RequestDispatcher
分派方法:在RequestDispatcher上调用forward()
注意:这里指出,如果已经提交了响应,就不能再转发请求!如下:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
response.setContentType("application/jar");
ServletContext ctx = getServletContext();
InputStream is = ctx.getResourceAsStream("bookCode.jar");
int read = 0;
byte[] bytes = new byte[1024];
OutputStream os = response.getOutputStream();
while((read = is.read(bytes))!=-1){
os.write(bytes, 0, read);
}
os.flush();
request.getRequestDispatcher("result.jsp").forward(request, response);
os.close;
}
这样会报一个大大的 IIIegalStateException 异常。