Cookie和Session详解

摘要:
常用的会话跟踪技术是cookie和会话。Cookie通过在客户端记录信息来确定用户身份,会话通过在服务器端记录信息来决定用户身份。目前,cookie已成为标准。所有主流浏览器如IE、Netscape、Firefox、Opera等都支持cookie。服务器检查cookie以识别用户状态。每个cookie都是cookie类的对象。当在cookie中使用Unicode字符时,需要对其进行编码,否则将出现乱码。例如,在cookie中使用数字证书来提供安全性。由于浏览器每次请求服务器时都会携带cookie,因此cookie的内容不能太多,否则会影响速度。

Session和Cookie详解

会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。

Cookie通过在客户端记录信息确定用户身份Session通过在服务器端记录信息确定用户身份

1.1  Cookie机制

  在程序中,会话跟踪是很重要的事情。理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。例如,用户A在超市购买的任何商品都应该放在A的购物车内,不论是用户A什么时间购买的,这都是属于同一个会话的,不能放入用户B或用户C的购物车内,这不属于同一个会话。

而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,必须引入一种机制。

Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

1.1.1  什么是Cookie

  Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。

由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客 户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务 器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

查看某个网站颁发的Cookie很简单。在浏览器地址栏输入javascript:alert (document. cookie)就可以了(需要有网才能查看)。JavaScript脚本会弹出一个对话框显示本网站颁发的所有Cookie的内容,如图1.1所示。

Cookie和Session详解第1张

                      图1.1  Baidu网站颁发的Cookie

图1.1中弹出的对话框中显示的为Baidu网站的Cookie。其中第一行BAIDUID记录的就是笔者的身份helloweenvsfei,只是Baidu使用特殊的方法将Cookie信息加密了。

 注意:Cookie功能需要浏览器的支持。

如果浏览器不支持Cookie(如大部分手机中的浏览器)或者把Cookie禁用了,Cookie功能就会失效。

不同的浏览器采用不同的方式保存Cookie。

IE浏览器会在“C:Documents and Settings你的用户名Cookies”文件夹下以文本文件形式保存,一个文本文件保存一个Cookie。

 

1.1.2  记录用户访问次数

  Java中把Cookie封装成了javax.servlet.http.Cookie类。每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进行操作。通过request.getCookie()获取客户端提交的所有Cookie(以Cookie[]数组形式返回),通过response.addCookie(Cookiecookie)向客户端设置Cookie。

  Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,一个request或者response同时使用多个Cookie。因为Cookie类位于包javax.servlet.http.*下面,所以JSP中不需要import该类。

1.1.3  Cookie的不可跨域名性

  很多网站都会使用Cookie。例如,Google会向客户端颁发Cookie,Baidu也会向客户端颁发Cookie。那浏览器访问Google会不会也携带上Baidu颁发的Cookie呢?或者Google能不能修改Baidu颁发的Cookie呢?

  答案是否定的。Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。

  Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作 Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名 不一样,因此Google不能操作Baidu的Cookie。

需要注意的是,虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。

注意:用户登录网站www.google.com之后会发现访问images.google.com时登录信息仍然有效,而普通的Cookie是做不到的。这是因为Google做了特殊处理。本章后面也会对Cookie做类似的处理。

1.1.4  Unicode编码:保存中文

  中文与英文字符不同,中文属于Unicode字符,在内存中占4个字符,而英文属于ASCII字符,内存中只占2个字节。Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会乱码。

  提示:Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码,因为浏览器不一定支持,而且JavaScript也不支持GBK编码。

1.1.5  BASE64编码:保存二进制图片

  Cookie不仅可以使用ASCII字符与Unicode字符,还可以使用二进制数据。例如在Cookie中使用数字证书,提供安全度。使用二进制数据时也需要进行编码。

  注意:本程序仅用于展示Cookie中可以存储二进制内容,并不实用。由于浏览器每次请求服务器都会携带Cookie,因此Cookie内容不宜过多,否则影响速度。Cookie的内容应该少而精。

1.1.6  设置Cookie的所有属性

  除了name与value之外,Cookie还具有其他几个常用的属性。每个属性对应一个getter方法与一个setter方法。Cookie类的所有属性如表1.1所示。

                                表1.1  Cookie常用属性

属  性  名

描    述

String name

该Cookie的名称。Cookie一旦创建,名称便不可更改

Object value

该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码

int maxAge

该Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1

boolean secure

该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false

String path

该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”

String domain

可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”

String comment

该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明

int version

该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范

 

1.1.7  Cookie的有效期

Cookie的maxAge决定着Cookie的有效期,单位为秒(Second)。Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。

如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的 Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。 下面代码中的Cookie信息将永远有效

Cookie cookie = new Cookie("username","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(Integer.MAX_VALUE);           // 设置生命周期为MAX_VALUE

response.addCookie(cookie);                    // 输出到客户端

如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该 Cookie即失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中。Cookie信息保存在浏 览器内存中,因此关闭浏览器该Cookie就消失了。Cookie默认的maxAge值为–1。

 

如果maxAge为0,则表示删除该Cookie。Cookie机制没有提供删除Cookie的方法,因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会被浏览器从Cookie文件或者内存中删除,

例如:

Cookie cookie = new Cookie("username","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(0);                          // 设置生命周期为0,不能为负数

response.addCookie(cookie);                    // 必须执行这一句

 

response对象提供的Cookie操作方法只有一个添加操作add(Cookie cookie)。

要想修改Cookie只能使用一个同名的Cookie来覆盖原来的Cookie,达到修改的目的。删除时只需要把maxAge修改为0即可。

注意:从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。浏览器提交Cookie时只会提交name与value属性。maxAge属性只被浏览器用来判断Cookie是否过期。

1.1.8  Cookie的修改、删除

Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。

如果要删除某个Cookie,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。注意是0而不是负数。负数代表其他的意义。读者可以通过上例的程序进行验证,设置不同的属性。

注意:修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。

1.1.9  Cookie的域名

Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。

正常情况下,同一个一级域名下的两个二级域名如www.helloweenvsfei.com和 images.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有 helloweenvsfei.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数,例如:

 

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie

cookie.setDomain(".helloweenvsfei.com");           // 设置域名

cookie.setPath("/");                              // 设置路径

cookie.setMaxAge(Integer.MAX_VALUE);               // 设置有效期

response.addCookie(cookie);                       // 输出到客户端

 

可以修改本机C:WINDOWSsystem32driversetc下的hosts文件来配置多个临时域名,然后使用setCookie.jsp程序来设置跨域名Cookie验证domain属性。

注意:domain参数必须以点(".")开始。另外,name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie,可以生成两个Cookie,domain属性分别为两个域名,输出到客户端。

*关于Cookie跨域共享

正常的cookie只能在一个应用中共享,即一个cookie只能由创建它的应用获得。 
1.可在同一应用服务器内共享方法:设置cookie.setPath("/"); 
本机tomcat/webapp下面有两个应用:webapp_a和webapp_b, 
1)原来在webapp_a下面设置的cookie,在webapp_b下面获取不到,path默认是产生cookie的应用的路径。 
2)若在webapp_a下面设置cookie的时候,增加一条cookie.setPath("/");或者cookie.setPath("/webapp_b/"); 
就可以在webapp_b下面获取到cas设置的cookie了。 
3)此处的参数,是相对于应用服务器存放应用的文件夹的根目录而言的(比如tomcat下面的webapp),因此cookie.setPath("/");之后,可以在webapp文件夹下的所有应用共享cookie,而cookie.setPath("/webapp_b/"); 
是指cas应用设置的cookie只能在webapp_b应用下的获得,即便是产生这个cookie的webapp_a应用也不可以。 
4)设置cookie.setPath("/webapp_b/jsp")或者cookie.setPath("/webapp_b/jsp/")的时候,只有在webapp_b/jsp下面可以获得cookie,在webapp_b下面但是在jsp文件夹外的都不能获得cookie。 
5)设置cookie.setPath("/webapp_b");,是指在webapp_b下面才可以使用cookie,这样就不可以在产生cookie的应用webapp_a下面获取cookie了 
6)有多条cookie.setPath("XXX");语句的时候,起作用的以最后一条为准。 
2。跨域共享cookie的方法:设置cookie.setDomain(".jszx.com"); 
A机所在的域:home.langchao.com,A有应用webapp_a 
B机所在的域:jszx.com,B有应用webapp_b 
1)在webapp_a下面设置cookie的时候,增加cookie.setDomain(".jszx.com");,这样在webapp_b下面就可以取到cookie。 
2)输入url访问webapp_b的时候,必须输入域名才能解析。比如说在A机器输入:http://lc-bsp.jszx.com:8080/webapp_b,可以获取webapp_a在客户端设置的cookie,而B机器访问本机的应用,输入:http://localhost:8080/webapp_b则不可以获得cookie。 
3)设置了cookie.setDomain(".jszx.com");,还可以在默认的home.langchao.com下面共享

 注:通常,cookie却不能跨越域传递,只有那些创建它的域才能访问,同一根域名下的二级域名,三级域名可以直接共享。但你可以利用重定向来间接的获取cookies。

1.1.10  Cookie的路径

domain属性决定运行访问Cookie的域名,而path属性决定允许访问Cookie的路径(ContextPath)。例如,如果只允许/sessionWeb/下的程序使用Cookie,可以这么写:

Cookie cookie = new Cookie("time","20080808");     // 新建Cookie

cookie.setPath("/session/");                          // 设置路径

response.addCookie(cookie);                           // 输出到客户端

设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。name相同但domain相同的两个Cookie也是两个不同的Cookie。

注意:页面只能获取它属于的Path的Cookie。例如/session/test/a.jsp不能获取到路径为/session/abc/的Cookie。使用时一定要注意。

 

1.1.11  Cookie的安全属性

HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可 能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为 true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。下面的代码设置secure属性为true:

 

Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie

cookie.setSecure(true);                           // 设置安全属性

response.addCookie(cookie);                        // 输出到客户端

提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。

 

1.1.12  JavaScript操作Cookie

Cookie是保存在浏览器端的,因此浏览器具有操作Cookie的先决条件。浏览器可以使用脚本程序如 JavaScript或者VBScript等操作Cookie。这里以JavaScript为例介绍常用的Cookie操作。例如下面的代码会输出本页面 所有的Cookie。

<script>document.write(document.cookie);</script>

由于JavaScript能够任意地读写Cookie,有些好事者便想使用JavaScript程序去窥探用户在其他网 站的Cookie。不过这是徒劳的,W3C组织早就意识到JavaScript对Cookie的读写所带来的安全隐患并加以防备了,W3C标准的浏览器会 阻止JavaScript读写任何不属于自己网站的Cookie。换句话说,A网站的JavaScript程序读写B网站的Cookie不会有任何结果。

1.1.13 永久登陆案例

  实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。本例把账号保存到名为account的Cookie中,把账号连同密钥用MD1算法加密后保存到名为ssid的Cookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。

 

Cookie和Session详解第2张Cookie和Session详解第3张
  1 <%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %>
  2 <%!                                              
  3     /* JSP方法 */
  4     private static final String KEY =":cookie@helloweenvsfei.com"; // 密钥
  5     
  6     public final static String calcMD1(String  ss) { // MD1 加密算法
  7 
  8        String s = ss == null ? "" : ss;                  // 若为null返回空
  9        
 10        char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9', // 字典
 11        'a', 'b', 'c', 'd', 'e', 'f' };                       
 12        try {
 13 
 14            byte[] strTemp = s.getBytes();                          // 获取字节
 15            MessageDigest mdTemp = MessageDigest.getInstance("MD1"); // 获取MD1
 16            mdTemp.update(strTemp);                                // 更新数据
 17            byte[] md = mdTemp.digest();                        // 加密
 18            int j = md.length;                                 // 加密后的长度
 19            char str[] = newchar[j * 2];                       // 新字符串数组
 20            int k = 0;                                         // 计数器k
 21            for (int i = 0; i< j; i++) {                       // 循环输出
 22                 byte byte0 = md[i];
 23                 str[k++] = hexDigits[byte0 >>> 4 & 0xf];
 24                 str[k++] = hexDigits[byte0 & 0xf];
 25            }
 26             return newString(str);                             // 加密后字符串
 27            } catch (Exception e){
 28            return null;
 29         }
 30     }
 31 %>
 32 <%
 33        request.setCharacterEncoding("UTF-8");              //设置request编码
 34        response.setContentType("text/html");                //设置MIME类型
 35     response.setCharacterEncoding("UTF-8");                //设置response编码
 36     String action = request.getParameter("action");      // 获取action参数
 37     
 38     if("login".equals(action)){                         // 如果为login动作
 39 
 40         String account = request.getParameter("account");            // 获取account参数
 41         String password = request.getParameter("password");            // 获取password参数                                                    
 42         int timeout = newInteger(request.getParameter("timeout"));    // 获取timeout参数
 43         String ssid = calcMD1(account + KEY);                         // 把账号、密钥使用MD1加密后保存
 44         Cookie accountCookie = new Cookie("account", account);        // 新建Cookie
 45         accountCookie.setMaxAge(timeout);              // 设置有效期
 46         Cookie ssidCookie = new Cookie("ssid", ssid);   // 新建Cookie
 47         ssidCookie.setMaxAge(timeout);                 // 设置有效期
 48         response.addCookie(accountCookie);             // 输出到客户端
 49         response.addCookie(ssidCookie);            // 输出到客户端
 50 
 51         //重新访问该页面(添加参数System.currentTimeMillis()禁止浏览器缓存页面内容)------------->此处重新请求该页面是为了在一个页面中处理完毕所有逻辑
 52         response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
 53         return;
 54     }else if("logout".equals(action)){                  // 如果为logout动作
 55 
 56         Cookie accountCookie = new Cookie("account", "");// 新建Cookie,内容为空                                       
 57         accountCookie.setMaxAge(0);                // 设置有效期为0,删除
 58         Cookie ssidCookie = new Cookie("ssid", ""); // 新建Cookie,内容为空
 59         ssidCookie.setMaxAge(0);                   // 设置有效期为0,删除
 60         response.addCookie(accountCookie);         // 输出到客户端
 61         response.addCookie(ssidCookie);         // 输出到客户端
 62 
 63       //重新访问该页面(添加参数System.currentTimeMillis()禁止浏览器缓存页面内容)------------->此处重新请求该页面是为了在一个页面中处理完毕所有逻辑
 64         response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
 65         return;
 66     }
 67 
 68     boolean login = false;                        // 是否登录
 69     String account = null;                        // 账号
 70     String ssid = null;                           // SSID标识
 71 
 72     if(request.getCookies() != null){               // 如果Cookie不为空
 73 
 74         for(Cookie cookie :request.getCookies()){  // 遍历Cookie
 75            if(cookie.getName().equals("account"))  // 如果Cookie名为
 76                account = cookie.getValue();       // 保存account内容
 77            if(cookie.getName().equals("ssid")) // 如果为SSID
 78                ssid = cookie.getValue();          // 保存SSID内容
 79         }
 80     }
 81 
 82     if(account != null && ssid !=null){    // 如果account、SSID都不为空
 83         login = ssid.equals(calcMD1(account + KEY));  // 如果加密规则正确, 则视为已经登录
 84     }
 85 %>
 86 
 87 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
 88 
 89        <legend><%= login ? "欢迎您回来" : "请先登录"%></legend>
 90 
 91         <% if(login){%>欢迎您, ${ cookie.account.value }. &nbsp;&nbsp;
 92 
 93          <a href="${ pageContext.request.requestURI }?action=logout">注销</a>
 94 
 95         <% } else {%>
 96 
 97         <formaction="${ pageContext.request.requestURI }?action=login" method="post">
 98            <table>
 99                <tr><td>账号:</td>
100                    <td><input type="text"name="account" style="200px; "></td>
101                </tr>
102                <tr><td>密码:</td>
103                    <td><inputtype="password" name="password"></td>
104                </tr>
105                <tr>
106                    <td>有效期:</td>
107                    <td><inputtype="radio" name="timeout" value="-1"
108                      checked> 关闭浏览器即失效 <br/> <input type="radio" 
109                    name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天 内有效 <br/><input type="radio" name="timeout"
110                    value="<%= Integer.MAX_VALUE %>"> 永久有效 <br/> </td>
111                </tr>
112 
113                <tr>
114                       <td></td>
115                    <td><input type="submit"value="登 录 " class="button"></td>
116                </tr>
117            </table>
118         </form>
119 <% } %>
代码示例

1.1.14 

 cookie与你的主机(域)相关,而非你的servlet或JSP页面。因而,尽管你的servlet可能只发送了单个cookie,你也可能会得到许多不相关的cookie。

1.1.15   如何使用cookie检测初访者?

(1)调用HttpServletRequest.getCookies()获取Cookie数组

(2)在循环中检索指定名字的cookie是否存在以及对应的值是否正确

(3)如果是则退出循环并设置区别标识

(4)根据区别标识判断用户是否为初访者从而进行不同的操作

注:不能仅仅因为cookie数组中不存在在特定的数据项就认为用户是个初访者。如果cookie数组为null,客户可能是一个初访者,也可能是由于用户将cookie删除或禁用造成的结果。但是,如果数组非null,也不过是显示客户曾经到过你的网站或域,并不能说明他们曾经访问过你的servlet。其它servlet、JSP页面以及非Java Web应用都可以设置cookie,依据路径的设置,其中的任何cookie都有可能返回给用户的浏览器。正确的做法是判断cookie数组是否为空且是否存在指定的Cookie对象且值正确。(某个session中是否存在某个特定的key且其value是否正确

 

 

2.Session机制

2.1  Session机制

除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力

2.2.1  什么是Session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

2.2.2  实现用户登录

Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。Session对象是在客户端第一次请求服务器的时候创建的。 Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。Servlet里通过request.getSession()方法获取该客户的 Session,

例如:

HttpSession session = request.getSession();       // 获取Session对象

session.setAttribute("loginTime", new Date());     // 设置Session中的属性

out.println("登录时间为:" +(Date)session.getAttribute("loginTime"));      // 获取Session属性

request还可以使用getSession(boolean create)来获取Session。区别是如果该客户的Session不存在,request.getSession()方法会返回null,而 getSession(true)会先创建Session再将Session返回。

*何时使用getSession(true) true),即 getSession()getSession(),何时使用 getSession(false) 呢?

一般情况下,若要向Session 中存放数据, 则使用 getSession(true) true),即 getSession() 。意义为:若当前存在 Session ,则使用当前的 Session ;若当前不存在 Session ,则创建一个新的Session 。因为存放数据是必须要有 Session 的。若要从Session 中获取数据, 则一般使用 getSession(false) 。意义为:若当前存在 Session则从中获取数据;若当前根本就没有 Session ,那就更不可能存在 Session 中的数据了。 因为你要查找的数据只可能出现在已经存在的 Session 中,而不可能存在于新建的 Session 中。

注:一个常见的错误是以为session在有客户端访问时就被创建,然而事实是直到某server端程序(如Servlet)调用HttpServletRequest.getSession(true)这样的语句时才会被创建。

Servlet中必须使用request来编程式获取HttpSession对象,而JSP中内置了Session隐藏 对象,可以直接使用。如果使用声明了<%@page session="false" %>,则Session隐藏对象不可用。下面的例子使用Session记录客户账号信息。

源代码如下:

Cookie和Session详解第4张Cookie和Session详解第5张
  1 代码1.9  session.jsp
  2 
  3 <%@ page language="java" pageEncoding="UTF-8"%>
  4 
  5 <jsp:directive.page import="com.helloweenvsfei.sessionWeb.bean.Person"/>
  6 
  7 <jsp:directive.page import="java.text.SimpleDateFormat"/>
  8 
  9 <jsp:directive.page import="java.text.DateFormat"/>
 10 
 11 <jsp:directive.page import="java.util.Date"/>
 12 
 13 <%!
 14 
 15     DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器
 16 
 17 %>
 18 
 19 <%
 20 
 21     response.setCharacterEncoding("UTF-8");        // 设置request编码
 22 
 23     Person[] persons =
 24 
 25     {           
 26 
 27        // 基础数据,保存三个人的信息
 28 
 29         new Person("Liu Jinghua","password1", 34, dateFormat.parse
 30         ("1982-01-01")),
 31 
 32         new Person("Hello Kitty","hellokitty", 23, dateFormat.parse
 33         ("1984-02-21")),
 34 
 35         new Person("Garfield", "garfield_pass",23, dateFormat.parse
 36         ("1994-09-12")),
 37 
 38      };
 39 
 40    
 41 
 42     String message = "";                      // 要显示的消息
 43 
 44    
 45 
 46     if(request.getMethod().equals("POST"))
 47 
 48     { 
 49 
 50         // 如果是POST登录       
 51 
 52         for(Person person :persons)
 53 
 54         {           
 55 
 56             // 遍历基础数据,验证账号、密码
 57 
 58             // 如果用户名正确且密码正确
 59 
 60            if(person.getName().equalsIgnoreCase(request.getParameter("username"))&&person.getPassword().equals(request.getParameter("password")))
 61 
 62            {              
 63 
 64                // 登录成功,设置将用户的信息以及登录时间保存到Session
 65 
 66                session.setAttribute("person", person);                   // 保存登录的Person
 67 
 68                session.setAttribute("loginTime", new Date());          // 保存登录的时间              
 69 
 70                response.sendRedirect(request.getContextPath() + "/welcome.jsp");
 71 
 72                return;
 73 
 74             }
 75 
 76         }      
 77 
 78         message = "用户名密码不匹配,登录失败。";       // 登录失败
 79 
 80     }
 81 
 82 %>
 83 
 84 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
 85 
 86 <html>
 87 
 88     // ... HTML代码为一个FORM表单,代码略,请看随书光盘
 89 
 90 </html>
 91 
 92  
 93 
 94 登录界面验证用户登录信息,如果登录正确,就把用户信息以及登录时间保存进Session,然后转到欢迎页面welcome.jsp。welcome.jsp中从Session中获取信息,并将用户资料显示出来。
 95 
 96 welcome.jsp代码如下:
 97 
 98 代码1.10  welcome.jsp
 99 
100 <%@ page language="java" pageEncoding="UTF-8"%>
101 
102 <jsp:directive.pageimport="com.helloweenvsfei.sessionWeb.bean.Person"/>
103 
104 <jsp:directive.page import="java.text.SimpleDateFormat"/>
105 
106 <jsp:directive.page import="java.text.DateFormat"/>
107 
108 <jsp:directive.page import="java.util.Date"/>
109 
110 <%!
111 
112     DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器
113 
114 %>
115 
116 <%
117 
118     Person person =(Person)session.getAttribute("person");                       // 获取登录的person
119 
120     Date loginTime =(Date)session.getAttribute("loginTime");                     // 获取登录时间
121 
122 %>
123 
124     // ... 部分HTML代码略
125 
126             <table>
127 
128                <tr><td>您的姓名:</td>
129 
130                    <td><%= person.getName()%></td>
131 
132                </tr>
133 
134                <tr><td>登录时间:</td>
135 
136                    <td><%= loginTime%></td>
137 
138                </tr>
139 
140                <tr><td>您的年龄:</td>
141 
142                    <td><%= person.getAge()%></td>
143 
144                </tr>
145 
146                <tr><td>您的生日:</td>
147 
148                    <td><%=dateFormat.format(person.getBirthday()) %></td>
149 
150                </tr>
151 
152             </table>
View Code

注意程序中Session中直接保存了Person类对象与Date类对象,使用起来要比Cookie方便。

当多个客户端执行程序时,服务器会保存多个客户端的Session。获取Session的时候也不需要声明获取谁的Session。Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见

提示:Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。

2.2.3  Session的生命周期

Session保存在服务器端。为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简。

Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。

Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。

2.2.4  Session的有效期

由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。

Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。

Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。 

2.2.5  Session的常用方法

Session中包括各种方法,使用起来要比Cookie方便得多。Session的常用方法如表1.2所示。

                             HttpSession的常用方法

方  法  名

描    述

void setAttribute(String attribute, Object value)

设置Session属性。value参数可以为任何Java Object。通常为Java Bean。value信息不宜过大

String getAttribute(String attribute)

返回Session属性

Enumeration getAttributeNames()

返回Session中存在的属性名

void removeAttribute(String attribute)

移除Session属性

String getId()

返回Session的ID。该ID由服务器自动创建,不会重复

long getCreationTime()

返回Session的创建日期。返回类型为long,常被转化为Date类型,例如:Date createTime = new Date(session.get CreationTime())

long getLastAccessedTime()

返回Session的最后活跃时间。返回类型为long

int getMaxInactiveInterval()

返回Session的超时时间。单位为秒。超过该时间没有访问,服务器认为该Session失效

void setMaxInactiveInterval(int second)

设置Session的超时时间。单位为秒

void putValue(String attribute, Object value)

不推荐的方法。已经被setAttribute(String attribute, Object Value)替代

Object getValue(String attribute)

不被推荐的方法。已经被getAttribute(String attr)替代

boolean isNew()

返回该Session是否是新创建的

void invalidate()

使该Session失效

Tomcat中Session的默认超时时间为20分钟。通过setMaxInactiveInterval(int seconds)修改超时时间。可以修改web.xml改变Session的默认超时时间。例如修改为60分钟:

<session-config>

   <session-timeout>60</session-timeout>      <!-- 单位:分钟 -->

</session-config>

注意:<session-timeout>参数的单位为分钟,而setMaxInactiveInterval(int s)单位为秒。

2.2.6  Session对浏览器的要求

  虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session 需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一 个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session 依据该Cookie来识别是否为同一用户。

该Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。

因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击桌面浏览器图标等打开的窗口)除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session。

注意:新开的浏览器窗口会生成新的Session,但子窗口除外。子窗口会共用父窗口的Session。例如,在链接上右击,在弹出的快捷菜单中选择“在新窗口中打开”时,子窗口便可以访问父窗口的Session。

如果客户端浏览器将Cookie功能禁用,或者不支持Cookie怎么办?例如,绝大多数的手机浏览器都不支持Cookie。Java Web提供了另一种解决方案:URL地址重写。 

2.2.7  URL地址重写

URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写 到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。 HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写,例如:

<td>

    <a href="http://t.zoukankan.com/<%=response.encodeURL("index.jsp?c=1&wd=Java") %>"> 
    Homepage</a>

</td>

该方法会自动判断客户端是否支持Cookie。如果客户端支持Cookie,会将URL原封不动地输出来。如果客户端不支持Cookie,则会将用户Session的id重写到URL中。重写后的输出可能是这样的:

<td>

    <ahref="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=
    1&wd=Java">Homepage</a>

</td>

即在文件名的后面,在URL参数的前面添加了字符串“;jsessionid=XXX”。其中XXX为Session的 id。分析一下可以知道,增添的jsessionid字符串既不会影响请求的文件名,也不会影响提交的地址栏参数。用户单击这个链接的时候会把 Session的id通过URL提交到服务器上,服务器通过解析URL地址获得Session的id。

如果是页面重定向(Redirection),URL地址重写可以这样写:

<%

    if(“administrator”.equals(userName))

    {

       response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”));

        return;

    }

%>

效果跟response.encodeURL(String url)是一样的:如果客户端支持Cookie,生成原URL地址,如果不支持Cookie,传回重写后的带有jsessionid字符串的地址。

对于WAP程序,由于大部分的手机浏览器都不支持Cookie,WAP程序都会采用URL地址重写来跟踪用户会话。比如用友集团的移动商街等。

注意:TOMCAT判断客户端浏览器是否支持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会支持Cookie,但是由于第一次请求时不会携带任何Cookie(因为并无任何Cookie可以携带),URL地址重写后的地址中仍然会带有jsessionid。当第二次访问时服务器已经在浏览器中写入Cookie了,因此URL地址重写后的地址中就不会带有jsessionid了。

2.2.8  Session中禁止使用Cookie

既然WAP上大部分的客户浏览器都不支持Cookie,索性禁止Session使用Cookie,统一使用URL地址重写会更好一些。Java Web规范支持通过配置的方式禁用Cookie。下面举例说一下怎样通过配置禁止使用Cookie。

打开项目sessionWeb的WebRoot目录下的META-INF文件夹(跟WEB-INF文件夹同级,如果没有则创建),打开context.xml(如果没有则创建),编辑内容如下:

代码1.11 /META-INF/context.xml

<?xml version='1.0' encoding='UTF-8'?>

<Context path="/sessionWeb"cookies="false">

</Context>

或者修改Tomcat全局的conf/context.xml,修改内容如下:

代码1.12  context.xml

<!-- The contents of this file will be loaded for eachweb application -->

<Context cookies="false">

    <!-- ... 中间代码略 -->

</Context>

部署后TOMCAT便不会自动生成名JSESSIONID的Cookie,Session也不会以Cookie为识别标志,而仅仅以重写后的URL地址为识别标志了。

注意:该配置只是禁止Session使用Cookie作为识别标志,并不能阻止其他的Cookie读写。也就是说服务器不会自动维护名为JSESSIONID的Cookie了,但是程序中仍然可以读写其他的Cookie。

*2.2.9 Session的工作原理(面试考点!!!)

(1) 写入 Session 列表  服务器对 当前应用中的 Session 是 以 Map 的形式进行管理的 ,这个 Map 称为 Session 列表 。 该 Map 的 key 为一个 32 位长度的随机串,这个随机 串称为 JSessionID value 则为 Session对象 的引用 。

          当用户第一次提交请求时,服务端 Servlet 中执行 到 request.getSession() 方法后,会自动生成一个 Map.Entry 对象, key 为一个根据某种算法新生成的 JSessionID value 则为新创建的 HttpSession 对象。

Cookie和Session详解第6张

 (2) 服务器生成并发送 Cookie

  在将Session 信息写 入 Session 列表后, 系统还会自动 将 JSESSIONID 作为 name 这个 32 位长度的 随机串作为 value 以 Cookie 的形式 存放到响应报头中,并随着响应,将 该Cookie 发送 到客户端。

 Cookie和Session详解第7张

(3) 客户端接收并发送 Cookie

  客户端接收到这个 Cookie 后会将其存放到浏览器的缓存中。 即,只要客户端浏览器不关闭,浏览器缓存中的 Cookie 就不会消失。当用户提交第二次请求时,会将 缓存中的这个 Cookie ,伴随着请求的头部信息,一块发送到服务端。

 Cookie和Session详解第8张

(4) 从 Session 列表中查找

  服务端从请求中读取到客户端发送来的Cookie ,并根据 Cookie 的 JSSESSIONID 的 值,从Map 中查找相应 key 所对应的 value ,即 Session 对象。然后,对该 Session 对象的域属性进行读写操作。

 

Session和Cookie的区别:

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。

(Cookie欺骗例子:https://www.cnblogs.com/mq0036/p/9110710.html)

3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能, 考虑到减轻服务器性能方面,应当使用COOKIE。

4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

5、所以个人建议:将登陆信息等重要信息存放为SESSION,其他信息如果需要保留,可以放在COOKIE中

保存session Id的几种方式

1.保存session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。

2.由于cookie可以被人为的禁止,必须有其它的机制以便在cookie被禁止时仍然能够把session id传递回服务器,经常采用的一种技术叫做URL重写,就是把session id附加在URL路径的后面,附加的方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

3.另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。

 

session何时被删除?

session在下列情况下被删除:

1.程序调用HttpSession.invalidate( )

2.距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间

3.服务器进程被停止

再次注意关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效。

 

URL重写有什么缺点?

   对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL。每个引用你的站点的URL,以及那些返回给用户的URL(即使通过间接手段,比如服务器重定向中的Location字段)都要添加额外的信息. 这意味着在你的站点上不能有任何静态的HTML页面(至少静态页面中不能有任何链接到站点动态页面的链接)。因此,每个页面都必须使用servlet或JSP动态生成。即使所有的页面都动态生成,如果用户离开了会话并通过书签或链接再次回来,会话的信息都会丢失,因为存储下来的链接含有错误的标识信息-该URL后面的SESSION ID已经过期了。

 

使用隐藏的表单域有什么缺点?

   仅当每个页面都是有表单提交而动态生成时,才能使用这种方法。单击常规的<A HREF..>超文本链接并不产生表单提交,因此隐藏的表单域不能支持通常的会话跟踪,只能用于一系列特定的操作中,比如在线商店的结账过程

 

会话属性的类型有什么限制吗?

通常会话属性的类型只要是Object就可以了。除了null或基本类型,如int,double,boolean。如果要使用基本类型的值作为属性,必须将其转换为相应的封装类对象

 

如何废弃会话数据?

 1.只移除自己编写的servlet创建的数据:调用removeAttribute(“key”)将指定键关联的值废弃

 2.删除整个会话(在当前Web应用中),调用invalidate,将整个会话废弃掉。这样做会丢失该用户的所有会话数据,而非仅仅由我们servlet或JSP页面创建的会话数据

 3.将用户从系统中注销并删除所有属于他(或她)的会话,调用logOut,将客户从Web服务器中注销,同时废弃所有与该用户相关联的会话(每个Web应用至多一个)。这个操作有可能影响到服务器上多个不同的Web应用。

 

如何使用会话显示每个客户的访问次数?

  由于客户的访问次数是一个整型的变量,但session的属性类型中不能使用int,double,boolean等基本类型的变量,所以我们要用到这些基本类型的封装类型对象作为session对象中属性的值,但像Integer是一种不可修改(Immutable)的数据结构:构建后就不能更改。这意味着每个请求都必须创建新的Integer对象,之后使用setAttribute来代替之前存在的老的属性的值。

 

sessionid如何产生?由谁产生?保存在哪里?

sessionid是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,有一个sessionid和它对应。tomcat生成的sessionid叫做jsessionid。

session在访问tomcat服务器HttpServletRequest的getSession(true)的时候创建,tomcat的ManagerBase类提供创建sessionid的方法:随机数+时间+jvmid;

存储在服务器的内存中,tomcat的StandardManager类将session存储在内存中,也可以持久化到file,数据库,memcache,redis等。客户端只保存sessionid到cookie中,而不会保存session,session销毁只能通过invalidate或超时,关掉浏览器并不会关闭session。

session存放在哪里:服务器端的内存中。不过session可以通过特殊的方式做持久化管理(memcache,redis)。

session的id是从哪里来的,sessionID是如何使用的:当客户端第一次请求session对象时候,服务器会为客户端创建一个session,并将通过特殊算法算出一个session的ID,用来标识该session对象

tomcat中session的创建:

ManagerBase是所有session管理工具类的基类,它是一个抽象类,所有具体实现session管理功能的类都要继承这个类,该类有一个受保护的方法,该方法就是创建sessionId值的方法:
(tomcat的session的id值生成的机制是一个随机数加时间加上jvm的id值,jvm的id值会根据服务器的硬件信息计算得来,因此不同jvm的id值都是唯一的),
StandardManager类是tomcat容器里默认的session管理实现类,
它会将session的信息存储到web容器所在服务器的内存里。
PersistentManagerBase也是继承ManagerBase类,它是所有持久化存储session信息的基类,PersistentManager继承了PersistentManagerBase,但是这个类只是多了一个静态变量和一个getName方法,目前看来意义不大,对于持久化存储session,tomcat还提供了StoreBase的抽象类,它是所有持久化存储session的基类,另外tomcat还给出了文件存储FileStore和数据存储JDBCStore两个实现。

免责声明:文章转载自《Cookie和Session详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇JVM内存管理:深入垃圾收集器与内存分配策略Python中urlretrieve函数下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Java任意JSON字符串中,包含指定关键词的VALUE

1 /**Java任意JSON字符串中,包含指定关键词的json值*/ 2 public static voidmain(String[] s){ 3 String attrjson = "{"12":"100kg","96":"sd"}"; 4 JSONObject obj =(JSONObject) J...

Hbase介绍及操作

1. Hbase概述 1.1 Hbase是什么 HBase是建立在HDFS之上的分布式面向列的数据库;属于KV结构数据,原生不支持标准SQL。它是一个Apache的开源项目,是横向扩展的。 HBase可以提供快速随机访问海量结构化数据。它利用了Hadoop的文件系统(HDFS)提供的容错能力。 HBase不同于一般的关系数据库,它是一个适合于非结构化数据...

C++实现01串排序

题目内容:将01串首先按长度排序,长度相同时,按1的个数从少到多进行排序,1的个数相同时再按ASCII码值排序。 输入描述:输入数据中含有一些01串,01串的长度不大于256个字符。 输出描述:重新排列01串的顺序,使得串按题目描述的方式排序。 题目分析: (1)定义一个多重集合容器,该容器的元素类型为string,采用设定的比较函数 (2)因为元素是st...

ASP.NET基础知识整理

Asp.net六大对象 1.Request-->读取客户端在Web请求期间发送的值 常用方法: 1、Request.UrlReferrer请求的来源,可以根据这个判断从百度搜的哪个关键词、防下载盗链、防图片盗链,可以伪造(比如迅雷)。 (使用全局一般处理程序) 2、Request.UserHostAddress获得访问者的IP地址 3、Reque...

前端跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源。也就是说如果协议,域名,或者端口有一个不同就是跨域。 那么为什么要用跨域? 其实是因为浏览器出于安全考虑,我们都知道浏览器有同源策略。如果没有同源策略的情况下,A网站可以被任意来源的Ajax访问到内容,如果当前A网站还处于登录态,那么对方就可以通过Ajax获得A网站的任何消息。当然跨域可以用来房子CS...

DevExpress WPF让创建绑定到数据的3D图表控件变得更容易(Part 1)

下载DevExpress v20.1完整版 富文本控件难上手?这堂公开课你一定不能错过,不同视图不同应用场景全解! 通过DevExpress WPF Controls,您能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 在本教程中,您将完成可视化数据源所需的步骤。 应该执行以下步骤,本文我们将...