小菜毛毛技术分享

与大家共同成长

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  164 Posts :: 141 Stories :: 94 Comments :: 0 Trackbacks

#

 摘 要 本文在分析对象池技术基本原理的基础上,给出了对象池技术的两种实现方式。还指出了使用对象池技术时所应注意的问题。

  关键词 对象池;对象池技术;Java 对象;性能

  Java对象的生命周期分析

  Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除。因此,对象的生命周期长度可用如下的表达式表示:T = T1 + T2 +T3。其中T1表示对象的创建时间,T2表示对象的使用时间,而T3则表示其清除时间。由此,我们可以看出,只有T2是真正有效的时间,而T1、T3则是对象本身的开销。下面再看看T1、T3在对象的整个生命周期中所占的比例。

  我们知道,Java对象是通过构造函数来创建的,在这一过程中,该构造函数链中的所有构造函数也都会被自动调用。另外,默认情况下,调用类的构造函数时,Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和double变量设置成0.0,逻辑值设置成false。所以用new关键字来新建一个对象的时间开销是很大的,如表1所示。

  表1 一些操作所耗费时间的对照表

运算操作 示例 标准化时间
本地赋值 i = n 1.0
实例赋值 this.i = n 1.2
方法调用 Funct() 5.9
新建对象 New Object() 980
新建数组 New int[10] 3100

  从表1可以看出,新建一个对象需要980个单位的时间,是本地赋值时间的980倍,是方法调用时间的166倍,而若新建一个数组所花费的时间就更多了。

  再看清除对象的过程。我们知道,Java语言的一个优势,就是Java程序员勿需再像C/C++程序员那样,显式地释放对象,而由称为垃圾收集器(Garbage Collector)的自动内存管理系统,定时或在内存凸现出不足时,自动回收垃圾对象所占的内存。凡事有利总也有弊,这虽然为Java程序设计者提供了极大的方便,但同时它也带来了较大的性能开销。这种开销包括两方面,首先是对象管理开销,GC为了能够正确释放对象,它必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等。其次,在GC开始回收“垃圾”对象时,系统会暂停应用程序的执行,而独自占用CPU。

  因此,如果要改善应用程序的性能,一方面应尽量减少创建新对象的次数;同时,还应尽量减少T1、T3的时间,而这些均可以通过对象池技术来实现。

  对象池技术的基本原理

  对象池技术基本原理的核心有两点:缓存和共享,即对于那些被频繁使用的对象,在使用完后,不立即将它们释放,而是将它们缓存起来,以供后续的应用程序重复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能。事实上,由于对象池技术将对象限制在一定的数量,也有效地减少了应用程序内存上的开销。

  实现一个对象池,一般会涉及到如下的类:

  1)对象池工厂(ObjectPoolFactory)类

  该类主要用于管理相同类型和设置的对象池(ObjectPool),它一般包含如下两个方法:

  ·createPool:用于创建特定类型和设置的对象池;

  ·destroyPool:用于释放指定的对象池;

  同时为保证ObjectPoolFactory的单一实例,可以采用Singleton设计模式,见下述getInstance方法的实现:

public static ObjectPoolFactory getInstance() {
 if (poolFactory == null) {
  poolFactory = new ObjectPoolFactory();
 }
 return poolFactory;
}

  2)参数对象(ParameterObject)类

  该类主要用于封装所创建对象池的一些属性参数,如池中可存放对象的数目的最大值(maxCount)、最小值(minCount)等。

  3)对象池(ObjectPool)类

  用于管理要被池化对象的借出和归还,并通知PoolableObjectFactory完成相应的工作。它一般包含如下两个方法:

   ·getObject:用于从池中借出对象;
   ·returnObject:将池化对象返回到池中,并通知所有处于等待状态的线程;

  4)池化对象工厂(PoolableObjectFactory)类

  该类主要负责管理池化对象的生命周期,就简单来说,一般包括对象的创建及销毁。该类同ObjectPoolFactory一样,也可将其实现为单实例。
通用对象池的实现

  对象池的构造和管理可以按照多种方式实现。最灵活的方式是将池化对象的Class类型在对象池之外指定,即在ObjectPoolFactory类创建对象池时,动态指定该对象池所池化对象的Class类型,其实现代码如下:

. . .
public ObjectPool createPool(ParameterObject paraObj,Class clsType) {
 return new ObjectPool(paraObj, clsType);
}
. . .

  其中,paraObj参数用于指定对象池的特征属性,clsType参数则指定了该对象池所存放对象的类型。对象池(ObjectPool)创建以后,下面就是利用它来管理对象了,具体实现如下:

public class ObjectPool {
 private ParameterObject paraObj;//该对象池的属性参数对象
 private Class clsType;//该对象池中所存放对象的类型
 private int currentNum = 0; //该对象池当前已创建的对象数目
 private Object currentObj;//该对象池当前可以借出的对象
 private Vector pool;//用于存放对象的池
 public ObjectPool(ParameterObject paraObj, Class clsType) {
  this.paraObj = paraObj;
  this.clsType = clsType;
  pool = new Vector();
 }
 public Object getObject() {
  if (pool.size() <= paraObj.getMinCount()) {
   if (currentNum <= paraObj.getMaxCount()) {
    //如果当前池中无对象可用,而且已创建的对象数目小于所限制的最大值,就利用
    //PoolObjectFactory创建一个新的对象
    PoolableObjectFactory objFactory =PoolableObjectFactory.getInstance();
    currentObj = objFactory.create Object (clsType);
    currentNum++;
   } else {
    //如果当前池中无对象可用,而且所创建的对象数目已达到所限制的最大值,
    //就只能等待其它线程返回对象到池中
    synchronized (this) {
     try {
      wait();
     } catch (InterruptedException e) {
      System.out.println(e.getMessage());
      e.printStackTrace();
     }
     currentObj = pool.firstElement();
    }
   }
  } else {
   //如果当前池中有可用的对象,就直接从池中取出对象
   currentObj = pool.firstElement();
  }
  return currentObj;
}
  public void returnObject(Object obj) {
   // 确保对象具有正确的类型
   if (obj.isInstance(clsType)) {
    pool.addElement(obj);
    synchronized (this) {
     notifyAll();
    }
   } else {
    throw new IllegalArgumentException("该对象池不能存放指定的对象类型");
   }
  }
}

  从上述代码可以看出,ObjectPool利用一个java.util.Vector作为可扩展的对象池,并通过它的构造函数来指定池化对象的Class类型及对象池的一些属性。在有对象返回到对象池时,它将检查对象的类型是否正确。当对象池里不再有可用对象时,它或者等待已被使用的池化对象返回池中,或者创建一个新的对象实例。不过,新对象实例的创建并不在ObjectPool类中,而是由PoolableObjectFactory类的createObject方法来完成的,具体实现如下:

. . .
public Object createObject(Class clsType) {
 Object obj = null;
 try {
  obj = clsType.newInstance();
 } catch (Exception e) {
  e.printStackTrace();
 }
 return obj;
}
. . .

  这样,通用对象池的实现就算完成了,下面再看看客户端(Client)如何来使用它,假定池化对象的Class类型为StringBuffer:

. . .
//创建对象池工厂
ObjectPoolFactory poolFactory = ObjectPoolFactory. getInstance ();
//定义所创建对象池的属性
ParameterObject paraObj = new ParameterObject(2,1);
//利用对象池工厂,创建一个存放StringBuffer类型对象的对象池
ObjectPool pool = poolFactory.createPool(paraObj,String Buffer.class);
//从池中取出一个StringBuffer对象
StringBuffer buffer = (StringBuffer)pool.getObject();
//使用从池中取出的StringBuffer对象
buffer.append("hello");
System.out.println(buffer.toString());
. . .

  可以看出,通用对象池使用起来还是很方便的,不仅可以方便地避免频繁创建对象的开销,而且通用程度高。但遗憾的是,由于需要使用大量的类型定型(cast)操作,再加上一些对Vector类的同步操作,使得它在某些情况下对性能的改进非常有限,尤其对那些创建周期比较短的对象
专用对象池的实现     

  由于通用对象池的管理开销比较大,某种程度上抵消了重用对象所带来的大部分优势。为解决该问题,可以采用专用对象池的方法。即对象池所池化对象的Class类型不是动态指定的,而是预先就已指定。这样,它在实现上也会较通用对象池简单些,可以不要ObjectPoolFactory和PoolableObjectFactory类,而将它们的功能直接融合到ObjectPool类,具体如下(假定被池化对象的Class类型仍为StringBuffer,而用省略号表示的地方,表示代码同通用对象池的实现):

public class ObjectPool {
 private ParameterObject paraObj;//该对象池的属性参数对象
 private int currentNum = 0; //该对象池当前已创建的对象数目
 private StringBuffer currentObj;//该对象池当前可以借出的对象
 private Vector pool;//用于存放对象的池
 public ObjectPool(ParameterObject paraObj) {
  this.paraObj = paraObj;
  pool = new Vector();
 }
 public StringBuffer getObject() {
  if (pool.size() <= paraObj.getMinCount()) {
   if (currentNum <= paraObj.getMaxCount()) {
    currentObj = new StringBuffer();
    currentNum++;
   }
   . . .
  }
  return currentObj;
 }
 public void returnObject(Object obj) {
  // 确保对象具有正确的类型
  if (StringBuffer.isInstance(obj)) {
   . . .
  }
 }

  结束语

  恰当地使用对象池技术,能有效地改善应用程序的性能。目前,对象池技术已得到广泛的应用,如对于网络和数据库连接这类重量级的对象,一般都会采用对象池技术。但在使用对象池技术时也要注意如下问题:

  ·并非任何情况下都适合采用对象池技术。基本上,只在重复生成某种对象的操作成为影响性能的关键因素的时候,才适合采用对象池技术。而如果进行池化所能带来的性能提高并不重要的话,还是不采用对象池化技术为佳,以保持代码的简明。

  ·要根据具体情况正确选择对象池的实现方式。如果是创建一个公用的对象池技术实现包,或需要在程序中动态指定所池化对象的Class类型时,才选择通用对象池。而大部分情况下,采用专用对象池就可以了。
posted @ 2010-03-05 15:24 小菜毛毛 阅读(197) | 评论 (0)编辑 收藏

AppFuse是一个集成了众多当前最流行开源框架与工具(包括Hibernate、ibatis、Struts、Spring、DBUnit、Ant、Log4J、Struts Menu、Xdoclet、SiteMesh、OSCache、JUnit、JSTL)于一身的Web开发框架。AppFuse提供了Web系统开发过程中都需要开发的一些功能,如登陆、用户密码加密,用户管理、根据不同的用户可以展现不同的菜单.J2EE开发者也可以在此基础上开发加入自己的功能模块。利用这个框架可以大幅度的提高开发速度。

转自:http://blog.chinaunix.net/u/11409/showart_436247.html

http://blog.csdn.net/changzhang/category/369570.aspx可参考这个网址
AppFuse是一个集成了当前最流行的Web应用框架的一个更高层次的Web开发框架,也可以说是一个Web开发基础平台,它与它所集成的各种框架相比,它提供了一部分所有Web系统开发过程中都需要开发的一些功能,如登陆、用户密码加密,用户管理、根据不同的用户可以展现不同的菜单,可以自动生成 40%-60%左右的代码,自带了默认的一些在CSS中设定的样式,使用这些样式能很快的改变整个系统的外观,还有自动化测试的功能。

它最大的价值就是为我们提供了一个Web开发的新的方式和思路,尽管这些技术在国外都已进很流行了,但在国内能够将Hibernate、Struts、 Spring、DBUnit、Ant、Log4J、Struts Menu、Xdoclet、SiteMesh、Velocity、JUnit、JSTL、WebWork这些技术集成到一个框架中的还不多见,所以即使不使用它的全部功能,它也给我们提供了一个很好的借鉴、学习的机会。

通过关注AppFuse,我们可以看到目前国外的主流开发都使用了哪些技术,开发方式是什么样的,可能达到什么样的结果,而在以前,是很少能够看到这样完整的例子的。

AppFuse的另一个启示是:我们可以依靠开源软件的功能降低开发成本,而且可以阅读开源软件的代码提高所在团队的整体实力。

AppFuse 的作者 Matt Raible是当今开源世界一个比较活跃的开发者,它是AppFuse、Struts Menu的作者,也是XDoclet、DisplayTag等一些著名开源项目的积极参与者,《Hibernate In Action》的作者就在感谢的名单里面提到他,XDoclet的下载版本中所带的Hibernate标签部分的例子就是他写的,他还是2004年 Apache技术年会的主讲人之一。

但是通过2个月的实际学习和使用,我也遇到一系列的问题,因为AppFuse是将其他的一些类库或者框架集成在一起的,集成的技术众多,而且有一些技术在国内甚至很少有人知道,资料也比较少,所以虽然作者经过了一些测试,但都是基于英文编码的,而对于中文编码来说,还潜在的存在着一些问题,虽然不是AppFuse的问题,但却降低了开发速度,下面是我在开发过程中遇到过的问题,有些解决了,有些还没有解决:
一.Struts
1. AppFuse中默认的MVC框架是Struts,而且使用的是LookupDispatchAction,并且使用的是按钮(button),在XP下用IE浏览效果还可以,但如果在2000或者98下,就使外观很难看,而且当时我还遇到一个问题:如果按钮显示中文,则在DisplayTag中翻页失灵,而且报错,后来我把BaseAction的相关方法改变了,才可以使用,因为国内的客户都比较重视界面,所以后来我将那些按钮都改成图片了,当然也要添加一些方法了,有点麻烦!
2. Struts中的标签如今推荐使用的只有html部分的标签了,其他的标签或者可以使用JSTL替代,或者已经不推荐使用了,而且AppFuse中推荐使用JSTL,而JSTL和struts的标签的联合使用时,需要的不是<html:标签>,而是<html-el:标签>,这个问题曾经困扰了我整整2天。
3. Struts的Validation的校验规则并不完善,比如如果使用客户端的javascript校验,则在邮箱中输入汉字根本校验不出来,到了服务器端报错。
4.最严重的问题是AppFuse生成的Struts的validation.xml文件中有许多多余的“.”,如果你去掉了,常常在执行ant的 deploy任务时又恢复原样。这样是提交表单的时候经常会报javascript的脚本错误或者缺少对象或者缺少value,所以我会手工的修改这个文件,然后把修改后的文件备份,当重新生成有错误的文件时,我会用备份的没有错误的文件去覆盖。
5. Struts的validatioin对于使用同一个FormBean的Action的校验方式比较复杂。(待解决)。
二.Hibernate
1. Hibernate是现在受到越来越多的人推崇的一个ORM工具(框架、类库),它将我们从繁琐的使用JDBC的开发过程中解放出来,但同时也带来了新的问题,如学习曲线,执行效率,数据库设计优化,还有最重要的灵活性。Hibernate不是一个很容易上手的东西,要完全驾驭它还需要读很多资料,但好的资料却很少。
2. 使用Xdoclet可以很方便的生成Hibernate中的持久类的配置文件(*.hbm.xml),但对一些特殊的映射却无能为力,如使用序列的id生成规则,序列的名字没有地方写,所以也只好先利用它生成主要的内容,然后手工修改。
3. 同样还是id的生成策略问题,如果使用序列、hilo等需要一些数据库机制支持的策略时,schemaExport并不能自动生成序列或者保存当前id的表,这项工作仍然要手工解决。
4. Hibernate中提供了几种关联,一对一、一对多、多对多,但对于怎样调整效率却没有一个很明确的提示,还要根据情况判定,这就带来和一些弹性的设计。
5. Hibernate中可以选择的操作数据库的方式有3种,其中HQL功能最强大,但有些功能使用标准查询可能会更方便,但会有一些限制,所以虽然它很灵活,但易用性不如JDBC好。
三.Spring
在AppFuse的过程中,Spring完全隐藏在幕后,除了一些配置外,几乎感觉不到它的存在,所以我在使用它的过程中并没有遇到什么麻烦,这里只是简单的介绍一下它在AppFuse中起到的作用。
1. Spring在AppFuse中起到的主要作用是对Hibernate的Session和事务的管理,利用Spring封装的Hibernate模板类,我们大大地减少了实现DAO的代码行数。
2. Spring还起到了连接映射文件和类之间的关联,及接口和实现类之间的关联,这些都依赖于Spring的IoC的机制的实现。
3. 对于字符进行编码和解码部分用到了Spring自带的Filter,只需要在配置文件中配置就好了。

四.SiteMesh
SiteMesh是一个基于Decorator模式的技术,它可以修饰返回的网页文件,它的工作方式受到越来越多的人的推崇,这点从Manning出版的一些技术书籍中可以看出来。
我在使用SiteMesh的过程中并不顺利,我参考了《Java Open Source Programming》,这本书中说SiteMesh在默认的情况下不对下载文件进行装饰,但我在下载文件时发现,我的文件内容被丢弃了,取而代之的是 SiteMesh的模板的内容,后来我通过修改SiteMesh的配置文件解决了这个问题,但感觉还有一些不太清楚的地方需要学习。

五.DisplayTag
DisplayTag 是一个优秀的显示内容的标签,从SourceForge的访问量来看,它是很活跃的项目,仅次于Ant、Hibernate、Xdoclet等几个著名的项目,我总结,它的主要功能有4项:显示、分页、排序、将显示的数据写入指定类型的文件中,然后下载。
1. 据我使用的情况看,我只使用了分页和显示的功能,因为当时我没有很好的解决中文编码的问题,所以排序会有问题,直到昨天,我在朋友的帮助下解决了这个问题,至此我可以放心使用的功能又增加了排序(我昨天简单的测试了一下是可以的)。

2. 但对于将显示的内容生成到一个指定格式的文件中的功能却有着很多缺陷,如:
(1) 生成的文件中只有显示的数据,那些没有显示在界面上的的数据,则不会被写到文件中。
(2) 如果修改了DisplayTag的显示的内容,比如添加一列,在这列中的内容不是字符,而是HTML的标签,则生成的文件只有这些HTML标签,而没有数据。
(3) 即使DisplayTag中没有我们定制的HTML脚本,生成的文件偶尔也有问题,比如:它会把“007”生成为“7”,把字符串自动的转换为整型值。有时候还生成空白内容的文件。
(4) DisplayTag生成的Excel文件兼容性不好,有时在Excel2003中不能正常打开,或者在XP下打开报错。
后来,我看了作者写的《Spring Live》,书中说如果想实现稳定的Excel,推荐使用POI,于是我使用POI生成Excel,稳定性和兼容性都不错。

六.DBUnit
DBUnit是一个可以被Ant集成的向数据库中添加数据和备份数据的一个类库,配置很方便,因为AppFuse已经集成好了,所以使用也很容易。
但是如果你使用EditPlus之类的工具手工修改了AppFuse生成的内容,则执行Ant的setup、setup-db或者deploy的任务时,常常报错,说无效的格式,这是因为这个被手工修改的文件再次被AppFuse执行后,它的第一行的文件声明的前几个字母是无效的,是因为本地的字符集编码的原因而引起了乱码,如果把这几个无效的字母去掉,问题就解决了。

七.Struts Menu
Struts Menu也是AppFuse的作者开发的一个开源软件,它可以根据配置文件读取当前用户可以使用的功能菜单,这个功能是我一直以来都想要的,我也找到了一些代码,但实现的都不如这个完善,没什么好说的,使用简单,配置容易,很好的解决了我的问题。
问题是我只使用了AppFuse提供的2个角色,对于多个角色的实验我还没有做。

八.XDoclet
在AppFuse中,使用Xdoclet生成了几乎一切的配置文件:Struts-config.xml、web.xml、validation.xml、*.hbm.xml等文件,如果使用AppGen的话,还会生成更多的文件,这一切都是使用Xdoclet实现的。
问题是我在Struts部分提到的,生成的Validation.xml文件中会多生成一个“.”,另外在生成资源文件时也会多生成一个“.”,目前我没有很好的阅读这段代码,不知道是不是Xdoclet的问题。

九.Ant
Ant并没有什么问题,但在执行作者写的Ant任务的时候,有一些任务不能正常执行,比如,运行模拟对象测试的任务,作者也在1.7版本的修复列表中提到以前版本有些ant任务不能执行,在1.7中修改了一些ant任务,使他们能够正常的执行了。
实际上,我们如果使用AppGen进行开发的话,使用的任务一般不超过8个。

十.JSTL
JSTL 是个好东西,我常用的有<c:>和<fmt:>部分的标签,但是如果使用JSTL进行逻辑判断,我并没有感觉比使用JSP的代码块优雅多少。另外,熟悉JSTL也需要一段时间,我就经历了面对着JSP页面不知道该怎么写JSTL语法的困境。当然,AppFuse中使用的基本都是 JSTL,包括向DisplayTag传递显示的数据,使用的都是JSTL语法,这方面的资料挺多,我参考的是电子工业出版社出的《JSP2.0技术》,说的很详细。

十一.Tomcat
你也许会说:“Tomcat就不用说了吧?”,是的,Tomcat一般都会使用,但是 ―――――――――――――Tomcat5和Tomcat4.X对于中文编码使用了不同的机制,这个问题困扰了我好久,我解决了页面上写入汉字显示乱码的问题,我也曾经以为DisplayTag对汉字不能排序,也不能正常分页是因为DisplayTag的开发者都是老外,是因为他们没有考虑中文的关系的原因。
直到昨天,我才知道这一切都是因为Tomcat5对汉字编码的实现的方式和Tomcat4不一样的原因,如果感兴趣,可以看看这个帖子: http://www.javaworld.com.tw/jute/post/view?bid=9&id=44042&sty=1&tpg=1&age=0

十二.JavaScript
JavaScript简单易学,但想运用自如就不太容易了。AppFuse中嵌入了几个js文件,里面包含了许多函数,值得我们好好的研究一下,比如,如果有一个必填字段没有填写,AppFuse会自动的聚焦在那个input上,类似的小技巧有很多,你可以自己去翻看。
但AppFuse 自带的JavaScript脚本有一个Bug,就是当DisplatyTag中没有可以显示的数据时,你用鼠标单击,它会报JavaScript错误,你仔细研究一下function highlightTableRows(tableId) 就知道了:我的解决办法是在location.href = link.getAttribute("href");前面添加一行判断:if (link != null)。

十三.资源文件国际化
对于Struts和DisplayTag都涉及到资源文件国际化AppFuse1.6.1很好的解决了Struts资源映射文件国际化的问题,你只需要在对应本国语言的资源文件中写入汉字,Ant中有一项执行native2ascii的任务,AppFuse自动的为你进行了资源文件的编码转换,而对于 DisplayTag的资源文件问题,还要自己执行native2ascii命令,为了避免每次都输入一串命令,我用Delphi写了个小工具,可视化的选择资源文件,点击按钮自动执行该命令,底层依赖于JDK。


经过2个多月的学习,我感觉这个框架非常不错,它为我以后的项目开发指出了一个新的方向,但如果想很熟练的使用这个框架进行开发,至少要对以下几种技术比较熟练:Struts(或者WebWork、Spring及其他的已经整合进来的MVC框架)、Hibernate(或者ibatis)、JSTL,当然其他的技术至少也要知道一点,否则遇到问题都不知道出在哪里。


目前我还没有解决的问题有:
1. 如何在翻页的时候才读取下面的数据?
2. 怎样对使用同一个FormBean的多个Form进行客户端校验?
3. 怎样优化Hibernate的效率?《Hibernate In Action》中提供了多种策略,有些时候应该使用lazy,有些时候应该使用outer-join。
4.在什么时机生成导出文件?目前我是在查询的Action中同时生成了导出文件,否则,到了下一页,我就不知道查询条件了,当然,如果把拼装后的HQL存储在Session或者Hidden中也可以解决这个问题,但是这样就破坏了DAO的封装,要把DAO封装后的HQL发送给Action,然后发送的到 Web界面层,所以目前我还在犹豫生成导出文件的时机选择在哪里?
5. 什么时候应该自己获取数据库连接,执行native SQL?具体需要注意些什么?
6. SiteMesh的模板优化?
7. DisplayTag的底层实现?


每个问题都比较棘手,要一个一个解决!

这个框架的优点是:如果熟悉了开发流程,可以大幅度的提高开发速度,如果业务不是很复杂,使用AppGen可以生成60%左右的代码,而且程序可维护性好,因为作者使用了多个设计模式对各个层面进行了封装,所以不同的模块代码风格出奇的一致,有利于开发人员快速上手,也有利于接收其他开发人员遗留的代码。

兔八哥
2004-2-3下午15:51

++++++++++++++++++++

引用:
有没有成功的项目同我们分享一下?

 

我已经使用AppFuse开发了2个项目了,都是教育系统的,系统都不大,我一个人用实际开发一个月,因为是公司的项目,源码不好外发,但主要的东西都是一样的,对于AppFuse我也没有修改多少,否则,往新版本移植就会有问题了。
我遇到的问题,能想起来的我都写下来了,如果有其他的问题,我们可以一起讨论。
最近我有个朋友在使用WebWork的AppFuse版本进行开发,他遇到的问题和我基本差不多,有交流才有进步,呵呵!
Very Happy

感谢楼上几位的热心解答,谢谢!

+++++++++++++++++++++++

引用:
目前我还没有解决的问题有:
1. 如何在翻页的时候才读取下面的数据?
2. 怎样对使用同一个FormBean的多个Form进行客户端校验?
3. 怎样优化Hibernate的效率?《Hibernate In Action》中提供了多种策略,有些时候应该使用lazy,有些时候应该使用outer-join。
4.在什么时机生成导出文件?目前我是在查询的Action中同时生成了导出文件,否则,到了下一页,我就不知道查询条件了,当然,如果把拼装后的HQL存储在Session或者Hidden中也可以解决这个问题,但是这样就破坏了DAO的封装,要把DAO封装后的HQL发送给Action,然后发送的到 Web界面层,所以目前我还在犹豫生成导出文件的时机选择在哪里?
5. 什么时候应该自己获取数据库连接,执行native SQL?具体需要注意些什么?
6. SiteMesh的模板优化?
7. DisplayTag的底层实现?


1.关于翻页的问题,如果你刚开始使用AppFuse开发的话,推荐使用valuelist,它可以和Hibernate很好的集成,我的一个网友用的就是这个东西,虽然界面没有DisplayTag漂亮,但关于分页却不用你操太多的心,
因为这几天天天开会,所以也没有做些技术实验,另一个朋友告诉我有一个老外把DisplayTag分页部分修改了,在JIRA上有源码下载,我下来了,还没有看,还有一个思路,就是分析DisplayTag的分页的格式,然后用Filter解析,然后把当前页号传入DAO,然后使用标准查询进行查询分页,但要对AppFuse的接口和方法添加参数,正在犹豫中,还有更简单的方法,直接在Session中放入当前的页号,每次都刷新,就不用Filter了,然后同样修改方法和接口。

2.对于Struts的使用同一个FormBean的多个Form进行客户端校验,在孙卫琴的Struts的书中已经提到了,即使她的方法不管用,也可以手写JavaScript来解决,只是我不愿意而已,如果别无他法,则只能如此了。

3. 优化Hibernate的效率,其实对我的程序来说问题不大,我的表比较少,基本只有3层的级联,而且对于数据字典和业务表之间的关联,我采用的是业务表到数据字典表的many-to-one,这样的单向关联比较简单,也能够满足我的要求,性能会好一点点,再加上分页功能,只查询当前也内容,然后参考《Hibernate In Action》的第七章的内容,提高接收效率应该是没有问题的。

4.关于到处文件的时机,我正在看关于模式的书籍,正在找答案,其实还有个简单的办法,就是把生成导出文件单独的实现,用户点击按钮才生产,当然这样就要把用户的查询条件记下来,当然也涉及到修改接口和方法了,AppFuse中,修改接口和方法很麻烦,如果代码生成后再修改,要改动很多处,所以前期设计很重要。

5.关于这一点,我一直在找一个硬性的标准,比如关联表超过多少个等等条件就应该自己获取数据库连接,但现在看来这也不是绝对的,如果能够大幅度提高效率,或者使用 native SQL可以减少工作量或者节省时间的话,那就可以使用,只是要对这种方式的利弊要有所了解,不幸的是,我还没有这样试过,试过的朋友请提供些建议,谢谢!

6.SiteMesh的优化,我看到一片文章,也是个老外写的,忘了出处,说SiteMesh对性能影响不大,可以放心使用,那我就暂时不考虑它了,呵呵。

7.DisplayTag的底层的原理我早就知道,而且它的文档的流程图也有,只是我需要知道更详细的实现细节,最近在读源码,应该很快就有结果了,如果我有好消息,会来这里把解决方案贴出来。


上面的文字只是我目前的一些思路,因为天天开会,也没有做技术实验,还不知道可行的程度,但我想应该都可以实现,我只不过是在找更好的办法,如果你有任何好的思路或者建议,请不吝告知,谢谢,任何建议和意见都是受欢迎的,只是要详细些,不要拿些空洞的模式来敷衍我,呵呵,目前这样的“高手”不少,我有些受够了,呵呵

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/changzhang/archive/2008/03/12/2172675.aspx



posted @ 2010-03-05 14:40 小菜毛毛 阅读(634) | 评论 (0)编辑 收藏

第 7 章 使用filter过滤请求

注意

Filter虽然很常用,但是覆盖的范围太广,这里我们只介绍设置编码和控制权限的过滤器,其他的使用方式还需要大家自行积累。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 8 章 配置listener监听器

  1. 了解Filter的使用。

7.1. 批量设置请求编码

编码问题会不会成为中国人学java的标志呢?

通过之前的讨论第 2.2.2 节 “POST乱码”,我们知道为了避免提交数据的乱码问题,需要在每次使用请求之前设置编码格式。在你复制粘贴了无数次request.setCharacterEncoding("gb2312");后,有没有想要一劳永逸的方法呢?能不能一次性修改所有请求的编码呢?

用Filter吧,它的名字是过滤器,可以批量拦截修改servlet的请求和响应。

我们编写一个EncodingFilter.java,来批量设置请求编码。

package anni;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodingFilter implements Filter {
public void init(FilterConfig config) throws ServletException {}
public void destroy() {}
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding("gb2312");
chain.doFilter(request, response);
}
}

在此EncodingFilter实现了Filter接口,Filter接口中定义的三个方法都要在EncodingFilter中实现,其中doFilter()的代码实现主要的功能:为请求设置gb2312编码并执行chain.doFilter()继续下面的操作。

与servlet相似,为了让filter发挥作用还需要在web.xml进行配置。

<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>anni.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

filter标签部分定义使用的过滤器,filter-mapping标签告诉服务器把哪些请求交给过滤器处理。这里的/*表示所有请求,/表示根路径,*(星号)代表所有请求,加在一起就变成了根路径下的所有请求。

这样,所有的请求都会先被EncodingFilter拦截,并在请求里设置上指定的gb2312编码。

例子在lingo-sample/07-01目录下,这次我们不需要在test.jsp中为请求设置编码也可以得到正常的中文参数了,EncodingFilter圆满的完成了它的工作。

7.2. 用filter控制用户访问权限

出于信息安全和其他一些原因的考虑,项目中的一些页面要求用户满足了一定条件之后才能访问。比如,让用户输入帐号和密码,如果输入的信息正确就在session里做一个成功登录的标记,其后在请求保密信息的时候判断session中是否有已经登录成功的标记,存在则可以访问,不存在则禁止访问。

如07-02例子中所示,进入首页看到的就是登录页面。

现在用户还没有登录,如果直接访问保密信息,就会显示无法访问保密信息的页面,并提醒用户进行注册。

返回登录页面后,输入正确的用户名和密码,点击登录。

后台程序判断用户名和密码正确无误后,在session中设置已登录的标记,然后跳转到保密信息页面。

我们要保护的页面是admin/index.jsp,为此我们在web.xml进行如下配置。

<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>anni.SecurityFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SecurityFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>

定义SecurityFilter过滤器,让它过滤匹配/admin/*的所有请求,这就是说,对/admin/路径下的所有请求都会接受SecurityFilter的检查,那么SecurityFilter里到底做了些什么呢?

public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession();
if (session.getAttribute("username") != null) {
chain.doFilter(request, response);
} else {
res.sendRedirect("../failure.jsp");
}
}

首先要将ServletRequest和ServletResponse转换成HttpServletRequest和HttpServletResponse,因为Filter本来设计成为多种协议服务,http协议仅仅是其中一部分。不过我们接触到的也只有http,而且也只有转换成对应HttpServletRequest和HttpServletResponse才能进行下面的session操作和页面重定向。

得到了http请求之后,可以获得请求对应的session,判断session中的username变量是否为null,如果不为null,说明用户已经登录,就可以调用doFilter继续请求访问的资源。如果为null,说明用户还没有登录,禁止用户访问,并使用页面重定向跳转到failure.jsp页面显示提示信息。

session中的username实在登录的时候设置进去的,值就是登录用户使用的用户名,详细代码可以参考07-02/WEB-INF/src/LoginServlet.java,登录和注销都写成了servlet并映射到/login.do和/logout.do这两个请求路径上。源代码和web.xml配置请自行参考07-02中的例子,这里就不复述了。

我们再来看看页面重定向的写法,res.sendRedirect()中使用的是"../failure.jsp",两个点(..)代表当前路径的上一级路径,这是因为SecurityFilter负责处理的是/admin/下的请求,而/failure.jsp的位置在/admin/目录的上一级,所以加上两个点才能正确跳转到failure.jsp。当然这里使用forward()也可以,但是要注意在不同路径下做请求转发会影响页面中相对路径的指向。相关讨论在:第 3.4.2 节 “forward导致找不到图片”

7.3. filter所谓的特性

7.3.1. 请求映射

filter-mapping和servlet-mapping都是将对应的filter或servlet映射到某个url-pattern上,当客户发起某一请求时,服务器先将此请求与web.xml中定义的所有url-pattern进行匹配,然后执行匹配通过的filter和servlet。

你可以使用三种方式定义url-pattern。

  1. 直接映射一个请求。

    <servlet-mapping>
        <servlet-name>ContactServlet</servlet-name>
        <url-pattern>/contact.do</url-pattern>
        </servlet-mapping>
        

    第 6.3 节 “使用servlet改写联系簿”中对servlet的映射,只有当请求是/contact.do的时候才会执行ContactServlet。/contact.do?id=1或/contact.do?method=list&id=1的请求也可以匹配到ContactServlet,这是因为根据http规范,请求的路径不包含问号以后的部分。

  2. 映射一个路径下的所有请求。

    <servlet-mapping>
        <servlet-name>EncodingFilter</servlet-name>
        <url-pattern>/*</url-pattern>
        </servlet-mapping>
        

    第 7.1 节 “批量设置请求编码”中这样使用星号(*)的形式,可以将某个路径下的所有请求都映射到EncodingFilter过滤器下,如果这个路径下还有子路径,那么子路径下的请求也会被EncodingFilter过滤。所以 /*这种写法就会过滤应用下所有的请求。

    如果像第 7.2 节 “用filter控制用户访问权限”中那样把映射配置成/admin/*,就会只处理/admin/路径下的请求,不会处理根路径下的/index.jsp和/failure.jsp。

    需要注意的是,这种写法必须以/开头,写成与绝对路径的形式,即便是映射所有请求也要写成/*,不能简化成*。

  3. 映射结尾相同的一类请求。

    <servlet-mapping>
        <servlet-name>ControllerServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
        </servlet-mapping>
        

    具体效果请参考07-03的例子,index.jsp中有四个链接,分别指向/a1.do, /a2.do, /xx/b1.do, /xx/yy/c1.do。

    web.xml中的ControllerServlet会接收以.do结尾的请求,并使用forward将请求转发到/test.jsp。

    点击/a1.do的情况。

    点击/xx/yy/c1.do的情况。

    这样做的一个好处是语义更清楚,只要看到以.do结尾的请求就知道肯定是交给ControllerServlet处理了,不管这个请求是在根路径还是子路径下,都会准确无误的找到对应的servlet。

    缺点就是不同路径之间进行forward,jsp里就不能再使用相对路径了,所以我们在test.jsp中使用request.getContextPath()获得当前应用在服务器中的位置(例子中是/07-03)将相对路径都组装成绝对路径,这种用法在以后也会经常用到。

    <%
        pageContext.setAttribute("ctx", request.getContextPath());
        %>
        <p><a href="${ctx}/index.jsp">返回</a></p>
        

    最后需要注意的是,这种请求映射就不能指定某一路径了,它必须是以星号(*)开始字母结尾,不能写成/*.do的形式。

现在咱们也发现java的请求映射有多傻了,灵活配置根本是不可能的任务。

想要获得所有以user开头.do结尾的请求吗?user*.do在url-pattern是无法识别的,只能配置成*.do,再去servlet中对请求进行筛选。

想要让一个servlet负责多个请求吗?/user/*,/admin/*,*.do写在一起url-pattern也不认识,只能配成多个servlet-mapping。

<servlet-mapping>
<servlet-name>ControllerServlet</servlet-name>
<url-pattern>/user/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ControllerServlet</servlet-name>
<url-pattern>/admin/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ControllerServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

java的复杂性在此处显露无疑。实际使用时,最好不要依赖web.xml中的配置,在自己的类中实现灵活配置才是正途。

7.3.2. 过滤链

其实在07-02这个例子里,我们使用了两个过滤器,EncodingFilter负责设置编码,SecurityFilter负责控制权限,那这两个过滤器是怎么起作用的呢?它们两个同时过滤一个请求时谁先谁后呢?

下面这个图会告诉我们答案。

所有的奥秘就在Filter中的FilterChain中。服务器会按照web.xml中过滤器定义的先后循序组装成一条链,然后一次执行其中的doFilter()方法。执行的顺序就如上图所示,执行第一个过滤器的chain.doFilter()之前的代码,第二个过滤器的chain.doFilter()之前的代码,请求的资源,第二个过滤器的chain.doFilter()之后的代码,第一个过滤器的chain.doFilter()之后的代码,最后返回响应。

因此在07-02中执行的代码顺序是:

  1. 执行EncodingFilter.doFilter()中chain.doFilter()之前的部分:request.setCharacterEncoding("gb2312");

  2. 执行SecurityFilter.doFilter()中chain.doFilter()之前的部分:判断用户是否已登录。

    如果用户已登录,则访问请求的资源:/admin/index.jsp。

    如果用户未登录,则页面重定向到:/failure.jsp。

  3. 执行SecurityFilter.doFilter()中chain.doFilter()之后的部分:这里没有代码。

  4. 执行EncodingFilter.doFilter()中chain.doFilter()之后的部分:这里也没有代码。

过滤链的好处是,执行过程中任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容。而在实际使用时,就要特别注意过滤链的执行顺序问题,像EncodingFilter就一定要放在所有Filter之前,这样才能确保在使用请求中的数据前设置正确的编码。

7.4. filter的详细配置

我们已经了解了filter的基本用法,还有一些细节配置在特殊情况下起作用。

在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。

到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要forward的时候也用到Filter,这样就需要如下配置。

<filter>
<filter-name>TestFilter</filtername>
<filter-class>anni.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filtername>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>EXCEPTION</dispatcher>
</filter-mapping>

这样TestFilter就会过滤所有状态下的请求。如果我们没有进行设置,默认使用的就是REQUEST。而EXCEPTION是在isErrorPage="true"的情况下出现的,这个用处不多,看一下即可。

这里FORWARD是解决request.getDispatcher("index.jsp").forward(request, response);无法触发Filter的关键,配置上这个以后再进行forward的时候就可以触发过滤器了。

Filter还有一个有趣的用法,在filter-mapping中我们可以直接指定servlet-mapping,让过滤器只处理一个定义在web.xml中的servlet。

<filter-mapping>
<filter-name>TestFilter</filter-name>
<servlet-name>TestServlet</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>anni.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/TestServlet</url-pattern>
</servlet-mapping>

直接指定servlet-name,TestFilter便会引用TestServlet配置的url-pattern,在某些filter与servlet绑定的情况下不失为一个好办法。

posted @ 2010-03-05 12:33 小菜毛毛 阅读(6225) | 评论 (0)编辑 收藏

1.UTF-8

原来用惯了GBK,被大家一再怂恿,才下定决心整个项目换用UTF-8编码。

编码问题几乎与所有国内Java项目相伴而生,不同内核的Linux、英文版日文版的WindowsXP总是让GBK很头痛,而改用UTF-8后,忽然就轻爽了,UTF-8,全世界语系连马尔代夫语都有自己的座位,实在找不到理由为什么还要用只支持大中华的GBK。

而且Springside也成了一个带有大量UTF-8中文注释的项目。

原GBK项目的转换方法

  把自己当成车衣厂女工,机械的使用UltraEdit和EditPlus的转换功能,忙活一两个小时,怎么都可以转完所有文件了。

  其中UltraEdit的转换方法是文件--〉转换--〉ASCII to UTF-8(Unicode编辑)。最好在Advanced-〉本地代码页那里也设一下UTF-8。

  然后就可以用设了UTF-8选项的IDE如IDEA,Eclipse直接编辑了。

2.国际化 I18N

      后台的menu.jsp 和 editBook.jsp 还有业务异常类,演示了i18N的应用。

2.1 i18N基础      

  1.编写messages.zh_CN.properties

     用中文写完后,用java带的native2ascii.exe或者ant的native任务把它转成非人类的Unicode编码。

  2.在纯Java API里,国际化是这样做的:

ResourceBundle rb = ResourceBundle.getBundle("messages");
String welcome = rb.getString("welcome");
String welcomeCalvin = MessageFormat.format(welcome,new String[]{"calvin"});

  第一二句从classpath里找到messages_zh_CN.properties,读出"欢迎你,{0}"字样。

  第三局把上文格式化成"欢迎你,calvin" 

 3.在Servlet环境下,国际化是这样做的   

51.la 专业、免费、强健的访问统计 则靠<fmt:message>tag

 

<%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt" %>
<fmt:message key="welcome"/>

      可以用context-param来定义默认的properties文件

    <context-param>
            <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
            <param-value>messages</param-value>
    </context-param>

     不过这样定义的缺点是只能定义一个文件,如果所有信息都集中在一个文件会好长,而如果有多个properties,就惟有在页面用<fmt:bundle>绑定了。

2.2 Spring的messageSource增强

Spring增加了MessageSource的概念

  一是ApplicationContext将充当一个单例的角色,不再需要每次使用i18时都初始化一次ResourceBundle

  二是可以代表多个Resource Bundle.

  在ApplicationContext的定义文件中,增加如下节点: 

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">

    <property name="basename" value= "messages"/>
</bean>

在pure Java中

context.getMessage("welcome", null, LocaleContextHolder.getLocale())

而经Spring MVC JSTL ViewResolver调用的JSP,<fmt:message>将继续发挥它的功效。

不过这里挺麻烦的,第一如果不是从MVC JSTL转的,messageSource的定义就会失效。而如果定义了<context-param>,则messageSource的定义又会失效.......

 还有,<spring:message> 鸡肋一块,因为它如果找不到key对应的就会抛异常,好恐怖。

还还有,spring还有有趣的theme机制,和i18n一样的原理,解决了"做成图片的文字"的国际化,让不同语言的美术字图片的路径分别定义在theme_zh_CN.properties和theme_en_US.properties里面。

posted @ 2010-03-05 11:36 小菜毛毛 阅读(811) | 评论 (0)编辑 收藏

一句话概括:==比较的是两个对象的引用(即内存地址)是否相等,而equals()比较的是两个对象的值(即内存地址里存放的值)是否相等。当然equals()在个别类中被重写了那就例外了。

详细论述:eqauls 与 = =之异同

1)比较方式角度:

= =是面向过程的操作符;equals是面向对象的操作符

= =不属于任何类,equals则是任何类(在Java中)的一个方法;

我们可以1)Primitive1 (基本类型)= = Primitive2(基本类型);

         2)Object Reference1(对象引用)= = Object Reference2(对象引用)

         3)Object Reference1 (对象引用) .equals(Object Reference2 (对象引用))

           这三种比较

           但却不能Primitive1 (基本类型).equals( Primitive2(基本类型));

 对于基本类型,没有面向对象中发送消息一说,自然也不会有

方法成员。

 

2)比较目的角度:

1)    如果要比较两个基本类型是否相等,请用= =;

2)    如果要比较两个对象引用是否相等,请用= =;

3)    如果要比较两个对象(逻辑上)是否一致,请用equals;

 

对两个对象(逻辑上)是否一致的阐释:

   有人会问:在C++中, 比较两个对象相等不是也可以用==吗?我知道您是指运算符重载,但是很遗憾,Java中不支持运算符重载(java中亦有重载过运算符,他们是“+”,“+=”,不过也仅此两个,而且是内置实现的);所以,对象的是否相等的比较这份责任就交由  equals()来实现 。   

这个“逻辑上”其实就取决于人类的看法,实际开发中,就取决于用户的需求;


第三节:equals()缘起:

        equals()是每个对象与生俱来的方法,因为所有类的最终基类就是Object(除去Object本身);而equals()是Object的方法之一。

        我们不妨观察一下Object中equals()的source code:

         public boolean equals(Object obj) {

                      return (this == obj);

          }

        注意 “return (this == obj)”

        this与obj都是对象引用,而不是对象本身。所以equals()的缺省实现就是比较

        对象引用是否一致;为何要如此实现呢? 前面我们说过:对象是否相等,是由我们的需求决定的,世界上的类千奇百怪(当然,这些类都是我们根据模拟现实世界而创造的),虽然Object是他们共同的祖先,可他又怎能知道他的子孙类比较相等的标准呢?但是他明白,任何一个对象,自己总是等于自己的,何谓“自己总是等于自己”呢,又如何判断“自己总是等于自己”呢?一个对象在内存中只有一份,但他的引用却可以有无穷多个,“对象自己的引用1=对象自己的引用2”,不就能判断“自己总是等于自己”吗?所以缺省实现实现自然也就是

        “return (this == obj)”;

        而到了我们自己编写的类,对象相等的标准由我们确立,于是就不可避免的要覆写

        继承而来的public boolean equals(Object obj);

        如果您有过编覆写过equals()的经验(没有过也不要紧),请您思考一个问题:

         “两个对象(逻辑上)是否一致”实际上是比较什么?没错,或许您已脱口而出:

       就是对象的属性(即field,或称数据成员)的比较。方法是不可比较的哦。(这个问题是不是有些弱智呢?哈哈)

 第四节:对一个推论的思考

推论如下:一言以蔽之:欲比较栈中数据是否相等,请用= =;

                      欲比较堆中数据是否相等,请用equals;

因为(根)基本类型,(根)对象引用都在栈中; 而对象本身在堆中;

         这句话又对又不对,问题出在哪,就是“数据”二字,先看栈中,数据或为基本类型,或为对象引用,用==比较当然没错;但是堆中呢?对象不是堆中吗?不是应该用equals比较吗?可是,我们比较的是堆中“数据”,堆中有对象,对象由什么构成呢?可能是对象引用,可能是基本类型,或两者兼而有之。如果我们要比较他们,该用什么呢,用”equals()”?不对吧,只能是”= =”!所以正确的结论是:欲比较栈中数据是否相等,请用= =; 欲比较堆中数据是否相等,请用equals;

因为(根)基本类型,(根)对象引用都在栈中(所谓“根”,指未被任何其他对象所包含); 而对象本身在堆中。

文章出处:DIY部落(http://www.diybl.com/course/3_program/java/javajs/2007917/71587.html#)

posted @ 2010-03-05 10:36 小菜毛毛 阅读(202) | 评论 (0)编辑 收藏

 1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

  2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

  3. Java中的数据类型有两种。

  一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

  另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

int a = 3;
int b = 3;

  编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

  特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

  另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。 4. String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

  5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:

  (1)先定义一个名为str的对String类的对象引用变量:String str;

  (2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。

  (3)将str指向对象o的地址。

  值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

  为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

  注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

  我们再来更进一步,将以上代码改成:

String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false

  这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

  事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。

  再修改原来代码:

String str1 = "abc";
String str2 = "abc";

str1 = "bcd";

String str3 = str1;
System.out.println(str3); //bcd

String str4 = "bcd";
System.out.println(str1 == str4); //true

  str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

  我们再接着看以下的代码。

String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false

  创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); //false

  创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

  以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

  6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。 7. 结论与建议:

  (1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

  (2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

  (3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

  (4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

posted @ 2010-03-05 10:35 小菜毛毛 阅读(199) | 评论 (0)编辑 收藏

 

JAVA语言中的反射机制:
    在Java 运行时 环境中,对于任意一个类,能否知道这个类有哪些属性和方法?
    对于任意一个对象,能否调用他的方法?这些答案是肯定的,这种动态获取类的信息,以及动态调用类的方法的功能来源于JAVA的反射。从而使java具有动态语言的特性。

  JAVA反射机制主要提供了以下功能:
      1.在运行时判断任意一个对象所属的类
      2.在运行时构造任意一个类的对象
      3.在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法)
      4.在运行时调用任意一个对象的方法(*****注意:前提都是在运行时,而不是在编译时)

  Java 反射相关的API简介:
      位于java。lang。reflect包中
        --Class类:代表一个类
        --Filed类:代表类的成员变量
        --Method类:代表类的方法
        --Constructor类:代表类的构造方法
        --Array类:提供了动态创建数组,以及访问数组的元素的静态方法。该类中的所有方法都是静态方法


----Class类
     在 java 的Object类中的申明了数个应该在所有的java类中被改写的methods:
hashCode(), equals(),clone(),toString(),getClass()等,其中的getClass()返回yige
Class 类型的对象。
     Class类十分的特殊,它和一般的类一样继承自Object,其实体用以表达java程序运行
时的 class和 interface,也用来表达 enum,array,primitive,Java Types 以及关键字void
,当加载一个类,或者当加载器(class loader)的defineClass()被JVM调用,便产生一个Class
对象,
     Class是Reflection起源,针对任何你想探勘的class(类),唯有现为他产生一个Class
的对象,接下来才能经由后者唤起为数十多个的反射API。


     Java允许我们从多种途径为一个类class生成对应的Class对象。
          --运用 getClass():Object类中的方法,每个类都拥有此方法
                                String str="abc";
                                Class cl=str.getClass();


         --运用 Class。getSuperclass():Class类中的方法,返回该Class的父类的Class
         --运用 Class。forName()静态方法:
         --运用 ,Class:类名.class
         --运用primitive wrapper classes的TYPE语法: 基本类型包装类的TYPE,如:Integer.TYPE
                      注意:TYPE的使用,只适合原生(基本)数据类型

----运行时生成instance
     想生成对象的实体,在反射动态机制中有两种方法,一个针对无变量的构造方法,一个针对带参数的
构造方法,,如果想调用带参数的构造方法,就比较的麻烦,不能直接调用Class类中的newInstance()
,而是调用Constructor类中newInstance()方法,首先准备一个Class[]作为Constructor的参数类型。
然后调用该Class对象的getConstructor()方法获得一个专属的Constructor的对象,最后再准备一个
Object[]作为Constructor对象昂的newInstance()方法的实参。
      在这里需要说明的是 只有两个类拥有newInstance()方法,分别是Class类和Constructor类
Class类中的newInstance()方法是不带参数的,而Constructro类中的newInstance()方法是带参数的
需要提供必要的参数。

    例:
      Class c=Class.forName("DynTest");
      Class[] ptype=new Class[]{double.class,int.class};
      Constructor ctor=c.getConstructor(ptypr);
      Object[] obj=new Object[]{new Double(3.1415),new Integer(123)};
      Object object=ctor.newInstance(obj);
      System.out.println(object);

----运行时调用Method
    这个动作首先准备一个Class[]{}作为getMethod(String name,Class[])方法的参数类型,接下来准备一个
Obeject[]放置自变量,然后调用Method对象的invoke(Object obj,Object[])方法。
     注意,在这里调用

----运行时调用Field内容
    变更Field不需要参数和自变量,首先调用Class的getField()并指定field名称,获得特定的Field对象后
便可以直接调用Field的 get(Object obj)和set(Object obj,Object value)方法

java 代码
  1. package cn.com.reflection;   
  2.   
  3. import java.lang.reflect.Field;   
  4. import java.lang.reflect.InvocationTargetException;   
  5. import java.lang.reflect.Method;   
  6.   
  7. public class ReflectTester {   
  8.   
  9.     /**  
  10.      * 在这个类里面存在有copy()方法,根据指定的方法的参数去 构造一个新的对象的拷贝  
  11.      * 并将他返回  
  12.      * @throws NoSuchMethodException   
  13.      * @throws InvocationTargetException   
  14.      * @throws IllegalAccessException   
  15.      * @throws InstantiationException   
  16.      * @throws SecurityException   
  17.      * @throws IllegalArgumentException   
  18.      */  
  19.     public Object copy(Object obj) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{   
  20.            
  21.         //获得对象的类型   
  22.         Class classType=obj.getClass();   
  23.         System.out.println("该对象的类型是:"+classType.toString());   
  24.            
  25.         //通过默认构造方法去创建一个新的对象,getConstructor的视其参数决定调用哪个构造方法   
  26.         Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});   
  27.            
  28.         //获得对象的所有属性   
  29.         Field[] fields=classType.getDeclaredFields();   
  30.            
  31.         for(int i=0;i
  32.             //获取数组中对应的属性   
  33.             Field field=fields[i];   
  34.                
  35.             String fieldName=field.getName();   
  36.             String stringLetter=fieldName.substring(01).toUpperCase();   
  37.                
  38.             //获得相应属性的getXXX和setXXX方法名称   
  39.             String getName="get"+stringLetter+fieldName.substring(1);   
  40.             String setName="set"+stringLetter+fieldName.substring(1);   
  41.                
  42.             //获取相应的方法   
  43.             Method getMethod=classType.getMethod(getName, new Class[]{});   
  44.             Method setMethod=classType.getMethod(setName, new Class[]{field.getType()});   
  45.                
  46.             //调用源对象的getXXX()方法   
  47.             Object value=getMethod.invoke(obj, new Object[]{});   
  48.             System.out.println(fieldName+" :"+value);   
  49.                
  50.             //调用拷贝对象的setXXX()方法   
  51.             setMethod.invoke(objectCopy,new Object[]{value});   
  52.                
  53.                
  54.         }   
  55.            
  56.         return objectCopy;   
  57.            
  58.     }   
  59.        
  60.        
  61.     public static void main(String[] args) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {   
  62.         Customer customer=new Customer();   
  63.         customer.setName("hejianjie");   
  64.         customer.setId(new Long(1234));   
  65.         customer.setAge(19);   
  66.            
  67.         Customer customer2=null;   
  68.         customer2=(Customer)new ReflectTester().copy(customer);   
  69.         System.out.println(customer.getName()+" "+customer2.getAge()+" "+customer2.getId());   
  70.            
  71.         System.out.println(customer);   
  72.         System.out.println(customer2);   
  73.            
  74.   
  75.     }   
  76.   
  77. }   
  78.   
  79.   
  80. class Customer{   
  81.        
  82.     private Long id;   
  83.        
  84.     private String name;   
  85.        
  86.     private int age;   
  87.        
  88.        
  89.     public Customer(){   
  90.            
  91.     }   
  92.   
  93.     public int getAge() {   
  94.         return age;   
  95.     }   
  96.   
  97.   
  98.     public void setAge(int age) {   
  99.         this.age = age;   
  100.     }   
  101.   
  102.   
  103.     public Long getId() {   
  104.         return id;   
  105.     }   
  106.   
  107.   
  108.     public void setId(Long id) {   
  109.         this.id = id;   
  110.     }   
  111.   
  112.   
  113.     public String getName() {   
  114.         return name;   
  115.     }   
  116.   
  117.   
  118.     public void setName(String name) {   
  119.         this.name = name;   
  120.     }   
  121.        
  122. }  


java 代码
  1. package cn.com.reflection;   
  2.   
  3. import java.lang.reflect.Array;   
  4.   
  5. public class ArrayTester1 {   
  6.   
  7.     /**  
  8.      * 此类根据反射来创建  
  9.      * 一个动态的数组   
  10.      */  
  11.     public static void main(String[] args) throws ClassNotFoundException {   
  12.            
  13.         Class classType=Class.forName("java.lang.String");   
  14.            
  15.         Object array= Array.newInstance(classType,10);  //指定数组的类型和大小   
  16.            
  17.          //对索引为5的位置进行赋值   
  18.         Array.set(array, 5"hello");   
  19.            
  20.         String s=(String)Array.get(array, 5);   
  21.            
  22.         System.out.println(s);   
  23.            
  24.            
  25.         //循环遍历这个动态数组   
  26.         for(int i=0;i<((String[])array).length;i++){   
  27.                
  28.             String str=(String)Array.get(array, i);   
  29.                
  30.             System.out.println(str);   
  31.         }   
  32.   
  33.     }   
  34.   
  35. }   
posted @ 2010-03-04 16:43 小菜毛毛 阅读(233) | 评论 (0)编辑 收藏

看到这个标题大家可能又想:哎,又一个重新发明轮子的人。在这里很想先声明一下,写这篇文章只是想让大家了解一下Spring到底是怎么运行的,并不是想重造轮子噢,希望大家看完这篇文章后能对Spring有更深入的了解,希望这篇文章对你有所帮助喔!好,言归正传,让我们来一起探索吧!
我们先从最常见的例子开始吧
Java代码 复制代码
  1. public static void main(String[] args) {   
  2.         ApplicationContext context = new FileSystemXmlApplicationContext(   
  3.                 "applicationContext.xml");   
  4.         Animal animal = (Animal) context.getBean("animal");   
  5.         animal.say();   
  6.     }  

这段代码你一定很熟悉吧,不过还是让我们分析一下它吧,首先是applicationContext.xml
Java代码 复制代码
  1. <bean id="animal" class="phz.springframework.test.Cat">   
  2.         <property name="name" value="kitty" />   
  3.     </bean>  

他有一个类phz.springframework.test.Cat
Java代码 复制代码
  1. public class Cat implements Animal {   
  2.     private String name;   
  3.     public void say() {   
  4.         System.out.println("I am " + name + "!");   
  5.     }   
  6.     public void setName(String name) {   
  7.         this.name = name;   
  8.     }   
  9. }  

实现了phz.springframework.test.Animal接口
Java代码 复制代码
  1. public interface Animal {   
  2.     public void say();   
  3. }  

很明显上面的代码输出I am kitty!

那么到底Spring是如何做到的呢?
接下来就让我们自己写个Spring 来看看Spring 到底是怎么运行的吧!

首先,我们定义一个Bean类,这个类用来存放一个Bean拥有的属性
Java代码 复制代码
  1. /* Bean Id */  
  2.     private String id;   
  3.     /* Bean Class */  
  4.     private String type;   
  5.     /* Bean Property */  
  6.     private Map<String, Object> properties = new HashMap<String, Object>();  

一个Bean包括id,type,和Properties。

接下来Spring 就开始加载我们的配置文件了,将我们配置的信息保存在一个HashMap中,HashMap的key就是Bean 的 Id ,HasMap 的value是这个Bean,只有这样我们才能通过context.getBean("animal")这个方法获得Animal这个类。我们都知道Spirng可以注入基本类型,而且可以注入像List,Map这样的类型,接下来就让我们以Map为例看看Spring是怎么保存的吧

Map配置可以像下面的
Java代码 复制代码
  1. <bean id="test" class="Test">   
  2.         <property name="testMap">   
  3.             <map>   
  4.                 <entry key="a">   
  5.                     <value>1</value>   
  6.                 </entry>   
  7.                 <entry key="b">   
  8.                     <value>2</value>   
  9.                 </entry>   
  10.             </map>   
  11.         </property>   
  12.     </bean>  

Spring是怎样保存上面的配置呢?,代码如下:
Java代码 复制代码
  1. if (beanProperty.element("map") != null) {   
  2.                     Map<String, Object> propertiesMap = new HashMap<String, Object>();   
  3.                     Element propertiesListMap = (Element) beanProperty   
  4.                             .elements().get(0);   
  5.                     Iterator<?> propertiesIterator = propertiesListMap   
  6.                             .elements().iterator();   
  7.                     while (propertiesIterator.hasNext()) {   
  8.                         Element vet = (Element) propertiesIterator.next();   
  9.                         if (vet.getName().equals("entry")) {   
  10.                             String key = vet.attributeValue("key");   
  11.                             Iterator<?> valuesIterator = vet.elements()   
  12.                                     .iterator();   
  13.                             while (valuesIterator.hasNext()) {   
  14.                                 Element value = (Element) valuesIterator.next();   
  15.                                 if (value.getName().equals("value")) {   
  16.                                     propertiesMap.put(key, value.getText());   
  17.                                 }   
  18.                                 if (value.getName().equals("ref")) {   
  19.                                     propertiesMap.put(key, new String[] { value   
  20.                                             .attributeValue("bean") });   
  21.                                 }   
  22.                             }   
  23.                         }   
  24.                     }   
  25.                     bean.getProperties().put(name, propertiesMap);   
  26.                 }  


接下来就进入最核心部分了,让我们看看Spring 到底是怎么依赖注入的吧,其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。让我们看看具体它是怎么做的吧。
首先实例化一个类,像这样
Java代码 复制代码
  1. public static Object newInstance(String className) {   
  2.         Class<?> cls = null;   
  3.         Object obj = null;   
  4.         try {   
  5.             cls = Class.forName(className);   
  6.             obj = cls.newInstance();   
  7.         } catch (ClassNotFoundException e) {   
  8.             throw new RuntimeException(e);   
  9.         } catch (InstantiationException e) {   
  10.             throw new RuntimeException(e);   
  11.         } catch (IllegalAccessException e) {   
  12.             throw new RuntimeException(e);   
  13.         }   
  14.         return obj;   
  15.     }  

接着它将这个类的依赖注入进去,像这样
Java代码 复制代码
  1. public static void setProperty(Object obj, String name, String value) {   
  2.         Class<? extends Object> clazz = obj.getClass();   
  3.         try {   
  4.             String methodName = returnSetMthodName(name);   
  5.             Method[] ms = clazz.getMethods();   
  6.             for (Method m : ms) {   
  7.                 if (m.getName().equals(methodName)) {   
  8.                     if (m.getParameterTypes().length == 1) {   
  9.                         Class<?> clazzParameterType = m.getParameterTypes()[0];   
  10.                         setFieldValue(clazzParameterType.getName(), value, m,   
  11.                                 obj);   
  12.                         break;   
  13.                     }   
  14.                 }   
  15.             }   
  16.         } catch (SecurityException e) {   
  17.             throw new RuntimeException(e);   
  18.         } catch (IllegalArgumentException e) {   
  19.             throw new RuntimeException(e);   
  20.         } catch (IllegalAccessException e) {   
  21.             throw new RuntimeException(e);   
  22.         } catch (InvocationTargetException e) {   
  23.             throw new RuntimeException(e);   
  24.         }   
  25. }  

最后它将这个类的实例返回给我们,我们就可以用了。我们还是以Map为例看看它是怎么做的,我写的代码里面是创建一个HashMap并把该HashMap注入到需要注入的类中,像这样,
Java代码 复制代码
  1. if (value instanceof Map) {   
  2.                 Iterator<?> entryIterator = ((Map<?, ?>) value).entrySet()   
  3.                         .iterator();   
  4.                 Map<String, Object> map = new HashMap<String, Object>();   
  5.                 while (entryIterator.hasNext()) {   
  6.                     Entry<?, ?> entryMap = (Entry<?, ?>) entryIterator.next();   
  7.                     if (entryMap.getValue() instanceof String[]) {   
  8.                         map.put((String) entryMap.getKey(),   
  9.                                 getBean(((String[]) entryMap.getValue())[0]));   
  10.                     }   
  11.                 }   
  12.                 BeanProcesser.setProperty(obj, property, map);   
  13.             }  

好了,这样我们就可以用Spring 给我们创建的类了,是不是也不是很难啊?当然Spring能做到的远不止这些,这个示例程序仅仅提供了Spring最核心的依赖注入功能中的一部分。
本文参考了大量文章无法一一感谢,在这一起感谢,如果侵犯了你的版权深表歉意,很希望对大家有帮助!

附件中包含该山寨Spring的源码,核心只有五个类,还有一个测试程序,phz.springframework.test.AnimalSayApp,可以直接运行。
posted @ 2010-03-04 16:28 小菜毛毛 阅读(2842) | 评论 (1)编辑 收藏

posted @ 2010-03-04 15:37 小菜毛毛 阅读(120) | 评论 (0)编辑 收藏

Struts工作流程

文章分类:招聘求职
一个用户的请求是通ActionServlet来处理和转发的。那么,ActionServlet如何决定把用户请求转发给哪个Action对象呢?这就需要一些描述用户请求路径和Action衍射关系的配置信息了。在Struts中,这些配置映射信息都存储在特定的XML文件Struts- config.xml中。在该配置文件中,每一个Action的映射信息都通过一个<Action>元素来配置。

这些配置信息在系统启动的时候被读入内存,供Struts在运行期间使用。在内存中,每一个<action>元素都对应一个org.apache.struts.action.ActionMapping类的实例。

对于采用Struts框架的web应用,在web应用启动时就会加载并初始化ActionServlet,ActionServlet从struts- config.xml文件中读取配置信息,把它们存放到各个配置对象中,例如Action的映射信息存放在ActionMapping对象中。

当ActionServlet接收到一个客户请求时,将执行如下流程:

1.检索和用户请求相匹配的ActionMapping实例,如果不存在,就返回用户请求路径无效信息。

2.如ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中。

3.根据配置信息决定是否需要表单验证。如果需要验证,就调用ActionForm的Validate()方法。

4.如果ActionForm的Validate()方法返回null或返回一个不包含ActionMessage的ActionErrors对象,就表示表单验证成功。

5.ActionServlet根据ActionMapping实例包含的映射信息决定将请求转发给哪个Action。如果相应的Action实例不存在,就先创建这个实例,然后调用Action的execute()方法。

6.Action的execute()方法返回一个ActionForward对象,ActionServlet再把客户请求转发给ActionForward对象指向的JSP组件。

7.ActionForward对象指向的JSP组件生成动态网页,返回给客户。

posted @ 2010-03-04 15:34 小菜毛毛 阅读(1864) | 评论 (1)编辑 收藏

仅列出标题
共17页: First 上一页 5 6 7 8 9 10 11 12 13 下一页 Last