ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁缘由及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头...

ASP.NET MVC Filters 4种默认过滤器的使用【附示例】

 

 过滤器(Filters)的出现使得咱们能够在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每一个请求都会响应内容,只响应特定内容给那些有特定权限的用户,过滤器理论上有如下功能:html

  1. 判断登陆与否或用户权限
  2. 决策输出缓存
  3. 防盗链
  4. 防蜘蛛
  5. 本地化与国际化设置
  6. 实现动态Action(作权限管理系统的好东西)

先来看一个简单的例子:新建一个AuthFiltersController,里面有两个Actionjava

复制代码
public ActionResult Index()
{
    return View();
}

[Authorize]
public ActionResult Welcome()
{
    return View();
}
复制代码

很显然,第一个名为Index的Action是没有过滤的,任何身份的请求均可以经过,只要在浏览器的URL栏里键入:localhost:****/AuthFilters/Index 就能获得对应的视图响应;
而第二个名为Welcome的Action上面标注了[Authorize],表示这是一个只处理那些经过身份验证的URL请求,若是没有经过身份验证就请求这个Action会被带到登陆页面。看看配置文件:node

<authentication mode="Forms">
  <forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>

根据配置文件的定义,登陆页面就是AccountController下名为LogOn的Action,那么就新建一个AccountController,并新建两个Action:web

复制代码
public ActionResult LogOn()
{
    return View();
}

[HttpPost]
public ActionResult LogOn(LogOnViewModel model)
{
    if (model.UserName.Trim() == model.Password.Trim()) //伪代码,只要输入的用户名和密码同样就过
    {
        if (model.RememberMe)
            FormsAuthentication.SetAuthCookie(model.UserName, true);   //2880分钟有效期的cookie
        else
            FormsAuthentication.SetAuthCookie(model.UserName, false);  //会话cookie
        return RedirectToAction("Welcome", "AuthFilters");
    }
    else
        return View(model);
}
复制代码

第一个是处理Get请求用于响应视图页面的,第二个是处理用户点击提交回发的登陆表单。算法

LogOnViewModel是用户登陆实体类,看具体定义:sql

复制代码
/// <summary>
/// 用户登陆类
/// </summary>
public class LogOnViewModel
{
    /// <summary>
    /// 用户名
    /// </summary>
    public string UserName { get; set; }

    /// <summary>
    /// 密码
    /// </summary>
    public string Password { get; set; }

    /// <summary>
    /// 记住我
    /// </summary>
    public bool RememberMe { get; set; }

}
复制代码

ok,按F6编译下项目,再按Ctrl + F5运行下项目,在URL里输入:localhost:****/AuthFilters/Index 很轻松的获得了Index这个Action的响应数据库

再定位到:localhost:****/AuthFilters/Welcome编程

可见,虽然定位到了Welcome这个Action,可是却并不像Index同样直接返回对应的视图,而是被带到了登陆页面。就是由于Welcome这个Action上被标注了[Authorize],拒绝了因此未验证用户的访问。设计模式

既然拒绝了未验证的用户,那就登陆下经过验证,看看上面LogOn里写的伪代码就知道,输入相同的用户名和密码就能登陆成功,用户名和密码都输入“wangjie”试试:数组

已经经过验证并获得Welcome这个Action的响应了。相比以前就是多生成了一个名为“.ASPXAUTH”的Cookie,这是个默认名,配置文件里能够修改。同时,若是登陆的时候勾选了“记住我”那么此Cookie的过时时间就是配置文件里定义的2880分钟。

ok,如今提升下难度,只设置名为“a”、“bb”、“ccc”的用户能够访问欢迎页面:

[Authorize(Users = "a,bb,ccc")]
public ActionResult Welcome()
{
    ViewBag.Message = "已登陆";
    return View();
}

再用“wangjie”登陆下发现跳不到欢迎页面了,由于指定了a、bb、ccc这三个用户才能够登陆。
先无论为什么在Action上标注Users = "a,bb,ccc"就能够控制能够访问的用户,但从操做性上来讲这样控制Action的访问权限仍是很方便的。可是若是项目大,用户对应的角色和权限变化比较大,每次变化都来从新标注Action显然不合适。MVC框架提供的过滤器(Filters)就派上了用场:

上图是Asp.Net MVC框架提供的几种默认Filter:受权筛选器、操做筛选器、结果筛选器、异常筛选器,下面来一一讲解,先看演示Demo结构图:

1、受权筛选器

受权筛选器用于实现IAuthorizationFilter接口和作出关因而否执行操做方法(如执行身份验证或验证请求的属性)的安全决策。 AuthorizeAttribute类和RequireHttpsAttribute类是受权筛选器的示例。受权筛选器在任何其余筛选器以前运行。
新建一个继承AuthorizeAttribute类的UserAuthorize类,F12定位到AuthorizeAttribute类,看看内部申明:

复制代码
public AuthorizeAttribute();

public string Roles { get; set; }
public override object TypeId { get; }
public string Users { get; set; }

protected virtual bool AuthorizeCore(HttpContextBase httpContext);
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext);
public virtual void OnAuthorization(AuthorizationContext filterContext);
protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext);
复制代码

上面演示的指定用户才能够访问就是利用了Users属性,并由基类帮助咱们验证,只放指定的Users用户经过。要实现自定义的验证只需重写下OnAuthorization和AuthorizeCore方法。为了演示效果,新建一个SampleData类用来初始化数据:

复制代码
/// <summary>
/// 测试数据(实际项目中,这些数据应该从数据库拿)
/// </summary>
public class SampleData
{
    public static List<User> users;
    public static List<Role> roles;
    public static List<RoleWithControllerAction> roleWithControllerAndAction;

    static SampleData()
    {
        // 初始化用户
        users = new List<User>(){
            new User(){ Id=1, UserName="wangjie", RoleId=1},
            new User(){ Id=2, UserName ="senior1", RoleId=2},
            new User(){ Id=3, UserName ="senior2", RoleId=2},
            new User(){ Id=5, UserName="junior1", RoleId=3},
            new User(){ Id=6, UserName="junior2", RoleId=3},
            new User(){ Id=6, UserName="junior3", RoleId=3}
        };
        // 初始化角色
        roles = new List<Role>()
        {
            new Role() { Id=1, RoleName="管理员", Description="管理员角色"},
            new Role() { Id=2, RoleName="高级会员", Description="高级会员角色"},
            new Role() { Id=3, RoleName="初级会员", Description="初级会员角色"}
        };
        // 初始化角色控制器和Action对应类
        roleWithControllerAndAction = new List<RoleWithControllerAction>()
        {
            new RoleWithControllerAction(){ Id=1, ControllerName="AuthFilters", ActionName="AdminUser", RoleIds="1"},
            new RoleWithControllerAction(){ Id=2, ControllerName="AuthFilters", ActionName="SeniorUser", 
Ids="1,2"},
            new RoleWithControllerAction(){ Id=3, ControllerName="AuthFilters", ActionName="JuniorUser", 
Ids="1,2,3"},
            new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"}
        };
    }
}
复制代码

简单明了,用户拥有角色,不一样角色能够访问的Action也不一样。这比较符合权限项目里的控制。再看看UserAuthorize类的具体定义:

复制代码
/// <summary>
/// 自定义用户受权
/// </summary>
public class UserAuthorize : AuthorizeAttribute
{
    /// <summary>
    /// 受权失败时呈现的视图
    /// </summary>
    public string AuthorizationFailView { get; set; }

    /// <summary>
    /// 请求受权时执行
    /// </summary>
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        //得到url请求里的controller和action:
        string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower();
        string actionName = filterContext.RouteData.Values["action"].ToString().ToLower();
        //string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
        //string actionName = filterContext.ActionDescriptor.ActionName;

        //根据请求过来的controller和action去查询能够被哪些角色操做:
        Models.RoleWithControllerAction roleWithControllerAction = 
base.SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && 
tionName.ToLower() == actionName);
        if (roleWithControllerAction != null)
        {
            this.Roles = roleWithControllerAction.RoleIds;     //有权限操做当前控制器和Action的角色id
        }
        base.OnAuthorization(filterContext);   //进入AuthorizeCore
    }

    /// <summary>
    /// 自定义受权检查(返回False则受权失败)
    /// </summary>
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (httpContext.User.Identity.IsAuthenticated)
        {
            string userName = httpContext.User.Identity.Name;    //当前登陆用户的用户名
            Models.User user = Database.SampleData.users.Find(u => u.UserName == userName);   //当前登陆用户对象

            if (user != null)
            {
                Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId);  //当前登陆用户的角色
                foreach (string roleid in Roles.Split(','))
                {
                    if (role.Id.ToString() == roleid)
                        return true;
                }
                return false;
            }
            else
                return false;
        }
        else
            return false;     //进入HandleUnauthorizedRequest 
    }

    /// <summary>
    /// 处理受权失败的HTTP请求
    /// </summary>
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        filterContext.Result = new ViewResult { ViewName = AuthorizationFailView };
    }
}
复制代码

自定义好受权类就能够到控制器上使用了,看看AuthFiltersController类:

复制代码
public class AuthFiltersController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    //[Authorize(Users = "a,bb,ccc")]
    [Authorize]
    public ActionResult Welcome()
    {
        ViewBag.Message = "普通已受权页面";
        return View();
    }

    [UserAuthorize(AuthorizationFailView = "Error")]   //管理员页面
    public ActionResult AdminUser()
    {
        ViewBag.Message = "管理员页面";
        return View("Welcome");
    }

    [UserAuthorize(AuthorizationFailView = "Error")]   //会员页面(管理员、会员均可访问)
    public ActionResult SeniorUser()
    {
        ViewBag.Message = "高级会员页面";
        return View("Welcome");
    }

    [UserAuthorize(AuthorizationFailView = "Error")]   //游客页面(管理员、会员、游客均可访问)
    public ActionResult JuniorUser()
    {
        ViewBag.Message = "初级会员页面";
        return View("Welcome");
    }
}
复制代码

Welcome这个Action使用了默认的受权验证,只要登录成功就能够访问。其余几个Action上都标注了自定义的UserAuthorize,并无标注Users="....",Roles=".....",由于这样在Action上写死用户或者角色控制权限显然是不可行的,用户和角色的对应以及不一样的角色能够操做的Action应该是从数据库里取出来的。为了演示就在SampleData类里初始化了一些用户和角色信息,根据SampleData类的定义,很明显wangjie拥有1号管理员角色,能够访问AuthFilters这个控制器下的全部Action,senior一、senior2拥有2号高级会员的角色,能够访问AuthFilters这个控制器下除了AdminUser以外的Action等等
再次登录下,就发现拥有高级会员角色的用户senior1是不能够访问AdminUser这个Action,会被带到AuthorizationFailView属性指定的Error视图:

2、操做筛选器、结果筛选器

操做筛选器用于实现IActionFilter接口以及包装操做方法执行。IActionFilter接口声明两个方法:OnActionExecuting和OnActionExecuted。OnActionExecuting在操做方法以前运行。OnActionExecuted在操做方法以后运行,能够执行其余处理,如向操做方法提供额外数据、检查返回值或取消执行操做方法。

结果筛选器用于实现IResultFilter接口以及包装ActionResult对象的执行。IResultFilter接口声明两个方法OnResultExecuting和OnResultExecuted。OnResultExecuting在执行ActionResult对象以前运行。OnResultExecuted在结果以后运行,能够对结果执行其余处理,如修改 HTTP 响应。OutputCacheAttribute 类是结果筛选器的一个示例。

操做筛选器和结果筛选器都实现ActionFilterAttribute类,看看类里定义的方法:

public virtual void OnActionExecuted(ActionExecutedContext filterContext);
public virtual void OnActionExecuting(ActionExecutingContext filterContext);
public virtual void OnResultExecuted(ResultExecutedContext filterContext);
public virtual void OnResultExecuting(ResultExecutingContext filterContext);

根据方法的名字就知道4个方法执行的顺序了:
OnActionExecuting是Action执行前的操做、OnActionExecuted则是Action执行后的操做、OnResultExecuting是解析ActionResult前执行、OnResultExecuted是解析ActionResult后执行
即:Action执行前:OnActionExecuting方法先执行→Action执行 →OnActionExecuted方法执行→OnResultExecuting方法执行→返回的ActionRsult中的 executeResult方法执行→OnResultExecuted执行

彻底能够重写OnActionExecuting方法实现上面受权筛选器同样的功能,由于OnActionExecuting方法是在Action方法执行前运行的,自定义一个实现ActionFilterAttribute类的ActionFilters类,OnActionExecuting方法这么写:

复制代码
/// <summary>
/// 在执行操做方法以前由 MVC 框架调用
/// </summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    string userName = filterContext.HttpContext.User.Identity.Name;    //当前登陆用户的用户名
    Models.User user = Database.SampleData.users.Find(u => u.UserName == userName);   //当前登陆用户对象

    if (user != null)
    {
        Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId);  //当前登陆用户的角色

        //得到controller:
        string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower();
        //string actionName = filterContext.RouteData.Values["action"].ToString().ToLower();
        if (ActionName == null)
            ActionName = filterContext.RouteData.Values["action"].ToString();

        //查询角色id
        Models.RoleWithControllerAction roleWithControllerAction = 
.SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && 
Name.ToLower() == ActionName.ToLower());
        if (roleWithControllerAction != null)
        {
            this.Roles = roleWithControllerAction.RoleIds;     //有权限操做当前控制器和Action的角色id
        }
        if (!string.IsNullOrEmpty(Roles))
        {
            foreach (string roleid in Roles.Split(','))
            {
                if (role.Id.ToString() == roleid)
                    return;   //return就说明有权限了,后面的代码就不跑了,直接返回视图给浏览器就好
            }
        }
        filterContext.Result = new EmptyResult();   //请求失败输出空结果
        HttpContext.Current.Response.Write("对不起,你没有权限!");   //打出提示文字
        //return;
    }
    else
    {
        //filterContext.Result = new ViewResult { ViewName = "Error" };
        filterContext.Result = new EmptyResult();
        HttpContext.Current.Response.Write("对不起,请先登陆!");
        //return;
    }
    //base.OnActionExecuting(filterContext);
}
复制代码

看看如何在ActionFiltersController控制器里使:

复制代码
public class ActionFiltersController : Controller
{
    [ActionFilters]
    public ActionResult Index()
    {
        return View();
    }

    [ActionFilters(ActionName = "Index")]
    public ActionResult Details()
    {
        return View();
    }

    [ActionFilters]
    public ActionResult Test()
    {
        return View();
    }
}
复制代码

很明显Index和Details这两个Action同用一个权限,看看初始化数据SampleData类的定义:

new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"}

只有2和3号角色能够访问,那么1号角色的wangjie用户应该是访问不了的,登陆试试:

3、异常筛选器

异常筛选器用于实现IExceptionFilter接口,并在ASP.NET MVC管道执行期间引起了未处理的异常时执行。异常筛选器可用于执行诸如日志记录或显示错误页之类的任务。HandleErrorAttribute类是异常筛选器的一个示例。

有以前受权筛选器、操做和结果筛选器的使用经验,再看异常筛选器就简单许多了,来看看自定义的继承自HandleErrorAttribute类的异常筛选类ExceptionFilters:

复制代码
/// <summary>
/// 异常筛选器
/// </summary>
public class ExceptionFilters : HandleErrorAttribute
{
    /// <summary>
    /// 在发生异常时调用
    /// </summary>
    public override void OnException(ExceptionContext filterContext)
    {
        //if (!filterContext.ExceptionHandled && filterContext.Exception is NullReferenceException)
        if (!filterContext.ExceptionHandled)
        {
            //获取出现异常的controller名和action名,用于记录
            string controllerName = (string)filterContext.RouteData.Values["controller"];
            string actionName = (string)filterContext.RouteData.Values["action"];
            //定义一个HandErrorInfo,用于Error视图展现异常信息
            HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);

            ViewResult result = new ViewResult
            {
                ViewName = this.View,
                ViewData = new ViewDataDictionary<HandleErrorInfo>(model)  //定义ViewData,泛型
            };
            filterContext.Result = result;
            filterContext.ExceptionHandled = true;
        }
        //base.OnException(filterContext);
    }
}
复制代码

看看如何在视图中使用:

[ExceptionFilters(View = "Exception")]
public ActionResult Index()
{
    throw new NullReferenceException("测试抛出异常!");
}

View是制定的捕获异常后显示给用户的视图:

再看一个Action:

[ExceptionFilters(View = "ExceptionDetails")]
public ActionResult Details()
{
    int i = int.Parse("hello,world!");
    return View();
}

把string类型的数据强转int,确定得报FormatException异常,看看ExceptionDetails视图如何定义的:

复制代码
@model System.Web.Mvc.HandleErrorInfo
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>异常</title>
</head>
<body>
    <p>
        抛错控制器:<b>@Model.ControllerName</b> 抛错方法:<b>@Model.ActionName</b> 抛错类型:<b>@Model.Exception.GetType

().Name</b>
    </p>
    <p>
        异常信息:<b>@Model.Exception.Message</b>
    </p>
    <p>
        堆栈信息:</p>
    <pre>@Model.Exception.StackTrace</pre>
</body>
</html>
复制代码

浏览器显示结果:

 

转载自:http://www.360doc.com/showweb/0/0/765540458.aspx

 

 

数据库常见死锁缘由及处理

 

数据库是一个多用户使用的共享资源,当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的状况。若对并发操做不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并发控制的一个很是重要的技术。在实际应用中常常会遇到的与锁相关的异常状况,当两个事务须要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严重影响应用的正常执行。 
  在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其余的事务不能对它读取和修改。加了共享锁的数据对象能够被其余事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。 
  下面总结下这两种锁形成的常见的死锁状况与解决方案:

一. 事务之间对资源访问顺序的交替

  1. 出现缘由: 
    一个用户A 访问表A(锁住了表A),而后又访问表B;另外一个用户B 访问表B(锁住了表B),而后企图访问表A;这时用户A因为用户B已经锁住表B,它必须等待用户B释放表B才能继续,一样用户B要等用户A释放表A才能继续,这就死锁就产生了。
  2. 解决方法: 
    这种死锁比较常见,是因为程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操做时,尽可能按照相同的顺序进行处理,尽可能避免同时锁定两个资源,如操做A和B两张表时,老是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任什么时候刻都应该按照相同的顺序来锁定资源。

二. 并发修改同一记录

  1. 出现缘由: 
     用户A查询一条纪录,而后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁因为A有共享锁存在因此必须等A释放掉共享锁,而A因为B的独占锁而没法上升的独占锁也就不可能释放共享锁,因而出现了死锁。这种死锁因为比较隐蔽,但在稍大点的项目中常常发生。 
     通常更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,而后修改行,此操做要求锁转换为排它 (X) 锁。若是两个事务得到了资源上的共享模式锁,而后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间,由于一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。因为两个事务都要转换为排它 (X) 锁,而且每一个事务都等待另外一个事务释放共享模式锁,所以发生死锁。
  2. 解决方法: 
    a. 使用乐观锁进行控制。乐观锁大可能是基于数据版本(Version)记录机制实现。即为数据增长一个版本标识,在基于数据库表的版本解决方案中,通常是经过为数据库表增长一个“version”字段来实现。读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操做过程当中,都没有对数据库数据加锁),大大提高了大并发量下的系统总体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。须要注意的是,因为乐观锁机制是在咱们的系统中实现,来自外部系统的用户更新操做不受咱们系统的控制,所以可能会形成脏数据被更新到数据库中。 
    b. 使用悲观锁进行控制。悲观锁大多数状况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操做最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。如一个金融系统,当某个操做员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),若是采用悲观锁机制,也就意味着整个操做过程当中(从操做员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操做员中途去煮咖啡的时间),数据库记录始终处于加锁状态,能够想见,若是面对成百上千个并发,这样的状况将致使灾难性的后果。因此,采用悲观锁进行控制时必定要考虑清楚。 
    c. SqlServer可支持更新锁 
    为解决死锁,SqlServer引入更新锁,它有以下特征: 
    (1) 加锁的条件:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。 
    (2) 解锁的条件:当读取数据完毕,执行更新操做时,会把更新锁升级为独占锁。 
    (3) 与其余锁的兼容性:更新锁与共享锁是兼容的,也就是说,一个资源能够同时放置更新锁和共享锁,可是最多放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能得到更新锁,而后再把更新锁升级为独占锁,其余事务必须等到前一个事务结束后,才能获取得更新锁,这就避免了死锁。 
    (4) 并发性能:容许多个事务同时读锁定的资源,但不容许其余事务修改它。 
    例子以下:
   T1:
    begin tran  select * from table(updlock) (加更新锁)    update table set column1='hello'    T2:    begin tran    select * from table(updlock)    update table set column1='world'

更新锁的意思是:“我如今只想读,大家别人也能够读,但我未来可能会作更新操做,我已经获取了从共享锁(用来读)到排他锁(用来更新)的资格”。一个事物只能有一个更新锁获此资格。 
T1执行select,加更新锁。 
T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。 
当后来有user三、user4…须要查询table表中的数据时,并不会由于T1的select在执行就被阻塞,照样能查询,提升了效率。

三. 索引不当致使全表扫描

  1. 出现缘由: 
    若是在事务中执行了一条不知足条件的语句,执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。相似的状况还有当表中的数据量很是庞大而索引建的过少或不合适的时候,使得常常发生全表扫描,最终应用系统会愈来愈慢,最终发生阻塞或死锁。
  2. 解决方法: 
    SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,创建相应的索引进行优化。

四.事务封锁范围大且相互等待

 

.NET源码中的链表

 

.NET中自带的链表是LinkedList类,而且已经直接实现成了双向循环链表。

其节点类LinkedListNode的数据结构以下,数据项包括指示到某个链表的引用,以及左,右节点和值。

 

[html]  view plain  copy
 
  1. public sealed class LinkedListNode<T>  
  2. {  
  3.   internal LinkedList<T> list;  
  4.   internal LinkedListNode<T> next;  
  5.   internal LinkedListNode<T> prev;  
  6.   internal T item;  
  7. }  


另外,获取前一个节点和后一个节点的实现以下:注意:里面的if-else结构的意义是当前一个(后一个)节点不为空且不是头节点时才不返回null,这样作的意义是当链表内只有1个节点时,其prev和next是指向自身的。

 

 

[csharp]  view plain  copy
 
  1. [__DynamicallyInvokable]  
  2.    public LinkedListNode<T> Next  
  3.    {  
  4.      [__DynamicallyInvokable] get  
  5.      {  
  6.        if (this.next != null && this.next != this.list.head)  
  7.          return this.next;  
  8.        else  
  9.          return (LinkedListNode<T>) null;  
  10.      }  
  11.    }  
  12.   
  13.    [__DynamicallyInvokable]  
  14.    public LinkedListNode<T> Previous  
  15.    {  
  16.      [__DynamicallyInvokable] get  
  17.      {  
  18.        if (this.prev != null && this != this.list.head)  
  19.          return this.prev;  
  20.        else  
  21.          return (LinkedListNode<T>) null;  
  22.      }  
  23.    }  

 

 

过有一个把链表置为无效的方法定义以下:

 

[csharp]  view plain  copy
 
  1.     internal void Invalidate()  
  2.     {  
  3.       this.list = (LinkedList<T>) null;  
  4.       this.next = (LinkedListNode<T>) null;  
  5.       this.prev = (LinkedListNode<T>) null;  
  6.     }  

 

 

而LinkedList的定义以下:主要的两个数据是头节点head以及长度count。

 

[csharp]  view plain  copy
 
  1. public class LinkedList<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable, ISerializable, IDeserializationCallback  
  2.   {  
  3.     internal LinkedListNode<T> head;  
  4.     internal int count;  
  5.     internal int version;  
  6.     private object _syncRoot;  
  7.     private SerializationInfo siInfo;  
  8.     private const string VersionName = "Version";  
  9.     private const string CountName = "Count";  
  10.     private const string ValuesName = "Data";  

 

 

而对此链表的主要操做,包括:

 

  • 插入节点到最后,Add(),也是AddLast()。
  • 在某个节点后插入,AddAfter(Node, T)。
  • 在某个节点前插入,AddBefore(Node, T)。
  • 插入到头节点以前,AddFirst(T)。
  • 清除全部节点,Clear()。
  • 是否包含某个值,Contains(T),也就是Find()。
  • 查找某个节点的引用,Find()和FindLast()。
  • 复制到数组,CopyTo(Array)
  • 删除某个节点,Remove(T)。
另外有几个内部方法,用来支撑复用插入和删除操做:
  • 内部插入节点,InternalInsertNodeBefore()
  • 内部插入节点到空链表,InternalInsertNodeToEmptyList()
  • 内部删除节点,InternalRemoveNode()
  • 验证新节点是否有效,ValidateNewNode()
  • 验证节点是否有效,ValidateNode()
 
插入新节点到链表最后的代码以下:
[csharp]  view plain  copy
 
  1. public void AddLast(LinkedListNode<T> node)  
  2. {  
  3.   this.ValidateNewNode(node);  
  4.   if (this.head == null)  
  5.     this.InternalInsertNodeToEmptyList(node);  
  6.   else  
  7.     this.InternalInsertNodeBefore(this.head, node);  
  8.   node.list = this;  
  9. }  
插入操做的第一步是验证节点是否有效,即节点不为null,且节点不属于其余链表。
[csharp]  view plain  copy
 
  1. internal void ValidateNewNode(LinkedListNode<T> node)  
  2.    {  
  3.      if (node == null)  
  4.        throw new ArgumentNullException("node");  
  5.      if (node.list != null)  
  6.        throw new InvalidOperationException(SR.GetString("LinkedListNodeIsAttached"));  
  7.    }  

若是头节点为空,则执行插入到空链表的操做:将节点的next和prev都指向为本身,并做为头节点。
[csharp]  view plain  copy
 
  1. private void InternalInsertNodeToEmptyList(LinkedListNode<T> newNode)  
  2.    {  
  3.      newNode.next = newNode;  
  4.      newNode.prev = newNode;  
  5.      this.head = newNode;  
  6.      ++this.version;  
  7.      ++this.count;  
  8.    }  
若是头节点不为空,则执行插入到头节点以前(注:由于是双向链表,因此插到头节点以前就至关于插到链表的最后了),具体的指针指向操做以下:
[csharp]  view plain  copy
 
  1. private void InternalInsertNodeBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)  
  2. {  
  3.   newNode.next = node;  
  4.   newNode.prev = node.prev;  
  5.   node.prev.next = newNode;  
  6.   node.prev = newNode;  
  7.   ++this.version;  
  8.   ++this.count;  
  9. }  

而插入新节点到指定节点以后的操做以下:一样仍是调用的内部函数,把新节点插入到指定节点的下一个节点的以前。有点绕,但确实让这个内部函数起到多个做用了。
[csharp]  view plain  copy
 
  1.     public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)  
  2.     {  
  3.       this.ValidateNode(node);  
  4.       this.ValidateNewNode(newNode);  
  5.       this.InternalInsertNodeBefore(node.next, newNode);  
  6.       newNode.list = this;  
  7.     }  

而插入新节点到指定节点以前的操做以下:直接调用插入新节点的内部函数,另外还要判断指定的节点是不是头节点,若是是的话,要把头节点变成新的节点。
[csharp]  view plain  copy
 
  1. public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)  
  2.     {  
  3.       this.ValidateNode(node);  
  4.       this.ValidateNewNode(newNode);  
  5.       this.InternalInsertNodeBefore(node, newNode);  
  6.       newNode.list = this;  
  7.       if (node != this.head)  
  8.         return;  
  9.       this.head = newNode;  
  10.     }  

把新链表插入到第一个节点(也就是变成头节点)的操做以下:若是链表为空就直接变成头节点,不然就插入到头节点以前,取代头节点。
[csharp]  view plain  copy
 
  1. public void AddFirst(LinkedListNode<T> node)  
  2.     {  
  3.       this.ValidateNewNode(node);  
  4.       if (this.head == null)  
  5.       {  
  6.         this.InternalInsertNodeToEmptyList(node);  
  7.       }  
  8.       else  
  9.       {  
  10.         this.InternalInsertNodeBefore(this.head, node);  
  11.         this.head = node;  
  12.       }  
  13.       node.list = this;  
  14.     }  

查找链表中某个值的操做以下:注意直接返回null的条件是头节点为空。而后就是遍历了,由于是双向链表,因此要避免死循环(遍历到头节点时跳出)。
[csharp]  view plain  copy
 
  1. public LinkedListNode<T> Find(T value)  
  2.     {  
  3.       LinkedListNode<T> linkedListNode = this.head;  
  4.       EqualityComparer<T> @default = EqualityComparer<T>.Default;  
  5.       if (linkedListNode != null)  
  6.       {  
  7.         if ((object) value != null)  
  8.         {  
  9.           while (!@default.Equals(linkedListNode.item, value))  
  10.           {  
  11.             linkedListNode = linkedListNode.next;  
  12.             if (linkedListNode == this.head)  
  13.               goto label_8;  
  14.           }  
  15.           return linkedListNode;  
  16.         }  
  17.         else  
  18.         {  
  19.           while ((object) linkedListNode.item != null)  
  20.           {  
  21.             linkedListNode = linkedListNode.next;  
  22.             if (linkedListNode == this.head)  
  23.               goto label_8;  
  24.           }  
  25.           return linkedListNode;  
  26.         }  
  27.       }  
  28. label_8:  
  29.       return (LinkedListNode<T>) null;  
  30.     }  

删除某个节点的操做以下:
[csharp]  view plain  copy
 
  1. public void Remove(LinkedListNode<T> node)  
  2. {  
  3.   this.ValidateNode(node);  
  4.   this.InternalRemoveNode(node);  
  5. }  

一样,内部删除节点的实现以下:若是节点指向本身,说明是头节点,因此直接把头节点置null。而后就是指针的指向操做了。
[csharp]  view plain  copy
 
  1. internal void InternalRemoveNode(LinkedListNode<T> node)  
  2.     {  
  3.       if (node.next == node)  
  4.       {  
  5.         this.head = (LinkedListNode<T>) null;  
  6.       }  
  7.       else  
  8.       {  
  9.         node.next.prev = node.prev;  
  10.         node.prev.next = node.next;  
  11.         if (this.head == node)  
  12.           this.head = node.next;  
  13.       }  
  14.       node.Invalidate();  
  15.       --this.count;  
  16.       ++this.version;  
  17.     }  

而清空链表的操做以下:遍历链表,逐个设置为无效,最后将内部的头节点也置为null。
[csharp]  view plain  copy
 
  1. public void Clear()  
  2. {  
  3.   LinkedListNode<T> linkedListNode1 = this.head;  
  4.   while (linkedListNode1 != null)  
  5.   {  
  6.     LinkedListNode<T> linkedListNode2 = linkedListNode1;  
  7.     linkedListNode1 = linkedListNode1.Next;  
  8.     linkedListNode2.Invalidate();  
  9.   }  
  10.   this.head = (LinkedListNode<T>) null;  
  11.   this.count = 0;  
  12.   ++this.version;  
  13. }  
 
链表转数组的实现以下:首先判断入参的有效性,而后从头节点开始遍历,依次复制到数组中,直到头结点(尽头)。
[csharp]  view plain  copy
 
  1. public void CopyTo(T[] array, int index)  
  2.    {  
  3.      if (array == null)  
  4.        throw new ArgumentNullException("array");  
  5.      if (index < 0 || index > array.Length)  
  6.      {  
  7.        throw new ArgumentOutOfRangeException("index", SR.GetString("IndexOutOfRange", new object[1]  
  8.        {  
  9.          (object) index  
  10.        }));  
  11.      }  
  12.      else  
  13.      {  
  14.        if (array.Length - index < this.Count)  
  15.          throw new ArgumentException(SR.GetString("Arg_InsufficientSpace"));  
  16.        LinkedListNode<T> linkedListNode = this.head;  
  17.        if (linkedListNode == null)  
  18.          return;  
  19.        do  
  20.        {  
  21.          array[index++] = linkedListNode.item;  
  22.          linkedListNode = linkedListNode.next;  
  23.        }  
  24.        while (linkedListNode != this.head);  
  25.      }  
  26.    }  

以上。
 

多线程下C#如何保证线程安全?

 

多线程编程相对于单线程会出现一个特有的问题,就是线程安全的问题。所谓的线程安全,就是若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的。 线程安全问题都是由全局变量及静态变量引发的。

  为了保证多线程状况下,访问静态变量的安全,能够用锁机制来保证,以下所示:

复制代码
1         //须要加锁的静态全局变量
 2         private static bool _isOK = false;
 3         //lock只能锁定一个引用类型变量
 4         private static object _lock = new object();
 5         static void MLock()
 6         {
 7             //多线程
 8             new System.Threading.Thread(Done).Start();
 9             new System.Threading.Thread(Done).Start();
10             Console.ReadLine();
11         }
12 
13         static void Done()
14         {
15             //lock只能锁定一个引用类型变量
16             lock (_lock)
17             {
18                 if (!_isOK)
19                 {
20                     Console.WriteLine("OK");
21                     _isOK = true;
22                 }
23             }
24         }
复制代码

  须要注意的是,Lock只能锁住一个引用类型的对象。另外,除了锁机制外,高版本的C#中加入了async和await方法来保证线程安全,以下所示:

复制代码
1 public static class AsynAndAwait
 2  {
 3         //step 1 
 4         private static int count = 0;
 5         //用async和await保证多线程下静态变量count安全
 6         public async static void M1()
 7         {
 8             //async and await将多个线程进行串行处理
 9             //等到await以后的语句执行完成后
10             //才执行本线程的其余语句
11             //step 2
12             await Task.Run(new Action(M2));
13             Console.WriteLine("Current Thread ID is {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
14             //step 6
15             count++;
16             //step 7
17             Console.WriteLine("M1 Step is {0}", count);
18         }
19 
20         public static void M2()
21         {
22             Console.WriteLine("Current Thread ID is {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
23             //step 3
24             System.Threading.Thread.Sleep(3000);
25             //step 4
26             count++;
27             //step 5
28             Console.WriteLine("M2 Step is {0}", count);
29         }
30 }
复制代码

  在时序图中咱们能够知道,共有两个线程进行交互,以下图所示:

  用async和await后,上述代码的执行顺序为下图所示:

 

  若每一个线程中对全局变量、静态变量只有读操做,而无写操做,通常来讲,这个全局变量是线程安全的;如有多个线程同时对一个变量执行读写操做,通常都须要考虑线程同步,不然就可能影响线程安全。

 

转载自:http://www.cnblogs.com/isaboy/p/C_async_await_lock_safe_thread_multi.html

 

.net实现支付宝在线支付

 

 流程参考《实物商品交易服务集成技术文档2.0.pdf》
网关地址http://paytest.rupeng.cn/AliPay/PayGate.ashx


网关参数说明:
partner:商户编号
return_url:回调商户地址(经过商户网站的哪一个页面来通知支付成功!)
subject:商品名称
body:商品描述
out_trade_no:订单号!!!(由商户网站生成,支付宝不确保正确性,只负责转发。)
total_fee:总金额
seller_email:卖家邮箱
sign:数字签名。
为按顺序链接 (总金额、 商户编号、订单号、商品名称、商户密钥)的MD5值。


重定向的url("http://paytest.rupeng.cn/AliPay/PayGate.ashx?partner="
                + partner + "&return_url=" + Server.UrlEncode(return_url) 
+ "&subject=" 
+ Server.UrlEncode(subject) 
+ "&body=" + Server.UrlEncode(body) 
+ "&out_trade_no=" + out_trade_no 
+ "&total_fee=" + total_fee + "&seller_email=" 
+ Server.UrlEncode(seller_email) + "&sign=" + sign)




回调商户接口地址参数说明:
out_trade_no :订单号。给PayGate.ashx传过去的out_trade_no再传回来
returncode :返回码,字符串。ok为支付成功,error为支付失败。
total_fee :支付金额
sign  :数字签名。为按顺序链接 (订单号、返回码、支付金额、商户密钥)为新字符串的MD5值。


(每一个商户的密钥是商户本身设置的,每一个人的都不同,只有支付宝和商户知道,因此没法猜想、假冒)




MD5算法要用如下的,大小写都不能错:
        /// <summary>
        /// 获得字符串的MD5散列值
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static String GetMD5(this string input)
        {
            System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
            byte[] bs = System.Text.Encoding.UTF8.GetBytes(input);
            bs = x.ComputeHash(bs);
            System.Text.StringBuilder s = new System.Text.StringBuilder();
            foreach (byte b in bs)
            {
                s.Append(b.ToString("x2").ToLower());
            }
            return s.ToString();
        }

 

  /// <summary>

        /// 付款
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Unnamed1_Click(object sender, EventArgs e)
        {
            string partner = "2";//商户编号
            string return_url = "http://localhost:5059/ReturnPage.ashx";//回调商户地址(经过商户网站的哪一个页面来通知支付成功!)
            string subject = "飞机"; //商品名称
            string body = "很是大的飞机";  //商品描述
            string out_trade_no = "aaabbb888";  //订单号!(由商户网站生成,支付宝不确保正确性,只负责转发。)
            string total_fee = "9"; //总金额
            string seller_email = "719862911@qq.com";//卖家邮箱  
            //商户密钥 abc123//不要写到url中
            //为按顺序链接 (总金额、 商户编号、订单号、商品名称、商户密钥)的MD5值。
            string sign = CommonHelper.getMD5Str(total_fee + partner + out_trade_no + subject + "abc123");//数字签名。


            Response.Redirect("http://paytest.rupeng.cn/AliPay/PayGate.ashx?partner="
                + partner + "&return_url=" + Server.UrlEncode(return_url) + "&subject=" + Server.UrlEncode(subject) + "&body=" + Server.UrlEncode(body) + "&out_trade_no=" + out_trade_no + "&total_fee=" + total_fee + "&seller_email=" + Server.UrlEncode(seller_email) + "&sign=" + sign);

        }

 

  public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/html";
            context.Response.Write("支付宝消息返回到了个人商户网站的这个页面\r\n");


            string out_trade_no = context.Request["out_trade_no"];//订单号。给PayGate.ashx传过去的out_trade_no再传回来
            string returncode = context.Request["returncode"];//返回码,字符串。ok为支付成功,error为支付失败。
            string total_fee = context.Request["total_fee"];//支付金额
            string sign = context.Request["sign"];//支付宝端返回 的数字签名
            string MySign = CommonHelper.getMD5Str(out_trade_no + returncode + total_fee + "abc123");//为按顺序链接 (订单号、返回码、支付金额、商户密钥)为新字符串的MD5值。
            if (sign!=MySign)
            {
                //提交的数据 =验证失败
                context.Response.Write("提交的数据 =验证失败");
                return;
            }
            if (returncode=="ok")
            {
                context.Response.Write("支付成功");
            }
            else if (returncode == "error")
            {
                context.Response.Write("支付失败");
            }
        }

 

转载自:https://blog.csdn.net/u014297475/article/details/52419202

 

彻头彻尾理解单例模式与多线程

 

摘要: 
   
  本文首先概述了单例模式产生动机,揭示了单例模式的本质和应用场景。紧接着,咱们给出了单例模式在单线程环境下的两种经典实现:饿汉式 和 懒汉式,可是饿汉式是线程安全的,而懒汉式是非线程安全的。在多线程环境下,咱们特别介绍了五种方式来在多线程环境下建立线程安全的单例,使用 synchronized方法synchronized块静态内部类双重检查模式 和 ThreadLocal 实现懒汉式单例,并总结出实现效率高且线程安全的单例所须要注意的事项。


版权声明:

本文原创做者:书呆子Rico 
做者博客地址:http://blog.csdn.net/justloveyou_/


一. 单例模式概述

  单例模式(Singleton),也叫单子模式,是一种经常使用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只须要拥有一个的全局对象,这样有利于咱们协调系统总体的行为。好比在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,而后服务进程中的其余对象再经过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。

  特别地,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。事实上,这些应用都或多或少具备资源管理器的功能。例如,每台计算机能够有若干个打印机,但只能有一个 Printer Spooler (单例) ,以免两个打印做业同时输出到打印机中。再好比,每台计算机能够有若干通讯端口,系统应当集中 (单例) 管理这些通讯端口,以免一个通讯端口同时被两个请求同时调用。总之,选择单例模式就是为了不不一致状态,避免政出多头。

  综上所述,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。


二. 单例模式及其单线程环境下的经典实现

  单例模式应该是23种设计模式中最简单的一种模式了,下面咱们从单例模式的定义、类型、结构和使用要素四个方面来介绍它。

一、单例模式理论基础

定义: 确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。

类型: 建立型模式

结构:

                      技术分享

  特别地,为了更好地理解上面的类图,咱们以此为契机,介绍一下类图的几个知识点:

  • 类图分为三部分,依次是类名、属性、方法;
  • 以<<开头和以>>结尾的为注释信息;
  • 修饰符+表明public,-表明private,#表明protected,什么都没有表明包可见;
  • 带下划线的属性或方法表明是静态的。

三要素:

  • 私有的构造方法;

  • 指向本身实例的私有静态引用;

  • 以本身实例为返回值的静态的公有方法。


二、单线程环境下的两种经典实现

  在介绍单线程环境中单例模式的两种经典实现以前,咱们有必要先解释一下 当即加载 和 延迟加载 两个概念。

  • 当即加载 : 在类加载初始化的时候就主动建立实例;

  • 延迟加载 : 等到真正使用的时候才去建立实例,不用时不去主动建立。

      在单线程环境下,单例模式根据实例化对象时机的不一样,有两种经典的实现:一种是 饿汉式单例(当即加载),一种是 懒汉式单例(延迟加载)饿汉式单例在单例类被加载时候,就实例化一个对象并交给本身的引用;而懒汉式单例只有在真正使用的时候才会实例化一个对象并交给本身的引用。代码示例分别以下:


饿汉式单例:

// 饿汉式单例 public class Singleton1 { // 指向本身实例的私有静态引用,主动建立 private static Singleton1 singleton1 = new Singleton1(); // 私有的构造方法 private Singleton1(){} // 以本身实例为返回值的静态的公有方法,静态工厂方法 public static Singleton1 getSingleton1(){ return singleton1; } }

  咱们知道,类加载的方式是按需加载,且加载一次。。所以,在上述单例类被加载时,就会实例化一个对象并交给本身的引用,供系统使用;并且,因为这个类在整个生命周期中只会被加载一次,所以只会建立一个实例,即可以充分保证单例。


懒汉式单例:

// 懒汉式单例 public class Singleton2 { // 指向本身实例的私有静态引用 private static Singleton2 singleton2; // 私有的构造方法 private Singleton2(){} // 以本身实例为返回值的静态的公有方法,静态工厂方法 public static synchronized Singleton2 getSingleton2(){ // 被动建立,在真正须要使用时才去建立 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }

  咱们从懒汉式单例能够看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给本身的引用。


  总之,从速度和反应时间角度来说,饿汉式(又称当即加载)要好一些;从资源利用效率上说,懒汉式(又称延迟加载)要好一些。


三、单例模式的优势

  咱们从单例模式的定义和实现,能够知道单例模式具备如下几个优势:

  • 在内存中只有一个对象,节省内存空间;

  • 避免频繁的建立销毁对象,能够提升性能;

  • 避免对共享资源的多重占用,简化访问;

  • 为整个系统提供一个全局访问点。


四、单例模式的使用场景

  因为单例模式具备以上优势,而且形式上比较简单,因此是平常开发中用的比较多的一种设计模式,其核心在于为整个系统提供一个惟一的实例,其应用场景包括但不只限于如下几种:

  • 有状态的工具类对象;
  • 频繁访问数据库或文件的对象;

五、单例模式的注意事项

  在使用单例模式时,咱们必须使用单例类提供的公有工厂方法获得单例对象,而不该该使用反射来建立,不然将会实例化一个新对象。此外,在多线程环境下使用单例模式时,应特别注意线程安全问题,我在下文会重点讲到这一点。


三. 多线程环境下单例模式的实现

  在单线程环境下,不管是饿汉式单例仍是懒汉式单例,它们都可以正常工做。可是,在多线程环境下,情形就发生了变化:因为饿汉式单例天生就是线程安全的,能够直接用于多线程而不会出现问题;但懒汉式单例自己是非线程安全的,所以就会出现多个实例的状况,与单例模式的初衷是相背离的。下面我重点阐述如下几个问题:

  • 为何说饿汉式单例天生就是线程安全的?

  • 传统的懒汉式单例为何是非线程安全的?

  • 怎么修改传统的懒汉式单例,使其线程变得安全?

  • 线程安全的单例的实现还有哪些,怎么实现?

  • 双重检查模式、Volatile关键字 在单例模式中的应用

  • ThreadLocal 在单例模式中的应用


  特别地,为了可以更好的观察到单例模式的实现是不是线程安全的,咱们提供了一个简单的测试程序来验证。该示例程序的判断原理是:

  开启多个线程来分别获取单例,而后打印它们所获取到的单例的hashCode值。若它们获取的单例是相同的(该单例模式的实现是线程安全的),那么它们的hashCode值必定彻底一致;若它们的hashCode值不彻底一致,那么获取的单例一定不是同一个,即该单例模式的实现不是线程安全的,是多例的。注意,相应输出结果附在每一个单例模式实现示例后。 
   
  若看官对上述原理不够了解,请移步个人博客《Java 中的 ==, equals 与 hashCode 的区别与联系》

public class Test { public static void main(String[] args) { Thread[] threads = new Thread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new TestThread(); } for (int i = 0; i < threads.length; i++) { threads[i].start(); } } } class TestThread extends Thread { @Override public void run() { // 对于不一样单例模式的实现,只需更改相应的单例类名及其公有静态工厂方法名便可 int hash = Singleton5.getSingleton5().hashCode(); System.out.println(hash); } }

一、为何说饿汉式单例天生就是线程安全的?

// 饿汉式单例 public class Singleton1 { // 指向本身实例的私有静态引用,主动建立 private static Singleton1 singleton1 = new Singleton1(); // 私有的构造方法 private Singleton1(){} // 以本身实例为返回值的静态的公有方法,静态工厂方法 public static Singleton1 getSingleton1(){ return singleton1; } }/* Output(彻底一致): 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 *///:~

  咱们已经在上面提到,类加载的方式是按需加载,且只加载一次。所以,在上述单例类被加载时,就会实例化一个对象并交给本身的引用,供系统使用。换句话说,在线程访问单例对象以前就已经建立好了。再加上,因为一个类在整个生命周期中只会被加载一次,所以该单例类只会建立一个实例,也就是说,线程每次都只能也一定只能够拿到这个惟一的对象。所以就说,饿汉式单例天生就是线程安全的。


二、传统的懒汉式单例为何是非线程安全的?

// 传统懒汉式单例 public class Singleton2 { // 指向本身实例的私有静态引用 private static Singleton2 singleton2; // 私有的构造方法 private Singleton2(){} // 以本身实例为返回值的静态的公有方法,静态工厂方法 public static synchronized Singleton2 getSingleton2(){ // 被动建立,在真正须要使用时才去建立 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }/* Output(不彻底一致): 1084284121 2136955031 2136955031 1104499981 298825033 298825033 2136955031 482535999 298825033 2136955031 *///:~

  上面发生非线程安全的一个显著缘由是,会有多个线程同时进入 if (singleton2 == null) {…} 语句块的情形发生。当这种这种情形发生后,该单例类就会建立出多个实例,违背单例模式的初衷。所以,传统的懒汉式单例是非线程安全的。


三、实现线程安全的懒汉式单例的几种正确姿式

1)、同步延迟加载 — synchronized方法

// 线程安全的懒汉式单例 public class Singleton2 { private static Singleton2 singleton2; private Singleton2(){} // 使用 synchronized 修饰,临界资源的同步互斥访问 public static synchronized Singleton2 getSingleton2(){ if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }/* Output(彻底一致): 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 *///:~

  该实现与上面传统懒汉式单例的实现惟一的差异就在于:是否使用 synchronized 修饰 getSingleton2()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例。

  从执行结果上来看,问题已经解决了,可是这种实现方式的运行效率会很低,由于同步块的做用域有点大,并且锁的粒度有点粗。同步方法效率低,那咱们考虑使用同步代码块来实现。

  更多关于 synchronized 关键字 的介绍, 请移步个人博文《Java 并发:内置锁 Synchronized》


2)、同步延迟加载 — synchronized块

// 线程安全的懒汉式单例 public class Singleton2 { private static Singleton2 singleton2; private Singleton2(){} public static Singleton2 getSingleton2(){ synchronized(Singleton2.class){ // 使用 synchronized 块,临界资源的同步互斥访问 if (singleton2 == null) { singleton2 = new Singleton2(); } } return singleton2; } }/* Output(彻底一致): 16993205 16993205 16993205 16993205 16993205 16993205 16993205 16993205 16993205 16993205 *///:~

  该实现与上面synchronized方法版本实现相似,此不赘述。从执行结果上来看,问题已经解决了,可是这种实现方式的运行效率仍然比较低,事实上,和使用synchronized方法的版本相比,基本没有任何效率上的提升。


3)、同步延迟加载 — 使用内部类实现延迟加载

// 线程安全的懒汉式单例 public class Singleton5 { // 私有内部类,按需加载,用时加载,也就是延迟加载 private static class Holder { private static Singleton5 singleton5 = new Singleton5(); } private Singleton5() { } public static Singleton5 getSingleton5() { return Holder.singleton5; } } /* Output(彻底一致): 482535999 482535999 482535999 482535999 482535999 482535999 482535999 482535999 482535999 482535999 *///:~

  如上述代码所示,咱们可使用内部类实现线程安全的懒汉式单例,这种方式也是一种效率比较高的作法。至于其为何是线程安全的,其与问题 “为何说饿汉式单例天生就是线程安全的?” 相相似,此不赘述。

  更多关于 内部类 的介绍, 请移步个人博文《 Java 内部类综述 》

  关于使用双重检查、ThreaLocal实现线程安全的懒汉式单例分别见第四节和第五节。


四. 单例模式与双重检查(Double-Check idiom)

  使用双重检测同步延迟加载去建立单例的作法是一个很是优秀的作法,其不但保证了单例,并且切实提升了程序运行效率。对应的代码清单以下:

// 线程安全的懒汉式单例 public class Singleton3 { //使用volatile关键字防止重排序,由于 new Instance()是一个非原子操做,可能建立一个不完整的实例 private static volatile Singleton3 singleton3; private Singleton3() { } public static Singleton3 getSingleton3() { // Double-Check idiom if (singleton3 == null) { synchronized (Singleton3.class) { // 1 // 只需在第一次建立实例时才同步 if (singleton3 == null) { // 2 singleton3 = new Singleton3(); // 3 } } } return singleton3; } }/* Output(彻底一致): 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 1104499981 *///:~

  如上述代码所示,为了在保证单例的前提下提升运行效率,咱们须要对 singleton3 进行第二次检查,目的是避开过多的同步(由于这里的同步只需在第一次建立实例时才同步,一旦建立成功,之后获取实例时就不须要同步获取锁了)。这种作法无疑是优秀的,可是咱们必须注意一点: 
   
  必须使用volatile关键字修饰单例引用。


  那么,若是上述的实现没有使用 volatile 修饰 singleton3,会致使什么情形发生呢? 为解释该问题,咱们分两步来阐述:

(1)、当咱们写了 new 操做,JVM 到底会发生什么?

  首先,咱们要明白的是: new Singleton3() 是一个非原子操做。代码行 singleton3 = new Singleton3(); 的执行过程能够形象地用以下3行伪代码来表示:

memory = allocate(); //1:分配对象的内存空间 ctorInstance(memory); //2:初始化对象 singleton3 = memory; //3:使singleton3指向刚分配的内存地址

  但实际上,这个过程可能发生无序写入(指令重排序),也就是说上面的3行指令可能会被重排序致使先执行第3行后执行第2行,也就是说其真实执行顺序多是下面这种:

memory = allocate(); //1:分配对象的内存空间 singleton3 = memory; //3:使singleton3指向刚分配的内存地址 ctorInstance(memory); //2:初始化对象

  这段伪代码演示的状况不只是可能的,并且是一些 JIT 编译器上真实发生的现象。


(2)、重排序情景再现 
   
  了解 new 操做是非原子的而且可能发生重排序这一事实后,咱们回过头看使用 Double-Check idiom 的同步延迟加载的实现:

  咱们须要从新考察上述清单中的 //3 行。此行代码建立了一个 Singleton 对象并初始化变量 singleton3 来引用此对象。这行代码存在的问题是,在 Singleton 构造函数体执行以前,变量 singleton3 可能提早成为非 null 的,即赋值语句在对象实例化以前调用,此时别的线程将获得的是一个不完整(未初始化)的对象,会致使系统崩溃。下面是程序可能的一组执行步骤:

  一、线程 1 进入 getSingleton3() 方法; 
  二、因为 singleton3 为 null,线程 1 在 //1 处进入 synchronized 块; 
  三、一样因为 singleton3 为 null,线程 1 直接前进到 //3 处,但在构造函数执行以前,使实例成为非 null,而且该实例是未初始化的; 
  四、线程 1 被线程 2 预占; 
  五、线程 2 检查实例是否为 null。由于实例不为 null,线程 2 获得一个不完整(未初始化)的 Singleton 对象; 
  六、线程 2 被线程 1 预占。 
  七、线程 1 经过运行 Singleton3 对象的构造函数来完成对该对象的初始化。

  显然,一旦咱们的程序在执行过程当中发生了上述情形,就会形成灾难性的后果,而这种安全隐患正是因为指令重排序的问题所致使的。让人兴奋地是,volatile 关键字正好能够完美解决了这个问题。也就是说,咱们只需使用volatile关键字修饰单例引用就能够避免上述灾难。


  特别地,因为 volatile关键字的介绍 和 类加载及对象初始化顺序 两块内容已经在我以前的博文中介绍过,再此只给出相关连接,再也不赘述。

  更多关于 volatile关键字 的介绍, 请移步个人博文《 Java 并发:volatile 关键字解析》

  更多关于 类加载及对象初始化顺序的介绍, 请移步个人博文《 Java 继承、多态与类的复用》


五. 单例模式 与 ThreadLocal

  借助于 ThreadLocal,咱们能够实现双重检查模式的变体。咱们将临界资源线程局部化,具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为 线程局部范围内的操做 。这里的 ThreadLocal 也只是用做标识而已,用来标识每一个线程是否已访问过:若是访问过,则再也不须要走同步块,这样就提升了必定的效率。对应的代码清单以下:

// 线程安全的懒汉式单例 public class Singleton4 { // ThreadLocal 线程局部变量 private static ThreadLocal<Singleton4> threadLocal = new ThreadLocal<Singleton4>(); private static Singleton4 singleton4 = null; // 不须要是 private Singleton4(){} public static Singleton4 getSingleton4(){ if (threadLocal.get() == null) { // 第一次检查:该线程是否第一次访问 createSingleton4(); } return singleton4; } public static void createSingleton4(){ synchronized (Singleton4.class) { if (singleton4 == null) { // 第二次检查:该单例是否被建立 singleton4 = new Singleton4(); // 只执行一次 } } threadLocal.set(singleton4); // 将单例放入当前线程的局部变量中  } }/* Output(彻底一致): 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 1028355155 *///:~

  借助于 ThreadLocal,咱们也能够实现线程安全的懒汉式单例。但与直接双重检查模式使用,本实如今效率上还不如后者。

  更多关于 ThreadLocal 的介绍, 请移步个人博文《 Java 并发:深刻理解 ThreadLocal》


六. 小结

  本文首先介绍了单例模式的定义和结构,并给出了其在单线程和多线程环境下的几种经典实现。特别地,咱们知道,传统的饿汉式单例不管在单线程仍是多线程环境下都是线程安全的,可是传统的懒汉式单例在多线程环境下是非线程安全的。为此,咱们特别介绍了五种方式来在多线程环境下建立线程安全的单例,包括:

  • 使用synchronized方法实现懒汉式单例;

  • 使用synchronized块实现懒汉式单例;

  • 使用静态内部类实现懒汉式单例;

  • 使用双重检查模式实现懒汉式单例;

  • 使用ThreadLocal实现懒汉式单例;


  固然,实现懒汉式单例还有其余方式。可是,这五种是比较经典的实现,也是咱们应该掌握的几种实现方式。从这五种实现中,咱们能够总结出,要想实现效率高的线程安全的单例,咱们必须注意如下两点:

  • 尽可能减小同步块的做用域;

  • 尽可能使用细粒度的锁。


七. 更多

  本文涉及内容比较广,涉及到 hashcode、synchronized 关键字、内部类、 类加载及对象初始化顺序、volatile关键字 和 ThreadLocal 等知识点,这些知识点在我以前的博文中均专门总结过,现附上相关连接,感兴趣的朋友能够移步到相关博文进行查看。

  更多关于 hashCode 与相等 的介绍,请移步个人博客《Java 中的 ==, equals 与 hashCode 的区别与联系》

  更多关于 synchronized 关键字 的介绍, 请移步个人博文《Java 并发:内置锁 Synchronized》

  更多关于 内部类 的介绍, 请移步个人博文《 Java 内部类综述 》

  更多关于 volatile关键字 的介绍, 请移步个人博文《 Java 并发:volatile 关键字解析》

  更多关于 类加载及对象初始化顺序的介绍, 请移步个人博文《 Java 继承、多态与类的复用》

  更多关于 ThreadLocal 的介绍, 请移步个人博文《 Java 并发:深刻理解 ThreadLocal》


  此外,

  更多关于 Java SE 进阶 方面的内容,请关注个人专栏 《Java SE 进阶之路》。本专栏主要研究Java基础知识、Java源码和设计模式,从初级到高级不断总结、剖析各知识点的内在逻辑,贯穿、覆盖整个Java知识面,在一步步完善、提升把本身的同时,把对Java的所学所思分享给你们。万丈高楼平地起,基础决定你的上限,让咱们携手一块儿勇攀Java之巅…

  更多关于 Java 并发编程 方面的内容,请关注个人专栏 《Java 并发编程学习笔记》。本专栏全面记录了Java并发编程的相关知识,并结合操做系统、Java内存模型和相关源码对并发编程的原理、技术、设计、底层实现进行深刻分析和总结,并持续跟进并发相关技术。


引用

Java 中的双重检查(Double-Check) 
单例模式与双重检测 
用happen-before规则从新审视DCL 
JAVA设计模式之单例模式 
23种设计模式(1):单例模式

 

转载自:http://www.mamicode.com/info-detail-1728587.html

 

App.Config详解及读写操做

 
App.Config详解
应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的。它是能够按须要更改的,开发人员可使用配置文件来更改设置,而没必要重编译应用程序。
配置文件的根节点是configuration。咱们常常访问的是appSettings,它是由.Net预约义配置节。咱们常用的配置文件的架构是象下面的形式。
先大概有个印象,经过后面的实例会有一个比较清楚的认识。下面的“配置节”能够理解为进行配置一个XML的节点。 
1.  向项目添加 app.config 文件:
右击项目名称,选择“添加”→“添加新建项”,在出现的“添加新项”对话框中,选择“添加应用程序配置文件”;若是项目之前没有配置文件,则默认的文件名称为“ app.config ”,单击“肯定”。出如今设计器视图中的app.config 文件为:
<? xml version = "1.0 "encoding = "utf-8 " ?>
< configuration >
</ configuration >
在项目进行编译后,在 bin/Debuge 文件下,将出现两个配置文件 ( 以本项目为例 ) ,一个名为“JxcManagement.EXE.config ”,另外一个名为“ JxcManagement.vshost.exe.config ”。第一个文件为项目实际使用的配置文件,在程序运行中所作的更改都将被保存于此;第二个文件为原代码“ app.config ”的同步文件,在程序运行中不会发生更改。
2.  connectionStrings 配置节:
请注意:若是您的 SQL 版本为 2005 Express 版,则默认安装时 SQL 服务器实例名为localhost/SQLExpress ,须更改如下实例中“ Data Source=localhost; ”一句为“ Data Source=localhost/SQLExpress; ”,在等于号的两边不要加上空格。
<!-- 数据库链接串 -->
     < connectionStrings >
         < clear />
         < add name = "conJxcBook "
              connectionString = "Data Source=localhost;Initial Catalog=jxcbook;User                                   ID=sa;password=******** "
              providerName = "System.Data.SqlClient " />
     </ connectionStrings >
3. appSettings 配置节:
appSettings 配置节为整个程序的配置,若是是对当前用户的配置,请使用 userSettings 配置节,其格式与如下配置书写要求同样。
<!-- 进销存管理系统初始化须要的参数 -->
     < appSettings >
         < clear />
         < add key = "userName "value = "" />
         < add key = "password "value = "" />
         < add key = "Department "value = "" />
         < add key = "returnValue "value = "" />
         < add key = "pwdPattern "value = "" />
         < add key = "userPattern "value = "" />
</ appSettings >
4. 读取与更新 app.config
对于app.config 文件的读写,参照了网络文章: http://www.codeproject.com/csharp/  SystemConfiguration.asp标题为“Read/Write App.Config File with .NET 2.0”一文。
请注意:要使用如下的代码访问app.config文件,除添加引用System.Configuration外,还必须在项目添加对System.Configuration.dll的引用。
4.1 读取connectionStrings配置节
/// <summary>
/// 依据链接串名字connectionName返回数据链接字符串
/// </summary>
/// <param name="connectionName"></param>
/// <returns></returns>
private static string GetConnectionStringsConfig(string connectionName)
{
string connectionString =
        ConfigurationManager .ConnectionStrings[connectionName].ConnectionString.ToString();
    Console .WriteLine(connectionString);
    return connectionString;
}
4.2 更新connectionStrings配置节
/// <summary>
/// 更新链接字符串
/// </summary>
/// <param name="newName"> 链接字符串名称 </param>
/// <param name="newConString"> 链接字符串内容 </param>
/// <param name="newProviderName"> 数据提供程序名称 </param>
private static void UpdateConnectionStringsConfig(string newName,
    string newConString,
    string newProviderName)
{
    bool isModified = false ;    // 记录该链接串是否已经存在
    // 若是要更改的链接串已经存在
    if (ConfigurationManager .ConnectionStrings[newName] != null )
    {
        isModified = true ;
    }
    // 新建一个链接字符串实例
    ConnectionStringSettings mySettings =
        new ConnectionStringSettings (newName, newConString, newProviderName);
    // 打开可执行的配置文件*.exe.config
    Configuration config =
        ConfigurationManager .OpenExeConfiguration(ConfigurationUserLevel .None);
    // 若是链接串已存在,首先删除它
    if (isModified)
    {
        config.ConnectionStrings.ConnectionStrings.Remove(newName);
    }
    // 将新的链接串添加到配置文件中.
    config.ConnectionStrings.ConnectionStrings.Add(mySettings);
    // 保存对配置文件所做的更改
    config.Save(ConfigurationSaveMode .Modified);
    // 强制从新载入配置文件的ConnectionStrings配置节
    ConfigurationManager .RefreshSection("ConnectionStrings" );
}
4.3 读取appStrings配置节
/// <summary>
/// 返回*.exe.config文件中appSettings配置节的value项
/// </summary>
/// <param name="strKey"></param>
/// <returns></returns>
private static string GetAppConfig(string strKey)
{
    foreach (string key in ConfigurationManager .AppSettings)
    {
        if (key == strKey)
        {
            return ConfigurationManager .AppSettings[strKey];
        }
    }
    return null ;
}
4.4 更新connectionStrings配置节
/// <summary>
/// 在*.exe.config文件中appSettings配置节增长一对键、值对
/// </summary>
/// <param name="newKey"></param>
/// <param name="newValue"></param>
private static void UpdateAppConfig(string newKey, string newValue)
{
    bool isModified = false ;   
    foreach (string key in ConfigurationManager .AppSettings)
    {
       if (key==newKey)
        {   
            isModified = true ;
        }
    }
 
    // Open App.Config of executable
    Configuration config =
        ConfigurationManager .OpenExeConfiguration(ConfigurationUserLevel .None);
    // You need to remove the old settings object before you can replace it
    if (isModified)
    {
        config.AppSettings.Settings.Remove(newKey);
    }   
    // Add an Application Setting.
    config.AppSettings.Settings.Add(newKey,newValue);  
    // Save the changes in App.config file.
    config.Save(ConfigurationSaveMode .Modified);
    // Force a reload of a changed section.
    ConfigurationManager .RefreshSection("appSettings" );

}

C#读写app.config中的数据

读语句:

 
                   
String str = ConfigurationManager.AppSettings["DemoKey"];

 写语句:

 
                   
Configuration cfa = 
  ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
cfa.AppSettings.Settings["DemoKey"].Value = "DemoValue";
cfa.Save();

 配置文件内容格式:(app.config)

 
                   
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DemoKey" value="*" />
</appSettings>
</configuration>

 红笔标明的几个关键节是必须的

 
                   
System.Configuration.ConfigurationSettings.AppSettings["Key"];

   可是如今FrameWork2.0已经明确表示此属性已通过时。并建议改成ConfigurationManager 

或WebConfigurationManager。而且AppSettings属性是只读的,并不支持修改属性值.

  可是要想调用ConfigurationManager必需要先在工程里添加system.configuration.dll程序集的引用。

(在解决方案管理器中右键点击工程名称,在右键菜单中选择添加引用,.net TablePage下便可找到)
添加引用后能够用 String str = ConfigurationManager.AppSettings["Key"]来获取对应的值了。

  更新配置文件:

 
                   
Configuration cfa = ConfigurationManager.
    OpenExeConfiguration(ConfigurationUserLevel.None);
cfa.AppSettings.Settings.Add("key", "Name") || 
  cfa.AppSettings.Settings["BrowseDir"].Value = "name";

  等等...
  最后调用
  cfa.Save(); 
  当前的配置文件更新成功。

*****************************************************************************************************************

读写配置文件app.config 
  在.Net中提供了配置文件,让咱们能够很方面的处理配置信息,这个配置是XML格式的。并且.Net中已经提供了一些访问这个文件的功能。

1、读取配置信息
  下面是一个配置文件的具体内容:

 

 
                   
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ConnenctionString" value="*" />
<add key="TmpPath" value="C:\Temp" />
</appSettings>
</configuration>

 .net提供了能够直接访问<appsettings>(注意大小写)元素的方法,在这元素中有不少的子元素,这些子元素名称都是 “add”,有两个属性分别是“key”和“value”。通常状况下咱们能够将本身的配置信息写在这个区域中,经过下面的方式进行访问:

 

 

 
                   
string ConString=System.Configuration
    .ConfigurationSettings.AppSettings["ConnenctionString"];
在appsettings后面的是子元素的key属性的值,例如appsettings["connenctionstring"],咱们就是访 问<add key="ConnenctionString" value="*" />这个子元素,它的返回值就是“*”,即value属性的值。

2、设置配置信息

  若是配置信息是静态的,咱们能够手工配置,要注意格式。若是配置信息是动态的,就须要咱们写程序来实现。在.Net中没有写配置文件的功能,咱们可使用操做XML文件的方式来操做配置文件。下面就是一个写配置文件的例子。

 

 
                   
private void SaveConfig(string ConnenctionString)
{
  XmlDocument doc=new XmlDocument();
  //得到配置文件的全路径
  string strFileName=AppDomain.CurrentDomain.BaseDirectory.ToString()
                   +"Code.exe.config";
  doc.LOAd(strFileName);
  //找出名称为“add”的全部元素
  XmlNodeList nodes=doc.GetElementsByTagName("add");
  for(int i=0;i<nodes.Count;i++)
  {
    //得到将当前元素的key属性
    XmlAttribute att=nodes[i].Attributes["key"];
    //根据元素的第一个属性来判断当前的元素是否是目标元素
    if (att.Value=="ConnectionString") 
    {
      //对目标元素中的第二个属性赋值
      att=nodes[i].Attributes["value"];
      att.Value=ConnenctionString;
      break;
    }
  }
  //保存上面的修改
  doc.Save(strFileName);
}
读取并修改App.config文件
1. 向项目添加app.config文件:
右击项目名称,选择“添加”→“添加新建项”,在出现的“添加新项”对话框中,选择“添加应用程序配置文件”;若是项目之前没有配置文件,则默认的文件名称为“app.config”,单击“肯定”。出如今设计器视图中的app.config文件为:
<?xmlversion="1.0"encoding="utf-8" ?>
<configuration>
</configuration>
在项目进行编译后,在bin\Debuge文件下,将出现两个配置文件(以本项目为例),一个名为“JxcManagement.EXE.config”,另外一个名为“JxcManagement.vshost.exe.config”。第一个文件为项目实际使用的配置文件,在程序运行中所作的更改都将被保存于此;第二个文件为原代码“app.config”的同步文件,在程序运行中不会发生更改。
2.  connectionStrings配置节:
请注意:若是您的SQL版本为2005 Express版,则默认安装时SQL服务器实例名为localhost\SQLExpress,须更改如下实例中“Data Source=localhost;”一句为“Data Source=localhost\SQLExpress;”,在等于号的两边不要加上空格。
<!--数据库链接串-->
     <connectionStrings>
         <clear />
         <addname="conJxcBook"
              connectionString="Data Source=localhost;Initial Catalog=jxcbook;User                                   ID=sa;password=********"
              providerName="System.Data.SqlClient" />
     </connectionStrings>
3. appSettings配置节:
appSettings配置节为整个程序的配置,若是是对当前用户的配置,请使用userSettings配置节,其格式与如下配置书写要求同样。
<!--进销存管理系统初始化须要的参数-->
     <appSettings>
         <clear />
         <addkey="userName"value="" />
         <addkey="password"value="" />
         <addkey="Department"value="" />
         <addkey="returnValue"value="" />
         <addkey="pwdPattern"value="" />
         <addkey="userPattern"value="" />
</appSettings>
4.读取与更新app.config
对于app.config文件的读写,参照了网络文章: http://www.codeproject.com/csharp/ SystemConfiguration.asp标题为“Read/Write App.Config File with .NET 2.0”一文。
请注意:要使用如下的代码访问app.config文件,除添加引用System.Configuration外,还必须在项目添加对System.Configuration.dll的引用。
4.1 读取connectionStrings配置节
///<summary>
///依据链接串名字connectionName返回数据链接字符串
///</summary>
///<param name="connectionName"></param>
///<returns></returns>
private static string GetConnectionStringsConfig(string connectionName)
{
string connectionString =
        ConfigurationManager.ConnectionStrings[connectionName].ConnectionString.ToString();
    Console.WriteLine(connectionString);
    return connectionString;
}
4.2 更新connectionStrings配置节
///<summary>
///更新链接字符串
///</summary>
///<param name="newName">链接字符串名称</param>
///<param name="newConString">链接字符串内容</param>
///<param name="newProviderName">数据提供程序名称</param>
private static void UpdateConnectionStringsConfig(string newName,
    string newConString,
    string newProviderName)
{
    bool isModified = false;    //记录该链接串是否已经存在
    //若是要更改的链接串已经存在
    if (ConfigurationManager.ConnectionStrings[newName] != null)
    {
        isModified = true;
    }
    //新建一个链接字符串实例
    ConnectionStringSettings mySettings =
        new ConnectionStringSettings(newName, newConString, newProviderName);
    // 打开可执行的配置文件*.exe.config
    Configuration config =
        ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    // 若是链接串已存在,首先删除它
    if (isModified)
    {
        config.ConnectionStrings.ConnectionStrings.Remove(newName);
    }
    // 将新的链接串添加到配置文件中.
    config.ConnectionStrings.ConnectionStrings.Add(mySettings);
    // 保存对配置文件所做的更改
    config.Save(ConfigurationSaveMode.Modified);
    // 强制从新载入配置文件的ConnectionStrings配置节
    ConfigurationManager.RefreshSection("ConnectionStrings");
}
4.3 读取appStrings配置节
///<summary>
///返回*.exe.config文件中appSettings配置节的value项
///</summary>
///<param name="strKey"></param>
///<returns></returns>
private static string GetAppConfig(string strKey)
{
    foreach (string key in ConfigurationManager.AppSettings)
    {
        if (key == strKey)
        {
            return ConfigurationManager.AppSettings[strKey];
        }
    }
    return null;
}
4.4 更新connectionStrings配置节
///<summary>
///在*.exe.config文件中appSettings配置节增长一对键、值对
///</summary>
///<param name="newKey"></param>
///<param name="newValue"></param>
private static void UpdateAppConfig(string newKey, string newValue)
{
    bool isModified = false;   
    foreach (string key in ConfigurationManager.AppSettings)
    {
       if(key==newKey)
        {   
            isModified = true;
        }
    }
 
    // Open App.Config of executable
    Configuration config =
        ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    // You need to remove the old settings object before you can replace it
    if (isModified)
    {
        config.AppSettings.Settings.Remove(newKey);
    }   
    // Add an Application Setting.
    config.AppSettings.Settings.Add(newKey,newValue);  
    // Save the changes in App.config file.
    config.Save(ConfigurationSaveMode.Modified);
    // Force a reload of a changed section.
    ConfigurationManager.RefreshSection("appSettings");
}
url: http://greatverve.cnblogs.com/archive/2011/07/18/app-config.html
 
 

判断客户端是iOS仍是Android,判断是否是在微信浏览器打开

 
复制代码
bool flag = false;
            string agent = System.Web.HttpContext.Current.Request.UserAgent.ToLower();
            string[] keywords = { "iphone", "ipod", "ipad", "itouch" };

            foreach (string item in keywords)
            {
                if (agent.Contains(item))
                {
                    flag = true;
                    break;
                }
            }
            if (flag)
            {
                ViewBag.Phone = "iOS";
            }
            else {
                ViewBag.Phone = "Android";
            }
复制代码
if (agent.ToLower().Contains("micromessenger")
{
微信浏览器
}
相关文章
相关标签/搜索