Servlet入门
文章目录
什么是Servlet
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
简单的概括:
-
Servlet 是 JavaEE 规范之一。规范就是接口
-
Servlet 就 JavaWeb 三大组件之一。三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器。
-
Servlet 是运行在服务器上的一个 java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端。
有以下几个特点:
- Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
- Servlet 是独立于平台的,因为它们是用 Java 编写的。
- 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
- Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。
Servlet的基本使用
-
编写一个类去实现 Servlet 接口
-
实现 service 方法,处理请求,并响应数据
-
到 web.xml 中去配置 servlet 程序的访问地址
这里我们service的方法体为一条打印语句,方便观察
public class ServletDemo1 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("ServletDemo1对象被创建了");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("ServletDemo1的service方法被执行了");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("ServletDemo1对象被销毁了");
}
}
web.xml
servlet设置对象名,然后给servlet取一个别名。紧接着写一个servlet-mapping标签,写入别名和处理的请求路径。当url-pattern的请求被Tomcat接收时,我们的ServletDemo1就会处理请求,并执行service()方法。
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.kaikeba.course06.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
Servlet的生命周期方法
初始化
当对应Servlet的处理请求的URL路径被初次访问时,Tomcat会创建Servlet的实例对象,并调用init()初始化方法,之后处理Request请求,用的都是同一个Servlet。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("ServletDemo1对象被创建了");
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("ServletDemo1的service方法被执行了");
}
我们看到,service方法和init方法一起执行了
对于比较核心的Servlet,比如SpringMVC 的DispathServlet,需要在web.xml配置文件中添加一条load on startup
配置,让Servlet随着服务器的启动而启动。该属性值取0~10之间的值,Servlet随服务器启动而创建。如果不配置该属性(一般情况),实际上会给其一个默认值为-1。
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.kaikeba.course06.ServletDemo1</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
可以看到我的Servlet对象在Tomcat启动的时候就已经创建了
销毁
当Tomcat服务器关闭时,Servlet随服务器的关闭而消亡,会调用destroy()方法,表示Servlet对象被销毁
Servlet的执行流程
- 用户发出Request请求被Tomcat服务器解析
- 请求解析器去servlet容器寻找有没有能够处理请求的处理器(Servlet)
- 如果有则执行对应处理器的service方法,将结果(response)返回
- 如果没有对应处理器,则加载web.xml配置信息,寻找是否存在可以处理请求的处理器的配置信息
- 如果仍然没有对应处理器,返回错误信息给用户(404)
- 如果找到对应处理器的配置信息,通过类加载器将该Servlet加载进内存,创建好Servlet之后,执行service方法,将结果(response)返回
Servlet的继承体系
GenericServlet
GenericServlet是一个对Servlet接口的空实现抽象子类,GenericServlet的方法void方法方法体都是空的,返回对象的方法的返回值都是null。
我们实现这个类只要重写我们需要的方法即可。
public class GenericServletDemo1 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("服务来了");
}
}
HttpServlet
GenericServlet虽然很方便,但是我们实际开发使用的最多的是HttpServlet。
除了Servlet自带的五种方法以外,HttpServlet自带了一些Http相关的方法,比如处理POST请求的方法doPost()方法,处理GET请求的doGet()方法。
public class HttpServletDemo2 extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("HttpServlet开始服务了");
}
@Override
public void init() throws ServletException {
System.out.println("HttpServlet来了");
}
@Override
public void destroy() {
System.out.println("HttpServlet销毁了");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get方法");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post方法");
}
}
Servlet3.0注解配置
注解配置的使用
我们删除之前的web.xml的配置文件,在我们要配置URL路径的Servlet类上添加@WebServlet()注解,括号里面传入我们要处理的请求的URL路径,以/
开头
@WebServlet("/hello")
public class ServletDemo1 implements Servlet {
...
}
原来的xml,我们看到别名是冗余的只是为了识别,一个servlet就8行,阅读性很差。
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.kaikeba.course06.ServletDemo1</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
URL路径
我们看一下@WebServlet注解的源码,我们知道如果注解中只有一个属性,那么默认就是value()属性。其中value()属性指的就是urlPatterns(),我们看到其实传入的是一个数组,所以我们的Servlet3.0的注解配置支持一个Servlet处理多个请求。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";
String[] value() default {};
String[] urlPatterns() default {};
int loadOnStartup() default -1;
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
如果要指定load-on-startup,我么可添加该参数,我们看到默认是-1,和web.xml的文件配置是一样的。
@WebServlet(urlPatterns = "/hello",loadOnStartup = 1)
public class ServletDemo1 implements Servlet {
...
}
URL的使用方式
- 普通方式
/hello
:处理URL路径为/hello的请求- ``/user/hello`:处理URL路径为/hello的请求
很好理解,我们的URL和请求一一对应,不区分大小写。
- 添加通配符
/*
:处理所有请求/user/*
:处理第一层为hello的所有请求*.do
:处理任何以.do结尾的请求
注意事项:
通配符的优先级是非常低的,所以默认情况下。用户发来一个请求,如果我们不是使用通配符的URL也能处理请求,则通配符是不生效的,但是如果没有一个Servlet可以处理一个请求,则通配符*
才会生效的。
特殊情况演示:
请求:/hello
执行顺序:/hello/*
> /hello
> /*
请求:/user/hello
执行顺序:/user/hello/*
> /user/hello
> /user/*
> /*
>
我们如果定义了多级目录,比如/a/b
就不要使用/a
这样的请求了!
- 错误方式
/*.do
-
/*/*
这种方式会失去匹配功能,也根据浏览器而异,一般情况下,该URL什么都无法接收。
-
/he*
:
- 特殊方式
-
/
在不同的浏览器对
/
请求处理不同。谷歌浏览器不会将/
作为请求,而在火狐浏览器的/
和/*
有相同的作用,但是如果处理/
和/*
的Servlet都存在,则优先执行/*
,所以我们尽量减少
HttpServlet
HttpServlet是GenericServlet的实现类,GenericServlet是Servlet接口的空实现抽象类,HttpServlet是我么最常用的Servlet了。
常量
HttpServlt定义了8个常量,对应浏览器的7种请求(少了一个HEAD请求)。
public abstract class HttpServlet extends GenericServlet {
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
......
}
处理HTTP请求
源码比较长,简单看一下:
- 使用request.getMethod()方法获取请求类型,值是“POST”,"GET"这样的大写。
- 使用一堆if – else if 来判断是哪种请求,然后执行对应方法,比如POST请求对应doPost()方法。
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);
}
}
一般操作
我们只要复写对应的doGet(),doPost()方法即可,Servlet收到请求时,会根据requset.getMethod()获得请求类型,我们不要去干重写service方法的迷惑操作。
@WebServlet("/service")
public class HttpServletDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get方法");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post方法");
}
}
中文乱码
Http之间传输数据可能会出现乱码问题,这需要我们统一编码:
接收到浏览器请求参数乱码
request.setCharacterEncoding("UTF-8");
接收用户数据乱码问题:
String username=request.getParameter("username");
//转码
username = new String(username.getBytes("ISO-8859-1") , "UTF-8");
System.out.println("userName="+username+"==password="+password);
发送数据给用户乱码问题:
//告诉服务器用编码来解析什么来解析
response.setCharacterEncoding("UTF-8");
//告诉客户端用什么编码
response.setHeader("content-type", "text/html;charset=UTF-8");
或者使用这个,一个顶两
response.setContentType("text/html;charset=UTF-8")