??xml version="1.0" encoding="utf-8" standalone="yes"?>
ThreadLocal是什?br />
ThreadLocal是什么呢Q其实ThreadLocalq是一个线E的本地实现版本Q它q不是一个ThreadQ而是thread local variableQ线E局部变量)。也许把它命名ؓThreadLocalVar更加合适。线E局部变量(ThreadLocalQ其实的功用非常单,是为每一个用该变量的线E都提供一个变量值的副本Q是每一个线E都可以独立地改变自q副本Q而不会和其它U程的副本冲H。从U程的角度看Q就好像每一个线E都完全拥有该变量。线E局部变量ƈ不是Java的新发明Q在其它的一些语a~译器实玎ͼ如IBM XL FORTRANQ中Q它在语a的层ơ提供了直接的支持。因为Java中没有提供在语言层次的直接支持,而是提供了一个ThreadLocal的类来提供支持,所以,在Java中编写线E局部变量的代码相对比较W拙Q这也许是线E局部变量没有在Java中得到很好的普及的一个原因吧?br />
ThreadLocal的设?br />
首先看看ThreadLocal的接口:
Object get() ; // q回当前U程的线E局部变量副?protected Object initialValue(); // q回该线E局部变量的当前U程的初始?br /> void set(Object value); // 讄当前U程的线E局部变量副本的?br />
ThreadLocal?个方法,其中值得注意的是initialValue()Q该Ҏ是一个protected的方法,昄是ؓ了子c重写而特意实现的。该Ҏq回当前U程在该U程局部变量的初始|q个Ҏ是一个gq调用方法,在一个线E第1ơ调用get()或者set(Object)时才执行Qƈ且仅执行1ơ。ThreadLocal中的实实现直接q回一个nullQ?br />protected Object initialValue() { return null; }
ThreadLocal是如何做Cؓ每一个线E维护变量的副本的呢Q其实实现的思\很简单,在ThreadLocalcM有一个MapQ用于存储每一个线E的变量的副本。比如下面的CZ实现Q?br />public class ThreadLocal
{
private Map values = Collections.synchronizedMap(new HashMap());
public Object get()
{
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread))
{
o = initialValue();
values.put(curThread, o);
}
return o;
}
public void set(Object newValue)
{
values.put(Thread.currentThread(), newValue);
}
public Object initialValue()
{
return null;
}
}
当然Q这q不是一个工业强度的实现Q但JDK中的ThreadLocal的实现M思\也类g此?br />
ThreadLocal的?br />
如果希望U程局部变量初始化其它|那么需要自己实现ThreadLocal的子cdƈ重写该方法,通常使用一个内部匿名类对ThreadLocalq行子类化,比如下面的例子,SerialNumcMؓ每一个类分配一个序Ppublic class SerialNum
{
// The next serial number to be assigned
private static int nextSerialNum = 0;
private static ThreadLocal serialNum = new ThreadLocal()
{
protected synchronized Object initialValue()
{
return new Integer(nextSerialNum++);
}
};
public static int get()
{
return ((Integer) (serialNum.get())).intValue();
}
}
SerialNumcȝ使用非常地单,因ؓget()Ҏ是static的,所以在需要获取当前线E的序号Ӟ单地调用Q?br />int serial = SerialNum.get();
卛_?br />
在线E是zd的ƈ且ThreadLocal对象是可讉K的时Q该U程持有一个到该线E局部变量副本的隐含引用Q当该线E运行结束后Q该U程拥有的所以线E局部变量的副本都将失效Qƈ{待垃圾攉器收集?br />
ThreadLocal与其它同步机制的比较
ThreadLocal和其它同步机制相比有什么优势呢QThreadLocal和其它所有的同步机制都是Z解决多线E中的对同一变量的访问冲H,在普通的同步机制中,是通过对象加锁来实现多个线E对同一变量的安全访问的。这时该变量是多个线E共享的Q用这U同步机刉要很l致地分析在什么时候对变量q行dQ什么时候需要锁定某个对象,什么时候释放该对象的锁{等很多。所有这些都是因为多个线E共享了资源造成的。ThreadLocal׃另一个角度来解决多线E的q发讉KQThreadLocal会ؓ每一个线E维护一个和该线E绑定的变量的副本,从而隔M多个U程的数据,每一个线E都拥有自己的变量副本,从而也没有必要对该变量进行同步了。ThreadLocal提供了线E安全的׃n对象Q在~写多线E代码时Q可以把不安全的整个变量装qThreadLocalQ或者把该对象的特定于线E的状态封装进ThreadLocal?br />
׃ThreadLocal中可以持有Q何类型的对象Q所以用ThreadLocal get当前U程的值是需要进行强制类型{换。但随着新的Java版本Q?.5Q将模版的引入,新的支持模版参数的ThreadLocal<T>cd从中受益。也可以减少强制cd转换Qƈ一些错误检查提前到了编译期Q将一定程度地化ThreadLocal的用?br />
ȝ
当然ThreadLocalq不能替代同步机Ӟ两者面向的问题领域不同。同步机制是Z同步多个U程对相同资源的q发讉KQ是Z多个U程之间q行通信的有效方式;而ThreadLocal是隔d个线E的数据׃nQ从Ҏ上就不在多个U程之间׃n资源Q变量)Q这样当然不需要对多个U程q行同步了。所以,如果你需要进行多个线E之间进行通信Q则使用同步机制Q如果需要隔d个线E之间的׃n冲突Q可以用ThreadLocalQ这极大地化你的程序,使程序更加易诅R简z?br />
time.clear();
time.set(Calendar.YEAR,year);
time.set(Calendar.MONTH,i-1);//注意,Calendar对象默认一月ؓ0
int day=time.getActualMaximum(Calendar.DAY_OF_MONTH);//本月份的天数
2.Calendar和Date的{?br />(1) Calendar转化为Date
Calendar cal=Calendar.getInstance();
Date date=cal.getTime();
(2) Date转化为Calendar
Date date=new Date();
Calendar cal=Calendar.getInstance();
cal.setTime(date);
3.格式化输出日期时?br />Date date=new Date();
SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println(df.format(date));
4.计算一q中的第几星?br />(1)计算某一天是一q中的第几星?br />Calendar cal=Calendar.getInstance();
cal.set(Calendar.YEAR, 2006);
cal.set(Calendar.MONTH, 8);
cal.set(Calendar.DAY_OF_MONTH, 3);
int weekno=cal.get(Calendar.WEEK_OF_YEAR);
(2)计算一q中的第几星期是几号
SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
Calendar cal=Calendar.getInstance();
cal.set(Calendar.YEAR, 2006);
cal.set(Calendar.WEEK_OF_YEAR, 1);
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
System.out.println(df.format(cal.getTime()));
输出:
2006-01-02
5.add()和roll()的用?br />(1)add()Ҏ
SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
Calendar cal=Calendar.getInstance();
cal.set(Calendar.YEAR, 2006);
cal.set(Calendar.MONTH, 8);
cal.set(Calendar.DAY_OF_MONTH, 3);
cal.add(Calendar.DATE, -4);
Date date=cal.getTime();
System.out.println(df.format(date));
cal.add(Calendar.DATE, 4);
date=cal.getTime();
System.out.println(df.format(date));
输出Q?br /> 2006-08-30
2006-09-03
(2)rollҎ
cal.set(Calendar.YEAR, 2006);
cal.set(Calendar.MONTH, 8);
cal.set(Calendar.DAY_OF_MONTH, 3);
cal.roll(Calendar.DATE, -4);
date=cal.getTime();
System.out.println(df.format(date));
cal.roll(Calendar.DATE, 4);
date=cal.getTime();
System.out.println(df.format(date));
输出Q?br /> 2006-09-29
2006-09-03
可见Q?font color="#ff3300">roll()Ҏ在本月内循环Q一般用add()ҎQ?/font>
1。实例化对象Q可以用如下两种ҎQ?br />DecimalFormat df=(DecimalFormat)NumberFormat.getInstance();
DecimalFormat df1=(DecimalFormat) DecimalFormat.getInstance();
因ؓDecimalFormatl承自NumberFormat?br />2。设定小C?br />pȝ默认数位数?Q如Q?br /> DecimalFormat df=(DecimalFormat)NumberFormat.getInstance();
System.out.println(df.format(12.3456789));
输出Q?2.346
现在可以通过如下Ҏ把小Cؓ设ؓ两位Q?br /> df.setMaximumFractionDigits(2);
System.out.println(df.format(12.3456789));
则输ZؓQ?2.35
3。将数字转化为百分比输出Q有如下两种ҎQ?br />(1)
df.applyPattern("##.##%");
System.out.println(df.format(12.3456789));
System.out.println(df.format(1));
System.out.println(df.format(0.015));
输出分别为:1234.57% 100% 1.5%
(2)
df.setMaximumFractionDigits(2);
System.out.println(df.format(12.3456789*100)+"%");
System.out.println(df.format(1*100)+"%");
System.out.println(df.format(0.015*100)+"%");
输出分别为:
1,234.57% 100% 1.5%
4。设|分l大?br /> DecimalFormat df1=(DecimalFormat) DecimalFormat.getInstance();
df1.setGroupingSize(2);
System.out.println(df1.format(123456789));
输出Q?,23,45,67,89
q可以通过df1.setGroupingUsed(false);来禁用分l设|,如:
DecimalFormat df1=(DecimalFormat) DecimalFormat.getInstance();
df1.setGroupingSize(2);
df1.setGroupingUsed(false);
System.out.println(df1.format(123456789));
输出Q?23456789
5。设|小Cؓ必须??br /> DecimalFormat df2=(DecimalFormat) DecimalFormat.getInstance();
df2.applyPattern("0.00");
System.out.println(df2.format(1.2));
输出Q?.20
2003 q?8 ?02 ?/p>
作ؓZMVC模式的Web应用最l典框架QStruts已经正式推出?.1版本Q该版本在以往版本的基上,提供了许多激动h心的新功能。本文就带你走qStruts 1.1L入地了解q些功能?/blockquote>说明Q?/b>希望本文的读者能有一定的Struts使用基础?
Struts 是基于Model 2之上的,而Model 2是经典的MVCQ模型-视图Q控制器Q模型的Web应用变体Q这个改变主要是׃|络应用的特?-HTTP协议的无状态性引L。Model 2的目的和MVC一P也是利用控制器来分离模型和视图,辑ֈ一U层间松散耦合的效果,提高pȝ灉|性、复用性和可维护性。在多数情况下,你可以将 Model 2与MVC{同h?/p>
下图表示一个基于Java技术的典型|络应用Q从中可以看出Model 2中的各个部分是如何对应于Java中各U现有技术的?/p>
![]()
在利用Model 2之前Q我们是把所有的表示逻辑和业务逻辑都集中在一P比如大杂烩似的JSPQ,有时也称q种应用模式为Model 1QModel 1的主要缺点就是紧耦合Q复用性差以及l护成本高?/p>
![]()
![]()
![]()
![]()
回页?/b>
既然Struts 1.1是基于Model 2之上Q那它的底层机制也就是MVCQ下面是Struts 1.1中的MVC实现C意图:
![]()
图解说明Q其中不同颜色代表MVC的不同部分:U色Q控制器Q、Ԍ模型Q和l色Q视图)首先Q控制器QActionServletQ进行初始化工作Q读取配|文Ӟstruts- config.xmlQ,Z同的Struts模块初始化相应的ModuleConfig对象。比如配|文件中的Action映射定义都保存在 ActionConfig集合中。相应地有ControlConfig集合、FormBeanConfig集合、ForwardConfig集合?MessageResourcesConfig集合{?/p>
提示Q?/b>模块是在Struts 1.1中新提出的概念,在稍后的内容中我们将详细介绍Q你现在可以单地把模块看作是一个子pȝQ它们共同组成整个应用,同时又各自独立。Struts 1.1中所有的处理都是在特定模块环境中q行的。模块的提出主要是ؓ了解决Struts 1.0中单配置文g的问题?
控制器接收HTTPhQƈ从ActionConfig中找出对应于该请求的Action子类Q如果没有对应的ActionQ控制器直接请求{发给JSP或者静态页面。否则控制器请求分发至具体Actionc进行处理?/p>
在控制器调用具体Action的executeҎ之前QActionForm对象利用HTTPh中的参数来填充自己(可选步骤,需要在配置文g中指定)。具体的ActionForm对象应该是ActionForm的子cd象,它其实就是一个JavaBean。此外,q可以在ActionFormcM调用validateҎ来检查请求参数的合法性,q且可以q回一个包含所有错误信息的ActionErrors对象。如果执行成功, ActionForm自动这些参C息以JavaBeanQ一般称之ؓform beanQ的方式保存在Servlet Context中,q样它们可以被其它Action对象或者JSP调用?/p>
Struts这些ActionForm的配|信息都攑֜FormBeanConfig集合中,通过它们Struts能够知道针对某个客户h是否需要创建相应的ActionForm实例?/p>
Action 很简单,一般只包含一个executeҎQ它负责执行相应的业务逻辑Q如果需要,它也q行相应的数据检查。执行完成之后,q回一?ActionForward对象Q控制器通过该ActionForward对象来进行{发工作。我们主张将获取数据和执行业务逻辑的功能放到具体的 JavaBean当中Q而Action只负责完成与控制有关的功能。遵循该原则Q所以在上图中我Action对象归ؓ控制器部分?/p>
提示Q?/b>其实在Struts 1.1中,ActionMapping的作用完全可以由ActionConfig来替代,只不q由于它是公共API的一部分以及兼容性的问题得以保留?ActionMapping通过l承ActionConfig来获得与其一致的功能Q你可以{同地看待它们。同理,其它例如ActionForward?ForwardConfig的关pM是如此?
下图l出了客L从发求到获得响应整个q程的图解说明?/p>
![]()
下面我们来详细地讨Z下其中的每个部分Q在q之前,先来了解一下模块的概念?/p>
![]()
![]()
![]()
![]()
回页?/b>
我们知道Q在Struts 1.0中,我们只能在web.xml中ؓActionServlet指定一个配|文Ӟq对于我们这些网上的教学例子来说当然没什么问题,但是在实际的应用开发过E中Q可能会有些ȝ。因多开发h员都可能同时需要修攚w|文Ӟ但是配置文g只能同时被一个h修改Q这栯定会造成一定程度上的资源争夺,势必会媄响开发效率和引v开发h员的抱怨?/p>
在Struts 1.1中,Z解决q个q行开发的问题Q提Z两种解决ҎQ?
- 多个配置文g的支?/li>
- 模块的支?/li>
支持多个配置文gQ是指你能够为ActionServlet同时指定多个xml配置文gQ文件之间以逗号分隔Q比如Struts提供的MailReader演示例子中就采用该种Ҏ?/p>
<!-- Action Servlet Configuration --> <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml, /WEB-INF/struts-config-registration.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
q种Ҏ可以很好地解决修改冲H的问题Q不同的开发h员可以在不同的配|文件中讄自己的Action、ActionForm{等Q当然不是说每个开发h员都需要自q配置文gQ可以按照系l的功能模块q行划分Q。但是,q里q是存在一个潜在的问题Q就是可能不同的配置文g之间会生冲H,因ؓ在ActionServlet初始化的时候这几个文g最l还是需要合q到一L。比如,在struts-config.xml中配|了一个名为success?lt; forward>Q而在struts-config-registration.xml中也配置了一个同L<forward>Q那么执行v来就会生冲H?/p>
Zd解决q种冲突QStruts 1.1中引q了模块QModuleQ的概念。一个模块就是一个独立的子系l,你可以在其中q行L所需的配|,同时又不必担心和其它的配|文件生冲H。因为前面我们讲q,ActionServlet是将不同的模块信息保存在不同的ModuleConfig对象中的。要使用模块的功能,需要进行以下的准备工作Q?/p>
1、ؓ每个模块准备一个配|文?/p>
2、配|web.xml文gQ通知控制?/p>
军_采用多个模块以后Q你需要将q些信息告诉控制器,q需要在web.xml文gq行配置。下面是一个典型的多模块配|:
<init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>config/customer</param-name> <param-value>/WEB-INF/struts-config-customer.xml</param-value> </init-param> <init-param> <param-name>config/order</param-name> <param-value>/WEB-INF/struts-config-order.xml</param-value> </init-param>
要配|多个模块,你需要在原有的一?lt;init-param>Q在Struts 1.1中将其对应的模块UCؓ~省模块Q的基础之上Q增加模块对应的<init-param>。其?lt;param-name>表示为config/XXX的Ş式,其中XXX为对应的模块名,<param-value>中还是指定模块对应的配置文g。上面这个例子说明该应用有三个模块,分别是缺省模块、customer和orderQ它们分别对应不同的配置文g?/p>
3、准备各个模块所需的ActionForm、Action和JSP{资?/p>
但是要注意的是,模块的出C同时带来了一个问题,卛_何在不同模块间进行{发?有两U方法可以实现模块间的{发,一U就是在< forward>Q全局或者本圎ͼ中定义,另外一U就是利用org.apache.struts.actions.SwitchAction?/p>
下面是一个全局的例子:
... <struts-config> ... <global-forwards> <forward name="toModuleB" contextRelative="true" path="/moduleB/index.do" redirect="true"/> ... </global-forwards> ... </struts-config>
可以看出Q只需要在原有的path属性前加上模块名,同时contextRelative属性置为true卛_。此外,你也可以?lt;action>中定义一个类似的本地<forward>?/p>
<action-mappings> <!-- Action mapping for profile form --> <action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin" validate="true"> <forward name="success" contextRelative="true" path="/moduleA/login.do"/> </action> </action-mappings>
如果你已l处在其他模块,需要{回到~省模块Q那应该cM下面q样定义Q即模块名ؓI?/p>
<forward name="success" contextRelative="true" path="/login.do"/>
此外Q你也可以用org.apache.struts.actions.SwitchActionQ例如:
... <action-mappings> <action path="/toModule" type="org.apache.struts.actions.SwitchAction"/> ... </action-mappings> ...
![]()
![]()
![]()
![]()
回页?/b>
我们首先来了解MVC中的控制器。在Struts 1.1中缺省采用ActionServletcL充当控制器。当然如果ActionServlet不能满你的需求,你也可以通过l承它来实现自己的类。这可以?WEB-INF/web.xml中来具体指定?/p>
要掌握ActionServletQ就必须了解它所扮演的角艌Ӏ首先,ActionServlet表示MVCl构中的控制器部分,它需要完成控制器所需的前端控制及转发h{职责。其ơ,ActionServlet被实Cؓ一个专门处理HTTPh的ServletQ它同时hservlet的特炏V在 Struts 1.1中它主要完成以下功能Q?
- 接收客户端请?
- Ҏ客户端的URI请求映到一个相应的Actionc?
- 从请求中获取数据填充Form BeanQ如果需要)
- 调用Actioncȝexecute()Ҏ获取数据或者执行业务逻辑
- 选择正确的视囑֓应客?/li>
此外QActionServletq负责初始化和清除应用配|信息的d。ActionServlet的初始化工作在initҎ中完成,它可以分Z个部分:初始化ActionServlet自n的一些信息以及每个模块的配置信息。前者主要通过initInternal、initOther?initServlet三个Ҏ来完成?/p>
我们可以?WEB-INF/web.xml中指定具体的控制器以及初始参敎ͼ׃版本的变化以及Struts 1.1中模块概늚引进Q一些初始参数被废弃或者移入到/WEB-INF/struts-config.xml中定义。下面列出所有被废弃的参敎ͼ相应地在web.xml文g中也不鼓励再使用?/p>
- application
- bufferSize
- content
- debug
- factory
- formBean
- forward
- locale
- mapping
- maxFileSize
- multipartClass
- nocache
- null
- tempDir
ActionServletҎ不同的模块来初始化ModuleConfigc,q在其中以XXXconfig集合的方式保存该模块的各U配|信息,比如ActionConfigQFormBeanConfig{?/p>
初始化工作完成之后,ActionServlet准备接收客户h。针Ҏ个请求,Ҏprocess(HttpServletRequest request, HttpServletResponse response)被调用。该Ҏ指定具体的模块,然后调用该模块的RequestProcessor的processҎ?/p>
protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { RequestUtils.selectModule(request, getServletContext()); getRequestProcessor(getModuleConfig(request)).process(request, response); }
RequestProcessor包含了Struts控制器的所有处理逻辑Q它调用不同的processXXXҎ来完成不同的处理。下表列出其中几个主要的ҎQ?/p>
Ҏ 功能 processPath 获取客户端的h路径 processMapping 利用路径来获得相应的ActionMapping processActionForm 初始化ActionFormQ如果需要)q存入正的scope?/td> processActionCreate 初始化Action processActionPerform 调用Action的executeҎ processForwardConfig 处理Actionq回的ActionForward
![]()
![]()
![]()
![]()
回页?/b>
对于ActionForm你可以从以下几个斚w来理解它Q?
- ActionForm 表示HTTPH体中的数据Q可以将其看作是模型和视囄中介Q它负责保存视图中的数据供模型或者视图用。Struts 1.1文档中把它比作HTTP和Action之间的防火墙Q这体现了ActionFormh的过滤保护的作用Q只有通过ActionForm验证的数据才能够发送到Action处理?/li>
- ActionForm是与一个或多个ActionConfig兌的JavaBeanQ在相应的action的executeҎ被调用之前,ActionForm会自动利用请求参数来填充自己Q初始化属性)?/li>
- ActionForm是一个抽象类Q你必须通过l承来实现自qcR?/li>
ActionForm 首先利用属性的getter和setterҎ来实现初始化Q初始化完毕后,ActionForm的validateҎ被调用,你可以在其中来检查请求参数的正确性和有效性,q且可以错误信息以ActionErrors的Ş式返回到输入H体。否则,ActionForm被作ؓ参数传给action?executeҎ以供使用?/p>
ActionForm bean的生命周期可以设|ؓsessionQ缺省)和requestQ当讄为sessionӞ记得在resetҎ中将所有的属性重新设|ؓ初始倹{?/p>
׃ActionForm对应于HTTPH体Q所以随着面的增多,你的ActionForm会急速增加。而且可能同一cd面字段会在不同的 ActionForm中出玎ͼq且在每个ActionForm中都存在相同的验证代码。ؓ了解册个问题,你可以ؓ整个应用实现一个ActionForm 或者至一个模块对应于一个ActionForm?/p>
但是Q聚合的代h是复用性很差,而且隄护。针对这个问题,在Struts 1.1中提ZDynaActionForm的概c?/p>
DynaActionFormc?/b>
DynaActionForm 的目的就是减ActionForm的数目,利用它你不必创徏一个个具体的ActionFormc,而是在配|文件中配置出所需的虚?ActionForm。例如,在下表中通过指定<form-bean>的type?"org.apache.struts.action.DynaActionForm"来创Z个动态的ActionForm--loginForm?/p>
<form-beans> <form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="actionClass" type="java.lang.String"/> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> </form-beans>
动态的ActionForm 的用方法跟普通的ActionForm相同Q但是要注意一炏V普通的ActionForm对象需要ؓ每个属性提供getter和setterҎQ以上面的例子而言Q我们需要提供getUsername() ?setUsername()Ҏ取得和设|username属性,同样地有一Ҏ法用于取得和讄password属性和actionClass属性?/p>
如果使用DynaActionFormQ它属性保存在一个HashMapcd象中Q同时提供相应的get(name) ?set(name)ҎQ其中参数name是要讉K的属性名。例如要讉KDynaActionForm中username的|可以采用cM的代码:
String username = (String)form.get("username")Q?
׃值存放于一个HashMap对象Q所以要记得对get()Ҏq回的Object对象做强制性类型{换。正是由于这点区别,如果你在Action中非帔RJ地使用ActionForm对象Q徏议还是用普通的ActionForm对象?/p>
在Struts 1.1中,除了DynaActionForm以外Q还提供了表单输入自动验证的功能Q在包org.apache.struts.validator中提供了许多有用的类Q其中最常见的就是DynaValidatorFormcR?/p>
DynaValidatorFormc?/b>
DynaValidatorForm是DynaActionForm的子c,它能够提供动态ActionForm和自动表单输入验证的功能。和使用DynaActionFormcMQ你必须首先在配|文件中q行配置Q?/p>
<form-beans> <form-bean name="loginForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="actionClass" type="java.lang.String"/> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> </form-beans>
同时要定义验证的插gQ?/p>
<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/> </plug-in>
其中的validator.xml和validator-rules.xml分别表示验证定义和验证规则的内容Q可以合q在一PQ比如针对上例中的DynaValidatorFormQ我们有如下验证定义Qvalidator.xmlQ:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN" "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd"> <!-- Validation Rules $Id: validation.xml--> <form-validation> <!-- ========== Default Language Form Definitions ===================== --> <formset> <form name="loginForm"> <field property="username" depends="required, minlength,maxlength"> <arg0 key="prompt.username"/> <arg1 key="${var:minlength}" name="minlength" resource="false"/> <arg2 key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> <var> <var-name>minlength</var-name> <var-value>3</var-value> </var> </field> <field property="password" depends="required, minlength,maxlength" bundle="alternate"> <arg0 key="prompt.password"/> <arg1 key="${var:minlength}" name="minlength" resource="false"/> <arg2 key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> <var> <var-name>minlength</var-name> <var-value>3</var-value> </var> </field> </form> </formset> </form-validation>
从上q定义中Q我们可以看到对于字Dusername有三w证:required, minlength, maxlengthQ意思是该字D不能ؓI,而且长度??6之间。而validator-rules.xml文g则可以采用Struts提供的缺省文件。注意在<form-bean>中定义的form是如何与validation.xml中的form兌h的。最后,要启动自动验证功能,q需要将Action配置的validate属性设|ؓtrue?/p>
<action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin"validate="true">
此时QStruts根据xml配置文g中的定义来检验表单输入,q将不符合要求的错误信息输出到页面。但是你可能会想Q这个功能虽然好Q可是什么检验都跑到服务器端执行Q效率方面和用户易用性方面是不是有些问题Q你可能会怀念v那简单的JavaScript客户端验证?/p>
不用担心Q在Struts 1.1中也支持JavaScript客户端验证。如果你选择了客L验证Q当某个表单被提交以后,Struts 1.1启动客户端验证,如果览器不支持JavaScript验证Q则服务器端验证被启动,q种双重验证机制能够最大限度地满各种开发者的需要?JavaScript验证代码也是在validator-rules.xml文g中定义的。要启动客户端验证,你必d相应的JSP文g中做如下讄Q?
- ?lt;html:form>增加onsubmit属?/li>
- 讄Javascript支持
下表中列Z一JSP文g的示例代码,U字部分为Javascript验证所需代码?/p>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <table bgcolor="#9AFF9A" cellspacing="0" cellpadding="10" border="1" width="100%"> <tr> <td> <table cellspacing="0" cellpadding="0" border="0" width="100%"> <tr bgcolor="#696969"> <td align="center"> <font color="#FFFFFF">Panel 3: Profile</font> </td> </tr> <tr> <td><br> <html:errors/> <html:form action="/login.do" focus="username" onsubmit="return validateLoginForm(this);"> <html:hidden property="actionClass"/> <center> <table> <tr> <td>UserName:</td> <td><html:text property="username" size="20"/></td> </tr> <tr> <td>Password:</td> <td><html:password property="password" size="20"/></td> </tr> <tr> <td colspan=2><html:submit property="submitProperty" value="Submit"/></td> </table> </center> </html:form> <html:javascript formName="loginForm" dynamicJavascript="true" staticJavascript="false"/> <script language="Javascript1.1" src="staticJavascript.jsp"></script> </td> </tr> </table> </td> </tr> </table>
其中onsubmit的gؓ"return validateLoginForm(this);"Q它的语法ؓQ?/p>
return validate + struts-config.xml中定义的form-bean名称 + (this);
staticJavascript.jsp的内容ؓQ?/p>
<%@ page language="java" %> <%-- set document type to Javascript (addresses a bug in Netscape according to a web resource --%> <%@ page contentType="application/x-javascript" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:javascript dynamicJavascript="false" staticJavascript="true"/>
如果validator-rules.xml中定义的基本验证功能不能满你的需求,你可以自己添加所需的验证类型?/p>
![]()
![]()
![]()
![]()
回页?/b>
我们通过l承ActioncL实现具体的执行类。具体Actioncȝ功能一般都在executeQ以前是performҎQ方法中完成Q其中主要涉及到以下几个斚wQ?
- 辅助ActionFormq行一些表单数据的查?/li>
- 执行必要的业务逻辑Q比如存取数据库Q调用实体bean{?/li>
- 更新服务器端的bean数据Q后l对象中可能会用到这些数据,比如在JSP中利用bean:write来获得这些数据?/li>
- Ҏ处理l果军_E序的去处,q以ActionForward对象的Ş式返回给ActionServlet?/li>
提示Q?/b>׃在Action和ActionForm中都可以实现验证ҎQ那么如何来安排它们之间的分工呢Q一般来_我们U着MVC分离的原则,也就是视囄的验证工作放在ActionForm来完成,比如输入不能为空Qemail格式是否正确Q利用ValidatorForm可以很轻村֜完成q些工作。而与具体业务相关的验证则攑օAction中,q样可以获得最大ActionForm重用性的可能?
前面我们提到q,我们d业务逻辑执行分离到单独的 JavaBean中,而Action只负责错误处理和程控制。而且考虑到重用性的原因Q在执行业务逻辑的JavaBean中不要引用Q何与Web应用相关的对象,比如HttpServletRequestQHttpServletResponse{对象,而应该将其{化ؓ普通的Java对象。关于这一点,可以参考Petstore中WAF框架的实现思\?/p>
此外Q你可能q注意到execute与perform的一个区别:executeҎ单地掷出Exception异常Q而performҎ则掷出ServletException和IOException 异常。这不是说Struts 1.1在异常处理功能方面弱化了Q而是Z配合Struts 1.1中一个很好的功能--宣称式异常处理机制?/p>
![]()
![]()
![]()
![]()
回页?/b>
和EJB中的宣称式事务处理概늱|宣称式异常处理其实就是可配置的异常处理,你可以在配置文g中指定由谁来处理ActioncM掷出的某U异常。你可以按照以下步骤来完成该功能Q?
- 实现org.apache.struts.action.ExceptionHandler的子c,覆盖executeҎQ在该方法中处理异常q且q回一个ActionForward对象
- 在配|文件中配置异常处理对象Q你可以配置一个全局的处理类或者单独ؓ每个Action配置处理c?/li>
下表定义了一个全局的处理类CustomizedExceptionHandlerQ它被用来处理所有的异常?/p>
<global-exceptions> <exception handler="com.yourcorp.CustomizedExceptionHandler" key="global.error.message" path="/error.jsp" scope="request" type="java.lang.Exception"/> </global-exceptions>
其中具体的参数含义,可以参考ExceptionHandler.java源文件?/p>
![]()
![]()
![]()
![]()
回页?/b>
讲完了模型和控制器,接下来我们要涉及的是视图。视囄角色主要是由JSP来完成,从JSP的规范中可以看出Q在视图层可?折腾"的技术不是很多,主要的就是自定义标记库的应用。Struts 1.1在原有的四个标记库的基础上新增了两个标记?-Tiles和Nested?/p>
其中Tiles除了替代Template的基本模板功能外Q还增加了布局定义、虚拟页面定义和动态页面生成等功能。Tiles强大的模板功能能够ə面获得最大的重用性和灉|性,此外可以l合Tiles配置文g中的面定义和Action的{发逻辑Q即你可以将一个Action转发C个在Tiles配置文g中定义的虚拟面Q从而减页面的数量。比如,下表中的Action定义了一个{发\径,它的l点是tile.userMainQ而后者是你在 Tiles配置文g中定义的一个页面?/p>
<!-- ========== Action Mapping Definitions ============================== --> <action-mappings> <!-- Action mapping for profile form --> <action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin" validate="true"> <forward name="success" path="tile.userMain"/> </action> </action-mappings>
Tiles配置文gQtiles-defs.xml
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration//EN" "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"> <tiles-definitions> <!-- ======================================================= --> <!-- Master definitions --> <!-- ======================================================= --> <!-- Page layout used as root for all pages. --> <definition name="rootLayout" path="/tiles-layouts/rootLayout.jsp"> <put name="titleString" value="CHANGE-ME"/> <put name="topMenu" value="/tiles-components/topMenu.jsp"/> <put name="leftMenu" value="/tiles-components/panel1.jsp"/> <put name="body" value="CHANGE-ME"/> <put name="footer" value="/tiles-components/footer.jsp"/> </definition> <!-- ======================================================= --> <!-- Page definitions --> <!-- ======================================================= --> <!-- User Login page --> <definition name="tile.userLogin" extends="rootLayout"> <put name="titleString" value="User Login"/> <put name="body" value="/src/userLogin.jsp"/> </definition> <!-- User Main page --> <definition name="tile.userMain" extends="rootLayout"> <put name="titleString" value="User Main"/> <put name="body" value="/src/userMain.jsp"/> </definition> </tiles-definitions>
而Nested标记库的作用是让以上q些基本标记库能够嵌套用,发挥更大的作用?/p>
![]()
![]()
![]()
![]()
回页?/b>
所谓的Commons Logging接口Q是指将日志功能的用与日志具体实现分开Q通过配置文g来指定具体用的日志实现。这样你可以在Struts 1.1中通过l一的接口来使用日志功能Q而不ȝ具体是利用的哪种日志实现Q有点于cMJDBC的功能。Struts 1.1中支持的日志实现包括QLog4JQJDK Logging APIQ?LogKitQNoOpLog和SimpleLog?/p>
你可以按照如下的方式来用Commons Logging接口Q可以参照Struts源文中的许多cd玎ͼQ?/p>
package com.foo; // ... import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; //... public class Foo { // ... private static Log log = LogFactory.getLog(Foo.class); // ... public void setBar(Bar bar) { if (log.isTraceEnabled()) { log.trace("Setting bar to " + bar); } this.bar = bar; } // ... }
而开启日志功能最单的办法是在WEB-INF/classes目录下添加以下两个文Ӟ
commons-logging.properties文gQ?/p>
# Note: The Tiles framework now uses the commons-logging package to output different information or debug statements. Please refer to this package documentation to enable it. The simplest way to enable logging is to create two files in WEB-INF/classes: # commons-logging.properties # org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog # simplelog.properties # # Logging detail level, # # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). #org.apache.commons.logging.simplelog.defaultlog=trace org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
simplelog.properties文gQ?/p>
# Logging detail level, # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). org.apache.commons.logging.simplelog.defaultlog=fatal
q里我们采用的日志实现是 SimpleLogQ你可以在simplelog.properties文g指定日志明细的别:traceQdebugQinfoQwarnQ?error和fatalQ从trace到fatal错误U别来高Q同时输出的日志信息也越来越。而这些别是?org.apache.commons.logging.log接口中的Ҏ一一对应的。这些别是向后包含的,也就是前面的U别包含后面U别的信息?/p>
![]()
![]()
![]()
![]()
回页?/b>
![]()
![]()
![]()
![]()
回页?/b>
![]()
![]()
王和?邮g地址Q?ok_winnerboy@sina.com
javaGrowing 2006-10-11 15:08 发表评论]]>
Set和数学中的集合是同一个概念,是没有重复元素的集合?/p>
q篇文章主要了Set是如何实?没有重复元素"Qno duplicate elementsQ的Q以及阐qC什么是“重复”(duplicateQ,是相同的地址I间Q是equals的返回gؓtrueQ是compareTo的返回gؓ0 Q还是有相同的hashCodeQ本文还l出了在什么情况下使用什么样的Set的徏议?/p>
注:本文不涉及范型?/p>
1、树形结构:
public interface Set<E> extends Collection<E>{}
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>{}
public class CopyOnWriteArraySet<E>extends AbstractSet<E>implements Serializable{}
public abstract class EnumSet<E extends Enum<E>>extends AbstractSet<E>implements Cloneable, Serializable{}
public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, Serializable{}
public final class JobStateReasonsextends HashSet<JobStateReason>implements PrintJobAttribute{}
public class LinkedHashSet<E>extends HashSet<E>implements Set<E>, Cloneable, Serializable{}
public class TreeSet<E>extends AbstractSet<E>implements SortedSet<E>, Cloneable, Serializable{}
可以看出Q可以实例化的类为:CopyOnWriteArraySetQHashSetQLinkedHashSetQTreeSet?br />2、Set是如何实现元素唯一性的
javadoc中对Set的描q第一D如下:“A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1
and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.?br /> q段话是Ҏ错,L下面分析?br /> 要进行下面的Q我们先了解一下Map。Map中的元素是“键Q值”对Q其中“键”必L唯一的。TreeSet和HashSet是利用q个Ҏ实现“no duplicate elements”。它把set中的元素作ؓMap中的“键”,从而保持元素的唯一性。这些键在Map中又是如何区分的呢?不同的Map有不同的做法Q而且区别很大?br /> 下面我们分别TreeSet、HashSet和CopyOnWriteArraySetq行Q?br />2.1、TreeSet部分Q?br /> 以下以TreeSetZq行分析?br /> LTreeSet的部分实体:
public class TreeSet<E> extends AbstractSet<E>
implements SortedSet<E>, Cloneable, java.io.Serializable
{
// The backing Map
private transient SortedMap<E,Object> m;
// The keySet view of the backing Map
private transient Set<E> keySet;
// Dummy value to associate with an Object in the backing Map
//q是每个键所指的对像
private static final Object PRESENT = new Object();
//constructor
private TreeSet(SortedMap<E,Object> m) {
this.m = m;
keySet = m.keySet();
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
//以下省略..........
}
可以看到TreeSet使用了SortedMap作ؓ其Map保存“键Q值”对Q而这个SortedMap的真正实体是TreeMap?br />
LCZE序1Q?br /> import java.util.*;
public class SetTest1 {
public static void main(String[] args){
Set set = new TreeSet();
set.add(new SetElement1("aa"));
set.add(new SetElement1("bb"));
}
static class SetElement1{
String s;
public SetElement1(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement1)obj).s);
}
}
}
该程序能够正常编译,但是q行时会抛出异常java.lang.ClassCastException。ؓ什么?
LCZE序2Q?br /> import java.util.*;
public class SetTest2 {
public static void main(String[] args){
Set set = new TreeSet();
set.add(new SetElement2("aa"));
set.add(new SetElement2("aa"));
set.add(new SetElement2("bb"));
System.out.println(set);
}
static class SetElement2 implements Comparable{
String s;
public SetElement2(String s){
this.s = s;
}
public String toString(){
return s;
}
public int compareTo(Object o){
return s.compareTo(((SetElement2)o).s);
}
public boolean equals(Object obj) {
return s.equals(((SetElement2)obj).s);
}
}
}
q行l果Q?br /> [aa, bb]
q正是我们所期望的结果。那“示例程?”和“示例程?”有什么区别?
是因为SetElement2实现了Comparable接口Q而SetElement1没有。SetElement2实现Comparable接口有什么用呢?因ؓ在TreeSet的addҎ中需要比较两个 ?元素的“值”。请看TreeMap中的compareҎQ?br /> private int compare(K k1, K k2) {
return (comparator==null ? ((Comparable</*-*/K>)k1).compareTo(k2)
: comparator.compare((K)k1, (K)k2));
}
可见q个Ҏ先把要比较的元素down cast成Comparablecd。这里就可以解释“示例程?”中Z么会抛出异常java.lang.ClassCastExceptionQ因SetElement1没有实现Comparable接口Q当然就不能down cast成Comparable。可见,要用TreeSet来做Z的SetQ那么Set中所装的元素都必dCComparable接口?br /> 说到q里Q你是不是想CTreeSet中是采用Comparable接口中的compareToҎ来判断元素是否相同(duplicateQ,而不是采用其他类似equals之类的东东来判断?br />
LCZE序3Q?br /> import java.util.Set;
import java.util.*;
public class SetTest3 {
public static void main(String[] args){
Set set = new HashSet();
set.add(new SetElement3("aa"));
set.add(new SetElement3("aa"));
set.add(new SetElement3("bb"));
System.out.println(set);
}
static class SetElement3 implements Comparable{
String s;
public SetElement3(String s){
this.s = s;
}
public String toString(){
return s;
}
public int compareTo(Object o){
//return s.compareTo(((SetElement3)o).s);
return -1;
}
public boolean equals(Object obj) {
return s.equals(((SetElement3)obj).s);
}
}
}
q行l果Q?br /> [bb, aa, aa]
看到没有Q有两个“aa”!Q这是因为compareToq回值始l是"-1"Q也是说“把M元素都看成不同”?br />
lg所qͼ你是否对javadoc中对Set功能的描q有了怀疑?Q?br />2.2、HashSet部分Q?br /> 以下以HashSetZq行分析?br /> 从HashsetcȝM部分Q?br /> public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
//q是每个键所指的对像
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<E,Object>();
}
public boolean add(E o) {
return map.put(o, PRESENT)==null;
}
//以下省略..........
}
public HashSet() {
map = new HashMap<E,Object>();
}
可以看到HashSet使用了HashMap作ؓ其Map保存“键Q值”对?br />
LCZE序4Q?br /> import java.util.*;
public class SetTest4 {
public static void main(String[] args){
Set set = new HashSet();
set.add(new SetElement4("aa"));
set.add(new SetElement4("aa"));
set.add(new SetElement4("bb"));
System.out.println(set);
}
static class SetElement4{
String s;
public SetElement4(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement4)obj).s);
}
}
}
q行l果Q?br /> [bb, aa, aa]
没有“示例程?”中的java.lang.ClassCastExceptionQ但是运行结果似乎不对,因ؓ有两个“aa”?br />
LCZE序5Q?br /> import java.util.*;
public class SetTest5 {
public static void main(String[] args){
Set set = new HashSet();
set.add(new SetElement5("aa"));
set.add(new SetElement5("aa"));
set.add(new SetElement5("bb"));
System.out.println(set);
}
static class SetElement5{
String s;
public SetElement5(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement5)obj).s);
}
public int hashCode() {
//return super.hashCode();
return s.hashCode();
}
}
}
q行l果Q?br /> [bb, aa]
q就对了。“示例程?”和“示例程?”有什么区别?是SetElement5重写了hashCodeҎ?br />
可见HashSet中是采用了比较元素hashCode的方法来判断元素是否相同QduplicateQ,而不是采用其他类似equals之类的东东来判断?br />
说了q么多,那javacd中到底有没有Ҏequals来判断元素是否相同(duplicateQ的Set呢?L下文?br />2.2、CopyOnWriteArraySet部分Q?br /> cCopyOnWriteArraySet是java.util.concurrent包中的一个类Q所以它是线E安全的?br /> CopyOnWriteArraySet是用CopyOnWriteArrayList作ؓ其盛攑օ素的容器。当往CopyOnWriteArrayListd新元素,它都要遍历整个ListQƈ且用equals来 ?比较两个元素是否相同?/p>
LCZE序6Q?br /> import java.util.*;
import java.util.concurrent.*;
public class SetTest6 {
public static void main(String[] args){
Set set = new CopyOnWriteArraySet();
set.add(new SetElement6("aa"));
set.add(new SetElement6("aa"));
set.add(new SetElement6("bb"));
System.out.println(set);
}
static class SetElement6{
String s;
public SetElement6(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement6)obj).s);
}
}
}
q行l果Q?br /> [aa, bb]
好了Q一切搞定!Q?/p>
3、ȝQ?br /> Javadoc中的一些描q可能是不准的Q大家要当心了!
Set中实现元素互异的各种Ҏ差异很大Q大致可以分ZU:使用equalsQ用hashCodeQ用compareTo。但是我q没有发现采用“判断地址I间是否相同”来判断元素是否相同的类Q当然我们可以用现有的三U方法来实现“判断地址I间是否相同”?br />
lg所qͼ我们可以ȝZ用Set的三U不同的情ŞQ(以下假设元素cMؓElementQ?br /> A、如果想使用Element的equalsҎ来判断元素是否相同,那么可以使用CopyOnWriteArraySet来构造类的实体?br /> B、如果Element实现了Comparable接口Q而且想用compareToҎ来判断元素是否相同,那么可以使用TreeSet来构造类的实体?br /> C、如果想使用判断hashCode是否相同的方法来判断元素是否相同Q那么可以用HashSet来构造类的实体?/p>
public event 代表?事g名?/td> |
如在ControlcM声明了一个Click事g成员Q其语法如下Q?br />
public event EventHandler Click; |
在C#中,增加了一个新的数据类型delegateQ代表)来解决事件处理问题。代表数据类型非常类gC语言中的指针Q其与指针不同的是,其是代码是安全的Q可理的。由于C#本n的简易性,对于没有使用qE及指针的E序来说Q理解delegate也是非常Ҏ的?br />
在C#中,通过使用delegateQ你可以通过"+="Q加{于Q操作符非常ҎCؓ.Net对象中的一个事件添加一个甚臛_个响应方法;q可以通过非常单的"-="Q减{于Q操作符取消q些响应Ҏ。如下面为temp按钮dClick事g的语句:
temp.Click+=new System.EventHandler(this.Test);//为testd事g处理Ҏ |
在上面声明事件的语句中,Eventhandler是一个delegate(代表)cdQ其?Netcd中如下声明的Q?
public delegate void EventHandler(object sender,EventArgs e); |
q样Q所有Ş?void 函娄?object 参数?EventArgs 参数?;的函数都可以作ؓControlcȝClick事g响应Ҏ了。如下面所定义的一个事件响应方法:
private void button1_Click(object sender, System.EventArgs e) |
׃是通过delegateQ代表类型)来处理事Ӟ因此Q可能通过累加使一个事件具有多个响应方法;与此同时Q还可以使一个方法作为多个事件的响应Ҏ。(注意Q在C#语言cM的event成员后面只能出现"+="?-="两个表示d与取消事件响应函数的操作W。)
不管是ASP.Netq是一般的Windows Forms ~程Q在C#中,基本上我们遇到的事g响应Ҏ都是说明成如下的形式Q?br />
private void button1_Click(object sender, System.EventArgs e) |
那么Q一个事件响应方法的存取权限、返回值类型、参数及cd甚至Ҏ名称{是否都必须固定不变呢?{案是:不是Q?br />
一般情况下Q事件的响应Ҏ中都有两个参敎ͼ其中一个代表引发事件的对象即senderQ由于引发事件的对象不可预知的,因此我们把其声明成ؓobjectcdQ所有的对象都适用。第二个参数代表引发事g的具体信息,各种cd的事件中可能不同Q这要根据类中事件成员的说明军_?br />delegate int MyEventHandler(object sender, ToolBarButtonClickEventArgs e);
private int MyTest(object sender,ToolBarButtonClickEventArgs e) {}
在给对象d事g响应Ҏ时就可以用如下的代码实现Q?/p>
Control.Event+=new MyEventHandler(MyTest);
ȝ来说,Java事g处理更直??而C#事g处理׃引用代理,使得E序更灵z?更体
现程序之间的松藕合?国鸟QStryon http://www.stryon.com.cnQ公司宣布在Java
开发^C实现微Y?NET,命名为iNET.q于q期推出iNET的Beta3版本,其中包括用
Java实现了C#的三U事件处理机制?/p>
Java与C#的事件处理都是实C事g?事g响应者机Ӟ但又不完全相同。Java实现的是一U事件源与事件响应者两U实体对象方式,q里的事件响应者也是事件监听者,而C#实现的是一U事件源-代理-事g响应者三U实体对象方式。下面就q两U方式来具体说明?/span>
public class MouseMovedExampleEvent extends java.util.EventObject
{ protected int x, yQ?br />/* 创徏一个鼠标移动事件MouseMovedExampleEvent */
MouseMovedExampleEvent(java.awt.Component source, Point location) {
super(source);
x = location.x;
y = location.y;
}
/* 获取鼠标位置*/
public Point getLocation() {
return new Point(x, y);
}}
׃Java事g模型是基于方法调用,因而需要一个定义ƈl织事g操纵Ҏ的方式。事件操U|法都被定义在l承?java.util.EventListenercȝEventListener接口中,按规定,EventListener接口的命名要?Listenerl尾。Q何一个类如果xU在EventListener接口中定义的Ҏ都必M实现q个接口方式q行。这个类也就是事件监听者。例如:
/*先定义了一个鼠标移动事件对?/
public class MouseMovedExampleEvent extends java.util.EventObject {
// 在此cM包含了与鼠标Ud事g有关的状态信?br /> ...
}
/*定义了鼠标移动事件的监听者接?/
interface MouseMovedExampleListener extends java.util.EventListener {
/*在这个接口中定义了鼠标移动事件监听者所应支持的Ҏ*/
void mouseMoved(MouseMovedExampleEvent mme);
}
class ArbitraryObject implements MouseMovedExampleListener {
public void mouseMoved(MouseMovedExampleEvent mme)
{ ... }
?br />
ArbitraryObject是MouseMovedExampleEvent事g的监听者?br />
Z各种可能的事件监听者把自己注册入合适的事g源中Q徏立源与事件监听者间的事件流Q事件源必须Z件监听者提供注册和注销的方法。在前面的bound属性介l中已看Cq种使用q程Q在实际中,事g监听者的注册和注销要用标准的设计格式Q?br />public void add< ListenerType>(< ListenerType> listener)Q?br />public void remove< ListenerType>(< ListenerType> listener)Q?/p>
例如Q首先定义了一个事件监听者接口:
void modelChanged(EventObject e);
}
接着定义事g源类
public abstract class Model {
private Vector listeners = new Vector(); // 定义了一个储存事件监听者的数组
/*上面设计格式中的< ListenerType>在此处即是下面的ModelChangedListener*/
public synchronized void addModelChangedListener(ModelChangedListener mcl)
{ listeners.addElement(mcl); }//把监听者注册入listeners数组?br />public synchronized void removeModelChangedListener(ModelChangedListener mcl)
{ listeners.removeElement(mcl); //把监听者从listeners中注销
?br />/*以上两个Ҏ的前面均冠以synchronizedQ是因ؓq行在多U程环境Ӟ可能同时有几个对象同时要q行
注册和注销操作Q用synchronized来确保它们之间的同步。开发工hE序员用这两个Ҏ建立源与?br />听者之间的事g?/
protected void notifyModelChanged()
{/**事g源用本Ҏ通知监听者发生了modelChanged事g*/
Vector l;
EventObject e = new EventObject(this);
/*首先要把监听者拷贝到l数组中,ȝEventListeners的状态以传递事件。这h保在事?br />传递到所有监听者之前,已接收了事g的目标监听者的对应Ҏ暂不生效?/
synchronized(this) {
l = (Vector)listeners.clone();
}
for (int i = 0; i < l.size(); i++) {
/* 依次通知注册在监听者队列中的每个监听者发生了modelChanged事gQ?br /> q把事g状态对象e作ؓ参数传递给监听者队列中的每个监听?/
((ModelChangedListener)l.elementAt(i)).modelChanged(e);
}
}
?/p>
在程序中可见事g源ModelcL式地调用了接口中的modelChangedҎQ实际是把事件状态对象e作ؓ参数Q传递给了监听者类中的modelChangedҎ?/p>