[.NET领域驱动设计实战系列]专题九:DDD案例:网上书店AOP和站点地图的实现
一、引言
在前面一专题介绍到,要让缓存生效还需要实现对AOP(面向切面编程)的支持。所以本专题将介绍了网上书店案例中AOP的实现。关于AOP的概念,大家可以参考文章:http://www.cnblogs.com/jin-yuan/p/3811077.html。这里我简单介绍下AOP:AOP可以理解为对方法进行截获,这样就可以在方法调用前或调用后插入需要的逻辑。例如可以在方法调用前,加入缓存查找逻辑等。这里缓存查找逻辑就在方法调用前被执行。通过对AOP的支持,每个方法就可以分为3部分了,方法调用前逻辑->具体需要调用的方法->方法调用后的逻辑。也就是在方法调用的时候“切了一刀”。
二、网上书店AOP的实现
你可以从零开始去实现AOP,但是目前已经存在很多AOP框架了,所以在本案例中将直接通过Unity的AOP框架(Unity.Interception)来实现网上书店对AOP的支持。通常AOP的实现放在基础设施层进行实现,因为可能其他所有层都需要加入对AOP的支持。本案例中将对两个方面的AOP进行实现,一个是方法调用前缓存的记录或查找,另一个是方法调用后异常信息的记录。在实现具体代码之前,我们需要在基础设施层通过Nuget来引入Unity.Interception包。添加成功之后,我们需要定义两个类分别去实现AOP框架中IInterceptionBehavior接口。由于本案例中需要对缓存和异常日志功能进行AOP实现,自然就需要定义CachingBehavior和ExceptionLoggingBehavior两个类去实现IInterceptionBehavior接口。首先让我们看看CachingBehavior类的实现,具体实现代码如下所示:
// 缓存AOP的实现
public class CachingBehavior : IInterceptionBehavior
{
private readonly ICacheProvider _cacheProvider; public CachingBehavior()
{
_cacheProvider = ServiceLocator.Instance.GetService<ICacheProvider>();
} // 生成缓存值的键值
private string GetValueKey(CacheAttribute cachingAttribute, IMethodInvocation input)
{
switch (cachingAttribute.Method)
{
// 如果是Remove,则不存在特定值键名,所有的以该方法名称相关的缓存都需要清除
case CachingMethod.Remove:
return null;
// 如果是Get或者Update,则需要产生一个针对特定参数值的键名
case CachingMethod.Get:
case CachingMethod.Update:
if (input.Arguments != null &&
input.Arguments.Count > )
{
var sb = new StringBuilder();
for (var i = ; i < input.Arguments.Count; i++)
{
sb.Append(input.Arguments[i]);
if (i != input.Arguments.Count - )
sb.Append("_");
} return sb.ToString();
}
else
return "NULL";
default:
throw new InvalidOperationException("无效的缓存方式。");
}
} #region IInterceptionBehavior Members
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
// 获得被拦截的方法
var method = input.MethodBase;
var key = method.Name; // 获得拦截的方法名
// 如果拦截的方法定义了Cache属性,说明需要对该方法的结果需要进行缓存
if (!method.IsDefined(typeof (CacheAttribute), false))
return getNext().Invoke(input, getNext); var cachingAttribute = (CacheAttribute)method.GetCustomAttributes(typeof (CacheAttribute), false)[];
var valueKey = GetValueKey(cachingAttribute, input);
switch (cachingAttribute.Method)
{
case CachingMethod.Get:
try
{
// 如果缓存中存在该键值的缓存,则直接返回缓存中的结果退出
if (_cacheProvider.Exists(key, valueKey))
{
var value = _cacheProvider.Get(key, valueKey);
var arguments = new object[input.Arguments.Count];
input.Arguments.CopyTo(arguments, );
return new VirtualMethodReturn(input, value, arguments);
}
else // 否则先调用方法,再把返回结果进行缓存
{
var methodReturn = getNext().Invoke(input, getNext);
_cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);
return methodReturn;
}
}
catch (Exception ex)
{
return new VirtualMethodReturn(input, ex);
}
case CachingMethod.Update:
try
{
var methodReturn = getNext().Invoke(input, getNext);
if (_cacheProvider.Exists(key))
{
if (cachingAttribute.IsForce)
{
_cacheProvider.Remove(key);
_cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);
}
else
_cacheProvider.Update(key, valueKey, methodReturn);
}
else
_cacheProvider.Add(key, valueKey, methodReturn.ReturnValue); return methodReturn;
}
catch (Exception ex)
{
return new VirtualMethodReturn(input, ex);
}
case CachingMethod.Remove:
try
{
var removeKeys = cachingAttribute.CorrespondingMethodNames;
foreach (var removeKey in removeKeys)
{
if (_cacheProvider.Exists(removeKey))
_cacheProvider.Remove(removeKey);
}
// 执行具体截获的方法
var methodReturn = getNext().Invoke(input, getNext);
return methodReturn;
}
catch (Exception ex)
{
return new VirtualMethodReturn(input, ex);
}
default: break;
} return getNext().Invoke(input, getNext);
} public bool WillExecute
{
get { return true; }
}
#endregion
}
从上面代码可以看出,通过Unity.Interception框架来实现AOP变得非常简单了,我们只需要实现IInterceptionBehavior接口中的Invoke方法和WillExecute属性即可。并且从上面代码可以看出,AOP的支持最核心代码实现在于Invoke方法的实现。既然我们需要在方法调用前查找缓存,如果缓存不存在再调用方法从数据库中进行查找,如果存在则直接从缓存中进行读取数据即可。自然需要在 getNext().Invoke(input, getNext)代码执行前进缓存进行查找,然而上面CachingBehavior类正式这样实现的。
介绍完缓存功能AOP的实现之后,下面具体看看异常日志的AOP实现。具体实现代码如下所示:
// 用于异常日志记录的拦截行为
public class ExceptionLoggingBehavior :IInterceptionBehavior
{
/// <summary>
/// 需要拦截的对象类型的接口
/// </summary>
/// <returns></returns>
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
} /// <summary>
/// 通过该方法来拦截调用并执行所需要的拦截行为
/// </summary>
/// <param name="input">调用拦截目标时的输入信息</param>
/// <param name="getNext">通过行为链来获取下一个拦截行为的委托</param>
/// <returns>从拦截目标获得的返回信息</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
// 执行目标方法
var methodReturn = getNext().Invoke(input, getNext);
// 方法执行后的处理
if (methodReturn.Exception != null)
{
Utils.Log(methodReturn.Exception);
} return methodReturn;
} // 表示当拦截行为被调用时,是否需要执行某些操作
public bool WillExecute
{
get { return true; }
}
}
异常日志功能的AOP实现与缓存功能的AOP实现类似,只是一个需要在方法执行前注入,而一个是在方法执行后进行注入罢了,其实现原理都是在截获的方法前后进行。方法截获功能AOP框架已经帮我们实现了。
到此,我们网上书店AOP的实现就已经完成了,但要正式生效还需要通过配置文件把AOP的实现注入到需要截获的方法当中去,这样执行这些方法才会执行注入的行为。对应的配置文件如下标红部分所示:
<!--Unity的配置信息-->
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
<container>
<extension type="Interception" /> <!--Cache Provider-->
<register type="OnlineStore.Infrastructure.Caching.ICacheProvider, OnlineStore.Infrastructure" mapTo="OnlineStore.Infrastructure.Caching.EntLibCacheProvider, OnlineStore.Infrastructure" /> <!--仓储接口的注册-->
<register type="OnlineStore.Domain.Repositories.IRepositoryContext, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.EntityFrameworkRepositoryContext, OnlineStore.Repositories">
<lifetime type="singleton" />
</register>
<register type="OnlineStore.Domain.Repositories.IProductRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ProductRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.ICategoryRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.CategoryRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IProductCategorizationRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ProductCategorizationRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IUserRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IShoppingCartRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ShoppingCartRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IShoppingCartItemRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ShoppingCartItemRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IOrderRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.OrderRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IUserRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IUserRoleRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRoleRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IRoleRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.RoleRepository, OnlineStore.Repositories" /> <!--Domain Services-->
<register type="OnlineStore.Domain.Services.IDomainService, OnlineStore.Domain" mapTo="OnlineStore.Domain.Services.DomainService, OnlineStore.Domain" />
<!--应用服务的注册-->
<register type="OnlineStore.ServiceContracts.IProductService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.ProductServiceImp, OnlineStore.Application">
<!--注入AOP功能的实现-->
<interceptor type="InterfaceInterceptor" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
</register> <register type="OnlineStore.ServiceContracts.IOrderService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.OrderServiceImp, OnlineStore.Application">
<!--注入AOP功能的实现-->
<interceptor type="InterfaceInterceptor" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
</register>
<register type="OnlineStore.ServiceContracts.IUserService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.UserServiceImp, OnlineStore.Application">
<!--注入AOP功能的实现-->
<interceptor type="InterfaceInterceptor" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
</register> <!--Domain Event Handlers-->
<register type="OnlineStore.Domain.Events.IDomainEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Domain" mapTo="OnlineStore.Domain.Events.EventHandlers.OrderDispatchedEventHandler, OnlineStore.Domain" name="OrderDispatchedEventHandler" />
<register type="OnlineStore.Domain.Events.IDomainEventHandler`1[[OnlineStore.Domain.Events.OrderConfirmedEvent, OnlineStore.Domain]], OnlineStore.Domain" mapTo="OnlineStore.Domain.Events.EventHandlers.OrderConfirmedEventHandler, OnlineStore.Domain" name="OrderConfirmedEventHandler" /> <!--Event Handlers-->
<register name="orderSendEmailHandler" type="OnlineStore.Events.IEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Events" mapTo="OnlineStore.Events.Handlers.SendEmailHandler, OnlineStore.Events.Handlers" /> <!--Event Aggregator-->
<register type="OnlineStore.Events.IEventAggregator, OnlineStore.Events" mapTo="OnlineStore.Events.EventAggregator, OnlineStore.Events">
<constructor>
<param name="handlers">
<array>
<dependency name="orderSendEmailHandler" type="OnlineStore.Events.IEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Events" />
</array>
</param>
</constructor>
</register> <!--Event Bus-->
<!--<register type="OnlineStore.Events.Bus.IEventBus, OnlineStore.Events" mapTo="OnlineStore.Events.Bus.EventBus, OnlineStore.Events">
<lifetime type="singleton" />
</register>--> <!--注入MsmqEventBus-->
<register type="OnlineStore.Events.Bus.IEventBus, OnlineStore.Events"
mapTo="OnlineStore.Events.Bus.MsmqEventBus, OnlineStore.Events">
<lifetime type="singleton" />
<constructor>
<param name="path" value=".\Private$\OnlineStoreQueue" />
</constructor>
</register>
</container>
</unity>
<!--END: Unity-->
到此,网上书店案例中AOP的实现就完成了。通过上面的配置可以看出,客户端在调用应用服务方法前后会调用我们注入的行为,即缓存行为和异常日志行为。通过对AOP功能的支持,就不需要为每个需要进行缓存或需要异常日志行为的方法来重复写这些相同的逻辑了。从而避免了重复代码的重复实现,提高了代码的重用性和降低了模块之间的依赖性。
三、网上书店案例中站点地图的实现
在大部分网站中都实现了站点地图的功能,在Asp.net中,我们可以通过SiteMap模块来实现站点地图的功能,在Asp.net MVC也可以通过MvcSiteMapProvider第三方开源框架来实现站点地图。所以针对网上书店案例,站点地图的支持也是必不可少的。下面让我们具体看看站点地图在本案例中是如何去实现的呢?
在看实现代码之前,让我们先来理清下实现思路。
本案例中站点地图的实现,并没有借助MvcSiteMapProvider第三方框架来实现。其实现原理首先获得用户的路由请求,然后根据用户请求根据站点地图的配置获得对应的配置节点,接着根据站点地图的节点信息生成类似">首页>"这样带标签的字符串;如果获得的节点是配置文件中某个父节点的子节点,此时会通过递归的方式找到其父节点,然后递归地生成对应带标签的字符串,从而完成站点地图的功能。分析完实现思路之后,下面让我们再对照下具体的实现代码来加深理解。具体的实现代码如下所示:
public class MvcSiteMap
{
private static readonly MvcSiteMap _instance = new MvcSiteMap(); private static readonly XDocument Doc = XDocument.Load(HttpContext.Current.Server.MapPath(@"~/SiteMap.xml")); private UrlHelper _url = null; private string _currentUrl; public static MvcSiteMap Instance
{
get { return _instance;}
} private MvcSiteMap()
{
} public MvcHtmlString Navigator()
{
// 获得当前请求的路由信息
_url = new UrlHelper(HttpContext.Current.Request.RequestContext);
var routeUrl = _url.RouteUrl(HttpContext.Current.Request.RequestContext.RouteData.Values);
if (routeUrl != null)
_currentUrl = routeUrl.ToLower(); // 从配置的站点Xml文件中找到当前请求的Url相同的节点
var c = FindNode(Doc.Root);
var temp = GetPath(c); return MvcHtmlString.Create(BuildPathString(temp));
} // 从SitMap配置文件中找到当前请求匹配的节点
private XElement FindNode(XElement node)
{
// 如果xml节点对应的url是否与当前请求的节点相同,如果相同则直接返回xml对应的节点
// 如果不同开始递归子节点
return IsUrlEqual(node) == true ? node : RecursiveNode(node);
} // 判断xml节点对应的url是否与当前请求的url一样
private bool IsUrlEqual(XElement c)
{
var a = GetNodeUrl(c).ToLower();
return a == _currentUrl;
} // 递归Xml节点
private XElement RecursiveNode(XElement node)
{
foreach (var c in node.Elements())
{
if (IsUrlEqual(c) == true)
{
return c;
}
else
{
var x = RecursiveNode(c);
if (x != null)
{
return x;
}
}
} return null;
} // 获得xml节点对应的请求url
private string GetNodeUrl(XElement c)
{
return _url.Action(c.Attribute("action").Value, c.Attribute("controller").Value,
new {area = c.Attribute("area").Value});
} // 根据对应请求url对应的Xml节点获得其在Xml中的路径,即获得其父节点有什么
// SiteMap.xml 中节点的父子节点一定要配置对
private Stack<XElement> GetPath(XElement c)
{
var temp = new Stack<XElement>();
while (c != null)
{
temp.Push(c);
c = c.Parent;
}
return temp;
} // 根据节点的路径来拼接带标签的字符串
private string BuildPathString(Stack<XElement> m)
{
var sb = new StringBuilder();
var tc = new TagBuilder("span");
tc.SetInnerText(">");
var sp = tc.ToString();
var count = m.Count;
for (var x = ; x <= count; x++)
{
var c = m.Pop();
TagBuilder tb;
if (x == count)
{
tb = new TagBuilder("span");
}
else
{
tb = new TagBuilder("a");
tb.MergeAttribute("href", GetNodeUrl(c));
} tb.SetInnerText(c.Attribute("title").Value);
sb.Append(tb);
sb.Append(sp);
} return sb.ToString();
}
}
对应的站点地图配置信息如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<node title="首页" area="" action="Index" controller="Home">
<node title="我的" area="UserCenter" action="Manage" controller="Account">
<node title="订单" area="" action="Orders" controller="Home" />
<node title="账户" area="" action="Manage" controller="Account" />
<node title="购物车" area="" action="ShoppingCart" controller="Home" />
</node>
<node title="关于" area="AboutCenter" action="About" controller="Home" >
<node title="Online Store 项目" area="" action="About" controller="Home" />
<node title="联系方式" area="" action="Contact" controller="Home" />
</node>
</node>
实现完成之后,下面让我们具体看看本案例中站点地图的实现效果看看,具体运行效果如下图所示:
四、小结
到这里,本专题的内容就结束了。本专题主要借助Unity.Interception框架在网上书店中引入了AOP功能,并且最后简单介绍了站点地图的实现。在下一专题将对CQRS模式做一个全面的介绍。
本案例所有源码:https://github.com/lizhi5753186/OnlineStore_Second/
[.NET领域驱动设计实战系列]专题九:DDD案例:网上书店AOP和站点地图的实现的更多相关文章
- [.NET领域驱动设计实战系列]专题十一:.NET 领域驱动设计实战系列总结
一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核心复杂性应对之道.实现领域驱动设计 ...
- [.NET领域驱动设计实战系列]专题一:前期准备之EF CodeFirst
一.前言 从去年已经接触领域驱动设计(Domain-Driven Design)了,当时就想自己搭建一个DDD框架,所以当时看了很多DDD方面的书,例如领域驱动模式与实战,领域驱动设计:软件核心复杂性 ...
- [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店
一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这 ...
- [.NET领域驱动设计实战系列]专题十:DDD扩展内容:全面剖析CQRS模式实现
一.引言 前面介绍的所有专题都是基于经典的领域驱动实现的,然而,领域驱动除了经典的实现外,还可以基于CQRS模式来进行实现.本专题将全面剖析如何基于CQRS模式(Command Query Respo ...
- [.NET领域驱动设计实战系列]专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
一.引言 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作.此时客户会在自己的订单状态看到店家已经发货.从上面的业务逻辑可以看出,当用户 ...
- [.NET领域驱动设计实战系列]专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
一.引言 在上一专题中,商家发货和用户确认收货功能引入了消息队列来实现的,引入消息队列的好处可以保证消息的顺序处理,并且具有良好的可扩展性.但是上一专题消息队列是基于内存中队列对象来实现,这样实现有一 ...
- [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...
- [.NET领域驱动设计实战系列]专题六:DDD实践案例:网上书店订单功能的实现
一.引言 上一专题已经为网上书店实现了购物车的功能了,在这一专题中,将继续对网上书店案例进行完善,本专题将对网上书店订单功能的实现进行介绍,现在废话不多说了,让我们来一起看看订单功能是如何实现的吧. ...
- [.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)
一.前言 在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现.然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫. ...
随机推荐
- NC 单据保存时间过长,判断数据库锁表解决办法
SELECT s.sid, s.serial# FROM gv$locked_object l, dba_objects o, gv$session s WHERE l.object_id = o.o ...
- Flask备注三(Context)
Flask备注三(Context) Flask支持不同的应用场景下,对应不同的local context(本地上下文环境),用来提供当前环境下的资源.lcoal context和全局变量以及局部变量最 ...
- cmd运行的程序的工作目录
如图所示,cmd通过输入自己编写的程序的实际路径,或者将程序放在环境变量中然后在cmd中执行,用start执行,该程序运行时的工作目录都是cmd当前所在目录:在cmd中输入该程序的快捷方式执行该程序, ...
- python课程第四周重点记录
1.迭代器 names = iter(["alex","jack","rain"]) #声明列表的一个迭代器 names.__next__( ...
- css鼠标手型cursor中hand与pointer
css鼠标手型cursor中hand与pointer Example:CSS鼠标手型效果 <a href="#" style="cursor:hand"& ...
- Three ways to set specific DeviceFamily XAML Views in UWP
Three ways to set specific DeviceFamily XAML Views in UWP http://igrali.com/2015/08/02/three-ways-to ...
- 1341 - Aladdin and the Flying Carpet ---light oj (唯一分解定理+素数筛选)
http://lightoj.com/volume_showproblem.php?problem=1341 题目大意: 给你矩形的面积(矩形的边长都是正整数),让你求最小的边大于等于b的矩形的个数. ...
- 用Asp.net写自己的服务框架
阅读目录 开始 理解Asp.net管线 HttpHandler HttpModule 关于Content-Encoding的解释 选 HttpHandler 还是 HttpModule ? 看不见的性 ...
- 2016某知名互联网公司PHP面试题及答案
1 字符串"\r","\n","\t","\x20"分别代表什么 答案: "\r"代表的含义是: 在 ...
- Spring控制反转(IOC)和依赖注入(DI),再记不住就去出家!
每次看完spring的东西感觉都理解了,但是过了一段时间就忘,可能是不常用吧,也是没理解好,这次记下来. 拿ssh框架中的action,service,dao这三层举例: 控制反转:完成一个更新用户信 ...