WEB-9-Servlet
Servlet
1. servlet概述
1.1.servlet是什么
Servlet是sun公司提供的一门用于开发动态web资源的技术。
按照这套规范写出来的Servlet可以放置到web应用中在Servlet容器中运行。
1.2.开发Servlet步骤
(1)写一个类, 实现Servlet接口, 并实现其中的方法
(2)在web.xml中为servlet配置对外访问路径。
1.3.案例: 手写一个servlet
详细步骤参见: /FristServlet/手写一个servlet.txt
2.使用myeclipse开发servlet
使用myeclipse开发Servlet程序时, 可以新建一个Servlet, 默认继承HttpServlet, 在Servlet内部会覆盖doGet和doPost方法, 分别来处理GET和POST请求。
2.1.新建项目
新建一个Web Project,给一个项目名称,其他保持默认即可,然后finish,弹出的对话框选择yes即可。
2.2.新建一个servlet
新建一个servlet,如果new中找不到servlet,注意视图是否切换到MyEclipse中,给一个包名和类名,下面对勾只留doGet()和doPost(),其他去掉,下一步
这里将最后两行删掉,是一些xml中的提示,其他地方一般保持默认即可,也可以修改对外访问路径,其中name和URL会自动配置到xml中。最后finash。
将servlet中的注释以及方法中的默认实现全部删掉。
为什么要继承HttpServlet?
因为这是一个继承了GenericServlet的类,已经提供doGet()和doPost()方法,可以方便我们开发web项目。
而GenericServlet是一个基础的实现,如果要用此类,需要自己写doGet()和doPost()方法,并且需要在service方法中对请求进行判断,会比较麻烦。
HttpServlet底层又是如何实现的?
可以打开HttpServlet源码,发现HttpServlet也是继承自GenericServlet,同时,HttpServlet会写各种方法,比如doGet()和doPost(),然后在service方法中进行判断,不同的请求调用不同的方法。
注意:复制一个servlet,web.xml中是不会自动生成配置信息的,所以一般不要复制,而是要新建!
2.3.将tomcat配置到myeclipse中
详细步骤见:/resource/在MyEclipse中配置自己安装的tomcat
如果不配置,则每次都要手动将程序发布到tomcat中。
2.3.将web应用发布到自己的tomcat中
这里注意项目名称是否是自己要发布的项目,然后finish,最后ok。
也可以修改发布的项目名称,参见/resource/在myeclipse中修改web应用发布到Tomcat中的应用的名称.pdf
在MyEclipse中启动tomcat
启动后,可以通过浏览器访问servlet,比如:
http://localhost/day09/servlet/SecondServlet
可以查看servers中tomcat如果是debug模式,则可以修改方法中的代码而无需重启服务器,但是若要新建一个servlet,则需要重启服务器。
3. Servlet的继承结构
Servlet接口 – 提供了一个Servlet应该具有的最基本的功能
|
|-- GenericServlet类, 实现了Servlet接口, 并实现了其中大部分的方法, 但是service方法没有实现, 这个方法需要开发人员自己去实现
|
|-- HttpServlet类, 继承了GenericServlet, 并实现了service方法, 在service方法中是根据不同的请求方式, 调用不同的doXxx方法, 因 此 我们在开发中, 只需要写一个类, 继承HttpServlet, 并覆盖 doGet()和 doPost()方法分别来处理Get请求和POST请求即可!!
4.修改servlet模版
方式一:
将\resource\修改Servlet模板\com.genuitec.eclipse.wizards_9.0.0.me201108091322.jar文件拷贝到:
[Myeclipse安装目录]\Common\plugins目录下,会提示是否替换文件,确定替换即可,如果未出现提示,则看是否进对目录,或者是其他版本的MyEclipse
方式二:
(1)在[Myeclipse安装目录]\Common\plugins目录下找到文件:com.genuitec.eclipse.wizards.xxx.jar,在此文件中的Templates目录下可以看到Servlet.java源代码。
(2)打开源代码,将doGet()和doPost()两个方法的注释和方法中的内容删掉,在doPost()中调用doGet()方法即可。
(3)修改之后保存
(4)重新启动Myeclipse即可以使用新的模板代码了
5.Servlet调用过程
5.1.调用过程
参照<<servlet调用图解>>
5.2.Servlet生命周期
Servlet实例在第一次被访问时创建, 创建之后服务器会立即调用init方法进行初始化的操作, 从此以后该实例会一直驻留在服务器的内存中, 为后续的请求服务, 只要有请求访问servlet, 服务器就会调用service方法来处理这个请求, 直到服务器关闭或者是web应用被移出容器时为止, 随着web应用的销毁, servlet实例也会跟着销毁, 在销毁之前, 服务器就调用destroy方法进行善后的处理.
6.servlet虚拟路径的配置
在web.xml中的servlet对外访问的虚拟路径的配置, 可以直接写一个路径, 或者通过 * 号匹配符写一个路径.
方式一:直接写一个路径: /servlet/SecondServlet
方式二:通过*号匹配符写一个路径:
(1)以 / 开头, 以 /* 结尾, 如: /servlet/* /a/* /*
(2)以 *.后缀 的形式, 如: *.html *.servlet *.do *.action
使用*号匹配符写路径, 路径的配置变得更加灵活, 但是也可能会造成, 一个url会被多个servlet Mapping所匹配
Url:http://localhost/day09/servlet/SecondServlet.do
Servlet1:Test1: /servlet/*
Servlet2:Test2: *.do
匹配规则:
*.后缀的优先级永远最低!!
哪一个更接近哪一个起作用!!
示例:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪个servlet响应
Servlet引擎将调用Servlet3。
当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
7.Request
代表http请求的对象
7.1.继承结构 (!!重要)
ServletRequest – 提供一个request对象最基本的功能
|
|-- HttpServletRequest – 继承了ServletRequest接口, 并在其基础上添加了很多和Http协议相关的方法
7.2.request的功能 (!!!重要)
7.2.1.获取客户端相关的信息
getRequestURL方法 -- 返回客户端发出请求完整URL
如: http://localhost/day09/servlet/SecondServlet
getRequestURI方法 -- 返回请求行中的资源名部分
如: /day09/servlet/SecondServlet
getQueryString方法 -- 返回请求行中的参数部分
如: username=zhangfei&password=123
getRemoteAddr方法 -- 返回发出请求的客户机的IP地址
如: 127.0.0.1 //可能会出现0:0:0:0:0:0:0:1形式,是ipv6的表现形式。
getMethod -- 得到客户机请求方式
如: GET或POST
!!getContextPath -- 获得当前web应用虚拟目录名称
如: /day09
注意:在写路径时不要将web应用的虚拟路径的名称写死, 应该在需要写web应用的名称的地方通过getContextPath方法动态获取
7.2.2.获取请求头信息
getHeader(name)方法 --- String
getHeaders(String name)方法 --- Enumeration<String>
可以通过遍历枚举遍历每一个信息
例如:while (values.hasMoreElements()) {
String value = (String) values.nextElement();
System.out.println(value);
}
getHeaderNames方法 --- Enumeration<String>
getIntHeader(name)方法 --- int
getDateHeader(name)方法 --- long(日期对应毫秒)
7.2.3.获取请求参数(!!!重要)
getParameter(String name) --- String 通过name获得值
getParameterValues(String name) --- String[ ] 通过name获得多值 checkbox
getParameterMap() --- Map<String,String[ ]> key :name value: 多值
getParameterNames() --- Enumeration<String> 获得所有name
例如:创建servlet:RequestDemo2,并在webRoot下,将css那天的regist文件夹放进去,并修改form中的属性:
<!-- <form action="http://localhost/day09/servlet/RequestDemo2"> --> <!-- 也可以简写为如下格式 --> <form action="/day09/servlet/RequestDemo2"> |
RequestDemo2中代码如下:
//getParameter(String name) String username = request.getParameter("username"); System.out.println("username:"+username); //getParameterValues(String name) //可以自定义一个网址拼接参数,如: //http://localhost/day09/servlet/RequestDemo2?like=lanqiu&like=zuqiu&like=ppq String[] likes = request.getParameterValues("like"); System.out.println("likes:"+Arrays.toString(likes)); //getParameterMap()获取所有请求参数组成的map集合 //可以自己指定map的类型 Map<String,String[]> map = request.getParameterMap(); for (Map.Entry<String,String[]> entry:map.entrySet()) { String key = entry.getKey(); String[] values = entry.getValue(); System.out.println(key+" : "+Arrays.toString(values)); } |
请求参数中的乱码问题
乱码分析: 编码时和解码时使用的码表不一致造成的!!
编码: 是在浏览器进行的, 浏览器发送数据使用的是什么编码?
浏览器在打开当前页面时使用的是什么码表,也会使用相同的码表来发送数据.
打开页面使用utf-8, 所以发送数据也是用utf-8码表
解码: 是在服务器端进行的, 服务器端接收数据使用的又是什么编码?
如果不指定, 服务器会使用默认的码表来接收浏览器发送过来的数据, 默认的码表是iso8859-1.
解决方案:
乱码造成的原因是编码不一致, 所以应该让两端的编码保持一致!, 应该通知服务器使用utf-8来接受客户端发送过来的数据!!
request.setCharacterEncoding(“utf-8”);//用来通知服务器使用什么编码来接受请求实体内容中的数据, 如果使用的是POST提交, POST提交的请求参数就是在请求实体内容中!, 所有这个方法可以解决POST提交的乱码问题!!!
GET提交的请求参数由于不在请求实体内容中,而是在请求行中的请求资源路径后面拼接着, 所以这行代码对GET提交的参数乱码不起作用!!!
GET提交的参数乱码问题该如何解决???
GET提交的乱码问题可以通过手动编解码来解决!!
//>>username为乱码, 通过乱码反向编码得回二进制数组
byte[] bytes = username.getBytes("iso8859-1");
//>>通过二进制数组查询正确的码表, 得出正确的数据
username = new String(bytes, "utf-8");
7.2.4.实现请求转发(!!!重要)
请求重定向: 302状态码+location响应头
请求转发: 和请求重定向都可以实现资源的跳转, 但是区别是请求转发是服务器内部的并且是同一个WEB应用内部的资源跳转
请求转发的特点:
一次请求对应一次响应
地址栏地址不会发生变化
请求转发只能在同一个WEB应用内部资源之间进行跳转! 不能是不同的WEB应用或者不同的主机!
实现代码:
创建servlet:RequestDemo3和RequestDemo4,RequestDemo3转发到RequestDemo4 在RequestDemo3中: /* * 在web阶段写路径时,除了请求转发和请求包含在写 * 路径是不用包含web应用的虚拟路径,其他地方都需要 * 加上web应用的虚拟路径。 * 完整路径:http://localhost/day09/servlet/RequestDemo4 */ request.getRequestDispatcher("/servlet/RequestDemo4").forward(request, response);
在RequestDemo4中响应: response.getWriter().write("1$");
|
request开发细节:
在转发之前, 如果response缓冲区被写入了数据但是还没有打给浏览器, 在转发时response缓冲区(数据)将会被清空!
例如:在RequestDemo3中添加代码:
response.getWriter().write("no money!");
发现浏览器中并未得到"no money!"这段字符串的响应。
在转发之前, 如果response缓冲区被写入了数据并且已经打给了浏览器, 转发将会失败!!
例如:在RequestDemo3中添加代码:
response.getWriter().write("no money!");
response.flushBuffer();
发现浏览器可以显示"no money!",因为强制刷新了,但是转发就会报错,因为已经响应过了,一次请求对应一次响应。
在同一个Servlet中转发不能进行多次!!(A既转发B, 又转发给C)
但是可以进行多重转发(比如A转发给B, B再转发给C)
7.2.5.作为域对象来使用(!!!重要)
域对象:如果一个对象具有一个可以被看见的范围, 利用该对象上的map可以在整个范围内实现资源的共享!
域对象提供的方法(可以操作map中的数据):
setAttribute(String name, Object value); 用来存储一个对象,也可以称之为存储一个域属性
getAttribute(String name); 用来获取request中的数据
removeAttribute(String name); 用来移除request中的域属性
getAttributeNames(); 获取所有域属性的名称
生命周期:
一次请求开始时创建request对象, 一次请求结束时销毁request对象
作用范围:
整个请求链
主要功能:
在整个范围内共享数据
带数据到目的地
例如:在RequestDemo3中可以设置一些属性,比如是从数据库查询出来的数据,转发到RequestDemo4后,在RequestDemo4中从request域中获取数据,并打印到页面中。
7.2.6.实现请求包含
请求包含是服务器内部资源合并的现象
如果浏览器访问Servlet A, 但是A不能独立的处理这次请求, 需要另外一个Servlet B帮忙, 于是在A中可以将B包含进来, 包含的代码如下:
request.getRequestDispatcher(“B的路径”).include(request, response);
将B包含进来后, 将会由A和B共同来处理这次请求, 处理的结果也会合并在一起, 一起打给浏览器!