Servlet
1.Servlet简介
Servlet是服务器端的重要组件,直译为服务端的小程序,它属于动态资源,用来处理请求,服务器接收到请求后会调用Servlet来处理请求。
Servlet的主要作用
接收请求
处理请求
完成响应
例如:
当我们要完成一个登录功能时,用户会将输入的用户名和密码以POST请求的形式发送到服务器,但是服务器本身并不具有能力来读取用户发送的用户名和密码,也就不可能对用户名和密码进行验证,所以当服务器收到这类请求后需要将请求转个一个Servlet处理。
Servlet实现类由我们编写,而由Web服务器(Servlet容器)调用,每个Servlet都必须实现javax.servlet.Servlet
2.HelloServlet
步骤:
a)创建动态WEB项目WEB_Servlet
b)项目下创建包com.atguigu.web.servlet
c)包下创建一个类HelloServlet并实现javax.servlet.Servlet接口
d)在HelloServlet的service()方法中加入一行打印语句System.out.println(“hello”);
e)在WEB-INF目录下的web.xml文件中注册映射Servlet
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.atguigu.web.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url- pattern >
</servlet-mapping>
f)启动服务器,在浏览器中访问:http://localhost:8080/WEB_Servlet/HelloServlet
具体代码
类:com.atdongruan.web.servlet.HelloServlet
public class HelloServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {}
@Override
public ServletConfig getServletConfig() {return null;}
@Override
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException {System.out.println("hello");}
@Override
public String getServletInfo() {return null;}
@Override
public void destroy() {}
}
web.xml配置文件
<?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_2_5.xsd" id="WebApp_ID"
version="2.5">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.atdongruan.web.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
</web-app>
web.xml文件详解:
因为创建好的Servlet需要由Servlet容器调用,而Servlet容器并不能知道我们所创建的Servlet的存在,所以需要在web.xml文件中进行注册。
<servlet></servlet>用于注册servlet
<servlet-name>用于设置servlet的名字,在服务器中关于servlet的一切配置都需要servlet-name来进行配置
<servlet-class>用于设置servlet的全类名,用于创建servlet的实例(反射)
而仅仅注册是远远不够的,因为Servlet是用来处理客户端发送的请求的,所以还需要为Servlet映射一个请求地址。
<servlet-mapping>用于映射需要servlet处理的请求地址
<servlet-name>为servlet的名字,和<servlet>中的<name>有对应关系
<url-pattern>需要servlet处理的请求地址
3.Servlet接口
3.1 Servlet生命周期
生命周期,以人举例的话,从人的出生到死亡称为一个人的生命周期,在人的整个生命中有很多的阶段,但总的来说可以大致分为三个阶段:出生、工作、死亡。每一阶段都有每一阶段要做的事。对于我们的Servlet也是一样。
Servlet的生命周期指的是Servlet由实例化到被销毁的过程。同样也被分为了三个阶段:实例化、处理请求、被销毁。而每个阶段我们都有对应的方法来实现响应的功能,在实例化阶段需要调用init()方法来做初始化操作,处理请求阶段调用service()方法处理请求,销毁对象之前调用destroy()做释放资源等操作。
Servlet生命周期相关方法
public void init(ServletConfig config)
public void service(ServletRequest req, ServletResponse res)
public void destroy()
3.1.1 Servlet初始化
服务器会在Servlet第一次处理请求、或服务器启动时创建Servlet实例,默认是在第一次处理请求时进行实例化的。
对于每个Servlet,服务器只会创建一个Servlet实例。以我们的HelloServlet为例,当我们通过浏览器访问http://localhost:8080/WEB_Servlet/HelloServlet时,服务器会根据路径/HelloServlet从配置文件中找到url-pattern值为/HelloServlet的servlet-mapping,然后在servlet-mapping中找到servlet-name为HelloServlet。接下来,找到servlet-name为HelloServlet的<servlet>。最后,获得servlet的全类名,通过全类名创建类的实例,这个实例会放到一个集合中。这一过程后容器再也不会对该servlet做实例化操作,而是直接从集合中获取servlet实例处理请求。
Servlet实例化后会立即调用public void init(ServletConfig config)方法。这里主要做一些获取配置信息等在处理请求前需要做的准备工作。
init()方法在Servlet的整个生命周期中只会被调用一次。
init(ServletConfig config)方法被调用时,容器会传递一个ServletConfig对象作为参数,该对象可以获取Servlet相关的配置信息。
3.1.2 Servlet处理请求
当Servlet收到请求时,服务器会调用Servlet的service()方法来处理请求。每收到一次请求就调用一次service()方法,所以service()方法是被多次调用的。因此,我们开发时主要的业务都发生在service()方法中。
service(ServletRequest req, ServletResponse res)被调用时,荣器会创建一个ServletRequest和一个ServletResponse对象作为参数传递进来,ServletRequest对象主要封装了客户端发送过来的请求信息,ServletResponse对象主要用来封装了服务器响应给客户端的数据。关于这两个对象后边我们还要详细的学习,这里就先不讨论了。
3.1.3Servlet销毁
一般情况下Servlet对象不会被销毁,但当服务器关闭或者重启时Servlet对象也将被销毁,这时在servlet被销毁前它的destroy()方法会被调用,该方法主要做一些销毁前的收尾工作,比如:释放资源、保存数据等。在实际应用中,这类工作主要由其他对象完成所以该方法并不常用。
思考:
Servlet容器是如何销毁servlet对象的?
如何测试Servlet的生命周期?
4.Servlet相关接口
ServletRequest和ServletResponse
ServletRequest是由容器创建并传递到service()方法中,容器所创建的对象实际上是HttpServletRequest,所以开发中我们都会将ServletRequest强转成HttpServletRequest。
HttpServletRequest的方法:
String getParameter(String paramName):获取指定请求参数的值;
String getMethod():获取请求方法,例如GET或POST;
String getHeader(String name):获取指定请求头的值;
void setCharacterEncoding(String encoding):设置请求体的编码!因为GET请求没有请求体,所以这个方法只只对POST请求有效。当调用request.setCharacterEncoding(“utf-8”)之后,再通过getParameter()方法获取参数值时,那么参数值都已经通过了转码,即转换成了UTF-8编码。所以,这个方法必须在调用getParameter()方法之前调用!
HttpServletResponse的方法
PrintWriter getWriter():获取字符响应流,使用该流可以向客户端输出响应信息。例如response.getWriter().print(“<h1>Hello JavaWeb!</h1>”);
ServletOutputStream getOutputStream():获取字节响应流,当需要向客户端响应字节数据时,需要使用这个流,例如要向客户端响应图片;
void setCharacterEncoding(String encoding):用来设置字符响应流的编码,例如在调用setCharacterEncoding(“utf-8”);之后,再response.getWriter()获取字符响应流对象,这时的响应流的编码为utf-8,使用response.getWriter()输出的中文都会转换成utf-8编码后发送给客户端;
void setHeader(String name, String value):向客户端添加响应头信息,例如setHeader(“Refresh”, “3;url=http://www.atguigu.com”),表示3秒后自动刷新到http:// www.atguigu.com;
void setContentType(String contentType):该方法是setHeader(“content-type”, “xxx”)的简便方法,即用来添加名为content-type响应头的方法。content-type响应头用来设置响应数据的MIME类型,例如要向客户端响应jpg的图片,那么可以setContentType(“image/jepg”),如果响应数据为文本类型,那么还要台同时设置编码,例如setContentType(“text/html;chartset=utf-8”)表示响应数据类型为文本类型中的html类型,并且该方法会调用setCharacterEncoding(“utf-8”)方法;
void sendError(int code, String errorMsg):向客户端发送状态码,以及错误消息。例如给客户端发送404:response(404, “您要查找的资源不存在!”)。
ServletConfig
ServletConfig对象对应着web.xml文件中的一个<servlet>元素,例如:想要获取元素中的<servlet-name>的值,那么可以使用servletConfig.getServletName()方法来获取。
ServletConfig对象同样有容器创建,然后作为参数传递给init()方法,可以在init()方法中使用。
ServletConfig的方法:
String getServletName():获取Servlet在web.xml文件中的配置名称,即<servlet-name>指定的名称;
ServletContext getServletContext():用来获取ServletContext对象,ServletContext会在后面讲解;
String getInitParameter(String name):用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值;
Enumeration getInitParameterNames():用来获取在web.xml中配置的所有初始化参数名称;
在web.xml中为servlet配置初始化参数
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>省略</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
每一个init-param表示一个初始化参数,通过getInitParameter(String name)可以根据param-name的值获取到param-value的值。
每个servlet中可以配置多个init-param,servlet只能获取自身的初始化参数,而不能获得其他servlet的初始化参数。
5.GenericServlet
在显示开发中我们如果直接实现servlet接口功能也是可以正常实现的,但是所面临的问题是:如果我直接实现Servlet接口,那接口中的所有方法都必须要实现,但是这些方法有的我们用不到,而有的方法实现起来很麻烦而且没给servlet实现的代码都差不多,于是我们就需要一个抽象类来帮助我们实现servlet接口,实现一些通用的方法,而我们只需要继承GenericServlet就可以了,这样的好处是我们不在需要重写全部方法,而只需要重写必须的和我们需要的方法就可以。
代码:
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable{
private transient ServletConfig config;
public GenericServlet() { }
public void destroy() {}
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {return config; }
public ServletContext getServletContext() {
return 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) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletName() { return config.getServletName(); }
}
GenericServlet中有两个重载的init()方法,由于ServletConfig对象是在容器调用init(ServletConfig config)方法是传过来的,只能在该方法中使用所以我们在类中定义了一个类型为ServletConfig的对象,而在init(ServletConfig config)方法中对该变量进行赋值。这样做就会有一个问题存在,如果子类在继承父类时重写了该方法,那赋值操作将不会被调用,这时如果使用ServletConfig的方法将导致空指针异常。所以,在这个类中又定义了一个init()方法,这个方法将在init(ServletConfig
config)方法中被调用,并且是在config对象赋值之后调用,而初始化的操作我们可以通过重写config()方法来完成,这样做既保证了对ServletConfig的赋值,又可以正常做初始化操作。
像这种将固定的代码编写到一个方法中,而将有可能发生变化的代码交给子类编写。实际上是一种叫做模板模式的设计模式,而像init()这种没有方法体,而又被父类方法调用,需要子类重写的方法称为钩子方法。
GenericServlet同时还实现了ServletConfig接口,实现这个接口的好处是我们直接通过this就可以调用ServletConfig对象的方法了,而这些方法实际调用的都是,成员变量config的方法。
6.HttpServlet
HttpServlet是GenericServlet的子类,他是Tomcat中专门处理HttpServlet的Servlet(实际上Tomcat只处理Http类型的请求)所以在一般情况下我们都会通过继承HttpServlet来实现servlet。
HttpServlet和GenericServlet一样都是一个抽象类,也就是不能对它们进行实例化,与GenericServlet不同HttpServlet中没有任何抽象方法,所以需要我们根据实际需求来重写它的方法。
在GenericServlet中service(ServletRequest req, ServletResponse res)方法是一个抽象方法,必须要由子类实现,而在HttpServlet中已经被实现,而在HttpServlet的实现的方法中会被强制转换成HttpServletRequest和HttpServletResponse(不用担心会出现类型转换异常,因为传过来的对象本身就是该类型)。转换类型之后会调用HttpServlet中的一个重载的service(HttpServletRequest
req, HttpServletResponse resp)方法。也就是说如果我们要重写service()方法不必再去重写GenericServlet的service()方法了,而可以直接重写HttpServlet重载的service()方法就可以了。
在service(HttpServletRequest req, HttpServletResponse resp)方法中,会对它的请求类型进行判断,然后根据请求类型的不同再去调用不同的方法。如:post类型的请求回去调用doPost(),get请求回去调用doGet(),delete请求回去调用doDelete(),以此类推。但是在实际应用中我们只会使用到doPost()和doGet(),所以一般情况下我们不会去重写service()方法,而是去重写更简单的doGet()或者doPost()。
7.Servlet扩展
问题1:Servlet的构造器调用了几次?
这个问题实际上很容易测试,只需要在Servlet的中写一个无参构造器,在方法中写一个打印语句,然后向该Servlet发送请求,会发现打印语句仅仅输出了一次,由此证明构造器只调用了一次。上边我们也说过,Servlet是单实例的,而调用构造器就是用来创建实例的。所以构造器只会被调用一次。
问题2:Servlet是线程安全的吗?
由于Servlet是单实例的,所以当容器调用service方法处理请求时是以多线程的方式调用的,但是因为性能问题所以在这个方法中并没有考虑同步的问题,所以Servlet并不是线程安全的,但是这样做的好处是性能较好。
由于Servlet不是线程安全的,所以尽量不要在使用Servlet处理请求时操作变量,因为有可能会出现同步的问题。(实际应用中只有非常高的并发的情况下才有可能出现这个问题,虽然如此但还是尽量不要那么做)。
问题3:Servlet实例只能在第一次请求时被创建吗?
一般情况下Servlet会在第一次收到请求时被创建,所以当我们第一次访问某个Servlet时会比平时慢一些,这种我们称为第一次惩罚。
如果希望在服务器启动时就创建Servlet的实例,可以在web.xml中进行配置,在servlet标签中还有一个load-on-startup标签,这个标签需要一个大于等于0的整数作为参数,当配置了这个属性后,该Servlet将会在服务器启动时就被创建,且值越小创建的时机越早。
问题4:url-pattern映射的规则是什么?
url-pattern配置的Servlet映射的地址,他的配置规则如下:
精确匹配:
当前项目下指定的URL必须完全
如:/path/ServletName
只有URL为:http://localhost:8080/项目名/path/ServletName
由此也可知/是代表的项目根目录,而在html中/代表的是服务器根目录
路径匹配:
当前项目下指定路径的URL地址
如:/path/*
当URL为:http://localhost:8080/项目名/path/任意值
全匹配:
当前项目下所有的URL地址
如:/*
所有URL都可以
后缀匹配:
当前项目下指定后缀的URL地址
如:*.action
当URL为:http://localhost:8080/项目名/任意值. action
优先级:
1、精确匹配
2、路径匹配
3、全匹配
4、后缀匹配
5、还有一种“/”,这种也是全部匹配,优先级最低
关于“*”:
“*”就是通配符,匹配任意字符
“*”只能出现前面和后面,不能出现在中间
如:/*.action错误
“*”只能出现一次
如:*/*错误
“*”不能单独出现
如:* 错误
问题5:web.xml文件仅仅是看到的那么简单吗?
web.xml文件整个项目中的配置文件,非常重要。但是纵观我们项目下的web.xml文件似乎内容不多,那如此重要的文件为什么只配置的这么少的内容呢?实际上在Tomcat中还有一个总的web.xml文件,就在%CATALINA_HOME%/conf目录下。
重要配置:
DefaultServlet:用于处理静态资源的默认Servlet
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
JspServlet:用于处理JSP的Servlet
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
session的过期时间
<session-config>
<session-timeout>30</session-timeout>
</session-config>
MIME类型
<mime-mapping>
<extension>123</extension>
<mime-type>application/vnd.lotus-1-2-3</mime-type>
</mime-mapping>
以下省略。。。。。。
8.ServletContext
8.1 ServletContext简介
每个WEB应用服务器都会为其创建一个ServletContext对象,项目启动时ServletContext对象被创建,项目停止或重新加载时ServletContext对象被销毁
ServletContext对象主要作用就是在Servlet之间共享数据和加载WEB应用的配置信息,还记得ServletConfig对象可以获取到每个Servlet的配置信息吧,而我们的ServletContext可以或取整个WEB应用的配置信息。
8.2 获取ServletContext
在Serlet接口中,可以通过init方法中的ServletConfig调用getServletContext()方法来获得ServletContext对象。
GenericServlet和HttpServlet可以直接调用getServletContext()方法。(实际上也是ServletConfig的getServletContext()方法)。
HttpSession对象的getServletContext()方法同样也可以获取。
8.3 域对象
ServletContext是JavaWeb的四个域对象之一
PageContext
ServletRequest
HttpSession
ServletContext
域对象主要用来存储传递数据,每个域对象的内部都有一个map用来存储对象
读取数据的方法:
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“key”, “value”),在ServletContext中保存了一个域属性,域属性名称为key,域属性的值为value;
Object getAttribute(String name):用来获取ServletContext中的数据;
void removeAttribute(String name):用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
Enumeration getAttributeNames():获取当前域中所有属性的名称;
8.4 获取当前应用的初始化参数
和ServletConfig功能类似,ServletContext也可以获取初始化参数,这不过config获取的是当前Servlet的而context获得的是整个Web应用的;
由于整个应用中都拥有同一个ServletContext所以所有Servlet都能获得相同的初始化参数。
在web.xml中配置初始化参数,在根标签中创建context-param元素,通过元素中param-name和param-value标签分别配置key和value,每一个context-param代表一条键值对的初始化参数。
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>1234</param-value>
</context-param>
</web-app>
8.5 获取项目根目录
项目根目录就是端口号后边的第一个路径。如:http://localhost:8080/hello/HelloServlet,红色部分/hello就是项目的根目录。这样做的目的是,因为在项目在开发时或交付后,项目名很有可能被修改,也就是红色部分会变成其他内容,当项目根目录修改后,页面中很多资源的路径会失效,所以需要动态获取根目录。
主要通过servletContext. getContextPath()来获取。
通过HttpServletRequest对象也可以获得该值。
8.6 获取资源的流和真实路径(物理路径)
在web项目中,我们访问服务器上的资源一般都是使用URL地址或者相对路径,但是有时我们会需要获取服务器中的文件流,或者获取文件的物理地址(比如在上传或下载文件时)。
获取文件流:
InputStream in = getServletContext().getResourceAsStream("/1.jpg");
该方法会获取项目根目录下1.jpg的输入流
获取文件真实路径
String realPath = getServletContext().getRealPath("/1.jpg");
该方法获得项目根目录下1.jpg的路径地址
注意:给方法并不会判断文件是否存在
9.请求和响应
9.1请求和响应的过程
9.2HttpServletResponse对象
HttpServletResponse对象封装了服务器响应给客户端的信息,该对象由服务器创建,在Servlet处理请求时,服务器会调用Servlet的service方法并将HttpServletResponse对象作为参数传递。所以,我们可以直接在service方法中使用该对象。一般我们习惯简称他为response。
response的主要功能有:
设置响应头信息
response.setHeader("Refresh", "3;URL=http://www.baidu.com")
response.setContentType("text/html;charset=UTF-8");
设置状态码
response.sendError(404,"访问的资源未找到");
设置响应体
response.getWriter().print("<h1>Hello World</h1>");
重定向
response.sendRedirect("index.html");
9.3HttpServletRequest对象
HttpServletRequest对象封装了客户端发送给服务器的信息,该对象由服务器创建,在Servlet处理请求时,服务器会调用Servlet的service方法并将HttpServletRequest对象作为参数传递。所以,我们可以直接在service方法中使用该对象。一般我们习惯简称他为request。
request的主要功能有:
获取请求参数
String username = request.getParameter("username");
在请求域中读写数据
request.setAttribute("key1", "value1");
String key1 = (String) request.getAttribute("key1");
获取项目名
String contextPath = request.getContextPath();
转发请求
request.getRequestDispatcher("/index.html").forward(request, response);
9.4转发和重定向
9.4.1 转发
转发是通过request对象发起的,通过request对象获取一个RequestDispatcher对象,通过RequestDispatcher的forward方法发起转发。
转发是在服务器内部进行的:
整个过程浏览器只发送了一个请求。
浏览器不能知道转发行为的发生。
由于在服务器内部进行,所以转发以项目路径为根目录,输入地址时不需要输入项目名。
转发是一次请求,所以request中的数据可以共享。
转发只能转发到应用内部的资源,而不能转发到其他应用
9.4.2 重定向
重定向是通过response对象发起的,通过response的sendRedirect()方法进行重定向。
重定向是在浏览器中进行的:
整个过程中,浏览器发送了两次请求。
浏览器知道转发行为的发生。
由于在浏览器端进行,重定向的路径是以服务器目录为根目录,所以输入地址时需要输入项目名。
重定向是两次请求,不能共享request中的数据。
重定向不只限定于内部资源,可以重定向到任意web资源。
9.5 路径问题
通常我们访问一个web应用地址格式如下:http://localhost:8080/MyWeb/HelloServlet
http://localhost:8080 这一部分我们称它为服务器的根目录
/MyWeb 这一部分我们称它为项目的根目录
/HelloServlet 这一部分是我们Servlet的映射地址
绝对路径和相对路径
绝对路径:使用“/ ”开头的路径称为决定路径,绝对路径表示从根目录开始寻找资源。
相对路径:不使用“ / ”开头的路径称为相对路径,相对路径表示从当前资源所在目录开始寻找资源
9.5.1 服务器端路径
服务器端路径,主要指在Servlet中使用转发时的路径。
服务器端的根目录指的是项目的根目录,也就是我们的项目名。
例如,我们现在访问如下地址的Servlet:
http://localhost:8080/MyWeb/hello/HelloServlet
在HelloServlet中调用转发方法
request.getRequestDispatcher("/index.html").forward(request, response);
在路径地址处如果加了“ / ”相当于由项目根目录开始寻找资源
也就相当于将请求转发到如下地址:
http://localhost:8080/MyWeb/index.html
request.getRequestDispatcher("index.html").forward(request, response);
在路径地址处如果不加“/ ”相当于由当前项目所在目录开始寻找资源
也就相当于将请求转发到如下地址:
http://localhost:8080/MyWeb/hello/index.html
在实际应用中,由于我们的资源(Servlet和JSP)所在的位置有可能会发生变动,所以通常我们会使用绝对路径。
9.5.2 客户端路径
客户端路径,主要是值在页面中引用外部资源,以及在Servlet中做重定向操作时的路径。
客户端路径的根目录指的是我们tomcat的服务器的根目录,也就是项目名前面那段路径。
例1:我们现在访问如下地址的Servlet:
http://localhost:8080/MyWeb/hello/HelloServlet
在HelloServlet中调用重定向方法
response.sendRedirect("/index.html");
在路径地址处如果加了“ / ”相当于由项目根目录开始寻找资源
也就相当于将请求重定向到如下地址:
http://localhost:8080/ index.html
但是这个地址明显不是我们想要的,所以在重定向使用绝对路径时必须要加入项目的名字,如下:
response.sendRedirect("/MyWeb/index.html");
如此请求将会重定向到http://localhost:8080/MyWeb/ index.html
response.sendRedirect("index.html");
在路径地址处如果不加“/ ”相当于由当前项目所在目录开始寻找资源
也就相当于将请求重定向到如下地址:
http://localhost:8080/MyWeb/hello/index.html
例2:在MyWeb项目中有form.html页面,目录结构如下:
webapps/MyWeb/hello/form.html
现在我在form.html中创建超链接访问/hello/HelloServlet
连接格式如下:
<a href=”/HelloServlet”>HelloServlet</a>
使用绝对路径,网页和重定向的根目录相同,都是服务器的根目录
因此点击超链接后会访问如下地址
http://localhost:8080 /HelloServlet
这个地址明显不对,所以应该从项目名开始写起
正确如下:
<a href=”/MyWeb/hello/HelloServlet”>HelloServlet</a>
点击后访问地址:
http://localhost:8080 /MyWeb/hello/HelloServlet
<a href=”HelloServlet”>HelloServlet</a>
使用相对路径,会从当前html所在目录开始寻找资源,也就是从/MyWeb/hello/开始。
因此点击超链接后会访问如下地址:
http://localhost:8080 /MyWeb/hello/HelloServlet
同样的,在实际开发中客户端的路径我们也会使用绝对路径,而不使用相对路径。
但是,这块有一个问题,在实际开发中我们项目名有可能会改变,比如:开发中的名字可能为DMS,而实际部署时就变成了Baidu_DMS。但是这是我们在项目中的路径是以/DMS开头的,那就意味着,我们要把项目中所有的页面中、Servlet中的/DMS修改成/Baidu_DMS,如此一来工作量是十分大的,那要如何解决呢?实际上我们可以通过request对象动态的获取项目名来解决这个问题,但是这还需要配合JSP使用,所以我们在这里先不考虑该问题。
9.5编码问题
9.5.1 为什么要使用编码
9.5.2 常见的编码
ASKII码
最早的字符编码,使用一个字节中的7位来表示,一共只有128个。
ISO-8859-1
ISO组织在ASKII基础上定义的编码,用来对其进行扩展,使用一个字节中的8位来表示,一共有256个。
GB2312
国标码,中国规范,主要加入的许多中文。
GBK
同样也是国标码,对GB2312的扩充其中添加了更多的中文。
GB18030
也是国标码,国家强制标准,于GB2312兼容,但实际系统中应用并不广泛。
UTF-8
万国码,支持全部字符,存储效率较高,我们所使用的编码
9.5.3 请求编码
请求编码是浏览器发送给服务器的编码格式。
浏览器发送给服务器的请求编码主要由页面中的Content-Type响应头的编码决定,例如值为:Content-Type: text/html; charset=utf-8,那么页面请求将以utf-8的编码发送。那么,就要求我们必须要将Content-Type的编码设置成utf-8。
虽然浏览器发送来的编码格式已经确定,但是还要注意我们这里服务器中解析编码的格式并不是utf-8还需要我们设置。
Tomcat中默认以iso解析请求,而浏览器是以utf-8发送过来请求,如此一来必定会出现乱码。所以还需要设置解析请求的编码:
POST请求
POST请求比较简单,只需要在通过request对象获取请求参数之前调用request.setCharacterEncoding("utf-8")来指定编码。
GET请求
由于Tomcat收到请求后默认会已iso对我们的url在进行解码,所以如果直接设置request的编码是不行的还需要设置我们Tomcat解析uri的默认编码。
在%CATALINA_HOME%/conf/server.xml文件中,找到<Connector />标签在标签中加入属性URIEncoding="utf-8"
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="utf-8" />
9.5.4 响应编码
响应编码是服务器发送浏览器的编码格式。
Tomcat默认以ISO-8859-1编码响应请求,但是该编码并不支持中文,所以我们在响应中文数据时不能使用该编码,而应该使用“UTF-8”。
设置响应编码:
response.setCharacterEncoding("UTF-8");
通过setCharacterEncoding设置的是字符输出流的编码。
这里设置的只是输出的字符编码,但是作为浏览器来说会默认已GBK去解析,所以还需要通过设置请求头告诉浏览器如何解析。
response.setContentType("text/html;charset=UTF-8");
实际上我们在调用response.setContentType("text/html;charset=UTF-8")时,就已经将我们的输出编码也同时设置了,所以只需要调用此方法即可。
9.5.5 URL编码
客户端在向服务器发送请求时需要对非英文数据进行编码操作,编码后在将请求发送服务器,而服务器在收到请求后会自动进行解码操作来解析请求。还记得我们在server.xml中配置的URIEncoding=”UTF-8“吧,它就是来配置Tomcat服务器以何种编码对URL进行解码操作的。
URL编码并不是字符编码,而是浏览器在字符编码的基础上将其转换成试用于互联网传输的编码格式。
例如“李”字的三中编码分别为:
Byte编码:[-26, -99, -114]
URL编码:%E6%9D%8E
解码后:李
在java中可以使用两个类分别进行编码和解码操作
编码:import java.net.URLEncoder;
例如:String encode = URLEncoder.encode("李","utf-8");
解码import java.net.URLDecoder;
例如:String decode = URLDecoder.decode("%E6%9D%8E ","utf-8");