1、servlet的体系架构
serlvet 的这套东西大概如下图,这些是基本的东西 。HttpSerlvet,HttpServletRequest 这些扩展的东西就没有放到这里了。
这些接口是干什么用的呢:
- ServletRequest:规定了获取请求相关信息的接口,由容器实现。
- ServletResponse : 由容器实现,提供了相关响应请求的接口。
- ServletConfig:由web容器实现 提供了相关servlet配置信息的接口
- ServletContext: web容器会对每个web应用创建一个环境,所以的组件,jsp 等都运行在同一个环境中,ServletContext就是提供了这个环境访问途径。由容器实现
- servlet: 由业务实现 提供了 init() service() destroy() 给容器对servlet进行初始化和 调用服务 和销毁 的时候调用。
相关Servlet的接口说明可以看
http://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/
2、serlvet和web容器协同工作流程
工作流程大致如下:
- 初始化环境: 根据context.xml的配置 进行初始化 (这个时候就会实例化一个ServletContext)
- 加载web.xml 根据web.xml描述初始化相关组件
- 有请求到达容器的时候 容器处理请求 实例一个 servletRequest 和 servletResponse
- 调用servlet的service方法 将servletRequest 和 servletResponse 实例传给了 servlet
- servlet 处理完成相关业务逻辑 将结果写入servletResponse 中
下面是模拟这个工作过程的测试代码 (摘取重要代码)
处理请求的过程
...
private void processRequest(ServerSocket serverSocket) throws IOException {
System.out.println("等待请求");
Socket socket=serverSocket.accept();
System.out.println("接受到请求:"+socket.getRemoteSocketAddress().toString());
InputStream in=socket.getInputStream();
BaseRequest request = new BaseRequest(in);
OutputStream out = socket.getOutputStream();
BaseResponse response = new BaseResponse(out);
ServletProcessor.processServletRequest(request, response);
socket.close();
}
...
...
public static void processServletRequest(BaseRequest request, BaseResponse response) {
String uri = request.getUri();
String servletName ="com.saoyor.controller.HelloController";
Servlet servlet = loadServlet(servletName);
servlet.service(request, response);
}
...
处理请求数据
...
public BaseRequest(InputStream in) {
this.inputStream = in;
prepareContent();
parseAndSetUri();
}
private void prepareContent() {
byte[] buffer = new byte[2048];
int i = -1;
try {
i = inputStream.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(i);
for (int k = 0; k < i; k++) {
requestContentBuffer.append((char) buffer[k]);
}
System.out.println(requestContentBuffer.toString());
}
private void parseAndSetUri() {
String requestString = requestContentBuffer.toString();
int index1 = requestString.indexOf(' ');
int index2 = -1;
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
}
this.uri = (index2 > index1) ? requestString.substring(index1 + 1,
index2) : null;
}
public String getUri() {
return this.uri;
}
...
3、servlet的生命周期
1、 servlet的创建
有两种创建方式 一种是配置容器启动的时候就创建,另外一种是 容器请求的时候发现没有这个servlet就会根据web.xml的表述创建一个。
servlet被创建的时候会调用 一次init()方法 并且会传人 ServletConfig对象
2、servlet处理请求
servlet对象一旦被创建就会一直存在 直到被销毁了。所以的对这个servlet请求都会调用同一个对象的service方法。
3、servlet的销毁
servlet只有在容器关闭 或者被移除的时候才会销毁 销毁的时候会调用servlet的destroy()方法 可以处理程序在被销毁前需要处理的事情
如果下图
因为每个请求使用同一个的servlet,这样就牵扯到了线程的安全问题,这里有一些开发建议
用方法的局部变量保存请求中的专有数据。对方法中定义的局部变量,进入方法的每个线程都有自己的一份方法变量拷贝。任何线程都不会修改其他线程的局部变量。
只用Servlet的成员变量来存放那些不会改变的数据。有些数据在Servlet生命周期中不发生任何变化,通常是在初始时确定的,这些数据可以使用成员变量保存,如数据库连接名称、其他资源的路径等。
对可能被请求修改的成员变量同步。有时数据成员变量或者环境属性可能被请求修改。当访问这些数据时应该对它们同步,以避免多个线程同时修改这些数据。
如果Servlet访问外部资源,那么需要同步访问这些资源。例如,假设Servlet要从文件中读写数据。当一个线程读写一个文件时,其他线程也
可能正在读写这个文件。文件访问本身不是线程安全的,所以必须编写同步访问这些资源的代码。
在编写线程安全的Servlet时,下面两种方法是不应该使用的:
在Servlet API中提供了一个SingleThreadModel接口,实现这个接口的Servlet在被多个客户请求时一个时刻只有一个线程运行。这个接口已被标记不推荐使用。
对doGet()或doPost()方法同步。如果必须在Servlet中使用同步代码,应尽量在最小的代码块范围上进行同步。同步代码越小,Servlet执行得才越好。
4、web.xml
web.xml 是用来配置业务组件的,容器启动的时候会加载这份web.xml去指导容器初始化相关组件。
下面是一份用来测试的web.xml
<web-app>
<display-name>Archetype Created Web Application</display-name>
<description>用了测试servlet 相关功能</description>
<!-- 过滤器配置 过滤器是会运行在请求之前 和 响应之后的-->
<!-- 过滤器在 文档中的位置一定要在servlet的配置之前-->
<filter>
<filter-name>requestfilter</filter-name>
<filter-class>com.soyor.servlet.filter.RequestFilter</filter-class>
</filter>
<!-- 配置过滤哪些 servlet -->
<filter-mapping>
<filter-name>requestfilter</filter-name>
<servlet-name>Hello</servlet-name>
</filter-mapping>
<!-- 过滤器同样可以对请求路径起作用-->
<filter-mapping>
<filter-name>requestfilter</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
<!-- 定义servlet-->
<servlet>
<servlet-name>Hello</servlet-name>
<servlet-class>com.soyor.servlet.HelloController</servlet-class>
<!-- servlet的初始化变量 -->
<init-param>
<param-name>abc</param-name>
<param-value>123</param-value>
</init-param>
<!-- servlet的加载的优先级 <0 的时候 启动不加载-->
<load-on-startup>2</load-on-startup>
</servlet>
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>com.soyor.servlet.TestController</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/Hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
web.xml 相关元素说明
元素 | 说明 |
---|---|
icon | 元素指出IDE和GUI工具用来表示Web应用的一个和两个图像文件的位置。 |
display-name | 元素提供GUI工具可能会用来标记这个特定的Web应用的一个名称。 |
description | 元素给出与此有关的说明性文本。 |
context-param | 元素声明应用范围内的初始化参数。 |
filter | 过滤器元素将一个名字与一个实现javax.servlet.Filter接口的类相关联。 |
filter-mapping | 一旦命名了一个过滤器,就要利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。 |
listener | servlet API的版本2.3增加了对事件监听程序的支持,事件监听程序在建立、修改和删除会话或servlet环境时得到通知。Listener元素指出事件监听程序类。 |
servlet | 在向servlet或JSP页面制定初始化参数或定制URL时,必须首先命名servlet或JSP页面。Servlet元素就是用来完成此项任务的。 |
servlet-mapping | 服务器一般为servlet提供一个缺省的URL:http://host/webAppPrefix/servlet/ServletName。但是,常常会更改这个URL,以便servlet可以访问初始化参数或更容易地处理相对URL。在更改缺省URL时,使用servlet-mapping元素。 |
session-config | 如果某个会话在一定时间内未被访问,服务器可以抛弃它以节省内存。可通过使用HttpSession的setMaxInactiveInterval方法 明确设置单个会话对象的超时值,或者可利用session-config元素制定缺省超时值。 |
mime-mapping | 如果Web应用具有想到特殊的文件,希望能保证给他们分配特定的MIME类型,则mime-mapping元素提供这种保证。 |
welcom-file-list | 元素指示服务器在收到引用一个目录名而不是文件名的URL时,使用哪个文件。 |
error-page | 元素使得在返回特定HTTP状态代码时,或者特定类型的异常被抛出时,能够制定将要显示的页面。 |
taglib | 元素对标记库描述符文件(Tag Libraryu Descriptor file)指定别名。此功能使你能够更改TLD文件的位置,而不用编辑使用这些文件的JSP页面。 |
resource-env-ref | 元素声明与资源相关的一个管理对象。 |
resource-ref | 元素声明一个资源工厂使用的外部资源。 |
security-constraint | 元素制定应该保护的URL。它与login-config元素联合使用 |
login-config | 用login-config元素来指定服务器应该怎样给试图访问受保护页面的用户授权。它与sercurity-constraint元素联合使用。 |
security-role | 元素给出安全角色的一个列表,这些角色将出现在servlet元素内的security-role-ref元素的role-name子元素中。分别地声明角色可使高级IDE处理安全信息更为容易。 |
env-entry | 元素声明Web应用的环境项。 |
ejb-ref | 元素声明一个EJB的主目录的引用。 |
ejb-local-ref | 元素声明一个EJB的本地主目录的应用。\ |