Cookie-session概述与应用
在很多WEB应用中,我们都需要对用户的会话进行跟踪,需要记录用户的状态,比如商城中用户将商品添加到购物车,商城必须能识别哪些会话是属于同一个用户,既是哪些客户端添加了哪些商品,不能出现,A用户的客户端添加的商品跑到B用户客户端的购物车里头!
在Servlet技术中,有两种机制可以实现这种对会话的跟踪:
①Cookie机制:放在客户端
②Session机制:放在服务器端
1.Cookie
Cookie技术是客户端的解决方案,当用户使用浏览器访问一个支持Cookie的网站的时候,一个cookie的运行过程分为以下四步:
当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置,对于Windows操作系统而言,IE浏览器的Cookie 放在:[系统盘]:\Users\用户名\Cookies目录中;
通常,我们可以从很多网站的登录界面中看到“请记住我”这样的选项,如果你勾选了它之后再登录,那么在下一次访问该网站的时候就不需要进行重复而繁琐的登录动作了,而这个功能就是通过Cookie实现的。
其实本质上cookies就是http的一个扩展。有两个http头部是专门负责设置以及发送cookie的,它们分别是Set-Cookie以及Cookie。其中如果包含Set-Cookie这个头部时,意思就是指示客户端建立一个cookie,并且在后续的http请求中自动发送这个cookie到服务器端,直到这个cookie过期。如果cookie的生存时间是整个会话期间的话,那么浏览器会将cookie保存在内存中,浏览器关闭时就会自动清除这个cookie。另外一种情况就是保存在客户端的硬盘中,浏览器关闭的话,该cookie也不会被清除,下次打开浏览器访问对应网站时,这个cookie就会自动再次发送到服务器端。
Java中把Cookie封装成了javax.servlet.http.Cookie类。每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进行操作。通过response.addCookie(Cookie cookie)向客户端设置Cookie,通过request.getCookie()获取客户端提交的所有Cookie(以Cookie[]数组形式返回)。
例:创建cookie,我们怕与session会话对象冲突,在页面page中,设置session=“false”
*注:session="false"对360无效,我是微软的Edge上云行,这里有作用。
<%@ page language="java" session="false" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>cookie Test</title>
</head>
<body>
<%
Cookie cookie=new Cookie("uuid","uuidvalue");
response.addCookie(cookie);
%>
</body>
</html>
例:获取cookie值,并打印在浏览器上,cookie对象默认关闭浏览器就清除
<body>
<%
Cookie[] cookieArr = request.getCookies();
if(cookieArr!=null && cookieArr.length>0){
for(int i=0;i<cookieArr.length;i++){
out.println("Cookie的名字:"+cookieArr[i].getName());
out.println("Cookie的值:"+cookieArr[i].getValue());
}
}else{
Cookie cookie=new Cookie("uuid","uuidvalue");
response.addCookie(cookie);
}
%>
</body>
Cookie对象里的属性(每个属性都有set和get方法):
我只用1,2,3,其它都没有用过。
①String name:该Cookie的名称。Cookie一旦创建,名称便不可更改。
②String value:该Cookie的值。
③int maxAge:该Cookie失效的时间,单位秒。
(如果为正数,则该Cookie在>maxAge秒之后失效。如果为负数,
该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。
如果为0,表示删除该Cookie。默认为–1。)
④boolean secure:该Cookie是否仅被使用安全协议传输。默认为false。
⑤String path:该Cookie的使用路径。
如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。
如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。
⑥String domain:可以访问该Cookie的域名。
如果设置为“.google”,则所有以“google”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。
⑦String comment:该Cookie的用处说明。
⑧ int version:该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的。
例:设置过期时间,10秒,打开浏览器,运行一下locahost:8081/mvcproject/cookie.jsp,再刷新一下,可看到网页上打印cookie名与值,过10秒再刷新一下,没有东东了。
<body>
<%
Cookie[] cookieArr = request.getCookies();
if(cookieArr!=null && cookieArr.length>0){
for(int i=0;i<cookieArr.length;i++){
out.println("Cookie的名字:"+cookieArr[i].getName());
out.println("Cookie的值:"+cookieArr[i].getValue());
}
}else{
Cookie cookie=new Cookie("uuid","uuidvalue");
cookie.setMaxAge(10); //保存10秒自动清除,不设置时间,默认(-1)关闭浏览器就清除
response.addCookie(cookie);
}
%>
</body>
过了10秒再刷新,没东西了
js操作cookie
<title>cookie Test</title>
<script type="text/javascript">
//js对 cookie的操作,设置,获取,删除,下面调用函数,设置了一天后过期
function setCookie(c_name,c_value,c_expires){
var expires="";
if(c_expires!=null){
var sysDate =new Date();
alert(sysDate.toUTCString());
sysDate.setDate(sysDate.getDate()+c_expires);
alert(sysDate.toUTCString());
expires=";expires="+sysDate.toUTCString();
}
document.cookie = c_name+"="+escape(c_value)+expires; //创建cookie设置过期时间,单位为天
}
//setCookie("ssid","abc",1);
alert(document.cookie); //打印创 建的cookie
//js获取cookie
function getCookie(c_name){
//document.cookie,得到的是一个字符串式,每个cookie以健值对形式存再用;号隔开,最后一个键值对没有分号
if(document.cookie.length>0){
var start = document.cookie.indexOf(c_name+"=");
start = start+c_name.length+1;
var end = document.cookie.indexOf(";",start);
if(end==-1)
end=document.cookie.length; //如果没找到;,则表示;取最后个键值对
var cval=document.cookie.substring(start,end);
return unescape(cval);
}
}
alert(getCookie("ssid")); //abc
//js删除cookie,即把cookie的生期设成比当前时间更早的时间即可,那怕一毫秒也行
function deleteCookie(c_name){
var sysDate = new Date();
sysDate.setDate(sysDate.getDate()-1);
var time = ";expires="+sysDate.toUTCString();
var val = getCookie(c_name);
document.cookie= c_name+"="+escape(val)+time;
}
deleteCookie("ssid");
alert(getCookie("ssid"));//上面的abc值没了
</script>
</head>
<body>
实现指定的时间登陆不退出
以‘jsp基础知识’中讲的MVC例子为基础,在该项目’mvcproject’中,添加login.jsp,把index.jsp改为main.jsp,让登陆成功进入main.jsp。
- login.jsp
提交请求,登陆页面,转发到UserController.java中的login(req,resp)方法,该方法中判断用户名,密码在数据库中找得到不。如果找的到,则马上创建客户端cookie,一个是userKey,一个是ssid,ssid是加密处理后的username,
javascript代码:window.οnlοad=function()则是当一打开登录页面时,就会执行,它会判断当前cookie中是否有userKey,ssid两个cookie,如果有,则会让表单提交登录,这样就会让自动进入main.jsp页面。
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登陆页面</title>
<script type="text/javascript">
function getCook(c_name){
//document.cookie,得到的是一个字符串式,每个cookie以健值对形式存再用;号隔开,最后一个键值对没有分号
if(document.cookie.length>0){
var start = document.cookie.indexOf(c_name+"=");
start = start+c_name.length+1;
var end = document.cookie.indexOf(";",start);
if(end==-1)
end=document.cookie.length; //如果没找到;,则表示;取最后个键值对
var cval=document.cookie.substring(start,end);
return unescape(cval);
}
}
window.onload=function(){
var form = document.getElementById("loginform");
var username= document.getElementById("username");
if(getCook("ssid")!=null && getCook("ssid")!="" && getCook("userKey")!=null && getCook("userKey")!=""){
username.value=getCook("userKey");
form.submit();
}
}
</script>
</head>
<body>
<%if(request.getAttribute("not")!=null){ %>
<span style="color:red;"><%=request.getAttribute("not") %></span>
<% }
%>
<form id="loginform" action="<%=request.getContextPath()%>/login.udo">
用户名:<input id="username" type="text" name="username"/><br><br>
密 码:<input type="password" name="password"/><br><br>
记住我一天:<input type="radio" name="expiredays" value="1"/>记住我一个月<input type="radio" name="expiredays" value="30"/>记住我永久:<input type="radio" name="expiredays" value="30000"/>
<br><br>
<input type="submit" value="登 录"/>
</form>
</body>
</html>
- UserController.java
第二次或更多后来访问login.jsp时,会查询userKey(此时加密)与ssid(创建时加密了),如果两者不为空且相等,则转发到main.jsp页,如果为空,或相不等,则quest.setAttribute(“not”,“用户名或密码错误 !”),存储,再转发到login.jsp重新输入,并在登录页显示错误信息。
private void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
//拿到login.jsp提交的三个参数值
String username=req.getParameter("username");
String password=req.getParameter("password");
String expiredays = req.getParameter("expiredays");
Cookie[] cookies = req.getCookies();
boolean login =false; //标记:true,表示已经登录,false,表示没有登录
String account= null; //登录账号
String ssid = null; //通过cookie拿到的一个判断用该不该成功登录的标志
if(cookies !=null && cookies.length>0) {
for(Cookie cookie:cookies) {
if(cookie.getName().equals("userKey")) { //客户端的cookie,userKey是账号
account=cookie.getValue();
}
if(cookie.getName().equals("ssid")) { //客户端的cookie,这个是该不该成功登陆的标记
ssid=cookie.getValue();
}
}
}
if(account!=null && ssid!=null ) { //如果账号,登录成功标记,都不为空,看看ssid是否等于提交的用户名,
//第一次登陆是不执行,因为cookies是空的,ssid已是加密存在了客户端
login =ssid.equals(CookieUtils.md5Encrypt(username)); // md5Encrtypt是加密用户名方法
}
if(!login) { //第一次login为false,取反为true,表示还没登陆呢
//用户第一次打开登页的话,尝试输入用户名,密码去登录,这时要查询数据库有没有该用户,有的话返回一个用户对象
User user=userService.login(username,password); //没有的话返回一个null,
//我们实现login(xxx,xxx)方法的步子:先服务层:接口,实现类,再dao层:接口,实现类
if(user!=null) { //对的,登际成功
expiredays = expiredays==null?"":expiredays;
switch (expiredays) {
case "1": //选择了记住我一天,创建一个cookie对象,设置cookie对象的有效时间是一天
CookieUtils.createCookie(username, req, resp, 3600*24);
break;
case "30": //选择了记住我一月,创建一个cookie对象,设置cookie对象的有效时间是30天
CookieUtils.createCookie(username, req, resp, 3600*24*30);
break;
case "30000": //选择了记住记久,创建一个cookie对象,设置cookie对象的有效时间是30000天,对我来说吧
CookieUtils.createCookie(username, req, resp, Integer.MAX_VALUE);
break;
default: //创建一个cookie对象,有效时间是moren的-1,即关闭浏览器清除失效
CookieUtils.createCookie(username, req, resp, -1);
break;
}
//到这已登录成功,准许客户,进入main.jsp
req.getRequestDispatcher("/main.jsp").forward(req, resp);
}else {
req.setAttribute("not", "用户名或密码是错误 的!");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}else { //已经登录过,并在有效期,可直接进入main.jsp页
req.getRequestDispatcher("/main.jsp").forward(req, resp);
}
}
3.创建Cookie,加密方法放在工具层类CookieUtil.java
public class CookieUtils {
private static final String KEY=":XIONGSHAOWEN123:";
public static void createCookie(String username,HttpServletRequest req,HttpServletResponse resp,int sec) {
Cookie userCookie=new Cookie("userKey", username);
Cookie ssidCookie=new Cookie("ssid", md5Encrypt(username));
userCookie.setMaxAge(sec);
ssidCookie.setMaxAge(sec);
resp.addCookie(userCookie);
resp.addCookie(ssidCookie);
}
public static String md5Encrypt(String ss) {
ss=ss==null?"":ss+KEY;
char[] md5Digits= {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
try {
MessageDigest md=MessageDigest.getInstance("MD5");//加密方式,还有sha1,sha2...(Geihub里用到)
byte[] ssarr=ss.getBytes();
md.update(ssarr);
byte[] mssarr=md.digest(); //真正加密
int len=mssarr.length;
char[] str=new char[len*2];
int k=0;
for(int i=0;i<len;i++) {
byte b=mssarr[i];
str[k++]=md5Digits[b>>>4 & 0xf];
str[k++]=md5Digits[b & 0xf];
}
System.out.println(str);
return new String(str);
}catch(Exception e) {
e.printStackTrace();
}
return null;
}
}
session概述与应用
jsp中,session是内置对象,HttpServlet中对应的是HttpSession(HttpSession session=request.getSession()).
Session机制
除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,它是通过服务器来保持状态的,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。我们可以把客户端浏览器与服务器之间一系列交互的动作称为一个 Session。从这个语义出发,我们会提到Session持续的时间,会提到在Session过程中进行了什么操作等等;
Session指的是服务器端为客户端所开辟的存储空间,在其中保存的信息就是用于保持状态。从这个语义出发,我们则会提到往Session中存放什么内容,如何根据键值从 Session中获取匹配的内容等.
要使用Session,第一步当然是创建Session了。那么Session在何时创建呢?而在Java中,当客户端第一次访问Servlet或jsp页面的时候自动创建(是否一定会创建了,不一定)
,通过调用HttpServletRequest的getSession方法可以获得session对象。Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问html、image等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。
在创建了Session的同时,服务器会为该Session生成唯一的Session id,而这个Session id在随后的请求中会被用来重新获得已经创建的Session;在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有Sessionid(放在Cookie文件里);当客户端再次发送请求的时候,会将这个Sessionid带上,服务器接受到请求之后就会依据Sessionid找到相应的Session,从而再次使用之。(默认情况下session也要依赖cookie机制来实现)
Session相当于程序在服务器上建立的一份客户档案,会给客户自动分配一个编号id,客户来访的时候只需要提供自己的id,就可以查询对应客户档案内容
Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。Session对象是在客户端第一次请求服务器的时候创建的。Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。Servlet里通过request.getSession()方法获取该客户的Session,例如:jsp中
request还可以使用getSession(boolean create)来获取Session。区别是如果该客户的Session不存在,request.getSession()方法会返回null,而getSession(true)会先创建Session再将Session返回。
Servlet中必须使用request来编程式获取HttpSession对象,而JSP中内置了Session隐藏对象,可以直接使用。如果使用声明了<%@page session=“false” %>,则Session隐藏对象不可用。
Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。
由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。
Tomcat中Session的默认超时时间为20分钟。通过setMaxInactiveInterval(int seconds)修改超时时间。也可以修改web.xml改变Session的默认超时时间。例如修改为60分钟:
在server.xml中定义context时采用如下定义(单位为秒):
URL地址重写处理不支持cookie的浏览器
好多手机浏览器不支持cookie,从而获取不到SesssionID.还有跨域名不支持cookie.
URL地址重写是对客户端不支持Cookie的解决方案(正常情况下Seesion要依靠Cookie来识别的)。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写,例如:
如果是页面重定向(Redirection),URL地址重写可以这样写:
该方法会自动判断客户端是否支持Cookie。如果客户端支持Cookie,会将URL原封不动地输出来。如果客户端不支持Cookie,则会将用户Session的id重写到URL中。重写后的输出可能是这样的:
由于大部分的手机浏览器都不支持Cookie,程序都会采用URL地址重写来跟踪用户会话。索性禁止Session使用Cookie,统一使用URL地址重写会更好一些。Java Web规范支持通过配置的方式禁用Cookie。下面举例说一下怎样通过配置禁止使用Cookie。
打开项目根目录下的META-INF文件夹(跟WEB-INF文件夹同级,如果没有则创建),打开context.xml(如果没有则创建),编辑内容如下: /META-INF/context.xml:
部署后TOMCAT便不会自动生成名JSESSIONID的Cookie,Session也不会以Cookie为识别标志,而仅仅以重写后的URL地址为识别标志了
注意:该配置只是禁止Session使用Cookie作为识别标志,并不能阻止其他的Cookie读写。也就是说服务器不会自动维护名为JSESSIONID的Cookie了,但是程序中仍然可以读写其他的Cookie。
处理没有登陆 ,但可以进入其它页面的问题
改进以前写的项目‘mvcproject’,以前的时候我没有成功登入时,但我输入首页地址,可以进入该页面,这肯定是不行的。我们来修改一下代码如下:
原理:用到Session域对象的存储特性,整个会话中重定向,或转发中都可见存储的状态,再来判断是否成功登入,如果成功登入,则让进其它页,没有成功登入,则跳转到登陆页。
这里,我用session存储登录成功,已登陆过,分别存储登成功的用户名,再在其它页上获取它再判断是否为空,决定让其进入本页。
UserController.java----login()—》req.getSession()处
default: //创建一个cookie对象,有效时间是moren的-1,即关闭浏览器清除失效
CookieUtils.createCookie(username, req, resp, -1);
break;
}
//到这已登录成功,准许客户,进入main.jsp
req.getSession().setAttribute("usernamesession",user.getUsername() );
req.getRequestDispatcher("/main.jsp").forward(req, resp);
}else {
req.setAttribute("not", "用户名或密码是错误 的!");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}else { //已经登录过,并在有效期,可直接进入main.jsp页
req.getSession().setAttribute("usernamesession",username);
req.getRequestDispatcher("/main.jsp").forward(req, resp);
}
}
}
其它页.jsp,写入代码,注意:getAttribute(“usernamesession”)
<body>
<% String username=(String)request.getSession().getAttribute("usernamesession");
if(username==null || "".equals(username)){
response.sendRedirect(request.getContextPath()+"/login.jsp");
}
%>
.....
注销用户
private void logout(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
//记录登录状态的cookie删除
//记录登录状态的seesion也要删除
Cookie[] cookies=req.getCookies();
if(cookies!=null && cookies.length>0) {
for(Cookie cookie:cookies) {
if(cookie.getName().equals("userKey")) {
cookie.setMaxAge(0);
resp.addCookie(cookie); //更新Cookie对象
}
if(cookie.getName().equals("ssid")) {
cookie.setMaxAge(0);
resp.addCookie(cookie); //更新Cookie对象
}
}
}
HttpSession session = req.getSession();
if(session!=null) {
session.removeAttribute("usernamesession");
}
//退出登录后,跳转到login.jsp
resp.sendRedirect(req.getContextPath()+"/login.jsp");
}
Session防止表单重复提交
第一种情况:表单提交成功以后,直接点击浏览器上回退按钮,不是也不刷新页面,然后点击提交按钮再次提交表单。对数据库有操作,重复提交会给我们的数据库添加很多无意义,无效数据。
解决方案:
使用一个token的机制
- token就是令牌的意思
- 服务器在处理请求之前先来检查浏览器的token
- token由服务器来创建,并交给浏览器,浏览器在向服务器发送请求时需要带着这个token
- 服务器处理请求前检查token是否正确,如果正确,则正常处理,否则返回一个错误页面
- 服务器所创建的token只能使用一次(移除掉)
- token一般使用一个唯一的标识
- 在jsp页面,获取uuid作为token
- UUID:32位字符串,通常作为对象或者表的唯一标识,根据机器码和时间戳(从1970年1月1日开始到现在的秒数)生成。
UUID含义是通用唯一识别码 (Universally Unique Identifier),javaJDK提供了UUID.randomUUID().toString()是一个自动生成主键的方法。它生成的值能保证对在同一时空中的所有机器都是唯一的,是由一个十六位的数字组成,表现出来的形式。UUID的唯一缺陷在于生成的结果串会比较长。
例子:mvcproject中,如果我把login.jsp中的javascript代码(功能:如成功登录过,再进入登录页,自动进入首面)云掉,或注释掉,这样我可以制造回退login.jsp页,可以再次提交表单。让我们方便测试,测试成功,再去掉注释。
主要修改的代码:两个地方,1.login.jsp 2.UserController.java—login()
login.jsp添加一个隐藏表单,让它提交uuid
<body>
<%
String uuid=UUID.randomUUID().toString();
session.setAttribute("uuid",uuid); //创建uuid,则由隐藏表单携带提交到服务器,控制层
%>
<form id="loginform" action="<%=request.getContextPath()%>/login.udo" method="post">
<!--隐藏表单,提交一份uuid-->
<input type="hidden" name="token" value="<%=uuid%>">
用户名:<input id="username" type="text" name="username"/><br><br>
密 码:<input type="password" name="password"/><br><br>
记住我一天:<input type="radio" name="expiredays" value="1"/>记住我一个月<input type="radio" name="expiredays" value="30"/>记住我永久:<input type="radio" name="expiredays" value="30000"/><br><br>
<input type="submit" value="登 录"/>
</form>
</body>
UserController.java----login(x,x)
private void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
//拿到login.jsp隐藏表单提交的参数值
String token = req.getParameter("token"); //通过表单提交的获取uuid型token
token= token==null?"":req.getParameter("token"); //防止空指针异常,null的话,让其等空字符串。
HttpSession session=req.getSession();
String tokenSession=(String)session.getAttribute("uuid"); //通过session获取uuid型token
tokenSession= tokenSession==null?"":(String)session.getAttribute("uuid");
session.removeAttribute("uuid"); //取出来放在变量中,马上又从sesion中删除之
..........
if(token.equals(tokenSession)) { //如果两个令,是相等的,说明表单第一次提交
if(!login) { //第一次login为false,取反为true,表示还没登陆呢
..............
}else {
System.out.println("重复提交了表单!不合法");
req.getRequestDispatcher("/main.jsp").forward(req, resp);
}
}
第二种情况: 在提交表单时,如果网速较差,可能会导致点击提交按钮多次,这种情况也会导致表单重复提交。
为了模拟网速慢,我登陆程序中,让主线程(当然这里也只有一个线程)沉睡几秒,相当于卡了下子,再我打开登录页,点击提交几秒没有反应,我可能会多点几次,这样造成多次提交了
处理办法:
在登录页,用js代码来控制提交按钮的操作,当第一次,点击按钮后,按钮显示灰色(表不可再点了)。我们让它的父节点,即整个表单来提交数据
private void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
try {
Thread.sleep(2000); //沉睡2秒,模拟网速慢。
}catch(Exception e) {
e.printStackTrace();
}
.......
}
login.jsp 修改js代码,加几行代码如下:
window.onload=function(){
var form = document.getElementById("loginform");
var username= document.getElementById("username");
var submitbt= document.getElementById("submitbt");
if(getCook("ssid")!=null && getCook("ssid")!="" && getCook("userKey")!=null && getCook("userKey")!=""){
username.value=getCook("userKey");
form.submit();
}
submitbt.onclick = function(){
this.disabled=true; //第一次登陆,让登陆按钮失效
this.parentNode.submit();
}
}
</script>
</head>
<body>
验证码
login.jsp,生成一个表单,和图片元素,提交请求
提交的控制层UserController.java----drawCode()方法,先把字符串,用session保存起来,response获得输出流,把字符串图片写到输出流—ImageIO.write(x,x,out),浏览器会识别加载
/**
* 画一张验证码图片
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
public void drawCode(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
resp.setContentType("image/jpg"); //设置服务器响应内容是图片
int width=100;
int height=30;
//具体画验证码
CaptchaUtils captcha=CaptchaUtils.getInstance(); //返回四位字符的图片对象
captcha.set(width, height);
String cc = captcha.generateCheckCode(); //验证码
HttpSession session=req.getSession();
session.setAttribute("checkCode", cc);
OutputStream out=resp.getOutputStream();
ImageIO.write(captcha.generateCheckImg(cc), "jpg", out);
}
CaptchaUtils.java ,有生成四个字符的字符串,有生成含字符串的图片
public class CaptchaUtils {
private int width; //验证码图片宽度
private int height; //验证码图片高度
private int num; //验证码字符个数
private String code; //验证码字典
private static final Random ran = new Random(); //取随机数对象
//单例模式 ,懒汉模式
private static CaptchaUtils captchaUtils;
private CaptchaUtils() {
code="0123456789abcdefgABCDEFG";
num=4;
}
public static CaptchaUtils getInstance() {
if(captchaUtils==null)
captchaUtils=new CaptchaUtils();
return captchaUtils;
}
public void set(int width,int height,int num,String code) {
this.width=width;
this.height=height;
this.setNum(num);
this.setCode(code);
}
public void set(int width,int height) {
this.width=width;
this.height=height;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 生成四个字符,(字典中的)
* @return
*/
public String generateCheckCode() {
StringBuffer cc= new StringBuffer();
for(int i=0;i<num;i++) {
cc.append(code.charAt(ran.nextInt(code.length())));
}
return cc.toString();
}
public BufferedImage generateCheckImg(String checkcode) {
//创建一个图片对象
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//获取画笔
Graphics2D graphic = img.createGraphics();
graphic.setColor(Color.WHITE);
graphic.fillRect(0, 0, width, height);
graphic.setColor(Color.BLACK);
graphic.drawRect(0, 0, width-1, height-1);
Font font=new Font("宋体",Font.BOLD+Font.ITALIC,(int)(height*0.8));
graphic.setFont(font);
for(int i=0;i<num;i++) {
graphic.setColor(new Color(ran.nextInt(155),ran.nextInt(255),ran.nextInt(255)));
graphic.drawString(String.valueOf(checkcode.charAt(i)), i*(width/num)+4, (int)(height*0.8));
}
//加一些点
for(int i=0;i<(width+height);i++) {
graphic.setColor(new Color(ran.nextInt(255),ran.nextInt(255),ran.nextInt(255)));
graphic.drawOval(ran.nextInt(width), ran.nextInt(height), 1, 1);
}
//加一些线
for(int i=0;i<2;i++) {
graphic.setColor(new Color(ran.nextInt(255),ran.nextInt(255),ran.nextInt(255)));
graphic.drawLine(0, ran.nextInt(height), width, ran.nextInt(height));
}
return img;
}
}
JavaBean的理解总结
javaBean
简单理解:
javaBean在MVC设计模型中是model,又称模型层,在一般的程序中,我们称它为数据层,就是用来设置数据的属性和一些行为,然后我会提供获取属性和设置属性的get/set方法
即
Javabean 就是一个类,这个类就定义一系列 get 和 set 方法。 So simple !Javabean 就是为了和 jsp 页面传数据化简交互过程而产生的。
而使用 javabean 之后,优势也就是 java 的优势:组件技术,代码重用,易于维护
一、什么是JavaBean
JavaBean是一个遵循特定写法的Java类,它通常具有如下特点:
1.这个Java类必须具有一个无参的构造函数
2.属性必须私有化。
3.私有化的属性必须通过public类型的方法暴露给其它程序,并且方法的命名也必须遵守一定的命名规范。
JavaBean在J2EE开发中,通常用于封装数据,对于遵循以上写法的JavaBean组件,其它程序可以通过反射技术实例化JavaBean对象,并且通过反射那些遵守命名规范的方法,从而获知JavaBean的属性,进而调用其属性保存数据。
User.java
public class User {
private int id;
private String username;
private String password;
private Date regDate; //要用java.util中的日期类,数据库里字段名:reg_date
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getRegDate() {
return regDate;
}
public void setRegDate(Date regDate) {
this.regDate = regDate;
}
//空构造,要的,方便其它框架调用
public User() {
super();
}
public User(int id, String username, String password, String phoneNo, String address, Date regDate) {
super();
this.id = id;
this.username = username;
this.password = password;
this.regDate = regDate;
}
@Override //方便测试,输出
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password ", regDate=" + regDate + "]";
}
}
二、JavaBean的属性
JavaBean的属性可以是任意类型,并且一个JavaBean可以有多个属性。每个属性通常都需要具有相应的setter、 getter方法,setter方法称为属性修改器,getter方法称为属性访问器。
属性修改器必须以小写的set前缀开始,后跟属性名,且属性名的第一个字母要改为大写,例如,name属性的修改器名称为setName,password属性的修改器名称为setPassword。
属性访问器通常以小写的get前缀开始,后跟属性名,且属性名的第一个字母也要改为大写,例如,name属性的访问器名称为getName,password属性的访问器名称为getPassword。
一个JavaBean的某个属性也可以只有set方法或get方法,这样的属性通常也称之为只写、只读属性。
三、在JSP中使用JavaBean(这种用法只是做为了解, 现在项目开发,基本不用了!,绝大多数情况都是在控制层使用,视图层提交)
JSP技术提供了三个关于JavaBean组件的动作元素,即JSP标签,它们分别为:
<jsp:useBean>标签
:用于在JSP页面中查找或实例化一个JavaBean组件。
<jsp:setProperty>
标签:用于在JSP页面中设置一个JavaBean组件的属性。
<jsp:getProperty>
标签:用于在JSP页面中获取一个JavaBean组件的属性。
EL标签
EL 全名为Expression Language
EL 语法很简单,它最大的特点就是使用上很方便。接下来介绍EL主要的语法结构:${sessionScope.user.sex}
所有EL都是以${为起始、以}为结尾的。上述EL范例的意思是:从Session的范围中,取得用户的性别。
<body>
<%
User user=new User();
user.setUsername("admin");
session.setAttribute("user",user);
%>
<!-- EL标签取session里user里的username -->
${sessionScope.user.username}
</body>
假若依照之前JSP Scriptlet的写法如下:
User user = (User)session.getAttribute("user");
String sex = user.getSex( );
两者相比较之下,可以发现EL 的语法比传统JSP Scriptlet 更为方便、简洁。
.与 [ ] 运算符
EL 提供 . 和 [ ] 两种运算符来导航数据。下列两者所代表的意思是一样的:
${sessionScope.user.username}等于${sessionScope.user["username"]}
. 和 [ ] 也可以同时混合使用,如下:
${sessionScope.shoppingCart[0].price}
回传结果为shoppingCart中第一项物品的价格。
不过,以下两种情况,两者会有差异:
(1) 当要存取的属性名称中包含一些特殊字符,如. 或 – 等并非字母或数字的符号,就一定要使用 [ ],例如:${user.My-Name }
上述是不正确的方式,应当改为:${user["My-Name"] }
(2) 我们来考虑下列情况:
${sessionScope.user[data]}
此时,data 是一个变量,假若data的值为"sex"时,那上述的例子等于${sessionScope.user.sex};
假若data 的值为"name"时,它就等于${sessionScope.user.name}
。因此,如果要动态取值时,就可以用上述的方法来做,但. 无法做到动态取值。
<body>
<%
User user1=new User();
user1.setUsername("admin");
User user2=new User();
user2.setUsername("xiongshaowen");
session.setAttribute("user1",user1);
session.setAttribute("user2", user2);
%>
${sessionScope[param.data].username } <!-- param相当于request.getParamter("xxx") -->
浏览器地址栏中分别输入参数?data=user1,?data=user2
EL 变量
EL 存取变量数据的方法很简单,例如:${username}。它的意思是取出某一范围中名称为username的变量。因为我们并没有指定哪一个范围的username,所以它的默认值会先从Page 范围找,假如找不到,再依序到Request、Session、Application范围。假如途中找到username,就直接回传,不再继续找下去,但是假如全部的范围都没有找到时,就回传null,当然EL表达式还会做出优化,页面上显示空白,而不是打印输出NULL。
我们也可以指定要取出哪一个范围的变量:
范例说明
${pageScope.username} 取出Page范围的username变量
${requestScope.username} 取出Request范围的username变量
${sessionScope.username} 取出Session范围的username变量
${applicationScope.username} 取出Application范围的username变量
<body>
<%
request.setAttribute("req", "request");
session.setAttribute("ses", "session");
pageContext.setAttribute("page","pageContext");
application.setAttribute("app", "application");
String name="xiongshaowen";
%>
${req }<br>
${ses }<br>
${page }<br>
${app }<br>
${name } <!-- 取不出来的 -->
</body>
自动转变类型
EL 除了提供方便存取变量的语法之外,它另外一个方便的功能就是:自动转变类型,我们来看下面这个范例:
${param.count + 20}
假若窗体传来count的值为10时,那么上面的结果为30。之前在JSP 之中不能这样做,原因是从窗体所传来的值,它们的类型一律是String,所以当你接收之后,必须再将它转为其他类型,如:int、float 等等,然后才能执行一些数学运算,下面是之前的做法:
String str_count = request.getParameter("count");
int count = Integer.parseInt(str_count);
count = count + 20;
所以,注意不要和java的语法(当字符串和数字用“+”链接时会把数字转换为字符串)搞混淆喽。
EL 隐含对象
JSP有9个隐含对象,而EL也有自己的隐含对象。EL隐含对象总共有11 个
不过有一点要注意的是如果你要用EL输出一个常量的话,字符串要加双引号,不然的话EL会默认把你认为的常量当做一个变量来处理,这时如果这个变量在4个声明范围不存在的话会输出空,如果存在则输出该变量的值。
<body>
${"xiongshaowen"} <!--输出当作常量,而不是当变量去域中找它的值-->
</body>
属性(Attribute)与范围(Scope)
与范围有关的EL 隐含对象包含以下四个:pageScope、requestScope、sessionScope 和
applicationScope,它们基本上就和JSP的pageContext、request、session和application一样,所以笔者在这里只稍略说明。不过必须注意的是,这四个隐含对象只能用来取得范围属性值,即JSP中的getAttribute(String name),却不能取得其他相关信息,例如:JSP中的request对象除可以存取属性之外,还可以取得用户的请求参数或表头信息等等。但是在EL中,它就只能单纯用来取得对应范围的属性值,例如:我们要在session 中储存一个属性,它的名称为username,在JSP 中使用session.getAttribute(“username”) 来取得username 的值, 但是在EL中,则是使用${sessionScope.username}
来取得其值的。
cookie
所谓的cookie是一个小小的文本文件,它是以key、value的方式将Session Tracking的内容记录在这个文本文件内,这个文本文件通常存在于浏览器的暂存区内。JSTL并没有提供设定cookie的动作,因为这个动作通常都是后端开发者必须去做的事情,而不是交给前端的开发者。假若我们在cookie 中设定一个名称为userCountry的值,那么可以使用${cookie.userCountry}来取得它,而不能设定它。
header 和headerValues
header 储存用户浏览器和服务端用来沟通的数据,当用户要求服务端的网页时,会送出一个记载要求信息的标头文件,例如:用户浏览器的版本、用户计算机所设定的区域等其他相关数据。假若要取得用户浏览器的版本,即${header[“User-Agent”]}。另外在鲜少机会下,有可能同一标头名称拥有不同的值,此时必须改为使用headerValues 来取得这些值。
注意:因为User-Agent 中包含“-”这个特殊字符,所以必须使用“[]”,而不能写成
$(header.User-Agent)。
initParam
就像其他属性一样,我们可以自行设定web 站台的环境参数(Context),当我们想取得这些参数initParam就像其他属性一样,我们可以自行设定web 站台的环境参数(Context),当我们想取得这些参数
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun/xml/ns/j2ee"
xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">:
<context-param>
<param-name>userid</param-name>
<param-value>mike</param-value>
</context-param>:
</web-app>
那么我们就可以直接使用 ${initParam.userid}来取得名称为userid,其值为mike 的参数。之前的做法:String userid = (String)application.getInitParameter(“userid”);
param和paramValues
在取得用户参数时通常使用一下方法:
request.getParameter(String name)
request.getParameterValues(String name)
在 EL中则可以使用param和paramValues两者来取得数据。
${param.name}
${paramValues.name}
这里param 的功能和request.getParameter(String name)相同,而paramValues和
request.getParameterValues(String name)相同。如果用户填了一个表格,表格名称为username,则我们就可以使用${param.username}来取得用户填入的值。
看到这里,大家应该很明确EL表达式只能通过内置对象取值,也就是只读操作,如果想进行写操作的话就让后台代码去完成,毕竟EL表达式仅仅是视图上的输出标签罢了。
pageContext
我们可以使用 ${pageContext}来取得其他有关用户要求或页面的详细信息。下表列出了几个比较常用的部分
Expression | 说明 |
---|---|
${pageContext.request.queryString} | 取得请求的参数字符串 |
${pageContext.request.requestURL} | 取得请求的URL,但不包括请求之参数字符串,即servlet的HTTP地址。 |
${pageContext.request.contextPath} | 服务的webapplication的名称 |
${pageContext.request.method} | 取得HTTP的方法(GET、POST)${pageContext.request.protocol} |
${pageContext.request.remoteUser} | 取得用户名称 |
${pageContext.request.remoteAddr} | 取得用户的IP地址 |
${pageContext.session.new} | 判断session是否为新的,所谓新的session,表示刚由server产生而client尚未使用 |
${pageContext.session.id} | 取得session的ID |
${pageContext.servletContext.serverInfo} | 取得主机端的服务信息 |
这个对象可有效地改善代码的硬编码问题,如页面中有一A标签链接访问一个SERVLET,如果写死了该SERVLET的HTTP地址那么如果当该SERVLET的SERVLET-MAPPING改变的时候必须要修改源代码,这样维护性会大打折扣。
EL算术运算
表达式语言支持的算术运算符和逻辑运算符非常多,所有在Java语言里支持的算术运算符,表达式语言都可以使用;甚至Java语言不支持的一些算术运算符和逻辑运算符,表达式语言也支持。
<table>
<tr>
<td><b>表达式语言</b></td>
<td><b>计算结果</b></td>
</tr>
<tr>
<td>\${1}</td>
<td>${1}</td>
</tr>
<tr>
<td>\${1.2+2.3}</td>
<td>${1.2+2.3}</td>
</tr>
<tr>
<td>\${1.2E4+1.4}</td>
<td>${1.2E4+1.4}</td>
</tr>
<tr>
<td>\${-4-2}</td>
<td>${-4-2}</td>
</tr>
<tr>
<td>\${21*2}</td>
<td>${21*2}</td>
</tr>
<tr>
<td>\${3/4}</td>
<td>${3/4}</td>
</tr>
<tr>
<td>\${3div4}</td>
<td>${3 div 4}</td>
</tr>
<!-- 计算除法 -->
<tr>
<td>\${3/0}</td>
<td>${3/0}</td>
</tr>
<!-- 计算求余 -->
<tr>
<td>\${10%4}</td>
<td>${10%4}</td>
</tr>
<!-- 计算求余 -->
<tr>
<td>\${10 mod 4}</td>
<td>${10 mod 4}</td>
</tr>
<!-- 计算三目运算符 -->
<tr>
<td>\${(1==2) ? 3 : 4}</td>
<td>${(1==2)?3:4}</td>
</tr>
</table>
--------------------------------------------------------------------------
表达式语言 计算结果
${1} 1
${1.2+2.3} 3.5
${1.2E4+1.4} 12001.4
${-4-2} -6
${21*2} 42
${3/4} 0.75
${3div4} 0.75
${3/0} Infinity
${10%4} 2
${10 mod 4} 2
${(1==2) ? 3 : 4} 4
上面页面中示范了表达式语言所支持的加、减、乘、除、求余等算术运算符的功能,读者可能也发现了表达式语言还支持div、mod等运算符。而且表达式语言把所有数值都当成浮点数处理,所以3/0的实质是3.0/0.0,得到结果应该是Infinity(无穷大)。
注意:在使用EL 关系运算符时,不能够写成:
${param.password1} = = ${param.password2}
或者
${ ${param.password1 } = = ${ param.password2 } }
而应写成
${ param.password1 = = param.password2 }
自定义标签
前面我们在jsp页面中,我们对集合或数组的显示,都是通过java的循环遍历来做的!
这个样子做,太麻烦,而且不利于前端美工和后端java程序员的分离,程序修改和维护起来都非常麻烦,我们希望的,java代码和前端页面完全分离!这个时候,EL标签就不够用了,比如循环遍历,if分支等等EL是做不到的!
首先我们想到的,能不能将循环,if分支也做成一个像EL的标签:
JDK为我们提供了自定义标签的接口
这是Java中标签规范的继承体系,实现Tag接口的我们叫做传统式标签库开发,这种开发模式略显发复杂,基本已经被SimpleTag式的简单式开发标签库给取代了。Java中提供了一个默认的实现类SimpleTagSupport
来实现自定义标签,我们只要继承此类即可。
接下来我们就看自定义标签的开发步骤:
①编写标签功能的java类(标签处理器)
②编写标签库描述文件(.tld)
③中jsp页面中导入和使用自定义的标签
例1:自定义打印Hello Tag的标签
1.自定义类HelloTag.java
public class HelloTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
getJspContext().getOut().println("Hello Tag!");//getJspContext(),可拿到页面域对象(使用标签的页面)
}
}
2.编写标签库描述文件.tld。
到Tomcat服务器上的webapps/examples/WEB-INF/jsp2中复制一个过来,修改名字存放到我们的项目中WEB-INF的任意子路径下。删除一些标签成如下内容:
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun/xml/ns/j2ee"
xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun/xml/ns/j2ee http://java.sun/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>HelloTag描述</description>
<tlib-version>1.0</tlib-version>
<!-- 标签的名字 -->
<short-name>hellotag</short-name>
<!-- 标签的id,非常重要 -->
<uri>http://hellotag/core</uri>
<tag>
<description>Outputs Hello, World</description>
<name>helloWorld</name>
<tag-class>cn.xsw.mvcproject.tag.HelloTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
3.使用标签库也是有两个步骤,首先导入标签库,然后引用标签。我们使用taglib编译指令导入标签库,具体格式如下:
<%@ taglib uri="tld文件中指定的唯一标识id" prefix="指定标签前缀"%>
我们使用标签的格式如下:<刚刚指定的前缀 :标签名 />标签名就是我们标签库中每个tag都会有的name的值.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://hellotag/core" prefix="hellotag" %>
<html>
<head>
<title>HelloTage标签</title>
</head>
<body>
<hellotag:helloWorld/>
</body>
</html>
例2:自定义带属性的标签,打印从控制层映射数据库结果集到网页中
MyTag
public class MyTag extends SimpleTagSupport{
private String map; //自定义标签,属性必须是字符串,整数等基本数据类型
public String getMap() {
return map;
}
public void setMap(String map) {
this.map = map;
}
//index.jsp请求控制层处理数据以map形式转发到它自已,这里标签doTag()方法中,可用request对象来
@SuppressWarnings("unchecked")
@Override
public void doTag() throws JspException, IOException {
PageContext pageContext=(PageContext)getJspContext();
HttpServletRequest hreq=(HttpServletRequest)pageContext.getRequest();
Map<String,Integer> maps = (Map<String, Integer>)hreq.getAttribute("map");
for(String key:maps.keySet()) {
pageContext.getOut().println("<tr>");
pageContext.getOut().println("<td>");
pageContext.getOut().println(key);
pageContext.getOut().println("</td>");
pageContext.getOut().println("<td>");
pageContext.getOut().println(maps.get(key));
pageContext.getOut().println("</td>");
pageContext.getOut().println("</tr>");
}
}
}
mytag.tld
tld页面看,这个页面基本没有什么改动,只是多了个attribute元素,attribute中有几个子元素,第一个是name指定这个属性的唯一标识,第二个required指定该属性是否是必须属性。
主要关心name这个元素。这个值和jsp页面调用标签时使用的属性名必须一样,并且这个属性值还必须和标签处理类中的私有属性名一样(这个私有属性必须是String类型),这就是为了jsp页面的属性值能够自动的传入到标签处理类的属性中,我们看这个标签处理类.
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun/xml/ns/j2ee"
xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun/xml/ns/j2ee http://java.sun/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>MyTag库描述</description>
<tlib-version>1.0</tlib-version>
<!-- 标签的名字 -->
<short-name>mytag库短名字</short-name>
<!-- 标签的id,非常重要 -->
<uri>http://mytag/core</uri>
<tag>
<description></description>
<name>mytag</name>
<tag-class>cn.xsw.mvcproject.tag.MyTag</tag-class>
<body-content>empty</body-content> <!-- empty指单标签,用/完事,不带标签体 -->
<attribute>
<name>map</name>
<required>true</required> <!-- 属性map是必须的 -->
<rtexprvalue>true</rtexprvalue> <!-- 可以接受表达式 -->
</attribute>
</tag>
</taglib>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!--<%@ taglib uri="http://hellotag/core" prefix="hellotag" %>-->
<%@ taglib uri="http://mytag/core" prefix="mt" %>
<html>
<head>
<title>自定义标签</title>
</head>
<body>
<!-- <hellotag:helloWorld/> -->
<table border="1" cellpadding="0" cellspacing="0">
<mt:mytag map="map11"/> <!-- map111自定义名字-->
</table>
</body>
</html>
控制层,可以在UserController.java定义一个index()方法,让它注入Map数据到域对象中,再通转发到jsp页面中,再调用标签,来读到数据打印到jsp页面中
/**
* 浏览器,请求index.udo,访问到这里来
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@SuppressWarnings("unused")
private void index(HttpServletRequest req,HttpServletResponse resp)throws ServletException, IOException {
Map<String,Integer> map = new HashMap<String, Integer>();
map.put("张三", 20);
map.put("李四", 30);
map.put("熊少文", 43);
req.setAttribute("map", map);
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
测试:
例3:自定义带标签体的标签
我们可以利用标签体来简化我们上一个案例中的标签处理类。
MyTag.java-----把输出html标签的代码云掉,再加三行代码即可
public class MyTag extends SimpleTagSupport{
.........
@SuppressWarnings("unchecked")
@Override
public void doTag() throws JspException, IOException {
PageContext pageContext=(PageContext)getJspContext();
HttpServletRequest hreq=(HttpServletRequest)pageContext.getRequest();
Map<String,Integer> maps = (Map<String, Integer>)hreq.getAttribute("map");
for(String key:maps.keySet()) {
//有标签体的注入数据
pageContext.setAttribute("name",key);
pageContext.setAttribute("age", maps.get(key));
getJspBody().invoke(null); //null 表示立刻写入到标签body内
}
}
}
mytag.tld—只改一个地方即可
<?xml version="1.0" encoding="UTF-8"?>
.....
<tag>
<description>大在大</description>
<name>mytag</name>
<tag-class>cn.xsw.mvcproject.tag.MyTag</tag-class>
<body-content>scriptless</body-content> <!-- empty指单标签,没有标签体 scriptless可以包含静态资源,即可有标签体-->
<attribute>
<name>map</name>
<required>true</required> <!-- 属性map是必须的 -->
<rtexprvalue>true</rtexprvalue> <!-- 可以接受表达式 -->
</attribute>
</tag>
</taglib>
index.jsp
</head>
<body>
<table border="1" cellpadding="0" cellspacing="0">
<mt:mytag map="map111">
<tr>
<td>${name}</td>
<td>${age}</td>
</tr>
</mt:mytag>
</table>
</body>
</html>
UserController.java 控制层不用改
tld文件中的改动不多,就是将body-content的值改动成scriptless,这表示标签体可以是静态的html,但是不能是jsp脚本。而我们之前一直是empty,它指定该标签是没有标签体的。
getJspBody()表示获取整个标签体的所有内容,返回的是一个fragment对象,这个对象的一个方法invoke就是用于输出整个内容到jsp页面,如果参数为null表示直接输出。
jstl
JSP Standard Tag Library(简称JSTL),是一套预先定义好、协助程序设计人员简化JSP网页制作的标签函数库,规格包含了各种网页运作所需的运用,例如循环、流程控制、输入/出、文本格式化,甚至XML文件处理及数据库访问操作均为其涵盖范围。
到JSP2.0后,通过结合使用JSTL和EL,可以开发出没有java代码的JSP,这使得JSP的开发非常简单,非Java程序员都能胜任。另外,通过使用JSTL可以使编程的代码量大大减少。
我们只用到核心标签,偶尔用到函数标签,其它如sql标签不可能在mvc中使用
设置JSTL运行环境
1.下载JSTL的jar包,下载地址:http://tomcat.apache/taglibs/standard/
Impl: taglibs-standard-impl-1.2.5.jar JSTL实现类库
Spec: taglibs-standard-spec-1.2.5.jar JSTL标准接口
EL: taglibs-standard-jstlel-1.2.5.jar JSTL1.0标签-EL相关
Compat: taglibs-standard-compat-1.2.5.jar 兼容版本
如果不使用JSTL1.0标签,可以忽略taglibs-standard-jstlel包,
taglibs-standard-compat包,应该是兼容以前版本标签库,
所以一般只需要 taglibs-standard-impl 和 taglibs-standard-spec 两个jar包
也可以不用下载,不下载可以到tomcat目录里去copy也是可以的!下载的JSTL以jar包形式存在,直接将其保存到WEB-INF\lib当中。
用WINRAR软件打开jar包,将其中META-INF文件夹中的几个主要标签配置文件(c.tld(主要是这个核心)、fmt.tld、fn.tld(要用到函数这个也要)、sql.tld、x.tld)保存在WEB-INF文件夹中(或其子目录里)。
使用JSTL之前,必须引用taglib指令声明网页所要使用的标签种类 。
<%@taglib prefix=tabName uri=uriString %>
prefix代表标签种类的前缀词,核心标签库前缀约定俗成为‘c’
uri代表标签的URI
以前,指令声明网页使用标签的两种方式。
1、直接指定路径
<%@ taglib prefix=“mytag” uri=“/WEB-INF/testcomp.tld” %>
2、间接引用
<%@ taglib prefix="tt" uri="http://testcomp/testcomp/core" %>
这样做需要在web.xml中增加下面的内容:
<jsp-config>
<taglib>
<taglib-uri>http://testcomp/testcomp/core</taglib-uri>
<taglib-location>/WEB-INF/testcomp.tld</taglib-location>
</taglib>
</jsp-config>
很明显,间接的方式更加灵活,当.tld文件改变存放位置时只需要修改web.xml,而不用去修改每个jsp页面。
注意: 在现在的新版本和编程环境, 如果没发现标签出问题的话,环境是可以直接通过反射从jar包里获取tld配置, 可以省略copy配置dlt文件,也可以省略xml中的配置! 只需要在jsp页面头上加标签指令就OK, 但uri地址不能错!
不过我们还是要了解去前的配置,这有助于我们解决出现的问题
使用核心标签
<c:out>
语法1:未包含主体(body)
<c:out value= "value" [escapeXml= "{true|false}"]
[default= "defaultValue"] />
语法2:包含主体(body)
<c:out value= "value" [escapeXml= "{true|false}"]>
default value
</c:out>
例1 c:out-网页输出’xxxxx’
<%@ taglib prefix="c" uri="http://java.sun/jsp/jstl/core" %>
<html>
<head>
<title>c:out</title>
</head>
<body>
<c:out value="jstl tag xiong"></c:out>
<br><br>
不使用jstl标签:<<Javaweb>>
<br><br>
使用jstl标签:<c:out value="<<Javaweb>>"/>
<br><br>
使用jstl标签:<c:out value="<<Javaweb>>" escapeXml="false"/>
使用jstl标签:
<c:out value="" escapeXml="true" >
<<Javaweb>>
</c:out>
</body>
</html>
----------------------------------------------------------------------------
jstl tag xiong
不使用jstl标签:<>
使用jstl标签:<<Javaweb>>
使用jstl标签:<>
使用jstl标签:
<c:set>
语法1:将value值储存至范围变量varName
<c:set value="value" var="varName"
[scope="{page|request|session|application}"]/>
语法2:将本体(body)内容设定储存至范围变量varName
<c:set var="varName" [scope="{page|request|session|application}"]>
body content
</c:set>
语法3:将value值储存至目标对象target的属性propertyName
<c:set target="target" property="propertyName" value="值"/>
语法4:将本体(body)内容储存至目标对象target的属性propertyName
<c:set target="target" property="propertyName">
body content
</c:set>
例1:set设置测试
<%@ page import="cn.xsw.mvcproject.model.User" language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun/jsp/jstl/core" %>
<body>
<c:set var="abc" value="字母"/>
<c:set var="abc" value="熊少文" scope="session"/>
<%
out.println(pageContext.getAttribute("abc"));
%>
<br><br>
<%
out.println(session.getAttribute("abc"));
%>
<%
User user= new User();
pageContext.setAttribute("user",user);
out.println("<br><br>");
out.println("没有set之前:"+user);
%>
<br>
<c:set target="${user}" property="username" value="李海英"/>
<br>
set之后:<c:out value="${pageScope.user }"/>
</body>
------------------------------------------------------------------------
字母
熊少文
没有set之前:User [id=0, username=null, password=null, phoneNo=null, address=null, regDate=null]
set之后:User [id=0, username=李海英, password=null, phoneNo=null, address=null, regDate=null]
<c:remove>
用来移除某个范围变量的內容值
<c:remove
var="varName"
[scope="{page|request|session|application}"]/>
例1:移除上面创建的user
<body>
<%
User user= new User();
pageContext.setAttribute("user",user);
%>
<br>
<c:set target="${user}" property="username" value="李海英"/>
<br>
set之后:<c:out value="${pageScope.user.username }"/>
<br>
<c:remove var="user" scope="page"/>
remove之后:<c:out value="${pageScope.user.username}"/>
</body>
JSTL标签的URL处理
<c:url>标签作用是将一个URL地址格式化为一个字符串,并且保存在一个变量当中。它具有URL自动重写功能。value指定的URL可以是当前工程的一个URL地址,也可以是其他web工程的URL。但是这时需要context属性。
也可以添加需要传递的参数。
属性:
var :变量名称
value:要格式化的URL
scope:作用域范围,默认为page
context:其他工程路径
语法1:无本体内容,就是单纯的显示url地址
<c:url value="value" [context="context"]
[var="varName"] [scope="{page|request|session|application}"]/>
语法2:在本体内容指定url地址的参数?+的形式跟在后面
<c:url value="value" [context="context"]
[var="varName"] [scope="{page|request|session|application}"]>
<c:param name="" value="">
</c:url>
<c:param>标签
语法1:將属性值指定給value属性
<c:param name="name" value="value"/>
语法2:將属性值指定給本体內容
<c:param name="name">
parameter value
</c:param>
<c:param>标签放在<c:url>本体內容当中,可用来设定连接所要传递的参数內容:
<c:url value=urlstring >
<c:param name="firstPara" value="123456" />
</c:url>
例:重写url,把另一个工程的index.html写出来,拼接成“/工程名/页面”
<body>
<c:url value="http://www.baidu" var="url"/> <!-- scope默认为page -->
<c:out value="${url }"/> <!-- 只是打印,没有链接功能的 -->
<br>
<c:url value="/index1.jsp" var="url" context="/jspservlet"/>
<c:out value="${url }"/>
<a href="${url}">jspservlet首页</a>
<br><br>
<c:url value="/index1.jsp" var="url" context="/jspservlet">
<c:param name="id" value="123"/>
</c:url>
<c:out value="${url }"/>
</body>
<c:import>标签——载入外部文件
语法1:载入数据内容直接嵌入标签或是输出成为String对象
<c:import url="url" [context="context"]
[var="varName"] [scope="{page|request|session|application}"]
[charEncoding="charEncoding"]>
optional body content for <c:param> subtags
</c:import>
<%@ taglib prefix="c" uri="http://java.sun/jsp/jstl/core" %>
<html>
<head>
<title>import载入文件</title>
</head>
<body>
载入了百度首页<br>
<c:import url="http://www.baidu" charEncoding="UTF-8"/>
</body>
</html>
<c:redirect>标签——重新定向网页
语法1:无主体(body)内容
<c:redirect url="value" [context="context"]/>
语法2:指定搜寻字符串参数的主体内容 ,带参数
<c:redirect url="value" [context="context"]/>
<c:param> subtags
</c:redirect>
例:跳转到百度首页,并且带参数 https://www.baidu?id=123456
<%@ taglib prefix="c" uri="http://java.sun/jsp/jstl/core" %>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>c:redirect重定向</title>
</head>
<body>
<c:redirect url="http://www.baidu">
<c:param name="id" value="123456"/>
</c:redirect>
</body>
</html>
JSTL流程控制
核心标签中的if、choose和when,提供相当于程序语言流程控制的功能 。
<c:if>与Java中的if语句相同 。
<c:choose>则被运用于需要进行多重判断的场合,它本身是一个框架,判断内容则由<c:when>和<c:otherwise>两个标签完成 。
语法1:无本体内容
<c:if test="testCondition"
var="varName" [scope="{page|request|session|application}"]/>
语法2:包含本体内容
<c:if test="testCondition"
[var="varName"] [scope="{page|request|session|application}"]>
body content
</c:if>
例:
<%@ taglib prefix="c" uri="http://java.sun/jsp/jstl/core" %>
<head>
<title>cifwhenchoose流程控制</title>
</head>
<body>
<c:if test="${3>20}" var="rs"/>
<c:out value="${rs}"/>
<br>
<c:if test="${3>2}" var="rss">
test的结果是true才会显示我这行内容
</c:if>
</body>
</html>
--------------------------------------------------------------------------
false
test的结果是true才会显示我这行内容
<c:choose>、<when>与<otherwise>
<c:choose>标签与Java的switch语句的功能一样,用于在众多选项中做出选择。
语法 - <c:choose>
<c:choose>
<c:when test="<boolean>">
...
</c:when>
<c:when test="<boolean>">
...
</c:when>
...
...
<c:otherwise>
...
</c:otherwise>
例:
<!-- 多重条件 -->
<c:set value="21" var="var"/>
<br><br>
<c:choose>
<c:when test="${var==1 }">
111111
</c:when>
<c:when test="${var==2 }">
222222
</c:when>
<c:when test="${var==3}">
333333
</c:when>
<c:otherwise>
4444444
</c:otherwise>
</c:choose>
JSTL迭代-运行循环
<c:forEach>
语法1:迭代对象集合内容。
<c:forEach [var="varName"] items="collection"
[varStatus="varStatusName"]
[begin="begin"] [end="end"] [step="step"]>
body content
</c:forEach>
语法2:迭代特定次数。
<c:forEach [var="varName"] [varStatus="varStatusName"]
begin="begin" end="end" [step="step"]>
body content
</c:forEach>
例:在控制类中写一个方法(TagController.java—cforeach()),创建一个表List list,注放到request域空间中,转发到/jstl/cforEachtest.jsp页面中,打印(EL打印)出来
/**
* jstl测试循环迭代,cforEachtest.jsp页面
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@SuppressWarnings("unused")
private void cforeach(HttpServletRequest req,HttpServletResponse resp)throws ServletException, IOException {
List<String> list = new ArrayList<>();
list.add("熊少文");
list.add("徐会凤");
list.add("黄任刚");
req.setAttribute("peoples", list);
req.getRequestDispatcher("/jstl/cforEachtest.jsp").forward(req, resp);
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>c-循环</title>
</head>
<body>
<c:forEach items="${peoples}" var="p">
<c:out value="${p}"/><br>
</c:forEach>
<!--以表格的形式打印-->
<table border="1" bgcolor="yellow">
<c:forEach items="${peoples}" var="p">
<tr>
<td>${p}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
测试:http://localhost:8081/mvcproject/cforeach.do
varStatus也就是status封装了当前遍历的状态,比如,可以从该对象上
查看是遍历到了第几个元素:${status.count}
<c:out value=”${status.current}”/> 当前对象
<c:out value=”${status.index}”/> 此次迭代的索引
<c:out value=”${status.count}”/> 已经迭代的数量
<c:out value=”${status.first}”/> 是否是第一个迭代对象
<c:out value=”${status.last}”/> 是否是最后一个迭代对象
例:
<body>
<table border="1" bgcolor="yellow">
<c:forEach items="${peoples}" var="p" varStatus="status">
<tr>
<td>现在循环到第 ${status.count} 个了</td>
<td>${p}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
begin、end以及step则是相关的属性,分别代表迭代的开始、结束以
及区间,这三个值相互影响,设定不正确可能导致程序流程的运行错
误 。
<c:forTaokens >标签
主要针对字符串类型的数据作设计,它可以解析一段字符串当中,以特定符号所分隔的字符串成员 。
<c:forTokens items="stringOfTokens" delims="delimiters"
[var="varName"]
[varStatus="varStatusName"]
[begin="begin"] [end="end"] [step="step"]>
body content
</c:forTokens>
<c:forTokens items="google,runoob,taobao" delims="," var="name">
<c:out value="${name}"/><p>
</c:forTokens>
例:
<body>
<!-- c:forTokens -->
<c:forTokens items="google,runoob,taobao" delims="," var="name">
<c:out value="${name }"/>
<p>
</c:forTokens>
</body>
----------------------------------------------------------------------------
google
runoob
taobao
JSTL格式化和函数
格式化日期
<fmt:formatDate value="date" [type="{time|date|both}"]
[dateStyle="{default|short|medium|long|full}"]
[timeStyle="{default|short|medium|long|full}"]
[pattern="customPattern"]
[timeZone="timeZone"]
[var="varName"]
[scope="{page|request|session|application}"]/>
声明指令
<%@ taglib prefix="fmt" uri="http://java.sun/jsp/jstl/fmt" %>
例:
<body>
<%
Date date= new Date();
pageContext.setAttribute("date",date);
%>
${date}
<br><br>
<!-- 格式化日期 -->
<fmt:formatDate value="${date}" dateStyle="short"/><br>
<fmt:formatDate value="${date}" dateStyle="medium"/><br>
<fmt:formatDate value="${date}" dateStyle="long"/><br>
<fmt:formatDate value="${date}" dateStyle="full"/><br>
<fmt:formatDate value="${date}" type="time" timeStyle="short"/><br>
<fmt:formatDate value="${date}" type="time" timeStyle="long"/><br>
<fmt:formatDate value="${date}" type="both" timeStyle="long"/><br>
<fmt:formatDate value="${date}" type="both" pattern="yyyy-MM-dd HH:mm:ss" timeStyle="long"/><br>
</body>
Mon May 05 13:13:27 CST 2025
25-5-5
2025-5-5
2025年5月5日
2025年5月5日 星期一
下午1:16
下午01时16分12秒
2025-5-5 下午01时17分31秒
2025-05-05 13:21:00
函数标签
<%@ taglib uri="http://java.sun/jsp/jstl/functions" prefix="fn" %>
例:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="c" uri="http://java.sun/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>函数标签处理字符串</title>
</head>
<body>
<c:set var="str1" value="abcdefghijklmnopqrstuvwxyz" />
<c:set var="str2" value="${fn:substring(str1,5,15)}" />
${str2}
</body>
</html>
fghijklmno
发布者:admin,转转请注明出处:http://www.yc00.com/web/1754949247a5219676.html
评论列表(0条)