1.Servlet 入门实例
第一步:implements Servlet
创建一个JavaWeb项目,并创建一个servlet类-----HelloServlet,实现接口 Servlet
public class HelloServlet implements Servlet{
// 只被调用一次,第一次请求Servlet时,创建Servlet的实例,调用构造器
public HelloServlet() {
System.out.println("构造器 HelloServelt()...");
}
// 该方法用于初始化Servlet,就是把该Servlet装载入内存
// 只被调用一次,在创建好实例后立即被调用
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化方法 init()...");
}
// 被多次调用,每次请求都会调用service方法。实际用于响应请求的
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException,
IOException {
System.out.println("执行方法主体 service()...");
}
// 只被调用一次,在当前Servlet所在的WEB应用被卸载前调用,用于释放当前Servlet所占用的资源
@Override
public void destroy() {
System.out.println("servlet 销毁时调用方法 destroy()...");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
}
第二步:web.xml
在 web.xml 文件中配置上面创建的 HelloServlet 映射关系
<?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">
<!--在tomcat 服务器中运行时,如果不指名访问文件名,默认的根据项目名访问文件顺序如下配置 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!--给创建的 Servlet 配置映射关系 -->
<servlet>
<servlet-name>helloServlet</servlet-name>
<!--servlet的完整名称-->
<servlet-class>com.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 与上面配置的 servlet-name 名字要对应,一个servlet可以有多个 servlet-mapping -->
<servlet-name>helloServlet</servlet-name>
<!--访问路径-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
第三步:部署到tomcat
将项目部署在 tomcat 服务器,如何部署请看这篇文章:https://cloud.tencent.com/developer/article/1012658,然后启动服务器
第四步:启动并访问
-
我们直接通过项目名来访问,由于我们在 web.xml 文件中配置了 ,那么会依次找下面配置的文件,我们只创建了一个 index.jsp,那么就会访问这个JSP 文件

-
我们可以看控制台打印内容如下:

-
如果我们不断的刷新 http://localhost:8080/ServletImprove/hello 这个访问链接,那么控制台如下:

2.Servlet 生命周期
我们通过上面的实例,可以看到也就是只有第一次才会执行构造器和 init() 方法,后面每次点击都只调用 service() 方法。那这是为什么呢?
- 客户端向 Web 服务器发送请求,服务器查询 web.xml 文件配置。根据请求信息找到对应的 Servlet。
- Servlet 引擎检查是否已经装载并创建了该 Servlet 的实例对象,如果有,则直接执行第4步,否则执行第3步,
- Web 服务器加载 Servlet,并调用 Servlet 构造器(只会调用一次),创建 Servlet 的实例对象。并调用 init() 方法,完成 Servlet 实例对象的初始化(只会调用一次)
- Web 服务器把接收到的 http 请求封装成 ServletRequest 对象,并创建一个响应消息的 ServletResponse 对象,作为 service() 方法的参数传入。(每一次访问都会调用一次该方法)
- 执行 service() 方法,并将处理信息封装到 ServletResponse 对象中返回
- 浏览器拆除 ServletResponse 对象,形成 http 响应格式,返回给客户端。
- Web 应用程序停止或者重新启动之前,Servlet 引擎将卸载 Servlet实例,并在卸载之前调用 destory() 方法
3.Server 继承关系
3.1 Servlet接口
public interface Servlet {
// init()方法接收一个ServletConfig参数,由容器传入,ServletConfig就是Servlet的配置,
// 在web.xml中定义Servlet时通过init-param标签配置的参数由ServletConfig保存
public void init(ServletConfig config) throws ServletException;
// 获取Servlet配置
public ServletConfig getServletConfig();
// 处理具体请求
public void service(ServletRequest req, ServletResponse res)throws ServletException,
IOException;
// 获取Servlet的相关信息
public String getServletInfo();
// 销毁对象,服务器关闭时执行
public void destroy();
}
3.2 GenericServlet抽象类
抽象类,实现了Servlet接口的部分方法
- 实现了ServletConfig的接口
- 可以调用ServletConfig里面的方法,不需要获取getServletConfig对象。
- 获取ServletContext可以直接调用getServletContext,不用getServletConfig().getServletContext()
- 用init()把ServletConfig变成内部变量引用,然后重写ServletConfig接口的所有方法,用这个变量调用
- 实现了部分Servlet方法
- 提供了无参init()方法
- getServletConfig()
- getServletInfo()
- 扩展:提供了两个log()方法,
- 一个记录日志
- 一个记录异常,其具体实现是通过传给ServletConfig的日志实现的
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws
ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
}
创建Servlet示例
public class HelloServlet extends GenericServlet{
public HelloServlet() {
System.out.println("构造器 HelloServelt()...");
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化方法 init()...");
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException,
IOException {
System.out.println("执行方法主体 service()...");
}
@Override
public void destroy() {
System.out.println("servlet 销毁时调用方法 destroy()...");
}
}
3.3 HttpServlet类
HttpServlet是基于Http协议实现的Servlet基类,我们刚开始写Servlet的时候是实现Servlet接口,后面在写Servlet的时候直接继承HttpServlet类就可以了,HttpServlet处理请求主要是通过重写父类的service()来完成具体请求的
- 在第一个service里是把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse,然后调用第二个service()方法
- 第二个service则根据http不同的请求,调用相应的方法处理,如doGet,doPost,所以我们代码也可以写在doGet,doPost里

public abstract class HttpServlet extends GenericServlet {
// doGet,doPost,doDelete....
protected void service(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
}
创建servlet示例
public class HelloServlet extends HttpServlet{
public HelloServlet() {
System.out.println("构造器 HelloServelt()...");
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化方法 init()...");
}
// 处理 post 请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
}
// 处理get请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
}
@Override
public void destroy() {
System.out.println("servlet 销毁时调用方法 destroy()...");
}
}
4.Servlet 线程安全问题
我们通过 Servlet 的生命周期可以知道,Servlet 类的构造器只会在第一次访问的时候调用,后面的请求都不会再重新创建 Servlet 实例。即 Servlet 是单例,那么既然是单例的,那就要注意多线程访问所造成的安全问题。如下:
public class HelloServlet extends GenericServlet{
//多线程共享资源
private int i = 0;
public void service(ServletRequest req, ServletResponse res) throws ServletException,
IOException {
i++;
//为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
我们用两个浏览器,输入 http://localhost:8080/ServletImprove/hello,然后一起访问,不断刷新,结果如下:
结果分析:显然,我们用两个浏览器访问,便相当于两个线程,第一个访问,已经执行了 i++,但是还没来得及打印 i 的值,就马上就睡眠了;接着第二个浏览也来访问,执行 i++,那么i的值相当于增加加了两次1,然后这两个浏览器输出最终结果。这便造成了多线程访问共享资源造成冲突。那么如何解决多线程冲突呢?
那么在 Servlet 中如何解决线程安全问题呢?
4.1 使用同步代码块
对操作共享变量的位置添加 synchronized 关键字,是线程串行化运行
public class HelloServlet extends GenericServlet{
//多线程共享资源
private int i = 0;
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
synchronized (this) {
i++;
// 为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
但是,这种办法虽然能解决多线程同步问题,但是如果延时程序特别长,那么会造成访问假死的现象。即第一个线程访问结果没有出来,第二个线程就会一直卡死,出不来结果。
4.2 实现SingleThreadModel
SingleThreadModel 接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题
public class HelloServlet extends GenericServlet implements SingleThreadModel{
//多线程共享资源
private int i = 0;
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
i++;
// 为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
但是,如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销,在现在的Servlet开发中基本看不到SingleThreadModel的使用,这种方式了解即可,尽量避免使用。
4.3 避免使用实例变量
线程安全问题很大一部分是由于实例变量造成的,那么我们只要在 Servlet 里面不定义任何的实例变量,那么就不会有线程安全的问题。因为在 Java 内存模型中,方法中的临时变量是在栈上分配空间,而且每个线程都有自己的私有栈空间,不会造成线程安全问题。
public class HelloServlet extends GenericServlet{
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
int i = 0;
i++;
// 为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
5.Servlet 重定向和转发
本质区别:转发只发出了一次请求,而重定向发出了两次请求
- 地址栏变化
- 转发:地址栏是初次发出请求的地址
- 重定向:地址栏不再是初次发出的请求地址,地址栏为最后响应的那个地址
- request对象
- 转发:在最终的Servlet中,request对象和中转的那个request是同一个对象
- 重定向:在最终的Servlet中,request对象和中转的那个request不是同一个对象
- 访问资源
- 转发:只能转发给当前WEB应用的资源
- 重定向:可以重定向到任何资源response.sendRedirect(“http://www.baidu.com”);是可以的 转发就不行
- / 根目录
- 转发:/ 代表的是当前WEB应用的根目录(http://localhost:8080/项目名称/)
- 重定向: / 代表的是当前WEB站点的根目录(http://localhost:8080/)
注意:这两条跳转语句不能同时出现在一个页面中,否则会报IllegalStateException - if the response was already committed
5.1 重定向(sendRedirect)
浏览器访问http://localhost:8080/ServletImprove/hello
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.sendRedirect("index.jsp");// 重定向
}
5.2 转发(forward)
浏览器访问http://localhost:8080/ServletImprove/hello
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// response.sendRedirect("index.jsp");
request.getRequestDispatcher("/index.jsp").forward(request, response); // 转发
6.Servlet 与 Jsp
JSP 的本质就是 Servlet,JSP 经过编译后就会变为一个类似 Servlet 的Java文件。区别是JSP侧重于视图,Servlet主要用于控制逻辑
- Servlet 基本是JAVA程序代码构成,擅长于流程控制和事务处理,当然也可以用来生成html代码,但是通过Servlet来生成动态网页很不直观.
- JSP由HTML代码和JSP标签构成,可以方便地编写动态网页,当然里面也可以编写 Java代码,但是整体看上去不够优雅。而且比较麻烦
我们可以看一个 JSP 文件,index.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
index.jsp
</body>
</html>
经过编译后:很显然下面的代码结构和 Servlet 是差不多的
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static final javax.servlet.jsp.JspFactory _jspxFactory =
javax.servlet.jsp.JspFactory.getDefaultFactory();
private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.tomcat.InstanceManager _jsp_instancemanager;
public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
return _jspx_dependants;
}
public void _jspInit() {
_el_expressionfactory =
_jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFacto
ry();
_jsp_instancemanager =
org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
}
public void _jspDestroy() {
}
public void _jspService(final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
try {
response.setContentType("text/html; charset=ISO-8859-1");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"
\"http://www.w3.org/TR/html4/loose.dtd\">\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-
1\">\r\n");
out.write("<title>Insert title here</title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write("\tindex.jsp\r\n");
out.write("</body>\r\n");
out.write("</html>");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try { out.clearBuffer(); } catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
JSP 页面的九个隐含对象:
- request:HttpServletRequest的一个对象,封装请求信息
- response:用于封装响应数据,其作用域是本页面
- pageContext:页面的上下文,是PageContext的一个对象,可以从该对象中获取其它8个隐含对象
- session:代表浏览器和服务器的一次会话,是HttpSession 的一个对象
- application:代表当前WEB应用,是ServletContext对象
- config:当前JSP对应Servlet的ServletConfig对象
- out:JspWriter对象,调用out.prinln()可以直接把字符串打印到浏览器上
- page:指向当前JSP对应的Servlet对象的应用,但为Object类型,只能调用 Object 类的方法
- exception:在声明了page指令的isErrorPage="true"时,才可以使用
7.Servlet 与 fileter
什么是过滤器 filter?
- JavaWEB 的一个重要组件,可以对发送到 Servlet 的请求进行拦截,并对响应也进行拦截
- 生命周期和 Servlet 的类似。只不过其构造方法和初始化方法是在容器启动时就调用了,而其 doFilter() 方法则是在每次请求的时候调用
- 故过滤器可以对请求进行拦截过滤。可以用来进行权限设置,对传输数据进行加密等等操作
怎么实现一个filter?
第一步:实现 Filter 接口
public class HelloFilter implements Filter{
public HelloFilter() {
System.out.println("构造器 HelloFilter()...");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init()...");
}
@Override
// 过滤操作
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("doFilter()...");
}
@Override
public void destroy() {
System.out.println("destroy()...");
}
}
第二步:web.xml 配置filter
<!--给创建的过滤器配置关系 -->
<filter>
<filter-name>helloFilter</filter-name>
<filter-class>com.ys.filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>helloFilter</filter-name>
<!-- 这表示可以拦截任何请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
启动服务器:我们发现还没发送请求,过滤器的 构造方法和 init() 方法就已经开始运行了
服务器启动成功之后,我们输入任意连接,比如
每刷新一次,控制台都会打印 doFilter()…














还没有评论,来说两句吧...