<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>downpour</title>
    <description>非澹泊无以明志，非宁静无以致远。</description>
    <link>http://downpour.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>2008年6月10日</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/201908" style="color:red;">http://downpour.javaeye.com/blog/201908</a>&nbsp;
          发表时间: 2008年06月10日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          现在不是一个很封闭的时代。消息的对称性已经做得非常好。无论是网络还是舆论，都可以让你在几秒钟甚至几分钟之内知道一则你所关心的消息。<br /><br />今天早上在地铁上，突然预感到今天的股市可能不妙。国际油价狂飙加上美股大跌，国家上调存款准备金率，不知道今天会低开多少。我心里盘算着要是今天低开在2%之内，我就抛光。结果，直接低开接近3%，我连抛的心情都没有了。<br /><br />早上，看到一个人的博客。一个绝对意识流的作品。可惜，这个作品除了能够证明不封闭时代的消息不对称性存在的概率以外，对我来说，还是另一种形式的判决书。我承认我还是不够刚毅的，大学四年还是没有锻炼出来。<br /><br />我以前一直觉得自己是个诗人，其实自己什么都不是，可惜我小时候不懂，从来没有意识到这个问题。当我有一天突然意识到这一点的时候，我就封笔了。有时候，写东西可能只是为了纪念某个人或者某件事。今天的日子和心境，当我试图再次提起笔来写点什么的时候，我突然发觉我已经什么都不会写了，心中荡漾的始终是我的封笔之作：<br /><br />吴越披洪泽，<br />晨辉照涟漪。<br />瑜映水中景，<br />一叶孤舟来。<br /><br />或许，我以后再也写不出所谓的诗了。此时此刻，陨落的心情，无以言表。
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/201908#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 10 Jun 2008 20:02:11 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/201908</link>
        <guid>http://downpour.javaeye.com/blog/201908</guid>
      </item>
      <item>
        <title>项目管理的初步想法</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/186429" style="color:red;">http://downpour.javaeye.com/blog/186429</a>&nbsp;
          发表时间: 2008年04月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          1. 根据需求，将项目尽可能拆分 <br /> <br />可能出现的情况是：将一个项目进行拆分，分成2到3个子项目进行单独开发。当前设想的主要可能性：主项目、统计和调度项目、核心框架项目等。 <br /> <br />核心框架项目成熟后可以以Jar包方式发布。该项目主要包含对传统框架的一些封装和扩展。 <br /> <br />统计和调度项目主要为主项目提供统计支持和后台调度运行支持，独立运行在一台机器上。理想的状况：将整个调度运行在一个大的Framework中，这个Framework能够查看系统的统计、调度的日志；同时可以对其中的统计模块、调度模块的进行状态进行监控；也可以以系统的方式暂定或者重启某个统计模块或者调度模块。针对具体项目的主要工作，是在这个Framework中，进行具体业务逻辑的编写。对于统计，可以指定统计的接口、统计的实施时间和内容等等。对于调度，可以指定调度的接口以及调度时间和频度。 <br /> <br />主项目承担主要业务逻辑，可能根据项目规模的不同需要支持集群。主项目需要依赖于其他项目而存在。也有可能需要根据实际情况，对主项目进行进一步的拆分。 <br /> <br />2. 人员管理 <br /> <br />项目人员主要包含3部分：核心人员、外包人员和实习生。 <br /> <br />核心人员主要承担项目需求转化为技术内容的工作。这些工作主要包含项目需求获取、项目设计、项目核心代码编写、项目代码Review和项目代码Merge的工作。 <br />外包人员主要根据被分配的模块不同，进行代码的编写工作。在他们编写代码时，需要严格按照核心Framework的规定进行编码。涉及到复杂业务逻辑的地方，应配合核心人员对业务逻辑接口和业务逻辑流程进行代码化整理。外包人员的组成将主要是具有若干年工作经验的民工，以模块的工作量进行结算。 <br />实习生的主要工作是编写简单代码、一切辅助工作和测试。他们需要承担简单的代码编写工作以及测试工作。同时，项目中的数据准备、文件管理、数据库管理等杂乱工作，也需要由实习生完成。实习生的结算方式为月结。 <br /> <br />3.  角色分配 <br /> <br />根据项目人员的组成结构和项目需要，可以划分以下系统角色： <br />1) BA <br />目前情况下，BA应该就是整个项目的PM，需要完成所有的商务上的所有事项，同时需要进行需求整理，项目的总体管理等。BA需要与Architect和Assist共同工作，从而完成工作任务划分和任务分配以及在需求基础上的Prototype的搭建工作。 <br />2) Architect <br />这个角色主要在技术上保证项目的顺利进行，其职责将包括与BA一起进行项目设计、项目范围界定、项目模块和任务的划分、核心代码的编写等。Architect的另外一个重要的工作是把握整个项目的进程和代码管理，包括Code Review，Code Merge等工作。此外，Architect需要负责项目的开发环境、测试环境、文档环境搭建等。 <br />3) Developer <br />这个角色可能主要由外包人员和实习生构成，是所有角色中的核心力量。Developer的重要原则是在既定的Framework下，高质量的完成Coding任务。 <br />4) SA <br />SA这个角色不一定在所有的项目中都包含。其主要职责是在操作系统、系统构架、数据库等底层层面提供优化策略和技术支持。这部分工作，当前情况下，如果需要，可以进行外包。 <br />5) Assist <br />Assist角色主要由实习生完成，这个角色可以承包项目中所有的杂务，同时也需要完成Coding任务。 <br /> <br />4. 项目管理工具 <br /> <br />项目管理的工具很多，可以选择一个功能相对较全，而且相对比较集中的工具：http://www.redmine.org/projects/show/redmine <br />上述工具可以管理的内容包含：项目新闻(进度)发布、Bug Tracking、文档管理、Wiki和论坛等。我们在项目中所涉及到的一些功能可能只需要其中的进度发布、Bug Tracking、文档管理等。 <br /> <br />项目中的版本管理工具可以使用SVN。 <br /> <br />5. 版本管理方法 <br /> <br />由于当前项目的状况比较特殊，项目的版本管理变得比较重要。在吸取了一些典型的外包项目管理的经验之后，我们可以使用分Project、分Branch的方式对项目进行版本管理。 <br /> <br />分项目是指，项目拆分后，不同的项目将使用不同的Reposity。 <br /> <br />分Branch是指，针对某个项目，对每个Developer小组，创建多个Branch进行独立开发，频繁Merge的方式进行管理。基本原则：在Trunk上创建一个主Branch，由Architect负责Trunk和这个主Branch进行管理，包括代码的Merge和Merge前的Code Review等。然后以Developer小组为单位，创建基于模块的Branch，所有的Developer，在各自的Branch上进行开发。每个基于模块的Branch，以3天为单位，由Architect负责将代码Merge到主Branch上，同时做Code Review。在Trunk上，以周为单位，保持可运行版本。
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/186429#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 24 Apr 2008 16:02:58 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/186429</link>
        <guid>http://downpour.javaeye.com/blog/186429</guid>
      </item>
      <item>
        <title>若干条J2EE应用中运用“配置”的最佳实践</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/185542" style="color:red;">http://downpour.javaeye.com/blog/185542</a>&nbsp;
          发表时间: 2008年04月22日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          本文所提到的所有内容的前提是使用一些开源框架搭建简单的J2EE应用时，对配置的运用方面的一些总结出来的最佳实践。<br /><br />1. 尽最大的可能简化你的配置<br /><br />这一点似乎是基本原则，没有人会愿意多写一行代码，配置也是代码，多一行配置，就意味着多一行的维护量。简化配置的主要途径大致有:<br />1) 尽可能减少配置文件的数量<br />2) 使用语义鲜明的Annotation来代替复杂的XML文件配置<br />3) 使用CoC来代替配置文件<br />4) 使用一些特殊的技巧来简化配置文件的内容<br /><br />2. 分离关注点，让配置文件各尽其用<br /><br />这一点似乎与第一点有所背离，不过事实上，分离关注点对于配置文件的可维护性是非常重要的一点。<br /><br />举一个针对Spring+Hibernate的配置场景作为例子。通常我们需要一个Spring的配置文件(applicationContext.xml)，来配置DataSource和SessionFactory，由于Spring本身提供了针对Hibernate的Global属性进行配置的选项，所以，其实我们可以通过如下的配置文件，对Spring+Hibernate完成配置：<br /><br /><pre name="code" class="xml">

&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    &lt;property name="mappingResources">
        &lt;list>
           &lt;value>com/demo2do/demo/entity/User.hbm.xml&lt;/value>
           &lt;value>com/demo2do/demo/entity/Order.hbm.xml&lt;/value>
           &lt;value>com/demo2do/demo/entity/Admin.hbm.xml&lt;/value>
        &lt;/list>
    &lt;/property>
    &lt;property name="hibernateProperties">
        &lt;props>
           &lt;prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect&lt;/prop>
           &lt;prop key="hibernate.show_sql">true&lt;/prop>
        &lt;/props>
     &lt;/property>
&lt;/bean>
</pre><br /><br />在这里，我们发现，一个SessionFactory的配置实在太长了，一旦我们需要对其中的某些配置进行改动，就需要用肉眼去观察我们所需要修改的配置片段。在项目开发过程中，我们会发现，这个文件的这个配置片段修改频度会非常高，因为在一个团队中，每个人都可能需要增加一个持久化类，或者对hibernate进行一些全局化的配置修改。结果，这段配置可能会在版本管理上造成merge的混乱。<br /><br />所以，我们可以在这个基础上对这段配置进行重构，重构的原则就在于把Hibernate的配置和Spring的配置进行关注点分离。我们选择hibernate.properties对Hibernate的一些Global的选项进行指定。同时使用指定持久化类hbm配置文件路径的方式，批量定义持久化类。重构后的配置文件变成了2个：<br /><br /><pre name="code" class="xml">
&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    &lt;property name="mappingDirectoryLocations">
            &lt;list>
				&lt;value>classpath*:persist/system&lt;/value>
				&lt;value>classpath*:persist/role&lt;/value>
				&lt;value>classpath*:persist/activity&lt;/value>
				&lt;value>classpath*:persist/extension&lt;/value>
				&lt;value>classpath*:persist/user&lt;/value>
            &lt;/list>
        &lt;/property>
&lt;/bean>
</pre><br /><br /><pre name="code" class="java">
######################
### Query Language ###
######################

## define query language constants / function names
hibernate.query.substitutions true 1, false 0, yes 'Y', no 'N'

#################
### Platforms ###
#################

## MySQL

hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect
hibernate.connection.url jdbc:mysql://127.0.0.1:3306/test
hibernate.connection.username root
hibernate.connection.password root

## Oracle

#hibernate.dialect org.hibernate.dialect.OracleDialect

....
</pre><br /><br />此时，我们可以发现，程序员可以独立工作，不需要为增加持久化类修改公共配置而烦恼，hibernate.properties也更加清晰的反映hibernate相关的配置。<br /><br />这还不够，因为在项目中，我们往往可能在不同的DataSource的实现上切换。多数情况下，我们会使用类似C3P0这样的数据连接池，当然，也可能会通过JNDI来指定我们的DataSource。所以，我们在这里很有必要对JDBC连接相关的关注点再一次进行分离。引入一个jdbc.properties的文件指定JDBC相关的链接信息，并在Spring配置文件中导入这些配置：<br /><br /><pre name="code" class="xml">
&lt;!-- A Local dataSource Definition using c3p0 connection pool -->
	&lt;bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"	destroy-method="close">
		&lt;property name="driverClass" value="${connection.driver_class}"/>
		&lt;property name="jdbcUrl" value="${jdbc.connection.url}"/>
		&lt;property name="idleConnectionTestPeriod" value="${jdbc.pool.c3p0.idle_connection_test_period}" />
		&lt;property name="preferredTestQuery" value="${jdbc.pool.c3p0.preferred_test_query}" />
		&lt;property name="maxIdleTime" value="${jdbc.pool.c3p0.max_idle_time}" />
		&lt;property name="properties">
			&lt;props>
				&lt;prop key="user">${jdbc.connection.username}&lt;/prop>
				&lt;prop key="password">${jdbc.connection.password}&lt;/prop>
				&lt;prop key="c3p0.acquire_increment">${jdbc.pool.c3p0.acquire_increment}&lt;/prop>
				&lt;prop key="c3p0.max_size">${jdbc.pool.c3p0.max_size}&lt;/prop>
				&lt;prop key="c3p0.min_size">${jdbc.pool.c3p0.min_size}&lt;/prop>
			&lt;/props>
		&lt;/property>
	&lt;/bean>
	
	&lt;!-- Hibernate SessionFactory definition using exposed dataSource -->
	&lt;!-- hibernate.properties and hibernate.cfg.xml will be loaded on startup -->
	&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		&lt;property name="dataSource" ref="dataSource"/>
		&lt;property name="mappingDirectoryLocations">
            &lt;list>
				&lt;value>classpath*:persist/system&lt;/value>
				&lt;value>classpath*:persist/role&lt;/value>
				&lt;value>classpath*:persist/activity&lt;/value>
				&lt;value>classpath*:persist/extension&lt;/value>
				&lt;value>classpath*:persist/user&lt;/value>
            &lt;/list>
        &lt;/property>
	&lt;/bean>
</pre><br /><br /><pre name="code" class="java">
connection.driver_class=com.mysql.jdbc.Driver

jdbc.connection.url=jdbc:mysql://192.168.1.251:3307/test
jdbc.connection.username=root
jdbc.connection.password=root

jdbc.pool.c3p0.acquire_increment=2
jdbc.pool.c3p0.max_size=20
jdbc.pool.c3p0.min_size=2
jdbc.pool.c3p0.preferred_test_query='SELECT 1'
jdbc.pool.c3p0.idle_connection_test_period=18000
jdbc.pool.c3p0.max_idle_time=25000
</pre><br /><br />这样，一段配置变成了3个文件。但是通过重构，我们可以轻松在本地实现数据库切换(修改jdbc.properties)，数据连接池切换(修改jdbc.properties和applicationContext.xml)，hibernate的Global配置切换等功能。这些功能会提供你更加灵活的调试方式。<br /><br />3. 整理你的配置文件，不要让它们分散到各处<br /><br />这一点作为一个最佳实践提出来，主要是因为在项目中，我们可能使用到的技术框架和配置文件是很难预期的。而默认情况下，这些配置文件所存放的位置也不同，比如Spring，往往会把applicationContext.xml文件放在WEB-INF/目录下，而hibernate和struts默认是放在classpath下的。这就为管理配置文件带来了不便。所以，一个比较好的做法，是把我们的配置文件都放在一起。通过存放在不同的目录结构进行管理。例如，把所有的配置文件都放到classpath下。<br /><br />如果你用maven进行项目管理，maven会为你创建专门存放配置文件的目录(通常是一个叫做resource的目录)。如果不使用maven，我们也可以做类似的工作。以我个人的习惯为例，我喜欢在项目中建立一个叫做conf的source folder来存放所有的配置文件。由于conf是一个source folder，所以它会被类似eclipse这样的IDE自动编译到classpath下，那么我们就可以在这个目录下创建一些package，例如context的package专门存放Spring相关的配置文件，persistent专门存放ORM相关的配置文件，web专门存放表示层的配置文件。而其他的一些配置文件，则直接放到conf根目录下。<br /><br />一个典型的目录结构如下：<br /><br /><img src="http://downpour.javaeye.com/topics/download/1ab74ee9-6082-3f59-8bbd-73f836c62826" /><br /><br />如果你用这种方式来管理你的配置文件，有一个极大的好处就在于，不必再担心你团队的成员为找不到配置文件而发愁，他们被集中存放，集中管理了。<br /><br />当然，任何事情不能做得过于偏激，有时候，我们需要package level的配置文件，例如struts的validation，类型转化定义，i18n配置文件，等等。这些package level的配置文件，我们完全没有必要把他们集中到一起，因为他们在各自的package中承担着各自的作用。实际上，如果把他们集中到一起，反而会造成这样那样的问题，这些问题将会在第五点最佳实践中有所涉及。<br /><br />4. 选择合理的配置类型，XML or Annotation or Properties？<br /><br />之前已经谈到了尽可能简化配置，其中的重要途径是使用Annotation来代替XML进行配置。在这里我想提出的是，XML和Annotation甚至是Properties文件，他们都各自有各自的特点，我们可以根据实际情况，选择最合适的方式进行配置。<br /><br />比如说在Spring2.5中，XML配置可以被用作全局的，公共的配置，这些配置包括：DataSource定义、SessionFactory定义、事务定义等等。而Annotation可以被用作Bean定义，而无需在XML文件中一一指定。此时，两者结合将成为一个比较好的选择。<br /><br />再比如Hibernate的全局配置，可以使用hibernate.properties，也可以使用hibernate.cfg.xml。但是properties文件无法指定持久化类，不过properties文件在定义全局配置时，显然比xml的语义性更强，也更容易编辑(你只要把Hibernate发行包中的模板properties复制一份过来改一下就行了)<br /><br />而hibernate的持久化类的配置，Annotation和XML的比较上，Annotation似乎更占上风。虽然个人不喜欢在Domain Object上加过多的Annotation，不过不得不说，在降低维护成本上，Annotation占有了绝对的优势。可惜，hibernate的Annotation最大的缺点，就在于它的Annotation所定义的属性，与XML定义的属性是互相不兼容的，因而带来了一定的学习成本。<br /><br />5. 在团队工作中，尽可能不要把团队操作度很高的配置放到一个文件中<br /><br />这条最佳实践其实应该成为一条很重要的最佳实践。因为merge代码或者merge配置文件而给程序员带来痛苦的情况是数不胜数的。因此，解决这个问题的最佳方案就是尽量把团队操作度很高的配置文件拆分开。<br /><br />在项目中，那些配置的团队操作度很高呢？我大致考虑了以下一些情况：<br />1) 持久化类的配置在SessionFactory中的定义<br />2) web层的配置文件(struts-config.xml)等<br />3) 涉及到i18n的资源类文件<br />4) Spring中的Bean定义<br /><br />第一条，如果使用XML进行持久化类的配置，那么可以通过指定路径来解决这个问题。如果你使用的是Annotation，那么很不幸，当前还没有可以通过指定带有Annotation的持久化类的package的方式来简化配置。当然，要自己实现一个似乎也并不困难，有兴趣的朋友可以自己尝试一下。<br /><br />第二条，在Struts2中，已经提供了一些0配置的方案，通过使用这些方案，大家可以最大程度上降低配置的公用性。同时Struts2也支持将XML文件分开定义，每个程序员可以工作在自己那个模块所在的XML文件上。<br /><br />第三条，之前很多的做法，是在classpath下定义一个统一的资源文件，所有的资源信息都写在一起，这会造成操作冲突的几率很大。比较合适的做法，就是在Action的package level定义与这个Action相关的一些资源，这也是Struts比较推荐的做法。<br /><br />第四条，如果你使用Spring2.5，可以使用Annotation来代替Bean定义。<br /><br />6. 适度使用Annotation和CoC<br /><br />这条是值得讨论的。有关XML和Annotation的是是非非，在其他的帖子中经常有讨论。我的观点在于，对于公用的全局的配置，使用XML，而单独的Bean相关的配置，使用Annotation。对于Domain Object的配置，个人倾向使用XML配置进行关注点分离。<br /><br />谈到CoC，这是一个懒人的时代，RoR中的一些约定大于配置的观念也开始深入人心。不过在Java世界，这一点似乎还没有完全被推开。甚至有人问我，这家伙写的东西，怎么连个配置文件都没有，我哪里知道谁对应谁呢。所以CoC，我想也有个度，甚至有时候，需要一定的Reference Doc进行说明，否则，过度的CoC，也会给许多人带来困惑。
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/185542#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 22 Apr 2008 17:07:53 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/185542</link>
        <guid>http://downpour.javaeye.com/blog/185542</guid>
      </item>
      <item>
        <title>使用HTML＋CSS编写一个灵活的Tab页</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/52036" style="color:red;">http://downpour.javaeye.com/blog/52036</a>&nbsp;
          发表时间: 2007年02月12日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          最近在研究CSS，正好结合项目做了一个灵活的Tab页，使用纯HTML＋CSS实现，正好总结一下。<br /><br />首先看一下预览界面：<br /><img src="http://www.demo2do.com/pics/cssed.jpg" /><br />样例HTML可以访问：http://www.demo2do.com/htmldemo/school/attendance/AttendanceGlobal.html <br /><br />下面开始讲述一下完成上述页面的步骤。<br /><br />1. 构建HTML<br />构建HTML是整个过程最基础的部分。我们构建HTML比较关键的一个原则就是“还HTML标签其本来的含义”。所以在这里，我们应该合理分析一下期望做到的HTML的结构的情况，并加以分析，选择比较合适的HTML标签，而不是采用非标准的Table布局或者充斥着大量div和class的布局方式。事实上，现在存在着一种误区，就是凡事采用了DIV＋CSS的方式进行页面编程的就是Web标准的，其实这是完全错误的观点，很容易就导致了“多div症”（divitus）或者“多类症”（classitis）。<br />回到正题，我们分析一下页面样式，可以将整个Tab页分成2个部分，分别是一级菜单和二级菜单，他们有类似的特点，并以横向方式排列。HTML标签中的无序列表就可以反映出这种逻辑关系。所以我们分别采用2个无序列表来表示一级菜单和二级菜单。代码如下：<br /><pre name="code" class="java">
&lt;div class="navg">
	&lt;div id="attendance" class="mainNavg">
		&lt;ul>
			&lt;li id="attendanceNavg">&lt;a href="#">考勤管理&lt;/a>&lt;/li>
			&lt;li id="teachNavg">&lt;a href="#">教学管理&lt;/a>&lt;/li>
			&lt;li id="communicationNavg">&lt;a href="#">家校互通&lt;/a>&lt;/li>
			&lt;li id="systemNavg">&lt;a href="#">系统管理&lt;/a>&lt;/li>
		&lt;/ul>
	&lt;/div>	
	&lt;div id="dailyAttendance" class="secondaryNavg">
		&lt;ul>
			&lt;li id="dailyAttendanceNavg">&lt;a href="#">当天考勤&lt;/a>&lt;/li>
			&lt;li id="leaveApproveNavg">&lt;a href="#">请假审批&lt;/a>&lt;/li>
			&lt;li id="attendanceStatisticsNavg">&lt;a href="#">考勤统计&lt;/a>&lt;/li>
			&lt;li id="attendanceCollectNavg">&lt;a href="#">考勤汇总&lt;/a>&lt;/li>
		&lt;/ul>
	&lt;/div>
&lt;/div>
</pre><br /><br />其中，2个div将菜单级别划分开。其实在以后还会有其他的功效。此时，我们不妨View一下这张页面，我们可以惊喜的发现，这张页面就想Word文档一样，是可读的，这一点我们可以在整个过程做完以后再一次验证。<br /><br /><img src="http://www.demo2do.com/pics/nocss.jpg" /><br /><br />2. 构建基本CSS<br /><br />先简单的让ul横向排列，这里面要注意元素float之后要注意清理<br /><br />然后通过分别在LI 和 A 元素上应用背景来实现主菜单样式，这里有个比较重要的地方是A这个元素变成块级元素（display: block），这样可以便于我们下面做一些处理，也能使整个菜单应用到链接样式。<br />而其中的line-height，恰恰可以使A中的字纵向居中。text-align使得A中的字横向居中。<br /><pre name="code" class="java">
.navg .mainNavg UL {
	margin: 0;
	padding: 0;
	list-style: none;
}
.navg .mainNavg UL LI {
	float: left;	
	background-color: #E1E9F8;
	background: url(../images/tab_right.gif) no-repeat right top;
	margin: 10px 3px;
	height: 25px;
}

.navg .mainNavg UL LI A {
	display: block;
	height: 25px;
	padding: 0 25px;
	line-height: 24px;
	background-color: #E1E9F8;
	background: url(../images/tab_left.gif) no-repeat left top;
	text-decoration: none;
	float: left;
	text-align:center;
	color: #fff;
	font-weight: bold;	
}
</pre><br /><br />3. 使宽度自适应<br /><br />我们在这里使用滑动门技术来做宽度自适应。下面简单介绍一下滑动门技术<br /><br />简单来说，就是在LI上应用一幅大图像背景，并让这个背景居于右侧<br /><br /><img src="http://www.demo2do.com/htmldemo/images/tab_right.gif" /><br /><br />然后在A上应用一个小图像背景，并让这个背景居于左侧，遮住大图像边缘<br /><br /><img src="http://www.demo2do.com/htmldemo/images/tab_left.gif" /><br /><br />这样无论菜单文字内容长度怎么变，都不会破坏原来的结构了。<br /><br />4. 当前菜单高亮显示<br /><br />如果高亮当前页面，这个有很多种做法，最死板的是在每个页面上显式的定义类。<br />但是对于web项目来说，页面多数是动态的，所以这样不是最理想的方法。<br /><br />我这里采用的方法是CSS选择器的灵活使用<br /><br /><pre name="code" class="java">
#attendance #attendanceNavg,
#teach #teachNavg,
#communication #communicationNavg,
#system #systemNavg {
	background: url(../images/tab_right_on.gif) no-repeat right top;
}
#attendance #attendanceNavg A,
#teach #teachNavg A,
#communication #communicationNavg A,
#system #systemNavg A {
	background: url(../images/tab_left_on.gif) no-repeat left top;
	color: #0000ff;
}
</pre><br /><br />在&lt;div id="attendance" class="mainNavg">的代码中，我们可以使用不同的id作为选择器，由于CSS中的选择器id的优先级将大于class，所以只要根据id配合上li上面的id，就可以达到动态选择高亮选中的目的。<br /><br />事实上，由于我们的页面都是动态的，所以id可以由后台生成，这样就可以通过id的不同组合非常精巧的实现了我们的需求。<br /><br />5. 小技巧<br /><br />最后可能还有一个问题你在想怎么实现的，就是高亮的tab如何把下面的横线遮掉的<br /><br />很简单，图片上的小技巧。将高亮的图片高度设置为25px，而普通的图片设置为24px。然后通过padding，就可以将那根横线遮去了。<br /><br /><br />我们可以使用类似的方式，把二级菜单也做出来，这里就不详细叙述了。大家可以结合源码试一下。<br /><br />附件为
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/52036#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 12 Feb 2007 23:52:00 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/52036</link>
        <guid>http://downpour.javaeye.com/blog/52036</guid>
      </item>
      <item>
        <title>OpenSessionInView详解</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/32001" style="color:red;">http://downpour.javaeye.com/blog/32001</a>&nbsp;
          发表时间: 2006年11月01日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          OpenSessionInViewFilter是Spring提供的一个针对Hibernate的一个支持类，其主要意思是在发起一个页面请求时打开Hibernate的Session，一直保持这个Session，直到这个请求结束，具体是通过一个Filter来实现的。<br /><br />由于Hibernate引入了Lazy Load特性，使得脱离Hibernate的Session周期的对象如果再想通过getter方法取到其关联对象的值，Hibernate会抛出一个LazyLoad的Exception。所以为了解决这个问题，Spring引入了这个Filter，使得Hibernate的Session的生命周期变长。<br /><br />首先分析一下它的源码，可以发现，它所实现的功能其实比较简单：<br /><pre name="code" class="java">
SessionFactory sessionFactory = lookupSessionFactory(request);
Session session = null;
boolean participate = false;

if (isSingleSession()) {
	// single session mode
	if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
	// Do not modify the Session: just set the participate flag.
	participate = true;
       }	else {
	logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
	session = getSession(sessionFactory);
	TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
	}
} else {
	// deferred close mode
	if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
// Do not modify deferred close: just set the participate flag.
	participate = true;
    } else {
	SessionFactoryUtils.initDeferredClose(sessionFactory);
    }
}

try {
	filterChain.doFilter(request, response);
} finally {
	if (!participate) {
           	if (isSingleSession()) {
	         	// single session mode
		TransactionSynchronizationManager.unbindResource(sessionFactory);
		logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
		closeSession(session, sessionFactory);
	}else {
		// deferred close mode
		SessionFactoryUtils.processDeferredClose(sessionFactory);
	}
}
}
</pre><br />在上述代码中，首先获得SessionFactory，然后通过SessionFactory获得一个Session。然后执行真正的Action代码，最后根据情况将Hibernate的Session进行关闭。整个思路比较清晰。<br /><br />注意，在这里有个2个Tips：<br />1）通过getSession()获得的这个Session做了一次<br />session.setFlushMode(FlushMode.NEVER); 有关FlushMode可以参考一下这篇文章。<a href="http://www2.matrix.org.cn/resource/article/2006-10-08/Hibernate+FlushMode+NEVER_312bca85-5699-11db-91a0-d98dff0aec60.html" target="_blank">http://www2.matrix.org.cn/resource/article/2006-10-08/Hibernate+FlushMode+NEVER_312bca85-5699-11db-91a0-d98dff0aec60.html</a><br />2）Spring对拿到的Session做了一次绑定到当前线程的做法，使得这个Session是线程安全的。<br /><br />从上述代码其实可以得到一些对我们的开发有帮助的结论：<br />1）如果使用了OpenSessionInView模式，那么Spring会帮助你管理Session的开和关，从而你在你的DAO中通过HibernateDaoSupport拿到的getSession()方法，都是绑定到当前线程的线程安全的Session，即拿即用，最后会由Filter统一关闭。<br />2）由于拿到的Hibernate的Session被设置了session.setFlushMode(FlushMode.NEVER); 所以，除非你直接调用session.flush()，否则Hibernate session无论何时也不会flush任何的状态变化到数据库。因此，数据库事务的配置非常重要。（我们知道，在调用org.hibernate.Transaction.commit()的时候会触发session.flush()）我曾经见过很多人在使用OpenSessionInView模式时，都因为没有正确配置事务，导致了底层会抛出有关FlushMode.NEVER的异常。<br /><br />OpenSessionInView这个模式使用比较简单，也成为了大家在Web开发中经常使用的方法，不过它有时候会带来一些意想不到的问题，这也是在开发中需要注意的。<br />1. 在Struts＋Spring＋Hibernate环境中，由于配置的问题导致的模式失效<br />这个问题以前论坛曾经讨论过，可以参考一下下面这个帖子：<br /><a href="http://www.javaeye.com/topic/15057" target="_blank">http://www.javaeye.com/topic/15057</a><br /><br />2. OpenSessionInView的效率问题<br />这个问题也有人在论坛提出过，Robbin曾经做过具体的测试，可以具体参考一下下面这个帖子：<br /><a href="http://www.javaeye.com/topic/17501" target="_blank">http://www.javaeye.com/topic/17501</a><br /><br />3. 由于使用了OpenSessionInView模式后造成了内存和数据库连接问题<br />这个问题是我在生产环境中碰到的一个问题。由于使用了OpenSessionInView模式，Session的生命周期变得非常长。虽然解决了Lazy Load的问题，但是带来的问题就是Hibernate的一级缓存，也就是Session级别的缓存的生命周期会变得非常长，那么如果你在你的Service层做大批量的数据操作时，其实这些数据会在缓存中保留一份，这是非常耗费内存的。还有一个数据库连接的问题，存在的原因在于由于数据库的Connection是和Session绑在一起的，所以，Connection也会得不到及时的释放。因而当系统出现业务非常繁忙，而计算量又非常大的时候，往往数据连接池的连接数会不够。这个问题我至今非常头痛，因为有很多客户对数据连接池的数量会有限制，不会给你无限制的增加下去。<br /><br />4. 使用了OpenSessionInView模式以后取数据的事务问题<br />在使用了OpenSessionInView以后，其实事务的生命周期比Session的生命周期来得短，就以为着，其实有相当一部分的查询是不被纳入到事务的范围内的，此时是否会读到脏数据？这个问题我至今不敢确认，有经验的朋友请指教一下。<br /><br />最后提一下OpenSessionInView模式的一些替代方案，可以使用OpenSessionInViewInterceptor来代替这个Filter，此时可以使用Spring的AOP配置，将这个Interceptor配置到你所需要的层次上去。另外就是只能使用最古老的Hibernate.initialize()方法进行初始化了。
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/32001#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 01 Nov 2006 17:13:00 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/32001</link>
        <guid>http://downpour.javaeye.com/blog/32001</guid>
      </item>
      <item>
        <title>一个简单的ServletContextLoader装载器</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/25039" style="color:red;">http://downpour.javaeye.com/blog/25039</a>&nbsp;
          发表时间: 2006年09月26日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          在很多项目中，会碰到一种需求，就是在应用服务器启动的时候，把一些东西从数据库里面读到内存中去。例如，对于一些权限信息，或者一些数据字典等等。实现这种需求本身不是很困难，写一个类，然后实现ServletContextListener这个接口，再到web.xml里面去配置一下就可以了。（我想已经有很多应用服务器支持ServletContextListener这个接口了吧，像Websphere5.0这种垃圾除外）<br />现在的问题是，由于需求是不断变化的，说不定哪天增加了一个又要在系统启动时往内存里面写点啥。此时，要么在原来的类的后面，加一段代码，要么就再写一个类，再配一个Listener。一般我会采用后面一种做法，因为这样至少可以做到对这些Listener保持可配置性，当业务发生变化时，简单改变配置文件就可以完成需求。不过这带来了一个问题，就是有可能web.xml就比较凌乱，而且还要搞清楚和其他一些系统启动运行的Listener的关系。<br />所以最近根据这个情况设计了一个简单的ServletContextLoader的装载器。在web.xml里面只需要配置一个Listener，而这个listener的作用就是依次按顺序调用其他的Listener。此时，其他的Listener都可以通过Spring的注入进入这个总的Listener服从调度。<br /><pre name="code" class="java">
/**
 * @author zhou.lu
 */

public class ServletContextLoaderListener implements ServletContextListener {

    private static final Log logger = LogFactory.getLog(ServletContextLoaderListener.class);

    private ServletContextLoader servletContextLoader;

    public void contextInitialized(ServletContextEvent event) {
       this.servletContextLoader = createServletContextLoader(event);
       this.servletContextLoader.initServletContext(event.getServletContext());
    }

    private ServletContextLoader createServletContextLoader(ServletContextEvent event) {
       ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext());
       return (ServletContextLoader) applicationContext.getBean("servletContextLoader");
    }

    public void contextDestroyed(ServletContextEvent event) {
       this.servletContextLoader.closeServletContext(event.getServletContext());
    }
}
</pre><br />这个就是一个总的Listener，这里模仿了Spring的源码，将具体的调度指派给ServletContextLoader这个接口来完成，而具体的实现则通过Spring拿到。接下来看一下它的具体实现：<br /><pre name="code" class="java">
/**
 * @author zhou.lu
 */
public class ServletContextLoaderImpl implements ServletContextLoader, InitializingBean {

    private static final Log logger = LogFactory.getLog(ServletContextLoaderImpl.class);

    private List servletContextLoaders;

    public void setServletContextLoaders(List servletContextLoaders) {
       this.servletContextLoaders = servletContextLoaders;
    }

    public void afterPropertiesSet() throws Exception {
       for (int i = 0; i &lt; servletContextLoaders.size(); i++) {
           // check every servletContextLoader and set to loaders
           Object loader = servletContextLoaders.get(i);
           if (!(loader instanceof ServletContextLoader)) {
              throw new IllegalArgumentException("Unsupported ServletContextLoader:" + loader.getClass());
           }
       }
    }

    public void initServletContext(ServletContext servletContext) {
       for (int i = 0; i &lt; servletContextLoaders.size(); i++) { 
           ServletContextLoader loader = (ServletContextLoader) servletContextLoaders.get(i);
           loader.initServletContext(servletContext);
       }
    }

    public void closeServletContext(ServletContext servletContext) {
       for (int i = 0; i &lt; servletContextLoaders.size(); i++) { 
           ServletContextLoader loader = (ServletContextLoader) servletContextLoaders.get(i);
           loader.closeServletContext(servletContext);
       }
    }
}
</pre><br />其实这个具体的实现就是做了所有的调度。而所有实现ServletContextLoader这个接口，并且被注入到这个类中去的ServletContextLoader会被依次执行其中的方法。因而，所以如果以后要做系统启动时的数据加载，只要简单实现ServletContextLoader这个接口，并且配置到Spring的配置文件中即可。例如：<br /><pre name="code" class="java">
public class MasterDataLoader implements ServletContextLoader {
    private SystemService systemService;
    public void initServletContext(ServletContext servletContext) {
       // Master Data Loader
       Map masterData = systemService.getMasterData();
       // TODO Add to servletContext
    }

    public void closeServletContext(ServletContext servletContext) {
       // TODO Clear servletContext
    }

    public void setSystemService(SystemService systemService) {
       this.systemService = systemService;
    }
}
</pre><br />然后是Spring的配置文件片断：<br /><pre name="code" class="java">
&lt;bean id="systemService" parent="baseTxProxy">
  &lt;property name="target">
   &lt;bean class="com.adt.surecenter.service.impl.SystemServiceImpl"
    autowire="byName"/>
  &lt;/property>
 &lt;/bean>  

 &lt;bean id="commandMappingLoader" class="com.adt.surecenter.loader.CommandMappingLoader" autowire="byName" />
 
 &lt;bean id="masterDataLoader" class="com.adt.surecenter.loader.MasterDataLoader" autowire="byName" />
 
 &lt;bean id="servletContextLoader" class="com.adt.core.loader.ServletContextLoaderImpl" autowire="byName" >
  &lt;property name="servletContextLoaders">
   &lt;list>
    &lt;ref bean="masterDataLoader"/>
   &lt;/list>   
  &lt;/property>  
 &lt;/bean>

</pre><br />最后别忘记在web.xml中配置最初写的那个Listener，并且放置在Spring的Listener之后，就ok了。<br /><pre name="code" class="java">
	&lt;listener>
		&lt;listener-class>org.springframework.web.context.ContextLoaderListener&lt;/listener-class>
	&lt;/listener>
	
	&lt;listener>
		&lt;listener-class>com.adt.core.loader.ServletContextLoaderListener&lt;/listener-class>
	&lt;/listener>
</pre><br />此时，你在web.xml中的配置简化了，你所需要实现的具体的每个servletContextLoader看上去就像一个POJO，实现某个接口，收到Spring的管理，遵循Spring的IoC。<br /><br />好了，收工，洗手。
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/25039#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 26 Sep 2006 10:49:52 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/25039</link>
        <guid>http://downpour.javaeye.com/blog/25039</guid>
      </item>
      <item>
        <title>做人难</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/24528" style="color:red;">http://downpour.javaeye.com/blog/24528</a>&nbsp;
          发表时间: 2006年09月18日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这两天很莫名的陷入一个帖子的争论。吵架一向不是我的风格，故而每次Javaeye所经历的大的讨论，论战甚至吵架我都会站在一旁看看，决不参与。因为每个人都有自己的观点，或许我无法说服别人认同我的观点，不要紧，我同样也不反对别人的观点，你爱怎样想怎样想。只要我自己有一个相对正确的认识就好了。<br /><br />想想这两天真是发神经病了，居然回了那么多帖子，简直就是浪费时间，浪费生命啊。不过搞不懂，怎么这年头做人那么难？每说一句都要被人扣帽子，真是冤枉，终于理解那些文革中冤死的人了。真是口水的力量啊。<br /><br />顺便也简单抱怨几句。最近我总是对那些没事情就喜欢发表自己的言论或者作品的人特别反感。像前一段时间的一个什么SpeedFramework。一个纯粹不负责任的人，发布了一个不知道啥的框架，还在宣传。我所不解的是，为什么自己不能确定这个东西的好坏之前就发布出来，还不接收别人的意见和讨论。包括这两天的这个帖子也是。我觉得比较纳闷的是，在还没有做出非常详细的证明之前，居然可以连续对多个东西下肯定的结论。事实上，缺乏思考的言论有时候会给别人带来很多的误解。这也是对别人的一种不负责任啊。<br /><br />做人难，做一个“诤人”更难。以后还是恢复以前的风格，不再回复这样的垃圾了。
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/24528#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 18 Sep 2006 00:46:59 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/24528</link>
        <guid>http://downpour.javaeye.com/blog/24528</guid>
      </item>
      <item>
        <title>有关oracle中聚合函数rank和dense_rank的使用</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/24445" style="color:red;">http://downpour.javaeye.com/blog/24445</a>&nbsp;
          发表时间: 2006年09月15日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          今天碰到了一个业务场景，需要对一个表做分组分类的聚合查询。由于表很大，所以在数据库端解决这个问题是最佳的选择。于是请了Ray同学帮忙，他教了我一个Oracle上的聚合函数的使用，非常不错。<br /><br />其实现在Oracle对于rank和dense_rank已经支持合计功能，不过这次我仅仅使用了其分析功能。具体语法如下：RANK ( ) OVER ( [query_partition_clause] order_by_clause )。<br /><br />下面给出一些来自网上的示例：<br /><br />TABLE：S （subject，mark）<br /><br />数学，80<br />语文，70<br />数学，90<br />数学，60<br />数学，100<br />语文，88<br />语文，65<br />语文，77<br /><br />现在我想要的结果是：每门科目的前3名的分数<br /><br />数学，100<br />数学，90<br />数学，80<br />语文，88<br />语文，77<br />语文，70<br />那么语句就这么写：<br /><br />select * from (select rank() over(partition by subject order by mark desc) rk,S.* from S) T<br />where T.rk&lt;=3;<br /><br />dense_rank与rank()用法相当，但是有一个区别：dence_rank在处理相同的等级时，等级的数值不会跳过。rank则跳过。<br /><br />例如：表<br /><br />    A B C<br /> a          liu          wang<br /> a          jin          shu<br /> a          cai          kai<br /> b          yang      du<br /> b          lin          ying<br /> b          yao        cai<br /> b          yang      99<br /><br />例如：当rank时为：<br /><br />select m.a,m.b,m.c,rank() over(partition by a order by b) liu from test3 m<br /><br /> A          B             C          LIU<br /> a          cai          kai          1<br /> a          jin           shu        2<br /> a          liu           wang     3<br /> b          lin           ying        1<br /> b          yang      du           2<br /> b          yang      99           2<br /> b          yao        cai           4<br /><br />而如果用dense_rank时为：<br /><br />select m.a,m.b,m.c,dense_rank() over(partition by a order by b) liu from test3 m<br /><br /> A          B             C          LIU<br /> a          cai          kai          1<br /> a          jin           shu        2<br /> a          liu           wang     3<br /> b          lin           ying        1<br /> b          yang      du           2<br /> b          yang      99           2<br /> b          yao        cai           3
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/24445#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 15 Sep 2006 15:47:03 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/24445</link>
        <guid>http://downpour.javaeye.com/blog/24445</guid>
      </item>
      <item>
        <title>Struts与Spring整合的几种方法</title>
        <author>downpour</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://downpour.javaeye.com">downpour</a>&nbsp;
          链接：<a href="http://downpour.javaeye.com/blog/24239" style="color:red;">http://downpour.javaeye.com/blog/24239</a>&nbsp;
          发表时间: 2006年09月12日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          论坛中用Struts＋Spring的人不少，以前的帖子也有问过Struts＋Spring的整合方式。前面的帖子中ReadOnly老大曾经提到过Spring2.0新增加的一个整合方式。今天简单把这几种整合方式小结一下。<br /><br />在这之前，别忘了用一下Google大法，一般早有人会对类似的问题做过回答，果然，在ibm developworks上有一篇文章，一下子涵盖了三种整合方式，有兴趣的xdjm可以参考下面的链接：http://www-128.ibm.com/developerworks/cn/java/j-sr2.html。<br /><br />下面着重谈一下Spring2.0新增的一个整个方式，我感觉挺不错，可以完全将Struts的配置和Spring的配置分离。具体步骤如下：<br />1. 编写Spring的配置文件applicationContext.xml，简单起见，仅仅定义一个Service对象。<br /><div class="quote_title">引用</div><div class="quote_div"><br />&lt;?xml version="1.0" encoding="UTF-8"?><br />&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><br /><br />&lt;beans><br /><br />	&lt;bean id="userService" class="com.bearingpoint.gdc.zero.service.impl.UserServiceImpl" /><br /><br />&lt;/beans><br /></div><br />这看上去就和普通的Spring配置文件没有任何分别。<br />2. 编写Struts的配置文件struts-config.xml，注意其中的controller的配置，用到了Spring2.0的新特性。<br /><div class="quote_title">引用</div><div class="quote_div"><br />&lt;?xml version="1.0" encoding="UTF-8"?><br />&lt;!DOCTYPE struts-config <br />	PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" <br />	"http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"<br />><br /><br />&lt;struts-config><br /><br />&lt;action-mappings><br /><br />	&lt;action path="/addUser"<br />			type="com.bearingpoint.gdc.zero.action.user.AddUser"<br />			scope="request"<br />	><br />			&lt;forward name="success" path="/index.jsp" /><br />	&lt;/action><br />		<br />&lt;/action-mappings><br /><br />&lt;controller processorClass="org.springframework.web.struts.AutowiringRequestProcessor" /><br /><br />&lt;/struts-config><br /></div><br /><br />3. 然后为你的Struts的Action注入你需要的Service<br /><div class="quote_title">引用</div><div class="quote_div"><br />	private UserService userService;<br /><br />	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {<br />		User user = new User();<br />		userService.addUser(user);<br />		return mapping.findForward("success");<br />	}<br /><br />	/**<br />	 * @param userService<br />	 *            The userService to set.<br />	 */<br />	public void setUserService(UserService userService) {<br />		this.userService = userService;<br />	}<br /></div><br />看上去你好像啥都没做，而事实上，注入工作已经由AutowiringRequestProcessor自动完成。<br /><br />4. 编写web.xml进行测试。<br /><div class="quote_title">引用</div><div class="quote_div"><br />?xml version="1.0" ?><br />&lt;!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"><br /><br />&lt;web-app><br /><br />    &lt;context-param><br />        &lt;param-name>contextConfigLocation&lt;/param-name><br />        &lt;param-value>/WEB-INF/classes/applicationContext.xml&lt;/param-value><br />    &lt;/context-param><br /><br />	&lt;listener><br />		&lt;listener-class>org.springframework.web.context.ContextLoaderListener&lt;/listener-class><br />	&lt;/listener><br />	<br />	&lt;servlet><br />		&lt;servlet-name>struts&lt;/servlet-name><br />		&lt;servlet-class>org.apache.struts.action.ActionServlet&lt;/servlet-class><br />		&lt;init-param><br />			&lt;param-name>config&lt;/param-name><br />			&lt;param-value>/WEB-INF/classes/struts-config.xml&lt;/param-value><br />		&lt;/init-param><br />		&lt;init-param><br />			&lt;param-name>detail&lt;/param-name><br />			&lt;param-value>2&lt;/param-value><br />		&lt;/init-param><br />		&lt;init-param><br />			&lt;param-name>validate&lt;/param-name><br />			&lt;param-value>true&lt;/param-value><br />		&lt;/init-param><br />		&lt;load-on-startup>2&lt;/load-on-startup><br />	&lt;/servlet><br />		<br />	&lt;servlet-mapping><br />		&lt;servlet-name>struts&lt;/servlet-name><br />		&lt;url-pattern>*.do&lt;/url-pattern><br />	&lt;/servlet-mapping><br />	<br />    &lt;welcome-file-list><br />        &lt;welcome-file>index.jsp&lt;/welcome-file><br />    &lt;/welcome-file-list><br />&lt;/web-app><br /></div><br /><br />最后，启动Jetty进行测试，顺利运行通过！<br /><br />看上去如此简单，配置起来也没有什么很特别的地方，只是按照常规来写你的Spring和Struts的配置文件就好了。<br /><br />不过在这里还是说一下其中的要注意两个问题：<br />1. 这种autowire的注入支持两种不同的方式，分别是byName和byType，默认是byType。我想这对于绝大多数开发者来说是够了。<br />2. 鉴于在http://www.javaeye.com/topic/15057中所提到的OpenSessionInView模式的失效的问题。我仔细看了一下Spring的源码。对于这种autowire的整合方式，不推荐在struts-config.xml文件中配置ContextLoaderPlugIn，而是采用web.xml中的ContextLoaderListener来加载Spring的初始化配置。否则，你的OpenSessionInView模式可能会失效。
          <br/>
          <span style="color:red;">
            <a href="http://downpour.javaeye.com/blog/24239#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 12 Sep 2006 15:00:24 +0800</pubDate>
        <link>http://downpour.javaeye.com/blog/24239</link>
        <guid>http://downpour.javaeye.com/blog/24239</guid>
      </item>
  </channel>
</rss>