JavaWeb初探
Day 01
25/06/2020
JAVA反射
获取Class实例:
通过静态变量class获取,即直接通过类名获取
通过调用Class.forName(name)方法获取,传递类完整名称为参数
通过实例变量的getClass()方法获取
Class实例在JVM中是唯一的
1 | // example |
访问字段
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
1 | class Student{ |
Field对象的一些方法
getName()
:返回字段名称,例如,"name"
;getType()
:返回字段类型,也是一个Class
实例,例如,String.class
;getModifiers()
:返回字段的修饰符,它是一个int
,不同的bit表示不同的含义。
通过Field实例获取或修改字段的值
1 | // 还是上面Student的例子 |
获取方法
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
1 | class Student{ |
Method对象的一些方法:
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
invoke()方法,对Mehtod实例调用该方法,相当于调用Method对应的实例的方法,第一个参数为对象实例,后面跟可变参数,即该方法的参数列表
1 | m1.invoke(); // 相当于student.get() |
关于多态:
即在子类重写了父类方法时,反射机制又是如何呢?也同样遵循多态的原则。
1 | // example |
调用构造方法
通过反射创建实例:
newInstance()方法
,要求该构造方法为public无参数构造方法- 通过Constructor对象来实现能够调用任意的构造方法
对于构造Constructor对象的方法:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。
1 | Student s = Student.class.newInstance(); |
获取继承关系
获取父类Class实例:
getSuperClass()方法
1 | Class i = int.class; |
获取接口:
getInterfaces()方法
1 | Class i[] = int.class.getInterfaces(); // 因为存在多个接口 所以返回Class数组 |
两个Class对象判断是否为继承关系:
使用isAssignableFrom()方法
1 | Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number |
动态代理
可以在运行期动态创建某个interface
的实例。
JDK提供的动态创建接口对象的方式,就叫动态代理。
即可以实现在不编写实现类的情况下,创建接口的实例。利用Proxy.newProxyInstance()方法
在运行期动态创建一个interface
实例的方法如下:
- 定义一个
InvocationHandler
实例,它负责实现接口的方法调用; - 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
- 使用的
ClassLoader
,通常就是接口类的ClassLoader
; - 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的
InvocationHandler
实例。
- 使用的
- 将返回的
Object
强制转型为接口。
但实质其实就是JDK帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。
参考:廖雪峰博客-Java
Day 02
26/06/2020
XML
定义:XML全称为Extensible Markup Language,意思是可扩展的标记语言。XML语法上和HTML比较相似,但HTML中的元素是固定的,而XML的标签是可以由用户自定义的。
1 | <!--部分xml 例子--> |
XML语法
文档声明:
文档声明必须为
<?xml
开头,以?>
结束;文档声明必须从文档的0行0列位置开始;
文档声明只有三个属性:
versioin:指定XML文档版本。必须属性,因为我们不会选择1.1,只会选择1.0;
encoding:指定当前文档的编码。可选属性,默认值是utf-8;
standalone:指定文档独立性。可选属性,默认值为yes,表示当前文档是独立文档。如果为no表示当前文档不是独立的文档,会依赖外部文件。
1 | <?xml version="1.0" encoding="utf-8" standalone="no"?> |
元素
元素是XML文档中最重要的组成部分:
普通元素的结构:开始标签、元素体、结束标签,例如:
<hello>大家好</hello>
;元素体:元素体可以是元素,也可以是文本,例如:
<b><a>你好</a></b>
,其中<b>
元素的元素体是<a>
元素,而<a>
元素的元素体是文本;空元素:空元素只有开始标签,而没有结束标签,例如:
<c/>
,但元素必须自己闭合。
元素属性:
属性是元素的一部分,它必须出现在元素的开始标签中;
属性的定义格式:属性名=属性值,其中属性值必须使用单引或双引;
一个元素可以有0~N个属性,但一个元素中不能出现同名属性;
1 | <student number="1001"> |
转义
转义字符:例如<
,>
等等这类符号,需要使用相应的转义字符来代替。例如:<
用<
代替。
CDATA段:由于使用转义字符会大大降低XML文档可读性,使用CDATA段就不会有这种问题,格式如下:
1 | <a><![CDATA[<a>]]></a> |
需要注意的是,在CDATA段中不能包含”]]>”,即CDATA段的结束定界符。
处理指令
处理指令,简称PI(Processing instruction)。处理指令用来指挥解析器如何解析XML文档内容。
例如,在XML文档中可以使用xml-stylesheet指令,通知XML解析器,应用css文件显示xml文档内容。
放于文档声明下方
1 |
格式
格式良好的XML就是格式正确的XML文档,只有XML的格式是良好的,XML解释器才能解释它。下面是对格式良好XML文档的要求:
必须要有XML文档声明;
必须且仅能有一个根元素;
元素和属性的命名必须遵循XML要求:
XML命名区分大小写,例如
<a>
和<A>
是两上不同的元素;名称中可以包含:字母、数字、下划线、减号,但不能以数字、减号开头;
不能以xml开头,无论是大写还是小写都不可以,例如
<xml>
、<Xml>
、<XML>
都是错误的;不能包含空格,例如
<ab cd>
是错误的。
元素之间必须合理包含,例如:
<a><b>xxx</b></a>
是合理的,而<a><b>xxx</a></b>
就是错误的包含。
DTD
定义:DTD(Document Type Definition),文档类型定义,用来约束XML文档。或者可以把DTD理解为创建XML文档的结构!例如可以用DTD要求XML文档的根元素名为<students>
,<students>
中可以有1~N个<student>
,<student>
子元素为<name>
、<age>
和<sex>
,<student>
元素还有number属性。
1 | <!--例子--> |
分类
内部DTD:在XML文档内部嵌入DTD,只对当前XML文档有效;
外部DTD:独立的DTD文件,扩展名为.dtd;
本地DTD:DTD文件在本地,不在网络上。自己项目,或本公司内部使用的;
公共DTD:DTD文件在网络上,不在本地。都是大公司或组织发布的,共大家使用!
内部DTD
1 |
|
位置:内部DTD在文档声明下面,在根元素上面;
语法格式:放到
<!DOCTYPE 根元素名称[ ]>
之间;只对当前XML文档有效;
本地DTD
1 |
|
位置:本地硬盘上;
语法格式:直接定义元素或属性即可;
本地所有XML文档都可以引用这个dtd文件;
公共DTD
1 |
|
格式为:<!DOCTYPE 根元素 PUBLIC "DTD名称" "DTD网址">
DTD语法
定义元素
定义元素语法:<!ELEMENT 元素名 元素描述>
<!ELEMENT name (#PCDATA)>
,定义名为name的元素,内容为文本类型。<!ELEMENT student (name,age,sex)>
,定义名为student元素,内容依次为name、age、sex元素;<!ELEMENT student ANY>
,定义名为student元素,内容任意;<!ELEMENT student EMPTY>
,定义名为student元素,不能有内容,即空元素,注意空元素是可以有属性的。
子元素出现次数
可以使用*
、+
、?
来指定子元素出现的次数
*
:可以出现0~N次;+
:可以出现1~N次;?
:可以出现0~1次。
例如:<!ELEMENT student(name,age?,hobby*,grade+) >
,定义student元素,第一子元素为name,必须且仅能出现一次,age是可有可无的,hobby可以出现0N次,grade可以出现1N次。
枚举类型子元素
<!ELEMENT student (name | age | sex)>
,表示student子元素为name、age、sex其中之一,必须仅且能选择其一。
定义属性
定义属性的语法:
<!ATTLIST 元素名 属性名 属性类型 设置说明>
例如:<!ATTLIST student number CDATA #REQUIRED>
,给student元素定义属性number,类型为文本,这个默认是必须的。
属性设置说明:
#REQUIRED
:说明属性是必须的;#IMPLIED
:说明属性是可选的;默认值
:在不给出属性值时,使用默认值。
属性的类型:
CDATA
:文本类型;Enumerated
:枚举类型;ID
:ID类型,ID类型的属性用来标识元素的唯一性,即元素的ID属性值不能与其他元素的ID属性值相同;IDREF
:ID引用类型,用来指定另一个元素,与另一个元素建立关联关系,IDREF类型的属性值必须是另一个元素的ID。
1 | <!--例子--> |
定义实体
将多而长的字符串定义为用简单符号表示的实体,从而在使用时只用使用该符号就可以了,方便且简洁。
实体分为两种:一般实体和参数实体。
一般实体:在XML文档中使用;
- 定义一般实体:
<!ENTITY 实体名 "实体值">
,例如:<!ENTITY 大美女 "白冰">
; - 一般实体引用:
&实体名;
,例如<xxx>&大美女;</xxx>
。
- 定义一般实体:
参数实体:在DTD使用。
- 定义参数实体:
<!ENTITY % 实体名 "实体值">
,”%”与实体名之间的空格是必须的;例如:<!ENTITY % friend "student friend IDREF #IMPLIED">
- 参数实体引用:
%实体名;
;例如:<!ATTLIST %friend;>
- 定义参数实体:
参数实体是在DTD内部使用,而不是在XML中使用。在内部DTD中使用参数实体会有诸多限制
1 | <!--参数实体例子--> |
Schema
特点:
Schema是新的XML文档约束;
Schema要比DTD强大很多;
Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml。
1 | <!--students.xsd--> |
schema名称空间
名称空间定义:如果一个XML文档中使用多个Schema文件,而这些Schema文件中定义了相同名称的元素时就会出现名字冲突。这就像一个Java文件中使用了import java.util.*和import java.sql.*时,在使用Date类时,那么就不明确Date是哪个包下的Date了。
总之名称空间就是用来处理元素和属性的名称冲突问题,与Java中的包是同一用途。如果每个元素和属性都有自己的名称空间,那么就不会出现名字冲突问题,就像是每个类都有自己所在的包一样,那么类名就不会出现冲突。
目标名称空间
在XSD文件中为定义的元素指定名称,即指定目标名称空间。这需要给<xsd:schema>
元素添加targetNamespace
属性。
例如:<xsd:schema targetNamespace="http://www.itcast.cn/xml">
名称空间可以是任意字符串,但通常我们会使用公司的域名作为名称空间,这与Java中的包名使用域名的倒序是一样的!千万不要以为这个域名是真实的,它可以是不存在的域名。
如果每个公司发布的Schema都随意指定名称空间,如a、b之类的,那么很可能会出现名称空间的名字冲突,所以还是使用域名比较安全,因为域名是唯一的。
当使用了targetNamespace
指定目标名称空间后,那么当前XSD文件中定义的元素和属性就在这个名称空间之中了。
XML指定XSD文件
在XML文件中需要指定XSD约束文件,这需要使用在根元素中使用schemaLocation
属性来指定XSD文件的路径,以及目标名称空间。格式为:schemaLocation="目标名称空间 XSD文件路径"
例如:<students schemaLocation="http://www.itcast.cn/xml students.xsd">
schemaLocation
是用来指定XSD文件的路径,也就是说为当前XML文档指定约束文件。但它不只要指定XSD文件的位置,还要指定XSD文件的目标名称空间。
其中http://www.itcast.cn/xml
为目标名称空间,students.xsd
为XSD文件的位置,它们中间使用空白符(空格或换行)分隔。
也可以指定多个XSD文件,格式为:
schemaLocation="目标名称空间1 XSD文件路径1 目标名称空间2 XSD文件路径2"
下面是spring配置文件的例子,它一共指定两个XSD文件
1 |
|
定义名称空间
现在我们已经知道一个XML中可以指定多个XSD文件,例如上面Spring的配置文件中就指定了多个XSD文件,那么如果我在<beans>
元素中给出一个子元素<bean>
,你知道它是哪个名称空间中的么?显然是无法知道的。
所以只是使用schemaLocation
指定XSD是不够的,它只是导入了这个XSD及XSD的名称空间而已。schemaLocation
的作用就相当于Java中导入Jar包的作用!最终还是在Java文件中使用import来指定包名的。
xmlns
是用来指定名称空间前缀的,所谓前缀就是”简称”,例如中华人民共和国简称中国一样,然后我们在每个元素前面加上前缀,就可以处理名字冲突了。
格式为:xmln:前缀="名称空间"
注意,使用xmlns
指定的名称空间必须是在schemaLocation
中存在的名称空间。
1 | <beans |
默认名称空间
在一个XML文件中,可以指定一个名称空间没有前缀,那么在当前XML文档中没有前缀的元素就来自默认名称空间。
1 | <beans |
W3C的元素和属性
如果我们的XML文件中需要使用W3C提供的元素和属性,那么可以不在schemaLocation属性中指定XSD文件的位置,但一定要定义名称空间,例如:
1 | <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <!--指定名称空间--> |
上面定义了一个名称空间,前缀为xsi,名称空间为http://www.w3.org/2001/XMLSchema-instance
。这个名称空间无需在schemaLocation
中存在。
你可能已经发现了,schemaLocation
这个属性其实是w3c定义的属性,与元素一定,属性也需要指定”出处”,xsi:schemaLocation
中的xsi
就是名称空间前缀。也就是说,上面我们在没有指定xsi
名称空间时,就直接使用schemaLocation
是错误的。也就是在上面其他的例子中,我们是没有指明xsi
代表什么的,所以就是错误的,因为不存在xsi
这个前缀。
XML解析器
DOM and SAX 简介
定义:XML是保存数据的文件,XML中保存的数据也需要被程序读取然后使用。那么程序使用什么来读取XML文件中的数据呢?XML解析器!例如.properties文件的解析器是Properties类一样!
XML不只被Java语言使用,还被C++、C#、Javascript等等语言使用,所以解析XML不是一门语言的工作!
主流的XML解析有两种标准:DOM和SAX。它们是标准,是思想,不是真正的解析器,它们是跨语言的!!!
DOM(Document Object Model):W3C组织提供的解析XML文档的标准接口;
SAX(Simple API for XML):社区讨论的产物,是一种事实上的标准。
Apache的xerces组件实现了DOM和SAX,所以在我们在Java中解析XML需要使用xerces。所以我们称xerces是DOM、SAX解析器。
DOM解析原理
DOM要求解析器把整个XML文档装载到一个Document对象中。即使用DOM解析器解析XML文档的结果就是一个Document对象。
一个XML文档解析后对应一个Document对象,可以通过Document对象获取根元素,然后在通过根元素获取根元素的子元素…,这说明DOM解析方式保留了元素之间的结构关系!
优点:元素与元素之间的结构关系保留了下来;
缺点:如果XML文档过大,那么把整个XML文档装载进内存,可能会出现内存溢出的现象!
SAX解析原理
DOM解析后的结果是一个Document对象,而SAX解析没有结果!SAX要求在开始解析之前用户提供一个接口的实现对象,然后把接口实现对象传递给SAX解析器,然后在SAX解析器的过程中不断调用实现对象的方法。
DOM是解析时把数据放到了Document对象中,然后用户从Document中获取需要的数据;
SAX要求用户参与到解析过程中来,把想要做的事情写到接口实现对象中,然后SAX在解析过程中来调用接口实现对象的方法。
SAX解析器会在解析XML文档的过程中,在发生特定事件时,调用接口中特定的方法。例如在SAX解析到某个元素的开始标签时,输出元素名称!其中解析到开始标签就是特定的事件,而输出元素名称,就是接口中方法的实现。
1 | // 接口中方法 |
接口的实现由我们来完成,然后我们需要把接口实现类对象”交给”SAX解析器,然后让SAX开始解析。SAX会在特定事件发生时,调用接口中的方法,完成我们交给它的任务。
优点:适合解析大XML文件(内存空间占用小),因为是解析一行处理一行,处理完了就不需要在保留数据了;
缺点:因为是解析一行处理一行,解析之后数据就丢失了,所以元素与元素之间的结构关系没有保留下来。
JAXP
JAXP(Java API for XML Processing)是由Java提供的,JAXP是对所有像xerces一样的解析的提供统一接口的API。
当我们使用JAXP完成解析工作时,还需要为JAXP指定xerces或其他解析器,当需要更换解析器时,无需修改代码,只需要修改配置即可。
JAXP不是解析器,但使用它可以方便的切换解析器。所以在我们的程序中只会使用JAXP,而不会直接使用Xeces。
JDOM和DOM4j
DOM和SAX是跨语言的XML解析准备,在Java中使用并不方便。而JDOM和DOM4j是专门为Java语言提供的解析工具!使用起来很方便,所以真实开发中使用JDOM或DOM4J比较多。
又因为DOM4J与JDOM比较结果为DOM4j完胜,所以这里只会对DOM4j介绍,而不会介绍JDOM。
DOM 和 SAX 解析
DOM中的核心概念就是节点,在XML文档中的元素、属性、文本、处理指令,在DOM中都是节点!
JAXP-DOM
使用DOM解析XML的目标就是获取到Document对象,然后在从Document中获取到需要的数据。Document对象就是XML文档在内存中的样子。
1 | // 获取Document对象 三步 |
1 | // 遍历Document |
JAXP-SAX
使用SAX解析XML文档需要先给出DefaultHandler的子类,重写其中的方法。然后在使用SAX开始解析时把DefaultHandler子类对象传递给SAX解析器。
1 | class MyContentHandler extends DefaultHandler { |
使用SAX解析首先需要获取工厂,再通过工厂获取解析器对象,然后使用解析对象完成解析工作:
1 | SAXParserFactory factory = SAXParserFactory.newInstance(); |
DOM4j 解析
DOM4J是针对Java开发人员专门提供的XML文档解析规范,它不同与DOM,但与DOM相似。DOM4J针对Java开发人员而设计,所以对于Java开发人员来说,使用DOM4J要比使用DOM更加方便。
在DOM4J中,也有Node、Document、Element等接口,结构上与DOM中的接口比较相似。但它们是不同的类:
Node
Attribute:表示属性节点;
Branch:表示可以包含子元素的节点:
- Document:表示整个文档;
- Element:表示元素节点;
CharacterData:表示文本节点:
- Text:表示文本内容;
- CDATA:表示CDATA段内容;
- Comment:表示注释内容。
再次强调,DOM和DOM4J是不同的,DOM中的Document是org.w3c.Document,而DOM4J中的Document是org.dom4j.Document,它们是不同的类,其他Node、Element也是一样。
读取、保存、创建Document
使用dom4j需要导入:
dom4j.jar
jaxen.jar
1 | // 读取 |
1 | // 保存xml文档 |
1 | // 创建Document |
遍历Document
1 | public void fun1() throws DocumentException { |
添加student元素
1 | SAXReader reader = new SAXReader(); |
查询元素
1 | SAXReader reader = new SAXReader(); |
修改元素
1 | SAXReader reader = new SAXReader(); |
删除学生元素
1 | SAXReader reader = new SAXReader(); |
DOM4j API 介绍
Node方法:
String asXML():把当前节点转换成字符串,如果当前Node是Document,那么就会把整个XML文档返回;
String getName():获取当前节点名字;Document的名字就是绑定的XML文档的路径;Element的名字就是元素名称;Attribute的名字就是属性名;
Document getDocument():返回当前节点所在的Document对象;
short getNodeType():获取当前节点的类型;
String getNodeTypeName():获取当前节点的类型名称,例如当前节点是Document的话,那么该方法返回Document;
String getStringValue():获取当前节点的子孙节点中所有文本内容连接成的字符串;
String getText():获取当前节点的文本内容。如果当前节点是Text等文本节点,那么本方法返回文本内容;例如当前节点是Element,那么当前节点的内容不是子元素,而是纯文本内容,那么返回文本内容,否则返回空字符串;
void setDocument(Document doc):给当前节点设置文档元素;
void setParent(Element parent):给当前节点设置父元素;
void setText(String text):给当前节点设置文本内容;
Branch方法:
- void add(Element e):添加子元素;
- void add(Node node):添加子节点;
- void add(Comment comment):添加注释;
- Element addElement(String eleName):通过名字添加子元素,返回值为子元素对象;
- void clearContent():清空所有子内容;
- List content():获取所有子内容,与获取所有子元素的区别是,
liSi 元素没有子元素,但有子内容; - Element elementById(String id):如果元素有名为”ID”的属性,那么可以使用这个方法来查找;
- int indexOf(Node node):查找子节点在子节点列表中的下标位置;
- Node node(int index):通过下标获取子节点;
- int nodeCount():获取子节点的个数;
- Iterator nodeIterator():获取子节点列表的迭代器对象;
- boolean remove(Node node):移除指定子节点;
- boolean remove(Commont commont):移除指定注释;
- boolean remove(Element e):移除指定子元素;
- void setContent(List content) :设置子节点内容;
Document方法:
- Element getRootElement():获取根元素;
- void setRootElement():设置根元素;
- String getXmlEncoding():获取XML文档的编码;
- void setXmlEncoding():设置XML文档的编码;
Element方法:
- void add(Attribute attr):添加属性节点;
- void add(CDATA cdata):添加CDATA段节点;
- void add(Text Text):添加Text节点;
- Element addAttribute(String name, String value):添加属性,返回值为当前元素本身;
- Element addCDATA(String cdata):添加CDATA段节点;
- Element addComment(String comment):添加属性节点;
- Element addText(String text):添加Text节点;
- void appendAttributes(Element e):把参数元素e的所有属性添加到当前元素中;
- Attribute attribute(int index):获取指定下标位置上的属性对象;
- Attribute attribute(String name):通过指定属性名称获取属性对象;
- int attributeCount():获取属性个数;
- Iterator attributeIterator():获取当前元素属性集合的迭代器;
- List attributes():获取当前元素的属性集合;
- String attributeValue(String name):获取当前元素指定名称的属性值;
- Element createCopy():clone当前元素对象,但不会copy父元素。也就是说新元素没有父元素,但有子元素;
- Element element(String name):获取当前元素第一个名称为name的子元素;
- Iterator elementIterator():获取当前元素的子元素集合的迭代器;
- Iterator elementIterator(String name):获取当前元素中指定名称的子元素集合的迭代器;
- List elements():获取当前元素子元素集合;
- List elements(String name):获取当前元素指定名称的子元素集合;
- String elementText(String name):获取当前元素指定名称的第一个元素文件内容;
- String elementTextTrime(String name):同上,只是去除了无用空白;
- boolean isTextOnly():当前元素是否为纯文本内容元素;
- boolean remove(Attribute attr):移除属性;
- boolean remove(CDATA cdata):移除CDATA;
- boolean remove(Text text):移除Text。
DocumentHelper静态方法介绍:
- static Document createDocument():创建Dcoument对象;
- static Element createElement(String name):创建指定名称的元素对象;
- static Attribute createAttrbute(Element owner, String name, String value):创建属性对象;
- static Text createText(String text):创建属性对象;
- static Document parseText(String text):通过给定的字符串生成Document对象;
Day 03
27/06/2020
C/S
C/S结构即客户端/服务器(Client/Server),例如QQ;
需要编写服务器端程序,以及客户端程序,例如我们安装的就是QQ的客户端程序;
缺点:软件更新时需要同时更新客户端和服务器端两端,比较麻烦;
优点:安全性比较好。
B/S
- B/S结构即浏览器/服务器(Browser/Server);
- 优点:只需要编写服务器端程序;
- 缺点:安全性较差。
Tomcat
Tomcat的目录结构
bin:该目录下存放的是二进制可执行文件,如果是安装版,那么这个目录下会有两个exe文件:tomcat6.exe、tomcat6w.exe,前者是在控制台下启动Tomcat,后者是弹出UGI窗口启动Tomcat;如果是解压版,那么会有startup.bat和shutdown.bat文件,startup.bat用来启动Tomcat,但需要先配置JAVA_HOME环境变量才能启动,shutdawn.bat用来停止Tomcat;
conf:这是一个非常非常重要的目录,这个目录下有四个最为重要的文件:
- server.xml:配置整个服务器信息。例如修改端口号,添加虚拟主机等;下面会详细介绍这个文件;
- tomcatusers.xml:存储tomcat用户的文件,这里保存的是tomcat的用户名及密码,以及用户的角色信息。可以按着该文件中的注释信息添加tomcat用户,然后就可以在Tomcat主页中进入Tomcat Manager页面了;
- web.xml:部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。这些MIME类型是客户端与服务器之间说明文档类型的,如用户请求一个html网页,那么服务器还会告诉客户端浏览器响应的文档是text/html类型的,这就是一个MIME类型。客户端浏览器通过这个MIME类型就知道如何处理它了。当然是在浏览器中显示这个html文件了。但如果服务器响应的是一个exe文件,那么浏览器就不可能显示它,而是应该弹出下载窗口才对。MIME就是用来说明文档的内容是什么类型的!
- context.xml:对所有应用的统一配置,通常我们不会去配置它。
lib:Tomcat的类库,里面是一大堆jar文件。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中,当然也可以把应用依赖的jar文件放到这个目录中,这个目录中的jar所有项目都可以共享之,但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的Jar包了,所以建议只把Tomcat需要的Jar包放到这个目录下;
logs:这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中。
temp:存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除!
webapps:存放web项目的目录,其中每个文件夹都是一个项目;如果这个目录下已经存在了目录,那么都是tomcat自带的。项目。其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。
http://localhost:8080/examples
,进入示例项目。其中examples就是项目名,即文件夹的名字。work:运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。
LICENSE:许可证。
NOTICE:说明文件。
Web 应用
静态应用
- 在webapps下创建一个hello目录;
- 在webapps\hello\下创建index.html;
- 启动tomcat;
- 打开浏览器访问
http://localhost:8080/hello/index.html
1 | <!--index.html--> |
动态应用
- 在webapps下创建hello1目录;
- 在webapps\hello1\下创建WEB-INF目录;
- 在webapps\hello1\WEB-INF\下创建web.xml;
- 在webapps\hello1\下创建index.html。
- 打开浏览器访问
http://localhost:8080/hello/index.html
1 |
|
完整的Web应用还需要在WEB-INF目录下创建:
- classes;
- lib目录;
webapps
|- hello
|-index.html
|-WEB-INF
|-web.xml
|-classes
|-lib
- hello:应用目录,hello就是应用的名称;
- index.html:应用资源。应用下可以有多个资源,例如css、js、html、jsp等,也可以把资源放到文件夹中,例如:hello\html\index.html,这时访问URL为:http://localhost:8080/hello/html/index.html;
- WEB-INF:这个目录名称必须是大写,这个目录下的东西是无法通过浏览器直接访问的,也就是说放到这里的东西是安全的;
- web.xml:应用程序的部署描述符文件,可以在该文件中对应用进行配置,例如配置应用的首页
1 | <welcome-file-list> |
classes:存放class文件的目录;
lib:存放jar包的目录;
配置外部应用
也可以把应用放到Tomcat之外,这就是外部应用了。例如我们把上面写的hello应用从webapps目录中剪切到C盘下,即C:/hello。现在hello这个Web应用已经不在Tomcat中了,这时我们需要在tomcat中配置外部应用的位置,配置的方式一共有两种:
- conf/server.xml:打开server.xml文件,找到
<Host>
元素,在其中添加<Context>
元素,代码如下:
1 | <Host name="localhost" appBse="webapps" |
1)path:指定当前应用的名称
2)docBase:指定应用的物理位置
3)浏览器访问路径:http://localhost:8080/itcast_hello/index.html
- conf/catalana/localhost:在该目录下创建itcast_hello.xml文件,在该文件中编写
<Context>
元素,代码如下:
1 | <Context docBase="C:/hello/"/> |
1)文件名:指定当前应用的名称;
2)docBase:指定应用的物理位置;
3)浏览器访问路径:http://localhost:8080/itcast_hello/index.html
server.xml
1 | <Server> |
<Server>
:根元素,表示整个服务器的配置信息;<Service>
:<Server>
的子元素,在<Server>
中只能有一个<Service>
元素,它表示服务;<Connector>
:<Service>
的子元素,在<Service>
中可以有N个<Connector>
元素,它表示连接。<Engine>
:<Service>
的子元素,在<Service>
中只能有一<Engine>
元素,该元素表示引擎,它是<Service>
组件的核心。<Host>
:<Engine>
的子元素,在<Engine>
中可以有N个<Host>
元素,每个<Host>
元素表示一个虚拟主机。所谓虚拟主机就像是真的主机一样,每个主机都有自己的主机名和项目目录。例如<Host name="localhost" appBase="webapps">
表示主机名为localhost,这个主机的项目存放在webapps目录中。访问这个项目下的主机时,需要使用localhost主机名,项目都存放在webapps目录下。<Context>
:<Host>
元素的子元素,在<Host>
中可以有N个<Context>
元素,每个<Context>
元素表示一个应用。如果应用在<Host>
的appBase指定的目录下,那么可以不配置<Context>
元素,如果是外部应用,那么就必须配置<Context>
。如果要为应用指定资源,也需要配置<Context>
元素。
我们可以把<Server>
看作是一个大酒店:
<Service>
:酒店的服务部门;<Connector>
:服务员;<Engine>
:后厨;<Host>
:后厨中的一个区,例如川菜区是一个<Host>
、粤菜区是一个<Host>
;<Context>
:后厨的一个厨师。
用户发出一个请求:http://localhost:8080/hello/index.jsp
。发现是http/1.1协议,而且还是8080端口,所以就交给了处理这一请求的”服务员(处理HTTP请求的<Connector>
)”,”服务员”再把请求交给了”后厨(<Engine>
)”,因为请求是要一盘水煮鱼,所以由”川菜区(<Host>
)”负责,因为”大老王师傅<Context>
“做水煮鱼最地道,所以由它完成。
<Connector>
:关心请求中的http、和8080;<Host>
:关心localhost;<Context>
:关心hello
映射虚拟主机
我们的目标是,在浏览器中输出:http://www.itcast.cn
就可以访问我们的项目。
完成这一目标,我们需要做三件事:
修改端口号为80,这一点应该没有问题吧;
在本机上可以解析域名为127.0.0.1,这需要修改C:\WINDOWS\system32\drivers\etc\hosts文件,添加对
http://www.itcast.cn
和127.0.01的绑定关系;在server.xml文件中添加一个
<Host>
(主机)。
1 | <Host name="www.itcast.cn" appBase="F:/itcastapps" unpackWARs="true" autoDeploy="true"> |
name="www.itcast.cn"
:指定虚拟主机名为www.itcast.cn
;- appBase=”F:/itcastapps”:指定当前虚拟主机的应用程序存放目录为F:/itcastapps。
- 在itcastapps目录下创建名为ROOT的应用,因为一个主机只可以有一个名为ROOT的应用,名为ROOT的应用在浏览器中访问是可以不给出应用名称。
HTTP 协议
HTTP(hypertext transport protocol),即超文本传输协议。这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。
HTTP就是一个通信规则,通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。其实我们要学习的就是这个两个格式!客户端发送给服务器的格式叫”请求协议”;服务器发送给客户端的格式叫”响应协议”。
URL 和 URI
URL:统一资源定位符,就是一个网址,例如:http://www.itcast.cn
就是一个URL。/hello/index.html
也是一个URL,URL必须是一个真实存在的网址。
URI:统一资源标识符:比URI包含了URL,URI的范围更加宽泛,URI可以是一个不存在的网址。在网络上用来标签资源的都是URI,例如zhangSan@163.com
也是URI。
请求协议
请求协议的格式如下:
1 | 请求首行; |
浏览器发送给服务器的内容就这个格式的,如果不是这个格式服务器将无法解读!在HTTP协议中,请求有很多请求方法,其中最为常用的就是GET和POST。不同的请求方法之间的区别,后面会一点一点的介绍。
响应协议
响应协议的格式如下:
1 | 响应首行; |
响应内容是由服务器发送给浏览器的内容,浏览器会根据响应内容来显示。
Day 04
28/06/2020
Servlet
Servlet是JavaWeb的三大组件之一,它属于动态资源。Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:
- 接收请求数据;
- 处理请求;
- 完成响应。
例如客户端发出登录请求,或者输出注册请求,这些请求都应该由Servlet来完成处理!Servlet需要我们自己来编写,每个Servlet必须实现javax.servlet.Servlet
接口。
实现Servlet的方式
实现Servlet有三种方式:
- 实现javax.servlet.Servlet接口;
- 继承javax.servlet.GenericServlet类;
- 继承javax.servlet.http.HttpServlet类;
通常我们会去继承HttpServlet类来完成我们的Servlet,但学习Servlet还要从javax.servlet.Servlet接口开始学习。
1 | // Servlet.java |
创建应用
开始第一个Servlet应用!首先在webapps目录下创建helloservlet目录,它就是我们的应用目录了,然后在helloservlet目录中创建准备JavaWeb应用所需内容:
创建/helloservlet/WEB-INF目录;
创建/helloservlet/WEB-INF/classes目录;
创建/helloservlet/WEB-INF/lib目录;
创建/helloservlet/WEB-INF/web.xml文件;
接下来我们开始准备完成Servlet,完成Servlet需要分为两步:
编写Servlet类;
在web.xml文件中配置Servlet;
1 | // HelloServlet.java |
我们暂时忽略Servlet中其他四个方法,只关心service()方法,因为它是用来处理请求的方法。我们在该方法内给出一条输出语句!
1 | <!--web.xml 重要 背--> |
在web.xml中配置Servlet的目的其实只有一个,就是把访问路径与一个Servlet绑定到一起,上面配置是把访问路径:”/helloworld”与”cn.itcast.servlet.HelloServlet”绑定到一起。
<servlet>
:指定HelloServlet这个Servlet的名称为hello;<servlet-mapping>
:指定/helloworld访问路径所以访问的Servlet名为hello。<servlet>
和<servlet-mapping>
通过<servlet-name>
这个元素关联在一起了!
接下来,我们编译HelloServlet,注意,编译HelloServlet时需要导入servlet-api.jar,因为Servlet.class等类都在servlet-api.jar中。
javac -classpath F:/tomcat6/lib/servlet-api.jar -d . HelloServlet.java
然后把HelloServlet.class放到/helloworld/WEB-INF/classes/目录下,然后启动Tomcat,在浏览器中访问:http://localhost:8080/helloservlet/helloworld
即可在控制台上看到输出!
- /helloservlet/WEB-INF/classes/cn/itcast/servlet/HelloServlet.class;
Servlet接口
Servlet 的生命周期
所谓xxx的生命周期,就是说xxx的出生、服务,以及死亡。Servlet生命周期也是如此!与Servlet的生命周期相关的方法有:
服务器会在Servlet第一次被访问时创建Servlet,或者是在服务器启动时创建Servlet。如果服务器启动时就创建Servlet,那么还需要在web.xml文件中配置。也就是说默认情况下,Servlet是在第一次被访问时由服务器创建的。
而且一个Servlet类型,服务器只创建一个实例对象,例如在我们首次访问http://localhost:8080/helloservlet/helloworld
时,服务器通过”/helloworld”找到了绑定的Servlet名称为cn.itcast.servlet.HelloServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器才会通过反射来创建HelloServlet的实例。当我们再次访问http://localhost:8080/helloservlet/helloworld
时,服务器就不会再次创建HelloServlet实例了,而是直接使用上次创建的实例。
在Servlet被创建后,服务器会马上调用Servlet的void init(ServletConfig)
方法。请记住, Servlet出生后马上就会调用init()
方法,而且一个Servlet的一生。这个方法只会被调用一次。这好比小孩子出生后马上就要去剪脐带一样,而且剪脐带一生只有一次。
我们可以把一些对Servlet的初始化工作放到init方法中!
service
当服务器每次接收到请求时,都会去调用Servlet的service()
方法来处理请求。服务器接收到一次请求,就会调用service()
方法一次,所以service()
方法是会被调用多次的。正因为如此,所以我们才需要把处理请求的代码写到service()
方法中!
destroy
Servlet是不会轻易离去的,通常都是在服务器关闭时Servlet才会离去!在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()
方法,我们可以把Servlet的临终遗言放到destroy()
方法中,例如对某些资源的释放等代码放到destroy()
方法中。
Servlet 接口相关类型
在Servlet接口中还存在三个我们不熟悉的类型:
ServletRequest:
service()
方法的参数,它表示请求对象,它封装了所有与请求相关的数据,它是由服务器创建的;ServletResponse:
service()
方法的参数,它表示响应对象,在service()
方法中完成对客户端的响应需要使用这个对象;ServletConfig:
init()
方法的参数,它表示Servlet配置对象,它对应Servlet的配置信息,那对应web.xml文件中的<servlet>
元素。
ServletRequest和ServletResponse
ServletRequest和ServletResponse是Servlet#service()
方法的两个参数,一个是请求对象,一个是响应对象,可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。你以后会发现,这两个对象就像是一对恩爱的夫妻,永远不分离,总是成对出现。
ServletRequest和ServletResponse的实例由服务器创建,然后传递给service()方法。如果在service() 方法中希望使用HTTP相关的功能,那么可以把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse。这也说明我们经常需要在service()方法中对ServletRequest和ServletResponse进行强转,这是很心烦的事情。不过后面会有一个类来帮我们解决这一问题的。
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.itcast.cn")
,表示3秒后自动刷新到http://www.itcast.cn
;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在web.xml文件中的配置名,那么可以使用servletConfig.getServletName()方法获取!
1 | <servlet> |
ServletConfig对象是由服务器创建的,然后传递给Servlet的init()方法,你可以在init()方法中使用它!
String getServletName():获取Servlet在web.xml文件中的配置名称,即
<servlet-name>
指定的名称;ServletContext getServletContext():用来获取ServletContext对象,ServletContext会在后面讲解;
String getInitParameter(String name):用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值;
Enumeration getInitParameterNames():用来获取在web.xml中配置的所有初始化参数名称;
在<servlet>
元素中还可以配置初始化参数:
1 | <servlet> |
在OneServlet中,可以使用ServletConfig对象的getInitParameter()方法来获取初始化参数,例如:
String value1 = servletConfig.getInitParameter("paramName1");//获取到paramValue1
GenericServlet
GenericServlet是Servlet接口的实现类,我们可以通过继承GenericServlet来编写自己的Servlet。下面是GenericServlet类的源代码:
1 | // GenericServlet.java |
在GenericServlet中,定义了一个ServletConfig config实例变量,并在init(ServletConfig)方法中把参数ServletConfig赋给了实例变量。然后在该类的很多方法中使用了实例变量config。
如果子类覆盖了GenericServlet的init(StringConfig)方法,那么this.config=config这一条语句就会被覆盖了,也就是说GenericServlet的实例变量config的值为null,那么所有依赖config的方法都不能使用了。如果真的希望完成一些初始化操作,那么去覆盖GenericServlet提供的init()方法,它是没有参数的init()方法,它会在init(ServletConfig)方法中被调用。
GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getServletContext()等ServletConfig的方法。
HttpServlet
HttpServlet类是GenericServlet的子类,它提供了对HTTP请求的特殊支持,所以通常我们都会通过继承HttpServlet来完成自定义的Servlet。
HttpServlet类中提供了service(HttpServletRequest,HttpServletResponse)方法,这个方法是HttpServlet自己的方法,不是从Servlet继承来的。在HttpServlet的service(ServletRequest,ServletResponse)方法中会把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse,然后调用service(HttpServletRequest,HttpServletResponse)方法,这说明子类可以去覆盖service(HttpServletRequest,HttpServletResponse)方法即可,这就不用自己去强转请求和响应对象了。
其实子类也不用去覆盖service(HttpServletRequest,HttpServletResponse)方法,因为HttpServlet还要做另一步简化操作,下面会介绍。
1 | // HttpServlet.java |
doGet()和doPost()
在HttpServlet的service(HttpServletRequest,HttpServletResponse)方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法,这说明我们在子类中去覆盖doGet()或doPost()方法即可。
Servlet 细节
Servlet与线程安全
因为一个类型的Servlet只有一个实例对象,那么就有可能会现时出一个Servlet同时处理多个请求,那么Servlet是否为线程安全的呢?答案是:”不是线程安全的”。这说明Servlet的工作效率很高,但也存在线程安全问题!
所以我们不应该在Servlet中随意创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。
让服务器在启动时就创建Servlet
默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。
1 | <servlet> |
在<servlet>
元素中配置<load-on-startup>
元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>
元素的值必须是大于等于0的整数,它的使用是服务器启动时创建Servlet的顺序。上例中,根据<load-on-startup>
的值可以得知服务器创建Servlet的顺序为Hello1Servlet、Hello2Servlet、Hello3Servlet。
<url-pattern>
<url-pattern>
是<servlet-mapping>
的子元素,用来指定Servlet的访问路径,即URL。它必须是以”/“开头!
1)可以在<servlet-mapping>
中给出多个<url-pattern>
,例如:
1 | <servlet-mapping> |
那么这说明一个Servlet绑定了两个URL,无论访问/AServlet还是/BServlet,访问的都是AServlet。
2)还可以在<url-pattern>
中使用通配符,所谓通配符就是星号”*“,星号可以匹配任何URL前缀或后缀,使用通配符可以命名一个Servlet绑定一组URL,例如:
<url-pattern>/servlet/*<url-patter>
:/servlet/a、/servlet/b,都匹配/servlet/*;<url-pattern>*.do</url-pattern>
:/abc/def/ghi.do、/a.do,都匹配*.do;<url-pattern>/*<url-pattern>
:匹配所有URL;
请注意,通配符要么为前缀,要么为后缀,不能出现在URL中间位置,也不能只有通配符。例如:/*.do
就是错误的,因为星号出现在URL的中间位置上了。.也是不对的,因为一个URL中最多只能出现一个通配符。
注意,通配符是一种模糊匹配URL的方式,如果存在更具体的<url-pattern>
,那么访问路径会去匹配具体的<url-pattern>
。例如:
1 | <servlet> |
当访问路径为http://localhost:8080/hello/servlet/hello1
时,因为访问路径即匹配hello1的<url-pattern>
,又匹配hello2的<url-pattern>
,但因为hello1的<url-pattern>
中没有通配符,所以优先匹配,即设置hello1。
web.xml文件的继承(了解)
每个完整的JavaWeb应用中都需要有web.xml,但我们不知道所有的web.xml文件都有一个共同的父文件,它在Tomcat的conf/web.xml路径。
ServletContext
服务器会为每个应用创建一个ServletContext对象:
ServletContext对象的创建是在服务器启动时完成的;
ServletContext对象的销毁是在服务器关闭时完成的。
ServletContext对象的作用是在整个Web应用的动态资源之间共享数据!例如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值,这就是共享数据了。
获取ServletContext
在Servlet中获取ServletContext对象:
在void init(ServletConfig config)中:
ServletContext context = config.getServletContext();
,ServletConfig类的getServletContext()方法可以用来获取ServletContext对象;在GenericeServlet或HttpServlet中获取ServletContext对象:
GenericServlet类有getServletContext()方法,所以可以直接使用this.getServletContext()来获取;
1 | public class MyServlet implements Servlet { |
域对象的功能
ServletContext是JavaWeb四大域对象之一:
PageContext;
ServletRequest;
HttpSession;
ServletContext;
所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据,下面是ServletContext对象用来操作数据的方法:
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“xxx”, “XXX”),在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
Object getAttribute(String name):用来获取ServletContext中的数据,当前在获取之前需要先去存储才行,例如:String value = (String)servletContext.getAttribute(“xxx”);,获取名为xxx的域属性;
void removeAttribute(String name):用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
Enumeration getAttributeNames():获取所有域属性的名称;
获取应用初始化参数
还可以使用ServletContext来获取在web.xml文件中配置的应用初始化参数!注意,应用初始化参数与Servlet初始化参数不同:
1 | <web-app ...> |
1 | ServletContext context = this.getServletContext(); |
获取资源相关方法
获取真实路径
还可以使用ServletContext对象来获取Web应用下的资源,例如在hello应用的根目录下创建a.txt文件,现在想在Servlet中获取这个资源,就可以使用ServletContext来获取。
获取a.txt的真实路径:String realPath = servletContext.getRealPath(“/a.txt”),realPath的值为a.txt文件的绝对路径:F:\tomcat6\webapps\hello\a.txt;
获取b.txt的真实路径:String realPath = servletContext.getRealPath(“/WEB-INF/b.txt”);
获取资源流
不只可以获取资源的路径,还可以通过ServletContext获取资源流,即把资源以输入流的方式获取:
获取a.txt资源流:InputStream in = servletContext.getResourceAsStream(“/a.txt”);
获取b.txt资源流:InputStream in = servletContext.getResourceAsStream(“/WEB-INF/b.txt”);
获取指定目录下所有资源路径
还可以使用ServletContext获取指定目录下所有资源路径,例如获取/WEB-INF下所有资源的路径:
1 | Set set = context.getResourcePaths("/WEB-INF"); |
访问量统计
1 | ServletContext application = this.getServletContext(); // 获取ServletContext对象 |
获取类路径资源
这里要讲的是获取类路径下的资源,对于JavaWeb应用而言,就是获取classes目录下的资源。
1 | InputStream in = this.getClass().getResourceAsStream("/xxx.txt"); |
Class类的getResourceAsStream(String path):
- 路径以”/“开头,相对classes路径;
- 路径不以”/“开头,相对当前class文件所有路径,例如在cn.itcast.servlet.MyServlet中执行,那么相对/classes/cn/itcast/servlet/路径;
ClassLoader类的getResourceAsStream(String path):
- 相对classes路径;
Day 05
29/06/2020
response
response是Servlet.service方法的一个参数,类型为javax.servlet.http.HttpServletResponse。在客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。
response对象的功能分为以下四种:
- 设置响应头信息;
- 发送状态码;
- 设置响应正文;
- 重定向;
响应正文
response是响应对象,向客户端输出响应正文(响应体)可以使用response的响应流,repsonse一共提供了两个响应流对象:
PrintWriter out = response.getWriter():获取字符流;
ServletOutputStream out = response.getOutputStream():获取字节流;
当然,如果响应正文内容为字符,那么使用response.getWriter(),如果响应内容是字节,例如下载时,那么可以使用response.getOutputStream()。
注意,在一个请求中,不能同时使用这两个流!也就是说,要么你使用repsonse.getWriter(),要么使用response.getOutputStream(),但不能同时使用这两个流。不然会抛出IllegalStateException
异常。
字符流
- 字符编码
在使用response.getWriter()时需要注意默认字符编码为ISO-8859-1,如果希望设置字符流的字符编码为utf-8,可以使用response.setCharaceterEncoding(“utf-8”)来设置。这样可以保证输出给客户端的字符都是使用UTF-8编码的!
但客户端浏览器并不知道响应数据是什么编码的!如果希望通知客户端使用UTF-8来解读响应数据,那么还是使用response.setContentType(“text/html;charset=utf-8”)方法比较好,因为这个方法不只会调用response.setCharaceterEncoding(“utf-8”),还会设置content-type响应头,客户端浏览器会使用content-type头来解读响应数据。
- 缓冲区
response.getWriter()是PrintWriter类型,所以它有缓冲区,缓冲区的默认大小为8KB。也就是说,在响应数据没有输出8KB之前,数据都是存放在缓冲区中,而不会立刻发送到客户端。当Servlet执行结束后,服务器才会去刷新流,使缓冲区中的数据发送到客户端。
如果希望响应数据马上发送给客户端:
- 向流中写入大于8KB的数据;
- 调用response.flushBuffer()方法来手动刷新缓冲区;
设置响应头
可以使用response对象的setHeader()方法来设置响应头!使用该方法设置的响应头最终会发送给客户端浏览器!
response.setHeader(“content-type”, “text/html;charset=utf-8”):设置content-type响应头,该头的作用是告诉浏览器响应内容为html类型,编码为utf-8。而且同时会设置response的字符流编码为utf-8,即response.setCharaceterEncoding(“utf-8”);
response.setHeader("Refresh","5; URL=http://www.itcast.cn")
:5秒后自动跳转到传智主页。
设置状态码及其他方法
response.setContentType(“text/html;charset=utf-8”):等同与调用response.setHeader(“content-type”, “text/html;charset=utf-8”);
response.setCharacterEncoding(“utf-8”):设置字符响应流的字符编码为utf-8;
response.setStatus(200):设置状态码;
response.sendError(404, “您要查找的资源不存在”):当发送错误状态码时,Tomcat会跳转到固定的错误页面去,但可以显示错误信息。
重定向
当你访问http://www.sun.com
时,你会发现浏览器地址栏中的URL会变成http://www.oracle.com/us/sun/index.html
,这就是重定向了。
重定向是服务器通知浏览器去访问另一个地址,即再发出另一个请求。
响应码为200表示响应成功,而响应码为302表示重定向。所以完成重定向的第一步就是设置响应码为302。
因为重定向是通知浏览器再第二个请求,所以浏览器需要知道第二个请求的URL,所以完成重定向的第二步是设置Location头,指定第二个请求的URL地址。
1 | public class AServlet extends HttpServlet { |
上面代码的作用是:当访问AServlet后,会通知浏览器重定向到传智主页。客户端浏览器解析到响应码为302后,就知道服务器让它重定向,所以它会马上获取响应头Location,然后发出第二个请求。
更便捷的重定向
1 | public class AServlet extends HttpServlet { |
response.sendRedirect()方法会设置响应头为302,以设置Location响应头。
如果要重定向的URL是在同一个服务器内,那么可以使用相对路径,例如:
1 | public class AServlet extends HttpServlet { |
重定向的URL地址为:http://localhost:8080/hello/BServlet
小结
- 重定向是两次请求;
- 重定向的URL可以是其他应用,不局限于当前应用;
- 重定向的响应头为302,并且必须要有Location响应头;
- 重定向就不要再使用response.getWriter()或response.getOutputStream()输出数据,不然可能会出现异常;
request
request是Servlet.service()方法的一个参数,类型为javax.servlet.http.HttpServletRequest。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给service()方法,这说明在service()方法中可以通过request对象来获取请求数据。
request的功能可以分为以下几种:
封装了请求头数据;
封装了请求正文数据,如果是GET请求,那么就没有正文;
request是一个域对象,可以把它当成Map来添加获取数据;
request提供了请求转发和请求包含功能。
域方法
request是域对象!在JavaWeb中一共四个域对象,其中ServletContext就是域对象,它在整个应用中只创建一个ServletContext对象。request其中一个,request可以在一个请求中共享数据。
一个请求会创建一个request对象,如果在一个请求中经历了多个Servlet,那么多个Servlet就可以使用request来共享数据。现在我们还不知道如何在一个请求中经历多个Servlet,后面在学习请求转发和请求包含后就知道了。
下面是request的域方法:
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“xxx”, “XXX”),在request中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
Object getAttribute(String name):用来获取request中的数据,当前在获取之前需要先去存储才行,例如:String value = (String)request.getAttribute(“xxx”);,获取名为xxx的域属性;
void removeAttribute(String name):用来移除request中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
Enumeration getAttributeNames():获取所有域属性的名称;
request获取请求头数据
request与请求头相关的方法有:
String getHeader(String name):获取指定名称的请求头;
Enumeration getHeaderNames():获取所有请求头名称;
int getIntHeader(String name):获取值为int类型的请求头。
request其他方法
request中还提供了与请求相关的其他方法,有些方法是为了我们更加便捷的方法请求头数据而设计,有些是与请求URL相关的方法。
int getContentLength():获取请求体的字节数,GET请求没有请求体,没有请求体返回-1;
String getContentType():获取请求类型,如果请求是GET,那么这个方法返回null;如果是POST请求,那么默认为application/x-www-form-urlencoded,表示请求体内容使用了URL编码;
String getMethod():返回请求方法,例如:GET
Locale getLocale():返回当前客户端浏览器的Locale。java.util.Locale表示国家和言语,这个东西在国际化中很有用;
String getCharacterEncoding():获取请求体编码,如果没有setCharacterEncoding(),那么返回null,表示使用ISO-8859-1编码;
void setCharacterEncoding(String code):设置请求编码,只对请求体有效!注意,对于GET而言,没有请求体!!!所以此方法只能对POST请求中的参数有效!
String getContextPath():返回上下文路径,例如:/hello
String getQueryString():返回请求URL中的参数,例如:name=zhangSan
String getRequestURI():返回请求URI路径,例如:/hello/oneServlet
StringBuffer getRequestURL():返回请求URL路径,例如:http://localhost/hello/oneServlet,即返回除了参数以外的路径信息;
String getServletPath():返回Servlet路径,例如:/oneServlet
String getRemoteAddr():返回当前客户端的IP地址;
String getRemoteHost():返回当前客户端的主机名,但这个方法的实现还是获取IP地址;
String getScheme():返回请求协议,例如:http;
String getServerName():返回主机名,例如:localhost
int getServerPort():返回服务器端口号,例如:8080
eg. 可以使用request.getRemoteAddr()方法获取客户端的IP地址,然后判断IP是否为禁用IP。
1 | String ip = request.getRemoteAddr(); |
request获取请求参数
最为常见的客户端传递参数方式有两种:
浏览器地址栏直接输入:一定是GET请求;
超链接:一定是GET请求;
表单:可以是GET,也可以是POST,这取决与
<form>
的method属性值;
GET请求和POST请求的区别:
GET请求:
请求参数会在浏览器的地址栏中显示,所以不安全;
请求参数长度限制长度在1K之内;
GET请求没有请求体,无法通过request.setCharacterEncoding()来设置参数的编码;
POST请求:
请求参数不会显示浏览器的地址栏,相对安全;
请求参数长度没有限制;
1 | <a href="/hello/ParamServlet?p1=v1&p2=v2">超链接</a> |
下面是使用request获取请求参数的API:
- String getParameter(String name):通过指定名称获取参数值;
1 | public void doGet(HttpServletRequest request, HttpServletResponse response) |
String[] getParameterValues(String name):当多个参数名称相同时,可以使用方法来获取;
Enumeration getParameterNames():获取所有参数的名字;
Map getParameterMap():获取所有参数封装到Map中,其中key为参数名,value为参数值,因为一个参数名称可能有多个值,所以参数值是String[],而不是String。
请求转发与请求包含
无论是请求转发还是请求包含,都表示由多个Servlet共同来处理一个请求。例如Servlet1来处理请求,然后Servlet1又转发给Servlet2来继续处理这个请求。
请求转发
在AServlet中,把请求转发到BServlet:
1 | public class AServlet extends HttpServlet { |
请求包含
在AServlet中,把请求包含到BServlet:
1 | public class AServlet extends HttpServlet { |
请求转发与请求包含比较
如果在AServlet中请求转发到BServlet,那么在AServlet中就不允许再输出响应体,即不能再使用response.getWriter()和response.getOutputStream()向客户端输出,这一工作应该由BServlet来完成;如果是使用请求包含,那么没有这个限制;
请求转发虽然不能输出响应体,但还是可以设置响应头的,例如:response.setContentType(“text/html;charset=utf-8”);
请求包含大多是应用在JSP页面中,完成多页面的合并;
请求转发大多是应用在Servlet中,转发目标大多是JSP页面;
请求转发与重定向比较
请求转发是一个请求,而重定向是两个请求;
请求转发后浏览器地址栏不会有变化,而重定向会有变化,因为重定向是两个请求;
请求转发的目标只能是本应用中的资源,重定向的目标可以是其他应用;
请求转发对AServlet和BServlet的请求方法是相同的,即要么都是GET,要么都是POST,因为请求转发是一个请求;
重定向的第二个请求一定是GET;
路径
与路径相关的操作
超链接
表单
转发
包含
重定向
<url-pattern>
ServletContext获取资源
Class获取资源
ClassLoader获取资源
客户端路径
超链接、表单、重定向都是客户端路径,客户端路径可以分为三种方式:
绝对路径;
以”/“开头的相对路径;
不以”/“开头的相对路径;
例如:http://localhost:8080/hello1/pages/a.html
中的超链接和表单如下:
1 | 绝对路径:<a href="http://localhost:8080/hello2/index.html">链接1</a> |
链接1和表单1:没什么可说的,它使用绝对路径;
链接2和表单2:以”/“开头,相对主机,与当前a.html的主机相同,即最终访问的页面为
http://localhost:8080/hello2/index.html
;链接3和表单3:不以”/“开头,相对当前页面的路径,即a.html所有路径,即最终访问的路径为:
http://localhost:8080/hello1/pages/index.html
;
建议使用”/“
强烈建议使用”/“开头的路径,这说明在页面中的超链接和表单都要以”/“开头,后面是当前应用的名称,再是访问路径:
<form action="/hello/servlet/AServlet">
</form>
<a href="/hello/b.html">链接</a>
其中/hello是当前应用名称,这也说明如果将来修改了应用名称,那么页面中的所有路径也要修改,这一点确实是个问题。这一问题的处理方案会在学习了JSP之后讲解!
在Servlet中的重定向也建议使用”/“开头。同理,也要给出应用的名称!例如:
1 | response.sendRedirect("/hello/BServlet"); |
其中/hello是当前应用名,如果将来修改了应用名称,那么也要修改所有重定向的路径,这一问题的处理方案是使用request.getContextPath()来获取应用名称。
1 | response.sendRedirect(request.getContextPath() + "/BServlet"); |
服务器端路径
服务器端路径必须是相对路径,不能是绝对路径。但相对路径有两种形式:
以”/“开头;
不以”/“开头;
其中请求转发、请求包含都是服务器端路径,服务器端路径与客户端路径的区别是:
客户端路径以”/“开头:相对当前主机;
服务器端路径以”/“开头:相对当前应用;
1 | // eg1 |
假设访问AServlet的路径为:http://localhost:8080/hello/servlet/AServlet
因为路径以”/“开头,所以相对当前应用,即http://localhost:8080/hello/BServlet
。
1 | // eg2 |
假设访问AServlet的路径为:http://localhost:8080/hello/servlet/AServlet
因为路径不以”/“开头,所以相对当前应用,即http://localhost:8080/hello/servlet/BServlet
。
<url-pattern>
路径
<url-pattern>
必须使用”/“开头,并且相对的是当前应用。
ServletContext获取资源
必须是相对路径,可以”/“开头,也可以不使用”/“开头,但无论是否使用”/“开头都是相对当前应用路径。
例如在AServlet中获取资源,AServlet的路径路径为:http://localhost:8080/hello/servlet/AServlet
:
1 | public class AServlet extends HttpServlet { |
path1和path2是相同的结果:http://localhost:8080/hello/a.txt
Class获取资源
Class获取资源也必须是相对路径,可以”/“开头,也可以不使用”/“开头。
1 | package cn.itcast; |
其中fun1()方法获取资源时以”/“开头,那么相对的是当前类路径,即/hello/WEB-INF/classes/a.txt文件;
其中fun2()方法获取资源时没有以”/“开头,那么相对当前Demo.class所在路径,因为Demo类在cn.itcast包下,所以资源路径为:/hello/WEB-INF/classes/cn/itcast/a.txt。
ClassLoader获取资源
ClassLoader获取资源也必须是相对路径,可以”/“开头,也可以不使用”/“开头。但无论是否以”/“开头,资源都是相对当前类路径。
1 | public class Demo { |
fun1()和fun2()方法的资源都是相对类路径,即classes目录,即/hello/WEB-INF/classes/a.txt
编码
请求编码
直接在地址栏中给出中文
请求数据是由客户端浏览器发送服务器的,请求数据的编码是由浏览器决定的。例如在浏览器地址栏中给出:http://localhost:8080/hello/AServlet?name=传智
,那么其中”传智”是什么编码的呢?不同浏览器使用不同的编码,所以这是不确定的!
IE:使用GB2312;
FireFox:使用GB2312;
Chrome:使用UTF-8;
通常没有哪个应用要求用户在浏览器地址栏中输入请求数据的,所以大家只需了解一下即可。
在页面中发出请求
通常向服务器发送请求数据都需要先请求一个页面,然后用户在页面中输入数据。页面中有超链接和表单,通过超链接和表单就可以向服务器发送数据了。
因为页面是服务器发送到客户端浏览器的,所以这个页面本身的编码由服务器决定。而用户在页面中输入的数据也是由页面本身的编码决定的。
1 |
|
当用户在index.html页面中输入数据时,都是UTF-8列表的。因为这个页面本身就是UTF-8编码的!
页面的编译就是页面中输入数据的编码。
GET请求解读编码
当客户端通过GET请求发送数据给服务器时,使用request.getParameter()获取的数据是被服务器误认为ISO-8859-1编码的,也就是说客户端发送过来的数据无论是UTF-8还是GBK,服务器都认为是ISO-8859-1,这就说明我们需要在使用request.getParameter()获取数据后,再转发成正确的编码。
例如客户端以UTF-8发送的数据,使用如下转码方式:
String name = request.getParameter(“name”);
name = new String(name.getBytes(“iso-8859-1”), “utf-8”);
POST请求解读编码
当客户端通过POST请求发送数据给服务器时,可以在使用request.getParameter()获取请求参数之前先通过request.setCharacterEncoding()来指定编码,然后再使用reuqest.getParameter()方法来获取请求参数,那么就是用指定的编码来读取了。
也就是说,如果是POST请求,服务器可以指定编码!但如果没有指定编码,那么默认还是使用ISO-8859-1来解读。
request.setCharacterEncoding(“utf-8”);
String name = request.getParameter(“name”);
响应编码
响应:服务器发送给客户端数据!响应是由response对象来完成,如果响应的数据不是字符数据,那么就无需去考虑编码问题。当然,如果响应的数据是字符数据,那么就一定要考虑编码的问题了。
response.getWriter().print(“传智”);
上面代码因为没有设置repsonse.getWriter()字符流的编码,所以服务器使用默认的编码(ISO-8859-1)来处理,因为ISO-8859-1不支持中文,所以一定会出现乱码的。
所以在使用response.getWriter()发送数据之前,一定要设置response.getWriter()的编码,这需要使用response.setCharacterEncoding()方法:
response.setCharacterEncoding(“utf-8”);
response.getWriter().print(“传智”);
上面代码因为在使用response.getWriter()输出之前已经设置了编码,所以输出的数据为utf-8编码。但是,因为没有告诉浏览器使用什么编码来读取响应数据,所以很可能浏览器会出现错误的解读,那么还是会出现乱码的。当然,通常浏览器都支持来设置当前页面的编码,如果用户在看到编码时,去设置浏览器的编码,如果设置的正确那么乱码就会消失。但是我们不能让用户总去自己设置编码,而且应该直接通知浏览器,服务器发送过来的数据是什么编码,这样浏览器就直接使用服务器告诉他的编码来解读!这需要使用content-type响应头。
response.setContentType(“text/html;charset=utf-8”);
response.getWriter().print(“传智”);
上面代码使用setContentType()方法设置了响应头content-type编码为utf-8,这不只是在响应中添加了响应头,还等于调用了一次response.setCharacterEncoding(“utf-8”),也就是说,通过我们只需要调用一次response.setContentType(“text/html;charset=utf-8”)即可,而无需再去调用response.setCharacterEncoding(“utf-8”)了。
在静态页面中,使用<meta>
来设置content-type响应头,例如:
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
URL编码
通过页面传输数据给服务器时,如果包含了一些特殊字符是无法发送的。这时就需要先把要发送的数据转换成URL编码格式,再发送给服务器。
其实需要我们自己动手给数据转换成URL编码的只有GET超链接,因为表单发送数据会默认使用URL编码,也就是说,不用我们自己来编码。
例如:”传智”这两个字通过URL编码后得到的是:”%E4%BC%A0%E6%99%BA”。URL编码是先需要把”传智”转换成字节,例如我们现在使用UTF-8把”传智”转换成字符,得到的结果是:”[-28, -68, -96, -26, -103, -70]”,然后再把所有负数加上256,得到[228, 188, 160, 230, 153, 186],再把每个int值转换成16进制,得到[E4, BC, A0, E6, 99, BA],最后再每个16进制的整数前面加上”%”。
通过URL编码,把”传智”转换成了”%E4%BC%A0%E6%99%BA”,然后发送给服务器!服务器会自动识别出数据是使用URL编码过的,然后会自动把数据转换回来。
当然,在页面中我们不需要自己去通过上面的过程把”传智”转换成”%E4%BC%A0%E6%99%BA”,而是使用Javascript来完成即可。当后面我们学习了JSP后,就不用再使用Javascript了。
1 | <script type="text/javascript"> |
因为URL默认只支持ISO-8859-1,这说明在URL中出现中文和一些特殊字符可能无法发送到服务器。所以我们需要对包含中文或特殊字符的URL进行URL编码。
服务器会自动识别数据是否使用了URL编码,如果使用了服务器会自动把数据解码,无需我们自己动手解码。
String s = “传智”;
s = URLEncoder.encode(s, “utf-8”);// %E4%BC%A0%E6%99%BA
s = URLDecoderdecode(s, “utf-8”);//传智
Day 06
30/06/2020
JSP
JSP(Java Server Pages)是JavaWeb服务器端的动态资源。它与html页面的作用是相同的,显示数据和获取数据。
JSP = html + Java脚本(代码片段) + JSP动作标签
JSP语法
JSP 脚本
JSP脚本就是Java代码片段,它分为三种:
<%...%>
:Java语句;<%=…%>
:Java表达式;<%!...%>
:Java定义类成员;
1 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> |
内置对象 out
out对象在JSP页面中无需创建就可以使用,它的作用是用来向客户端输出。
1 | <body> |
其中<%=…%>
与out.print()
功能是相同的!它们都是向客户端输出,例如:
<%=s1%>
等同于<% out.print(s1); %>
<%="hello"%>
等同于<% out.print("hello"); %>
,也等同于直接在页面中写hello一样。
多个<%...%>
可以通用
在一个JSP中多个<%…%>是相通的。例如:
1 | <body> |
循环打印表格:
1 | <body> |
JSP 原理
JSP是一种特殊的Servlet,当JSP页面首次被访问时,容器(Tomcat)会先把JSP编译成Servlet,然后再去执行Servlet。所以JSP其实就是一个Servlet!
JSP真身存放目录
JSP生成的Servlet存放在${CATALANA}/work目录下,我经常开玩笑的说,它是JSP的”真身”。我们打开看看其中的内容,了解一下JSP的”真身”。
你会发现,在JSP中的静态信息(例如<html>
等)在”真身”中都是使用out.write()完成打印!这些静态信息都是作为字符串输出给了客户端。
JSP的整篇内容都会放到名为_jspService
的方法中!你可能会说<@page>
不在”真身”中,<%@page>
我们明天再看。
1 | public void _jspService(final javax.servlet.http.HttpServletRequest request, |
JSP脚本
JSP脚本一共三种形式:
<%...%>
:内容会直接放到”真身”中;<%=…%>
:内容会放到out.print()中,作为out.print()的参数;<%!…%>
:内容会放到_jspService()方法之外,被类直接包含;
前面已经讲解了<%...%>
和<%=…%>
,但还没有讲解<%!...%>
的作用!
现在我们已经知道了,JSP其实就是一个类,一个Servlet类。<%!...%>
的作用是在类中添加方法或成员的,所以<%!...%>
中的内容不会出现在_jspService()
中。
1 | <%! |
JSP注释
我们现在已经知道JSP是需要先编译成.java,再编译成.class的。其中<%-- ... --%>
中的内容在JSP编译成.java时会被忽略的,即JSP注释。
也可以在JSP页面中使用html注释:<!-- … -->
,但这个注释在JSP编译成的.java中是存在的,它不会被忽略,而且会被发送到客户端浏览器。但是在浏览器显示服务器发送过来的html时,因为<!-- … -->
是html的注释,所以浏览器是不会显示它的。
会话跟踪技术
什么是会话跟踪技术
我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了。从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束。在通话过程中,你会向10086发出多个请求,那么这多个请求都在一个会话中。
在JavaWeb中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。
在一个会话的多个请求中共享数据,这就是会话跟踪技术。例如在一个会话中的请求如下:
请求银行主页;
请求登录(请求参数是用户名和密码);
请求转账(请求参数与转账相关的数据);
请求信誉卡还款(请求参数与还款相关的数据)。
在这上会话中当前用户信息必须在这个会话中共享的,因为登录的是张三,那么在转账和还款时一定是相对张三的转账和还款!这就说明我们必须在一个会话过程中有共享数据的能力。
会话路径技术使用Cookie或session完成
我们知道HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但HTTP协议中可以使用Cookie来完成会话跟踪!
在JavaWeb中,使用session来完成会话跟踪,session底层依赖Cookie技术。
Cookie
Cookie概述
Cookie翻译成中文是小甜点,小饼干的意思。在HTTP中它表示服务器送给客户端浏览器的小甜点。其实Cookie就是一个键和一个值构成的,随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。
Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!
Cookie规范
Cookie大小上限为4KB;
一个服务器最多在客户端浏览器上保存20个Cookie;
一个浏览器最多保存300个Cookie;
上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范”扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。
Cookie与HTTP头
Cookie是通过HTTP请求和响应头在客户端和服务器端传递的:
- Cookie:请求头,客户端发送给服务器端;
- 格式:Cookie: a=A; b=B; c=C。即多个Cookie用分号离开;
- Set-Cookie:响应头,服务器端发送给客户端;
- 一个Cookie对应一个Set-Cookie:
- Set-Cookie: a=A
- Set-Cookie: b=B
- Set-Cookie: c=C
Cookie的覆盖
如果服务器端发送重复的Cookie那么会覆盖原有的Cookie,例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。
Cookie第一例
我们这个案例是,客户端访问AServlet,AServlet在响应中添加Cookie,浏览器会自动保存Cookie。然后客户端访问BServlet,这时浏览器会自动在请求中带上Cookie,BServlet获取请求中的Cookie打印出来。
1 | // AServlet.java |
1 | // BServlet.java |
Cookie 生命
什么是Cookie的生命
Cookie不只是有name和value,Cookie还有生命。所谓生命就是Cookie在客户端的有效时间,可以通过setMaxAge(int)来设置Cookie的有效时间。
cookie.setMaxAge(-1):cookie的maxAge属性的默认值就是-1,表示只在浏览器内存中存活。一旦关闭浏览器窗口,那么cookie就会消失。
cookie.setMaxAge(60*60):表示cookie对象可存活1小时。当生命大于0时,浏览器会把Cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活1小时;
cookie.setMaxAge(0):cookie生命等于0是一个特殊的值,它表示cookie被作废!也就是说,如果原来浏览器已经保存了这个Cookie,那么可以通过Cookie的setMaxAge(0)来删除这个Cookie。无论是在浏览器内存中,还是在客户端硬盘上都会删除这个Cookie。
案例:显示上次访问时间
创建Cookie,名为lasttime,值为当前时间,添加到response中;
在AServlet中获取请求中名为lasttime的Cookie;
如果不存在输出”您是第一次访问本站”,如果存在输出”您上一次访问本站的时间是xxx”;
1 | // AServlet.java |
Cookie的path
什么是Cookie的路径
现在有WEB应用A,向客户端发送了10个Cookie,这就说明客户端无论访问应用A的哪个Servlet都会把这10个Cookie包含在请求中!但是也许只有AServlet需要读取请求中的Cookie,而其他Servlet根本就不会获取请求中的Cookie。这说明客户端浏览器有时发送这些Cookie是多余的!
可以通过设置Cookie的path来指定浏览器,在访问什么样的路径时,包含什么样的Cookie。
Cookie路径与请求路径的关系
下面我们来看看Cookie路径的作用:
下面是客户端浏览器保存的3个Cookie的路径:
a: /cookietest;
b: /cookietest/servlet;
c: /cookietest/jsp;
下面是浏览器请求的URL:
A: http://localhost:8080/cookietest/AServlet
;
B: http://localhost:8080/cookietest/servlet/BServlet
;
C: http://localhost:8080/cookietest/jsp/CServlet
;
请求A时,会在请求中包含a;
请求B时,会在请求中包含a、b;
请求C时,会在请求中包含a、c;
也就是说,请求路径如果包含了Cookie路径,那么会在请求中包含这个Cookie,否则不会请求中不会包含这个Cookie。
A请求的URL包含了”/cookietest”,所以会在请求中包含路径为”/cookietest”的Cookie;
B请求的URL包含了”/cookietest”,以及”/cookietest/servlet”,所以请求中包含路径为”/cookietest”和”/cookietest/servlet”两个Cookie;
B请求的URL包含了”/cookietest”,以及”/cookietest/jsp”,所以请求中包含路径为”/cookietest”和”/cookietest/jsp”两个Cookie;
设置Cookie的路径
设置Cookie的路径需要使用setPath()方法,例如:
cookie.setPath(“/cookietest/servlet”);
如果没有设置Cookie的路径,那么Cookie路径的默认值当前访问资源所在路径,例如:
访问
http://localhost:8080/cookietest/AServlet
时添加的Cookie默认路径为/cookietest;访问
http://localhost:8080/cookietest/servlet/BServlet
时添加的Cookie默认路径为/cookietest/servlet;访问
http://localhost:8080/cookietest/jsp/BServlet
时添加的Cookie默认路径为/cookietest/jsp;
Cookie的domain
Cookie的domain属性可以让网站中二级域共享Cookie,次要!
百度你是了解的对吧!
http://www.baidu.com
http://zhidao.baidu.com
http://news.baidu.com
http://tieba.baidu.com
现在我希望在这些主机之间共享Cookie(例如在www.baidu.com
中响应的cookie,可以在news.baidu.com
请求中包含)。很明显,现在不是路径的问题了,而是主机的问题,即域名的问题。处理这一问题其实很简单,只需要下面两步:
设置Cookie的path为”/“:c.setPath(“/“);
设置Cookie的domain为”.baidu.com”:c.setDomain(“.baidu.com”)。
当domain为”.baidu.com”时,无论前缀是什么,都会共享Cookie的。但是现在我们需要设置两个虚拟主机:www.baidu.com
和news.baidu.com
。
第一步:设置windows的DNS路径解析
找到C:\WINDOWS\system32\drivers\etc\hosts文件,添加如下内容
1 | 127.0.0.1 localhost |
第二步:设置Tomcat虚拟主机
找到server.xml文件,添加<Host>
元素,内容如下:
1 | <Host name="www.baidu.com" appBase="F:\webapps\www" |
第三步:创建A项目,创建AServlet,设置Cookie。
1 | Cookie[] cs = request.getCookies(); |
把B项目的WebRoot目录复制到F:\webapps\news目录下,并把WebRoot目录的名字修改为ROOT。
第五步:访问www.baidu.com\AServlet
,然后再访问news.baidu.com\BServlet
。
Cookie保存中文
Cookie的name和value都不能使用中文,如果希望在Cookie中使用中文,那么需要先对中文进行URL编码,然后把编码后的字符串放到Cookie中。
向客户端响应中添加Cookie
1 | String name = URLEncoder.encode("姓名", "UTF-8"); |
从客户端请求中获取Cookie
1 | response.setContentType("text/html;charset=utf-8"); |
显示曾经浏览过的商品
1 | <%--index.jsp--%> |
1 | // GoodServlet |
1 | // CookieUtils |
HttpSession
HttpSession概述
javax.servlet.http.HttpSession接口表示一个会话,我们可以把一个会话内需要共享的数据保存到HttpSession对象中!
获取HttpSession对象
HttpSession request.getSesssion():如果当前会话已经有了session对象那么直接返回,如果当前会话还不存在会话,那么创建session并返回;
HttpSession request.getSession(boolean):当参数为true时,与requeset.getSession()相同。如果参数为false,那么如果当前会话中存在session则返回,不存在返回null;
HttpSession是域对象
我们已经学习过HttpServletRequest、ServletContext,它们都是域对象,现在我们又学习了一个HttpSession,它也是域对象。它们三个是Servlet中可以使用的域对象,而JSP中可以多使用一个域对象,明天我们再讲解JSP的第四个域对象。
HttpServletRequest:一个请求创建一个request对象,所以在同一个请求中可以共享request,例如一个请求从AServlet转发到BServlet,那么AServlet和BServlet可以共享request域中的数据;
ServletContext:一个应用只创建一个ServletContext对象,所以在ServletContext中的数据可以在整个应用中共享,只要不启动服务器,那么ServletContext中的数据就可以共享;
HttpSession:一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享session中的数据;
下面是session的域方法:
void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:session.setAttribute(“xxx”, “XXX”),在session中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
Object getAttribute(String name):用来获取session中的数据,当前在获取之前需要先去存储才行,例如:String value = (String) session.getAttribute(“xxx”);,获取名为xxx的域属性;
void removeAttribute(String name):用来移除HttpSession中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
Enumeration getAttributeNames():获取所有域属性的名称;
登录案例
需要的页面:
login.jsp:登录页面,提供登录表单;
index1.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
index2.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
Servlet:
- LoginServlet:在login.jsp页面提交表单时,请求本Servlet。在本Servlet中获取用户名、密码进行校验,如果用户名、密码错误,显示”用户名或密码错误”,如果正确保存用户名session中,然后重定向到index1.jsp;
当用户没有登录时访问index1.jsp或index2.jsp,显示”您还没有登录”。如果用户在login.jsp登录成功后到达index1.jsp页面会显示当前用户名,而且不用再次登录去访问index2.jsp也会显示用户名。因为多次请求在一个会话范围,index1.jsp和index2.jsp都会到session中获取用户名,session对象在一个会话中是相同的,所以都可以获取到用户名!
1 | <%--login.jsp--%> |
1 | <%--index1.jsp--%> |
1 | <%--index2.jsp--%> |
1 | // LoginServlet |
session的实现原理
session底层是依赖Cookie的!我们来理解一下session的原理吧!
当我首次去银行时,因为还没有账号,所以需要开一个账号,我获得的是银行卡,而银行这边的数据库中留下了我的账号,我的钱是保存在银行的账号中,而我带走的是我的卡号。
当我再次去银行时,只需要带上我的卡,而无需再次开一个账号了。只要带上我的卡,那么我在银行操作的一定是我的账号!
当首次使用session时,服务器端要创建session,session是保存在服务器端,而给客户端的session的id(一个cookie中保存了sessionId)。客户端带走的是sessionId,而数据是保存在session中。
当客户端再次访问服务器时,在请求中会带上sessionId,而服务器会通过sessionId找到对应的session,而无需再创建新的session。
session与浏览器
session保存在服务器,而sessionId通过Cookie发送给客户端,但这个Cookie的生命为-1,即只在浏览器内存中存在,也就是说如果用户关闭了浏览器,那么这个Cookie就丢失了。
当用户再次打开浏览器访问服务器时,就不会有sessionId发送给服务器,那么服务器会认为你没有session,所以服务器会创建一个session,并在响应中把sessionId中到Cookie中发送给客户端。
你可能会说,那原来的session对象会怎样?当一个session长时间没人使用的话,服务器会把session删除了!这个时长在Tomcat中配置是30分钟,可以在${CATALANA}/conf/web.xml找到这个配置,当然你也可以在自己的web.xml中覆盖这个配置!
1 | <session-config> |
session失效时间也说明一个问题!如果你打开网站的一个页面开始长时间不动,超出了30分钟后,再去点击链接或提交表单时你会发现,你的session已经丢失了!
session其他常用API
String getId():获取sessionId;
int getMaxInactiveInterval():获取session可以的最大不活动时间(秒),默认为30分钟。当session在30分钟内没有使用,那么Tomcat会在session池中移除这个session;
void setMaxInactiveInterval(int interval):设置session允许的最大不活动时间(秒),如果设置为1秒,那么只要session在1秒内不被使用,那么session就会被移除;
long getCreationTime():返回session的创建时间,返回值为当前时间的毫秒值;
long getLastAccessedTime():返回session的最后活动时间,返回值为当前时间的毫秒值;
void invalidate():让session失效!调用这个方法会被session失效,当session失效后,客户端再次请求,服务器会给客户端创建一个新的session,并在响应中给客户端新session的sessionId;
boolean isNew():查看session是否为新。当客户端第一次请求时,服务器为客户端创建session,但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,这时session的状态为新。
URL重写
我们知道session依赖Cookie,那么session为什么依赖Cookie呢?因为服务器需要在每次请求中获取sessionId,然后找到客户端的session对象。那么如果客户端浏览器关闭了Cookie呢?那么session是不是就会不存在了呢?
其实还有一种方法让服务器收到的每个请求中都带有sessioinId,那就是URL重写!在每个页面中的每个链接和表单中都添加名为jSessionId的参数,值为当前sessionid。当用户点击链接或提交表单时也服务器可以通过获取jSessionId这个参数来得到客户端的sessionId,找到sessoin对象。
1 | <%--index.jsp--%> |
也可以使用response.encodeURL()对每个请求的URL处理,这个方法会自动追加jsessionid参数,与上面我们手动添加是一样的效果。
1 | <a href='<%=response.encodeURL("/day06_5/index.jsp") %>' >主页</a> |
使用response.encodeURL()更加”智能”,它会判断客户端浏览器是否禁用了Cookie,如果禁用了,那么这个方法在URL后面追加jsessionid,否则不会追加。
案例:一次性图片验证码
验证码有啥用
在我们注册时,如果没有验证码的话,我们可以使用URLConnection来写一段代码发出注册请求。甚至可以使用while(true)来注册!那么服务器就废了!
验证码可以去识别发出请求的是人还是程序!当然,如果聪明的程序可以去分析验证码图片!但分析图片也不是一件容易的事,因为一般验证码图片都会带有干扰线,人都看不清,那么程序一定分析不出来。
VerifyCode类
现在我们已经有了cn.itcast.utils.VerifyCode类,这个类可以生成验证码图片!下面来看一个小例子。
1 | public void fun1() throws IOException { |
在页面中显示动态图片
我们需要写一个VerifyCodeServlet,在这个Servlet中我们生成动态图片,然后它图片写入到response.getOutputStream()流中!然后让页面的元素指定这个VerifyCodServlet即可。
1 | // VerifyCodeServlet |
1 | <%--index.jsp--%> |
在注册页面中使用验证码
1 | <form action="/day06_6/RegistServlet" method="post"> |
RegistServlet
修改VerifyCodeServlet
1 | public class VerifyCodeServlet extends HttpServlet { |
总结验证码案例
VerifyCodeServlet:
生成验证码:VerifyCode vc = new VerifyCode(); BufferedImage image = vc.getImage();
在session中保存验证码文本:request.getSession.getAttribute(“vCode”, vc.getText());
把验证码输出到页面:VerifyCode.output(image, response.getOutputStream);
regist.jsp:
表单中包含username和code字段;
在表单中给出指向VerifyCodeServlet,用来在页面中显示验证码图片;
提供”看不清,换一张”链接,指向_change()函数;
提交到RegistServlet;
RegistServlet:
- 获取表单中的username和code;
获取session中的vCode;
- 比较code和vCode是否相同;
相同说明用户输入的验证码正确,否则输入验证码错误。
Day 07
01/07/2020
JSP 指令
JSP指令的格式:<%@指令名 attr1="" attr2="" %>
,一般都会把JSP指令放到JSP文件的最上方,但这不是必须的。
JSP中有三大指令:page、include、taglib,最为常用,也最为复杂的就是page指令了。
page 指令
page指令是最为常用的指定,也是属性最多的属性!
page指令没有必须属性,都是可选属性。例如<%@page %>,没有给出任何属性也是可以的!
在JSP页面中,任何指令都可以重复出现!
<%@ page language="java"%>
<%@ page import="java.util.*"%>
<%@ page pageEncoding="utf-8"%>
这也是可以的!
page指令的pageEncoding和contentType(重点)
pageEncoding指定当前JSP页面的编码!这个编码是给服务器看的,服务器需要知道当前JSP使用的编码,不然服务器无法正确把JSP编译成java文件。所以这个编码只需要与真实的页面编码一致即可!在MyEclipse中,在JSP文件上点击右键,选择属性就可以看到当前JSP页面的编码了。
contentType属性与response.setContentType()方法的作用相同!它会完成两项工作,一是设置响应字符流的编码,二是设置content-type响应头。例如:<%@ contentType="text/html;charset=utf-8"%>
,它会使”真身”中出现response.setContentType(“text/html;charset=utf-8”)。
无论是page指令的pageEncoding还是contentType,它们的默认值都是ISO-8859-1,我们知道ISO-8859-1是无法显示中文的,所以JSP页面中存在中文的话,一定要设置这两个属性。
其实pageEncoding和contentType这两个属性的关系很”暧昧”:
当设置了pageEncoding,而没设置contentType时: contentType的默认值为pageEncoding;
当设置了contentType,而没设置pageEncoding时: pageEncoding的默认值与contentType;
也就是说,当pageEncoding和contentType只出现一个时,那么另一个的值与出现的值相同。如果两个都不出现,那么两个属性的值都是ISO-8859-1。所以通过我们至少设置它们两个其中一个!
page指令的import属性
import是page指令中一个很特别的属性!
import属性值对应”真身”中的import语句。
import属性值可以使逗号:<%@page import="java.net.*,java.util.*,java.sql.*"%>
import属性是唯一可以重复出现的属性:
<%@page import="java.util.*" import="java.net.*" import="java.sql.*"%>
但是,我们一般会使用多个page指令来导入多个包:
<%@ page import="java.util.*"%>
<%@ page import="java.net.*"%>
<%@ page import="java.text.*"%>
page指令的errorPage和isErrorPage
我们知道,在一个JSP页面出错后,Tomcat会响应给用户错误信息(500页面)!如果你不希望Tomcat给用户输出错误信息,那么可以使用page指令的errorPage来指定错误页!也就是自定义错误页面,例如:<%@page errorPage="xxx.jsp"%>
。这时,在当前JSP页面出现错误时,会请求转发到xxx.jsp页面。
1 | <%--a.jsp--%> |
1 | <%--b.jsp--%> |
在上面代码中,a.jsp抛出异常后,会请求转发到b.jsp。在浏览器的地址栏中还是a.jsp,因为是请求转发!
而且客户端浏览器收到的响应码为200,表示请求成功!如果希望客户端得到500,那么需要指定b.jsp为错误页面。
1 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> |
注意,当isErrorPage为true时,说明当前JSP为错误页面,即专门处理错误的页面。那么这个页面中就可以使用一个内置对象exception了。其他页面是不能使用这个内置对象的!
温馨提示:IE会在状态码为500时,并且响应正文的长度小于等于512B时不给予显示!而是显示”网站无法显示该页面”字样。这时你只需要添加一些响应内容即可,例如上例中的b.jsp中我给出一些内容,IE就可以正常显示了!
web.xml中配置错误页面
不只可以通过JSP的page指令来配置错误页面,还可以在web.xml文件中指定错误页面。这种方式其实与page指令无关,但想来想去还是在这个位置来说明比较合适!
web.xml
1 | <error-page> |
<error-page>
有两种使用方式:
<error-code>
和<location>
子元素;<exception-type>
和<location>
子元素;其中
<error-code>
是指定响应码;<location>
指定转发的页面;<exception-type>
是指定抛出的异常类型。当出现404时,会跳转到error404.jsp页面;
当出现RuntimeException异常时,会跳转到error.jsp页面;
当出现非RuntimeException的异常时,会跳转到error500.jsp页面。
这种方式会在控制台看到异常信息!而使用page指令时不会在控制台打印异常信息。
page指令的authFlush和buffer
buffer表示当前JSP的输出流(out隐藏对象)的缓冲区大小,默认为8kb。
authFlush表示在out对象的缓冲区满时如何处理!当authFlush为true时,表示缓冲区满时把缓冲区数据输出到客户端;当authFlush为false时,表示缓冲区满时,抛出异常。authFlush的默认值为true。
这两个属性一般我们也不会去特意设置,都是保留默认值!
page指令的isELIgnored
后面我们会讲解EL表达式语言,page指令的isELIgnored属性表示当前JSP页面是否忽略EL表达式,默认值为false,表示不忽略(即支持)。
page指令的其他属性
language:只能是Java,这个属性可以看出JSP最初设计时的野心!希望JSP可以转换成其他语言!但是,到现在JSP也只能转换成Java代码;
info:JSP说明性信息;
isThreadSafe:默认为false,为true时,JSP生成的Servlet会去实现一个过时的标记接口SingleThreadModel,这时JSP就只能处理单线程的访问;
session:默认为true,表示当前JSP页面可以使用session对象,如果为false表示当前JSP页面不能使用session对象;
extends:指定当前JSP页面生成的Servlet的父类;
<jsp-config>
(了解)
在web.xml页面中配置<jsp-config>
也可以完成很多page指定的功能!
1 | <jsp-config> |
include指令
include指令表示静态包含!即目的是把多个JSP合并成一个JSP文件!
include指令只有一个属性:file,指定要包含的页面,例如:<%@include file="b.jsp"%>
。
静态包含:当hel.jsp页面包含了lo.jsp页面后,在编译hel.jsp页面时,需要把hel.jsp和lo.jsp页面合并成一个文件,然后再编译成Servlet(Java文件)。
很明显,在ol.jsp中在使用username变量,而这个变量在hel.jsp中定义的,所以只有这两个JSP文件合并后才能使用。通过include指定完成对它们的合并!
taglib指令
这个指令需要在学习了自定义标签后才会使用,现在只能做了了解而已!
在JSP页面中使用第三方的标签库时,需要使用taglib指令来”导包”。例如:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
其中prefix表示标签的前缀,这个名称可以随便起。uri是由第三方标签库定义的,所以你需要知道第三方定义的uri。
JSP 九大内置对象
什么是JSP九大内置对象
在JSP中无需创建就可以使用的9个对象,它们是:
out(JspWriter):等同与response.getWriter(),用来向客户端发送文本数据;
config(ServletConfig):对应”真身”中的ServletConfig;
page(当前JSP的真身类型):当前JSP页面的”this”,即当前对象;
pageContext(PageContext):页面上下文对象,它是最后一个没讲的域对象;
exception(Throwable):只有在错误页面中可以使用这个对象;
request(HttpServletRequest):即HttpServletRequest类的对象;
response(HttpServletResponse):即HttpServletResponse类的对象;
application(ServletContext):即ServletContext类的对象;
session(HttpSession):即HttpSession类的对象,不是每个JSP页面中都可以使用,如果在某个JSP页面中设置
<%@page session="false"%>
,说明这个页面不能使用session。
在这9个对象中有很多是极少会被使用的,例如:config、page、exception基本不会使用。
在这9个对象中有两个对象不是每个JSP页面都可以使用的:exception、session。
在这9个对象中有很多前面已经学过的对象:out、request、response、application、session、config。
通过”真身”来对照JSP
我们知道JSP页面的内容出现在”真身”的_jspService()方法
中,而在_jspService()
方法开头部分已经创建了9大内置对象。
1 | public void _jspService(HttpServletRequest request, HttpServletResponse response) |
pageContext对象
在JavaWeb中一共四个域对象,其中Servlet中可以使用的是request、session、application三个对象,而在JSP中可以使用pageContext、request、session、application四个域对象。
pageContext 对象是PageContext类型,它的主要功能有:
域对象功能;
代理其它域对象功能;
获取其他内置对象;
域对象功能
pageContext也是域对象,它的范围是当前页面。它的范围也是四个域对象中最小的!
void setAttribute(String name, Object value);
Object getAttrbiute(String name, Object value);
void removeAttribute(String name, Object value);
代理其它域对象功能
还可以使用pageContext来代理其它3个域对象的功能,也就是说可以使用pageContext向request、session、application对象中存取数据,例如:
1 | pageContext.setAttribute("x", "X"); // 向pageContext中存储数据 |
void setAttribute(String name, Object value, int scope):在指定范围中添加数据;
Object getAttribute(String name, int scope):获取指定范围的数据;
void removeAttribute(String name, int scope):移除指定范围的数据;
Object findAttribute(String name):依次在page、request、session、application范围查找名称为name的数据,如果找到就停止查找。这说明在这个范围内有相同名称的数据,那么page范围的优先级最高!
获取其他内置对象
一个pageContext对象等于所有内置对象,即1个当9个。这是因为可以使用pageContext对象获取其它8个内置对象:
JspWriter getOut():获取out内置对象;
ServletConfig getServletConfig():获取config内置对象;
Object getPage():获取page内置对象;
ServletRequest getRequest():获取request内置对象;
ServletResponse getResponse():获取response内置对象;
HttpSession getSession():获取session内置对象;
ServletContext getServletContext():获取application内置对象;
Exception getException():获取exception内置对象;
JSP 动作标签
JSP动作标签概述
动作标签的作用是用来简化Java脚本的!
JSP动作标签是JavaWeb内置的动作标签,它们是已经定义好的动作标签,我们可以拿来直接使用。
如果JSP动作标签不够用时,还可以使用自定义标签(今天不讲)。JavaWeb一共提供了20个JSP动作标签,但有很多基本没有用,这里只介绍一些有坐标的动作标签。
JSP动作标签的格式:<jsp:标签名 …>
<jsp:include>
<jsp:include>
标签的作用是用来包含其它JSP页面的!你可能会说,前面已经学习了include指令了,它们是否相同呢?虽然它们都是用来包含其它JSP页面的,但它们的实现的级别是不同的!
include指令是在编译级别完成的包含,即把当前JSP和被包含的JSP合并成一个JSP,然后再编译成一个Servlet。
include动作标签是在运行级别完成的包含,即当前JSP和被包含的JSP都会各自生成Servlet,然后在执行当前JSP的Servlet时完成包含另一个JSP的Servlet。它与RequestDispatcher的include()方法是相同的!
1 | <%--hel.jsp--%> |
1 | <%--lo.jsp--%> |
其实<jsp:include>
在”真身”中不过是一句方法调用,即调用另一个Servlet而已。
<jsp:forward>
forward标签的作用是请求转发!forward标签的作用与RequestDispatcher#forward()方法相同。
1 | <%--hel.jsp--%> |
1 | <%--lo.jsp--%> |
注意,最后客户端只能看到lo.jsp的输出,而看不到hel.jsp的内容。也就是说在hel.jsp中的<h1>hel.jsp</h1>
是不会发送到客户端的。<jsp:forward>
的作用是”别再显示我,去显示它吧!”。
<jsp:param>
还可以在<jsp:include>
和<jsp:forward>
标签中使用<jsp:param>
子标签,它是用来传递参数的。下面用<jsp:include>
来举例说明<jsp:param>
的使用。
1 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> |
1 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> |
JavaBean
JavaBean概述
什么是JavaBean
JavaBean是一种规范,也就是对类的要求。它要求Java类的成员变量提供getter/setter方法,这样的成员变量被称之为JavaBean属性。
JavaBean还要求类必须提供仅有的无参构造器,例如:public User() {…}
1 | // User.java |
JavaBean属性
JavaBean属性是具有getter/setter方法的成员变量。
也可以只提供getter方法,这样的属性叫只读属性;
也可以只提供setter方法,这样的属性叫只写属性;
如果属性类型为boolean类型,那么读方法的格式可以是get或is。例如名为abc的boolean类型的属性,它的读方法可以是getAbc(),也可以是isAbc();
JavaBean属性名要求:前两个字母要么都大写,要么都小写:
1 | public class User { |
JavaBean可能存在属性,但不存在这个成员变量,例如:
1 | public class User { |
上例中User类有一个名为username的只读属性!但User类并没有username这个成员变量!
还可以更变态一点:
1 | public class User { |
上例中User类中有一个名为username的属性,它是可读可写的属性!而Use类的成员变量名为hello!也就是说JavaBean的属性名取决于方法名称,而不是成员变量的名称。但通常没有人做这么变态的事情。
内省(了解)
内省的目标是得到JavaBean属性的读、写方法的反射对象,通过反射对JavaBean属性进行操作的一组API。例如User类有名为username的JavaBean属性,通过两个Method对象(一个是getUsenrmae(),一个是setUsername())来操作User对象。
如果你还不能理解内省是什么,那么我们通过一个问题来了解内省的作用。现在我们有一个Map,内容如下:
1 | Map<String,String> map = new HashMap<String,String>(); |
现在需要把map的数据封装到一个User对象中!User类有两个JavaBean属性,一个叫username,另一个叫password。
你可能想到的是反射,通过map的key来查找User类的Field!这么做是没有问题的,但我们要知道类的成员变量是私有的,虽然也可以通过反射去访问类的私有的成员变量,但我们也要清楚反射访问私有的东西是有”危险”的,所以还是建议通过getUsername和setUsername来访问JavaBean属性。
内省之获取BeanInfo
我们这里不想去对JavaBean规范做过多的介绍,所以也就不在多介绍BeanInfo的”出身”了。你只需要知道如何得到它,以及BeanInfo有什么。
通过java.beans.Introspector的getBeanInfo()方法来获取java.beans.BeanInfo实例。
1 | BeanInfo beanInfo = Introspector.getBeanInfo(User.class); |
得到所有属性描述符(PropertyDescriptor)
通过BeanInfo可以得到这个类的所有JavaBean属性的PropertyDescriptor对象。然后就可以通过PropertyDescriptor对象得到这个属性的getter/setter方法的Method对象了。
1 | PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); |
每个PropertyDescriptor对象对应一个JavaBean属性:
String getName():获取JavaBean属性名称;
Method getReadMethod:获取属性的读方法;
Method getWriteMethod:获取属性的写方法。
完成Map数据封装到User对象中
1 | public void fun1() throws Exception { |
commons-beanutils
提到内省,不能不提commons-beanutils这个工具。它底层使用了内省,对内省进行了大量的简化!
使用beanutils需要的jar包:
commons-beanutils.jar;
commons-logging.jar;
设置JavaBean属性
1 | User user = new User(); |
获取JavaBean属性
1 | User user = new User("admin", "admin123"); |
封装Map数据到JavaBean对象中
1 | Map<String,String> map = new HashMap<String,String>(); |
JSP与JavaBean相关的动作标签
在JSP中与JavaBean相关的标签有:
<jsp:useBean>
:创建JavaBean对象;<jsp:setProperty>
:设置JavaBean属性;<jsp:getProperty>
:获取JavaBean属性;
我们需要先创建一个JavaBean类:
1 | // User.java |
<jsp:useBean>
<jsp:useBean>
标签的作用是创建JavaBean对象:
在当前JSP页面创建JavaBean对象;
把创建的JavaBean对象保存到域对象中;
1 | <jsp:useBean id="user1" class="tt.User" /> |
上面代码表示在当前JSP页面中创建User类型的对象,并且把它保存到page域中了。下面我们把<jsp:useBean>
标签翻译成Java代码:
1 | <% |
这说明我们可以在JSP页面中完成下面的操作:
1 | <jsp:useBean id="user1" class="tt.User" /> |
<jsp:useBean>
标签默认是把JavaBean对象保存到page域,还可以通过scope标签属性来指定保存的范围:
1 | <jsp:useBean id="user1" class="tt.User" scope="page"/> |
<jsp:useBean>
标签其实不一定会创建对象!!!其实它会先在指定范围中查找这个对象,如果对象不存在才会创建,我们需要重新对它进行翻译:
1 | <jsp:useBean id="user4" class="tt.User" scope="applicatioin"/> |
1 | <% |
<jsp:setProperty>
和<jsp:getProperty>
<jsp:setProperty>
标签的作用是给JavaBean设置属性值,而<jsp:getProperty>
是用来获取属性值。在使用它们之前需要先创建JavaBean:
1 | <jsp:useBean id="user1" class="tt.User" /> |
EL(表达式语言)
EL概述
EL的作用
JSP2.0要把html和css分离、要把html和javascript分离、要把Java脚本替换成标签。标签的好处是非Java人员都可以使用。
JSP2.0 – 纯标签页面,即:不包含<% … %>、<%! … %>,以及<%= … %>
EL(Expression Language)是一门表达式语言,它对应<%=…%>。我们知道在JSP中,表达式会被输出,所以EL表达式也会被输出。
EL的格式
格式:${…}
例如:${1 + 2}
关闭EL
如果希望整个JSP忽略EL表达式,需要在page指令中指定isELIgnored=”true”。
如果希望忽略某个EL表达式,可以在EL表达式之前添加”",例如:\${1 + 2}
。
EL运算符
运算符 | 说明 | 范例 | 结果 |
---|---|---|---|
+ |
加 | ${17+5} |
22 |
- |
减 | ${17-5} |
12 |
* |
乘 | ${17*5} |
85 |
/ 或div |
除 | ${17/5}或${17 div 5} |
3 |
% 或mod |
取余 | ${17%5}或${17 mod 5} |
2 |
== 或eq |
等于 | ${5==5}或${5 eq 5} |
true |
!= 或ne |
不等于 | ${5!=5}或${5 ne 5} |
false |
< 或lt |
小于 | ${3<5}或${3 lt 5} |
true |
> 或gt |
大于 | ${3>5}或${3 gt 5} |
false |
<= 或le |
小于等于 | ${3<=5}或${3 le 5} |
true |
>= 或ge |
大于等于 | ${3>=5}或${3 ge 5} |
false |
&&或and | 并且 | ${true&&false}或${true and false} |
false |
!或not | 非 | ${!true}或${not true} |
false |
||或or | 或者 | ` ${true | |
empty | 是否为空 | ${empty ""} ,可以判断字符串、数据、集合的长度是否为0,为0返回true。empty还可以与not或!一起使用。${not empty ""} |
true |
EL不显示null
当EL表达式的值为null时,会在页面上显示空白,即什么都不显示。
EL表达式格式
先来了解一下EL表达式的格式!现在还不能演示它,因为需要学习了EL11个内置对象后才方便显示它。
操作List和数组:
${list[0]}
、${arr[0]}
;操作bean的属性:
${person.name}
、${person['name']}
,对应person.getName()
方法;操作Map的值:
${map.key}
、${map['key']}
,对应map.get(key)
。
EL内置对象
EL一共11个内置对象,无需创建即可以使用。这11个内置对象中有10个是Map类型的,最后一个是pageContext对象。
pageScope
requestScope
sessionScope
applicationScope
param;
paramValues;
header;
headerValues;
initParam;
cookie;
pageContext;
域相关内置对象(重点)
域内置对象一共有四个:
pageScope:
${pageScope.name}
等同与pageContext.getAttribute("name");
requestScope:
${requestScope.name}
等同与request.getAttribute("name");
sessionScoep:
${sessionScope.name}
等同与session.getAttribute("name");
applicationScope:
${applicationScope.name}
等同与application.getAttribute("name");
如果在域中保存的是JavaBean对象,那么可以使用EL来访问JavaBean属性。因为EL只做读取操作,所以JavaBean一定要提供get方法,而set方法没有要求。
1 | // Person.java |
1 | <body> |
全域查找:${person}表示依次在pageScope、requesScopet、sessionScope、appliationScope四个域中查找名字为person的属性。
请求参数相关内置对象
param和paramValues这两个内置对象是用来获取请求参数的。
- param:Map<String,String>类型,param对象可以用来获取参数,与request.getParameter()方法相同。
注意,在使用EL获取参数时,如果参数不存在,返回的是空字符串,而不是null。这一点与使用request.getParameter()方法是不同的。
- paramValues:paramValues是Map<String, String[]>类型,当一个参数名,对应多个参数值时可以使用它。
请求头相关内置对象
header和headerValues是与请求头相关的内置对象:
header: Map<String,String>类型,用来获取请求头。
headerValues:headerValues是Map<String,String[]>类型。当一个请求头名称,对应多个值时,使用该对象,这里就不在赘述。
应用初始化参数相关内置对象
- initParam:initParam是Map<String,String>类型。它对应web.xml文件中的
<context-param>
参数。
Cookie相关内置对象
- cookie:cookie是Map<String,Cookie>类型,其中key是Cookie的名字,而值是Cookie对象本身。
pageContext对象
pageContext:pageContext是PageContext类型!可以使用pageContext对象调用getXXX()方法,例如pageContext.getRequest(),可以${pageContext.request}。也就是读取JavaBean属性!!!
EL表达式 | 说明 |
---|---|
${pageContext.request.queryString} | pageContext.getRequest().getQueryString(); |
${pageContext.request.requestURL} | pageContext.getRequest().getRequestURL(); |
${pageContext.request.contextPath} | pageContext.getRequest().getContextPath(); |
${pageContext.request.method} | pageContext.getRequest().getMethod(); |
${pageContext.request.protocol} | pageContext.getRequest().getProtocol(); |
${pageContext.request.remoteUser} | pageContext.getRequest().getRemoteUser(); |
${pageContext.request.remoteAddr} | pageContext.getRequest().getRemoteAddr(); |
${pageContext.session.new} | pageContext.getSession().isNew(); |
${pageContext.session.id} | pageContext.getSession().getId(); |
${pageContext.servletContext.serverInfo} | pageContext.getServletContext().getServerInfo(); |
EL函数库
什么EL函数库
EL函数库是由第三方对EL的扩展,我们现在学习的EL函数库是由JSTL添加的。JSTL明天再学!
EL函数库就是定义一些有返回值的静态方法。然后通过EL语言来调用它们!当然,不只是JSTL可以定义EL函数库,我们也可以自定义EL函数库。
EL函数库中包含了很多对字符串的操作方法,以及对集合对象的操作。例如:${fn:length(“abc”)}
会输出3,即字符串的长度。
导入函数库
因为是第三方的东西,所以需要导入。导入需要使用taglib指令!
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
EL函数库介绍
- String toUpperCase(String input):
- String toLowerCase(String input):
- int indexOf(String input, String substring):
- boolean contains(String input, String substring):
- boolean containsIgnoreCase(String input, String substring):
- boolean startsWith(String input, String substring):
- boolean endsWith(String input, String substring):
- String substring(String input, int beginIndex, int endIndex):
- String substringAfter(String input, String substring):
- substringBefore(String input, String substring):
- String escapeXml(String input):
- String trim(String input):
- String replace(String input, String substringBefore, String substringAfter):
- String[] split(String input, String delimiters):
- int length(Object obj):
- String join(String array[], String separator):
1 | <%"fn" uri="http://java.sun.com/jsp/jstl/functions" %> prefix= |
自定义EL函数库
写一个类,写一个有返回值的静态方法;
编写itcast.tld文件,可以参数fn.tld文件来写,把itcast.tld文件放到/WEB-INF目录下;
在页面中添加taglib指令,导入自定义标签库。
1 | // ItcastFuncations.java |
itcast.tld(放到classes下)
1 |
|
index.jsp
1 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> |
Day 08
02/07/2020
JSTL 标签库
什么是JSTL
JSTL是apache对EL表达式的扩展(也就是说JSTL依赖EL),JSTL是标签语言!JSTL标签使用以来非常方便,它与JSP动作标签一定,只不过它不是JSP内置的标签,需要我们自己导包,以及指定标签库而已!
如果你使用MyEclipse开发JavaWeb,那么在把项目发布到Tomcat时,你会发现,MyEclipse会在lib目录下存放jstl的Jar包!如果你没有使用MyEclipse开发那么需要自己来导入这个JSTL的Jar包:jstl-1.2.jar。
JSTL标签库
JSTL一共包含四大标签库:
core:核心标签库,我们学习的重点;
fmt:格式化标签库,只需要学习两个标签即可;
sql:数据库标签库,不需要学习了,它过时了;
xml:xml标签库,不需要学习了,它过时了。
使用taglib指令导入标签库
除了JSP动作标签外,使用其他第三方的标签库都需要:
导包;
在使用标签的JSP页面中使用taglib指令导入标签库;
下面是导入JSTL的core标签库:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
这里的/jsp需要注意*
prefix="c"
:指定标签库的前缀,这个前缀可以随便给值,但大家都会在使用core标签库时指定前缀为c;uri="http://java.sun.com/jstl/core"
:指定标签库的uri,它不一定是真实存在的网址,但它可以让JSP找到标签库的描述文件;
core标签库常用标签
out和set
out
<c:out value=”aaa”/> |
输出aaa字符串常量 |
---|---|
<c:out value=”${aaa}”/> |
与${aaa}相同 |
<c:out value=”${aaa}” default=”xxx”/> |
当${aaa}不存在时,输出xxx字符串 |
<% request.setAttribute("a","<script>alert('hello');</script>"); %> <c:out value="${a }" default="xxx" escapeXml="false" /> |
当escapeXml为false,不会转换“<”、“>”。这可能会受到JavaScript攻击。 |
set
<c:set var=”a” value=”hello”/> |
在pageContext中添加name为a,value为hello的数据。 |
---|---|
<c:set var=”a” value=”hello” scope=”session”/> |
在session中添加name为a,value为hello的数据。 |
remove
<% pageContext.setAttribute("a", "pageContext"); request.setAttribute("a", "session"); session.setAttribute("a", "session"); application.setAttribute("a", "application"); %> <c:remove var="a"/> <c:out value="${a }" default="none"/> |
删除所有域中name为a的数据! |
---|---|
<c:remove var="a" scope=”page”/> |
删除pageContext中name为a的数据! |
url
url标签会在需要URL重写时添加sessionId。
<c:url value="/"/> |
输出上下文路径:/day08_01/ |
---|---|
<c:url value="/" var="a" scope="request"/> |
把本该输出的结果赋给变量a。范围为request |
<c:url value="/AServlet"/> |
输出:/day08_01/AServlet |
<c:url value="/AServlet"> <c:param name="username" value="abc"/> <c:param name="password" value="123"/> </c:url> |
输出:/day08_01/AServlet?username=abc&password=123 如果参数中包含中文,那么会自动使用URL编码! |
if
if标签的test属性必须是一个boolean类型的值,如果test的值为true,那么执行if标签的内容,否则不执行。
1 | <c:set var="a" value="hello"/> |
choose
choose标签对应Java中的if/else if/else结构。when标签的test为true时,会执行这个when的内容。当所有when标签的test都为false时,才会执行otherwise标签的内容。
1 | <c:set var="score" value="${param.score }"/> |
forEach
forEach当前就是循环标签了,forEach标签有多种两种使用方式:
使用循环变量,指定开始和结束值,类似for(int i = 1; i <= 10; i++) {};
循环遍历集合,类似for(Object o : 集合);
1 | <c:set var="sum" value="0" /> |
遍历集合和数组
1 | <% |
遍历list
1 | <% |
遍历map
1 | <% |
forEach标签还有一个属性:varStatus,这个属性用来指定接收“循环状态”的变量名,例如:<forEach varStatus=”vs” …/>
,这时就可以使用vs这个变量来获取循环的状态了。
count:int类型,当前以遍历元素的个数;
index:int类型,当前元素的下标;
first:boolean类型,是否为第一个元素;
last:boolean类型,是否为最后一个元素;
current:Object类型,表示当前项目。
1 | <c:forEach var="item" items="${ns }" varStatus="vs"> |
fmt标签库常用标签
fmt标签库是用来格式化输出的,通常需要格式化的有时间和数字。
格式化时间:
1 | <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> |
格式化数字:
1 | <% |
自定义标签
自定义标签概述
自定义标签的步骤
其实我们在JSP页面中使用标签就等于调用某个对象的某个方法一样,例如:<c:if test=””>,这就是在调用对象的方法一样。自定义标签其实就是自定义类一样!
定义标签处理类:必须是Tag或SimpleTag的实现类;
编写标签库描述符文件(TLD);
SimpleTag接口是JSP2.0中新给出的接口,用来简化自定义标签,所以现在我们基本上都是使用SimpleTag。
Tag是老的,传统的自定义标签时使用的接口,现在不建议使用它了。
SimpleTag接口介绍
SimpleTag接口内容如下:
void doTag():标签执行方法;
JspTag getParent():获取父标签;
void setParent(JspTag parent):设置父标签
void setJspContext(JspContext context):设置PageContext
void setJspBody(JspFragment jspBody):设置标签体对象;
请记住,万物皆对象!在JSP页面中的标签也是对象!你可以通过查看JSP的“真身”清楚的知道,所有标签都会变成对象的方法调用。标签对应的类我们称之为“标签处理类”!
标签的生命周期:
当容器(Tomcat)第一次执行到某个标签时,会创建标签处理类的实例;
然后调用setJspContext(JspContext)方法,把当前JSP页面的pageContext对象传递给这个方法;
如果当前标签有父标签,那么使用父标签的标签处理类对象调用setParent(JspTag)方法;
如果标签有标签体,那么把标签体转换成JspFragment对象,然后调用setJspBody()方法;
每次执行标签时,都调用doTag()方法,它是标签处理方法。
1 | // HelloTag.java |
标签库描述文件(TLD)
标签库描述文件是用来描述当前标签库中的标签的!标签库描述文件的扩展名为tld,你可以把它放到WEB-INF下,这样就不会被客户端直接访问到了。
hello.tld
1 |
|
使用标签
在页面中使用标签分为两步:
使用taglib导入标签库;
使用标签;
1 | <%@ taglib prefix="it" uri="/WEB-INF/hello.tld" %> |
自定义标签进阶
继承SimpleTagSupport
继承SimpleTagSuppport要比实现SimpleTag接口方便太多了,现在你只需要重写doTag()方法,其他方法都已经被SimpleTagSuppport完成了。
1 | public class HelloTag extends SimpleTagSupport { |
有标签体的标签
我们先来看看标签体内容的可选值:
<body-content>
元素的可选值有:
empty:无标签体。
JSP:传统标签支持它,SimpleTag已经不再支持使用
<body-content>JSP</body-content>
。标签体内容可以是任何东西:EL、JSTL、<%=%>、<%%>,以及html;scriptless:标签体内容不能是Java脚本,但可以是EL、JSTL等。在SimpleTag中,如果需要有标签体,那么就使用该选项;
tagdependent:标签体内容不做运算,由标签处理类自行处理,无论标签体内容是EL、JSP、JSTL,都不会做运算。这个选项几乎没有人会使用!
自定义有标签体的标签需要:
获取标签体对象:JspFragment jspBody = getJspBody();;
把标签体内容输出到页面:jspBody.invoke(null);
tld中指定标签内容类型:scriptless。
1 | public class HelloTag extends SimpleTagSupport { |
1 | <tag> |
1 | <itcast:hello> |
不执行标签下面的页面内容
如果希望在执行了自定义标签后,不再执行JSP页面下面的东西,那么就需要在doTag()方法中使用SkipPageException。
1 | public class SkipTag extends SimpleTagSupport { |
1 | <tag> |
1 | <itcast:skip/> |
带有属性的标签
一般标签都会带有属性,例如<c:if test=””>
,其中test就是一个boolean类型的属性。完成带有属性的标签需要:
在处理类中给出JavaBean属性(提供get/set方法);
在TLD中部属相关属性。
1 | public class IfTag extends SimpleTagSupport { |
1 | <tag> |
1 | <% |
MVC
MVC设计模式
MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
MVC模式最早为Trygve Reenskaug)提出,为施乐帕罗奥多研究中心(Xerox PARC)的Smalltalk语言发明的一种软件设计模式。
MVC可对程序的后期维护和扩展提供了方便,并且使程序某些部分的重用提供了方便。而且MVC也使程序简化,更加直观。
控制器Controller:对请求进行处理,负责请求转发;
视图View:界面设计人员进行图形界面设计;
模型Model:程序编写程序应用的功能(实现算法等等)、数据库管理;
注意,MVC不是Java的东西,几乎现在所有B/S结构的软件都采用了MVC设计模式。但是要注意,MVC在B/S结构软件并没有完全实现,例如在我们今后的B/S软件中并不会有事件驱动!
JavaWeb与MVC
JavaWeb的经历了JSP Model1、JSP Model1二代、JSP Model2三个时期。
JSP Model1第一代
JSP Model1是JavaWeb早期的模型,它适合小型Web项目,开发成本低!Model1第一代时期,服务器端只有JSP页面,所有的操作都在JSP页面中,连访问数据库的API也在JSP页面中完成。也就是说,所有的东西都耦合在一起,对后期的维护和扩展极为不利。
JSP Model1第二代
JSP Model1第二代有所改进,把业务逻辑的内容放到了JavaBean中,而JSP页面负责显示以及请求调度的工作。虽然第二代比第一代好了些,但还让JSP做了过多的工作,JSP中把视图工作和请求调度(控制器)的工作耦合在一起了。
JSP Model2
JSP Model2模式已经可以清晰的看到MVC完整的结构了。
JSP:视图层,用来与用户打交道。负责接收用来的数据,以及显示数据给用户;
Servlet:控制层,负责找到合适的模型对象来处理业务逻辑,转发到合适的视图;
JavaBean:模型层,完成具体的业务工作,例如:开启、转账等。
JSP Model2适合多人合作开发大型的Web项目,各司其职,互不干涉,有利于开发中的分工,有利于组件的重用。但是,Web项目的开发难度加大,同时对开发人员的技术要求也提高了。
JavaWeb三层框架
我们常说的三层框架是由JavaWeb提出的,也就是说这是JavaWeb独有的!
所谓三层是表述层(WEB层)、业务逻辑层(Business Logic),以及数据访问层(Data Access)。
WEB(表述)层:包含JSP和Servlet等与WEB相关的内容;
业务逻辑层:业务层中不包含JavaWeb API,它只关心业务逻辑,它对应功能;
数据访问层:封装了对数据库的访问细节;
注意,在业务层中不能出现JavaWeb API,例如request、response等。也就是说,业务层代码是可重用的,甚至可以应用到非Web环境中。业务层的每个方法可以理解成一个万能,例如转账业务方法。业务层依赖数据层,而Web层依赖业务层!
Day 09
03/07/2020
案例 用户注册登录
要求:3层框架,使用验证码
功能分析
注册
登录
JSP页面
regist.jsp
- 注册表单:用户输入注册信息;
- 回显错误信息:当注册失败时,显示错误信息;
login.jsp
- 登录表单:用户输入登录信息;
- 回显错误信息:当登录失败时,显示错误信息;
index.jsp
- 用户已登录:显示当前用户名,以及“退出”链接;
- 用户未登录:显示“您还没有登录”;
实体类
User:
String username;
String password;
Servlet
VerifyCodeServlet
- 生成验证码;
- 在session中保存验证码文本;
- 把图片输出到页面
RegistServlet
获取用户名、密码,封装到User对象中;
获取验证码、获取确认密码;
校验用户名、密码、验证码不能为空,校验失败,向request中保存错误信息,转发回regist.jsp显示错误信息;
比较两次输入的错误是否一致,如果不一致,向request中保存错误信息,转发回regist.jsp显示错误信息;
获取session中的验证码,与表单输入的验证码比较,如果不一致,向request中保存错误信息,转发回regist.jsp显示错误信息;
使用UserService的regist()方法完成注册,如果注册失败,向request中保存错误信息,转发回regist.jsp显示错误信息,如果注册成功,转发到login.jsp页面,表示注册成功;
LoginServlet
获取用户名、密码、验证码;
校验用户名、密码、验证码是否为空,校验失败,向request中保存错误信息,转发回login.jsp显示错误信息;
获取session中的验证码,与表单中的验证码比较,如果不同,向request中保存错误信息,转发回login.jsp显示错误信息;
删除session中的验证码;
通过UserService的login()方法完成登录,如果抛出异常,获取异常信息,保存到request中,转发到login.jsp显示错误信息;
向session中保存当前用户对象;
转发到index.jsp页面,表示登录成功!
QuitServlet
获取session,销毁之;
重定向到index.jsp;
Service
UserException:为UserService使用的异常类;
UserService:
void regist(User user):
使用UserDao的findByUsername()方法查询名为user.getUsername()的用户,如果用户存在,说明用户名已经被注册,抛出异常;
使用UserDao的add(User)方法保存用户信息;
User login(String username, String password):
使用UserDao的findByUsername()方法查询名为user.getUsername()的用户,如果用户不存在,说明用户名错误,抛出异常;
如果查询到了User,那么比较参数password与user.getPassword()是否相等,如果不等,说明密码错误,抛出异常;
如果一致,表示登录成功,返回User对象;
DAO
UserDao:
void add(User):
创建SAXReader对象,获取Document对象,再获取根元素;
给root元素添加子元素;
给子元素设置username属性,值为user.getUsername();
给子元素设置password属性,值为user.getPassword();
创建OutputFormat对象,指定缩进为“\t”,指定添加换行;
设置OutputFormat清空原有空白;
使用FileWrtier和OutputFormat创建XMLWriter对象;
使用XMLWriter对象的write()保存Document;
关闭XMLWriter对象;
User findByUsername(String username):
创建SAXReader对象,获取Document对象;
使用Xpath(//user[username=’xxx’])来查询元素;
如果元素没有查询到,返回null;
如果元素查询到了,那么创建User对象;
把元素的username属性赋给User的username属性;
把元素的password属性赋给User的password属性;
返回user对象;
流程图
注册
用户在浏览器地址栏中请求regist.jsp;
服务器发送html给浏览器;
浏览器收到html,开始解析,并显示;
解析到
<img>
时,请求VerifyCodeServlet;VerifyCodeServlet生成验证码图片,保存验证码文本,把图片响应给浏览器;
浏览器显示在页面中显示图片。
登录
省略。。。
代码
login.jsp
1 | <body> |
regist.jsp
1 | <body> |
index.jsp
1 | <body> |
1 | // VerifyCodeServlet.java |
1 | // RegistServlet.java |
1 | // LoginServlet.java |
1 | // QuitServlet.java |
1 | // UserException.java |
1 | // UserService.java |
1 | // UserDao.java |
小结
在这里呢小结一下,对于之前这么多天的学习,对JavaWeb有了一个初步的了解,例如Servlet、Tomcat、jsp、xml等相关都有了一定的认识,但是还不够深入,对于某些基础的东西还不够了解,所以在这里这个部分的学习就先告一段落了,之后的学习则是从b站上进行更基础更细节的学习了,整理的东西也不会太多了。在那上面学习完成之后,还会在这里继续之前没完成的数据库部分,因为本人已经在大学中学习了数据库这门课了,所以对于这部分之后的内容就先不做延申了,之后会来补上。
b站的学习相关博文则会在本博客新开一篇来更新。