1. Servlet简介
Servlet是服务器端的重要组件,直译为服务端的小程序,它属于动态资源,用来处理请求,服务器接收到请求后会调用Servlet来处理请求。
Servlet的主要作用
接收请求
处理请求
完成响应
例如:
当我们要完成一个登录功能时,用户会将输入的用户名和密码以POST请求的形式发送到服务器,但是服务器本身并不具有能力来读取用户发送的用户名和密码,也就不可能对用户名和密码进行验证,所以当服务器收到这类请求后需要将请求转一个Servlet处理。
Servlet:
狭义上的Servlet:仅仅指的是这个servlet接口
广义的Servlet:不仅包含该Servlet接口,还包含该Servlet的接口的子类!
Servlet是JavaWeb部分的三大组件:
JavaWeb部分三大组件:Servlet、Filter、Listener,这三个组件都需要交给web服务器管理!
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配置文件
<?xmversion="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处理的请求地址
总结:
自定义一个Servlet:
1).自定义一个类实现Servlet接口,实现接口中的方法
2).将自定义的servlet类在web.xml文件中配置一下!
<!-- 使用servlet标签配置我们自定义的servlet --> <servlet> <!-- 在servlet-name该标签中,我们可以配置一个servletName, 建议和servlet类的简类名一致 ,该servletName是我们自己使用--> <servlet-name>HelloServlet</servlet-name> <!-- 使用servlet-class标签配置自定义servlet类的全类名, 这个全类名是给服务器使用的,服务器会使用这个全类名创建该类所对应的servlet对象 --> <servlet-class>com.neuedu.servlet.HelloServlet</servlet-class> </servlet> <!-- 使用servlet-mapping标签配置servlet映射 --> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <!-- 该url-pattern 是用来请求映射url地址的,也就说 该标签中的内容是给浏览器访问使用的! --> <url-pattern>/Hello</url-pattern> </servlet-mapping>
3. Servlet接口
3.1 Servlet生命周期
生命周期,以人举例的话,从人的出生到死亡称为一个人的生命周期,在人的整个生命中有很多的阶段,但总的来说可以大致分为三个阶段:出生、工作、死亡。每一阶段都有每一阶段要做的事。对于我们的Servlet也是一样。
Servlet的生命周期指的是Servlet由实例化到被销毁的过程。同样也被分为了三个阶段:实例化、处理请求、被销毁。而每个阶段我们都有对应的方法来实现响应的功能,在实例化阶段需要调用init()方法来做初始化操作,处理请求阶段调用service()方法处理请求,销毁对象之前调用destroy()做释放资源等操作。
Servlet生命周期相关方法
1)构造器:servlet对象不是在tomcat服务器启动的时候创建的,而是在我们第一次
访问该servlet的时候调用,该构造器只会被执行一次,说明该Servlet是单例的!
但是是多线程的!
2)init方法:在我们第一次访问该Servlet时候调用【在构造器被调用之后紧接着被调用】
该方法只会被调用一次
3)service方法:每次我们访问该Servlet的时候,这个service方法都会被调用一次!
所以我们写的业务逻辑代码,主要就写在这个方法中!
4)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相关的配置信息。
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的初始化参数。
总结:
代表:代表当前servet的配置信息,每个servlet对应唯一的一个ServletConfig对象
<servlet>
<servlet-name>BServlet</servlet-name>
<servlet-class>com.neuedu.servlet.BServlet</servlet-class>
</servlet>
获取:该是由Tomcat服务器创建,然后通过参数的形式传递给init方法,
我们只需要在init方法中使用就可以!
功能:
1)获取当前servlet的名称[getServletName()]
2)获取当前servlet的初始化参数[getInitParameter("username")]
3)获取Servletcontext[getServletContext()]
ServletContext:
代表:ServletContext代表的是当前web应用!
获取 :通过ServletConfig对象获取[getServletContext()]
功能:
1)获取整个web应用的初始化参数【getServletContext()】
2)可以做为javaweb部分的四大域对象之一使用
3)可以获取当前项目的名称【getContextPath()】
4)可以获取文件的绝对路径[getRealPath("/index.html")]
注:
路径:
虚拟路径:http://localhost:8080/servlet01/BServlet
真实路径:是指的在本地磁盘上的具体路径(详见8.6)
Throwable
算术异常类:ArithmeticExecption
空指针异常类:NullPointerException
类型强制转换异常:ClassCastException
数组下标越界异常:ArrayIndexOutOfBoundsException
文件未找到异常:FileNotFoundException
操作数据库异常:SQLException
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.2 HttpServletResponse对象
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.3 HttpServletRequest对象
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资源。
总结:
对比:
浏览器请求次数 地址栏是否发生变化 发生在服务器端/浏览器端 浏览器能否感知
转发 1 否 服务器端 否
重定向 2 是 浏览器端 是
转发靠request实现
重写向靠response实现
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
总结:
路径问题:
相对路径:相对路径指的是相对于当前文件所在目录的路径!
http://localhost:8080/servlet01/
http://localhost:8080/servlet01/html/a.html
html/a.html
局限性
绝对路径:绝对路径是以/开头表示的
浏览器解析,/代表http://localhost:8080/【服务器的根目录】<a>
服务器解析时,/代表 http://localhost:8080/项目名/ [项目的根目录]
常见的路径:
url-pattern 、 转发的路径
这两个路径都是由服务器端解析,/代表项目的根目录
重定向的路径、 页面中的路径
这两个路径都是由浏览器端解析,/代表服务器的根目录
<base>标签
<!-- base标签由一个href属性,这个属性可以做为我们在页面中相对路径的前缀
也就是说:我们在当前页面中写的相对路径,它会默认以base标签中的href
属性值做为前缀,
所以base标签可以让我们以使用相对路径的方式来使用绝对路径!
base标签仅仅对相对路径起作用,对绝对路径不起作用!
-->
<base href="http://localhost:8080/servlet01/html/"/>
十、解决tomcat端口被占用:Port 8005 required by Tomcat v7.0 Server at localhost is already in use
问题提示8005端口被占用
首先:在cmd下,输入 netstat -ano|findstr 8005 (什么端口号被占用就输入什么端口号),回车
再输入 taskkill /pid 2044 /f 回车 这里的2044对应上图的2044
完成后,重启一下eclipse
十一、乱码原因
Http协议进行通信的时候是基于请求和响应的,传输的内容我们称之为报文!
Http协议会按照一定的规则将报文编码,然后在读取的时候再使用响应的解码格式进行解码!
这个一定的规则指的就是字符集:ASCII,iso8859-1,gbk,gb2312,utf-8
编码:将字符按照字符集转化为二进制格式
解码:将二进制代码按照响应的字符集转化为字符!
乱码的根本原因:就是编码和解码指定的字符集不一致造成的!
解决方案:统一编码和解码的字符集,统一成utf-8字符集!
请求报文:
1).浏览器编码---------->服务器解码
浏览器是按照给定页面的编码格式进行编码的,所以只需要设置网页的编码格式为utf-8就可以了!
get请求:
解码:使用的是tomcat默认的解码格式,默认是iso8859-1;
我们在server.xml文件中设置:
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
post请求:
解码:使用的是tomcat默认的解码格式,默认是iso8859-1;
在服务器端我们使用的是:req.setCharacterEncoding("utf-8");来设置解码格式
注意:req.setCharacterEncoding("utf-8")一定要设置在第一次获取请求参数之前!
响应编码
服务器编码------------->浏览器解码
第一种解决方案:
resp.setCharacterEncoding("utf-8");
resp.setHeader("Content-Type", "text/html;charset=utf-8");
第二种解决方案:
resp.setHeader("Content-Type", "text/html;charset=utf-8");
第三种方案:
resp.setContentType("text/html;charset=utf-8");