zoukankan      html  css  js  c++  java
  • ASP.NET保持用户状态的九种选择

    - 摘要:ASP.NET为保持用户请求之间的数据提供了多种不同的途径。你可以使用Application对象、cookie、hidden fields、Sessions或Cache对象,以及它们的大量的方法。决定什么时候使用它们有时很困难。本文将介绍了上述的技术,给出了什么时候使用它们的一些指导。尽管这些技术中有些在传统ASP中已经存在,但是有了.NET框架组件后该在什么时候使用它们发生了变化。为了在ASP.NET中保持数据,你需要调整从先前的ASP中处理状态中学习到的知识。 随着Web时代的到来,在无状态的HTTP世界中管理状态成为Web开发者的一个大问题。最近出现了几种存储和检索数据的不同技术。本文我将解释ASP.NET开发者能怎样通过页面请求维护或传递状态。 在ASP.NET中,有几种保持用户请求间数据的途径--实际上太多了,使没有经验的开发者对在哪个特定的环境下使用哪个对象很困惑。为了回答这个问题,需要考虑下面三个条件: .谁需要数据? .数据需要保持多长时间? .数据集有多大? 通过回答这些问题,你能决定哪个对象为保持ASP.NET应用程序请求间数据提供了最佳的解决方案。图1列出了不同的状态管理对象并描述了什么时候使用它们。ASP.NET中添加了四个新的对象:Cache、Context、ViewState和Web.Config文件。ASP.NET也支持传统的ASP对象,包括Application、 Cookie、有隐藏字段的 Form Post 、 QueryString和Sessions。注意这五个数据容器的正确使用方法发生了改变,因此有经验的程序员在考虑这些熟悉的对象时也许需要学习一些知识。 保持方法 谁需要数据 保持多长时间 数据量大小 Application 所有用户 整个应用程序生命期 任意大小 Cookie 一个用户 可以很短,如果用户不删除也可以很长 小的、简单数据 Form Post 一个用户 到下一次请求(可以跨越多个请求重复使用) 任意大小 QueryString 一个或一组用户 到下一次请求(可以跨越多个请求重复使用) 小的、简单数据 Sessions 一个用户 用户活动时一直保持+一段时间(一般20分钟) 可以是任何大小,但是因为用户有单独的Sessions 存储,所有它应该最小。 Cache 所有用户或某些用户 根据需要 可大可小、可简单可复杂 Context 一个用户 一个请求 可以保持大对象,但是一般不这样使用 ViewState 一个用户 一个Web窗体 最小 Config file 所有用户 知道配置文件被更新 可以保持大量数据,通常组织小的字符串和XML结构 表1. ASP.NET中的数据容器对象 Application 让我们通过回答上面的状态问题判定条件来说明该对象。谁需要数据?所有的用户需要访问它。需要保持数据多长时间?永久保持,或在应用程序生存期中保持。数据多大?可以是任何大小--在任何给定的时刻只有数据的一个副本存在。 在传统ASP中,Application对象提供了一个保存频繁使用但很少改变的数据片的位置,例如菜单内容和参考数据。尽管在ASP.NET 中Application依然作为数据容器存在,但是有其它一些更适合以前保存在传统ASP应用程序的Application集合中的数据的对象。 在传统的ASP中,如果被保存的数据在应用程序的生存期中根本不会改变(或很少改变,例如只读数据和大多数情况下是读操作的数据),Application对象是理想的选择。连接字符串就是保存在Application变量中的一个最普通的数据片,但是在ASP.NET中类似的配置数据最好保存在Web.config文件中。如果使用Application对象一个需要考虑的问题是任何写操作要么在Application_OnStart事件(global.asax)中,要么在Application.Lock部分中完成。尽管使用Application.Lock来确保写操作正确地执行是必要的,但是它串行化了对Application对象的请求,而这对于应用程序来说是个严重的性能瓶颈。图2演示了怎样使用Application对象,它包括一个Web窗体和它的代码文件。 Application.aspx
    Set Application Variable:
    Name *
    Value *
    Application.aspx.cs private void btnSubmit_Click(object sender, System.EventArgs e) { if(IsValid) { Application.Lock(); Application[txtName.Text] = txtValue.Text; Application.UnLock(); lblResult.Text = "The value of " + txtName.Text + " in the Application object is " + Application[txtName.Text].ToString() + ""; } } 代码段1.在ASP.NET中访问Application对象 它的输出如下图所示: 图1. Application对象的内容 注意图3中Application对象的内容是追踪输出的显示。追踪是个伟大的调试工具,但是在某个点,被打开的有追踪的页面可能出现在产品环境中。如果出现这种情况,你肯定不希望显示敏感的信息。这就是为什么Application对象从来不是推荐的存放敏感信息(例如连接字符串)的位置的主要原因之一。 Cookies 当特定的用户需要特定的数据片,并且需要把数据在某个可变的时段中保持的时候,cookie就非常方便。它的生命周期可能与浏览器窗体的一样短,也可以长达数月、数年。cookie可以小到只有几个字节的数据,因为它们在每个浏览器请求中传递,它们的内容需要尽可能的小。 Cookie提供了一条灵活的、强大的维护用户请求间数据的途径,这就是为什么Internet上大多数动态站点使用它们的原因。因为cookie可以存储的数据量很受限制,最好只在cookie中保存键字段,其它的数据保存在数据库或其它的服务器端数据容器中。但是由于不是所有的浏览器都支持cookie,并且它可以被用户禁止或删除,因此它们也不能用于保存关键数据。你应该很好地处理用户的cookie被删除的情况。最后,cookie作为简单的明文文本保存在用户的计算机中,因此在它里面不能保存敏感的、未加密的数据。 图2.单值和多值cookie 有种特殊的cookie可以保存单个值或名称/值对的集合。
    微博:软件开发大师 微博:北京淘宝联盟
    广告位招商网站 跑运输网站
    图4显示了单个和多个值cookie的示例,通过ASP.NET的内建追踪特性输出。这些值可以在ASP.NET页面中使用Request.Cookies和Response.Cookies集合来维护,这在代码段2中演示。 Cookies.aspx.cs //使用HttpCookie类是指cookie的值和/或子值 HttpCookie cookie; if(Request.Cookies[txtName.Text] == null) cookie = new HttpCookie(txtName.Text, txtValue.Text); else cookie = Request.Cookies[txtName.Text]; if(txtSubValueName.Text.Length > 0) cookie.Values.Add(txtSubValueName.Text, txtSubValueValue.Text); cookie.Expires = System.DateTime.Now.AddDays(1); // tomorrow Response.AppendCookie(cookie); //检索cookie的值 if(!Request.Cookies[txtName.Text].HasKeys) lblResult.Text = "The value of the " + txtName.Text + " cookie is " + Request.Cookies[txtName.Text].Value.ToString() + ""; else { lblResult.Text = "The value of the " + txtName.Text + " cookie is " + Request.Cookies[txtName.Text].Value.ToString() + ", with subvalues:
    "; foreach(string key in Request.Cookies[txtName.Text].Values.Keys) { lblResult.Text += "[" + key + " = " + Request.Cookies[txtName.Text].Values[key].ToString() + "]
    "; } } 删除Cookie // 把的值设置为空并把终止时间设置为过去某个时刻 Response.Cookies[txtName.Text].Value = null; Response.Cookies[txtName.Text].Expires = System.DateTime.Now.AddMonths(-1); //上个月 代码段2.Accessing 在ASP.NET中访问Cookies Form Post / 隐藏的窗体字段 特定的用户需要窗体的数据,并且它需要在单个请求到应用程序终止的任何阶段都保持。这些数据事实上可以是任意大小的,它随着每个form post在网络上向前和向后发送。 在传统的ASP中,这是在应用程序中暴露状态的通常的途径,特别是在多页面窗体应用程序中。但是在ASP.NET中这种技术不太适合了,因为只要你使用postback模型(也就是页面发回给自己),Web控件和ViewState自动处理了这些操作。ViewState是ASP.NET对这种技术的实现,我将在本文的后部分讨论它。访问通过POST发送的窗体值是使用HttpRequest对象的窗体集合完成的。在图6中,一个ASP.NET页面设置了某个用户的ID,在这以后它保持在一个隐藏的窗体字段中。后面的向任何页面的请求保留这个值,直到页面使用Submit按钮链接到其它的用户。 Form1.aspx

    Form 1

    Your username:

    Set Hidden Form Username Variable:
    Username *
    <="<" /> Form1.aspx.cs private void Page_Load(object sender, System.EventArgs e) { if(!IsPostBack) // 新的请求或者来自form2.aspx的请求 { // 检查窗体集合 if(Request.Form["username"] == null) pnlSetValue.Visible = true; else { //需要设置用户名值 pnlSetValue.Visible = false; username = Request.Form["username"].ToString(); lblUsername.Text = username; //数据绑定到隐藏的窗体字段值 this.DataBind(); } } } private void btnSubmit_Click(object sender, System.EventArgs e) { if(IsValid) { //隐藏窗体来设置值 pnlSetValue.Visible = false; username = txtName.Text; lblResult.Text = "Username set to " + txtName.Text + "."; lblUsername.Text = username; this.DataBind(); } } Form2.aspx

    Form 2

    Your username:

    <="<" /> Form2.aspx.cs private void Page_Load(object sender, System.EventArgs e) { if(Request.Form["username"] != null) { username = Request.Form["username"].ToString(); lblUsername.Text = username; this.DataBind(); } } 代码段3.在ASP.NET中使用隐藏窗体字段 在ASP.NET中一个页面上只能存在一个服务器端窗体,并且该窗体必须提交返回到自身(仍然可以使用客户端窗体,没有限制)。隐藏窗体字段再也没有用于在.NET框架组件上建立的应用程序间传递数据的主要原因之一是.NET框架组件控件都可以使用ViewState自动维护自己的状态。ViewState简单地把使用隐藏窗体字段设置和检索值所包含的工作封装进一个使用简单的集合对象中。 QueryString QueryString对象中保存的数据由单独的用户使用。它的生命周期可能只有一个请求那么短,也可能有用户使用应用程序的时间那么长(如果构造正确的话)。这类数据一般小于1KB。QueryString中的数据在URL中传递,对于用户来说是可见的,因此你能猜到,使用这种技术时,敏感的数据或可用于控制应用程序的数据需要加密。 也就是说,QueryString是在ASP.NET Web窗体间发送信息的一条很好的途径。例如,如果有一个含有产品列表的数据表格(DataGrid),并且在表格上有一个链接导向产品的细节页面,使用QueryString就是理想的,可以把产品的ID包含在链接到产品细节页面的QueryString中(例如productdetails.aspx?id=4)。使用QueryStrings的另一个好处是页面的状态包含在URL中。这意味着用户可以把某个通过QueryStrings建立的窗体放入他的收藏夹中。当它们作为收藏返回到页面时,将与作收藏的时候一样。很明显这只在页面不依赖QueryString外的所有状态和不作任何改变的时候有作用。 敏感数据,以及任何不希望用户操作的变量应该避免出现在此处(除非加密使用户不能阅读)。并且URL中不合法的字符必须使用Server.UrlEncode编码,如图7所示。当处理单个ASP.NET页面时,对维护状态来说ViewState是比QueryString好的选择。对于长期的数据存储,Cookie、Sessions或Cache都比QueryStrings更加适于作为数据容器。 Querystring.aspx
    Set Querystring Variable:
    Name *
    Value *
    Set querystring x equal to 1 Querystring.aspx.cs private void Page_Load(object sender, System.EventArgs e) { // 检索cookie的值 if(Request.QueryString.HasKeys()) { lblResult.Text = "The values of the " + txtName.Text + " querystring parameter are:
    "; foreach(string key in Request.QueryString.Keys) { lblResult.Text += "[" + key + " = " + Request.QueryString[key].ToString() + "]
    "; } } } private void btnSubmit_Click(object sender, System.EventArgs e) { if(IsValid) { string url = "querystring.aspx?"; foreach(string key in Request.QueryString.Keys) { url += key + "=" + Request.QueryString[key].ToString() + "&"; } Response.Redirect(url + txtName.Text + "=" + Server.UrlEncode(txtValue.Text)); } } 代码段4.在ASP.NET中使用QueryStrings传递数据 Sessions Sessions数据对于特定的用户是特定的。它的生存期是用户持续请求的时间加上后来一段时间(一般是20分钟)。Sessions可以保持或大或小的数据量,但是如果应用程序用于成百上千的用户,那么总共的存储应该保持最小。 不幸的是在传统的ASP中Sessions对象的名声很不好,因为它把应用程序约束到特定的计算机上,阻碍了用户分组和Web范围的可伸缩性。在ASP.NET中几乎没有这些问题,因为改变Sessions保存的位置很简单。在默认情况下(性能最好的情况),Sessions数据仍然保存在本地Web服务器的内存中,但是ASP.NET支持使用外部状态服务器或数据库管理Sessions数据。 使用Sessions对象很简单,并且它的语法与传统ASP相同。但是Sessions对象是保存用户数据的方法中效率很低的一种,因为即使用户停止使用应用程序后它仍然保持在内存中一段时间。这对于非常繁忙的站点的可伸缩性有严重的影响。其它的选择允许对释放内存的更多的控制,例如Cache对象也许更适合大量的大数据值。并且在默认情况下ASP.NET Sessionss依赖于cookie,因此如果用户禁止或不支持cookie,Sessionss就不能工作,但是可以配置Sessionss支持cookie无关。对于小的数据量,Sessionss对象是保存只需要在用户当前对话中保持的特定数据的极好位置。下面的例子演示了怎样设置和从Sessionss对象中检索值: private void btnSubmit_Click(object sender, System.EventArgs e) { if(IsValid) { // 设置Sessions值 Sessions[txtName.Text] = txtValue.Text; //读取和显示刚才的设置 lblResult.Text = "The value of " + txtName.Text + " in the Sessions object is " + Sessions[txtName.Text].ToString() + ""; } } 该Web窗体与Application对象中使用的几乎相同,当允许页面追踪时Sessions集合的内容也是可见的。你需要记住的是即使没有使用,Sessionss也会有应用程序开销。把Sessionss状态设置为只读的也可以优化只需要读而不需要写数据的页面。可以使用下面两种途径之一来配置Sessionss: <%@ Page EnableSessionsstate="false" %><%@ Page EnableSessionsstate="readonly" %> ASP.NET Sessionss可以在Web.config或Machine.config中的Sessionsstate元素中配置。下面是在 Web.config中的设置的例子:

    -

    ASP.NET中的新状态容器

    前面我们提到,ASP.NET为保存用户请求间的数据添加了几种新的途径。这些途径给了你如何保持状态信息更好的控制。这些技术的范围可以窄到只有一个请求那么小(Context对象),也可以宽到整个Web服务器和服务器上的所有应用程序(Machine.config文件)。在多数情况下你有多种保存特定数据片的选择--使用每个方法描述的问题和答案来决定某个对象是否适合你的需要。

    Cache

    Cache对象用于单个用户、一组用户或所有的用户。这种数据为多个请求保持。它可以保持很长时间,但是不能超过应用程序重新启动的时间,并且数据的终止基于时间或者其它的依赖关系。它可以高效率地保持大量或少量地数据。

    Cache 是ASP.NET中最"酷"的对象之一。它提供了难以置信的灵活性、通用性和性能,因此在ASP.NET应用程序中它通常是比Application或Sessions更好的保持数据的对象。本文没有详细介绍Cache对象的使用方法,但是仍然可以说它是一个万能对象。与其它的集合对象相似,它是一个简单的名称-值集合,但是通过使用指定特定用户的键值可以缓存特定用户的值。同样你可以缓存不同的相关数据的多个数据集,例如几个有键(如fordcars 、 chevycars、gmcars)的汽车集合。Cache中的数据可以给定一个绝对的、可变的或基于文件的终止时间。它们也实现了一个回调功能,在被缓存的值从缓存中提取时被调用,这个功能很有用,因为接着你能检查它是否为最新的数据变量,如果不是(或数据源不可用),就重新缓存被终止的值。

    添加和访问缓存中值的语法与先前谈到的相似。但是Cache给访问集合内容的标准索引器方法作了补充,它支持多种方法,允许对被缓存数据的更多的控制。最频繁使用的方法是Insert,它支持几种重载,允许你指定依赖、超时值、优先级和回调。下面是一些简单的例子:

    // 给缓存添加项
    Cache["myKey"] = myValue;

    // 从缓存中读取项
    Response.Write(Cache["myKey"]);

    // 把CacheDuration增加10秒并把项添加到缓存中
    Cache.Insert("myKey",myValue, null, System.DateTime.Now.AddSeconds(10),
    System.Web.Caching.Cache.NoSlidingExpiration);

    Cache对象的最强大的特性之一是当缓存中的某个项终止时执行回调的能力。它使用了委托或函数指针,这在本文中没有讨论。幸运的是一旦你有了某些这些技术怎样工作的示例,就能通过简单的剪切和粘贴在应用程序中使用它们,不需要知道委托是怎样工作的复杂过程。有很多使用这种功能的原因,最通常的是在数据终止时用当前数据重新填充缓存,或者如果重新填充缓存的数据源不可用时恢复旧的缓存数据。

    在我的例子中,简单地缓存了当前时间,当缓存超期的时候,我将给缓存中的字符串末尾添加一个星号(*)。在超过时间后,你能通过计算星号的数量来确定缓存超期了多少次。图9演示了回调的重要概念,并且提供了给使用缓存建立更多功能回调程序的好模板。

    private void Page_Load(object sender, System.EventArgs e)
    {
    string cacheKey = "myKey";
    string data = "";
    // 检查数据是否已经被缓存了
    if(Cache[cacheKey]==null)
    {
    // 因为数据在缓存中,所有读取数据
    data = System.DateTime.Now.ToString();

    //建立回调委托的一个实例
    CacheItemRemovedCallback callBack =new CacheItemRemovedCallback(onRemove);

    Label1.Text = "Generated: " + data;

    Cache.Insert(cacheKey,data,null,
    System.DateTime.Now.AddSeconds(5),
    System.Web.Caching.Cache.NoSlidingExpiration,
    System.Web.Caching.CacheItemPriority.Default,
    callBack);
    }
    else
    {
    Label1.Text = "Cached: " + Cache[cacheKey].ToString();
    }
    }

    private void onRemove(string key, object val,CacheItemRemovedReason reason)
    {
    //建立回调委托的一个实例
    CacheItemRemovedCallback callBack =new CacheItemRemovedCallback(onRemove);
    Cache.Insert(key,val.ToString() +
    "*",null,System.DateTime.Now.AddSeconds(5),Cache.NoSlidingExpiration,
    System.Web.Caching.CacheItemPriority.Default, callBack);
    }

    代码段5.缓存回调示例

    注意代码段中一个重要的特性是在Page_Load中使用模式(pattern)来确定是否使用缓存中的数据。当你处理缓存中的项时也可能使用这种模式。使用if语句来检查缓存的当前内容是否为空(因为要多次引用,为缓存键使用了一个变量)。如果是空的,从数据源生成数据并放入缓存中。如果不是空的,从缓存中返回数据。如果数据访问逻辑很复杂,你需要把整个if语句放入一个独立的函数,该函数的任务是检索数据。
    Cache对象的功能比先前我们讨论的大多数对象多得多。这也是ASP.NET更强大的功能之一,并且我明确地推荐阅读关于它的更多内容。

    Context

    Context对象保持单个用户、单个请求的数据,并且数据只在该请求期间保持。Context容器可以保持大量的数据,但是典型的情况下是保存小的数据片,因为它经常通过global.asax中的某个处理方法为每个请求实现。

    Context容器(从Page对象访问或使用System.Web.HttpContext.Current)被提供用于保持需要在不同的HttpModules和HttpHandlers之间传递的值。它也可以用于保持某个完整请求的相应信息。例如,IbuySpy入口在global.asax中的Application_BeginRequest事件过程中给容器填满了许多配置信息。注意这只在当前请求中可用,如果你希望在下一个请求中也能使用,请考虑使用ViewState。

    从Context集合中设置和获取数据使用的语法与前面讨论的其它集合对象(如Application、Sessions和 Cache)的相似。下面是两个简单的例子:

    // 给Context添加项
    Context.Items["myKey"] = myValue;

    // 从Context中读取项
    Response.Write(Context["myKey"]);

    ViewState

    ViewState为单个用户保持状态信息,保持期为ASPX页面工作时间。ViewState容器可以保持大量的数据,但是必须小心管理ViewState的大小,因为它增加了每个请求和回应的下载(download)大小。

    ViewState是ASP.NET中的一个新容器,也许你已经使用它了,但是你可能还是不了解它。这是因为所有的内建Web控件都使用ViewState在页面回发(postback)间保持自己的值。但是你必须小心,因为它影响应用程序的性能。影响的大小依赖于回发之间使用ViewState的多少--对大多数Web窗体来说数量非常小。

    确定某个页面上每个控件使用的ViewState的数量最简单的方法是打开页面追踪并检查每个控件负载了多少个ViewState。如果某个特定控件不需要在回发之间保持数据,请通过把EnableViewState设置为false关闭该对象的ViewState。你也可以通过在浏览器中查看的HTML源并检查隐藏窗体字段__VIEWSTATE来确定某个给定的ASP.NET页面ViewState的总共大小。注意这些内容都是使用Base64编码的,用于放置偶然的查看和维护。ViewState也可以通过给@Page指令添加EnableViewState="false"在整个页面中禁止。

    典型的Web窗体不需要直接维护ViewState。但是如果你建立自定义Web控件,就需要了解它是怎样工作的,并为你的控件实现它,这样该控件的工作方式才能与随ASP.NET发布的Web控件同样地工作。向ViewState读取或写入值都可以通过上面讨论地其它集合对象的语法完成:

    // 给ViewState添加项
    ViewState["myKey"] = myValue;

    //从Context读取项
    Response.Write(ViewState["myKey"]);

    当建立自定义Web控件时,你也许希望它们有ViewState的好处。这在控件的属性层可以简单实现。代码段6演示了怎样保存一个简单的自定义控件的PersonName属性到ViewState中,并在该控件的Render方法中使用它。

    namespace MSDN.StateManagement
    {
    public class HelloPerson : System.Web.UI.Control
    {
    public string PersonName
    {
    get
    {
    string s = (string)ViewState["PersonName"];
    return ((s == null) ? "" : s);
    }
    set
    {
    ViewState["PersonName"] = value;
    }
    }
    protected override void Render(System.Web.UI.HtmlTextWriter writer)
    {
    writer.Write("Hello " + PersonName);
    }
    }
    }

    代码段6.在ViewState中保存数据

    Web.config和Machine.config文件

    这些文件中的数据对于某个应用程序的所有用户来说都可以使用。Web.config文件中存储的数据可用于应用程序的整个生命周期。这些数据一般很小,该对象一般用于保持文件位置和数据库连接的字符串。大的数据片最好保存在其它位置。

    作为其它多样集合对象的补充,ASP.NET引入了一组XML配置文件用于管理应用程序甚至于整个服务器的很多设置。每个ASP.NET应用程序使用Web.config文件来设置它的许多属性,每个服务器在系统文件夹下有一个作为应用程序基础的Machine.config文件。这些设置都作为默认值使用,除非重载。作为保存配置数据的补充,这些文件可以保存应用程序(或多个应用程序)需要的数据。

    无论什么时候应用程序启动都会读取配置信息,接着这些信息被缓冲。由于被缓冲了,应用程序可以快速读取它们,因此不需要考虑应用程序的瓶颈,因为它经常执行某个文本文件的一些整型信息。此外,某个应用程序的Web.config的改变将导致应用程序重新启动。这确保了对配置文件信息的修改立即反映到应用程序中。

    数据库连接信息,默认图像路径和XML数据文件路径是通常保存在Web.config文件中的数据片。在Web.config文件中保存数据的语法如下,在理想的情况下你也许希望使用集成的SQL身分验证:

    <configuration>
    <!-应用程序特殊设置 -->
    <appSettings>
    <add key="connectionString" value="server=myDBServer;
    uid=myUID;pwd=myPassword;database=myDB" />
    </appSettings>
    <system.web>
    <!-所有的wsb设置 -->
    </system.web>
    </configuration>

    为了访问ASP.NET页面中的值,可以使用ConfigurationSettings集合,它在System.Configuration名字空间中。下面的简单例子演示了怎样提取前面的连接字符串到一个本地变量中:

    using System.Configuration;
    ooo
    String strConnString =
    ConfigurationSettings.AppSettings["connectionString"];

    给System.Configuration名字空间添加一个引用减少了引用这些值的代码数量。因为对Web.config或 Machine.config的修改将导致应用程序立即重新启动,典型情况下这些值只由服务器系统管理员手动修改。因此你可以认为这些文件是保存只读数据而不是应用程序中修改的数据的好位置。

    结论

    有效的状态管理意味着识别的用户经验、数据错误与快速的页面或事务处理之间的巨大差别。尽管状态管理在ASP 3.0中不太适用,但是ASP.NET把它带到了本文讨论的状态对象的控制之下。小心地使用它们将使你给用户展示最佳的Web经验。

  • 相关阅读:
    PHP 8.0 带来的新特性
    do sth 之 提取了一份文档
    Java入门15---网络编程
    Java入门14---logback
    限流策略
    JConsole 可视化工具
    SpringBoot注解---6.声明式事务
    SpringBoot注解---5.AOP
    SpringBoot注解---4.扩展原理
    SpringBoot注解---2.组件赋值
  • 原文地址:https://www.cnblogs.com/suizhikuo/p/2443061.html
Copyright © 2011-2022 走看看