Xml &Tomcat
Xml
eXtendsible markup language 可扩展的标记语言
XML有什么用?
-
可以用来保存数据;
-
可以用来做配置文件;
-
数据传输载体;
(xml是一个倒置的树形结构)
定义xml
其实就是一个文件,文件后缀为.xml
. 文档声明
简单声明 version:解析这个xml的时候,使用什么版本的解析器解析
<?xml version="1.0"?>
encoding: 解析xml中的文字的时候,使用什么编码来翻译
<?xml version="1.0" encoding="gbk"?>
standalone:no–表示该文档关联其他文件;yes–表示这是一个独立的文档
<?xml version="1.0" encoding="gbk" standalone="no"?>
encoding详解
在解析xml的时候,使用什么编码去解析 ----解码(默认计算机编码:gbk)
计算机存储的不是文字,而是存储这些文字所对应的二进制,文字所对应的二进制到底是什么?根据文件使用的编码来得到
默认文件保存的时候,使用的是GBK编码保存
所以要想让我们的xml能够正常的现实中中文,有两种解决办法:
-
让encoding也是gbk或者gb2312;
-
如果encoding是utf-8,那么保存文件的时候也必须使用 utf-8;
-
保存文件的时候见到的 ANSI 对应的其实是我们的本地编码 GBK。
(为了通用,建议使用utf-8 编码保存,以及encoding都是utf-8。)
元素的定义(标签)
-
其实就是里面的标签, <> 括起来的都叫做标签,成对出现,如下:
<stu> </stu>
-
文档声明下来的第一个元素叫做根元素(根标签)
-
标签里面可以嵌套标签
<stu> <name>张三</name> <age/> </stu>
-
空标签(既是开始,又是结束)例如:
<age/>
-
标签可以自己定义(html中标签不能自己定义),所以xml是可扩展标记语言
-
标签的命名规则(命名尽量简单,做到见名知意):
名字可以含有字母、数字以及其他的字符;
名字不能以数字或者标点符号开始;
名字不能以字符“xml"(或者XML、Xml)开始;
名字不能包含空格
简单标签 & 复杂标签
- 简单标签
元素里面包含了普通的文字
- 复杂标签
元素里面还可以嵌套其他的元素
属性的定义
定义在元素里面<元素名称 属性名称=“属性的值”></元素名称> 如下:
<?xml version="1.0" encoding="UTF-8"?>
<stus>
<stu1 id="10086">
<name>张三</name>
<age>18</age>
</stu1>
<stu2>
<id>10087</id>
<name>李四</name>
<age>19</age>
</stu2>
</stus>
xml注释
与html注释一样,如下:(快捷键Ctrl+shift+/)
<!-- 这里有两个学生:张三 李四 -->
注释不能再文档的第一行,必须在文档声明的下面
CDATA区
(一般很少出现,配置文件中就不会出现;出现在服务器向客户端传输数据)
- 非法字符
严格的讲, 在xml中仅有字符"<“和”&"是非法的,省略号、引号、大于号是合法的,但是把它们替换成实体引用是好的习惯。
< <
& &
> >
- 如果某段字符串里面有过多的字符,并且里面包含了类似标签或者关键字的这种文字,不想让xml解析器去解析,那么可以使用CDATA来包装,不过这个CDATA一般比较少看到,通常在服务器给客户端返回数据的时候看到
<des><![CDATA[<a href="http://www.baidu.com">黑马训练营</a>]]></des>
xml解析
其实就是获取元素里面的字符数据或者属性元素
xml的解析方式(面试常问)
有很多种,但是常用的有两种:
- DOM: (document object model)把整个xml全部读到内存当中,形成树形结构,整个文档(即整个树结构)称之为document对象, 属性(如:id)对应Attribute对象,所有的元素节点(如:name age)对应Element对象,文本(如:张三)也可以称之为Text对象。以上所有对象都可以称之为Node节点;
缺点:如果xml特别大,那么就会造成内存溢出.
优点:可以对文档进行增删操作。
- SAX:(Simple API for xml) 基于事件驱动,读一行,解析一行。
优点: 不会造成内存溢出。
缺点:不可以进行增删,只能查询。
###针对这两种解析方式的API
一些组织或者公司,针对以上两种解析方式,给出的解决方案有哪些?
jaxp (sun公司做的,比较繁琐)
jdom
dom4j (使用比较广泛)
Dom4j基本用法
-
创建SaxReader对象;
-
指定解析的xml文件;
-
获取根元素;
-
根据根元素获取子元素(或者再根据子元素获取子孙元素)。
try { //1.创建SAX读取对象,还有DOMReader() SAXReader reader = new SAXReader(); //2.指定解析的xml源:reader.read(path|file+inputtStream) Document document = reader.read(new File("src/xml/demo.xml")); //3.得到元素 //3.1得到根元素 Element rootelement=document.getRootElement(); //3.2得到根元素下面的子元素,元素名称stu1 rootelement.element("stu1"); //3.3 得到根元素下面的子孙元素 //System.out.print(rootelement.element("stu1").element("age").getText()); //3.4得到根元素下面的所有元素 List<Element> elements=rootelement.elements(); for(Element element:elements) { String name =element.element("name").getText(); String age=element.element("age").getText(); String id=element.element("id").getText(); System.out.print("name"+name+";age"+age+";id"+id); } } catch (Exception e) { e.printStackTrace(); }
Dom4j的Xpath使用
dom4j里面支持Xpath的写法,Xpath其实是xml的路径语言;
支持我们在解析xml的时候,能够快速的定位到具体的一个元素
-
添加jar包依赖;
jaxen-1.1-beta-6 jar
-
在查找指定的节点的时候,根据Xpath语法规则来查找;
-
后续的代码和之前的解析代码一样。
try { //1.创建SAX读取对象 SAXReader reader = new SAXReader(); //2.指定解析的xml源 Document document = reader.read(new File("src/xml/demo.xml")); //3.得到元素 //3.1得到根元素 Element rootelement=document.getRootElement(); // 要想使用Xpath,还得添加支持的jar //获取指定元素 Element element=(Element) rootelement.selectSingleNode("//name"); System.out.println(element.getText()); //获取所有name元素 //获取第一个name节点(这里用Element这种类型的对象调用selectNodes()方法,但是定义这个selectNodes的方法是定义在一个接口Node中的,请问这么写有没有错? -----没错:element、attribute、text都是node的子接口,体现了Java的多态) List<Element> list=rootelement.selectNodes("//name"); for (Element element2 : list) { System.out.println(element2.getText()); } } catch (Exception e) { e.printStackTrace(); }
xml约束(了解)
如下的xml文档,属性的ID是一样的,这在生活中是不可能出现的;并且第二个学会说呢过的姓名出现了好几个,这种情况一般也很少。那么如何规定ID的值唯一,或者元素只出现一次,不能出现多次?甚至规定里面只能具体出现的元素的名字。
<?xml version="1.0" encoding="UTF-8"?>
<stus>
<stu id="10086">
<name>李武</name>
<age>67</age>
</stu>
<stu id="10086">
<name>李六</name>
<name>李七</name>
<name>李八</name>
<age>67</age>
</stu>
</stus>
文档约束有两大类:DTD,Schema(面试:了解区别,优缺点)
小结(下面):
- DTD:语法自成一派,早期就出现,可读性差
- schema:使用xml的语法规则,xml解析器解析起来比较方便。它的出现是为了替代DTD,但是schema约束的文本内容比DTD的内容还要多,所以目前也没有真正意义上的替代DTD。
- 一个xml可以引用多个schema约束,但只能引用一个dtd约束。
DTD
**语法自成一派,早期就出现,可读性差**
-
引入网络上的DTD
<?xml version="1.0" encoding="UTF-8"?>引入dtd来约束这个xml:<!文档类型 根标签的名字 网络上的dtd dtd的名称 dtd的路径>
-
引入本地的DTD
-
直接在xml里面嵌入dtd的约束规则 :
<!DOCTYPE stus [
<!ELEMENT stus(stu)>
<!ELEMENT stu(name,age)>
<!ELEMENT name(#PCDATA)>
<!ELEMENT age(#PCDATA)>
]>
<stus>
<stu>
<name>李武</name>
<age>67</age>
</stu>
</stus>
dtd文档中的内容:
<!ELEMENT stus (stu) +>: 如果没有+号(stus下面有一个元素stu,但是只有一个)
<!ELEMENT stu (name, age)>: stu下面有两个元素name,age,顺序必须是name age
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ATTLIST stu id ID #IMPLIED>: stu有一个属性id 属性类型 属性的默认值(IMPLIED 表示属性可有可无)
元素个数:(类似于正则表达式)
+ 表示一个或多个
* 表示零个或多个
?表示零个或一个
属性类型的定义:
CDATA: 属性是普通的文字
ID:属性的值必须唯一
元素的顺序:
<!ELEMENT stu (name , age)>: 按照name age的顺序来
<!ELEMENT stu (name | age)>: 两个中只能包含一个子元素
schema
schema其实是一个xml,使用xml的语法规则,xml解析器解析起来比较方便。它的出现是为了替代DTD,但是schema约束的文本内容比DTD的内容还要多,所以目前也没有真正意义上的替代DTD。
约束文档:
<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns(xml namespace):表示名称空间/命名空间
targetNamespace: 目标名称空间下面定义的那些元素都与这个名称空间绑定上
elementFormDefault:元素的格式化情况-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/teacher"
elementFormDefault="qualified">
<!-- teachers和teacher是复杂元素,
需要标签complexType:表示这个元素是复杂元素;
需要标签sequence:表示下面的元素是有序的 -->
<element name="teachers">
<complexType>
<sequence maxOccurs="unbounded">
<element name="teacher">
<complexType>
<sequence>
<!-- name和age是简单元素 -->
<element name="name" type="string"></element>
<element name="age" type="int"></element>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
实例文档:
<?xml version="1.0" encoding="UTF-8"?>
<!-- xnlns:xsi:这里必须是这样的写法,也就是这个值已经是固定的了
xmlns:这里是名称空间,也是固定了, 写的是schema里面的顶部项目名称空间
xsi:schemaLocation:有两段,前半段是名称空间,也就是目标空间的值,后面是约束文档的路径 -->
<teachers
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.example.org/teacher"
xsi:schemaLocation="http://www.example.org/teacher teacher.xsd"
>
<teacher>
<name>历史</name>
<age>26</age>
</teacher>
<teacher>
<name>物理</name>
<age>22</age>
</teacher>
</teachers>
名称空间的作用
一个xml如果指定它的约束规则,假设使用的是dtd,则这个xml只能使用一个dtd,不能指定多个dtd; 但是如果一个xml的约束是定义在schema里面,并且是多个schema,那么是可以的;(简言之:一个xml可以引用多个schema约束,但只能引用一个dtd约束)。
名称空间的作用是:在写元素的时候,可以指定该元素使用的是那一套约束规则。默认情况下,如果只有一套规则,那么都可以这么写
xmlns:aa="http://www.baidu.com/teacher"
xmlns:bb="http://www.baidu.com/teacher"
<teacher>
<aa:name>历史</aa:name>
<age>26</age>
</teacher>
<teacher>
<bb:name>物理</bb:name>
<age>22</age>
</teacher>
程序架构
- C/S(client/server)
例如 QQ 微信 LOL就是使用 C/S架构
优点:
有一部分代码写在客户端,用户体验比较好
缺点:
服务器更新,客户端也要跟着更新,占用资源大(比较吃硬盘)
- B/S(browser/server)目前很多程序朝着B/S的方向发展
例如 网页游戏,网页QQ… 是使用B/S架构
优点:
客户端只要有浏览器就可以了,占用资源少,不用更新
缺点:
用户体验不佳
服务器
实质上服务器就是一台配置比较高的电脑
###Web服务器软件
客户端在浏览器的地址栏上输入地址,然后web服务器软件,接收请求,然后响应消息。
接收并处理客户端的请求,返回资源或信息
web应用 需要服务器支撑 index.html
比较常见的web服务器软件有:
Tomcat apache(免费)
weblogic BEA
WebSphere IBM
IIS 微软
Tomcat安装
-
直接解压,然后找到 bin/startup.bat(双击) [startup.sh是在Linux操作系统中使用的];
-
可以安装:
启动之后,如果能够正常看到黑窗口,表明安装成功;为了确保万无一失,最好在浏览器的地址栏上输入:http://localhost:8080,如果有看到内容,就表明成功了;
- 如果双击了startup.bat 看到一闪而过的情形,一般都是JDK的环境变量没有配置成功
Tomcat目录介绍
bin
包含了一些jar,bat文件(常用startup.bat)
conf
tomcat 的配置 (常用的 server.xml / web.xml /)
lib
Tomcat 运行所需的jar文件
logs
运行的日志文件
temp
临时文件
webapps
发布在Tomcat服务器上的项目,就存放在这个目录(想让另外一台访问哪个项目,就把该项目放到这个文件夹下面)
work(目前不用管)
jsp 翻译成class文件存放地
如何把一个项目发布到Tomcat中
需求:如何能让其他电脑访问我这台电脑上的资源(例如:stus.xml)。
有三种方法解决这个需求:
1. 拷贝这个文件stus.xml到webapps/ROOT文件夹下面,客户端在浏览器中访问:
http://localhost:8080/stus.xml
或者在webapps下面新建一个文件夹xml,然后把stu.xml拷贝到这个新建的文件夹中(先关闭Tomcat,注意浏览器把webapps下的每个文件夹当做是一个项目,所以需要新建文件夹),客户端在浏览器中访问:
http://localhost:8080/xml/stus.xml
注意:这里
http://localhost:8080:其实对应的是到webapps/ROOT下面访问
http://localhost:8080/xml:其实对应的是到webapps/xml下面访问
localhost:本机地址
8080:对应的是Tomcat服务器在这台电脑对应的端口(默认)
或者使用IP地址访问:
http://192.168.37.48:8080/xml/stus.xml
2. 配置虚拟路径
使用localhost:8080打开tomcat首页,在找到tomcat的documentation入口,点击进去后,点击configuration,然后在左侧接着找到Context入口,点击进去。找到Defining a context定义一个虚拟的路径(Inside a Host element in the main conf/server.xml).
http://localhost:8080/docs/config/context.html
-
在conf/server.xml 中找到 Host元素节点;
-
加入以下内容:
<!-- docBase:项目的路径地址,如:C:\Java\workspace\XmlDemo\src\xml path :对应的虚拟路径,一定要以 / 开头; 对应的访问方式为:http:localhost:8080/teacher.xml--> <Context docBase="C:\Java\workspace\XmlDemo\src\xml" path="/a"></Context>
-
在浏览器地址栏上输入:
http://localhost:8080/a/teacher.xml
3. 配置虚拟路径
-
在Tomcat/conf/catalina/localhost/文件夹下新建一个xml文件,名字可以自己定义person.xml。
-
在这个文件里面写入一下内容:
<?xml version="1.0" encoding="utf-8"?> -
在浏览器上面访问
http://localhost:8080/teacher/teacher.xml
给Eclipse 配置Tomcat
-
在server里面 右键新建一个服务器,选择到apache分类,找到对应的Tomcat版本,接着一步步配置即可;
-
配置完成后,在server里面右键刚才的服务器,然后open,找到上面的 Server Location,选择中间的 Use Tomcat installation;
-
新建一个动态的web工程(other下),在webContent下定义html文件,右键web工程,run as server
总结
xml:
会定义xml;
会解析xml(dom4j解析);
Tomcat:
会安装,会启动,会访问;
会设置虚拟路径;
会给eclipse配置Tomcat。
HTTP协议 & Servlet
HTTP协议
- 什么是协议
双方在交互、通信的时候,遵守的一种规范、规则;
- HTTP协议
针对网络上的客户端 与 服务器在执行http请求的时候,遵守的一种规范:
其实就是规定了客户端在访问服务器的时候,需要带上那些东西;服务器返回数据的时候,也要带上什么东西
- http协议的版本:
1.0 (请求数据,服务器返回后,将会断开连接)
1.1(请求数据,服务器返回后,连接还会保持,除非服务器或客户端关掉。有一定的时间限制,如果都空着这个连接,那么后面会自己断掉)
演示客户端 如何 与服务器通讯
在浏览器地址栏中键入网络地址,回车;或者是平常注册的时候,点击了注册按钮,浏览器都会现实出来一些东西。那么底层的浏览器和服务器是怎么通讯的,它们都传递了那些数据?怎么看呢?如下步骤:
- 安装抓包工具 HttpWatch(IE插件)
- 打开Tomcat。输入 localhost:8080 打开首页
- 在首页上找到examples字样,然后找到 servlets example,点击;
- 点击Request Parameters 的 Execute ;到达页面,键入first name和 last name;
- 在点击提交之前,先打开HttpWatch抓包工具(位于IE浏览器右上角的小箭头),点击record;再点击提交;然后选中抓包页面的有颜色的进度条,然后点击stream,就可以看到浏览器(左边)和服务器(右边)的交互。
HTTP请求(浏览器向服务器请求)数据解释
请求的数据里面包括三个部分内容:请求行、请求头、请求体
- 请求行:(第一行)
POST/examples/servlets/servlet/RequestParamExample HTTP/1.1
POST: 请求方式(以post去提交数据);get也是请求方式;另外还有很多其他的请求方式,如OPTIONS/Delete/trace/put请求方式
/examples/servlets/servlet/RequestParamExample:请求的地址路径,就是要访问哪个地方
HTTP/1.1: 协议版本
- 请求头:(出来第一行,一直到空行之前,都是请求头)
Accept: application/x-ms-application, image/jpeg,application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */*
Referer: Http://localhost:8080/examples/servlets/servlet/RequestParamExample
Accept-Language:zh-CN
User-Agent:Mozilla/4.0(compatible; MSIE 8.0;Windoms NT 6.1; Wow64; Trident/4.0;SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: localhost:8080
Content-Length: 31
Connection: Keep-Alive
Cache-control: no-cache
Accept: 客户端向服务器端表示:“我能支持什么类型的数据”。
Referer: 真正请求的地址路径,全路径.
Accept-Language:支持语言格式
User-Agent:向服务器表明,当前来访的客户端信息(是PC端,还是Android/IOS端访问)
Content-Type:提交的数据类型,经过urlencoding编码的form表单数据
Accept-Encoding: gzip, deflate:压缩算法
Host: 主机地址
Content-Length: 内容、数据长度
Connection: 保持连接
Cache-control:对缓存的操作(no-cache:每一次请求这个地址路径,不从缓存中拿数据,而是直接越过缓存,向服务器请求新的数据访问)
- 请求体:(最后一行)
浏览器真正发送给服务器的数据,发送的数据呈现的是key=value的形式; 如果存在多个数据,那么使用 &;
firstname=zhang&lastname=sansan
HTTP响应(服务器响应浏览器)数据解释
响应的数据里面包含三个部分的内容:响应行,响应头,响应体
HTTP/1.1 200 OK
Server: Apache-Copyto/1.1
Content-Type: text/html;charset=ISO-8859-1
ContenT-LENGTH:673
Date: Fri, 17 Feb 2017 02:53:02 GMT
... ...(这里还有很多数据)
-
响应行:(第一行)
HTTP/1.1 200 OK
HTTP/1.1: 协议版本
200: 代表状态码(状态码是指:咱们这次交互到底是什么结果的一个code)
200:表示成功,正常出来,得到数据;
3打头的代表重定向;
403: Forbiddern 拒绝;404: NOT Found; (4打头的代表客户端有问题)
500:(5打头的代表服务器端有问题)
OK: 对应前面的状态码
- 响应头:
Server: 服务器的类型(tomcat)
Content-Type: 服务器返回给客户端的内容类型
Content-Length: 返回的数据长度
Date: 通讯的日期,响应的时间
post 和 get请求的区别
- 请求路径不同:
post请求在URL后面不跟任何的数据;get请求在URL后面跟上数据;
- 数据传输方式不同:
带上的数据不同,post请求会使用流的方式写数据;get请求是在地址栏上跟数据(即请求体不同);
- 由于post请求使用流的方式写数据,所以一定需要一个Content.Length 的头来说明数据的长度
!小结:(面试会问)
- post:
-1. 数据以流的方式写过去,不会再地址栏上面显示。现在一般客户端/浏览器 向 服务器 提交数据的都使用post;
-2. 以流的方式写数据,所以数据没有大小限制。
- get:
-1. 会在地址栏后面拼接数据,所以有安全隐患。现在一般从服务器获取数据,并且客户端也不要提交上面数据的时候,可以使用get。
-2. 能够带的数据有限(1kb大小)。
web资源
在我们的应用体系(或者http协议)中,规定了请求和响应的双方:客户端/浏览器 和 服务器端。与web相关的资源。
(服务器可以返回的一切东西都可以看做是web资源)
有两种分类:
-
静态资源:(即使请求访问的时间点不同,产生这份数据的源代码是不变的)
如: html,js, css -
动态资源
如:servlet/jsp [jsp:比如说注册 或者 登录 时输入数据]
注意!!!!(经验总结)
- 小结1:
- 运行web工程前,需要先关掉tomcat server(即叉掉startup.bat窗口);
- 如果电脑上装载了两个tomcat,并且都运行过;那么可能会出现startup.bat窗口闪退的情况。这是因为tomcat端口被占用,解决这个问题需要:
打开命令窗口:win+R,然后键入cmd->确定
在窗口输入:C: (敲回车键)
再输入: cd\windows\system32 (敲回车键)
再运行命令: 输入 netstat -ano | findstr 8080 查看占用8080端口的进程;(这里可以看到占用端口的进程PID(进程标识符)为6668)
再运行命令: 输入 tasklist|findstr 6668 查看进程6668的运行程序(这里查到进程的名称为 javaw.exe)
再运行命令: 输入 taskkill /f /t /im javaw.exe 杀死这个进程就可以了。
- 小结2:myeclipse开启自动提示功能的方法:
-
打开MyEclipse,然后单击“window”→“Preferences”。
-
单击“java”,展开“Editor”,单击“ContentAssist”。
-
找到右下的“Auto activation triggers for Java”这个选项,该选项用来设置触发代码提示的符号。
-
在“.”后加上abc字母,然后单击“apply”→“OK”。
-
单击“File”→“Export”,在弹出的窗口中选择“Perferences”,点击“下一步”。
-
选择导出文件路径,并为文件命名,点击“保存”,例如保存在D盘,名字为java.epf
-
用记事本打开java.epf文件按“ctrl+F”快捷键,输入“.abc”,点击“查找下一个”,查找到“.abc”的配置信息。
-
把“.abc”改成“.abcdefghijklmnopqrstuvwxyz@(,”,保存并关闭java.epf。
-
回到MyEclipse界面,单击“File”→“Import”,在弹出的窗口中选择“Perferences”,点击“下一步”,选择刚已经修改的java.epf文件,点击“打开”,点击“Finish”。
Servlet
- servlet 是什么?
其实就是一个Java程序,运行在我们的web服务器上,用于接收和响应客户端的http请求。
更多的是配合动态资源来做。当然静态资源也需要使用到servlet,,只不过tomcat里面已经定义好了一个DafaultServlet。(Tomcat其实就是一个容器,里面放置servlet。)
Hello Servlet
- 得写一个web工程,要有一个服务器;
- 测试运行web工程。
- 新建一个类,实现一个Servlet接口;如下:
public class helloServlet implements Servlet{
public void destroy() {
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
public void init(ServletConfig arg0) throws ServletException {
}
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
System.out.print("HelloServlet");
}
}
- 配置Servlet. 用意就是:告诉服务器,我们的应用有这么一个servlet.(在webcontent/WEB-INF/web.xml 里面写上一下内容:)
<!-- 向tomcat报告:我这个应用里面有一个servlet,名字叫做helloServlet, 具体的路径com.itheima.servlet.helloServlet -->
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.itheima.servlet.helloServlet</servlet-class>
</servlet>
<!-- 注册servlet的映射,servlet-name:找到上面注册的具体servlet;url-pattern:在地址栏上的path,一定要以 / 开头-->
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>
Servlet的执行过程
URL:http://localhost:8080/Helloweb/a
- 找到tomcat应用;
- 找到项目Helloweb(其实就是将web项目放入tomcat应用的wtpwebapps文件夹中);
- 找到web.xml配置文件,然后在里面找到 url-pattern, 看看里面有没有 pattern 的内容是 /a 的;
- 在配置文件中,找到servlet-mapping中与pattern对应的那个servlet-name[helloServlet];
- 在配置文件中,找到servlet元素中的 与上面找到的servlet-name相同的 servlet-name[helloServlet];
- 然后根据 对应的 servlet-class (路径)找到类helloServlet, 并开始创建该类的实例;
- 继而执行该servlet实例中的service()方法;
Servlet的通用写法:
因为在上面创建Servlet类是实现Servlet接口,必须重写改接口的所有方法,很不方便;所以考虑实现了该接口的类:GenericServlet 和 HttpServlet,创建Servlet类的时候可以继承这两个类,不需要重写所有方法。
Servlet(接口)
|
|
GenericServlet(Servlet的子类,是一个抽象类,实现了Servlet接口/ServletConfig/java.io.Serializable)
|
|
HttpServlet(抽象类,继承GenericServlets, 实现 java.io.Serializable; 用于处理处理http的请求的类)
- 定义一个类,继承HttpServlet 复写 doGet 和 doPost 方法;如下:(为什么会执行dopost和doget,而不是service();因为service() 方法的内部,对来访的请求,首先是对 get还是post进行判断,然后分别调用doGet和doPost)
public class helloServlet02 extends HttpServlet{
//get请求就会到这来
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doGet(req, resp);
System.out.print("get请求");
}
//post请求就会到这来
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doPost(req, resp);
System.out.print("post请求");
}
}
servlet的生命周期
- 生命周期:指从创建到销毁的一段时间
从创建(初始化)、接受和响应消息、到销毁,一定会执行的那些方法,就是servlet的生命周期方法;
- servlet的生命周期的方法:
- init():
/* 1. 在**创建该Servlet的实例的时候,就执行该方法**(类似于构造方法);
* 一个servlet只会初始化一次,即init()方法只会执行一次;
* 默认情况下是:初次访问该servlet,才会创建实例
*/
public void init(ServletConfig arg0) throws ServletException {
System.out.println("helloServlet03 初始化");
}
- service():
/* 2. 只有客户端来了一个请求,就会执行这个方法service();
* 该方法可以调用很多次,一次请求,对应一次service()方法的调用;
*/
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
System.out.println("helloServlet03 service()方法调用了");
}
- destroy():
/* 3. 销毁servlet的时候,就会调用该方法destroy();
* 什么时候调用该方法? 如下情形:
* (1)该项目从tomcat服务器的里面移除的时候;
* (2)正常关闭服务器(点击shutdown.bat); 注意,使用console界面的红色按钮停止tomcat属于非正常手段关闭;
*/
public void destroy() {
System.out.println("helloServlet03 destroy()方法调用了");
}
doGet 和 doPost 不算是生命周期的方法(它们不一定会执行)
让servlet创建实例的时机提前
为什么需要提前呢?(以下几个原因)
-
默认情况下,只有在初次访问servlet的时候,才会执行init方法;有的时候,我们可能需要在这个方法里面执行一些初始化工作,甚至做一些比较耗时的逻辑;
-
那么初次访问,可能会在init方法中逗留太久的时间,那么有没有方法可以让这个初始化的时机提前一些;
-
在配置的时候,使用 load-on-startup 元素来指定让servlet提前创建实例(即提前初始化);给定的数字越小,启动的时机就越早;一般 不写负数,从2开始即可。
<servlet>
<servlet-name>helloServlet04</servlet-name>
<servlet-class>com.itheima.servlet.helloServlet04</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
ServletConfig(使用频率不高)
servlet的配置,通过这个ServletConfig对象,可以获取servlet在配置的时候一些信息
(先说,再说怎么做;最后再写有什么用;)
(新建一个动态web项目,演示servletConfig)
public class helloServletConfig extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1. 得到servlet配置对象;
ServletConfig config = getServletConfig();
//2. 获取到的是配置servlet里面servlet-name的文本内容;
String servletName=config.getServletName();
System.out.println("servletName---"+servletName);
//3. 可以获取具体的某一个参数;
String adress=config.getInitParameter("adress");
System.out.println("adress---"+adress);
//4. 可以获取所有的参数
Enumeration<String> initParameterNames = config.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String elem = (String) initParameterNames.nextElement();
String key=config.getInitParameter(elem);
System.out.println("elem="+elem+";key="+key);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}
(将上面的servletconfig web项目导出为jar文件,即右击项目,点击 export-export–java/JAR file–选择保存路径保存–finish)
为什么需要有这个servletConfig?
- 未来我们自己开发的一些应用,使用到一些技术,或者一些代码,我们不会,但是有人写出来了,它的代码放置在了自己的servlet类里面;
- 刚好这个servlet里面需要一个数字或者叫做变量值。但是这个值不能是固定的。所以要求使用到这个servlet的公司,在注册servlet的时候,必须要在web.xml里面,声明init-params
-
请在你的web工程里面,添加jar(具体步骤是:复制上面路径下的jar文件;粘贴到 新建的web项目下的webContent/WEB-INF/lib下);
-
在web.xml里面,注册servlet(注意这里servlet-class:是jar包中class文件的路径)
-
请添加如下参数
<init-param> <param-name>number</param-name> <param-value>44</param-value> </init-param>
总结:
- http协议:
1.使用httpWatch 抓包看看http请求背后的细节
2.基本了解 请求和响应的数据内容: (请求行 请求头 请求体;响应行 响应头 响应体;)
3.get和post的区别
- Servlet:
1.会使用简单的servlet:
写一个类,实现接口servlet(或者继承httpservlet)
配置servlet(在web.xml文件中)
会访问servlet
2.servlet的生命周期
— init(): 只运行一次,默认初次访问就会调用;或者通过配置,让它提前 load-on-startup;
— service(): 多次执行,一次请求对应一次service();
— destroy(): 执行一次:销毁的时候、从服务器移除 或者 正常关闭服务器;
3.ServletConfig(对象,类)
获取配置的信息,参数等;
HttpServletRequest 和 HttpServletResponse
Servlet 配置方式
- 全路径配置
以 / 开头,例如 /a 或者 /a/bb;
localhost:8080/项目名称/a
- 路径匹配,前半段匹配
以 / 开头,但是以 * 结束;例如 /a/* 或者 /*
(*号:其实是通配符;表示该位置及后面位置可以匹配任意文字)
localhost:8080/项目名称/a/bbb
- 以扩展名匹配,后半段匹配
写法:(没有/,以*开头。) .扩展名;例如:.aa
ServletContext(使用频率较高)
servlet上下文
虚拟机中的每个web工程都只有一个servletContext对象,说白了就是不管在哪个servlet里面,获取这个servletContext类的对象都是同一个。
servletContext如何得到对象:
//1. 获取servletContext对象
ServletContext context = getServletContext();
servletContext有什么用
- 可以获取全局配置参数;
- 可以获取web工程中的资源;
- 存取数据,servlet间共享数据(域对象);
(具体如下)
1. 可以获取全局配置参数;
web.xml中配置全局参数:
<!-- context-param是全局参数,哪个servlet都可以拿,通过servletContext拿全局参数 -->
<context-param>
<param-name>adress</param-name>
<param-value>陕西西安---</param-value>
servletContext对象获取全局参数:
public class ServletContext02 extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取servletContext对象
ServletContext context = getServletContext();
String adress=context.getInitParameter("adress");
System.out.println("这是02发布的数据--adress="+adress);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
2. 可以获取web应用中的资源:
方式1:获取资源在tomcat里面的绝对路径: getRealPath()方法;(利用servletContext先获取绝对路径,然后自己new InputStream())
首先,在下面的类中使用FileInputStream()在web项目中是不OK,如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 创建属性对象
Properties properties=new Properties();
/*2. 指定载入的数据源
* 此处,如果想获取web工程下的资源,用普通的FileInputStream写法是不OK的;
* 因为路径不对了。FileInputStream的相对路径,其实是根据jre来确定的。
* 但是我们这是一个web工程,jre后面会由tomcat管理;
* 所以这里真正的相对路径应该是tomcat里面的bin目录;
* 因此,需要将config.properties文件复制到tomcat的斌目录下(可以新建一个classes文件夹
* ,把文件粘贴进去;并将下面改为new FileInputStream("classes/config.properties");)
*/
InputStream is= new FileInputStream("src/config.properties");
properties.load(is);
//3.获取name属性的值
String name = properties.getProperty("name");
System.out.println("name="+name);
}
修改上面的代码,添加如下servletContext对象获取绝对路径:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取servletContext对象
ServletContext context= getServletContext();
//获取给定的文件在服务器上面的绝对路径
String path = context.getRealPath("file/config.properties");
//1. 创建属性对象
Properties properties=new Properties();
//2. 指定载入的数据源
InputStream is= new FileInputStream(path);
properties.load(is);
//3.获取name属性的值
String name = properties.getProperty("name");
System.out.println("name="+name);
}
注意:这里String path = context.getRealPath(“”),得到的是项目在tomcat里面的根目录;
方式2: 获取资源,并转化为流对象,getResourceAsStream();如下:(根据相对路径直接获取流对象)
//1.获取servletContext对象
ServletContext context = getServletContext();
//2. 创建属性对象
Properties properties = new Properties();
//3. 获取web工程下的资源,转化成流对象,前面隐藏当前工程的根目录
InputStream is= context.getResourceAsStream("file/config.properties");
properties.load(is);
//4.获取name属性的值
String name = properties.getProperty("name");
System.out.println("name2=" + name);
is.close();
} catch (Exception e) {
e.printStackTrace();
}
方式3:(不是利用servletContext去获取web资源的方式)通过classLoad去获取web工程下的资源。 如下:this.getClass().getClassLoader(). getResource AsStream("…/…/file/config.properties")
/**
* 利用classLoad获取web资源
*/
private void test3() {
try {
/*
* 注意:
* 1. 对servletContext对象而言: 相对路径是---工程在tomcat里面的目录
* 即:C:\Users\RenJuan\Desktop\努力\tomcat服务器\apache-tomcat-6.0.39\wtpwebapps\ServletContext03
*
* 2. 对classLoad而言: 相对路径是---工程中类所在目录
* 即:C:\Users\RenJuan\Desktop\努力\tomcat服务器\apache-tomcat-6.0.39\wtpwebapps\ServletContext03\WEB-INF\classes
*
* 3. 默认的classLoad 的路径是上面的这个路径,我们必须得回到ServletContext03这个目录下面,才能进入file目录。如何返回上一级目录呢?
* 利用 ../ 可以返回到上一级; ../../可以返回到上两级;
*
* 这里 ../../ -----返回到了 C:\Users\RenJuan\Desktop\努力\tomcat服务器\apache-tomcat-6.0.39\wtpwebapps\ServletContext03
*
* ../../file/config.properties----达到了C:\Users\RenJuan\Desktop\努力\tomcat服务器\apache-tomcat-6.0.39\wtpwebapps\ServletContext03file/config.properties
*/
//.getClass():表示获取该Java文件的类文件;.getClassLoad():表示获取对应类文件的类加载器
InputStream is = this.getClass().getClassLoader()
.getResourceAsStream("../../file/config.properties");
Properties properties = new Properties();
properties.load(is);
String name = properties.getProperty("name");
System.out.println("name3=" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
小知识:
相对路径:有参照物,相对谁?这里相对的是工程在tomcat里面的根目录:(有以下两种形式)
a路径---工程在tomcat里面的目录;
例如: C:\Users\RenJuan\Desktop\努力\tomcat服务器\apache-tomcat-6.0.39\wtpwebapps\ServletContext03
b路径---file/config.properties
绝对路径:没有参照物
C:\Users\RenJuan\Desktop\努力\tomcat服务器\apache-tomcat-6.0.39\wtpwebapps\ServletContext03\file\config.properties
3. 使用servletContext存取数据(资源存取)
!注意:1 2 属于准备工作,并没有使用servletContext存取数据
1.定义一个登录的html页面,定义一个form表单:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="ServletContext04" method="get">
<h2>请按照下面内容填写,并登录</h2>
账户:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
2.定义一个Servlet,如下:
public class ServletContext04 extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
/* request: 包含请求的信息
* reponse: 响应数据给浏览器,就靠这个对象
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取数据
String username=request.getParameter("username");
String password=request.getParameter("password");
System.out.println("username="+username+";password="+password);
PrintWriter writer = response.getWriter();
//2. 校验数据
if("zhangsan".equals(username)&&"123".equals(password)){
//(1)仅向console输出内容: System.out.println("登录成功");
//(2)向客户端输出内容
//PrintWriter writer = response.getWriter();
//writer.write("login success...");
//(3)
//<1>成功的次数累加
Object obj = getServletContext().getAttribute("count");
//默认就是0次
int totalCount=0;
if(obj!=null){
totalCount=(Integer) obj;
}
System.out.println("已经登录成功的次数是:"+totalCount);
getServletContext().setAttribute("count", totalCount+1);
//<2>成功就跳转至success.html;
//首先,设置状态码(重新定位,设置状态码)
response.setStatus(302);
//然后,定位跳转的位置是那个页面
response.setHeader("Location", "success.html");
}else{
//System.out.println("登录失败");
writer.write("login faied...");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
3.针对成功或者失败,进行判断,然后跳转到不一样的页面;(response.setStatus(302); response.setHeader(“Location”, “success.html”);)
(首先新建一个跳转页面success.html 如下; 然后再servlet中利用response.setStatus 和 respons.setHeader完成页面的跳转)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>登录成功了</h2>
<a href="">获取网站登录成功的总数</a>
</body>
</html>
//(3)成功就跳转至success.html;
//首先,设置状态码(重新定位,设置状态码)
response.setStatus(302);
//然后,定位跳转的位置是那个页面
response.setHeader("Location", "success.html");
4.ServletContext获取登录成功总数(存取数据)getServletContext().getAttribute()
新建另一个CountServlet对象, 来获取上面servletContext04对象中得到的登录成功总数.
public class CountServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.取值
int count = (Integer) getServletContext().getAttribute("count");
//2.输出到界面
response.getWriter().write("the number of loginning successful:"+count);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
细节
-
load.html文档表单中的 action 对应的实际是 web.xml 文档中的servlet注册时内容(即所需要的servlet的路径)
相对路径:
a路径:servlet的路径 http://localhost:8080/(web.xml 文档中的内容)
b路径: 当前这个html的路径: http://localhost:8080/(success.html即html文档名)
ServletContext何时创建?何时销毁?
创建: 服务器启动的时候,会为每一个web应用程序,创建一个ServletContext对象;
销毁: 从服务器移除托管,或者是关闭服务器,则ServletContext销毁
- ServletContext 的作用范围:
只要在同一个项目里面,都可以取(即同一个项目中的servlet之间可以共享数据,但不同项目之间的servlet之间却不能取数据)。
HttpServletRequest
这个对象封装了客户端提交过来的一切数据(包括 请求行,请求头,请求体)
- 可以获取头信息(利用getHeaderNames(); getHeaderName(); getHeader();)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 得到一个枚举集合,detHeaderNames():获得全部的请求头的名字;getHeader():获得某请求头对应的请求头的值
Enumeration <String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = (String) headerNames.nextElement();
String value =request.getHeader(name);
System.out.println("请求头name:"+name+";请求头value:"+value);
}
}
- 可以获取客户端提交过来的数据,即获取请求体(利用getParameter(); getParameterNames(); getParameterMap()😉
//2.获取到的是客户端提交过来的数据,请求体
//(1)获取单个数据
String name=request.getParameter("name");
String adress=request.getParameter("adress");
System.out.println("name="+name+";adress="+adress);
//(2)获得客户端提交过来的所有参数,得到一个枚举集合
//Enumeration <String> parameterNames = request.getParameterNames();
//(3)获得客户端提交过来的所有参数,得到一个map集合
Map<String,String[]> parameterMap = request.getParameterMap();
Set<String> keySet = parameterMap.keySet();
Iterator<String> iterator=keySet.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
System.out.println(key+"; 它对应的值的总数="+parameterMap.get(key).length);
for(int i=0;i<parameterMap.get(key).length;i++){
String value = parameterMap.get(key)[i];
System.out.println(key+"="+value);
}
}
- 可以获取客户端提交过来的中文数据,(英语不管在什么编码下都是英文)(利用 username = new String(username.getBytes(“ISO-8859-1”),“UTF-8”);)
客户端提交数据给服务器,如果数据中带有中文的话,有可能会出现乱码情况,那么可以参考以下的方法解决。
- 如果是get方式(form表单的method=“get”)(username = new String(username.getBytes(“ISO-8859-1”),“UTF-8”);)
1. 代码转码
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username="+username+";password"+password);
//get请求过来的数据,在url地址栏上就已经经过编码了,所以我们取到的就是乱码;
//tomcat收到了这批数据,getParameter 默认使用 ISO-8859-1 去解码;
//先让文字回到 ISO-8859-1对应的字节数组,然后再按 utf-8 组拼写字符串
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");
System.out.println("username="+username+";password"+password);
}
直接在tomcat里面做配置,以后get请求过来的数据永远都是 utf-8 编码。
2. 可以在tomcat里面做设置处理,conf/servlet.xml,加上URIEncoding="utf-8"如下:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="utf-8"/>
- 如果是post方式(request.setCharacterEncoding(“utf-8”);)
//post请求过来的数据乱码处理;(因为getParameter默认的有一个编码方式,所以我们一定要在读取数据之前改变request的编码方式)
//注意: 下面这一行说的是设置请求体里面的文字编码。get请求用这行改变编码方式,是没有用的
request.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username="+username+";password"+password);
HttpservletResponse
负责响应或者返回数据给客户端。
输出数据到页面上
//写文字的话用两种方式都可以,但是文件的话用getWrite()是不行的!
//以字符流的形式写数据;
response.getWriter().write("<h1>hello response....</h1>");
//以字节流的形式写数据
response.getOutputStream().write("hello response2".getBytes());
响应的数据中有中文,那么很有可能出现中文乱码
-
以字符流输出:
//这里写出去的文字,默认使用的是ISO-8859-1编码; 我们可以指定写出去的时候,使用什么编码 //Step 1. 指定输出到客户端的时候,这些文字使用utf-8编码 response.setCharacterEncoding("utf-8"); //Step 2. 直接规定浏览器看这份数据的时候,使用什么编码来看 response.setHeader("Content-Type","text/html;charset=UTF-8"); response.getWriter().write("你好,中国");
-
以字节流输出:
//以字节流输出 /* * 如果想让服务器输出的中文,在客户端正常显示,只要确保一点: * 出去的时候用的编码和客户端看这份数据用的编码是一样的 * 默认情况下,getOutputStream 输出使用的是utf-8的编码;如果想指定具体的编码,可以在获取byte数组的时候,指定编码类型 */ //字符串String这个类里面getBytes()方法使用的码表是utf-8,和tomcat的默认编码无关;tomcat的默认码表还是ISO-8859-1 String csn=Charset.defaultCharset().name(); System.out.println("默认的String里面的getBytes方法使用的编码是:"+csn); //1 指定输出中文用的编码 response.getOutputStream().write("我爱黑马训练营".getBytes("utf-8"));//这里utf-8可以不用写 //2 指定浏览器看这份数据使用的编码 response.setHeader("Content-Type", "text/html;charset=utf-8"); ------------------------------------------------- 不管是字节流还是字符流,直接使用一行代码就可以了: response.setContentType("text/html; charset=utf-8");//设置响应的数据类型html文本,并告知浏览器使用utf-8来编码 再写数据即可
演练下载资源(文件下载)
1.直接以超链接的方式下载,不写sevlet(Java)代码,也能够下载东西下来。
让tomcat的默认servlet去提供下载:<br>
<a href="download/aa.jpg">aa.jpg</a><br>
<a href="download/bb.txt">bb.txt</a><br>
<a href="download/cc.rar">cc.rar</a><br>
原因是tomcat里面有一个默认的servlet—DefaultServlet,这个DefaultServlet专门用于处理放在tomcat服务器上的静态资源。
2.手动下载
html文档:
手动下载,提供下载:<br>
<a href="Demo01?filename=aa.jpg">aa.jpg</a><br>
<a href="Demo01?filename=bb.txt">bb.txt</a><br>
<a href="Demo01?filename=cc.rar">cc.rar</a><br>
sevlet(Java代码):
//1. 获取要下载的文件名字 ---- InputStream
String filename=request.getParameter("filename");
//2. 获取这个文件在tomcat里面的绝对路径地址
String path=getServletContext().getRealPath("download/"+filename);
//让浏览器收到这份资源的时候,以下载的方式提醒用户,而不是直接展示
response.setHeader("Content-Disposition", "attachment;filename="+filename);
//3. 转化为输入流
InputStream is= new FileInputStream(path);
OutputStream os=response.getOutputStream();
int len=0;
byte[] buffer=new byte[1024];
while( ( len = is.read(buffer) )!= -1 ){
os.write(buffer,0,len);
}
os.close();
is.close();
3.文件下载(中文名称)
针对浏览器的类型,对中文名称做编码处理 Firefox(Base64),IE/Chrome(URLEncoding).代码如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取要下载的文件名字 ---- InputStream
String filename=request.getParameter("filename");
//来一个get请求,这个filename有中文
filename = new String(filename.getBytes("ISO-8859-1"),"UTF-8");
System.out.println("filename="+filename);
/*
* 如果文件的名称中带有中文,那么需要对这个文件进行编码处理
* (1) 如果是IE 或者Chrome浏览器,使用URLEncoding 编码
* (2)如果是Firefox浏览器,使用Base64编码
*/
//获取来访的客户端类型
String clientType=request.getHeader("User-Agent");
if(clientType.contains("Firefox")){
filename=DownloadUtil.base64EncodeFileName(filename);
}else{
filename=URLEncoder.encode(filename,"UTF-8");
}
//2. 获取这个文件在tomcat里面的绝对路径地址
String path=getServletContext().getRealPath("download/"+filename);
//让浏览器收到这份资源的时候,以下载的方式提醒用户,而不是直接展示
response.setHeader("Content-Disposition","attachment;filename="+filename);
//3. 转化为输入流
InputStream is=new FileInputStream(path);
OutputStream os=response.getOutputStream();
int len=0;
byte[] bytes=new byte[1024];
while((len = is.read(bytes))!=-1){
os.write(bytes,0,len);
}
os.close();
is.close();
}
工具类:
public class DownloadUtil {
public static String base64EncodeFileName(String fileName){
BASE64Encoder base64Encoder=new BASE64Encoder();
try{
return "=?UTF-8?B?"+new String(base64Encoder.encode(fileName.getBytes("UTF-8")))+"?=";
}catch(java.io.UnsupportedEncodingException e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
请求转发和重定向
重定向
/*1.之前的写法:
*response.setStatus(302);
*response.setHeader("Location", "success_login.html");
*/
//2.重定向的更简单普遍的写法:重新定位方向(参数即跳转的位置)
response.sendRedirect("success_login.html");
请求转发
//请求转发的写法:(参数即跳转的位置)
request.getRequestDispatcher("success_login.html").forward(request,response);
两者的区别:
重定向:
- 地址上显示的是最后那个资源的路径地址;(返回302 ok)
- 请求次数最少有两次,服务器在第一次请求后,会返回302以及一个地址,浏览器再根据这个地址执行二次访问;
- 可以跳转到任意路径,不是自己的工程也可以跳;
- 效率稍微低一点,执行两次请求;
- 后续的请求无法使用上次request存储的数据,或者没法使用上一次的request对象,因为这是两次不同的请求;
请求转发:(比较常用)
- 地址上显示的是请求servlet的地址;(返回200 ok)
- 请求的次数只有一次,因为服务器内部帮客户端执行了后续的工作;
- 只能跳转到自己项目的资源路径;
- 效率上稍微高一点,因为只执行一次请求;
- 可以使用上一次的request请求;
#Cookie&Session
##Cookie
饼干,其实是一份小数据,是服务端给客户端的一份数据,并且存储在客户端上的一份小数据
应用场景
自动登录、浏览记录、购物车
为什么要有这个Cookie
http 的请求是无状态的(一次协议交互之后,第二次交互无记忆),客户端和服务器在通讯的时候,是无状态的,其实就是客户端在第二次来访的时候,服务器根本就不知道这个客户端以前有没有来访问过。为了更好地用户体验,更好地交互[自动登录],其实从公司层面讲,就是为了更好地收集用户习惯[大数据]。
Cookie 有什么用?
- 简单使用:
1.第一次请求:服务器生成cookie,并发送cookie给客户端(addCookie(cookie))
1.在响应的时候,添加Cookie:
Cookie cookie=new Cookie("aa","22");
//给响应,添加一个cookie
response.addCookie(cookie);
2.在客户端收到的信息里面,响应头中多了一个字段 Set-Cookie:aa=22
2.第二次请求:获取客户端带过来的cookie(request.getCookies())
3.获取客户端带过来的cookie
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie c : cookies) {
String cookieName = c.getName();
String cookieValue= c.getValue();
System.out.println(cookieName+"="+cookieValue);
}
}
补充(全代码):
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//Cookie的简单使用
//cookie 是服务器发送给客户端,并且保存在客户端上的一份小数据
/*
* 方法参数要什么就给什么
* 创建一个对象的几种手段:
* 1.直接new
* 2.单例模式|提供静态方法
* 3.工厂模式构建 例如stu对象
* stuFactory stuBuilder
*/
response.setContentType("text/html;charset=utf-8");
//一、发送cookie给客户端
Cookie cookie=new Cookie("aa","22");
//给响应,添加一个cookie
response.addCookie(cookie);
response.getWriter().write("请求成功了。。。");
//二、获取客户端带过来的cookie
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie c : cookies) {
String cookieName = c.getName();
String cookieValue= c.getValue();
System.out.println(cookieName+"="+cookieValue);
}
}
}
3.常用方法:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获得客户端请求带过来的cookie
//注:请求cookie最好放在响应cookie前面
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for (Cookie cookie : cookies) {
System.out.println(cookie.getName()+"="+cookie.getValue());
}
}
//2. 响应cookie,可以给客户端返回两个cookie
Cookie cookie1=new Cookie("name", "张三");
response.addCookie(cookie1);
//2.1这里为第二个cookie设置有效期: (注:设置有效期的代码需要放在响应cookie代码的前面)
/*
* cookie 的有效期:
* 默认情况下,关闭浏览器后,cookie就没有了。----> 针对没有设置cookie有效期的情况
* expiry:意思是有效期(以秒为单位)
* ---> 正值:表示这个时间过后,cookie将会失效
* ---> 负值:表示关闭浏览器之后,cookie就会失效,默认值是-1;
* ---> 0:表示删除这个cookie;
*/
cookie1.setMaxAge(60*60*24*7);//这里设置一周有效期
Cookie cookie2=new Cookie("age", "25");
response.addCookie(cookie2);
//3.cookie 的其他常用方法
//3.1 为某个cookie赋新的值
cookie1.setValue("wangwu");
//3.2 用于指定: 只有请求了指定的域名,才会带上该cookie
cookie1.setDomain(".itheima.com");
//3.3 只有访问该域名下的CookieDemo这个路径地址才会带cookie
cookie1.setPath("/CookieDemo");
//例如:访问www.itheima.com/CookieDemo,客户端会带cookie过来
}
例子1:显示最近访问(上一次访问)的时间
- 判断账号是否正确
- 如果正确,则获取cookie,但是的到的cookie是一个数组,我们从数组里面找到我们想要的对象;
- 如果找到对象为空,表明第一次登录,那么要添加cookie;
- 如果找到的对象不为空,表明不是第一次登录,更新cookie;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username=request.getParameter("username");
String password=request.getParameter("password");
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
if("adrimn".equals(username)&&"123".equals(password)){
//获取客户端请求是带的cookie数组
Cookie[] cookies = request.getCookies();
//利用自己写的工具类
Cookie findCookie = CookieUtil.findCookie(cookies, "last");
if(findCookie==null){
//如果是第一次登录
//1. 输出到浏览器页面
response.getWriter().write("欢迎您,"+username);
//2. 添加cookie到浏览器(并设置有效期)
Cookie cookie=new Cookie("last",System.currentTimeMillis()+"");
cookie.setMaxAge(60*60);//一小时
response.addCookie(cookie);
}else{
//如果不是第一次登录(浏览器请求会带有cookie)
//1.取以前的cookie值
Long lastVistTime=Long.parseLong(findCookie.getValue());
//2.输出到浏览器界面
response.getWriter().write("欢迎您,"+username+"; 上次登录的时间是:"+new Date(lastVistTime));
//3. 重置登录时间
findCookie.setValue(System.currentTimeMillis()+"");
response.addCookie(findCookie);
}
}else{
response.getWriter().write("登录失败");
}
}
例子2: 显示商品的浏览记录
准备工作
-
拷贝基础课第一天的html原型文件(包括首页,商品列表页面[里面有浏览记录内容],商品详情页面,用户登录页面,),到webContent里面;
-
在webContent目录下面,新建一个jsp文件(product_list.jsp),然后拷贝原来product_list.html内容到jsp里面;建好之后jsp文件中的所有charset和encoding(中的ISO-8859-1)全部改为utf-8;
拷贝html标签的所有内容,替换jsp的html标签即可
-
修改(product_info.html商品详情页面)里面的手机数码超链接地址为product_list.jsp;
-
修改页面(index.html首页)顶部的手机数码跳转的位置为 product_list.jsp
注:导入外部项目,若出现中文乱码问题可以:设置Windows–Preferences–General–Content Types–Text–HTML;
或者 Windows–Preferences–General–Content Types–Text–java source file;
分析(见图)
代码
1.首先设置product_list.jsp中的商品的超链接为:servlet的url-pattern?id=1(例如 ProductInfo?id=1)
<div style="float: left;height: 480px;width: 15%;">
<a href="ProductInfo?id=1"><img src="../img/0001.jpg"width="100%"height="100%" /></a>
</div>
2.利用cookie获取浏览记录(名为ProductInfo的servlet代码如下:)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取到当前用户准备浏览的商品id
String id=request.getParameter("id");
Cookie[] cookies = request.getCookies();
Cookie findCookie = CookieUtil.findCookie(cookies, "history");
//第一次浏览
if(findCookie==null){
//1. 响应,并返回cookie
Cookie cookie=new Cookie("history",id);
cookie.setMaxAge(60*60);
response.addCookie(cookie);
}else{
//第二次浏览
//1. 获取以前的cookie,因为以前的cookie,包含了浏览记录
String findValue = findCookie.getValue();
//2. 让现在浏览的商品,和以前浏览的商品,形成cookie新的值
//注意:addCookie()之前需要加上有效期
findCookie.setMaxAge(60*60);
findCookie.setValue(findValue+"#"+id);
response.addCookie(findCookie);
System.out.println(findCookie.getName()+"="+findCookie.getValue());
}
response.sendRedirect("product_info.html");
}
3.JSP显示浏览记录(product_list.jsp页面中的浏览记录显示部分的代码如下:)
<!--:第四部分:浏览记录-->2
<div style="width:1210px;margin:0 auto; padding: 0 9px;border:1px solid #ddd;border-top:2px solid #999;height:246px">
<div>
<h2>浏览记录 </h2>
</div>
<div>
<ul style="list-style: none;">
<%
Cookie[] cookies = request.getCookies();
Cookie findCookie = com.itheima.servlet.CookieUtil.findCookie(cookies, "history");
//如果findcookie为空,则表明没有浏览记录
if(findCookie==null){
%>
<h1>您还没有浏览任何商品</h1>
<%
}else{//如果不为空,则利用.split("#")分离出浏览过的所有商品id
//注意:如果重复浏览的商品,则先去掉之前的记录,再在增加新的记录(可以用LinkedList来实现,删除,插入)
String[] ids=findCookie.getValue().split("#");
for(String id:ids){
%>
<li style="width:150px; height:216; float:left;margin:0 8px 0 0;padding:0 18px 15px;text-align:center;"><img src="000<%=id %>.jpg" width="130px" height="130px"/></li>
<%
}
}
%>
</ul>
</div>
</div>
4.清除浏览记录
其实就是清除cookie,删除cookie是没有什么delete方法的,只有设置setMaxAge(0);
(1) 首先在product_list.jsp页面中增加 清除浏览记录 的按钮,代码如下:
<div>
<a href="clearHistory"><h1>清除浏览记录</h1></a>
</div>
(2) 然后写一个名为clearHistory的servlet,作用是清除cookie,代码如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//演练清除cookie
Cookie cookie = new Cookie("history","");
cookie.setMaxAge(0);//设置立即删除cookie
response.addCookie(cookie);
response.sendRedirect("product_list.jsp");
}
jsp 里面使用Java代码
jsp(Java server Pager—> 最终会翻译成一个类,就是servlet)
定义全局变量:
<%! int a=99;%>
定义局部变量 :
<% int b=999;%>
在jsp页面上,显示a或者b的值,如下:
<%=a %>
<%=b %>
小结(Cookie):
- cookie是服务器发送给客户端的一份小数据,并且存放在客户端上。
- 获取cookie,添加cookie:
String[] cookies=request.getCookies();
Cookie cookie=new Cookie("cookie名","cookie值")
response.add(cookie);
- Cookie 分类
会话cookie: 默认情况下,关闭了浏览器,那么cookie就会消失;
持久cookie:在有效期限内,有效,并且会保存在客户端
cookie.setMaxAge(0);//设置立即删除
cookie.setMaxAge(60*60);//有效期是60*60秒
- cookie 的好处是:用户体验会更好
- cookie的缺陷有:
由于cookie会保存在客户端上,所以有安全隐患;
还有一个问题,cookie的大小和个数有限制。(浏览器应该支持每台web服务器有20个cookie,总共有300个cookie,并且可能将每个cookie的大小限定为4KB)。
为了解决这个问题,所以有了------> Session
Session
翻译过来是:会话;Session是基于Cookie的一种会话机制(Cookie本身就是一种会话机制),Session是数据存放在服务器端(Cookie是服务器返回一小份数据给客户端,并且存放在客户端)
-
常用的API :
HttpSession session = request.getSession(); //得到会话id session.getId(); //存值 session.setAttribute("name", "value"); //取值 session.getAttribute("name"); //移除值 session.removeAttribute("name");
-
Session 何时创建?何时销毁?
1.创建:
如果有在servlet里面调用了 request.getSession();
2.销毁:
session是存放在服务器内存中的一份数据。当然可以持久化(Redis): 即使关闭了浏览器,session也不会销毁。
销毁的方法如下:
(1) 关闭服务器;
(2) session会话时间过期了,有效期过了。默认有效期是:30分钟(servlet中的web.xml文件中自己定义的30分钟,可以设置)
例子3:简单购物车
分析(见图)
代码
- cartServlet代码:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//1.获取要添加到购物车的商品id
Integer id = Integer.parseInt(request.getParameter("id"));
String[]names={"iPhone11","小米8","华为9","三星note11","vivo9"};
//取到id对应的商品名称
String name=names[id];
//2.获取购物车存放东西的session Map<String,Integer> iPhone11 3
//session只是一个容器,里面存放的是一个map对象,并且保证只存放一次
Map <String,Integer>map = (Map<String, Integer>) request.getSession().getAttribute("cart");
//首先判断session里面有没有存放任何东西
if(map==null){
map=new LinkedHashMap<String, Integer>();
//setAttribute():即表示在session里面放一个map
request.getSession().setAttribute("cart", map);
}
//3.判断购物车里面有没有商品
if(map.containsKey(name)){
map.put(name,map.get(name)+1);
}else{
map.put(name, 1);
}
response.getWriter().write("<a href='product_list.jsp'><h2>继续购物</h2></a>");
response.getWriter().write("<a href='cart.jsp'><h2>去购物车结算</h2></a>");
}
- 页面显示(cart.jsp代码):
<%@ page import="java.util.Map"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@page import="java.util.Set"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>您的购物车商品如下:</h2><br>
<%
//注意,要想获取购物车中的商品(名称,数量),就要先获得map;要想获得map,就要先获得session
Map<String,Integer>map=(Map<String,Integer>)session.getAttribute("cart");
if(map!=null){
Set<String> set=map.keySet();
for(String name:set){//name:商品名称,value:商品个数
int value=map.get(name);
%>
<h3>名称:<%=name %> 数量:<%=value %></h3>
<%
}
}
%>
</body>
</html>
- 清除购物车(session.invalidate();session.removeAttribute(“cart”);)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//清除购物车,其实就是清除session里面的map
HttpSession session = request.getSession();
//方法1:强制干掉session会话,里面存放的任何数据都没有了
session.invalidate();
/*//方法2:从session里面移除某一个数据
session.removeAttribute("cart");
response.sendRedirect("cart.jsp");*/
}
总结:
-
请求转发和重定向(面试常问)
-
Cookie:(更重要)
服务器发送给客户端的一份小数据,存放在客户端;
基本用法:
添加cookie
获取cookie
演练例子:
1.获取上次访问时间;
2.获取商品浏览记录;
什么时候生成cookie?
response.addCookie(new Cookie())
cookie分类
会话cookie: 默认情况下,关闭了浏览器,那么cookie就会消失;
持久cookie:在有效期限内,有效,并且会保存在客户端
cookie.setMaxAge(0);//设置立即删除
cookie.setMaxAge(60*60);//有效期是60*60秒
- Session:(靠cookie去传的)
基于cookie的一种会话技术(会话:在同一个网站上多次访问上面的一些超链接,属于一次会话);数据存放在服务器端
会在cookie里面添加一个字段JKFHSJAGFHAJ, 是tomcat服务器生成的;
setAttribute();
getAttribute();
getSession().getId();//得到会话id
removeAttribute();//移除数据
invalidate();//强制使会话失效
创建和销毁:
调用request.getSession()创建
服务器关闭,或者会话超时(30);则Session销毁
注:setAttribute存放的值,在浏览器关闭之后,还有(在服务器上),就算客户端把电脑砸了,也还有。
jsp & EL & JSTL
#JSP
Java Server Page
- 什么是jsp?
从用户角度看待,就是一个网页;从程序员角度来看,其实就是一个Java类,它继承了servlet,所以可以直接说jsp就是一个servlet
- 为什么会有jsp?(为了更好地用户体验)
html多数情况下用来显示静态内容,一成不变的;
但是有时候我们需要在网页上显示一些动态数据,比如:查询所有的学生信息,根据姓名去查询具体某个学生。这些动作都需要去查询数据库,然后在网页上显示。
html是不支持写Java代码的,但是jsp里面可以写Java代码。
怎么用jsp?
- jsp的三大指令:page指令;include指令;taglib指令。
- jsp的动作标签:(重点讲3个)jsp:forword;jsp:include;jsp:param。
- jsp的内置对象:
四个作用域: pageContext; request; session; application.
指令的写法:
<%@ 指令名字 %>
1.page指令
<%@ page language="java" contentType="text/html;charset=utf-8" pageEncoding="utf-8" extends="httpjspbase" import="" session="true或者false" errorPage="error.jsp"%>
- language:
表明jsp页面中可以写什么语言
- contentType:
其实就是说这个文件是什么类型,告诉浏览器我是什么内容类型,即怎么打开它,以及使用什么编码
contentType="text/html;charset=utf-8"
//text/html:MIME Type 这是一个文本,html网页
//或者可以是 video/mp4: 表示这是一个音频文件
- pageEncoding:
jsp文件下面的[如
<body><head>
中]的内容编码
- entends:
用于指定jsp翻译成Java文件后,继承的父类是谁,一般不会改。
- import:
导包使用的,一般不用手写。(快捷键:alt+/)
- session:
值可选的有true/false
用于控制在这个jsp页面里面能否直接使用session(true:可以直接用session.getAttribute())
具体的区别是:请看具体的tomcat/work/Catalina/localhost/具体项目名称/具体名称.jsp java文件;
(1)如果值为true: 在jspService里面看到session=pageContent.getOut();---->创建session;
(2)如果值为false: 没有上面的那一行代码;那么就没有该方法调用,也就是没有session对象了,在页面上自然也就不能直接使用session.getAttribute();
- errorPage:
指的是错误的页面,值需要给出:显示错误信息的页面的路径(比如error.jsp;在error.jsp中的page指令中可以配合使用 isErrorPage属性,令isErrorPage=“true”)
error.jsp页面的代码如下:
<%@ page language="java" contentType="text/html;charset=utf-8" pageEncoding="utf-8" isErrorPage="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/"
<html>
<head>
<meta http-equiv="Content-Type" content=:text/html;charset=utf-8">
<title>Insert title here</title>
</head>
<body>
出错了:<%=exception.toString()%>
</body>
</html>
- isErrorPage:
上面的errorPage用于指定错误的时候跑到哪个页面去,那么这个isErrorPage,就是声明某一个页面到底是不是错误的页面。
2.include 指令
<%@include file="另外一个文件的路径"%>
包含另外一个jsp的内容写进来(即另外一个页面上的内容在该页面显示),include可以写在jsp文档的任意位置,如
<body>
标签中
背后细节:---------> 静态包含:就是把另外一个页面的源代码拿过来一起输出,所有的标签元素都包含进来;
3.taglib 指令(引入标签库)
<%@ taglib prefix="" uri="" %>
uri:标签库的路径
prefix:标签库的别
jsp的动作标签
- 很多,在
<body>
中写<jsp:
就会提示很多;这里主要讲3个,如下:
<jsp:include page="指定的包含页面的路径"></jsp:include>
<jsp:param value="" name=""/>
<jsp:forword page="指定的跳转至的页面的路径"></jsp:forword>
注:Ctrl+c+/:jsp 中注释的快捷键
1. jsp:include 动态标签
<jsp:include page=""></jsp:include>
意思是:包含指定的页面。
注意:这里是动态包含,也就是不是把包含在页面里的所有元素标签全部拿过来,而是把它的运行结果拿过来。(查看源码就可以看出来)
等同于前面的include指令的效果,不同之处是:include指令是静态包含;而jsp:include是动态包含。
2. jsp:forword 动态标签
<jsp:forword page=""></jsp:forword>
意思是: 前往哪一个页面(跳转页面至---)。
等同于以下代码:(实际上jsp:forword的底层源代码就是下面的代码)
<%
//请求转发
request.getRequestDispatcher("跳转至的页面.jsp").forword(request,response);
%>
3. jsp:param 动态标签
<jsp:param value="" name=""/>
意思是: 在包含某个页面的时候,或者再跳转到某个页面的时候,加入这个参数。
other.jsp跳转至other2.jsp页面,并且携带参数value=“beijing” name="address"过去;代码如下:
<jsp:forword page="other2.jsp">
<jsp:param value="beijing" name="address"/>
</jsp:forword>
other2.jsp里面获取参数,代码如下:
<%=request.getParameter("address")%>
jsp的内置对象[9个](重点)
所谓的内置对象:就是我们可以直接在jsp页面中使用这些对象,不用创建
首先,包括四个作用域: pageContext、request、 session、application
作用域:其实就是表示这些对象可以存值,它们的取值范围有限定。通过 .setAttribute() 存值;通过 .getAttribute() 取值;具体如下:
使用作用域来存储数据:<br>
<%
pageContext.setAttribute("name","page");
request.setAttribute("name","request");
session.setAttribute("name","session");
application.setAttribute("name","application");
%>
取出四个作用域中的值:<br>
<%=pageContext.getAttribute("name")%>
<%=request.getAttribute("name")%>
<%=session.getAttribute("name")%>
<%=application.getAttribute("name")%>
四个作用域的区别:
1. pageContext(PageContext类型)
作用域仅限于当前页面
2. request(HttpServletRequest类型)
作用域仅限于一次请求,只要服务器对该请求作出了回应,这个域中存的值就没有了。
3. session(HttpSession类型)
作用域仅限于一次会话(多次请求与响应都属于一次会话,但只要关闭浏览器,再次请求就属于第二次会话)当中,一次会话结束后(关闭浏览器,或者会话过期),这个域中的值就没有了。
4. application(ServletContext类型)
整个工程都可以访问,服务器关闭后就不能访问了
平时比较少用的3个内置对象:
5. page(Object类型,就是这个jsp翻译成Java类的实例对象)
6. config(ServletConfig类型)
7. exception(Throwable类型)
(在page指令中有isErrorPage="true"时,按快捷键alt+/,提示才会显示exception内置对象)
剩下的内置对象:
8. out(JspWriter类型)
9. response(HTTPServletResponse类型)
两者的区别是:当两种输出方式同时出现在同一个jsp文档中时,先输出到页面response本身要输出的东西,然后才输出out里面的内容。(实际上,out.write()背后是 —> 把out对象输出的内容放置到response的缓冲区去)
EL(Expression Language)
为了简化jsp代码,具体一点就是为了简化在jsp里面写的Java代码;
* 写法格式:${ 表达式 }
取值用法
- 如何使用?(EL表达式没有快捷键)
1. 使用EL表达式取出4个作用域中存放的值
使用作用域来存储数据:<br>
<%
pageContext.setAttribute("name","page");
request.setAttribute("name","request");
session.setAttribute("name","session");
application.setAttribute("name","application");
%>
按普通手段取出四个作用域中的值:<br>
<%=pageContext.getAttribute("name")%>
<%=request.getAttribute("name")%>
<%=session.getAttribute("name")%>
<%=application.getAttribute("name")%>
使用EL表达式取出作用域中的值:<br>
//注:如下如果提示错误,就删掉,重新粘贴
${ pageScope.name }
${ requestScope.name }
${ sessionScope.name }
${ applicationScope.name }
2. 如果作用域中存的值是数组/集合/map,则如下取:
<%
String[] a={"aa","bb","cc",""dd};
pageContext.setAttribute("array",a);
%>
使用EL表达式取出作用域中数组的值:<br>
${array[0]},${array[1]},${array[2]},${array[3]}
<%
List list=new ArrayList();
list.add("11");
list.add("22");
list.add("33");
list.add("44");
pageContext.setAttribute("li",list);
%>
使用EL表达式取出作用域中集合中的值:<br>
${li[0]},${li[1]},${li[2]},${li[3]}
<%
Map map=new HashMap();
map.put("name","zhangsan");
map.put("age",15);
map.put("adress","beijing");
map.put("adress.aa","shenzhen");//注意这个
pageContext.setAttribute("ma",map);
%>
使用EL表达式取出作用域中集合中的值:<br>
${ma.name},${ma.age},${ma.adress},${ma["adress.aa"]}
3.取值细节
(1)
从域中取值,得先存值
<%
pageContext.setAttribute("name","zhangdan");
session.setAttribute("name","lisi");
%>
//直接指定了说:到pageContext这个作用域里面去找这个name:
${pageScope.name}
//先从page里面找,没有;再去request里面找,没有;再去session中找,没有;再去application里面找;(注意,有的话,就不会接着找下去了)
${name}
//指定从session中取值
${sessionScope.name}
(2)取值方式
* 如果这个值有下标,那么直接使用[];
* 如果值没有下标,直接使用.的方式去取;
//单个对象的存取属性值:
//先见一个class文件,新建一个User类,里面定义属性name,age(set,get方法,以及含参构造方法)
//再写jsp文件如下:
<%
User user=new User("zhangsan",28);
session.setAttribuet("u",user);
%>
${u.name},${u.age}
//判断对象是否为空
${empty u }
//做逻辑判断,或者算术运算
${a||b },${a>b },${a+b }
- 一般使用EL表达式,用的比较多的,都是从一个对象中取出它的属性值,比如取出某一个学生的姓名
EL表达式的11个内置对象(即隐式对象):
${对象名.成员 }//可以直接用对象名.
注:只有一个内置对象与jsp的内置对象同名,就是pageContext!
JSP:
—1. pageContext—PageContext实例
作用域相关对象:
—2. pageScope—Map类(可以用.)
—3. requestScope—Map类
—4. sessionScope—Map类
—5. applicationScope—Map类
头信息相关对象:
—6. header—Map类
—7. headerValues—Map类
参数信息相关对象:
—8. param—Map类
—9. paramValues—Map类
Cookie:
—10. cookie—Map类
全局初始化参数:
—11. initParam—Map类
一个例子:
<%
request.getParameter("address");
%>
使用EL表达式获取这个参数:
${param.address }
JSTL
引入
全程:jsp Standard Tag Library(jsp标准标签库)
作用:简化jsp的代码编写,替换<% %>写法,一般与EL表达式配合使用;(EL表达式一般都只能用来取值,却不能遍历;所以需要JSTL来执行遍历操作)
怎么用?
-
导入JSTL支持jar文件(jstl.jar)和standard.jar,到工程的webContent/web-inf/lib
-
在jsp页面上,使用taglib指令,来引入标签库
<%@ taglib prefix=“c” uri=“http://java.sun.com/jsp/jstl/core”%>
-
注意:如果想支持 EL表达式,那么引入的标签库必须是1.1的版本,1.0的版本不支持;(prefix=“c,也可以是别的比如itheima,c的原因是后面是core” uri=“使用1.1的核心版本,选择http://java.sun.com/jsp/jstl/core”)
基本用法
常用的标签
- <c:set></c:set>
<!--声明一个对象name,对象的值为zhangsan,默认存储到page,也可以通过scope指定存储域-->
<c:set var="name" value="zhangsan" scope="session"></c:set>
//取值
${sessionScope.name }
- <c:if test=“EL表达式”></c:if>
判断test里面的表达式是否满足,如果满足,就执行c:if标签中的代码;注意:c:if是没有else;
<c:set var="age" value="18"></c:set>
//定义一个变量名 flag 去接收前面表达式的值,然后存放在session域中
//var相当于:boolean flag = age>16
<c:if test="${age>16}" var="flag" scope="session">
年龄大于16岁了
</c:if>
//如果要写else,就再写如下的代码:
<c:if test="${age<=16}">
年龄大于16岁了
</c:if>
//输出判断的结果
${flag }
- <c:forEach></c:forEach>(比较常用)
用于做遍历
//从1开始遍历到10,得到的结果。复制给i,并且默认存储到page域中;step表示增幅为2
<c:forEach begin="1" end="10" var="i" step="2">
${i }
</c:forEach>
//items:表示遍历哪一个对象,注意:这里必须是EL表达式
//var:遍历出来的每一个元素用user去接收
<%
List list=new ArrayList();
list.add(new User("zhangsan",13));
list.add(new User("lisi",15));
list.add(new User("wangwu",17));
list.add(new User("zhaoliu",19));
pageContext.setAttribute("li",list);
%>
<c:forEach var="user" items="${li }">
${user.name },${user.age }
</c:forEach>
例子:学生信息管理系统
分析(见图)
-
先写login.jsp,并且搭配一个loginServlet 去获取登录信息;
-
创建用户表,里面只要有id, username, password;
-
创建 userDao接口,定义登录方法;
-
创建 userDaoImp1 实现类, 实现刚才的登录方法;
-
在loginServlet里面访问userDao, 判断登录结果,以区分对待;
-
创建stu_list.jsp页面, 让登录成功的时候,跳转过去;
-
创建学生表,里面字段随意;
-
创建 stuDao接口,定义查找所有学生信息的方法;
-
创建 stuDaoImp1 实现类,实现刚才的方法;
-
在登录成功的时候,完成三件事:
1)查询所有的学生信息;(访问stuDao)
2)把这个查询到的学生集合存储到作用域中;
3)跳转到stu_list.jsp页面。 -
在stu_list.jsp中,取出域中的集合,然后用c标签去遍历集合。
登录准备
- login.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>欢迎登陆学生信息管理系统</h2><br>
<form action="loginServlet" method="post">
账户:<input type="text" name="username"/><br>
密码:<input type="password" name="password"/><br>
<input type="submit" name="提交"/>
</form>
</body>
</html>
- login_servlet.java:
package com.itheima.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 下面是登录页面的servlet
*/
public class loginServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username+":"+password);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
创建数据库 & Dao
- 首先在SQLyog里面 创建数据库stus–>创建用户表user
CREATE DATABASE `stusmanager`;
USE `stusmanager`;
CREATE TABLE `stusmanager`.`user`( `id` INT(20) AUTO_INCREMENT UNIQUE , `username` VARCHAR(30) , `password` VARCHAR(30) );
INSERT INTO `stusmanager`.`user`(`id`,`username`,`password`) VALUES ( '1','adrimn','adrimn');
INSERT INTO `stusmanager`.`user`(`id`,`username`,`password`) VALUES ( '2','张三','123');
INSERT INTO `stusmanager`.`user`(`id`,`username`,`password`) VALUES ( '3','李四','456');
INSERT INTO `stusmanager`.`user`(`id`,`username`,`password`) VALUES ( '4','赵柳','789');
- 然后新建一个userDao接口和一个userDaoImp1实现类
Dao接口
package com.itheima.dao;
/**
* 该dao定义了对用户表的访问规则
*/
public interface userDao {
/**
* 这里简单就返回一个Boolean类型,成功或者失败即可;
*
* 但是开发的时候,登陆的方法,一旦成功,这里应该返回用户的个人信息
* @param username
* @param password
* @return true:登录成功; false:登录失败
*/
boolean login(String username, String password);
boolean login();
}
Dao实现类
package com.itheima.dao.imp1;
import com.itheima.dao.userDao;
public class userDaoImp1 implements userDao {
public boolean login(String username, String password) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs = null;
try {
//1.和数据库建立连接
conn = JDBCUtil.getConn();
//2.和数据库打交道,创建sql语句
String sql="select * from user where username=? and password=?";
ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, password);
//3.执行查询,获得结果集
rs = ps.executeQuery();
return rs.next();//这行代码等价于下面的if---else--
/*if(rs.next()){
return true;
}else{
return false;
}*/
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtil.release(conn,ps,rs);
}
return false;
}
/* 用来验证是否连接上数据库
*/
public boolean login() {
Connection conn=null;
try {
conn = JDBCUtil.getConn();
System.out.println("数据库状态:" + conn.isClosed());
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
集成JDBC环境
-
先把JDBC的jar复制到webContent/WEB-INF/lib;
mysql-connector-java-5.1.46-bin.jar
-
新建一个JDBC的工具类(之前学MySQL时写过),其中需要在src下面写一个 .propertiesde 的文件;
JDBCUtil.java代码:
package com.itheima.util;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.Properties;
import com.mysql.jdbc.PreparedStatement;
public class JDBCUtil {
static String driverClass=null;
static String url=null;
static String user=null;
static String password=null;
static{
try {
Properties properties=new Properties();
InputStream is =JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(is);
driverClass = properties.getProperty("driverClass");//注意:这里要加上引号
url = properties.getProperty("url");
user = properties.getProperty("user");
password= properties.getProperty("password");
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn(){
Connection conn=null;
try {
Class.forName(driverClass);
conn = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
public static void release(Connection conn, java.sql.PreparedStatement ps,
ResultSet rs) {
closeRs(rs);
closePs(ps);
closeConn(conn);
}
private static void closeConn(Connection conn) {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
conn=null;
}
}
private static void closePs(java.sql.PreparedStatement ps) {
try {
if (ps != null) {
ps.close();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
ps=null;
}
}
private static void closeRs(ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
rs=null;
}
}
}
jdbc.properties代码:
driverClass = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost/stusmanager
user = root
password = hxzgnpy123
登录显示
改进上面的login_servlet.java代码:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//提交的数据可能有中文,怎么处理
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
//1.获取客户端提交的信息
String username = request.getParameter("username");
String password = request.getParameter("password");
//System.out.println(username+":"+password);
//2.去访问dao,看看是否满足登录;去数据库访问有没有这个用户
//new userDaoImp1().login();
userDao udao = new userDaoImp1();
boolean isSuccess = udao.login(username, password);
//3.针对dao的结果,做出反应
if(isSuccess){
response.getWriter().write("登录成功");
}else{
response.getWriter().write("用户名或密码错误");
}
}
查询所有学生信息
-
在sqlyog中,新建学生信息表stus;
-
然后新建一个stuDao接口和一个stuDaoImp1实现类;
stuDao.java
package com.itheima.dao;
import java.util.List;
import com.itheima.domain.Student;
/**
*该dao定义了对学生表stus的访问规则
*/
public interface stuDao {
List<Student> findAll();
}
stuDaoImp1.java
package com.itheima.dao.imp1;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import com.itheima.dao.stuDao;
import com.itheima.domain.Student;
import com.itheima.util.JDBCUtil;
public class stuDaoImp1 implements stuDao {
public List<Student> findAll() {
List <Student> list=new ArrayList();
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs = null;
try {
//1.和数据库建立连接
conn = JDBCUtil.getConn();
String sql="select * from stus";
ps=conn.prepareStatement(sql);
rs=ps.executeQuery();
//数据多了,用对象;对象多了,用集合;
while(rs.next()){
Student student=new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
student.setGender(rs.getString("gender"));
student.setAdress(rs.getString("adress"));
list.add(student);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtil.release(conn,ps,rs);
}
return list;
}
}
- 改进上面的login_servlet.java代码:
if(isSuccess){
//response.getWriter().write("登录成功");
//1. 查询所有学生信息(注意:一个Dao对应一张表,需要再新建一个接口Dao和一个实现类)
stuDao sdao= new stuDaoImp1();
List<Student> list = sdao.findAll();
//2. 把查询到的消息保存在域中
HttpSession session = request.getSession();
session.setAttribute("li", list);
//3. 跳转页面 (方法一:重定向;方法二:请求转发)
response.sendRedirect("stu_list.jsp");
//request.getRequestDispatcher("stu_list.jsp").forward(request,response);
}else{
response.getWriter().write("用户名或密码错误");
}
页面显示学生信息
-
先把taglib的jar复制到webContent/WEB-INF/lib;
-
在stu_list.jsp中,取出域中的集合,然后用c标签去遍历集合。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>学生信息管理系统</title>
</head>
<body>
学生信息表:<br>
<table border="1" width="700">
<tr align="center">
<td>编号</td>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
<td>住址</td>
<td>操作</td>
</tr>
<c:forEach items="${li}" var="stu">
<tr align="center">
<td>${stu.id }</td>
<td>${stu.name }</td>
<td>${stu.age }</td>
<td>${stu.gender }</td>
<td>${stu.adress }</td>
<td><a href="#">删除</a> <a href="#">更新</a></td>
</tr>
</c:forEach>
</table>
</body>
</html>
总结:
- JSP
三大指令集
page
include
taglib
三个动作标签
<jsp:include>
<jsp:forward>
<jsp:param>
九个内置对象
//作用域
pageContext
request
session
application
page
config
exception
out
response
-
EL
${表达式 }
- 取4个作用域中的值
- 11个隐式对象(内置对象)
pageContext
pageScope
requestScope
sessionScope
applicationScope
header
headerValues
param
paramValues
initParam
cookie
- JSTL
使用1.1的版本,支持EL表达式;
拷贝jar包,通过taglib去引入标签库;
<c:set>
<c:if>
<c:forEach>