JSP介绍
基础知识
网页的静态动态
- 不用 和 是否有“动感”混为一谈
- 是否 随着 时间、地点、用户操作 的 改变而改变
- 动态网页 需要使用到 服务端脚本语言(JSP)
架构
CS:Client Server
不足:
- 如果 软件升级, 那么全部软件都需要升级
- 维护麻烦:需要维护每一台 客户端软件
- 每一台客户端 都需要安装 客户端软件
BS :Broswer Server
- 客户端可以通过 浏览器 直接访问服务端
注意:bs和cs各有优势。
常见状态码:
- 200:一切正常
- 300/301: 页面重定向 (跳转)
- 404:资源不存在
- 403:权限不足 (如果访问a目录,但是a目录设置 不可见)
- 500:服务器内部错误(代码有误)
- 其他编码:积累
get与post请求方式的区别:
get提交方式
连接/文件?参数名1=参数值1 & 参数名2=参数值2 & 参数名1=参数值1
form表单的method=”get” 和 地址栏 、超链接(
<a href="xx">
)请求方式 默认都属于get提交方式- get方式 在地址栏显示请求信息
- 但是地址栏能够容纳的 信息有限,4-5KB;
- 如果请求数据存在大文件,图片等 会出现地址栏无法容纳全部的数据而出错
文件上传操作,必须是post。
- post不会显示请求信息
Tomcat
- 官网:http://tomcat.apache.org/
- Tomcat服务器是一个实现了Servlet规范和JSP规范的容器
tomcat解压目录
1 | bin:可执行文件(startup.bat shutdown.bat) |
配置tomcat
- 配置jdk (必须配置JAVA_HOME)
- java_home,classPath,path
- 配置catalina_home
- 双击bin/startup.bat启动tomacat,
- 常见错误: 可能与其他服务的端口号冲突
- tomcat端口号默认8080 (此端口号较为常见,容易冲突),建议修改此端口 (8888)
修改端口号
找到tomcat\conf\server.xml文件
打开server.xml文件,找到
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
,把port = “8080”修改为指定的端口。重新启动tomcat,访问 http://localhost:8888/
虚拟路径
jsp:在html中嵌套的java代码
在项目/WEB-INF/web.xml中设置 默认的 初始页面
1
2
3<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>JSP执行流程
- jsp- java(Servlet文件) -class
- class存放路径
D:\study\apache-tomcat-8.5.30\work\Catalina\localhost\JspProject\org\apache\jsp
Jsp 和Servlet 可以相互转换
- 因为第一请求服务端 会有翻译和编译的过程,因此比较慢; 后续访问可以直接访问class,因此速度较快。但是 如果服务端修改了代码,则再次访问时 会重新的翻译、编译。
将web项目配置到 webapps以外的目录
conf/server.xml中配置host标签中:
- docBase:实际路径
- path:虚拟路径 (绝对路径、相对路径【相对于webapps】)
1
<Context docBase="D:\study\JspProject" path="/JspProject" />
重启,访问
Catalina文件夹下配置
D:\study\apache-tomcat-8.5.30\conf\Catalina\localhost
中新建 “项目名.xml”中新增一行:1
<Context docBase="D:\study\JspProject" path="/JspProject" />
配置虚拟主机访问
通过
www.test.com
访问本机修改 conf/server.xml
1
<Engine name="Catalina" defaultHost="www.test.com">
1
2
3<Host appBase="D:\study\JspProject" name="www.test.com">
<Context docBase="D:\study\JspProject" path="/"/>
</Host>修改host
C:\Windows\System32\drivers\etc\host
- 增加
1
127.0.0.1 www.test.com
访问流程:
1
www.test.com -> host找映射关系 ->server.xml找Engine的defaultHost ->通过"/"映射到欢迎页
重启Tomcat时机
一般而言,修改web.xml、配置文件、java 代码需要重启tomcat服务
但是如果修改 Jsp\html\css\js ,不需要重启
使用Eclipse开发Web项目
(JSP项目) tomcat
在Eclipse中创建的Web项目:
浏览器可以直接访问 WebContent中的文件,
其中的index1.jsp就在WebContent目录中;
但是WEB-INF中的文件 无法通过客户端(浏览器)直接访问,只能通过请求转发来访问
注意:并不是任何的内部跳转都能访问WEB-INF;原因是跳转有2种方式:请求转发 、重定向
配置tomcat运行时环境
JSP<=>Servlet
将tomcat/lib中的
servlet-api.jar
加入项目的构建路径右键项目->Build Path -> Add library ->Server Runtime
部署tomcat
在servers面板 新建一个 tomcat实例 , 再在该实例中 部署项目(右键-add)
,之后运行注意:一般建议 将eclipse中的tomcat与 本地tomcat的配置信息保持一致: 将eclipse中的tomcat设置为托管模式:
托管模式必须在eclipse中Tomcat运行之前设置,如果已经运行过,则需要删除重新关联
【第一次】创建tomcat实例之后, 双击,选择Server Location的第二项
统一字符集编码
编码分类:
设置jsp文件的编码(jsp文件中的pageEncoding属性): jsp -> java
设置浏览器读取jsp文件的编码(jsp文件中content属性)
一般将上述设置成 一致的编码,推荐使用UTF-8
文本编码:
将整个eclipse中的文件 统一设置 (推荐)
设置 某一个项目
设置单独文件
运行Servlet程序、访问测试
- 在弹出的窗口中,直接点击完成即可!!!
发布的过程如下
JSP
- JAVA serve pages
- JSP的页面元素:HTML+JAVA代码(脚本Scriptlet)+指令+注释
Java脚本编写
jsp中编写java代码
1
2
3
4
5
6
7<% 局部变量、java语句 %>
<%! 全局变量、定义方法 %>
<%=输出表达式 %>
<%
out.println("Hello World!");
%>
指令
page指令:
<%@ page ....%>
page指定的属性:
- language: jsp页面使用的脚本语言
- import: 导入类
- pageEncoding: jsp文件自身编码 jsp ->java
- contentType:浏览器解析jsp的编码
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
注释
HTML注释
- 可以被客户 通过浏览器查看源码 所观察到
1
<!-- -->
JAVA注释
1
// /*...*/
JSP注释
1
<%-- --%>
JSP九大内置对象
- 自带的,不需要new 也能使用的对象
request:请求对象
存储“客户端向服务端发送的请求信息”
request对象的常见方法:
name description String getParameter(String name) 根据请求的字段名key (input标签的name属性值) ,返回字段值value (input标签的value属性值) String[] getParameterValues(String name) 根据请求的字段名key ,返回多个字段值value (checkbox) void setCharacterEncoding (“编码格式utf-8”) :设置post方式的请求编码 (tomcat7以前默认iso-8859-1,tomcat8以后改为了utf-8) getRequestDispatcher(“b.jsp”) .forward(request,response) 请求转发 的方式跳转页面 A - > B ServletContext getServerContext() 获取项目的ServletContext对象
response :响应对象
提供的方法
function description void addCookie( Cookie cookie );
服务端向客户端增加cookie对象 void sendRedirect(String location ) throws IOException; 页面跳转的一种方式(重定向) void setContetType(String type) 设置服务端响应的编码(设置服务端的contentType类型)
session 会话对象
import javax.servlet.http.HttpSession;
session存储在服务端, Cookie(客户端,不是内置对象):
- Cookie是由 服务端生成的 ,再发送给客户端保存。
- 相当于本地缓存的作用: 客户端(hello.mp4,zs/abc)->服务端(hello.mp4;zs/abc)
- 作用:提高访问服务端的效率,但是安全性较差。
实现机制:第一次客户请求时 产生一个
sessionId
并复制给 cookie的JsessionId
然后发给客户端。最终通过sessionId和JsessionId判断是不是同一个会话。
session机制
客户端第一次请求服务端时,服务端会产生一个session对象(用于保存该客户的信息);
并且每个session对象 都会有一个唯一的
sessionId
( 用于区分其他session);服务端会 产生一个cookie,并且 该cookie的name=
JsessionId
,value=服务端sessionId
的值;然后 服务端会在 响应客户端的同时 将该cookie发送给客户端,至此 客户端就有了 一个cookie(
JsessionId
);因此,客户端的cookie就可以和服务端的session一一对应(
JsessionId
-sessionId
)客户端第二/n次请求服务端时:服务端会先用客户端cookie种的
JsessionId
去服务端的session中匹配sessionId
,如果匹配成功(cookie:jsessionId和session:sessionId相等),说明此用户不是第一次访问,无需登录;场景理解:
客户端: 顾客(客户端)
服务端: 存包处 - 商场(服务端)
顾客第一次存包:商场 判断此人是 之前已经存过包(通过你手里是否有钥匙)。
如果是新顾客(没钥匙) ,分配一个钥匙 给该顾客; 钥匙 会和 柜子 一一对应;第二/n次 存包:商场 判断此人是 之前已经存过包(通过你手里是否有钥匙)
如果是老顾客(有钥匙),则不需要分配;该顾客手里的钥匙 会 和柜子 自动一一对应。
session方法
function | description |
---|---|
String getId() | 获取sessionId |
boolean isNew() | 判断是否是 新用户(第一次访问) |
void invalidate() | 使session失效 (退出登录、注销) |
void setAttribute(String name, Object value) | |
Object getAttribute(); | |
void setMaxInactiveInterval(秒) | 设置最大有效 非活动时间 |
int getMaxInactiveInterval() | 获取最大有效 非活动时间 |
几个问题
- 浏览器关闭之后,服务器端对应的 Session 对象会被销毁吗?为什么?
- 浏览器关闭之后,服务器不会销毁 session 对象
- 因为 B/S 架构的系统基于 HTTP 协议,而 HTTP 协议是一种无连接/无状态的协议
- 什么是无连接/无状态?
- 请求的瞬间,浏览器和服务器之间的通道是打开的,请求响应结束之后,通道关闭
- 这样做的目的是降低服务器的压力
- session 对象在什么时候被销毁?
- web 系统中引入了 session 超时的概念
- 当很长一段时间(这个时间可以配置)没有用户再访问该 session 对象,此时 session 对象超时,web 服务器自动回收 session 对象
- 可配置:web.xml 文件中,默认是30分钟
<session-config> <session-timeout>120</session-timeout> </session-config>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-
### application全局对象
- String getContextPath() 虚拟路径
- String getRealPath(String name): 绝对路径(虚拟路径 相对的绝对路径)
### 其他五种不常用对象
| Object | description |
| :---------- | :------------------------------------ |
| pageContext | JSP页面容器 |
| config | 配置对象(服务器配置信息) |
| page | 当前JSP页面对象(相当于java中的this) |
| exception | 异常对象 |
| out | 输出对象,向客户端输出内容 |
### 统一请求的编码request
- get方式请求 如果出现乱码,解决:
- 统一每一个变量的 编码 (不推荐)
```java
new String( 旧编码,新编码);
name = new String(name.getBytes("iso-8859-1"),"utf-8");
修改server.xml ,一次性的 更改tomcat默认get提交方式的编码 (utf-8)
- 建议 使用tomcat时, 首先在server.xml中 统一get方式的编码..
URIEncoding="UTF-8"
- tomcat7 (iso-8859-1)
- tomcat8(utf-8)
- 建议 使用tomcat时, 首先在server.xml中 统一get方式的编码..
post指定字符编码
1
request.setCharacterEncoding("utf-8")
请求转发和重定向
转发:
- 张三(客户端) -> 【 服务窗口 A (服务端 ) -> 服务窗口B 】
重定向:
- 张三(客户端) -> 服务窗口 A (服务端 ) ->去找B
张三(客户端) -> 服务窗口 B (服务端 ) ->结束
请求转发
1
request.getRequestDispatcher("b.jsp").forward(request,response);
重定向
1
reponse.sendRedirect("b.jsp")
请求转发 | 重定向 | |
---|---|---|
地址栏是否改变 | 不变(check.jsp) | 改变(success.jsp) |
是否保留第一次请求时的数据 | 保留 | 不保留 |
请求的次数 | 1 | 2 |
跳转发生的位置 | 服务端 | 客户端发出的第二次跳转 |
四种范围对象
pageContext 当前页面有效 (页面跳转后无效)
request 同一次请求有效;其他请求无效 (请求转发后有效;重定向后无效)
session 同一次会话有效 (无论怎么跳转,都有效;关闭/切换浏览器后无效 ; 从 登陆->退出 之间 全部有效)
application 全局变量;整个项目运行期间都有效 (切换浏览器 仍然有效);关闭服务、其他项目 无效
多个项目共享、重启后仍然有效 :JNDI
生命周期(小->大)
object description scope pageContext JSP页面容器 (page对象); 当前页面有效 request 请求对象 同一次请求有效 session 会话对象 同一次会话有效 application 全局对象 全局有效(整个项目有效) 以上4个对象共有的方法:
function description Object getAttribute(String name)
根据属性名,获取属性值 void setAttribute(String name,Object obj)
设置属性值(新增,修改) void removeAttribute(String name)
根据属性名,删除对象 - 以上的4个范围对象,通过 setAttribute()复制,通过getAttribute()取值;
- 以上范围对象,尽量使用最小的范围。因为 对象的范围越大,造成的性能损耗越大。
Servlet
Java类必须符合一定的 规范:
必须继承 javax.servlet.http.HttpServlet
重写其中的service()之类的方法
1
2
3service(): 接受并识别和处理相应的所有提交的方法;
doGet(): 接受并处理所有get提交方式的请求
doPost(): 接受并处理所有post提交方式的请求Servlet要想使用,必须配置web.xml
Serlvet2.5:
1
2
3
4
5
6
7
8
9
10
11
12<web-app>
...
<servlet>
<servlet-name>WelcomeServlet</servlet-name>
<servlet-class>org.lanqiao.servlet.WelcomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WelcomeServlet</servlet-name>
<url-pattern>/WelcomeServlet</url-pattern>
</servlet-mapping>
</web-app>Servlet3.0:
@WebServlet
1
项目的根目录:WebContent 、src
<a href="WelcomeServlet">
所在的jsp是在 WebContent目录中,因此 发出的请求WelcomeServlet 是去请求项目的根目录。
Servlet流程
- 请求 ->
<url-pattern>
-> 根据<servlet-mapping>
中的<servlet-name>
去匹配<servlet>
中的<servlet-name>
,然后寻找到,求中将请求交由该执行。
编写Servlet步骤
导包
1
import javax.servlet.http.HttpServlet;
编写一个类继承HttpServlet
1
public class LoginServlet extends HttpServlet {...}
重写相应方法(doGet()、doPost())
配置Servlet
编写web.xml 中的servlet映射关系
Servlet3.0,与Servlet2.5的区别:
- Servlet3.0不需要在web.xml中配置,但需要在 Servlet类的定义处之上编写注解:
@WebServlet(“url-pattern的值”)
- 注意:url-pattern 要带斜杆
- Servlet3.0不需要在web.xml中配置,但需要在 Servlet类的定义处之上编写注解:
匹配流程: 请求地址 与
@WebServlet
中的值进行匹配,如果匹配成功 ,则说明 请求的就是该注解所对应的类
借助于Eclipse快速生成Servlet
- 直接新建Servlet即可!(继承、重写、web.xml 可以借助Eclipse自动生成)
项目根目录
WebContent、src(所有的构建路径)
例如:WebContent中有一个文件index.jsp,src中有一个Servlet.java
如果: index.jsp中请求
<a href="abc">...</a>
,则 寻找范围:既会在src根目录中找 也会在WebContent根目录中找如果:index.jsp中请求
<a href="a/abc"></a>
,寻找范围:先在src或WebContent中找a目录,然后再在a目录中找abc
web.xml中的
/
:代表项目根路径
Servlet生命周期
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
Servlet 通过调用
init ()
方法进行初始化。Servlet 调用
service()
方法来处理客户端的请求。Servlet 通过调用
destroy()
方法终止(结束)。最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
创建 Servlet:init()
方法
init()
方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化。Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时,但是您也可以指定 Servlet 在服务器第一次启动时被加载。
可以修改为 Tomcat启动时自动执行
Servlet2.5: web.xml
- 1自然数越小优先级越高。
1
2
3
4<servlet>
...
<load-on-startup>1</load-on-startup>
</servlet>Servlet3.0
1
@WebServlet( value="/WelcomeServlet" ,loadOnStartup=1 )
当用户调用一个 Servlet 时,就会创建一个 Servlet 实例,每一个用户请求都会产生一个新的线程,适当的时候移交给 doGet 或 doPost 方法。init() 方法简单地创建或加载一些数据,这些数据将被用于 Servlet 的整个生命周期。Servlet是线程不安全的,在Servlet类中可能会定义共享的类变量,这样在并发的多线程访问的情况下,不同的线程对成员变量的修改会引发错误。
init 方法的定义如下:
1
2
3public void init() throws ServletException {
// 初始化代码...
}
执行实际任务:service()
方法
service()
方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用service()
方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。
service()
方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用doGet
、doPost
、doPut
,doDelete
等方法。下面是该方法的特征:
1
2
3
4public void service(ServletRequest request,
ServletResponse response)
throws ServletException, IOException{
}service()
方法由容器调用,service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法,我们只需要根据来自客户端的请求类型来重写doGet()
或doPost()
即可。doGet()
和doPost()
方法是每次服务请求中最常用的方法。
doGet()
方法
GET 请求来自于一个 URL 的正常请求,或者来自于一个未指定 METHOD 的 HTML 表单,它由
doGet()
方法处理。1
2
3
4
5public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Servlet 代码
}
doPost()
方法
POST 请求来自于一个特别指定了 METHOD 为 POST 的 HTML 表单,它由
doPost()
方法处理。1
2
3
4
5public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Servlet 代码
}
Servlet 生命周期结束:destroy()
方法
destroy()
方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy()
方法可以让 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。在调用
destroy()
方法之后,servlet 对象被标记为垃圾回收。destroy 方法定义如下所示:1
2
3public void destroy() {
// 终止化代码...
}
常见问题
下面有关servlet service描述错误的是?
不管是post还是get方法提交过来的连接,都会在service中处理
service()是在javax.servlet.Servlet接口中定义的
service判断请求类型,决定是调用doGet还是doPost方法
错误:doGet/doPost 则是在 javax.servlet.GenericServlet 中实现的
下列有关Servlet的生命周期,说法不正确的是?
- 在Servlet生命周期的服务阶段,执行service()方法,根据用户请求的方法,执行相应的doGet()或是doPost()方法
- 在销毁阶段,执行destroy()方法后会释放Servlet 占用的资源
- destroy()方法仅执行一次,即在服务器停止且卸载Servlet时执行该方法
不正确:在创建自己的Servlet时候,应该在初始化方法init()方法中创建Servlet实例
下面有关servlet中init,service,destroy方法描述错误的是?
init()方法是servlet生命的起点。一旦加载了某个servlet,服务器将立即调用它的init()方法
service()方法处理客户机发出的所有请求
destroy()方法标志servlet生命周期的结束
错误:servlet在多线程下使用了同步机制,因此,在并发编程下servlet是线程安全的
Servlet继承关系
Servlet接口 – 通用的Servlet接口, 提供了一个Servlet应该具有的功能
- GenericServlet类, 实现了Servlet接口, 并实现了其中的大部分的方法, 但是service方法没有实现, 这个方法需要开发人员自己来实现
- HttpServlet类, 继承了GenericServlet, 并实现了service方法, 在service方法中, 判断请求方式, 根据不同的请求方式, 调用不同doXxx
- GenericServlet类, 实现了Servlet接口, 并实现了其中的大部分的方法, 但是service方法没有实现, 这个方法需要开发人员自己来实现
XxxServlet类, 在开发中, 我们只需要写一个类(XxxServlet), 继承HttpServlet, 并覆盖doGet和doPost方法, 来处理GET请求和POST请求即可!!
Servlet API
- 由两个软件包组成: 对应于HTTP协议的软件包、对应于除了HTTP协议以外的其他软件包
- 即Servlet API可以适用于任何通信协议。
- 我们学习的Servlet,是位于
javax.servlet.http
包中的类和接口,是基础HTTP协议。
HttpServletRequest接口
public interface HttpServletRequest extends ServletRequest
HttpServletRequest是一个接口,Servlet规范中重要的接口之一
HttpServletRequest接口的实现类是Web容器负责的,Tomcat服务器有自己的实现。程序员还是只需要面向HttpServletRequest接口调用方法即可,不需要关心具体的实现类
封装的信息
- HttpServletRequest一般对象的名字叫做:request;
- HttpServletRequest对象代表一次请求,一次请求对应一个request对象,100个请求对应100个request对象,所以request对象的生命周期是短暂的;
- 什么是一次请求?到目前为止,我们可以这样理解一次请求:在网页上点击超链接,到最终网页停下来,这就是一次完整的请求【如果是重定向,那这个操作就不止是一次请求了】;
- HttpServletRequest对象封装了HTTP请求协议的全部内容:
- 请求方式
- URI
- 协议版本号
- 表单提交的数据 ……
- 范围对象的选择
- ServletContext应用范围,可以跨用户传递数据
- ServletRequest请求范围,只能在同一个请求中传递数据【可以跨servlet传递数据,但是这几个servlet必须在同一个请求当中】
- 优先选择request范围
- 请求转发可以跨Servlet(跨请求)传递数据,一次请求对应的Servlet中的request对象的数据,可以传递给请求转发的另一个Servlet的request对象
获取信息
获取用户信息,表单提交的这些数据被自动封装在request对象中
格式:username=admin&password=123&sex=boy&interest=sport&interest=music&grade=gz&introduce=ok
表单提交的数据会自动封装在request对象中,request对象中有一个Map集合,存储这些数据,Map集合的key是name,value是一个字符串类型的一维数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Map<String, String[]>
key value(value是数组)
-----------------------------------
username {"admin"}
password {"123"}
sex {"boy"}
interest {"sport","music"}
grade {"gz"}
introduce {"ok"}
常用方法
1 | /*获取浏览器提交的数据(从表单)*/ |
forward【转发】
请求转发,是一次请求
获取请求转发器对象
调用请求转发器的**
forward()
**方法即可完成转发1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/*
/a路径对应AServlet,/b路径对应BServlet,
通过request对象的getRequestDispatcher方法获取请求转发器对象,
然后调用请求转发器对象的forward方法进行转发
以下代码是从AServlet转发到BServlet
*/
//获取请求转发器,以下转发器指向了BServlet
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/b");
//调用请求转发器的forward()
requestDispatcher.forward(request,response);
//也可以合并
request.getRequestDispatcher("/b").forward(request,response);
实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/*AServlet*/
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
User user = new User(111, "admin");
request.setAttribute("user",user); //向request范围中存数据
//转发
request.getRequestDispatcher("/b").forward(request,response);
}
}
/*BServlet*/
public class BServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
Object user = request.getAttribute("user"); //从request范围中取数据
writer.print(user);
}
/*
1. 浏览器向AServlet发出访问请求;
2. AServlet调用forward()方法,在服务器端将请求转发给BServlet;
3. 最终由BServlet做出响应
*/
}
GenericServlet类
缺省适配器模式
不适用缺省适配器模式的缺点
- 假设存在一个接口CommonIn,其中有9个方法,现在有一个类A直接去实现(implements)这个接口,但是A只需要接口中的3个方法,由于A是直接实现了这个接口,所以会有6个方法是空实现,代码十分丑陋。如何解决这个问题,使得代码更加优雅?方法就是使用缺省适配器模式。
使用缺省适配器模式
创建一个类Adapter,让这个类去直接实现CommonIn接口,然后将A类中需要使用的方法都定义为抽象方法。
让A类去继承(extends)Adapter
GenericServlet存在的意义
- 其实GenericServlet就是充当了一个适配器类的角色,使得我们不需要直接去实现Servlet接口【因为这个接口中的许多方法可能是不需要的,直接实现会使得代码丑陋】,而是去继承该适配器类。
- 适配器除了可以使得代码优雅之外,还可以在适配器中提供大量的方法,子类继承之后,可以在子类中直接使用,方便编程。
- SUN公司已经为我们写好了该类,直接使用即可,以下为源码。
package javax.servlet; import java.io.IOException; import java.util.Enumeration; public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { private static final long serialVersionUID = 1L; private transient ServletConfig config; public GenericServlet() { } @Override public void destroy() { } @Override public String getInitParameter(String name) { return getServletConfig().getInitParameter(name); } @Override public Enumeration<String> getInitParameterNames() { return getServletConfig().getInitParameterNames(); } @Override public ServletConfig getServletConfig() { return config; } @Override public ServletContext getServletContext() { return getServletConfig().getServletContext(); } @Override public String getServletInfo() { return ""; } @Override public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { //该空参init方法供子类需要进行重写 //如此设计的目的,若是子类需要重写init方法,就不用覆盖原来有参数的init方法而导致异常 //若是没有该方法,子类还是要重写init(ServletConfig config)方法的话,就必须在重写的init //方法之中第一句话写上super.init(config),如此才能调用父类的init方法 //以此保证this.config = config //而该无参init方法的存在就使得我们不需要再写super.init(config) //因此,以后建议重写无参数的init方法 } public void log(String msg) { getServletContext().log(getServletName() + ": " + msg); } public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); } @Override public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; @Override public String getServletName() { return config.getServletName(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-
### ServletConfig
- `javax.servlet.ServletConfig`·是一个接口
- Apache Tomcat服务器实现了Servlet规范,专门写了一个ServletConfig接口的实现类,实现类的完整类名:`org.apache.catalina.core.StandardWrapperFacade`
- JavaWeb程序员在编程的时候,一直是面向ServletConfig接口去完成调用,不需要关心具体的实现类。webapp放到Tomcat服务器中,ServletConfig的实现类是:`org.apache.catalina.core.StandardWrapperFacade`
- webapp放到JBOSS服务器中,ServletConfig的实现类可能就是另外一个类名了。这些所有的实现类,我们都不需要关心,只需要学习ServletConfig接口中有哪些可以使用的方法
### ServletConfig接口常用方法
| 方法 | 作用 |
| ------------------------------------ | -------------------------------------------------- |
| String getInitParameter(String name) | 在当前Web容器范围内,通过初始化参数name获取value |
| Enumeration getInitParameterNames() | 获取所有初始化参数的name |
| String getServletName() | 获取`<servlet-name>servletname</servlet-name>` |
| ServletContext getServletContext() | 获取ServletContext【Servlet上下文】application对象 |
- ServletContext中的常见方法(application):
- getContextPath():相对路径
- getRealPath():绝对路径
- setAttribute() 、getAttribute()
- 示例
- Servlet3.0方式 给当前Servlet设置初始值:
- 注意,此注解只隶属于某一个具体的Servlet ,因此无法为 整个web容器设置初始化参数 (如果要通过3.0方式设置 web容器的初始化参数,仍然需要在web.xml中设置)
```java
@WebServlet( .... , initParams= {@WebInitParam(name="serveltparaname30",value="servletparavalue30") } )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet>
<servlet-name>AServletConfig</servlet-name>
<servlet-class>com.glutnn.servlet.AServletConfig</servlet-class>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>AServletConfig</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;
public class AServletConfig implements Servlet {
private ServletConfig config;
public void init(ServletConfig config) throws ServletException {
//将局部变量config赋值给实例变量config
//目的:在service方法中也可以使用config
this.config = config;
}
//这个方法是供子类使用的,在子类中若想获取ServletConfig,可以调用这个方法
public ServletConfig getServletConfig() {
return config;
}
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
//获取ServletConfig对象,也可以直接使用,因为在init方法初始化已得到该对象
ServletConfig config = getServletConfig();
//1、通过初始化参数的name获取value
String user = config.getInitParameter("user");
String password = config.getInitParameter("password");
System.out.println("user="+user+"、"+"password="+password);
//2、获取所有初始化参数的name
Enumeration<String> names = config.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value = config.getInitParameter(name);
System.out.println(name+"="+value);
/*
* password=123
user=root
* */
}
//3、获取<servlet-name>servletname</servlet-name>
String servletName = config.getServletName();
System.out.println(servletName);//AServletConfig
//4、获取ServletContext【Servlet上下文】对象
ServletContext servletContext = config.getServletContext();
System.out.println(servletContext);//org.apache.catalina.core.ApplicationContextFacade@3db5c195
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
ServletContext接口
ServletContext
是javax.servlet.ServletContext
接口,Servlet规范- Tomcat服务器对ServletContext接口的实现类完整类名:
org.apache.catalina.core.ApplicationContextFacade
- JvaWeb程序员还是只要面向ServletContext接口调用方法即可,不需要关心Tomcat具体的实现
ServletContext解释
ServletContext被翻译为:Servlet上下文【Context一般翻译为上下文】
一个webapp只有一个web.xml文件,web.xml文件在服务器启动阶段被解析
一个webapp只有一个ServletContext对象,ServletContext在服务器启动阶段被实例化
ServletContext在服务器关闭的时候会被销毁
ServletContext对应的是web.xml文件,是web.xml文件的代表
ServletContext对象是所有Servlet对象四周环境的代表【在同一个webapp中,所有Servlet对象共享同一个“四周环境对象”,该对象就是ServletContext】
所有的用户若共享同一个数据,可以将这个数据放到ServletContext对象中
一般放到ServletContext对象中的数据不建议涉及到修改操作,因为ServletContext对象是多线程共享的一个对象,修改的时候会存在线程安全问题
ServletContext接口方法
方法 | 作用 |
---|---|
void setAttribute(String name,Object object) | 向ServletContext范围中添加数据(map.put(key,value);) |
Object getAttribute(String name) | 从ServletContext范围中获取数据(Object value = map.get(key);) |
void removeAttribute(String name) | 移除ServletContext范围中的数据(map.remove(key);) |
String getInitParameter(String name) | |
Enumeration getInitParameterNames() | |
String getRealPath(String path) |
以上方法演示图如下
Servlet、ServletConfig、ServletContext的关系
- 一个Servlet对应一个ServletConfig,100个Servlet对应100个ServletConfig
- 所有的Servlet共享一个ServletContext对象
- ServletContext范围可以完成跨用户传递数据
ServletContext的范围?
- 所有用户共享的一个范围对象,我们一般把ServletContext变量命名为:application
- 可见这个对象代表一个应用内,一个webapp只有一个这样的对象,范围极大!
HttpServlet类的原理
如何保证前后端处理方式相同
前端页面发送的请求方式要与服务器端需要的请求方式一致
- 服务器需要前端发送POST,那么前端就应该发送POST请求,否则服务器应当提示错误信息
- 服务器需要前端发送GET,那么前端就应该发送GET请求,否则服务器应当提示错误信息
怎么完成以上的需求?
在Javaweb程序中想办法获取该请求是什么类型的请求
当我们获取到请求方式以后,在Javaweb程序中可以使用Java语言中的if语句进行判断
if("POST".equals(method)) { //....... } else if("GET".equals(method)) { //....... }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-
- 怎么在Javaweb程序中获取请求方式?
- - **重点**:HTTP的请求协议全部信息会被自动封装到**`javax.servlet.http.HttpServletRequset`**对象中
- 在**`HtppServletRequest`**接口类型中有一个方法:**String getMethod()** 可以获取请求方式
- `public interface `**`javax.servlet.http.HtppServletRequest`**`extends`**`ServletRequest`**`{}`
- ```java
public void service(ServletRequest request , ServletResponse response)
throws ServletException,IOException{
//将ServletRequest强制转换为带有Http的接口类型
HttpServletRequest httpServletRequest = (HttpServletRequest) Request;
//获取浏览器发送的请求方式
String method = httpServletRequest.getMethod();
System.out.println(method);
}
/*
问题:HttpServletRequest 继承自 ServletRequest,为什么可以直接向下转型,将父类转换为子类?
其实`service()`方法的两个参数servletRequest、servletResponse,
实际上并不是`new servletRequest()`,Tomcat内部实际上是`new HttpServletRequest()`,
所以本质上servletRequest是HttpServletRequest,所以我们进行强转并不会报错;
同理,`service()`的另一个参数servletResponse本质上也是HttpservletResponse对象。
*/
//Tomcat内部创建service()的两个参数对象,实际上是使用了多态
ServletRequest request = new HttpServletRequest();
ServletResponse response = new HttpServletResponse();
过渡
- 为了使前后端的请求方式相同,我们需要在后端获取请求方式,然后进行判断前端发送的请求是否为规定的请求方式,如果不是规定的请求方式,就会报错;
- 以上功能在每一个Servlet类中都需要编写,怎么能封装一下,以后在每一个具体的Servlet类中不写这样的代码了,但是还是能够达到同样的效???
- SUN公司提供了一个类:**
HttpServlet
**
自定义HttpServlet类
1 | public class HttpServlet extends GenericServlet { |
- 只需要记住一句话:继承HttpServlet的那个类若要求GET请求,那么就重写doGet方法;若需要POST请求,就重写doPost方法
- 为什么?
- 当前端发送GET请求,而后端需要的是POST请求,此时后端重写的是doPost方法,并没有重写doGet方法,所以程序是执行HttpServlet中的doGet方法,报错提示应当发送POST请求。
- 当前端发送GET请求,而后端正好需要的是GET请求,此时后端重写doGet方法,并没有重写doPost方法,所以程序执行的是重写的doGet方法,也就是正常执行,不会报错了。
SUN公司的HttpServlet类
javax.servlet.http.HttpServlet
此类和我们自定义的HttpServlet类的使用方法和原理类似,所以总结如下的使用方法和注意
- 我们的Servlet继承HttpServlet后,后端需要的是什么请求,那么我们就重写对应的
doPost()
/doGet()
方法 doPost()
/doGet()
方法内就是我们的业务代码,doXXX()
可以看作main()
方法- 代码不在
service()
内编写了,不需要重写service()
方法 - HttpServlet中重载的两个
service()
方法并不需要也没有理由去重写这两个方法 - 当浏览器发送的请求方式和后台处理方式不同时,会出现一个错误,代号:405
- 我们的Servlet继承HttpServlet后,后端需要的是什么请求,那么我们就重写对应的
程序中乱码解决方案
- 乱码经常出现在什么位置?
- 数据保存过程中的乱码
- 数据展示过程中的乱码
- 数据传递过程中的乱码
数据保存过程中的乱码
数据保存到数据库表中的时候,数据出现乱码
导致数据保存过程中的乱码包括以下两种情况:
- 前一种情况:在保存之前,数据本身就是乱码,保存到数据库表中的时候一定是乱码
- 第二种情况:保存之前,数据不是乱码,但是由于数据库本身数据库不支持简体中文,保存之后出现乱码
数据展示过程中的乱码
最终显示到网页上的数据出现中文乱码
怎么解决?
经过执行Java程序之后,Java程序负责向浏览器响应的时候,中文乱码
- Java程序中设置响应的内容类型,以及对应的字符字符集response.setContentType(“text/html;charset=UTF-8”);
没有经过Java程序,直接访问的是静态页面
- 文件编码时字符集,与浏览器解析时使用的字符集要一致,例如文件编码时使用的字符集是UTF-8,那么可以在网页文件中使用
<meta content="text/html;charset=UTF-8">
标签来指定网页编码时使用的字符集
- 文件编码时字符集,与浏览器解析时使用的字符集要一致,例如文件编码时使用的字符集是UTF-8,那么可以在网页文件中使用
数据传递过程中的乱码【以Tomcat 7为例】
- 将数据从浏览器发送给服务器的时候,服务器接收到的数据时乱码
- 万能解决方案:
1 | //获取乱码字符 |
- 只适用于POST请求
1 | //设置字符编码方式 |
- 只适用于GET请求
Servlet线程安全问题
Servlet运行环境
- Servlet是在单实例多线程环境下运行的
程序何时存在线程安全问题?
- 多线程并发
- 有共享的数据
- 共享数据有修改操作
JVM中哪些数据可能存在线程安全问题?
局部变量内存空间不共享,一个线程一个栈,局部变量在栈中存储,因此局部变量不会存在线程安全问题
常量不会被修改,因此常量不会存在线程安全问题
所有线程共享一个堆
- 堆内存中new出来的对象在其中存储,对象内部有“实例变量”,所以“实例变量”的内存是多线程共享的。多线程共同访问实例变量,并且涉及到修改操作的时候就会存在线程安全问题
所有线程共享一个方法区
- 方法区中有静态变量,静态变量的内存也是共享的,若涉及到修改操作,静态变量也存在线程安全问题
数据库线程安全问题
线程安全问题不只是体现在JVM中,还有可能发生在数据库中
- 例如:多个线程共享同一张表,并且同时去修改表中的一些记录,那么这些记录就存在线程安全问题
怎么解决数据库表中数据的线程安全问题?至少有2种方案
- 在Java程序中使用synchronized关键字,线程排队执行,就不会在数据库中并发,解决线程安全问题
- 行级锁【悲观锁】
- 事务隔离级别,例如:串行化
- 乐观锁……
怎么解决线程安全问题?
- 不使用实例变量,尽量使用局部变量
- 若必须使用实例变量,那么可以考虑将该对象变成多例对象,一个线程一个Java对象,这样的话,实例变量的内存也不会共享了
- 若必须使用单例,那就只能使用synchronized线程同步机制,线程一旦排队执行,则吞吐量降低,只是这样做会降低用户体验
Servlet怎么解决线程安全问题?
- 不使用实例变量,尽量使用局部变量
- Servlet必须是单例的,所以只能考虑synchronized
案例
写一个注册页面,填入用户名并且提交后,后台Servlet获取用户名,并打印刚刚填写的用户名
- 当获取的用户名赋值给全局变量(实例变量)username时,会存在线程安全问题
- synchronized线程同步机制,将修改操作虽然解决了线程安全问题,但是降低了用户体验,线程线程需要排队进入synchronized代码块
- 使用局部变量,局部变量存储在栈中,一个线程一个栈,解决了线程安全问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class Register extends HttpServlet {
//全局变量(实例变量)
private String username;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
/**
* 1. 使用实例变量,存在线程安全问题
*/
// username = request.getParameter("username");
// writer.print(username);
/**
* 2. 使用synchronized,虽然解决了线程安全问题,
* 但是降低了用户体验,线程需要排队进入synchronized代码块
*/
// synchronized (this) {
// username = request.getParameter("username");
// writer.print(username);
// }
/**
* 3. 使用局部变量(推荐)
*/
String username = request.getParameter("username");
writer.print(username);
}
}
转发和重定向
跳转的方式
转发:foward
重定向:redirect
案例
定义一个register.html
1
2
3
4
5
6
7
8
9
10<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试转发和重定向</title>
</head>
<body>
<a href="/prj-servlet-02/a">AServlet</a>
</body>
</html>定义一个AServlet,一个BServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//向request范围中存数据
request.setAttribute("username","zhangsan");
//请求转发forward():一次请求
//request.getRequestDispatcher("/b").forward(request,response);
//重定向redirect:两次请求
//执行到此处,会将/prj-servlet-02/b路径响应给浏览器,浏览器又向web服务器发送一次全新的请求
//response.sendRedirect("/prj-servlet-02/b");//也可以像下面那样写
//此时浏览器地址变化,并且输出null,因为是两次不同的请求
response.sendRedirect(request.getContextPath()+"/b");
}
}
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class BServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//取得数据
Object username = request.getAttribute("username");
System.out.println(username);
}
}配置web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>register.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.test.servlet.AServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>BServlet</servlet-name>
<servlet-class>com.test.servlet.BServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/b</url-pattern>
</servlet-mapping>
</web-app>
转发和重定向的差异
- 相同点:都可以完成资源跳转
- 不同点:
- 转发是request对象触发的,重定向是response对象触发的
- 转发是一次请求,浏览器地址栏上地址不会发生变化;重定向是两次请求,浏览器地址栏上的地址会发生变化
- 重定向的路径需要加上web项目的根路径
- 转发是在本项目内部完成资源跳转
- 重定向可以跨app(web项目)跳转资源
跳转的下一个资源可以是什么?
- 跳转的下一个资源可以是web服务器中任何一种资源:可以是Servlet,也可以是HTML,也可以是JSP……
何时用转发,何时用重定向
- 若想完成跨app跳转,必须使用重定向
- 若在上一个资源中向request范围中存储了数据,希望在下一个资源中从request范围中将数据取出,必须使用转发
- 重定向可以解决浏览器的刷新问题(浏览器地址栏上的地址会发生变化)
- 重定向,浏览器刷新的是最后一次请求的页面
重定向的原理
- response.sendRedirect(“/jd/login”);
- 程序执行到以上代码,将请求路径/jd/login反馈给浏览器
- 浏览器自动又向web服务器发送了一次全新的请求:/jd/login
- 浏览器地址栏上最终显示的地址是:/jd/login
Cookie
不是内置对象,要使用必须new
javax.servlet.http.Cookie
但是,服务端会自动生成一个(服务端自动new一个cookie)
name=JSESIONID
的cookie 并返回给客户端Cookie可以保存会话状态,但是这个会话状态是保留在客户端上,只要Cookie清除或者失效,这个会话状态就没有了
Cookie不只是在JavaWeb中存在,只要是web开发,只要是B/S架构的系统,只要是基于HTTP协议,那么就有Cookie的存在,Cookie这种机制是HTTP协议规定的
通过F12可以发现 除了自己设置的Cookie对象外,还有一个name为
JsessionId
的cookiefunction description public Cookie(String name,String value) String getName() 获取name String getValue() 获取value void setMaxAge(int expiry) 最大有效期 (秒) 服务端准备Cookie:服务端response对象增加
1
2Cookie cookie = new Cookie(String cookieName,String cookieValue);
response.addCookie(Cookie cookie)客户端获取cookie: 客户端request对象获取
1
2
3
4
5
6
7
8
9
10//从request对象中获取所有提交的Cookie
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(Cookie cookie : cookies){
String cookieName = cookie.getName();
String cookieValue = cookie.getValue();
System.out.println(cookieName + "=" + cookieValue);
}
}不能直接获取某一个单独对象,只能一次性将全部的cookie拿到
建议 cookie只保存 英文数字,否则需要进行编码、解码
使用Cookie实现的功能:十天内免登录,保留购物车商品的状态在客户端上
设置Cookie有效时长
默认情况下,没有设置Cookie有效时长,该Cookie被默认保存在浏览器的缓存中
只要不关闭浏览器,Cookie就会存在,一旦关闭,Cookie就会消失
可以通过设置Cookie的有效时长,来保证Cookie保存在硬盘文件当中
- cookie.setMaxAge(60*60);//设置cookie最大有效时长1小时
但是,这个有效时长必须是大于0的。
换句话说,只要设置Cookie的有效时长大于0,则该Cookie会被保存在客户端硬盘文件中,有效时长过去之后,硬盘文件中的Cookie失效
- Cookie有效时长 = 0 【直接被删除】
- Cookie有效时长 < 0 【不会被存储】
- Cookie有效时长 > 0 【存储在硬盘文件中】
cookie和session的区别
session | cookie | |
---|---|---|
保存的位置 | 服务端 | 客户端 |
安全性 | 较安全 | 较不安全 |
保存的内容 | Object | String |
客户端中的Cookie再次发送
在浏览器客户端,无论是硬盘文件中的Cookie,还是缓存中保存的Cookie,什么时候会再次发送给服务器?
- 浏览器会不会提交发送这些Cookie给服务器,这是和请求路径有关的
- 请求路径和Cookie是紧密关联的
- 不同的请求路径会发送提交不同的Cookie
Cookie和路径绑定
默认情况下,Cookie会和哪些路径绑定在一起?
- /prj-servlet-18/test/createAndSendCookieToBrowser 请求服务器,服务器生成Cookie,并将Cookie发送给浏览器客户端,这个浏览器中的Cookie会默认和“**test/”这个路径绑定在一起。也就是说,以后只要发送“test/**”请求,Cookie一定会提交给服务器。
- /prj-servlet-18/a 请求服务器,服务器生成Cookie,并将Cookie发送给浏览器客户端,这个浏览器中的Cookie会默认和“**prj-servlet-18/”这个路径绑定在一起。也就是说,以后只要发送“prj-servlet-18/**”请求,Cookie一定会提交给服务器。
指定路径
- 其实路径是可以指定的,通过Java程序进行设置,保证Cookie和某个特定的路径绑定在一起
- cookie.setPath();
- 假设,执行了这样的程序:
cookie.setPath("/prj-servlet-18/king");
- 那么,Cookie将和“/prj-servlet-18/king”路径绑定在一起
- 只有发送“/prj-servlet-18/king”请求路径,浏览器才会提交Cookie给服务器
浏览器禁用Cookie的后果
- 浏览器禁用 Cookie ,则浏览器缓存中将不再保存 Cookie
- 导致在同一个会话中,无法获取到对应的会话对象
- 禁用 Cookie 之后,每一次获取的会话对象都是新的
- 浏览器禁用 Cookie 之后,如果还想拿到对应的 Session 对象,必须使用 URL 重写机制
- 重写 URL 会给编程带来难度/复杂度,所以一般的 web 站点是不建议禁用 Cookie 的
EL(Expression Language)
为了消JSP中的Java代码 ,JSP自带语法
语法:
${EL表达式}
- EL不需要导包
- 在el中调用属性,其实是调用的getXxx()方法
EL表达式语句在执行时,会调用pageContext.findAttribute方法,用标识符为关键字,分别从page、request、session、application四个域中查找相应的对象,找到则返回相应对象,找不到则返回”” (注意,不是null,而是空字符串)。
EL表达式可以很轻松获取JavaBean的属性,或获取数组、Collection、Map类型集合的数据
1 | ${范围.对象.属性.属性的属性 } |
操作符
操作属性,不是对象
- 使用
.
:指定对象的属性 []
: 如果是常量属性,需要使用双引号/单引号引起来;比点操作符更加强大
- 使用
[]强大之处:
可以容纳一些 特殊符号 (. ? -)
[]可以容纳 变量属性 (可以动态赋值)
1
2
3
4
5
6String x = "a";
${requestScope.a}
//等价于
${requestScope["a"]}
//等价于
${${requestScope[x]}可以处理数组,普通对象、map中的变量
1
${requestScope.arr[0] }
获取数据
获取域中的数据
1
2
3
4
5<%
request.setAttribute("name","孤傲苍狼");
%>
<%--${name}等同于pageContext.findAttribute("name") --%>
使用EL表达式获取数据:${name}获取bean的属性
1
2
3
4
5
6<%
Person p = new Person();
p.setAge(12);
request.setAttribute("person",p);
%>
使用el表达式可以获取bean的属性:${person.age}获取对象中的对象的属性
1
2
3
4
5
6
7
8
9<%
Person person = new Person();
Address address = new Address();
person.setAddress(address);
request.setAttribute("person",person);
%>
${person.address.name}获取list集合中指定位置的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14<%
Person p1 = new Person();
p1.setName("孤傲苍狼");
Person p2 = new Person();
p2.setName("白虎神皇");
List<Person> list = new ArrayList<Person>();
list.add(p1);
list.add(p2);
request.setAttribute("list",list);
%>
${list[1].name}JSTL迭代list集合
1
2
3<c:forEach var="person" items="${list}">
${person.name}
</c:forEach>获取map集合的数据
1
2
3
4
5
6
7
8
9
10
11
12<%
Map<String,String> map = new LinkedHashMap<String,String>();
map.put("a","aaaaxxx");
map.put("b","bbbb");
map.put("c","cccc");
map.put("1","aaaa1111");
request.setAttribute("map",map);
%>
<!-- 根据关键字取map集合的数据 -->
${map.c} //cccc
${map["1"]} //aaaa1111JSTL迭代map集合
1
2
3<c:forEach var="person" items="${map}">
${person.key}:${person.value}
</c:forEach>通过EL获取JSP 九大内置对象
1
2
3
4
5
6
7${pageContext }
${pageContext.request }
${pageContext.sessoin }
${pageContext.request.contextPaht} 获取web资源名称
在写超链接时不能写死了。
<a href=${pageContext.request.contextPath}/index.jsp></a>
JSTL:比EL更加强大
需要引入2个jar :jstl.jar standard.jar
1
2
3
4
5
6
7
8
9
10
11
12
13<dependency>
<groupid>javax.servlet</groupid>
<artifactid>jstl</artifactid>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupid>taglibs</groupid>
<artifactid>standard</artifactid>
<version>1.1.2</version>
<scope>runtime</scope>
</dependency>引入tablib :
- 其中
prefix="c"
:前缀 - 核心标签库: 通用标签库、条件标签库 迭代标签库
1
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
- 其中
通用标签库
<c:set>
赋值
注意
<c:set>
可以给不存在的变量赋值 (但不能给不存在的对象赋值)在某个作用域之中(4个范围对象),给某个变量赋值
1
<c:set var="变量名" value="变量值" scope="4个范围对象的作用域"/>
1
2
3
4
5
6
7<%--
request.setAttribute("name", "zhangsan") ;
--%>
<c:set var="name" value="zhangsan" scope="request"/>
${requestScope.name }给普通对象赋值,在某个作用域之中(4个范围对象),给某个对象的属性复制 (此种写法,不能指定scope属性)
1
<c:set target="${requestScope.student}" property="sname" value="zxs" />
给map对象赋值
1
2<c:set target="${requestScope.countries}" property="cn" value="中国" />
<c:set target="对象" property="对象的属性" value="赋值" />
<c:out>
:显示
1 | true:<c:out value='[百度](https://www.baidu.com/)' default='当value为空的,显示的默认值' escapeXml=”true” /> |
<c:remove >
:删除属性
1 | <c:remove var=”a” scope=”request”/> |
选择
单重选择
<c:if test=”” >
在使用
test=””
一定要注意后面是否有空格test=”${10>2}” // true
test=”${10>2 } “ // 2后面多了空格导致失效,非true
1
2
3<c:if test="${'<%=imgstr%>'==null}">
Print this sentence .
</c:if>if进行单一的判断,而when用在
<c:choose>
中,可以进行多个条件选择。if判断为true时,执行
<c:if>...</c:if>
中的语句,但其没有配对的else。<c:choose>
和<c:when>
、<c:otherwise>
一起实现互斥条件执行,类似于 java 中的 if else.
1
2
3
4
5
6<c:choose>
<c:when test="..."> </c:when>
<c:when test="..."> </c:when>
<c:when test="..."> </c:when>
<c:otherwise> </c:otherwise>
</c:choose>
循环(迭代标签库)
使用普通for
1
2for(int i=0;i<5;i++)
forEach
1
2
3<c:forEach var="name" items="${requestScope.names }" >
-${name }-
</c:forEach>可以在foreach便签中items属性作用域(requestScope)中保存的对象
1
2
3
4<!--for(String str:names)-->
<c:forEach var="student" items="${requestScope.students }" >
${student.sname }-${student.sno }
</c:forEach>
MVC设计模式
M
:Model ,模型 :一个功能。用JavaBean实现。V
: View,视图: 用于展示、以及与用户交互。使用html js css jsp jquery等前端技术实现C
:Controller,控制器 :接受请求,将请求跳转到模型进行处理;模型处理完毕后,再将处理的结果返回给 请求处 。 可以用jsp实现, 但是一般建议使用 Servlet实现控制器。JSP->Java(Servlet)->JSP
三层优化
加入接口,建议面向接口开发:
- 先接口再实现类
接口与实现类的命名规范
- 接口:interface,
- 起名:
I实体类Service
,如:IStudentService,IStudentDao
- 起名:
- 实现类:implements
- 起名
实体类ServiceImpl
: StudentServiceImpl,StudentDaoImpl
- 起名
1
2
3
4
5
6实现类:实体类层所在包名.Impl
实现类所在的包:xxx.service.impl xx.dao.impl
以后使用接口/实现类时,推荐写法:
接口 x = new 实现类();
IStudentDao studentDao = new StudentDaoImpl();- 接口:interface,
过滤器
过滤请求和响应
注解
@WebFilter("/*")
应用场景:
全局编码设置
用户登录验证(首页,登录页面,登录处理servlet不能拦截)
将servletRequest强转为HttpServletRequest,获取请求的链接
1
2HttpServletRequest req =(HttpServletRequest) request;
String uri=req.getRequestURI();敏感词汇的过滤
图片上传后压缩
生命周期
对象从创建到销毁过程
filter对象只会创建一次,init方法也只会执行一次
构造器: 服务器启动立即调用构造器创建Filter对象
init(): Filter对象创建成功立即调用(完成初始化操作)
doFilter(): 每次请求过滤资源时都会调用
destroy(): 关闭服务器时调用销毁filter对象
实现Filter接口
init()、destroy() 原理、执行时机 同Servlet
init()方法:这是过滤器的初始化方法,在Web容器创建了过滤器实例之后将调用这个方法进行一些初始化的操作,这个方法可以读取web.xml中为过滤器定义的一些初始化参数。
doFilter()方法:这是过滤器的核心方法,会执行实际的过滤操作,当用户访问与过滤器关联的URL时,Web容器会先调用过滤器的doFilter方法进行过滤。
destory()方法:这是Web容器在销毁过滤器实例前调用的方法,主要用来释放过滤器的资源等。
配置过滤器,类似servlet
通过doFilter()处理拦截,并且通过chain.doFilter(request, response);放行.
- 过滤器中doFilter方法参数:ServletRequest
- 在Servlet中的方法参数:HttpServletRequest
filter映射
注解方式
1
只拦截 访问MyServlet的请求
1
2
3
4
5
6
7
8
9
10
11// 完全匹配
<url-pattern>/MyServlet</url-pattern>
// 目录匹配
/aaa/bbb/*----最多的
/user/*:访问前台的资源进入此过滤器
/admin/*:访问后台的资源时执行此过滤器
// 扩展名匹配
*.abc *.jsp
// '/*.jsp'的写法是错误的拦截一切请求(每一次访问 都会被拦截)
1
<url-pattern>/*</url-pattern>
通配符
1
2
public class FilterDemo1 implements Filter {- 可以设置多个
<dispatcher>
子元素用来指定 Filter 对资源的多种调用方式进行拦截
1
2
3
4
5
6dispatcher请求方式:
REQUEST:默认值 拦截HTTP请求 get post
FORWARD:转发时才执行filter,是通过 RequestDispatcher 的 forward() 方法访问时
//通过<jsp:include page="..." />此种方式发出的请求
INCLUDE:只拦截拦截通过 request.getRequestDispatcher("").include()
ERROR:只拦截<error-page>发出的请求- 可以设置多个
过滤器链
- 可以配置多个过滤器,过滤器的先后顺序 是由
<filter-mapping>
的位置决定,谁定义在上面,谁先执行 - 注解配置: 按照类名的字符串比较规则比较,值小的先执行
- url-pattern可以使用servlet-name替代,也可以混用
- 一个
<filter>
可以对应多个<filter-mapping>
- web.xml配置:
filter-mapping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<filter>
<filter-name>firstFilter</filter-name>
<filter-class>com.jdxia.Filter.FirstFilter</filter-class>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>firstFilter</filter-name>
<!-- 这边的url-pattern写成servlet-name也可以 -->
<!-- url可以写成/* -->
<url-pattern>/MyServlet</url-pattern>
</filter-mapping>
<filter>
<filter-name>secondFilter</filter-name>
<filter-class>com.jdxia.Filter.SecondFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>secondFilter</filter-name>
<url-pattern>/MyServlet</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>- 可以配置多个过滤器,过滤器的先后顺序 是由
监听器
- 监听器就是监听某个对象的的状态变化的组件
- 监听器的相关概念:
- 事件源:被监听的对象 —– 三个域对象 request, session, servletContext
- 监听器:监听事件源对象 事件源对象的状态的变化都会触发监听器 —- 6+2
- 注册监听器:将监听器与事件源进行绑定
- 响应行为:监听器监听到事件源的状态变化时 所涉及的功能代码 —- 程序员编写代码
- 注解
@WebListener
监听域对象
- 第一维度:按照被监听的对象划分:
- ServletContext对象监听器
- HttpSession对象监听器
- ServletRequest对象监听器
- 第二维度:监听的内容分:
- 对象自身的创建和销毁的监听器
- 对象中属性的创建和消除的监听器
- session中的某个对象的状态变化的监听器
监听事件器的创建和销毁
监听对象(request/session/application)的创建和销毁,以及属性的变化
- 监听application域,
ServletContext,继承ServletContextListener
实现该接口中的contextInitialized和contextDestroyed方法,
启动服务器创建应用程序上下文时和关闭服务器销毁程序上下文时执行的操作。
- 监听 Session 域,
HttpSession,继承HttpSessionListener
监听的是用户会话对象的创建和销毁事件
初始页面index.jsp,会发现控制台打印sessionCreated,关闭用户会话,这时会打印sessionDestroyed。
- 监听request域,
ServletRequest,继承ServletRequestListener
启动项目,访问index.jsp?name=imooc,这里定义一个名为name的参数,参数值是imooc,提交请求
首先用户提交请求时,请求对象被创建,监听器监听到请求对象创建的事件,这时执行监听器的initialize方法,同时监听器获取到参数名为name的参数值并打印。因为request对象只在一次请求有效,所以服务器返回响应后请求对象便被销毁,这时执行监听器的destory方法。
监听域对象中属性的增加和删除的事件监听器
这一类监听器主要监听ServletContext、HttpSession和ServletRequest这三个域对象中属性的创建、销毁和修改的事件,要实现这三种监听器,就需要继承ServletContextAttributeListener、HttpSessionAttributeListener和ServletRequestAttributeListener这三个接口,
mysql分页
mysql:从0开始计数
0 0 9
1 10 19
n n*10
(n+1)*10-1
MYSQL实现分页的sql
limit 开始,多少条
第1页
1 | select * from student limit 0,10 ; |
复制
第2页
1 | select * from student limit 10,10 ; |
复制
mysql的分页语句:
1 | select * from student limit (页数-1)*页面大小,页面大小 |
复制
oracle分页
sql server/oracle:从1开始计数 : (n-1)*10+1 --- n*10
第n页 开始 结束
1 1 10
2 11 20
3 21 30
n (n-1)10+1 n10
1 | select *from student where sno >=(n-1)*10+1 and sno <=n*10 ; --此种写法的前提:必须是Id连续 ,否则 无法满足每页显示10条数据 |
复制
1 | select rownum,t.*from student t where rownum >=(n-1)*10+1 and rownum <=n*10 order by sno; |
复制
- 如果根据sno排序则rownum会混乱(解决方案:分开使用->先只排序,再只查询rownum)
- .rownum不能查询>的数据
1 | select s.* from student s order by sno asc; |
复制
oracle的分页查询语句:
1 | select *from |
复制
优化:
1 | select *from |
复制
SQLServer分页
3种分页sql
row_number() over(字段) ;
sqlserver2003:top –此种分页SQL存在弊端(如果id值不连续,则不能保证每页数据量相等)
select top 页面大小 * from student where id not in
( select top (页数-1)*页面大小 id from student order by sno asc )
sqlserver2005之后支持:
1 | select *from |
复制
1 | where r<=n*10 |
复制
1 | ) |
复制
SQLServer此种分页sql与oralce分页sql的区别: 1.rownum ,row_number() 2.oracle需要排序(为了排序,单独写了一个子查询),但是在sqlserver 中可以省略该排序的子查询 因为sqlserver中可以通过over直接排序
sqlserver2012之后支持:
1 | offset fetch next only |
复制
(n-1)10+1 — n10
mysql从0开始计数,Oracle/sqlserver 从1开始计数
分页实现
5个变量(属性)1.数据总数(查数据库)
1 | select count(*).. |
复制
2.页面大小(每页显示的数据条数)20 (用户自定义) 3.总页数 (程序自动计算)
总页数 = 100/20 =数据总数/页面大小
总页数 = 103/20 = 数据总数/页面大小+1
—>
1 | 总页数 = 数据总数%页面大小==0 ? 数据总数/页面大小:数据总数/页面大小+1 ; |
复制
4.当前页(页码) (用户自定义)5.当前页的对象集合(实体类集合):每页所显示的所有数据 (10个人信息)List<Student>
(查数据库,分页sql)
web路径
web路径: 1.不以/开始的相对路径,找资源,以当前资源的路径为基准,经常容易出问题2.以/开始的开始的相对路径,找资源,以服务器为标准(http//localhost/端口号)需要加项目名;
就是http//localhost/端口号/crud/…
1 | <% pageContext.setAttribute("APP_PATH", request.getContextPath()); |