[EntLib]微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(2)
来源:互联网 发布:淘宝刷流量会封店吗 编辑:程序博客网 时间:2024/05/29 06:38
[EntLib]微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(2)
在前一篇文章中,我简单的介绍了如何使用代码及配置文件来进行Unity容器配置,今天则继续介绍Unity容器的使用方法。
本篇文章将主要介绍:
1、注册对象之间的关系。
2、为已存在的对象注册关系。
3、Unity中Lifetime Managers介绍。
一、注册对象之间的关系
在上一篇文章中,已经简单介绍了如何使用Unity来注册对象与对象之间的关系,通过RegisterType方法来注册对象之间的关系。
首先来看下类关系图:
有2个接口类:IClass(班级接口)和ISubject(科目接口),其分别有2个实现类,现在首先要注册班级相关的对象关系,代码如下:
01
public
static
void
RegisterTypeCode()
02
{
03
//注册IClass与MyClass之间的关系
04
//此处为IClass的默认注册,每次调用Resolve<IClass>都会返回MyClass
05
container.RegisterType<IClass, MyClass>();
06
//如果再次进行默认注册,将会覆盖原来的注册
07
//container.RegisterType<IClass, YourClass>();
08
//注册IClass与YourClass之间的关系
09
//此处多了一个参数"your",表示一个命名注册,只有当调用Resolve<IClass>("your")时才会返回YourClass
10
//这种注册方法解决了当需要为某个对象注册多个对应关系时的冲突
11
container.RegisterType<IClass, YourClass>(
"your"
);
12
13
//获取具体的对象并调用
14
IClass myClass = container.Resolve<IClass>();
15
IClass yourClass = container.Resolve<IClass>(
"your"
);
16
myClass.ShowInfo();
17
yourClass.ShowInfo();
18
19
//ResolveAll<IClass>方法可以一次性获取与IClass有注册关系的非默认对象实例
20
//也就是已命名注册的对象,所以列表中只有一个对象YourClass
21
IEnumerable<IClass> classList = container.ResolveAll<IClass>();
22
foreach
(var item
in
classList)
23
{
24
item.ShowInfo();
25
}
26
}
这段代码展示了使用RegisterType方法来注册对象之间的关系,需要注意的是,在进行命名注册的时候,所提供的命名参数是大小写敏感的,所以输入“your”和“Your”表示的不是一个对象注册。同时这边还展示了如何通过Resolve方法来获取所需的对象,以及使用ResolveAll方法来获取与指定对象关联的所有对象列表。
有关RegisterType方法的其他重载我这边就不详细介绍了,可以点此查看详细的重载方法介绍。
接下来看下如何使用配置文件来进行配置(和上一篇的例子类似),配置文件代码如下:
01
<
unity
xmlns
=
"http://schemas.microsoft.com/practices/2010/unity"
>
02
<
alias
alias
=
"IClass"
type
=
"UnityStudyConsole.IDemo.IClass, UnityStudyConsole"
/>
03
<
alias
alias
=
"MyClass"
type
=
"UnityStudyConsole.Demo.MyClass, UnityStudyConsole"
/>
04
<
alias
alias
=
"YourClass"
type
=
"UnityStudyConsole.Demo.YourClass, UnityStudyConsole"
/>
05
06
<
container
name
=
"First"
>
07
<
register
type
=
"IClass"
mapTo
=
"MyClass"
/>
08
<
register
type
=
"IClass"
mapTo
=
"YourClass"
name
=
"your"
/>
09
</
container
>
10
</
unity
>
11
<
span
style
=
"font-family: Verdana;"
>读取并调用代码如下:</
span
>
01
public
static
void
RegisterTypeCodeConfiguration()
02
{
03
//获取指定名称的配置节
04
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection(
"unity"
);
05
06
container.LoadConfiguration(section,
"First"
);
07
08
IClass classInfo = container.Resolve<IClass>();
09
classInfo.ShowInfo();
10
}
二、为已存在的对象注册关系
在日常开发的过程中我们有时候会自己创建好一个对象,但是你又想对这个已经创建好的对象的生命周期进行管理,这个时候你可以使用Unity提供的RegisterInstance方法,代码示例如下:
01
public
static
void
RegisterInstance()
02
{
03
IClass myClass =
new
MyClass();
04
IClass yourClass =
new
YourClass();
05
//为myClass实例注册默认实例
06
container.RegisterInstance<IClass>(myClass);
07
//为yourClass实例注册命名实例,同RegisterType
08
container.RegisterInstance<IClass>(
"yourInstance"
, yourClass);
09
10
container.Resolve<IClass>().ShowInfo();
11
12
container.Resolve<IClass>(
"yourInstance"
).ShowInfo();
13
}
这段代码很简单,就是通过RegisterInstance方法为已存在的对象进行注册,这样可以通过UnityContainer来管理这些对象实例的生命周期。
需要注意的是,使用RegisterInstance来将已存在的实例注册到UnityContainer中,默认情况下其实用的是ContainerControlledLifetimeManager,这个生命周期是由UnityContainer来进行管理,UnityContainer会维护一个对象实例的强引用,当你将已存在的实例注册到UnityContainer后,每次通过Resolve方法获取对象都是同一对象,也就是单件实例(singleton instance),具体有关生命周期相关信息在下面进行介绍。
由于RegisterInstance是对已存在的实例进行注册,所以无法通过配置文件来进行配置。
有关RegisterInstance方法的其他重载我这边就不详细介绍了,可以点此查看详细的重载方法介绍。
三、Unity中Lifetime Managers介绍
我们在系统中引入Unity主要就是想通过Unity来解除对象之间的依赖关系,方便我们根据配置调用到所需的对象,而Unity默认情况下会自动帮我们维护好这些对象的生命周期,可能Unity自动维护的生命周期并不是我们想要的,我们想要根据具体的需求来更改这些对象的生命周期,下面我就介绍一下Unity中内置的生命周期管理器。
1、TransientLifetimeManager,瞬态生命周期,默认情况下,在使用RegisterType进行对象关系注册时如果没有指定生命周期管理器则默认使用这个生命周期管理器,这个生命周期管理器就如同其名字一样,当使用这种管理器的时候,每次通过Resolve或ResolveAll调用对象的时候都会重新创建一个新的对象。
需要注意的是,使用RegisterInstance对已存在的对象进行关系注册的时候无法指定这个生命周期,否则会报异常。
代码如下:
01
public
static
void
TransientLifetimeManagerCode()
02
{
03
//以下2种注册效果是一样的
04
container.RegisterType<IClass, MyClass>();
05
container.RegisterType<IClass, MyClass>(
new
TransientLifetimeManager());
06
Console.WriteLine(
"-------TransientLifetimeManager Begin------"
);
07
Console.WriteLine(
"第一次调用RegisterType注册的对象HashCode:"
+
08
container.Resolve<IClass>().GetHashCode());
09
Console.WriteLine(
"第二次调用RegisterType注册的对象HashCode:"
+
10
container.Resolve<IClass>().GetHashCode());
11
Console.WriteLine(
"-------TransientLifetimeManager End------"
);
12
}
配置文件如下:
1
<
register
type
=
"IClass"
mapTo
=
"MyClass"
>
2
<
lifetime
type
=
"transient"
/>
3
<!--<lifetime type="SessionLifetimeManager"
4
value="Session#1" typeConverter="SessionLifetimeConverter" />-->
5
</
register
>
如果想在配置文件中在在注册关系的时候更改一个生命周期管理器只需在<register>配置节下新增<lifetime>既可(如果不新增则默认使用TransientLifetimeManager)。
其中<lifetime>有3个参数:
1)type,生命期周期管理器的类型,这边可以选择Unity内置的,也可以使用自定义的,其中内置的生命周期管理器会有智能提示。
2)typeConverter,生命周期管理器转换类,用户自定义一个生命周期管理器的时候所创建一个转换器。
3)value,初始化生命周期管理器的值。
配置文件读取代码:
01
public
static
void
TransientLifetimeManagerConfiguration()
02
{
03
//获取指定名称的配置节
04
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection(
"unity"
);
05
container.LoadConfiguration(section,
"First"
);
06
07
Console.WriteLine(
"-------TransientLifetimeManager Begin------"
);
08
Console.WriteLine(
"第一次调用RegisterType注册的对象HashCode:"
+
09
container.Resolve<IClass>(
"transient"
).GetHashCode());
10
Console.WriteLine(
"第二次调用RegisterType注册的对象HashCode:"
+
11
container.Resolve<IClass>(
"transient"
).GetHashCode());
12
Console.WriteLine(
"-------TransientLifetimeManager End------"
);
13
}
效果图如下,可以看出每次产生的对象都是不同的:
2、ContainerControlledLifetimeManager,容器控制生命周期管理,这个生命周期管理器是RegisterInstance默认使用的生命周期管理器,也就是单件实例,UnityContainer会维护一个对象实例的强引用,每次调用的时候都会返回同一对象,示例代码如下:
01
public
static
void
ContainerControlledLifetimeManagerCode()
02
{
03
IClass myClass =
new
MyClass();
04
//以下2种注册效果是一样的
05
container.RegisterInstance<IClass>(
"ccl"
, myClass);
06
container.RegisterInstance<IClass>(
"ccl"
, myClass,
new
ContainerControlledLifetimeManager());
07
08
container.RegisterType<IClass, MyClass>(
new
ContainerControlledLifetimeManager());
09
Console.WriteLine(
"-------ContainerControlledLifetimeManager Begin------"
);
10
Console.WriteLine(
"第一次调用RegisterType注册的对象HashCode:"
+
11
container.Resolve<IClass>().GetHashCode());
12
Console.WriteLine(
"第二次调用RegisterType注册的对象HashCode:"
+
13
container.Resolve<IClass>().GetHashCode());
14
Console.WriteLine(
"第一次调用RegisterInstance注册的对象HashCode:"
+
15
container.Resolve<IClass>(
"ccl"
).GetHashCode());
16
Console.WriteLine(
"第二次调用RegisterInstance注册的对象HashCode:"
+
17
container.Resolve<IClass>(
"ccl"
).GetHashCode());
18
Console.WriteLine(
"-------ContainerControlledLifetimeManager End------"
);
19
}
配置文件如下:
1
<
register
type
=
"IClass"
mapTo
=
"MyClass"
name
=
"ccl"
>
2
<
lifetime
type
=
"singleton"
/>
3
</
register
>
效果图如下,可以看出每次获取的对象都是同一对象:
3、HierarchicalLifetimeManager,分层生命周期管理器,这个管理器类似于ContainerControlledLifetimeManager,也是由UnityContainer来管理,也就是单件实例。不过与ContainerControlledLifetimeManager不同的是,这个生命周期管理器是分层的,因为Unity的容器时可以嵌套的,所以这个生命周期管理器就是针对这种情况,当使用了这种生命周期管理器,父容器和子容器所维护的对象的生命周期是由各自的容器来管理,代码如下(RegisterInstance情况也类似,这边就不展示了):
01
public
static
void
HierarchicalLifetimeManagerCode()
02
{
03
container.RegisterType<IClass, MyClass>(
new
HierarchicalLifetimeManager());
04
//创建子容器
05
var childContainer = container.CreateChildContainer();
06
childContainer.RegisterType<IClass, MyClass>(
new
HierarchicalLifetimeManager());
07
08
Console.WriteLine(
"-------ContainerControlledLifetimeManager Begin------"
);
09
Console.WriteLine(
"第一次调用父容器注册的对象HashCode:"
+
10
container.Resolve<IClass>().GetHashCode());
11
Console.WriteLine(
"第二次调用父容器注册的对象HashCode:"
+
12
container.Resolve<IClass>().GetHashCode());
13
Console.WriteLine(
"第一次调用子容器注册的对象HashCode:"
+
14
childContainer.Resolve<IClass>().GetHashCode());
15
Console.WriteLine(
"第二次调用子容器注册的对象HashCode:"
+
16
childContainer.Resolve<IClass>().GetHashCode());
17
Console.WriteLine(
"-------ContainerControlledLifetimeManager End------"
);
18
}
由于配置文件不能配置这种层级效果,所以配置这种生命周期时只需要更改下生命周期名称:
1
<
register
type
=
"IClass"
mapTo
=
"MyClass"
name
=
"hl"
>
2
<
lifetime
type
=
"hierarchical"
/>
3
</
register
>
具体的效果图如下,可以看出父级和子级维护不同对象实例:
这边需要提一下的就是,Unity这种分级容器的好处就在于我们可以对于有不同生命周期的对象放在不同的容器中,如果一个子容器被释放,不会影响到其它子容器中的对象,但是如果根节点处父容器释放后,所有的子容器都将被释放。
4、PerResolveLifetimeManager,这个生命周期是为了解决循环引用而重复引用的生命周期,先看一下微软官方给出的实例:
01
public
interface
IPresenter
02
{ }
03
04
public
class
MockPresenter : IPresenter
05
{
06
public
IView View {
get
;
set
; }
07
08
public
MockPresenter(IView view)
09
{
10
View = view;
11
}
12
}
13
14
public
interface
IView
15
{
16
IPresenter Presenter {
get
;
set
; }
17
}
18
19
public
class
View : IView
20
{
21
[Dependency]
22
public
IPresenter Presenter {
get
;
set
; }
23
}
从这个例子中可以看出,有2个接口IPresenter和IView,还有2个类MockPresenter和View分别实现这2个接口,同时这2个类中都包含了对另外一个类的对象属性,这个就是一个循环引用,而对应的这个生命周期管理就是针对这种情况而新增的,其类似于TransientLifetimeManager,但是其不同在于,如果应用了这种生命周期管理器,则在第一调用的时候会创建一个新的对象,而再次通过循环引用访问到的时候就会返回先前创建的对象实例(单件实例),代码如下:
01
public
static
void
PerResolveLifetimeManagerCode()
02
{
03
var container =
new
UnityContainer()
04
.RegisterType<IPresenter, MockPresenter>()
05
.RegisterType<IView, View>(
new
PerResolveLifetimeManager());
06
07
var view = container.Resolve<IView>();
08
var tempPresenter = container.Resolve<IPresenter>();
09
var realPresenter = (MockPresenter)view.Presenter;
10
11
Console.WriteLine(
"-------PerResolveLifetimeManager Begin------"
);
12
Console.WriteLine(
"使用了PerResolveLifetimeManager的对象 Begin"
);
13
Console.WriteLine(
"通过Resolve方法获取的View对象:"
+
14
view.GetHashCode());
15
Console.WriteLine(
"View对象中的Presenter对象所包含的View对象:"
+
16
realPresenter.View.GetHashCode());
17
Console.WriteLine(
"使用了PerResolveLifetimeManager的对象 End"
);
18
Console.WriteLine(
""
);
19
Console.WriteLine(
"未使用PerResolveLifetimeManager的对象 Begin"
);
20
Console.WriteLine(
"View对象中的Presenter对象:"
+
21
realPresenter.GetHashCode());
22
Console.WriteLine(
"通过Resolve方法获取的View对象:"
+
23
tempPresenter.GetHashCode());
24
Console.WriteLine(
"未使用PerResolveLifetimeManager的对象 End"
);
25
Console.WriteLine(
"-------PerResolveLifetimeManager Begin------"
);
26
}
从代码中可以看出,在注册对象的时候,仅对IView和View应用了PerResolveLifetimeManager,所以第二次访问View对象会返回同一实例。
具体配置文件如下,有关构造函数注入和属性注入的内容在下一篇文章中进行介绍:
01
<
alias
alias
=
"IPresenter"
type
=
"UnityStudyConsole.IPresenter, UnityStudyConsole"
/>
02
<
alias
alias
=
"IView"
type
=
"UnityStudyConsole.IView, UnityStudyConsole"
/>
03
<
alias
alias
=
"MockPresenter"
type
=
"UnityStudyConsole.MockPresenter, UnityStudyConsole"
/>
04
<
alias
alias
=
"View"
type
=
"UnityStudyConsole.View, UnityStudyConsole"
/>
05
<
container
name
=
"Second"
>
06
<
register
type
=
"IPresenter"
mapTo
=
"MockPresenter"
>
07
<
constructor
>
08
<
param
name
=
"view"
type
=
"IView"
>
09
</
param
>
10
</
constructor
>
11
</
register
>
12
<
register
type
=
"IView"
mapTo
=
"View"
>
13
<
lifetime
type
=
"perresolve"
/>
14
<
property
name
=
"Presenter"
dependencyType
=
"IPresenter"
></
property
>
15
</
register
>
16
</
container
>
读取配置文件代码类似于前面其他配置文件读取代码,这里就不展示,具体请看示例代码。
具体的效果图如下:
可以看出2次调用View对象的HashCode都是一样的,而Presenter对象的HashCode不同。
5、PerThreadLifetimeManager,每线程生命周期管理器,就是保证每个线程返回同一实例,具体代码如下:
01
public
static
void
PerThreadLifetimeManagerCode()
02
{
03
container.RegisterType<IClass, MyClass>(
new
PerThreadLifetimeManager());
04
var thread =
new
Thread(
new
ParameterizedThreadStart(Thread1));
05
Console.WriteLine(
"-------PerResolveLifetimeManager Begin------"
);
06
Console.WriteLine(
"默认线程 Begin"
);
07
Console.WriteLine(
"第一调用:"
+
08
container.Resolve<IClass>().GetHashCode());
09
Console.WriteLine(
"第二调用:"
+
10
container.Resolve<IClass>().GetHashCode());
11
Console.WriteLine(
"默认线程 End"
);
12
thread.Start(container);
13
}
14
15
public
static
void
Thread1(
object
obj)
16
{
17
var tmpContainer = obj
as
UnityContainer;
18
Console.WriteLine(
"新建线程 Begin"
);
19
Console.WriteLine(
"第一调用:"
+
20
tmpContainer.Resolve<IClass>().GetHashCode());
21
Console.WriteLine(
"第二调用:"
+
22
tmpContainer.Resolve<IClass>().GetHashCode());
23
Console.WriteLine(
"新建线程 End"
);
Console.WriteLine("-------PerResolveLifetimeManager End------"); }
有关配置相关的代码与前面的生命周期管理器差不多,这边就不贴代码了,请看示例代码。
具体效果图如下:
同时需要注意的是,一般来说不建议在使用RegisterInstance对已存在的对象注册关系时使用PerThreadLifetimeManager,因为此时的对象已经在一个线程内创建了,如果再使用这个生命周期管理器,将无法保证其正确调用。
6、ExternallyControlledLifetimeManager,外部控制生命周期管理器,这个生命周期管理允许你使用RegisterType和RegisterInstance来注册对象之间的关系,但是其只会对对象保留一个弱引用,其生命周期交由外部控制,也就是意味着你可以将这个对象缓存或者销毁而不用在意UnityContainer,而当其他地方没有强引用这个对象时,其会被GC给销毁掉。
在默认情况下,使用这个生命周期管理器,每次调用Resolve都会返回同一对象(单件实例),如果被GC回收后再次调用Resolve方法将会重新创建新的对象,示例代码如下:
01
public
static
void
ExternallyControlledLifetimeManagerCode()
02
{
03
container.RegisterType<IClass, MyClass>(
new
ExternallyControlledLifetimeManager());
04
var myClass1 = container.Resolve<IClass>();
05
var myClass2 = container.Resolve<IClass>();
06
Console.WriteLine(
"-------ExternallyControlledLifetimeManager Begin------"
);
07
Console.WriteLine(
"第一次调用:"
+
08
myClass1.GetHashCode());
09
Console.WriteLine(
"第二次调用:"
+
10
myClass2.GetHashCode());
11
myClass1 = myClass2 =
null
;
12
GC.Collect();
13
Console.WriteLine(
"****GC回收过后****"
);
14
Console.WriteLine(
"第一次调用:"
+
15
container.Resolve<IClass>().GetHashCode());
16
Console.WriteLine(
"第二次调用:"
+
17
container.Resolve<IClass>().GetHashCode());
18
19
Console.WriteLine(
"-------ExternallyControlledLifetimeManager End------"
);
20
}
有关配置相关的代码与前面的生命周期管理器差不多,这边就不贴代码了,请看示例代码。
效果图如下:
以上就是本文的全部内容了,主要介绍了使用UnityContainer来注册对象之间的关系、注册已存在的对象之间的关系和Unity内置的生命周期管理器。
示例代码下载:点我下载
(注意:本文示例代码是基于VS2010+Unity2.0,所以请使用VS2010打开,如果没有安装VS2010,请将相关代码复制到相应的VS中运行既可)
微软企业库5.0 学习之路系列文章索引:
第一步、基本入门
第二步、使用VS2010+Data Access模块建立多数据库项目
第三步、为项目加上异常处理(采用自定义扩展方式记录到数据库中)
第四步、使用缓存提高网站的性能(EntLib Caching)
第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法——上篇
第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法——中篇
第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法——下篇
第六步、使用Validation模块进行服务器端数据验证
第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—上篇
第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—下篇
第八步、使用Configuration Setting模块等多种方式分类管理企业库配置信息
第九步、使用PolicyInjection模块进行AOP—PART1——基本使用介绍
第九步、使用PolicyInjection模块进行AOP—PART2——自定义Matching Rule
第九步、使用PolicyInjection模块进行AOP—PART3——内置Call Handler介绍
第九步、使用PolicyInjection模块进行AOP—PART4——建立自定义Call Handler实现用户操作日志记录
第十步、使用Unity解耦你的系统—PART1——为什么要使用Unity?
第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(1)
第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(2)
第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(3)
第十步、使用Unity解耦你的系统—PART3——依赖注入
第十步、使用Unity解耦你的系统—PART4——Unity&PIAB
扩展学习:
扩展学习篇、库中的依赖关系注入(重构 Microsoft Enterprise Library)[转]
出处:http://kyo-yo.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
- [EntLib]微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(2)
- [EntLib]微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(1)
- [EntLib]微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(3)
- [EntLib]微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART1——为什么要使用Unity?
- 微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART1——为什么要使用Unity?
- 微软企业库5.0 学习之路——第四步、使用缓存提高网站的性能(EntLib Caching)
- [EntLib]微软企业库5.0 学习之路——第五步、介绍EntLib.Validation模块信息、验证器的实现层级及内置的各种验证器的使用方法
- [EntLib]微软企业库5.0 学习之路——第一步、基本入门
- 微软企业库5.0 学习之路——基本入门
- 微软企业库5.0 学习之路——UnityPIAB 通过配置实现AOP
- 微软企业库Unity学习笔记(一)
- 微软企业库4.1学习笔记(三)企业库迁移和并行使用,以及企业库的扩展
- 【Unity Shaders】使用CgInclude让你的Shader模块化——Unity内置的CgInclude文件
- 【Unity学习笔记】——使用unity自带寻路系统进行寻路
- Unity学习之全景球的制作Part2
- 微软企业库5.0学习笔记(五)引用企业库程序集及企业库的依赖
- 微软企业库5.0学习笔记(五)引用企业库程序集及企业库的依赖
- 微软的Unity使用
- Ajax请求小结
- 操作符
- MultiByteToWideChar和WideCharToMultiByte用法详解
- sort排序大全
- JOhn学英语-2011-3-30
- [EntLib]微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(2)
- 【转贴】vector vs deque
- mybatis3.0.2整合spring3.0
- 新的目标
- 重现TortoiseSVN客户端的用户名密码验证框--转载
- 在LInux上安装:mysql C connector :How to install MySQL Connector/C on Mac OS X?
- 移位操作的妙用
- java常见错误以及可能原因
- Reapter入门1