JSP
1、什么是jsp?
(java server page: java服务器端页面技术)
sun公司制订的一种服务器端动态页面生成技术规范。
因为直接使用servlet虽然也可以生成动态页面,但是过于繁琐(需要使用out.println输出),并且难
以维护(如果要修改页面,就必须修改java源代码),所以,sun公司制订了一种新的规范专门用于生成动
态页面,即jsp。
jsp其实就是一个以.jsp为后缀的文件,该文件当中主要包含html和java代码。容器会将.jsp文件
转换成.java文件(其实就是一个servlet),然后调用。
2、如何写一个jsp文件?
写一个以.jsp为后缀的文件,然后在该文件中,添加html和java代码。编写完成之后不需要编译,
当客户端请求访问某个.jsp文件,服务器会自动将.jsp文件转换成一个.java文件。(该.java文件其实
就是一个servlet)。
3、jsp是如何执行的?
阶段一:容器要将jsp文件转换成对应的servlet类。(该类可在tomcat的work文件中查看)
如何转换成servlet类?
1) html(css,javascript) ---->service方法里,使用out.write输出
2)<% java代码片断 %>---->service方法里,照搬
3)<%=java表达式 %> ---->service方法里,使用out.print输出
4)<%! java声明 %> ----->给servlet添加新的属性或者方法
5)out.write方法会将null转换成””,而且write方法不能输出对象只能输出字符串和基本类型
out.println()可以输出对象
阶段二:容器会将servlet类编译实例化,初始化,然后执行service方法。
注意:第一次访问jsp文件时,会执行阶段一步骤,之后访问不再执行阶段一步骤。
直接访问转换好的servlet类。
案例:hello.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body style="font-size:30px;">
<%
for(int i=0;i<100;i++){
%>
helloWorld!<br/>
<%
}
%>
</body>
</html>
转换成的java文件hello_jsp.java
public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase..{
public void _jspInit(){ .... }
public void _jspDestroy(){ ..... }
public void _jspService(HttpServletRequest request, HttpServletResponse response){ .... }
}
1)类HttpJspBase继承了HttpServlet。覆盖了HttpServlet的service方法,覆盖的service方法中
调用了_jspService方法,HttpJsPBase中的_jspService方法是空的(钩子方法,要求被覆盖),
在hello_jsp类中重写了_jspService方法。
2)当有请求时,容器会去调用HttpServlet的service方法,但这个方法被HttpJspBase覆盖了,
所以会去调用HttpJspBase类中的service方法,这个方法又去调用_jspService方法,而
hello_jsp覆盖了_jspService方法,所以去调用hello_jsp类中的_jspService方法,完成业务逻
辑。
4、jsp文件的组成
(1)html(包括css、javascript)直接写在.jsp文件里即可。
(2)java代码
第一种形式:java代码片断 语法:<% java代码 %>
第二种形式:java表达式 语法:<%= java 表达式 %>
第三种形式:java声明<%! %>
<body style="font-size:30px;font-style:italic;">
<%!
int i = 100;
int sum(int a1,int a2){
return a1 + a2;
}
%>
<%=i%><br/>
<%=sum(1,1)%>
</body>
(3)指令
a、什么是指令?
所谓指令,就是告诉jsp引擎(容器),在将.jsp文件转换成.java文件时,做一些额外的处理。
比如导包。
jsp引擎:容器当中负责将.jsp文件转换成.java文件。并在运行时为jsp提供一些辅助支持的
模块。
b、指令语法
<% @指令名 属性名=属性值 %>
c、主要的指令
(1)page指令:
1)import属性:用于导包。
比如<%@page import="java.util.*,java.text.* " %>
2)contentType属性:等价于response.setContentType();
<%@page import="java.util.*,java.text.*" %>
3)pageEncoding属性
告诉容器, jsp文件的编码格式是什么。因为容器需要读取jsp文件的内容
(也就是解码: 本地编码格式 --->unicode),
有些容器不能够正确识别jsp文件的编码格式,所以最好加上该属性。
<%@page pageEncoding="utf8" %>
4)session属性
true(缺省)/false,当值为false时,容器不再添加获得session对象
<%@page session=”false” %>
5)errorPage属性
指定一个错误处理页面<%@page errorPage="a4.jsp" %>
6)isErrorPage属性
true/false(缺省),如果值为true,表示这是一个错误处理页面。
只有当isErrorPage属性等于true,才能使用exception隐含对象。
<%@page isErrorPage="true" %>
案例
a3.jsp
//当访问a3.jsp页面报错时,跳到指定的错误页面a4.jsp
<%@page errorPage="a4.jsp" pageEncoding="utf-8" %>
<html>
<body style="font-size:30px;font-style:italic;">
<%
String num = request.getParameter("num");
out.println(Integer.parseInt(num) + 100);
%>
</body>
</html>
a4.jsp
<%@page isErrorPage="true" pageEncoding="utf-8" %>
<html>
<body style="font-size:30px;font-style:italic;">
发生了错误: <%=exception.getMessage()%>
</body>
</html>
(2)include指令:
对于页面的公共部分,我们可以使用相同的jsp文件,并使用include指令导入,
如此可以实现代码的优化。
1)file属性:告诉容器,在将.jsp文件转换成.java文件时,在指令所在的位置插入file指
定的文件的内容。
<%@include file="head.jsp" %>
(3)taglib属性
用于导入书签
1)prefix属性:命名空间的前缀
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
(4)隐含对象
a、什么是隐含对象?
所谓隐含对象,指的是在.jsp文件当中,不用声明和创建该对象,就可以直接使用的对象。
b、为什么可以直接使用这些隐含对象?
.jsp文件对应的.java文件当中,因为容器会自动添加创建或者获得这些对象的语句。
out request response session application
c、九种隐含对象
out
request
response
session
application(就是servletContext,servlet上下文)
1)exception
必须设置isErrorPage=”true”才能使用这个隐含对象。可以通过该对象获得jsp页面运行的错
误信息。
<%@page isErrorPage="true"%>
2)config
就是ServletConfig,可以读取jsp的配置参数。
案例:为jsp配置初始化参数
web.xml
<servlet>
<servlet-name>a7</servlet-name>
<jsp-file>/a7.jsp</jsp-file>
<init-param>
<param-name>company</param-name>
<param-value>北京达内</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>a7</servlet-name>
<url-pattern>/abc.html</url-pattern>//可以随便写
</servlet-mapping>
a7.jsp
<body style="font-size:30px;font-style:italic;">
公司名称:<%=config.getInitParameter("company") %>
</body>
发送请求:
3)pageContext
是PageContext类的实例,容器会为每一个jsp实例(指的是jsp对应的那个servlet对象)创建一
个符合PageContext接口要求的对象,一般称之为page上下文。
两个特点:
1)唯一性
一个jsp实例对应唯一一个page上下文
2)一直存在
只要jsp实例没有被容器销毁,则page上下文就一直存在。
作用
1)绑定数据
setAttribute、getAttribute、removeAttribute
2)提供了相应的方法来获得其他八个隐含对象。对象
4)page
表示jsp实例本身 。相当于this。
如下jsp隐含对象访问范围从小到大
pageContext 只有对应的JSP实例自己可以访问,生命周期从JSP对象创建到JSP对象消亡。
request 一次请求能访问,生命周期在一起请求和响应期间。
session 一次会话期间能访问,多次请求和响应期间都存在。
ServletContext 整个应用内部所有组件都能访问,除非服务器关闭,否则一直存在。
(5)注释
a, <!-- 注释的内容 -->
允许注释的内容是java代码,如果是java代码,容器会执行。
但是执行的结果浏览器不会显示。
b, <%-- 注释的内容 -->
不允许出现java代码。如果是java代码,会被容器忽略。
a8.jsp
<html>
<body style="font-size:30px;font-style:italic;">
当前系统时间:<!-- <%=new Date()%> -->
当前系统时间:<%--<%=new Date()%>--%>
</body>
</html>
发送请求:http://localhost:8088/web10/a8.jsp,页面上都没有时间数据
查看页面源代码
<html>
<head></head>
<body style="font-size:30px;font-style:italic;">
当前系统时间:<!-- Sat Oct 26 15:11:33 CST 2013 -->
当前系统时间:
</body>
</html>
5、案例empList.jsp
localhost:8080/emp/empList.jsp(empList.jsp是放在webRoot下面的)
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@page import="dao.EmployeeDAO"%>
<%@page import="util.Factory"%>
<%@page import="java.util.List"%>
<%@page import="entity.Employee"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
.row1{ background-color: red; }
.row2{ background-color: yellow; }
</style>
</head>
<body style="font-size:30px;">
<table cellpadding="0" cellspacing="0" border="1" width="60%">
<tr style="">
<td>ID</td>
<td>姓名</td>
<td>薪水</td>
<td>年龄</td>
<td>操作</td>
</tr>
<%
EmployeeDAO dao=(EmployeeDAO)Factory.getInstance("EmployeeDAO");
List<Employee> employees=dao.findAll();
for(int i=0;i<employees.size();i++){
Employee e=employees.get(i);
%>
<tr class="row<%=(i%2+1) %>">
<td><%=e.getId() %></td>
<td><%=e.getName() %></td>
<td><%=e.getSalary() %></td>
<td><%=e.getAge() %></td>
<td><a href="">删除</a> <a href="">修改</a></td>
</tr>
<%
}
%>
</table>
</body>
</html>
6、转发
1) 什么是转发?
一个web组件(jsp/servlet)将未完成的处理通过容器转交给另一个web组件继续处理。转发的各个
组件会共享request和response对象。
最常见的情况是:一个servlet 将数据转交给一个jsp,由该jsp生成相应的页面。
2) 如何转发?
step1 先绑订数据 request.setAttribute(String name,Object obj);
跟绑订相关的另外两个方法 (如果name对应的值不存在,返回null。)
Object request.getAttribute(String name); 取出数据,然后进行下一步的处理。
request.removeAttribute(String name);
step2 获得转发器,转发
RequestDispatcher rd = request.getRequestDispatcher(String uri);
rd.forward(request,response);
3) 编程中要注意的问题
转发之前,不能够执行out.close或者out.flush。(容器不允许发送两个数据包)
转发之前,会将response中缓存的数据先清空。
4) 转发的特点
a. 转发的目的地只能是同一个应用内部的某个组件
b. 转发之后,浏览器地址栏的地址没有变化
c. 转发所涉及的各个组件可以共享同一个request,response对象
7、转发与重定向的区别
(1)转发所涉及的各个web组件(servlet和jsp)会共享request对象和response对象;重定向不行。
因为转发是一次请求,重定向是两次请求。
request和response对象的生存时间:
当请求到达容器,容器创建这两个对象,当响应发送完毕,容器会立即删除这两个对象。
(2)转发的地址必须是同一个应用内部的某个组件,重定向的地址是任意的。
(3)转发共享request,重定向不行。转发之后浏览器地址不变,重定向会变
(4)转发是一件事没做完,让给其他组件做。重定向是一件事已经做完。
8、表单中的中文问题
表单中文处理步骤
step1、jsp文件,要添加
<%@page pageEncoding="utf-8" contentType="text/html;charset=utf-8"%>
表单设置method="post"。
step2、在servlet类当中,添加request.setCharacterEncoding("utf-8");
step3 如果要访问数据库:
a. 保证数据库能够正常地保存中文。
对于mysql数据库 create database dbname default character set utf8;
b. 使用jdbc访问数据库时,必须要保证jdbc驱劢程序能够识别数据库中保存数据的编码。
jdbc:mysql://localhost:3306/jd1109db2? useUnicode=true&characterEncoding=utf8
9、如何处理servlet产生的系统异常?
方式1: 使用转发(更灵活一些,会绑定错误提示)
step1, 绑订错误提示信息。
step2, 转发到某个错误处理页面(比如,error.jsp)。
servlet
try {
dao.delete(id);
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("error", "系统异常,稍后重试!");
request.getRequestDispatcher("error.jsp").forward(request, response);
}
error.jsp
<body style="font-size:30px;color:red;">
<%=request.getAttribute("error")%>
</body>
方式2: 让容器来处理
step1, 编写一个错误处理页面(error.jsp)
step2, 将异常抛出给容器 throw new ServletException(e)
step3, 配置错误处理页面,让容器知道出现异常后,应该调用哪一个页面。
servlet
try {
dao.modify(e);
response.sendRedirect("/emp/list.do");
} catch (Exception e1) {
e1.printStackTrace();
//将异常抛给容器(容器调用这个servlet方法,有异常,容器解决)
throw new ServletException(e1);
}
web.xml
<web-app version="2.4" ....>
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>web.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>javax.servlet.ServletException</exception-type>
<location>/error.jsp</location>
</error-page>
</web-app>
10、路径问题
1) 链接地址、表单提交、重定向、转发的路径应该如何写?
a. 链接地址: <a href=""></a>
b. 表单提交: <form action="">
c. 重定向: response.sendRedirect("");
d. 转发: getRequestDispatcher("");
2) 相对路径不绝对路径
相对路径: 不以"/"开头的路径,比如:<a href="del.do"></a> 在当前路径下进行跳转
绝对路径: 以"/"开头的路径,比如:<a href="/appname/del.do"></a>
建议:因为相对路径较易出错,建议在实际开发中尽量使用绝对路径
3) 怎样写绝对路径?
链接、表单提交、重定向,绝对路径要从应用名开始写。 转发要从应用名之后开始写。
获得实际部署时的应用名 /appname
String request.getContextPath()
response.sendRedirect(request.getContextPath() + "/app3/sub/some.jsp");
<body style="font-szie:30px;">
a1....<br>
<a href="<%=request.getContextPath() %>/a2.jsp">a2.jsp</a>
</body>
<body style="font-size:30px;">
a2.jsp<br>
<a href="<%=request.getContextPath() %>/app/a1.jsp">a1.jsp</a>
</body>
转发:request.getRequestDispatcher("/emplist.jsp").forward(request, response);
10、状态管理
1)什么是状态管理?
将客户端与服务器之间的多次交互当作一个整体来看待,并且将多次操作所涉及的数据记录下
来。(状态管理就是这些数据的管理)
2)怎样进行状态管理?
第一种方式:将状态保存在客户端:cookie(在客户端管理用户的状态)
第二种方式:将状态保存在服务器端:session(在服务器端管理用户的状态)
11、Cookie
(1)什么是cookie?
浏览器在向服务器发送请求时,服务器将少量的数据以set-cookie消息头的形式发送给浏
览器。浏览器会将这些数据(key-value形式)保存起来(可能保存在内存里,也可能保存在
硬盘里)。当浏览器再次访问服务器时,会将这些数据以cookie消息头的形式发送给服务器。
通过这种方式,可以管理用户的状态。
服务器发送给浏览器端的形式:Set-Cookie: username=zhangsan
浏览器发送给服务器的形式:Cookie: username=zhangsan; userpwd=1234
(2)cookie适合放什么数据?
这个取决于网站自身,有的说网站会存储一些重要的用户信息(什么用户名、密码、浏览记录、
IP地址什么的)到Cookie里。事实上:
普通网站都不会存重要的信息,它们仅仅存一个你的登陆状态,也就是你拿用户名密码换取的
令牌,还有就是网站针对你的判定(比如你在这个网站上的唯一标识是什么,你访问的是我们的
哪台服务器,你使用的是我们的哪个版本的产品),这些信息你都不需要关心,它和你的隐私一
点关系都没有。
文艺一点的网站会将这些信息进行加密,目的是防止别人伪造这些信息欺骗网站。
在Cookie里存用户名、密码的,也许是央视网的做法在互联网上是极其极其少见的,可能只有
外行或者刚学网络开发的学生会这么做,这种网站是极其不安全的,你的信息很容易就泄漏了,
所以还是少去访问。
网站的一些为你推荐的产品,您可能会喜欢的宝贝等等功能都是把用户经常浏览信息记录下
来,用户再次登录根据信心返回相应的一些数据给客户。
淘宝显示浏览过的商品的功能就是使用cookie技术:
用户浏览淘宝页面时,服务器将items=1,3,4,5,6,7(商品的id值)这样的数据以cookie的形式
发送给浏览器并且设置cookie的保存时间比较长,用户之后再访问淘宝页面时,将intems=1,2..
发送给服务器,服务器根据这个id值查询出商品信息显示在页面上。
(2)创建cookie
Cookie cookie=new Cookie(String name,String value);
response.addCookie(cookie);
(3)查询cookie(如果没有cookie,则返回null)
Cookie[ ] cookies= request.getCookies( ); 注意:该方法有可能返回null
String name=cookie.getName();
String value=cookie.getValue();
案例:创建和查询cookie
<web-app version="2.4" ...>
<servlet>
<servlet-name>addCookie</servlet-name>
<servlet-class>web.AddCookieServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>addCookie</servlet-name>
<url-pattern>/addCookie</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>getCookie</servlet-name>
<servlet-class>web.GetCookieServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getCookie</servlet-name>
<url-pattern>/getCookie</url-pattern>
</servlet-mapping>
</web-app>
AddCookieServlet
public class AddCookieServlet extends HttpServlet{
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie=new Cookie("username","zhangsan");
Cookie cookie2=new Cookie("userpwd","1234");
response.addCookie(cookie);
response.addCookie(cookie2);
}
}
GetCookieServlet
public class GetCookieServlet extends HttpServlet{
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie[] cookies=request.getCookies();
for(Cookie cookie:cookies){
System.out.println(cookie.getName());
System.out.println(cookie.getValue());
}
}
}
在地址栏发送请求:时,服务器向浏览器发送两个Cookie
以Set-Cookie: username=zhangsan,Set-Cookie: userpwd=1234形式发送给浏览器。
在地址栏发送请求:时,浏览器将cookie发送给服务器,
以Cookie: username=zhangsan; userpwd=1234的形式发送给服务器
(4)cookie保存时的编码问题
cookie的值只能是ascii字符,如果是中文,需要将中文转换成ascii字符串形式。
可以使用URLEncoder.encode()方法和URLDecoder.decode()方法来进行这种转换。
java.net.URLEncoder.encode()可以对要传递的中文进行编码
/**
* encode方法先把字符串按照指定的编码格式(比如:utf-8),
* 然后将编码之后得到的字节数组转换成一个ascii字符串
*/
String str=URLEncoder.encode("过儿","utf-8");
System.out.println(str);
String str2=URLDecoder.decode(str,"utf-8");
System.out.println(str2);
案例:cookie中保存中文
AddCookieServlet
public class AddCookieServlet extends HttpServlet{
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie=new Cookie("username",URLEncoder.encode("张三丰","utf-8"));
Cookie cookie2=new Cookie("userpwd","1234");
response.addCookie(cookie);
response.addCookie(cookie2);
}
}
GetCookieServlet
public class GetCookieServlet extends HttpServlet{
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie[] cookies=request.getCookies();
for(Cookie cookie:cookies){
System.out.println(cookie.getName());
System.out.println(URLDecoder.decode(cookie.getValue(), "utf-8"));
}
}
}
(5)cookie的保存时间
cookie.setMaxAge(int seconds); 单位是秒
seconds > 0 :浏览器会将cookie以文件的方式保存在硬盘上。
在超过指定的时间以后,会删除该文件。
seconds < 0 :默认值,浏览器会将cookie保存在内存里面。只有当浏览器关闭之后才会删除。
seconds = 0 :立即删除该Cookie
AddCookieServlet
public class AddCookieServlet extends HttpServlet{
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie=new Cookie("username",URLEncoder.encode("张三丰","utf-8"));
Cookie cookie2=new Cookie("userpwd","1234");
//cookie会在60秒之后被删除
cookie.setMaxAge(60);
//cookie会在浏览器关闭时被删除
cookie2.setMaxAge(-1);
response.addCookie(cookie);
response.addCookie(cookie2);
}
}
(6)删除cookie
比如要删除一个name为"username"的cookie。
Cookie c = new Cookie("username","");
c.setMaxAge(0);
response.addCookie(c);
DelCookieServlet
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie=new Cookie("username","");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
(7)练习:查询名称为username的cookie,如果找不到,就添加名为username的cookie
public class FindCookieServlet extends HttpServlet{
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
Cookie[] cookies=request.getCookies();
if(cookies!=null){
boolean flag=false;
for(Cookie cookie:cookies){
if(cookie.getName().equals("username")){
out.println(URLDecoder.decode(cookie.getValue(),"utf-8"));
flag=true;
break;
}
}
if(!flag){
Cookie cookie=new Cookie("username",URLEncoder.encode("张三","utf-8"));
response.addCookie(cookie);
}
}else{
Cookie cookie=new Cookie("username",URLEncoder.encode("张三","utf-8"));
response.addCookie(cookie);
}
out.close();
}
}
(7) cookie的路径问题
1)什么是cookie的路径问题?
浏览器在向服务器上的某个地址发送请求时, 会先比较cookie的路径与向访问的路径
(地址)是否匹配,只有匹配的cookie,才会发送。
2)cookie的默认路径
cookie的路径默认等于创建这个cookie的组件的路径
比如:/web07/app01/addCookie.jsp创建了一个cookie,则该cookie的路径等于"/web07/app01"。
3)匹配规则
要访问的服务器的地址必须是cookie的路径或者是其子路径,浏览器才会发送该cookie。
http://localhost:8080/web07/findCookie1.jsp error
http://localhost:8080/web07/app01/findCookie2.jsp ok
http://localhost:8080/web07/app01/sub/findCookie3.jsp ok
4)修改cookie的路径
cookie.setPath(String path); 比如:cookie.setPath("/web07");
经常将cookie的路径设置为应用名,这样可以保证该cookie可以被该应用的其它组件都能访
问到。
5)要添加一个cookie,一般需要这样写
Cookie cookie=new Cookie(“username”,URLEncoder.encode(“李白”,”utf-8”));
cookie.setMaxAge(3600);
cookie.setPath(“/web07”);
response.addCookie(cookie);
(8) cookie的限制
1)不安全
因为cookie存放在浏览器端,可以很容器被查看到,所以,如果有敏感数据,一定需要加密。
2)cookie可以被用户禁止
3)cookie的大小有限制(大约只能存放4k左右的数据,具体大小跟浏览器有关系)
4)cookie的个数也有限制(浏览器大约只能保存300个左右的cookie)。
5)cookie只能保存字符串,并且,需要考虑编码问题。
(9)小知识
浏览器有保存密码的功能,存在安全隐患。密码信息将很容器被他人窃取。
安全建议:
OWASP建议网站设计者应该禁止浏览器保存用户登录密码,实现方法如下面的html标签。
<input type=”password” autocomplete=”offf”> autocomplete属性是html5后新增的属性
有了autocomplete属性的输入框,浏览器就不会记录密码了,Yahoo登陆页面就是这样的。
12、session
(1)什么是session?
session是一种服务器端的状态管理技术。
浏览器访问服务器时,服务器会创建一个session对象(该对象有一个id属性,其值是唯一的, 一
般称为sessionId)。
服务器在缺省情况下,会将sessionId以cookie机制(将sessionId放到set-cookie消息头)发送给浏
览器(浏览器把sessionId放入内存中)。
当浏览器再次访问服务器时,会将sessionId发送给服务器。服务器依据sessionId就可以找到对应
的session对象。通过这种方式,就可以管理用户的状态。
比如:
jsp页面在转成java文件时有:HttpSession session=pageContext.getSession()。
这个方法里实际调用了request.getSession()方法。
所以发送一个jsp的请求就会创建一个session对象。所以在jsp页面中可以直接使用session对象
a、浏览器发送请求:
查询数据时还没有创建session对象,转发到emplist.jsp时,jsp对应的java文件中创建了一个
session对象,发送响应时服务器将session对象的sessionId以cookie的形式发送给浏览器
Set-Cookie: JSESSIONID=70C101358F84CEF4A63B3EAFCCF1BC52; Path=/emp
该cookie的路径是/应用名
b、浏览器再次发送请求:
式发送给浏览器
Cookie: JSESSIONID=70C101358F84CEF4A63B3EAFCCF1BC52
注意浏览器保存的cookie JSESSIONID在浏览器关闭时就被删除了。过期时间为浏览会话结束时
也就是说,每次关闭浏览器后所有的session不可用。再次访问服务器都是重新创建session对象
(2)如何获得session对象
方式一: HttpSession session = request.getSession(boolean flag);
1)当flag = true:
服务器会先查看请求中是否包含sessionId, 如果没有,则创建一个session对象。
如果有,则依据sessionId去查找对应的session对象,如果找到,则返回。
如果找不到(可能被容器删除了),则创建一个新的session对象。
2)当flag = false:
服务器会先查看请求中是否包含sessionId, 如果没有,返回null。
如果有,则依据sessionId去查找对应的session对象,如果找到,则返回。
如果找不到,返回null。
方式二:HttpSession session = request.getSession(); 与request.getSession(true)等价。
(3)HttpSession接口提供的一些方法
1)获得sessionId。
String session.getId();
2)绑订数据
session.setAttribute(String name,Object obj); 绑定数据
Object session.getAttribute(String name); 如果name对应的值不存在,返回null。
session.removeAttribute(String name); 解除绑定
3)案例:计算用户是第几次访问?
web.xml
<servlet>
<servlet-name>count</servlet-name>
<servlet-class>session.CountServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>count</servlet-name>
<url-pattern>/count</url-pattern>
</servlet-mapping>
CountServlet
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
HttpSession session = request.getSession();
//session.setMaxInactiveInterval(35);
String sessionId = session.getId();
System.out.println("sessionId:" + sessionId);
Integer count = (Integer)session.getAttribute("count");
if(count == null){
//第一次访问
count = 1;
}else{
//不是第一次,在原有值的基础上加1
count ++;
}
session.setAttribute("count", count);
out.println("你是第:" + count + " 次访问");
//session.invalidate();把session对象内容清空
}
(4)session超时
1)什么是session的超时?
session对象不是一直存在的。
服务器会将超过指定时间的session对象删除(在指定的时间内,该session对象没有使用)。
原因是,过多的session对象会占用服务器过多的内存空间。
大部分服务器都会有一个缺省的超时限制,一般是30分钟。
2)修改服务器缺省的session超时限制。
方式一: session.setMaxInactiveInterval(int seconds);单位是秒
方式二: 服务器有一个缺省的超时限制,可以通过相应的配置文件来重新设置。
比如可以修改tomcat的web.xml(tomcat_home/conf下面)。
<session-config> <session-timeout>30</session-timeout> </session-config>
修改完之后,需要重新启动服务器。
另外,也可以将以上配置放在某个应用的web.xml文件当中。
(这样修改的内容只针对当前应用)
(5)立即删除session
session.invalidate(); 把session对象内容清空
(6)session的优缺点
优点:
session相对安全
session能够保存的数据类型更丰富
session能够保存的数据大小更大
缺点:
session需要将所有数据写在服务器端,所以,服务器会占用过多的内存空间。
可以考虑使用cookie来代替或者使用数据库来保存状态(即数据)。
注意:无论是cookie还是session都不能永久保存数据,要想永久保存数据就把数据插入到数据库里。
(7)session里通常存些什么值?
session通常用来保存与用户信息相关的:身份信息、登陆状态,用户的个性设置、权限列表,
其他的一些通用数据(比如购物车)
原则是把通用的,频繁存取的、小数据量的跟用户相关的数据放入session,视场景而定。
session就相当于一个缓存,放在内存中,避免多次存取而效率低下。但是
如果存放的数据比较多的话,会占用过多的内存
11、用户禁止cookie以后,如何继续使用session
当用户禁止cookie以后,服务器仍然会发送sessionId(以set-cookie消息头的方式)。但是,浏览器
会拒绝接受,这样,session机制会失效。
1)解决方式
使用url重写机制
2)什么是url重写?
如果要访问的web组件(jsp/servlet)需要session机制的支持,那么不能够直接输入该web组件
的地址,而应该使用服务器生成的包含有的sessionId的地址。
3)如何生成包含有sessionId的地址?
方式1(适用于链接、表单提交)
response.encodeURL(String url); 等价于 response.encodeUrl(String url)(不常用)
方式2(适用于重定向)
response.encodeRedirectURL(String url);
转发不需要、转发和session无关,是服务器内部完成,没有和浏览器进行交互
session没有被禁止时
<a href="count">vist countServlet</a>
访问countServlet步骤:
① 浏览器访问test.jsp
② 服务器为该浏览器用户创建一个Session对象,用于保存此次会话的数据
③ 服务器将sessionId返回给浏览器&&返回生成的显示给用户的页面
④ 浏览器将服务器传回的sessionId保存到内存中
⑤ 当用户点击“链接”<a href="count">时,浏览器发送带sessionId的请求给服务器
⑥ 服务器中的CountServlet通过该sessionId找到该用户对应的Session并做计数操作
⑦ CountServlet将计数结果和页面返回给用户浏览器
session被禁止时:
<a href="<%=response.encodeURL("count")%>">vist countServlet</a>
访问countServlet步骤:
① 浏览器访问test.jsp
② 服务器创建Session对象
③ test.jsp将页面和SessionId返回给浏览器
因为浏览器禁用了Cookie,所以浏览器并不保存sessionId, 为了能继续使用Session,
我们在test.jsp中重写了URL, 所以此时test.jsp返回给浏览器一个带有sessionId的地址:
http://localhost:8080/web08_session/count;jsessionid=596A1BC51F79553E341AF3B
④ 当用户点击“链接”,向服务器发送的请求中包含了sessionId
⑤ 服务器通过这个sessionId可以找到对应的Session
⑥ CountServlet将计数结果和页面返回给用户浏览器
12、过滤器
(1)什么是过滤器?
servlet规范当中定义的一种特殊的类,用于对servlet容器的调用过程进行拦截。
执行步骤:
1) 浏览器发送请求给服务器
2) 服务器的Servlet引擎创建Request对象&&Response对象
3) Servlet引擎先调用过滤器的doFilter方法,该方法有两个参数request和response,
(在过滤器中可以访问到Request对象&&Response对象)
4) 过滤器对拦截的内容进行处理
5) 之后调用SomeServlet的service方法
6) service方法执行
7) service方法执行结束后,将结果返回到过滤器
8) 过滤器将service方法返回的结果再次进行过滤
9) 最后,Servlet引擎将结果返回给浏览器
(2)怎样写一个过滤器?
step1、写一个java类,实现一个Filter接口
step2、在doFilter方法里,实现过滤的逻辑
step3、配置(web.xml)
案例:对用户的评论过滤
jsp页面:
<body style="font-size:30px;">
<form action="<%=request.getContextPath() %>/comment" method="post">
评论:<input type="text" name="content"><br>
<input type="submit" value="发表">
</form>
</body>
web.xml文件
<filter>
<filter-name>filter1</filter-name>
<filter-class>web.Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/comment</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>comment</servlet-name>
<servlet-class>web.CommentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>comment</servlet-name>
<url-pattern>/comment</url-pattern>
</servlet-mapping>
Filter1类
public class Filter1 implements Filter{
//容器再删除过滤器对象之前,会调用destory方法
public void destroy() {
System.out.println("过滤器被销毁");
}
//类似于servlet的service方法,请求到达时容器会调用doFilter方法来处理请求
// servletrequest是容器创建的request对象,servletresponse是response对象
//容器会将request对象和response对象作为参数传给doFilter方法
//FilterChain:过滤器链
// FilterChain 对象有一个doFilter方法,如果该方法调用了,
//表示让容器继续向后调用过滤器或者servlet
public void doFilter(ServletRequest servletrequest,ServletResponse servletresponse,
FilterChain filterchain)throws IOException, ServletException {
//HttpServletRequest是sun公司过度设计的产物
HttpServletRequest request=(HttpServletRequest) servletrequest;
HttpServletResponse response=(HttpServletResponse) servletresponse;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
String str=request.getParameter("content");
if(str.lastIndexOf("dog")!=-1){
out.println("评论当中包含了敏感字!");
}else{
filterchain.doFilter(servletrequest, servletresponse);
}
//容器启动之后,会立即创建过滤器对象
//接下来,会调用init方法.在调用init方法之前,容器会先创建好一个符合FilterConfig
//接口要求的对象。该对象可以用来访问过滤器的初始化参数
(getInitParamter(String paraname))
public void init(FilterConfig filterconfig) throws ServletException {
//FilterConfig相当于ServletConfig,用来读取初始化参数
System.out.println("过滤器被初始化");
}
}
CommentServlet类
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
String str=request.getParameter("content");
out.println("您的评论是:"+str);
}
(3)配置初始化参数
step1、web.xml中,使用<init-para>元素来配置初始化参数
step2、在Filter类中,使用FilterConfig.getInitParamter(String paraName);获得初始化参数
案例:将敏感字写在配置文件里
web.xml文件
<filter>
<filter-name>filter1</filter-name>
<filter-class>web.Filter1</filter-class>
<!-- 初始化参数 -->
<init-param>
<param-name>illegalStr</param-name>
<param-value>cat</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/comment</url-pattern>
</filter-mapping>
Filter1类
public class Filter1 implements Filter{
private FilterConfig config;
public Filter1(){
System.out.println("Filter1's constructor...");
}
public void destroy() {
System.out.println("Filter1's destroy...");
}
public void doFilter(ServletRequest arg0,
ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
System.out.println("Filter1's doFilter begin...");
HttpServletRequest request = (HttpServletRequest)arg0;
HttpServletResponse response = (HttpServletResponse)arg1;
request.setCharacterEncoding("utf-8");
String content = request.getParameter("content");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//读过滤器的初始化参数
String illegalStr = config.getInitParameter("illegalStr");
if(content.indexOf(illegalStr) != -1){
out.println("评论当中包含了敏感字");
}else{
//调用后续的过滤器或者servlet
arg2.doFilter(arg0, arg1);
}
System.out.println("Filter1's doFilter end.");
}
public void init(FilterConfig arg0) throws ServletException {
System.out.println("Filter1's init...");
config = arg0;
}
}
(4)过滤器的优先级
当有多个过滤器都满足过滤的条件时,依据<filter-mapping>的先后顺序依次执行。
(5)过滤器的优点
a、可以将多个web组件相同的处理逻辑集中写在一个过滤器中,方便代码的维护
b、可实现代码的“可插拔性”
给一个软件增加或者减少某个模块,不会影响程序的正常运行
13、ServletContext接口(servlet上下文)
(1)什么是ServletContext(servlet上下文)
context:环境 上下文 。
web服务器在启动时,会为每一个已经部署的应用创建唯一的一个ServletContext实例。 该实例
会一直存在,除非服务器关闭或者应用被删除。
可以把ServletContext看成是一个web应用的服务端组件的一个共享内存,在ServletContext中可以
存放共享数据。整个应用内部所有组件都能访问ServletContext的内容。
servlet上下文有两个特点:
1)唯一性
一个应用对应一个servlet上下文(有且只有一个)
2)一直存在
只要容器不关闭或者应用不卸载,servlet上下文就一直存在。
(2)如何获得ServletContext实例。
1)GenericServlet提供了getServletContext()方法
2)ServletConfig提供了getServletContext()方法
3)HttpSession提供了getServletContext()方法
4)FilterConfig提供了getServletContext()方法
以上几种方式都是得到同一个ServletContext对象。tomcat容器的ApplicationContext类实现了
ServletContext接口。以上方法最终是
ServletContext application = new ApplicationContext();
(3)servlet上下文的作用
1)绑定数据
setAttribute(String name,Object obj);
getAttribute(String name);
removeAttribute(String name);
注意:request对象、session对象、servlet上下文都有绑定数据的挡风区别如下:
a、request对象的生存时间是一次请求与响应期间,
session对象的生存时间是多次请求与响应期间(用户与服务器的一次会话)
servlet上下文是一直存在的。
所以,在满足使用条件的情况下,应该优先使用生命周期短的。
比如转发,选择用request,转发结束后,绑定的数据就没有用了,随着request对象的销毁,
数据也被销毁,这样会比较节省内存。如果转发使用servlet上下文时,转发结束后,数据还
保留着,这样会浪费内存空间。
b、request对象上面绑定的数据只能是同一个请求所涉及的组件可以访问,
session对象上面绑定的数据是同一个会话当中所涉及的组件可以访问,
servlet上下文上面绑定的数据是同一个应用当中的所有组件都可以访问。
2)配置全局的初始化参数
step1 在web.xml中,使用<context-param>配置的参数,可以被所有的servlet共享。
step2 使用String ServletContext.getInitParameter(String paraName);
web.xml配置时一般都有一定的顺序(约定俗成)
<web-app version="2.4" ...>
<!-- 全局的初始化参数 -->
<context-param>
<param-name>company</param-name>
<param-value>北京达内</param-value>
</context-param>
<!-- 过滤器的配置 -->
<!-- 监听器的配置 -->
<!-- servlet的配置 -->
</web-app>
Servlet代码
ServletContext sctx = getServletContext();
String company = sctx.getInitParameter("company");
System.out.println(" " + company);
3)依据逻辑路径获得实际部署时的物理路径。
String ServletContext.getRealPath(String url);
14、监听器 **
1) 什么是监听器?
servlet规范当中定义的一种特殊的类,作用是监听容器当中产生的一些事件并进行相应的处理。
容器产生的事件指的是两大类事件:
1)生命周期相关的事件
指的是当容器创建或者销毁request,session,ServletContext对象时产生的事件。
2)绑订事件
指的是当调用request,session,ServletContext对象的setAttribute,removeAttribute时产生的事件。
2) 如何写监听器
step1 写一个java类,实现特定的监听器接口类(依据要监听的事件类型)。
step2 在接口声明的方法中,实现监听的逻辑。
step3 配置(web.xml)。
3) 案例
a、统计在线人数
CountListener类
public class CountListener implements HttpSessionListener{
private int count = 0;//计数器
/**
* session对象被创建后,容器会调用这个方法
*/
public void sessionCreated(HttpSessionEvent httpsessionevent) {
System.out.println("session被创建");
count++;
//将在线人数绑定到servlet上下文对象上
HttpSession session=httpsessionevent.getSession();
ServletContext application=session.getServletContext();
application.setAttribute("count", count);
}
/**
* 当session对象被销毁后,容器会调用这个方法
*/
public void sessionDestroyed(HttpSessionEvent httpsessionevent) {
System.out.println("session被销毁");
count--;
HttpSession session=httpsessionevent.getSession();
ServletContext application=session.getServletContext();
application.setAttribute("count", count);
}
}
web.xml
<web-app version="2.4" ...>
<listener>
<listener-class>web.CountListener</listener-class>
</listener>
//index.jsp首页的意思
//当发送请求时(只到应用名)
如:http://localhost:8080/web09时,会自动定位到index.jsp
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
index.jsp
<html>
<body>
当前系统在线人数是:<!--application隐含对象就是ServletContext对象 -->
<%=application.getAttribute("count") %>
</body>
</html>
b、容器开启时,每隔5秒输出当前系统时间。容器关闭或应用卸载时,停止。
TaskListneer类
public class TaskListneer implements ServletContextListener{
private Timer timer=new Timer();
public void contextDestroyed(ServletContextEvent servletcontextevent) {
System.out.println("任务停止");
timer.cancel();
}
public void contextInitialized(ServletContextEvent servletcontextevent) {
System.out.println("任务开始");
timer.schedule(new TimerTask(){
public void run() {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println(sdf.format(new Date()));
}
}, 0,5000);
}
}
web.xml
<listener>
<listener-class>web.CountListener</listener-class>
</listener>
15、上传文件(扩展)
(1)在form中,设置method="post",设置enctype="multipart/form-data"。
enctype属性用于设置表单的编码方式,按照http协议,
对于文件上传,必须设置成"multipart/form-data"。
比如:
<form enctype="multipart/form-data" method="post">
"multipart/form-data":用来设置浏览器上传文件的格式。
(2)在服务器端读取文件的内容,并保存到相应的文件夹底下
如果enctype=”multipart/form-data”,则服务器不再解析请求数据包,也就是说
request.getParamter()方法返回null。
要使用 InputStream request.getInputStream();获得一个输入流,
然后分析InputStream流来获得参数值。
直接分析InputStream比较复杂,一般使用一些封装好的工具
(比如apache提供的 commons-fileupload.jar)来获得参数值。
16、servlet线程安全问题
1)servlet为什么会有线程安全问题?
a、servlet容器在默认情况下只会创建一个servlet实例
(比如SomeServlet在容器里面,只有一个SomeServlet对象)
b、当容器收到请求之后,会启动一个线程来处理
c、如果有多个请求同时访问某个servlet实例,即有多个线程调用同一个servlet实例的方法,
就有可能产生线程安全问题。(比如,这些线程同时去修改servlet属性)
2)如何解决
a、使用synchronized对整个service方法或者代码块加锁(建议使用代码块加锁)
b、让servlet实现SingleThreadModel接口
容器会为每一个实现了该接口的servlet创建多个实例(一个线程创建一个servlet对象)。
不建议使用,因为会创建过多的servlet对象。
SingleThreadModel中没有任何的方法,称为“标识”接口(作用和序列化接口一样)。