JavaWeb之Servlet技术
Servlet技术
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
使用 Servlet,可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。换句话说,当请求到达服务器时,web容器会将请求交给对应的Servlet处理,Servlet处理完毕后对客户端的请求进行响应。查看Servlet继承可以看出JavaEE提供了Servlet接口,实现类GenericServlet、HttpServlet,如果要创建自己的Servlet就需要从这些类和接口入手:
新建web项目
新建项目
第一步:Eclipse菜单栏点击File——>New,出现如图界面:
第二步:选择Dynamic web Project,进入如图界面,在图示位置中填写项目名称,项目位置选择默认即可。
如果此时没有Target runtime则点击New Runtime,进入如下界面,选择Tomcat 9。
点击Next按钮,进入如下界面,点击Browser选择Tomcat根目录并点击Finish按钮即可
第三步:如果已经有Target runtime则可以直接点击Finish按钮等待Eclipse开发工具对项目配置。
添加项目运行环境
创建好web项目后,因为项目中还没有web所需的依赖,因此我们要将相关依赖添加进项目,添加过程如下图:
第一步:在项目上点击鼠标右键,按照下图进行选择
第二步:在如图选项卡中点击Libraries选项卡,并点击Add Library。
第三部:在弹出对话框中选择Server Runtime。
第四步:选择Tomcat服务器并点击finish
第五步:在如图界面中Libraries选项卡中如果出现图示内容,即添加成功。
创建Servlet
Servlet接口
要编写一个Servlet,可以继承javax.servlet.Servlet接口,该接口定义了如下5个方法。
- public void init(ServletConfig config) throws ServletException
- public void service(ServletRequest req, ServletResponse res) throwsServletException, java.io.IOException
- public void destroy()
- public ServletConfig getServletConfig()
- public java.lang.String getServletInfo()
下面我们依次介绍这5个方法:
- init():在Servlet实例化之后,Servlet容器会调用init()方法,来初始化该对象,主要是为了让Servlet对象在处理客户请求前可以完成一些初始化的工作,例如,建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只能被调用一次。init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用ServletConfig对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对象获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。
- service():容器调用service()方法来处理客户端的请求。要注意的是,在service()方法被容器调用之前,必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。在service()方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
- destroy():当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destroy()方法,以便让Servlet对象可以释放它所使用的资源,保存数据到持久存储设备中,例如,将内存中的数据保存到数据库中,关闭数据库的连接等。当需要释放内存或者容器关闭时,容器就会调用Servlet对象的destroy()方法。在Servlet容器调用destroy()方法前,如果还有其他的线程正在service()方法中执行,容器会等待这些线程执行完毕或等待服务器设定的超时值到达。一旦Servlet对象的destroy()方法被调用,容器不会再把其他的请求发送给该对象。如果需要该Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端的请求。在destroy()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,该对象会被Java的垃圾收集器所回收。
- getServletConfig():该方法返回容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数。
- getServletInfo():返回一个String类型的字符串,其中包括了关于Servlet的信息,例如,作者、版本和版权。该方法返回的应该是纯文本字符串,而不是任何类型的标记(HTML、XML等)
接下来,创建一个Java类,以实现Servlet接口的方式来创建Servlet
1 | package cn.bytecollege.controller; |
按照上例代码创建好后可以发现需要重写Servlet接口中的所有方法,这里先不需要关注其他方法,只关注service方法即可。当请求到达web容器后,web容器寻找对应的Servlet,Servlet处理请求的业务都将在Service中进行。
虽然继承Servlet可以实现Servlet的创建,但是这么做有以下几个弊端:
- 继承接口就需要重写接口中的方法,在不需要重写的情况的造成代码的冗余。
- service方法本身并不具备区分请求类型的能力,需要开发者自行判断是哪种请求类型并作出相应的处理。
继承GenericServlet
除了继承Servlet接口创建Servlet以外,也可以继承GenericServlet,查看GenericServlet源码可以发现该类是一个抽象类,重写了Servlet接口中的方法(虽然方法内什么都没做),并且新增了若干方法。但是继承该类以后可以不重写Servlet中的方法。
同样继承GenericServlet抽象类需要重写Service方法,将Servlet处理请求的业务写在这个方法内。
1 | package cn.bytecollege.controller; |
继承HttpServlet
这种方式是推荐的创建Servlet的方式,因为HttpServlet继承了GenericServlet后重写了Service方法,并且在Service方法中对请求类型进行了判断,并且提供了doXxx()方法,也就是说,针对不同的请求类型有了不同的处理逻辑,如果需要实现自己的Servlet,重写对应的doXxx()方法即可,通常情况下,重写doGet()方法和doPost()方法即可。
1 | package cn.bytecollege.controller; |
Servlet配置
Servlet编写完毕后,需要对Servlet进行注册,注册后Servlet才能正常服务。这就相当于一个孩子诞生后需要在公安机关登记在册,才能享受应有的社会服务和行使应有的社会职责。
注册Servlet
web.xml配置
1 |
|
- servlet-name:只当前servlet的名称,通过该名称可以找到对应的servlet
- url-pattern:指该servlet访问的路径
- servlet-class:指该servlet对应的类,需要配置完全限定名
注意:同一个Servlet可以有不同的url,但是一个url只能对应一个Servlet
注解
@WebServlet
1 | @WebServlet(value = { "/index", "/index2", "/index3" },initParams = {@WebInitParam(name="token",value = "123"),@WebInitParam(name="username",value = "admin")},loadOnStartup = 1) |
查看@WebServlet注解源码如下:
1 |
|
动态注册
JavaEE不但提供了Servlet,还提供了Filter和Listener等组件,而Servlet的动态注册就需要用到Listener,在项目启动时进行注册。注册的步骤如下:
- 自定义Listener继承ServletContextListener
- 重写contextInitialized()方法
- 获取ServletContext对象
- 利用createServlet创建Servlet对象
- 注册Servlet
- 注册Servlet-mapping
1 |
|
Servlet路径匹配规则
当编写完Servlet后,需要在web.xml中注册Servlet以便访问,也就是说需要在xml中配置请求路径。配置好的路径有如下几种匹配方式,下面将就这几种方式进行讲解
完全匹配
1 |
|
路径匹配
1 |
|
扩展匹配
1 |
|
注意:扩展匹配前不能有”/“
在上述匹配中的优先级如下
完全匹配>最长路径匹配>路径匹配>扩展匹配
部署Servlet
当我们配置好Servlet后,我们需要添加Tomcat服务器,添加流程如下:
第一步:在Eclipse上方菜单栏中点击window按钮,选择preference。
第二步:在弹出的对话框中搜索server,并在对话框做成点击Runtime Environment,并点击右侧Add按钮
第三步:选择Apache Tomcat v9.0(因为在本教程内使用的是Tomcat9.0),并点击Next按钮
第四步:点击Browser按钮,选择解压后Tomcat的根目录,然后点击finish即可。
至此,我们已经在Eclipse中配置好了web服务器。
接下来,我们在项目上点击右键,选择Run as,并选择服务器即可
Servlet请求流程和生命周期
下面的图示将演示Servlet的请求流程及生命周期
从上图可以看出Servlet生命周期包含以下几步:
- 实例化:客户端向服务器发送请求,web容器产生request对象和response对象,并根据URL内存中寻找是否存在对应Servlet的对象,如果不存在则调用Servlet构造方法创建Servlet类的对象,如果存在则调用Service方法,并根据请求类型调用doGet或者doPost方法。对请求进行响应。
- 初始化:当Servlet对象创建完成以后调用init方法加载初始参数,该方法只调用1次
- 服务:web容器根据请求类型调用sevice方法,对请求进行响应。
- 销毁:当容器关闭时调用destory方法,该方法只调用1次
ServletRequest
当客户端浏览器向服务器发送HTTP请求以后,可以看出一个完整的请求报文包含以下内容:请求行,请求头,请求体。
服务器接收到该请求以后,就会对该请求进行解析,并封装为一个ServletRequest子类对象。下面我们来学习ServletRequest中的方法:
方法 | 描述 |
---|---|
public java.lang.Object getAttribute(java.lang.String name) | 返回以name为名字的属性的值。如果该属性不存在,这个方法将返回null |
public java.util.Enumeration getAttributeNames() | 返回请求中所有可用的属性的名字。如果在请求中没有属性,这个方法将返回一个空的枚举集合。 |
public void removeAttribute(java.lang.String name) | 移除请求中名字为name的属性。 |
public void setAttribute(java.lang.String name, java.lang.Object o) | 在请求中保存名字为name的属性。如果第二个参数o为null,那么相当于调用removeAttribute(name)。 |
public java.lang.String getCharacterEncoding() | 返回请求正文使用的字符编码的名字。如果请求没有指定字符编码,这个方法将返回null。 |
public int getContentLength() | 以字节为单位,返回请求正文的长度。如果长度不可知,这个方法将返回-1。 |
public java.lang.String getContentType() | 返回请求正文的MIME类型。如果类型不可知,这个方法将返回null。 |
public java.lang.String getLocalAddr() | 返回接收到请求的网络接口的IP地址 |
public java.lang.String getLocalName() | 返回接收到请求的IP接口的主机名 |
public int getLocalPort() | 返回接收到请求的网络接口的IP端口号 |
public java.lang.String getParameter(java.lang.String name) | 返回请求中name参数的值。如果name参数有多个值,那么这个方法将返回值列表中的第一个值。如果在请求中没有找到这个参数,这个方法将返回null。 |
public java.util.Enumeration getParameterNames() | 返回请求中包含的所有的参数的名字。如果请求中没有参数,这个方法将返回一个空的枚举集合。 |
public java.lang.String[] getParameterValues(java.lang.String name) | 返回请求中name参数所有的值。如果这个参数在请求中并不存在,这个方法将返回null。 |
public java.lang.String getProtocol() | 返回请求使用的协议的名字和版本,例如:HTTP/1.1。 |
public java.lang.String getRemoteAddr() | 返回发送请求的客户端或者最后一个代理服务器的IP地址。 |
public java.lang.String getRemoteHost() | 返回发送请求的客户端或者最后一个代理服务器的完整限定名。 |
public int getRemotePort() | 返回发送请求的客户端或者最后一个代理服务器的IP源端口 |
public RequestDispatcher getRequestDispatcher(java.lang.Stringpath) | 返回RequestDispatcher对象,作为path所定位的资源的封装。 |
public java.lang.String getServerName() | 返回请求发送到的服务器的主机名。通常获取到域名,没有域名则获取IP |
public int getServerPort() | 返回请求发送到的服务器的端口号。通常获取到URL中的端口号,获取不到重定向后的端口号 |
public void setCharacterEncoding (java.lang.String env) throwsjava.io.Unsupported EncodingException | 覆盖在请求正文中所使用的字符编码的名字。只针对POST请求 |
请求乱码解决
GET请求乱码
在Tomcat 7 以前Get请求会出现乱码,但是从Tomcat 7 以后解决了这个问题,Get请求很少出现乱码的情况,如出现则可以通过以下两种方式解决。
方式一:
1 | String str = new String(province.getBytes("iso-8859-1"), "utf-8"); |
方式二:
- 进入Tomcat下conf目录,找到Server.xml
- 搜索8080端口,找到Connector标签,并且在标签后添加URIEncoding属性
1 | <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="utf-8"/> |
POST请求
Post请求出现乱码也是开发中经常遇到的问题,下面这种方式可以解决。
1 | //获取请求参数前设置 |
ServletResponse
同理,当服务器要向客户端响应时,也会向客户端浏览器发送响应报文。我们在这里可以简单的理解为web容器会将响应报文封装进ServletResponse子类对象,下面我们学习ServletResponse的方法。
方法 | 描述 |
---|---|
public java.lang.String getContentType() | 返回在响应中发送的正文所使用的MIME类型。 |
public java.lang.String getCharacterEncoding() | 返回在响应中发送的正文所使用的字符编码(MIME字符集)。 |
public ServletOutputStream getOutputStream() throwsjava.io.IOException | 返回ServletOutputStream对象,用于在响应中写入二进制数据。javax.servlet.ServletOutputStream是一个抽象类,继承自java.io.OutputStream。 |
public java.io.PrintWriter getWriter() throws java.io.IOException | 返回PrintWriter对象,用于发送字符文本到客户端。PrintWriter对象使用getCharacterEncoding()方法返回的字符编码。如果没有指定响应的字符编码方式,默认将使用ISO-8859-1。 |
public void setCharacterEncoding(java.lang.String charset) | 设置发送到客户端的响应的字符编码,例如,UTF-8。 |
public void setContentType(java.lang.String type) | 设置要发送到客户端的响应的内容类型,此时响应应该还没有提交。给出的内容类型可以包括字符编码说明,例如:text/html;charset=UTF-8。如果这个方法在getWriter()方法被调用之前调用,那么响应的字符编码将仅从给出的内容类型中设置。 |
响应乱码
Servlet在响应时也有可能出现乱码,这是因为向浏览器响应后,浏览器并不知晓服务端让浏览器以何种字符编码进行显示,浏览器通常会以默认的编码进行解析响应内容,解决办法就是服务端告诉浏览器以何种编码解析即可。
1 | response.setContentType("text/hmlt;charset=utf-8"); |
ServletConfig
该对象用于获取Servlet配置项中配置的初始化参数。
1 |
|
注意:init-param用于配置初始化的参数,可以理解为其是以键值对的形式存在,其中param-name用于配置初始化参数的名称,param-value用于配置参数的值。该键值对会在Servlet实例化以后被解析并封装进ServletConfig对象。
ServletConfig对象具有以下方法:
方法名 | 方法描述 |
---|---|
public String getServletName(); | 获取Servlet实例的名称 |
public ServletContext getServletContext(); | 获取当前应用的上下文 |
public String getInitParameter(String name); | 根据名称获取配置的初始化参数指 |
public Enumeration<String> getInitParameterNames(); | 获取所有的初始化参数的名称,返回值是一个枚举类型 |
下面我们通过示例来学习ServletConfig接口的方法使用:
1 | @Override |