<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>ASOKA</title>
    <description>我们打着帝国主义的旗号，很平静地骂出一句：操！ </description>
    <link>http://lkfnn.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>WPS客户端开发实例</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/231234" style="color:red;">http://lkfnn.javaeye.com/blog/231234</a>&nbsp;
          发表时间: 2008年08月21日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>WPS客户端实例代码.</p>
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/231234#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 21 Aug 2008 16:24:37 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/231234</link>
        <guid>http://lkfnn.javaeye.com/blog/231234</guid>
      </item>
      <item>
        <title>反射实现AOP</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/201389" style="color:red;">http://lkfnn.javaeye.com/blog/201389</a>&nbsp;
          发表时间: 2008年06月08日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>好长时间没有用过Spring了. 突然拿起书.我都发现自己对AOP都不熟悉了.<br />其实AOP的意思就是面向切面编程.<br />OO注重的是我们解决问题的方法(封装成Method),而AOP注重的是许多解决解决问题的方法中的共同点,是对OO思想的一种补充!<br />还是拿人家经常举的一个例子讲解一下吧:<br />比如说,我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,或部分监控.也许我们就会在要一些方法前去加上一条日志记录,<br />我们写个例子看看我们最简单的解决方案<br />我们先写一个接口IHello.java代码如下:</p>
<pre name="code" class="java">package sinosoft.dj.aop.staticaop;
  
    public interface IHello {
     /**
       * 假设这是一个业务方法
       * @param name
       */
      void sayHello(String name);
  }</pre>
<p>&nbsp;里面有个方法,用于输入"Hello" 加传进来的姓名;我们去写个类实现IHello接口</p>
<pre name="code" class="java">package sinosoft.dj.aop.staticaop;
 
  public class Hello implements IHello {
 
      public void sayHello(String name) {
         System.out.println("Hello " + name);
     }
 
 }</pre>
<p>&nbsp;现在我们要为这个业务方法加上日志记录的业务,我们在不改变原代码的情况下,我们会去怎么做呢?也许,你会去写一个类去实现IHello接口,并依赖Hello这个类.代码如下:</p>
<pre name="code" class="java">package sinosoft.dj.aop.staticaop;
  
   public class HelloProxy implements IHello {
      private IHello hello;
  
       public HelloProxy(IHello hello) {
          this.hello = hello;
      }
  
      public void sayHello(String name) {
         Logger.logging(Level.DEBUGE, "sayHello method start .");
         hello.sayHello(name);
         Logger.logging(Level.INFO, "sayHello method end!");
 
     }
 
 }</pre>
<p>&nbsp;其中.Logger类和Level枚举代码如下:</p>
<pre name="code" class="java">Logger.java
  package sinosoft.dj.aop.staticaop;
  
  import java.util.Date;
  
   public class Logger{
       /**
       * 根据等级记录日志
       * @param level
       * @param context
      */
      public static void logging(Level level, String context) {
          if (level.equals(Level.INFO)) {
             System.out.println(new Date().toLocaleString() + " " + context);
         }
          if (level.equals(Level.DEBUGE)) {
             System.err.println(new Date() + " " + context);
         }
     }
 
 }
 
Level.java
 package sinosoft.dj.aop.staticaop;
 
  public enum Level {
     INFO,DEBUGE;
 }</pre>
<p>&nbsp;那我们去写个测试类看看,代码如下:</p>
<pre name="code" class="java">Test.java
 package sinosoft.dj.aop.staticaop;
 
  public class Test {
      public static void main(String[] args) {
         IHello hello = new HelloProxy(new Hello());
         hello.sayHello("Doublej");
     }
 }</pre>
<p>&nbsp;&nbsp;运行以上代码我们可以得到下面结果:<br />&nbsp;Tue Mar&nbsp; :: CST&nbsp; sayHello method start .<br />&nbsp;Hello Doublej<br />&nbsp;-- :: sayHello method end!</p>
<p>从上面的代码我们可以看出,hello对象是被HelloProxy这个所谓的代理态所创建的.这样,如果我们以后要把日志记录的功能去掉.那我们只要把得到hello对象的代码改成以下:</p>
<pre name="code" class="java">package sinosoft.dj.aop.staticaop;
 
  public class Test {
      public static void main(String[] args) {
         IHello hello = new Hello();
         hello.sayHello("Doublej");
     }
 }</pre>
<p>&nbsp;上面代码,可以说是AOP最简单的实现!<br />但是我们会发现一个问题,如果我们像Hello这样的类很多,那么,我们是不是要去写很多个HelloProxy这样的类呢.没错,是的.其实也是一种很麻烦的事.在jdk.以后.jdk跟我们提供了一个API&nbsp;&nbsp; java.lang.reflect.InvocationHandler的类. 这个类可以让我们在JVM调用某个类的方法时动态的为些方法做些什么事.让我们把以上的代码改一下来看看效果.<br />同样,我们写一个IHello的接口和一个Hello的实现类.在接口中.我们定义两个方法;代码如下 :</p>
<pre name="code" class="java">IHello.java
  package sinosoft.dj.aop.proxyaop;
  
   public interface IHello {
       /**
       * 业务处理A方法
       * @param name
       */
      void sayHello(String name);
       /**
      * 业务处理B方法
      * @param name
      */
     void sayGoogBye(String name);
 }
 


Hello.java 
  package sinosoft.dj.aop.proxyaop;
  
   public class Hello implements IHello {
  
       public void sayHello(String name) {
          System.out.println("Hello " + name);
      }
       public void sayGoogBye(String name) {
          System.out.println(name+" GoodBye!");
     }
 }</pre>
<p>&nbsp;我们一样的去写一个代理类.只不过.让这个类去实现java.lang.reflect.InvocationHandler接口,代码如下:</p>
<pre name="code" class="java">package sinosoft.dj.aop.proxyaop;
  
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Method;
  import java.lang.reflect.Proxy;
  
   public class DynaProxyHello implements InvocationHandler {
  
       /**
      * 要处理的对象(也就是我们要在方法的前后加上业务逻辑的对象,如例子中的Hello)
      */
     private Object delegate;
 
      /**
      * 动态生成方法被处理过后的对象 (写法固定)
      * 
      * @param delegate
      * @param proxy
      * @return
      */
      public Object bind(Object delegate) {
         this.delegate = delegate;
         return Proxy.newProxyInstance(
                 this.delegate.getClass().getClassLoader(), this.delegate
                         .getClass().getInterfaces(), this);
     }
      /**
      * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说,要处理的对象的方法只能通过此方法调用
      * 此方法是动态的,不是手动调用的
      */
     public Object invoke(Object proxy, Method method, Object[] args)
              throws Throwable {
         Object result = null;
          try {
             //执行原来的方法之前记录日志
             Logger.logging(Level.DEBUGE, method.getName() + " Method end  .");
             
             //JVM通过这条语句执行原来的方法(反射机制)
             result = method.invoke(this.delegate, args);
             //执行原来的方法之后记录日志
             Logger.logging(Level.INFO, method.getName() + " Method Start!");
          } catch (Exception e) {
             e.printStackTrace();
         }
         //返回方法返回值给调用者
         return result;
     }
 
 }</pre>
<p>&nbsp;上面类中出现的Logger类和Level枚举还是和上一上例子的实现是一样的.这里就不贴出代码了.</p>
<p>让我们写一个Test类去测试一下.代码如下:</p>
<pre name="code" class="java">Test.java
  package sinosoft.dj.aop.proxyaop;
  
   public class Test {
       public static void main(String[] args) {
          IHello hello = (IHello)new DynaProxyHello().bind(new Hello());
          hello.sayGoogBye("Double J");
          hello.sayHello("Double J");
          
      }
 }</pre>
<p>&nbsp;运行输出的结果如下:<br />&nbsp;Tue Mar&nbsp; :: CST&nbsp; sayGoogBye Method end&nbsp; .<br />&nbsp;Double J GoodBye!<br />&nbsp;-- :: sayGoogBye Method Start!<br />&nbsp;Tue Mar&nbsp; :: CST&nbsp; sayHello Method end&nbsp; .<br />&nbsp;Hello Double J<br />&nbsp;-- :: sayHello Method Start!</p>
<p>由于线程的关系,第二个方法的开始出现在第一个方法的结束之前.这不是我们所关注的!<br />从上面的例子我们看出.只要你是采用面向接口编程,那么,你的任何对象的方法执行之前要加上记录日志的操作都是可以的.他(DynaPoxyHello)自动去代理执行被代理对象(Hello)中的每一个方法,一个java.lang.reflect.InvocationHandler接口就把我们的代理对象和被代理对象解藕了.但是,我们又发现还有一个问题,这个DynaPoxyHello对象只能跟我们去在方法前后加上日志记录的操作.我们能不能把DynaPoxyHello对象和日志操作对象(Logger)解藕呢?<br />结果是肯定的.让我们来分析一下我们的需求.<br />我们要在被代理对象的方法前面或者后面去加上日志操作代码(或者是其它操作的代码),<br />那么,我们可以抽象出一个接口,这个接口里就只有两个方法,一个是在被代理对象要执行方法之前执行的方法,我们取名为start,第二个方法就是在被代理对象执行方法之后执行的方法,我们取名为end .接口定义如下 :</p>
<pre name="code" class="java">package sinosoft.dj.aop.proxyaop;
  
  import java.lang.reflect.Method;
  
   public interface IOperation {
       /**
       * 方法执行之前的操作
       * @param method
       */
     void start(Method method);
      /**
      * 方法执行之后的操作
      * @param method
      */
     void end(Method method);
 }</pre>
<p>&nbsp;我们去写一个实现上面接口的类.我们把作他真正的操作者,如下面是日志操作者的一个类:</p>
<pre name="code" class="java">LoggerOperation.java
 package sinosoft.dj.aop.proxyaop;
 
 import java.lang.reflect.Method;
 
  public class LoggerOperation implements IOperation {
 
      public void end(Method method) {
         Logger.logging(Level.DEBUGE, method.getName() + " Method end  .");
     }
 
      public void start(Method method) {
         Logger.logging(Level.INFO, method.getName() + " Method Start!");
     }
 
 }</pre>
<p>&nbsp;然后我们要改一下代理对象DynaProxyHello中的代码.如下:</p>
<pre name="code" class="java"> package sinosoft.dj.aop.proxyaop;
  
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Method;
  import java.lang.reflect.Proxy;
  
   public class DynaProxyHello implements InvocationHandler {
       /**
       * 操作者
      */
     private Object proxy;
      /**
      * 要处理的对象(也就是我们要在方法的前后加上业务逻辑的对象,如例子中的Hello)
      */
     private Object delegate;
 
      /**
      * 动态生成方法被处理过后的对象 (写法固定)
      * 
      * @param delegate
      * @param proxy
      * @return
      */
      public Object bind(Object delegate,Object proxy) {
         
         this.proxy = proxy;
         this.delegate = delegate;
         return Proxy.newProxyInstance(
                 this.delegate.getClass().getClassLoader(), this.delegate
                         .getClass().getInterfaces(), this);
     }
      /**
      * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说,要处理的对象的方法只能通过此方法调用
      * 此方法是动态的,不是手动调用的
      */
     public Object invoke(Object proxy, Method method, Object[] args)
              throws Throwable {
         Object result = null;
          try {
             //反射得到操作者的实例
             Class clazz = this.proxy.getClass();
             //反射得到操作者的Start方法
             Method start = clazz.getDeclaredMethod("start",
                      new Class[] { Method.class });
             //反射执行start方法
              start.invoke(this.proxy, new Object[] { method });
             //执行要处理对象的原本方法
             result = method.invoke(this.delegate, args);
 //            反射得到操作者的end方法
             Method end = clazz.getDeclaredMethod("end",
                      new Class[] { Method.class });
        //            反射执行end方法
              end.invoke(this.proxy, new Object[] { method });
 
          } catch (Exception e) {
             e.printStackTrace();
         }
         return result;
     }
 
 }</pre>
<p>&nbsp;然后我们把Test.java中的代码改一下.测试一下:</p>
<pre name="code" class="java">package sinosoft.dj.aop.proxyaop;
 
  public class Test {
      public static void main(String[] args) {
         IHello hello = (IHello)new DynaProxyHello().bind(new Hello(),new LoggerOperation());
         hello.sayGoogBye("Double J");
         hello.sayHello("Double J");
         
     }
 }</pre>
<p>&nbsp;结果还是一样的吧.</p>
<p>如果你想在每个方法之前加上日志记录,而不在方法后加上日志记录.你就把LoggerOperation类改成如下:</p>
<pre name="code" class="java"> package sinosoft.dj.aop.proxyaop;
  
  import java.lang.reflect.Method;
  
   public class LoggerOperation implements IOperation {
  
       public void end(Method method) {
          //Logger.logging(Level.DEBUGE, method.getName() + " Method end  .");
      }
 
      public void start(Method method) {
         Logger.logging(Level.INFO, method.getName() + " Method Start!");
     }
 
 }</pre>
<p>&nbsp;运行一下.你就会发现,每个方法之后没有记录日志了. 这样,我们就把代理者和操作者解藕了!</p>
<p>下面留一个问题给大家,如果我们不想让所有方法都被日志记录,我们应该怎么去解藕呢.?<br />我的想法是在代理对象的public Object invoke(Object proxy, Method method, Object[] args)方法里面加上个if(),对传进来的method的名字进行判断,判断的条件存在XML里面.这样我们就可以配置文件时行解藕了.如果有兴趣的朋友可以把操作者,被代理者,都通过配置文件进行配置 ,那么就可以写一个简单的SpringAOP框架了.</p>
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/201389#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 08 Jun 2008 17:55:47 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/201389</link>
        <guid>http://lkfnn.javaeye.com/blog/201389</guid>
      </item>
      <item>
        <title>ORACLE 全角数字转半角数字</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/200416" style="color:red;">http://lkfnn.javaeye.com/blog/200416</a>&nbsp;
          发表时间: 2008年06月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>数据库表　test 字段 id&nbsp; name age</p>
<p>全角数字:１２３４５６</p>
<p>半角数字:123456</p>
<p>&nbsp;</p>
<p>length和lengthb的区别:</p>
<p>length(123456)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6</p>
<p>lengthb(１２３４５６)&nbsp;&nbsp;&nbsp; 12</p>
<p>&nbsp;</p>
<p>to_single_byte函数用法:</p>
<p>to_single_byte(１２３４５６)&nbsp;&nbsp; 123456</p>
<p>&nbsp;</p>
<p>查找所有全角的数字：</p>
<p>select age from test where lengthB(age) &gt;6</p>
<p>&nbsp;</p>
<p>替换全角的为半角的:</p>
<p>update test&nbsp; t1 set t1.age = (select to_single_byte(t2.age) from test&nbsp; t2 where t1.id = t2.id)</p>
<p>&nbsp;</p>
<p>ok!!</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/200416#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 05 Jun 2008 10:42:55 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/200416</link>
        <guid>http://lkfnn.javaeye.com/blog/200416</guid>
      </item>
      <item>
        <title>Hibernate的&lt;query&gt;标签使用</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/195848" style="color:red;">http://lkfnn.javaeye.com/blog/195848</a>&nbsp;
          发表时间: 2008年05月22日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>Hibernate也可以将查询的SQL语句写在配置文件里，避免硬编码下面是个例子：</p>
<pre name="code" class="xml">&lt;?xml version="1.0"?&gt; 
&lt;!DOCTYPE hibernate-mapping 
    PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" 
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"&gt; 

&lt;hibernate-mapping&gt; 

    &lt;class name="onlyfun.caterpillar.User" table="USER"&gt; 

        &lt;id name="id" type="string"&gt; 
            &lt;column name="user_id" sql-type="char(32)" /&gt; 
            &lt;generator class="uuid.hex"/&gt; 
        &lt;/id&gt; 

        &lt;property name="name" type="string" not-null="true"&gt; 
            &lt;column name="name" length="16" not-null="true"/&gt; 
        &lt;/property&gt; 

        &lt;property name="sex" type="char"/&gt; 

        &lt;property name="age" type="int"/&gt; 

    &lt;/class&gt; 

    &lt;query name="onlyfun.caterpillar.queryUser"&gt; 
        &lt;![CDATA[ 
            select user.name from User as user where user.age = :age and user.sex = :sex 
        ]]&gt; 
    &lt;/query&gt; 

&lt;/hibernate-mapping&gt;</pre>
<p>&nbsp;在程序中引用&lt;query&gt;的name属性可以访问到sql：</p>
<pre name="code" class="java">Query query = session.getNamedQuery("onlyfun.caterpillar.queryUser"); 
query.setInteger("age", 25); 
query.setCharacter("sex", 'M'); 

List names = query.list(); 
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) { 
    String name = (String) iterator.next(); 
    System.out.println("name: " + name); 
}</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/195848#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 22 May 2008 23:58:49 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/195848</link>
        <guid>http://lkfnn.javaeye.com/blog/195848</guid>
      </item>
      <item>
        <title>TinyMCE3.0.8简体中文字体修改版</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/195295" style="color:red;">http://lkfnn.javaeye.com/blog/195295</a>&nbsp;
          发表时间: 2008年05月21日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器，由JavaScript写成。它对IE6+和Firefox1.5+都有着非常良好的支持。功能方面虽然不能称得上是最强，但绝对能够满足大部分网站的需求，并且功能配置灵活简单,更重要的是，TinyMCE是一个根据LGPL license发布的自由软件，你可以把它用于商业应用。<br /><br />     <span style="color: blue">附件是一个TinyMCE3.0.8的简体中文版,修改了中文字体太小的问题.</span><br /><br />例子:<br /><pre name="code" class="html">&lt;textarea id="beValue" rows="3" cols="4">&lt;/textarea></pre><br /><br />得到输入的值:<br /><br /><pre name="code" class="js">tinyMCE.get("beValue").getContent()</pre><br /><br />为什么有这么变态的取值方法!!!
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/195295#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 21 May 2008 15:04:27 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/195295</link>
        <guid>http://lkfnn.javaeye.com/blog/195295</guid>
      </item>
      <item>
        <title>ORACLE的MERGE 函数使用</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/194592" style="color:red;">http://lkfnn.javaeye.com/blog/194592</a>&nbsp;
          发表时间: 2008年05月19日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          MERGE语句是Oracle9i新增的语法，用来合并UPDATE和INSERT语句。通过MERGE语句，根据一张表或子查询的连接条件对另外一张表进行查询，连接条件匹配上的进行UPDATE，无法匹配的执行INSERT。这个语法仅需要一次全表扫描就完成了全部工作，执行效率要高于INSERT＋UPDATE。 <br />下面看个具体的例子： <br />SQL> CREATE TABLE T AS SELECT ROWNUM ID, A.* FROM DBA_OBJECTS A;<br />表已创建。<br />SQL> CREATE TABLE T1 AS <br />2 SELECT ROWNUM ID, OWNER, TABLE_NAME, CAST('TABLE' AS VARCHAR2(100)) OBJECT_TYPE<br />3 FROM DBA_TABLES;<br />表已创建。<br />SQL> MERGE INTO T1 USING T <br />2 ON (T.OWNER = T1.OWNER AND T.OBJECT_NAME = T1.TABLE_NAME AND T.OBJECT_TYPE = T1.OBJECT_TYPE)<br />3 WHEN MATCHED THEN UPDATE SET T1.ID = T.ID<br />4 WHEN NOT MATCHED THEN INSERT VALUES (T.ID, T.OWNER, T.OBJECT_NAME, T.OBJECT_TYPE);<br />6165 行已合并。<br />SQL> SELECT ID, OWNER, OBJECT_NAME, OBJECT_TYPE FROM T<br />2 MINUS<br />3 SELECT * FROM T1;<br />未选定行<br />MERGE语法其实很简单，下面稍微修改一下例子。<br />SQL> DROP TABLE T;<br />表已丢弃。<br />SQL> DROP TABLE T1;<br />表已丢弃。<br />SQL> CREATE TABLE T AS SELECT ROWNUM ID, A.* FROM DBA_OBJECTS A;<br />表已创建。<br />SQL> CREATE TABLE T1 AS SELECT ROWNUM ID, OWNER, TABLE_NAME FROM DBA_TABLES;<br />表已创建。<br />SQL> MERGE INTO T1 USING T <br />2 ON (T.OWNER = T1.OWNER AND T.OBJECT_NAME = T1.TABLE_NAME)<br />3 WHEN MATCHED THEN UPDATE SET T1.ID = T.ID<br />4 WHEN NOT MATCHED THEN INSERT VALUES (T.ID, T.OWNER, T.OBJECT_NAME);<br />MERGE INTO T1 USING T<br />*<br />ERROR 位于第 1 行:<br />ORA-30926: 无法在源表中获得一组稳定的行<br />这个错误是使用MERGE最常见的错误，造成这个错误的原因是由于通过连接条件得到的T的记录不唯一。最简单的解决方法类似：<br />SQL> MERGE INTO T1 <br />2 USING (SELECT OWNER, OBJECT_NAME, MAX(ID) ID FROM T GROUP BY OWNER, OBJECT_NAME) T <br />3 ON (T.OWNER = T1.OWNER AND T.OBJECT_NAME = T1.TABLE_NAME)<br />4 WHEN MATCHED THEN UPDATE SET T1.ID = T.ID<br />5 WHEN NOT MATCHED THEN INSERT VALUES (T.ID, T.OWNER, T.OBJECT_NAME);<br />5775 行已合并。<br />另外，MERGE语句的UPDATE不能修改用于连接的列，否则会报错，详细信息可以参考：http://blog.itpub.net/post/468/14844<br /><br />===============================================================<br /><br />ref: http://tomszrp.itpub.net/post/11835/263865 <br /><br />在Oracle 10g之前，merge语句支持匹配更新和不匹配插入2种简单的用法，在10g中Oracle对merge语句做了增强，增加了条件选项和DELETE操作。下面我通过一个demo来简单介绍一下10g中merge的增强和10g前merge的用法。<br /><br />　<br /><br />参考Oracle 的SQL Reference，大家可以看到Merge Statement的语法如下：<br />MERGE [hint] INTO [schema .] table [t_alias] USING [schema .] <br />{ table | view | subquery } [t_alias] ON ( condition ) <br />WHEN MATCHED THEN merge_update_clause <br />WHEN NOT MATCHED THEN merge_insert_clause;<br /><br />下面我在windows xp 下10.2.0.1版本上做一个测试看看<br /><br /><br />SQL> select * from v$version;<br /><br />BANNER<br />----------------------------------------------------------------<br />Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod<br />PL/SQL Release 10.2.0.1.0 - Production<br />CORE    10.2.0.1.0      Production<br />TNS for 32-bit Windows: Version 10.2.0.1.0 - Production<br />NLSRTL Version 10.2.0.1.0 - Production<br /><br />SQL> <br />一、创建测试用的表<br />SQL> create table subs(msid number(9),<br />  2                    ms_type char(1),<br />  3                    areacode number(3)<br />  4                    );<br /><br />表已创建。<br /><br />SQL> create table acct(msid number(9),<br />  2                    bill_month number(6),<br />  3                    areacode   number(3),<br />  4                    fee        number(8,2) default 0.00);<br /><br />表已创建。<br /><br />SQL> <br />SQL> insert into subs values(905310001,0,531);<br /><br />已创建 1 行。<br /><br />SQL> insert into subs values(905320001,1,532);<br /><br />已创建 1 行。<br /><br />SQL> insert into subs values(905330001,2,533);<br /><br />已创建 1 行。<br /><br />SQL> commit;<br /><br />提交完成。<br /><br />SQL> <br />　<br /><br />二、下面先演示一下merge的基本功能<br /><br />1) matched 和not matched clauses 同时使用<br />   merge into acct a <br />     using subs b on (a.msid=b.msid)<br />   when MATCHED then<br />        update set a.areacode=b.areacode<br />   when NOT MATCHED then<br />        insert(msid,bill_month,areacode) <br />        values(b.msid,'200702',b.areacode);<br />2) 只有not matched clause,也就是只插入不更新<br />   merge into acct a <br />     using subs b on (a.msid=b.msid)   <br />   when NOT MATCHED then<br />        insert(msid,bill_month,areacode) <br />        values(b.msid,'200702',b.areacode);<br /><br />3) 只有matched clause, 也就是只更新不插入<br />   merge into acct a <br />     using subs b on (a.msid=b.msid)<br />   when MATCHED then<br />        update set a.areacode=b.areacode<br />        <br />Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 <br />Connected as study<br /><br />SQL> select * from subs;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905310001 0            531<br /> 905320001 1            532<br /> 905330001 2            533<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /><br />SQL> <br />SQL> merge into acct a<br />  2       using subs b on (a.msid=b.msid)<br />  3     when MATCHED then<br />  4          update set a.areacode=b.areacode<br />  5     when NOT MATCHED then<br />  6          insert(msid,bill_month,areacode) <br />  7          values(b.msid,'200702',b.areacode);<br /><br />Done<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905320001     200702      532       0.00<br /> 905330001     200702      533       0.00<br /> 905310001     200702      531       0.00<br /><br />SQL> insert into subs values(905340001,3,534);<br /><br />1 row inserted<br /><br />SQL> select * from subs;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905340001 3            534<br /> 905310001 0            531<br /> 905320001 1            532<br /> 905330001 2            533<br /><br />SQL> <br />SQL> merge into acct a<br />  2       using subs b on (a.msid=b.msid)<br />  3     when NOT MATCHED then<br />  4          insert(msid,bill_month,areacode) <br />  5          values(b.msid,'200702',b.areacode);<br /><br />Done<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905320001     200702      532       0.00<br /> 905330001     200702      533       0.00<br /> 905310001     200702      531       0.00<br /> 905340001     200702      534       0.00<br /><br />SQL> update subs set areacode=999;<br /><br />4 rows updated<br /><br />SQL> select * from subs;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905340001 3            999<br /> 905310001 0            999<br /> 905320001 1            999<br /> 905330001 2            999<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905320001     200702      532       0.00<br /> 905330001     200702      533       0.00<br /> 905310001     200702      531       0.00<br /> 905340001     200702      534       0.00<br /><br />SQL> <br />SQL> merge into acct a<br />  2       using subs b on (a.msid=b.msid)<br />  3     when MATCHED then<br />  4          update set a.areacode=b.areacode;<br /><br />Done<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905320001     200702      999       0.00<br /> 905330001     200702      999       0.00<br /> 905310001     200702      999       0.00<br /> 905340001     200702      999       0.00<br /><br />SQL> <br />　<br />三、10g中增强一：条件操作<br /><br />1) matched 和not matched clauses 同时使用<br />   merge into acct a <br />     using subs b on (a.msid=b.msid)     <br />   when MATCHED then<br />        update set a.areacode=b.areacode<br />        where b.ms_type=0<br />   when NOT MATCHED then<br />        insert(msid,bill_month,areacode) <br />        values(b.msid,'200702',b.areacode)<br />        where b.ms_type=0;<br />2) 只有not matched clause,也就是只插入不更新<br />   merge into acct a <br />     using subs b on (a.msid=b.msid)   <br />   when NOT MATCHED then<br />        insert(msid,bill_month,areacode) <br />        values(b.msid,'200702',b.areacode)<br />        where b.ms_type=0;<br /><br />3) 只有matched clause, 也就是只更新不插入<br />   merge into acct a <br />     using subs b on (a.msid=b.msid)<br />   when MATCHED then<br />        update set a.areacode=b.areacode<br />        where b.ms_type=0;<br />        <br />        <br />Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 <br />Connected as study<br /><br />SQL> select * from subs;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905310001 0            531<br /> 905320001 1            532<br /> 905330001 2            533<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /><br />SQL> <br />SQL> merge into acct a<br />  2       using subs b on (a.msid=b.msid)<br />  3     when MATCHED then<br />  4          update set a.areacode=b.areacode<br />  5          where b.ms_type=0<br />  6     when NOT MATCHED then<br />  7          insert(msid,bill_month,areacode) <br />  8          values(b.msid,'200702',b.areacode)<br />  9          where b.ms_type=0;<br /><br />Done<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905310001     200702      531       0.00<br /><br />SQL> insert into subs values(905360001,0,536);<br /><br />1 row inserted<br /><br />SQL> select * from subs;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905360001 0            536<br /> 905310001 0            531<br /> 905320001 1            532<br /> 905330001 2            533<br /><br />SQL> <br />SQL> merge into acct a<br />  2       using subs b on (a.msid=b.msid)<br />  3     when NOT MATCHED then<br />  4          insert(msid,bill_month,areacode) <br />  5          values(b.msid,'200702',b.areacode)<br />  6          where b.ms_type=0;<br /><br />Done<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905310001     200702      531       0.00<br /> 905360001     200702      536       0.00<br /><br />SQL> update subs set areacode=888 where ms_type=0;<br /><br />2 rows updated<br /><br />SQL> select * from subs;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905360001 0            888<br /> 905310001 0            888<br /> 905320001 1            532<br /> 905330001 2            533<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905310001     200702      531       0.00<br /> 905360001     200702      536       0.00<br /><br />SQL> <br />SQL> merge into acct a<br />  2       using subs b on (a.msid=b.msid)<br />  3     when MATCHED then<br />  4          update set a.areacode=b.areacode<br />  5          where b.ms_type=0;<br /><br />Done<br /><br />SQL> select * from acct;<br /><br />      MSID BILL_MONTH AREACODE        FEE<br />---------- ---------- -------- ----------<br /> 905310001     200702      888       0.00<br /> 905360001     200702      888       0.00<br /><br />SQL> <br />四、10g中增强二：删除操作<br />An optional DELETE WHERE clause can be used to clean up after a <br />merge operation. Only those rows which match both the ON clause <br />and the DELETE WHERE clause are deleted.<br /><br />   merge into acct a <br />     using subs b on (a.msid=b.msid)<br />   when MATCHED then<br />        update set a.areacode=b.areacode        <br />        delete where (b.ms_type!=0);             <br /><br />SQL> select * from subs;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905310001 0            531<br /> 905320001 1            532<br /> 905330001 2            533<br /><br />SQL> select * from acct;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905310001 0            531<br /> 905320001 1            532<br /> 905330001 2            533<br /><br />SQL> <br />SQL>  merge into acct a<br />  2       using subs b on (a.msid=b.msid)<br />  3     when MATCHED then<br />  4          update set a.areacode=b.areacode<br />  5          delete where (b.ms_type!=0);<br /><br />Done<br /><br />SQL> select * from acct;<br /><br />      MSID MS_TYPE AREACODE<br />---------- ------- --------<br /> 905310001 0            531<br /><br />SQL> <br /><br />更为详尽的语法，请参考Oracle SQL Reference手册！
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/194592#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 19 May 2008 21:40:00 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/194592</link>
        <guid>http://lkfnn.javaeye.com/blog/194592</guid>
      </item>
      <item>
        <title>FCKeditor2.6 For Java</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/194548" style="color:red;">http://lkfnn.javaeye.com/blog/194548</a>&nbsp;
          发表时间: 2008年05月19日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-size: small; color: #0000ff;"><strong>1、首先登陆</strong></span><a href="http://www.fckeditor.net/download" target="_blank"><span style="font-size: small; color: #0000ff;"><strong>www.fckeditor.net/download</strong></span></a><span style="font-size: small; color: #0000ff;"><strong>下载FCKeditor的最新版本，需要下载2个压缩包，一个是基本应用，另一个是在为在jsp下所准备的配置。</strong></span></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FCKeditor 2.6 下载地址：<a href="http://sourceforge.net/project/downloading.php?group_id=75348&amp;filename=FCKeditor_2.6b.zip" target="_blank"><span style="color: #006600;">sourceforge.net/project/downloading.php</span></a></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FCKeditor.Java 下载地址：<a href="http://sourceforge.net/project/downloading.php?group_id=75348&amp;use_mirror=jaist&amp;filename=FCKeditor-2.3.gz.tar&amp;21055595" target="_blank"><span style="color: #006600;">sourceforge.net/project/downloading.php</span></a></p>
<p>下载之后分别为：<a href="http://sourceforge.net/project/downloading.php?group_id=75348&amp;filename=FCKeditor_2.6b.zip" target="_blank"><span style="color: #006600;">FCKeditor_2.6.zip</span></a> 和 <a href="http://sourceforge.net/project/downloading.php?group_id=75348&amp;use_mirror=jaist&amp;filename=FCKeditor-2.3.gz.tar&amp;21055595" target="_blank"><span style="color: #006600;">FCKeditor-2.3.zip</span></a> 将它们分别解压。</p>
<p><strong><span style="font-size: small; color: #0000ff;">2、首先在Eclipse下建立一个新项目例如：test&nbsp;&nbsp;&nbsp; 即</span></strong><a href="http://localhost:8080/test"><strong><span style="font-size: small; color: #0000ff;">http://localhost:8080/test</span></strong></a></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 在项目中新建文件夹 FCKeditor，然后将解压后的FCKeditor_2.6下fckeditor里面的editor、fckconfig.js、fckeditor.js、fckstyles.xml、fcktemplates.xml拷贝到FCKeditor目录下</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将解压后的FCKeditor-2.3文件夹中web/WEB-INF/lib下的包拷贝到test项目的lib中。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将FCKeditor-2.3文件夹下src下的FCKeditor.tld拷贝到test项目的WEB-INF下。</p>
<p><strong><span style="font-size: small; color: #0000ff;">3、修改WEB-INF下的web.xml， 如下：</span></strong></p>
<div class="dp-highlighter">
<div class="bar">
<div class="tools">
<pre name="code" class="xml"> &lt;servlet&gt;
       &lt;servlet-name&gt;Connector&lt;/servlet-name&gt;
       &lt;servlet-class&gt;com.fredck.FCKeditor.connector.ConnectorServlet&lt;/servlet-class&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;baseDir&lt;/param-name&gt;
           &lt;param-value&gt;/UserFiles/&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;debug&lt;/param-name&gt;
           &lt;param-value&gt;true&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
   &lt;/servlet&gt;

   &lt;servlet&gt;
       &lt;servlet-name&gt;SimpleUploader&lt;/servlet-name&gt;
       &lt;servlet-class&gt;com.fredck.FCKeditor.uploader.SimpleUploaderServlet&lt;/servlet-class&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;baseDir&lt;/param-name&gt;
           &lt;param-value&gt;/UserFiles/&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;debug&lt;/param-name&gt;
           &lt;param-value&gt;true&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;enabled&lt;/param-name&gt;
           &lt;param-value&gt;true&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;AllowedExtensionsFile&lt;/param-name&gt;
           &lt;param-value&gt;&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;DeniedExtensionsFile&lt;/param-name&gt;
           &lt;param-value&gt;php|php3|php5|phtml|asp|aspx|ascx|jsp|cfm|cfc|pl|bat|exe|dll|reg|cgi&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;AllowedExtensionsImage&lt;/param-name&gt;
           &lt;param-value&gt;jpg|gif|jpeg|png|bmp&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;DeniedExtensionsImage&lt;/param-name&gt;
           &lt;param-value&gt;&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;AllowedExtensionsFlash&lt;/param-name&gt;
           &lt;param-value&gt;swf|fla&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;init-param&gt;
           &lt;param-name&gt;DeniedExtensionsFlash&lt;/param-name&gt;
           &lt;param-value&gt;&lt;/param-value&gt;
       &lt;/init-param&gt;
       &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
   &lt;/servlet&gt;

&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;Connector&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/FCKeditor/editor/filemanager/browser/default/connectors/jsp/connector&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;

&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;SimpleUploader&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/FCKeditor/editor/filemanager/upload/simpleuploader&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;</pre>
&nbsp;</div>
</div>
</div>
<p><strong><span style="font-size: small; color: #0000ff;">4、修改FCKeditor文件夹下的fckeditor.js</span></strong></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 修改第50行的FCKeditor.BasePath。</p>
<p>改之后：</p>
<div class="dp-highlighter">
<div class="bar">
<div class="tools">
<pre name="code" class="js">50 FCKeditor.BasePath = 'FCKeditor/' ;</pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p><strong><span style="font-size: small; color: #0000ff;">5、修改FCKeditor文件夹下的fckconfig.js</span></strong></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 修改FCKConfig.DefaultLanguage、FCKConfig.LinkBrowserURL、FCKConfig.ImageBrowserURL、FCKConfig.FlashBrowserURL、</p>
<p>改之后：</p>
<div class="dp-highlighter">
<div class="bar">
<div class="tools">
<pre name="code" class="js">FCKConfig.DefaultLanguage   = 'zh-cn' ;

FCKConfig.LinkBrowserURL = FCKConfig.BasePath + "filemanager/browser/default/browser.html?Connector=connectors/jsp/connector" ;

FCKConfig.ImageBrowserURL = FCKConfig.BasePath + "filemanager/browser/default/browser.html?Type=Image&amp;Connector=connectors/jsp/connector" ;

FCKConfig.FlashBrowserURL = FCKConfig.BasePath + "filemanager/browser/default/browser.html?Type=Flash&amp;Connector=connectors/jsp/connector" ;

FCKConfig.LinkUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=File' ;

FCKConfig.ImageUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Image' ;

FCKConfig.FlashUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Flash' ;
</pre>
</div>
</div>
</div>
<p><strong><span style="font-size: small; color: #0000ff;">6、default.jsp内容如下：</span></strong></p>
<p><span style="font-size: x-small;"></span>
<pre name="code" class="html">&lt;%@ page contentType="text/html; charset=gb2312" language="java" %&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312" /&gt;
&lt;title&gt;FCKeditor测试&lt;/title&gt;
&lt;script type="text/javascript" src="FCKeditor/fckeditor.js"&gt;&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;form id="form1" name="form1" method="post" action="default_do.jsp"&gt;
&lt;table width="100%" border="0"&gt;
&lt;tr&gt;
    &lt;td height="25"&gt;
      &lt;textarea name="contest" id="contest" style="width:100%; height:400px;"&gt;&lt;/textarea&gt;
&lt;script type="text/javascript"&gt;
var oFCKeditor = new FCKeditor( 'contest' ) ;
//oFCKeditor.BasePath = 'FCKeditor/' ;
oFCKeditor.ToolbarSet = 'Default' ;
oFCKeditor.Width = '100%' ;
oFCKeditor.Height = '400' ;
oFCKeditor.Value = '' ;
oFCKeditor.ReplaceTextarea(); 
//oFCKeditor.Create() ;
&lt;/script&gt;
      &lt;input type="submit" name="Submit" value="提交" /&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</p>
<div class="dp-highlighter">
<div class="bar">
<div class="tools"></div>
</div>
</div>
<p><span style="font-size: small;"></span>
<pre name="code" class="html">&lt;%@ page contentType="text/html; charset=gb2312" language="java" %&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312" /&gt;
&lt;title&gt;FCKeditor测试接收结果&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;%
    String contest = new String(request.getParameter("contest").getBytes("ISO8859_1"), "GB2312");
out.print(contest);
%&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
</p>
<p><strong><span style="font-size: small; color: #0000ff;">7、default_do.jsp内容如下：</span></strong></p>
<div class="dp-highlighter">
<div class="bar">
<div class="tools"></div>
</div>
</div>
<p>
<p><span style="font-size: small;"><font size="3">
<p><strong><span style="color: #0000ff;">最后测试：</span></strong><a href="http://localhost:8080/test/default.jsp"><strong><span style="color: #0000ff;">http://localhost:8080/test/default.jsp</span></strong></a>
<p>&nbsp;</p>
<p>&nbsp;</p>
</p>
</font></span>
<p>&nbsp;</p>
</p>
</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/194548#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 19 May 2008 18:41:16 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/194548</link>
        <guid>http://lkfnn.javaeye.com/blog/194548</guid>
      </item>
      <item>
        <title>Hibernate的cascade和inverse</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/193620" style="color:red;">http://lkfnn.javaeye.com/blog/193620</a>&nbsp;
          发表时间: 2008年05月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          cascade属性并不是多对多关系一定要用的，有了它只是让我们在插入或删除对像时更方便一些，只要在cascade的源头上插入或是删除，所有cascade的关系就会被自己动的插入或是删除。便是为了能正确的cascade，unsaved-value是个很重要的属性。Hibernate通过这个属性来判断一个对象应该save还是update，如果这个对象的id是unsaved-value的话，那说明这个对象不是persistence object要save（insert)；如果id是非unsaved-value的话，那说明这个对象是persistence object（数据库中已存在），只要update就行了。saveOrUpdate方法用的也是这个机制。 <br />inverse属性默认是false的，就是说关系的两端都来维护关系。这个意思就是说，如有一个Student, Teacher和TeacherStudent表，Student和Teacher是多对多对多关系，这个关系由TeacherStudent这个表来表现。那么什么时候插入或删除TeacherStudent表中的记录来维护关系呢？在用hibernate时，我们不会显示的对TeacherStudent表做操作。对TeacherStudent的操作是hibernate帮我们做的。hibernate就是看hbm文件中指定的是"谁"维护关系，那个在插入或删除"谁"时，就会处发对关系表的操作。前提是"谁"这个对象已经知道这个关系了，就是说关系另一头的对象已经set或是add到"谁"这个对象里来了。前面说过inverse默认是false，就是关系的两端都维护关系，对其中任一个操作都会处发对表系表的操作。当在关系的一头，如Student中的bag或set中用了inverse＝"true"时，那就代表关系是由另一关维护的（Teacher）。就是说当这插入Student时，不会操作TeacherStudent表，即使Student已经知道了关系。只有当Teacher插入或删除时才会处发对关系表的操作。所以，当关系的两头都用inverse="true"是不对的，就会导致任何操作都不处发对关系表的操作。当两端都是inverse="false"或是default值是，在代码对关系显示的维护也是不对的，会导致在关系表中插入两次关系。 <br /><br />在一对多关系中inverse就更有意义了。在多对多中，在哪端inverse="true"效果差不多（在效率上）。但是在一对多中，如果要一方维护关系，就会使在插入或是删除"一"方时去update"多"方的每一个与这个"一"的对象有关系的对象。而如果让"多"方面维护关系时就不会有update操作，因为关系就是在多方的对象中的，直指插入或是删除多方对象就行了。当然这时也要遍历"多"方的每一个对象显示的操作修关系的变化体现到DB中。不管怎样说，还是让"多"方维护关系更直观一些。cascade和inverse有什么区别？ <br /><br />可以这样理解，cascade定义的是关系两端对象到对象的级联关系；而inverse定义的是关系和对象的级联关系。 <br /><br />   １、到底在哪用cascade="..."？   <br />    <br />  cascade属性并不是多对多关系一定要用的，有了它只是让我们在插入或删除对像时更方便一些，只要在cascade的源头上插入或是删除，所有 cascade的关系就会被自己动的插入或是删除。便是为了能正确的cascade，unsaved-value是个很重要的属性。Hibernate通 过这个属性来判断一个对象应该save还是update，如果这个对象的id是unsaved-value的话，那说明这个对象不是 persistence   object要save（insert)；如果id是非unsaved-value的话，那说明这个对象是persistence   object（数据库中已存在），只要update就行了。saveOrUpdate方法用的也是这个机制。   <br />    <br />  ２、到底在哪用inverse="ture"?   <br />   “set的inverse属性决定是否把对set的改动反映到数据库中去。inverse=false————反映；inverse=true————不反映”inverse属性默认为false<br />    <br />  inverse属性默认是false的，就是说关系的两端都来维护关系。这个意思就是说，如有一个Student,   Teacher和TeacherStudent表，Student和Teacher是多对多对多关系，这个关系由TeacherStudent这个表来表 现。那么什么时候插入或删除TeacherStudent表中的记录来维护关系呢？在用hibernate时，我们不会显示的对 TeacherStudent表做操作。对TeacherStudent的操作是hibernate帮我们做的。hibernate就是看hbm文件中指 定的是"谁"维护关系，那个在插入或删除"谁"时，就会处发对关系表的操作。前提是"谁"这个对象已经知道这个关系了，就是说关系另一头的对象已经set 或是add到"谁"这个对象里来了。前面说过inverse默认是false，就是关系的两端都维护关系，对其中任一个操作都会处发对表系表的操作。当在 关系的一头，如Student中的bag或set中用了inverse＝"true"时，那就代表关系是由另一关维护的（Teacher）。就是说当这插 入Student时，不会操作TeacherStudent表，即使Student已经知道了关系。只有当Teacher插入或删除时才会处发对关系表的 操作。所以，当关系的两头都用inverse="true"是不对的，就会导致任何操作都不处发对关系表的操作。当两端都是inverse= "false"或是default值是，在代码对关系显示的维护也是不对的，会导致在关系表中插入两次关系。   <br />    <br />  在一对多关系中inverse就更有意义了。在多对多中，在哪端inverse="true"效果差不多（在效率上）。但是在一对多中，如果要一方维护关 系，就会使在插入或是删除"一"方时去update"多"方的每一个与这个"一"的对象有关系的对象。而如果让"多"方面维护关系时就不会有update 操作，因为关系就是在多方的对象中的，直指插入或是删除多方对象就行了。当然这时也要遍历"多"方的每一个对象显示的操作修关系的变化体现到DB中。不管 怎样说，还是让"多"方维护关系更直观一些。<br /><br />    （1）对one-to-many而言，改变set，会让hibernate执行一系列的update语句， 不会delete/insert数据<br />    （2）对many-to-many而言，改变set,只修改关系表的数据，不会影响many-to-many的另一方。<br />    （3）虽然one-to-many和many-to-many的数据库操作不一样，但目的都是一个：维护数据的一致性。   <br />    <br />  ３、cascade和inverse有什么区别？   <br />  可以这样理解，cascade定义的是关系两端对象到对象的级联关系；而inverse定义的是关系和对象的级联关系。<br />  inverse只对set+one-to-many(或many-to-many)有效，对many-to-one, one-to-one无效。cascade对关系标记都有效。<br /><br />  inverse对集合对象整体起作用，cascade对集合对象中的一个一个元素起作用，如果集合为空，那么cascade不会引发关联操作。<br />  比如将集合对象置为null， school.setStudentSet(null)<br />   inverse导致hibernate执行:udpate STUDENT set SCHOOL_ID=null where SCHOOL_ID=?<br />   cascade则不会执行对STUDENT表的关联更新， 因为集合中没有元素。<br />  再比新增一个school, session.save(school)<br />   inverse导致hibernate执行：<br />    for( 对(school的每一个student ){<br />     udpate STUDENT set SCHOOL_ID=? where STUDENT_ID=? //将学生的school_id改为新的school的id<br />    }<br />   cascade导致hibernate执行：<br />    for( 对school的每一个student ){<br />     session.save(aStudent); //对学生执行save操作<br />    } <br />  extends:如果改变集合中的部分元素（比如新增一个元素），<br />   inverse: hibernate先判断哪些元素改变了，对改变的元素执行相应的sql<br />   cascade: 它总是对集合中的每个元素执行关联操作。<br />    （在关联操作中，hibernate会判断操作的对象是否改变）<br />  两个起作用的时机不同：<br />   cascade：在对主控方操作时，级联发生。<br />   inverse: 在flush时（commit会自动执行flush)，对session中的所有set，hibernate判断每个set是否有变化，<br />   对有变化的set执行相应的sql，执行之前，会有个判断：if( inverse == true ) return;可以看出cascade在先，inverse在后。<br />   inverse 对set + one-to-many 和 set + many-to-many 起的作用不同。hibernate生成的sql不同。<br />      对one-to-many，hibernate对many方的数据库表执行update语句。<br />      对many-to-many, hibernate对关系表执行insert/update/delte语句，注意不是对many方的数据库表而是关系表。<br />    cascase 对set都是一致的，不管one-to-many还是many-to-many。都简单地把操作传递到set中的每个元素。所以它总是更新many方的数据库表。<br /><br />  4、cascade和inverse有什么相同？<br />  这两个属性本身互不影响，但起的作用有些类似，都能引发对关系表的更新。<br /><br />  5、 建议：只对set + many-to-many设置inverse=false，其他的标记不考虑inverse属性，都设为inverse=true。对cascade，一 般对many-to-one，many-to-many，constrained=true的one-to-one 不设置级联删除。
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/193620#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 16 May 2008 13:06:59 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/193620</link>
        <guid>http://lkfnn.javaeye.com/blog/193620</guid>
      </item>
      <item>
        <title>突然想到</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/188450" style="color:red;">http://lkfnn.javaeye.com/blog/188450</a>&nbsp;
          发表时间: 2008年04月30日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          使事情陷入不可收拾的境地的，通常都是在想把事情变好的期望下产生的。<br />真的很是奇怪，可能是想把事情做到完美，但又没有完全的驾驭事情本身的能力。
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/188450#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 30 Apr 2008 15:56:51 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/188450</link>
        <guid>http://lkfnn.javaeye.com/blog/188450</guid>
      </item>
      <item>
        <title>两个公司的讽刺小品</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/143518" style="color:red;">http://lkfnn.javaeye.com/blog/143518</a>&nbsp;
          发表时间: 2007年11月26日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          两个公司的讽刺小品
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/143518#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 26 Nov 2007 10:02:43 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/143518</link>
        <guid>http://lkfnn.javaeye.com/blog/143518</guid>
      </item>
      <item>
        <title>EditPlus支持的正则表达式</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/139466" style="color:red;">http://lkfnn.javaeye.com/blog/139466</a>&nbsp;
          发表时间: 2007年11月09日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          EditPlus支持的正则表达式<br /><br />可怜的EditPlus对正则表达式的支持太有限了，竟然不支持重复频度的定义。<br />下面列出EditPlus查找或替换时支持的元字符：<br /><br /><pre name="code" class="java">表达式       说明

\t          制表符.

\n          新行.

.           匹配任意字符.

|           匹配表达式左边和右边的字符. 例如, "ab|bc" 匹配 "ab" 或者 "bc".

[]          匹配列表之中的任何单个字符. 例如, "[ab]" 匹配 "a" 或者 "b". "[0-9]" 匹配任意数字.

[^]         匹配列表之外的任何单个字符.例如, "[^ab]" 匹配 "a" 和 "b" 以外的字符. "[^0-9]" 匹配任意非数字字符.

*           其左边的字符被匹配任意次(0次，或者多次). 例如 "be*" 匹配 "b", "be" 或者 "bee".

+           其左边的字符被匹配至少一次(1次，或者多次). 例如 "be+" 匹配 "be" 或者 "bee" 但是不匹配 "b".

?           其左边的字符被匹配0次或者1次. 例如 "be?" 匹配 "b" 或者 "be" 但是不匹配 "bee".

^           其右边的表达式被匹配在一行的开始.例如 "^A" 仅仅匹配以 "A" 开头的行.

$           其左边的表达式被匹配在一行的结尾. 例如 "e$" 仅仅匹配以 "e" 结尾的行.

()          影响表达式匹配的顺序，并且用作表达式的分组标记.

\           转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\".</pre>
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/139466#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 09 Nov 2007 13:35:30 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/139466</link>
        <guid>http://lkfnn.javaeye.com/blog/139466</guid>
      </item>
      <item>
        <title>思想解放和民众的迷茫</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/137198" style="color:red;">http://lkfnn.javaeye.com/blog/137198</a>&nbsp;
          发表时间: 2007年11月01日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <em><span style="color: darkred">摘自：http://www.lupaworld.com/18335/viewspace_869.html  作者：历史与社会学系02(1)班 梁桂香</span></em><br /><br /><span style="color: red">摘 要:十五至十六世纪，是西欧由中世纪向近代转型时期，呈现在我们眼前的是一个复杂多样急剧变化的世界。在这个社会大变革大动荡时期,疯狂的物质利益追求,导致人民普遍的精神信仰危机,它猛烈冲击着西欧中世纪传统价值观念，人们感到急噪和迷茫，如何处理好转型时期人们的精神信仰与物质利益的追求，成为西欧社会转型的一个重要问题。</span><br /><br />十五至十六世纪，是西欧由中世纪向近代转型时期，呈现在我们眼前的是一个复杂多样急剧变化的世界。在这个社会大变革大动荡时期,疯狂的物质利益追求,导致人民普遍的精神信仰危机,它猛烈冲击着西欧中世纪传统价值观念，人们感到急噪和迷茫，如何处理好转型时期人们的精神信仰与物质利益的追求，成为西欧社会转型的一个重要问题。<br />一 问题提出<br />  在此期间,西欧封建主义和骑士制度逐渐衰弱,但封建等级观念却如同往常一样束缚着人们的思想、行为和生活方式，社会急需变革，西欧走入了社会转型的关键时期。14—17世纪起源于意大利、随即蔓延到几乎全欧洲的文艺复兴是思想文化领域内“人类从来没有经历过的最伟大、最进步的变革”［1］。在其推动下，由马丁·路德于德国发难的宗教改革是在宗教领域内反对以天主教为首的顽固守旧势力的运动。“文艺复兴的重大历史意义在于它促使欧洲人以神为中心过渡到以人为中心，在于人的觉醒，在于人们把重点从来世转移到现世，它唤醒了人们积极进取的精神、创造精神以及科学实验的精神，它是一次思想大解放，从根本上改变了人的价值观念，改变了人们对于生活的态度，从而在精神方面为资本主义胜利开辟了道路。”[2］而宗教改革是文艺复兴的继续，是人文主义思想在宗教领域的运用与发展，它从教会内部破除了封建的旧观念，树立了以人为主体的新思想。本文仅就从文艺复兴和宗教改革对西欧民众社会心理的影响来展开，不足之处，敬请指导。<br />二 文艺复兴运动对西欧民众社会心理的影响<br />  文艺复兴具有两层意涵,即是西欧社会由中世纪向近代转型的漫长历史过渡时期,彰显着那个时代的社会文化特征,也是孕育于中世纪的产物,标志着西方社会文化现代化历程的发展,体现西欧现代性生成的重要表征.广义的西欧包含意大利,狭义的西欧专指英、法等传统国家。而文艺复兴在十四和十五世纪从意大利开始，最早滥殇于意大利的佛罗伦萨，而此时佛罗伦萨的经济发展已为文艺复兴提供了基础，往后程度不同的扩展到欧洲其他地区。佛罗伦萨的圣玛丽亚诺维拉大教堂，但丁、乔叟、隆沙德、米尔顿等都可以证明这一切不能不对西欧民众社会心理产生深远影响。有关文艺复兴的起源，史学界经过长期研究，基本取得了一致，即“文艺复兴多重起源说”，也就是说，文艺复兴是在政治、经济和文化等多方面因素交互作用下产生的。刘建军认为文艺复兴的原因，更直接来自于文化演变上的因素，概而言之，既是由于中世纪内部因子生成的结果，同时也在于历史给予当时文化发展的四大馈赠（中世纪社会产生了现代城市、大学的出现、神学导致自然科学的发展和宗教内部发生了变革要求）和三大机遇（黑死病导致对人生问题的深入思考、古代文化典籍的重新发现在此时人们面前展示出了一个新世界、地理大发现拓展了人的视野）。[3]<br />  历史不会象戏剧中的情节那样突变,一种新的事物总要经历孕育、萌芽、冲突斗争、发展壮大等过程。有些学者认为文艺复兴并未使欧洲的基本经济情况发生变化，在文艺复兴的几个世纪里，首先在意大利，以后在欧洲，对官僚、上层资产阶级、贵族和君主产生过明显的影响，尽管他们当时所处的社会制度和思想意识各不相同。文艺复兴所带来的变化仅仅涉及到人民的一小部分，群众是置身于此种变化之外的，道德上的革新和文艺方面的新发展只与少数上层阶级有关，而对群众来讲是很遥远的。可是我认为这样来否定思想意识的重要性和有效性是错误的。基督教神学和它的一些道德在教会神甫们的思考出来之后，需要许多个实际才能渗透到西方公众的思想中去。文艺复兴的发展与传播亦是如此。在这个意义上讲，文化史与政治史相同，与经济史和社会史大部分相同——仅仅涉及到一个小圈子里的人。一个文化是一定社会的政治和经济在观念形态上的反映，当一种完全不同于原有的传统的经济模式建立起来后，它必然要求建立一种与之相适应的文化。而被排除在文化之外的人。肯定有自己的思想史，就是那些市民和农民的观点和想法。文艺复兴时期西欧民众的生活风格与其前后时期相比，差别仍是存在的。““生活风格”这一词包括或者试图包括生活的全部外在特征。从某种意义上讲，生活风格就是指历史上发生变化的事物，历史就是变化的事物的历史。生活风格不仅仅涉及到衣着、住房、装饰，它还包括我们对自己生活的看法，责任同兴趣的协调，我们学习的方法和我们学习的内容，我们祈求的方式和我们祈求的内容、目的。”[4]在我们的社会表现及其动因的这个广阔的领域里，文艺复兴时期证明是一场巨大的革新。<br />  欧洲思想界文化界人士复兴希腊、罗马古典文化的运动。但并非对古典文化亦步亦趋的简单模仿，而在很大程度上是一种创新，它是新兴资产阶级反封建斗争在意识形态领域里的反映。为求得自身的发展，他们必须首先在思想上从中世纪的宗教神学的桎梏下释放出来，不得不借用与基督教神学对立的富有生活气息的世俗古典文化来表达自己的世界观和人生观，但又超越了古典文化，其文学艺术作品及政治哲学思想都鲜明地表达出新的时代精神：重视现世生活、重视人的价值、强调运用人的理智，概括起来就是人文主义精神。文艺复兴文化是“市民阶级发展阶段的产物，是市民文化向资产阶级文化的过渡”［5］<br />三 宗教改革对西欧民众社会心理的影响<br />  中世纪初期,西欧社会生产力发展缓慢,10至11世纪,由于农奴的长期艰辛劳动,西欧社会生产力有了较大发展,不仅农业技术有了提高,而且在各种作坊的基础上产生了许多手工业行业,手工业的发展使一般农民不能够兼营,于是便出现了专门的手工业者,这些手工业者为了躲避封建主的压迫和发展生产,便竭力逃出庄园,到便于销售的关隘、渡口、交通要道、教堂附近以及罗马旧城等地方去，这样就逐渐产生了逃亡的奴隶手工业者聚居地。他们最初用篱笆把聚居地围起来，后来又建立了石头围墙，于是形成了中世纪最初的城市，出现了市民。因此，促使西欧中世纪早期城市兴起的最重要的原因是商业的发展和远程贸易的复苏。商业的发展促使城市的规模扩大，人口增多，城市贸易的范围也就随之扩大，到了十三世纪城市兴起的基础不在是远程贸易，而是手工市场。西欧中世纪城市兴起的另一个原因是由于移民而出现的独立的“移民市镇”，并由此而发展成为商业中心。城市是商业造就出来的最初的市民也是依靠商业为生的，在一切以土地为财富的社会里市民的社会地位低下，不被社会所接受，莎士比亚的《威尼斯商人》对人民的心理有过精彩的描述。但是，商业所带来的巨大财富和无限发展潜力，使西欧人很快意识到商业所带来的无穷魅力。从此，西欧城市迅速发展，意大利的佛罗伦萨、热那亚，法国的巴黎、马塞，英国的伦敦，德国的科伦，捷克的布拉格等都是当时著名的城市。<br />  中世纪的西欧城市是在封建领主领地上产生的封建主与城市的矛盾最终导致了西欧市民爆发了反对封建主，争取自治权的斗争。斗争首先从市民争取自由和自治开始的。有的城市的市民通过赎买，而更多的市民是通过暴力来获得的。自治城市的市政机构由市民选举，城市居民不在是封建主的农民，而是自由人。甚至从农村逃出来的奴隶在城里住满一年零一天就可以摆脱农奴的身份成为自由人。于是农村的人口不断涌向城市，使市民的人口不断增长，逐渐形成一个阶层，市民的地位也逐渐发生改变，由弱小集团发展成为一个社会阶层，开始逐渐完善自己的阶级特性。虽然成为自由人，但仍受到城市和行会的严格控制，市民获得的自由仍是整体自由，个人仍受限制。<br />  随着城市的发展和行会制度的接替西欧城市进入了全新的发展阶段，与中世纪城市有着本质区别，市场经济体制在城市中发展起来，西欧城市完全被抛进市场经济中，着一切对刚从传统的中世纪中走出来的市民是一个巨大的考验，他们在精神与物质，理想与现实之间展开了激烈的斗争。逐渐成熟的市民社会形成了一种市民的价值观，这种价值观对传统的基督教的价值观进行了全面的批判，主要表现在两个方面：第一，追求个性，以自我为中心。伴随而来的就是思想解放，蔑视法律，利几主义。第二，大力发展工商业，以拜金主义为核心。“在泛宗教化的人格废墟上建立起了一种泛经济化的人格模式，”[6]与基督教的轻视商业想对立，商业使人与人之间只有永恒的利益，导致道德沦丧，。但也使社会迅速发展。但当人们的物质生活满足之后，精神上却日见迷茫和空虚，渴望人与人之间的温情，对立仍是存在的。这就是为什么市民价值观是对基督教的批判，而不是完全否定的原因了。在中世纪的基督教社会，世俗生活与神圣生活，科学理性与宗教信仰始终处于格格不入的对立之中，然后这种对立关系并非是永恒的，人作为一种社会性的动物，既具有理性的禀赋，又有情感的要求，他不仅要用理性来解决情感，现实世界中的各种问题也要依赖信仰来维系希望和寄托理想正是因为现实世界中充满了不幸，人们才会产生一种超越现实的幻想。这既是一切宗教信仰的心理根源，也是使人类不断超越环境和超越自我的巨大精神动力。 <br />四 结 语<br />  在近代欧洲，通过宗教改革，新教伦理在传统与现代之间架起了一座桥梁，既为近代资本主义提供了合法性论证，又为近代资本主义的发展提供规范和约束，从而极大地促进了近代资本主义的发展。但是现在人们的物质条件大大的改善，为什么还有信仰危机呢?这不值得我们深思吗?我们正处于开放发达的现代社会，中国要在二十一世纪真正屹立于世界强国之林，现在急切需要的不是对中西、古今进行孰优孰劣的笼统判定，而是一种在自身传统中生长而又对传统加以扬弃的“新教伦理”，需要借助它来建立一种中国的现代文明秩序，促进和规范着我国社会各个方面的现代化进程。<br /><br /><br />注释： <br />［1］恩格斯：《自然辩证法导言》，《马恩选集》第三卷，第445页。<br />［2］尹虹：《略论欧洲文艺复兴的历史作用》，《广西社会科学》1999年第2期。<br /> [3］解光云：《试析意大利最早发生文艺复兴的原因》，《史学月刊》1998年第2期。<br /> [4]丹尼斯哈伊著，李玉成译：《意大利文艺复兴的历史背景》，三联出版社。1988年版22页。<br /> [5］袁庆和：《关于马丁·路德历史成就的评价问题》，《史学 月刊》1982年第2期。   <br /> [6]贾薇：《转型时期西欧市民与新教，《青海民族学院学报》，2003年第2期。
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/137198#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 01 Nov 2007 10:32:39 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/137198</link>
        <guid>http://lkfnn.javaeye.com/blog/137198</guid>
      </item>
      <item>
        <title>闲言碎语</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/135096" style="color:red;">http://lkfnn.javaeye.com/blog/135096</a>&nbsp;
          发表时间: 2007年10月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <span style="color: red">迷茫</span><br />          迷茫就是事情发生之前你不知道事情会发生，事情发生后你不知道该做什么。这个解释比新华字典要好一些吧，字典不能告诉你这个词的正真意思，他只会告诉你那是个什么字，更像是一部机器。<br />    <br />     <span style="color: red">不知道你问呀</span>     <br />          最近一直很奇怪一个事情，你不明白一个事情的时候，别人会说你不知道你问呀，一顿唠叨，很没有意义的话。一个不知道是怎么一回事的人，难到他会知道该问什么；如果你知道该问什么那就根本不是不知道，只是没有经历过而已，这是两种不同的概念。老大骂小弟的时候就常用这一句话，小弟就一脸迷茫的看着他，然后是小弟变成老大，继续去骂那些新的小弟，浮躁的教育产生浮躁的人，真是贻害万年。<br /><br />      <span style="color: red">粗心</span>     <br />          粗心就是你做了一百遍的事情突然做错了，这叫粗心。而另一种情况是：第一遍你做对了，第二遍你做对了，第三遍你做错了，这不叫粗心，只是你不知道要产生这样的结果需要那些充要条件，只是不熟悉罢了。出门我从来不会忘了带钥匙，这证明我不是一个粗心的人，可怜的背了二十多年的黑锅，现在还要背。太多的人对这个词产生错误的理解，所以拿出来说一说。<br /><br />      <span style="color: red">官僚</span><br />         一直对官僚这个词不是很理解，个人认为推卸责任是最官僚的一种表现形式。大家都拿着放大镜去看别人，却从来不照镜子。稍微高尚一点的也是脸上有疤的从来不照脸，身材不健全的人只照脸，一片皆大欢喜。
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/135096#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 24 Oct 2007 13:43:50 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/135096</link>
        <guid>http://lkfnn.javaeye.com/blog/135096</guid>
      </item>
      <item>
        <title>活着，还是死去</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/126092" style="color:red;">http://lkfnn.javaeye.com/blog/126092</a>&nbsp;
          发表时间: 2007年09月22日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <span style="color: red">活着，还是死去，这是个问题。是默然忍受命运暴虐的毒箭？还是挺身反抗人世无涯的苦难 斗争、将它们扫个干净？这二者 哪一种更高贵……</span><br /><br /><br /><u><em>to be or not to be 之所以经典并不是简简单单反映了哈姆莱德的心理问题 而是反映了莎士比亚的第三个阶段对社会问题转变和文艺复兴的迷茫性。</em></u><br /><br /><br />《哈姆雷特》是莎士比亚悲剧中的代表作品。在思想内容上达到了前所未有的深度和广度，深刻的揭示出17世纪初英国社会转型时期民众的迷茫。<br /><br />就人物性格的内在表现来看，《哈姆雷特》是最令人觉得扑朔迷离的，或者说是最富于哲学意味的。其中如父王为恶叔所弑，王位被篡，母后与凶手乱伦而婚，王储试图复仇而装疯卖傻等情节，均可见于古老的北欧传说，特别是丹麦历史学家所著的《丹麦史》中。这些尘封已久的原始资料，本来只记载着一些粗略的情节和苍白的姓名，毫无性格于动作可言，但是在莎士比亚的笔下，读者却发现自己生活在一群鲜活的人群中间，几乎和他们休憩相关，祸福与共。特别不可思议的是，其中出现了一个几百年来令世人叹为观止而有莫测高深的光辉典型。围绕这个主人公，可以提出很多问题。例如哈姆雷特是真疯还是假疯？这个性格的典型意义在哪里？这些问题都不是单凭剧情就可以解决的。<br /><br />要充分认识和正确评析本剧的中心人物，必须全面照顾他的性格和环境相矛盾的复杂性，认识他从“时代脱臼了，真糟糕，天生我要把它板正过来”这句豪言壮语，到“生存还是毁灭，这是一个值得考虑的问题”这句绝望的叹息的全部心里背景。实际上，哈姆雷特的尴尬在于以一个纤弱而又明达的心灵肩负着与其行为能力不相称的重任。用歌德的说法：“这是一株橡树给我栽在一个只应开放娇嫩的花朵的花瓶里。”哈姆雷特，一个纯洁，高尚，有道德，有知识，有决心，只能以思想代替行为，不可能成为英雄的人，就是那个“花瓶”；那项他承担不起，几乎连渺茫的希望都没有，但又决不可推卸的复仇重任，就是那株“橡树”。<br /><br />一旦“橡树”的根须膨胀开来，“花瓶”就非给挤破不可，这就是悲剧。在哈姆雷特身上，人的脆弱性和环境的残暴性是如此的相反而又相成，以至这个独特的性格在内涵方面显得致密而厚重，在外延方面也显得博大而深广。正是这样，有的专家便声称，哈姆雷特并不是一个客观的过时的角色，而是我们每个人自己。<br /><br />“生存还是毁灭，这是一个值得考虑的问题”他提出这个问题正是哲学的基本命题。因为刚刚发生在他身上的这些事引发了他对人生哲理的思考，在他的人生中诸事顺逆的时候，他是不会考虑到这个问题的，那时他看到的只是人生的光亮面，那时的生活无疑是美好的，而现在，突如其来的这场悲剧迫使他正视生活阴暗的一面和人性丑陋的一面。可以说，哈姆雷特对人生中阴暗的那一面还是有比较深刻的了解的。过去他对这一切只是视而不见而已。如今残酷的现实迫使他面对这一切。他预感到，自己已经被不可避免的拖入到一个悲剧的命运中。如果他父亲真是被害死的，那么为父报仇就成了他一生中不可推卸的使命。而他的敌人又是当今的国王，要想杀死他，肯定不是一件容易的事，但无论多么困难，杀父之仇是不能不报的，而他当前的任务是要想出一个巧妙的办法来核实他的叔父是否杀害了他的父亲。阴谋，暗算与残杀，这些是违背哈姆雷特善良纯真的本性的，但又是他复仇的使命所必须的。处在人生中花样年华的哈姆雷特背上了沉重的复仇使命，心中整日充满仇恨，使他内心阴暗而沉重，他陷入了无法自拔的痛苦的深渊。<br /><br />  对于死亡的“重重的顾虑使我们全变成了懦夫，决心的赤热的光彩，被审慎的思维盖上了一层灰色，伟大的事业在这一种考虑之下，也会逆流而退的。失去了行动的意义”。哈姆雷特在生死问题上的疑惑也预示着他在未来复仇行动上的犹豫不决，放过了一个轻而易举的复仇机会。那仅仅因为在复仇祈祷的时候，杀死他有可能使他进入天堂，那就太便宜了他。这一段心理活动的描写表现了他对人生无法解答的根本问题哲理的思考，这个问题可以说对整个人类，都具有普遍的意义。
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/126092#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 22 Sep 2007 08:30:54 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/126092</link>
        <guid>http://lkfnn.javaeye.com/blog/126092</guid>
      </item>
      <item>
        <title>什么是“成功项目”：谈谈软件的价值</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/123676" style="color:red;">http://lkfnn.javaeye.com/blog/123676</a>&nbsp;
          发表时间: 2007年09月14日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <em> 转自InfoQ 作者 熊节 发布于 2007年3月26日 上午8时20分 </em><br /><br /><br />在开始正文之前，我想先讲两个故事——关于软件项目的故事。<br /><br />故事一<br /><br />有两个软件项目（姑且称之为“项目 A”和“项目 B”），它们在开始时的预算都是 50 个人月，时间是 5 个月。<br /><br />项目 A 在 5 个月后完工，耗费成本 50 人月 <br />项目 B 在 6 个月后完工，耗费成本 70 人月 <br />在软件圈子里摸爬滚打多年的读者们对这个故事一定有自己的判断——而且我可以大致猜出是什么样的判断。不过先别着急，我们还有另一个故事。<br /><br />故事二<br /><br />有两个软件项目（仍然姑且称之为“项目 A”和“项目 B”），它们在开始时的计划都是交付 200 项功能。<br /><br />项目 A 在项目结束时一次性交付了最初计划的 200 项功能，但客户发现其中大约 30 项功能没有太大用处，而另外 30 项有用的功能要等到下一个项目才能实现。 <br />项目 B 在第一个月结束时交付了第一个版本，此后每两周交付一个新的版本。在项目进行的过程中，客户进行了一次业务调整，加入了 90 项新的功能，并搁置了 50 项用处不大的功能。最终该项目交付了 240 项功能。 <br />聪明的读者大概注意到了，前后两个故事讲的是同一回事，同样的两个项目。现在我的问题来了：请问哪个项目是更成功的项目？<br /><br />这个问题并不容易回答——实际上它没有标准答案。站在很多软件企业的立场上，项目 A 是一个理想的成功项目：按时间、按成本完成预先约定的任务。请注意，我用了“理想的”这个词，稍后我还会解释这个有趣的词，因为实际上的软件项目往往没有那么理想。<br /><br />而如果换一个角度，站在客户的立场上呢？也许付钱购买软件的客户会有一些不同的想法。项目 B 从开始之后一个月便交付了第一个可工作的版本，从那时起客户就开始使用这个软件的部分功能，并且不断地把自己使用的感受反馈给开发团队。在真实的业务运营过程中，客户甚至发现了一种新的盈利模式，并进行了一次大规模的业务调整，这次调整的结果也直观地体现在软件项目中。虽然项目B的整体交付速率低于项目 A，但它提供的所有功能都是客户真正需要的，它们为客户提供实实在在的价值——更不用说，客户提前好几个月就开始使用这个软件。<br /><br />实际上，这是一篇关于软件价值的文章。和“成功项目”一样，对于“软件的价值”，不同的人也会有不同的定义。不过作为付钱购买软件的客户，他对于软件价值的定义是一目了然的：他能够从使用软件中创造多少价值，软件能够为他的业务提供多少价值，这就是软件的价值。或者说得更简明一点：<br /><br />软件价值源自使用<br />这正是为什么很多客户青睐“项目 B”的原因——我并不打算声称所有客户都有同样的观点，稍后我也会举出反例，但至少支持这一观点的客户不在少数。因为他们处在一个残酷而快速变化的商业环境中：他们的供应商在变化，他们的客户在变化，他们所处的经济环境和政策环境也在变化。这一切的变化迫使他们的业务也要随之变化。更要命的是，今天这个经济全球化的时代是一个“快鱼吃慢鱼”的时代，客户迫切希望新的软件系统为他们带来竞争优势——哪怕这个软件系统尚未完成，只要能够投入使用。最后，客户对于新的软件系统究竟应该是什么样子并没有百分之百的把握，他们的想法往往要在真正使用软件之后才会浮现成型。几方面的因素加在一起，使得这些客户更愿意尽快开始使用软件、提出反馈、并不断完善软件，而不是提出一组需求、然后坐等几个月之后原封不动地拿到这些功能。<br /><br />一个真实的案例<br /><br />在 ThoughtWorks 的一个项目中，开发者们在项目开始之后一个月内就发布了第一个版本——只有一些简单的数据采集功能。在发布展示会上，发生了这样的对话……<br /><br />开发者：这是我们的第一个功能。我们从文本文件、Excel 数据表和遗留数据库采集数据，现在我们的数据库中有这些数据……（展示数据库结构） <br />客户：唔……有意思。要是你能做这样一个查询（写出查询要求），得到的结果可能会有用。 <br />开发者：可是我们的界面上没有地方做这样的查询操作。 <br />客户：啊，我不需要操作界面，只要每天深夜做一次查询，把报表发到我的信箱就可以了。 <br />开发者：这样吗……另一个问题是，这需要花我们几天时间。 <br />客户：不要紧，把别的任务往后放几天好了，我很想看到这份报表。 <br />开发者：那好吧，下周我们就会开始提供这个报表。 <br />猜猜结果怎么样？一周之后客户就开始每天接收这份报表，并根据报表内容做一些分析和决策。仅仅几个月之后，这份报表给客户带来的收益就已经超过了整个项目的投资——这时项目其他部分的开发甚至还没有完成。<br /><br />想想这个客户会怎么定义一个“成功的软件项目”？好吧，也许这个项目超过了预期的时间，也许投入了更多的人力，但这些并不意味着“项目失败”——只是付出更高的成本。关键在于，他投入的这些成本能够带来多大的收益，他的投资回报率是否划算。对于这个客户而言，如果项目能够随时给他提供可用的、能够创造最大价值的软件，能够随时让——就像故事中提到的——这种有价值的想法得以实现，这就是一个成功的项目。<br /><br />所以，亲爱的读者，请你忘记本文标题上出现的“敏捷”二字，我们在这里所说的不是别的，就是一种为客户创造最大化价值的软件开发方法。这样的方法有很多种，但它们有一个共同的特点：尽快、尽可能频繁地交付可以工作的软件，让客户尽快开始使用软件，从使用中创造价值、厘清思路、提出反馈。仍然以 ThoughtWorks 的项目为例，这些项目通常在启动开发阶段之后一个月内就会发布第一个版本，随后每一周或每两周发布一个新版本——每个版本都是一个可以工作的软件，每个版本都比前一个版本具有更丰富的功能，并且每个版本都包含客户认为迄今为止最有价值的那些功能。用软件开发的“黑话”，“开发下一个版本”的过程叫做“迭代”，这些开发方法最大的共同点就是“迭代式开发”——不是一股脑地交付全部功能，而是每次增加一点、渐进地交付最有价值的功能。<br /><br />软件开发的梦想与真实<br /><br />回到文章开始处的两个故事。我曾经说过，对于很多软件企业而言，项目 A 是一个“理想的”成功项目。那么，是什么让情况变得不那么理想？<br /><br />答案是一个所有软件开发者耳熟能详的词：需求变更。在真实的项目中，客户通常不会等到最后一天再照单全收整个项目，因为他知道自己的业务正在发生变化。这时需求变更就出现了，伴随着来回的扯皮和讨价还价。更糟的是，大量的需求变更发生在项目晚期——因为直到这时客户才真正看到、使用到这个软件，他的很多想法才真正浮现成型。随着这种“最后一分钟的需求变更”，项目超期、超出预算也就成了家常便饭。能够像项目A这样完工交付的，实在是凤毛麟角的幸运儿。<br /><br />为了对付需求变更这个噩梦，软件开发者们还发明了另一个词：变更控制。这个有趣的词暗示着：需求变更是一种“不好”的东西，是需要“控制”的东西。然而站在客户的角度上想想，他在亲身使用了软件之后提出的要求，难道不是最有价值的东西吗？把这种真正创造业务价值的要求“控制”起来，难道是合理的吗？<br /><br />在前面我也暗示过，并非所有的客户都一定青睐迭代式开发。那么，哪些软件项目不一定需要迭代式开发呢？从整篇文章的内容不难看出，如果客户的业务绝对不会变化，如果客户的需求巨细靡遗非常明确，如果客户不需要尽快开始使用软件以便收回成本，那么迭代式开发对他的帮助就会小得多。不过，如果读者认真思考的话，这样的例子也许并不多——也许比你最初认为的要少得多。一个很好的例子是“神州六号”火箭使用的计算机控制系统。还有多少这样的例子？读者不妨试着自己想想。<br /><br />如果我足够幸运的话，也许一些读者已经被这篇文章吊起了胃口：既然有这么好的软件开发方法，既然它能够为我们创造更大的价值，那还等什么呢，我们马上就动手吧。事情不会那么简单。为了让迭代式开发能够成为现实，为了确保尽快、尽可能频繁地交付，为了确保每次交付的都是最有价值的功能，我们——包括软件开发者、软件企业和客户——需要很多的改变。这里既有职责与权利的划分，也有开发过程和团队的重组，还有技术层面的实践指导。这些正是敏捷方法学所涵盖的内容。缺少了这些东西，“为客户创造最大价值”就只能成为一句空话。在后续的文章里，我们将结合 ThoughtWorks 的实践经验，逐步介绍敏捷方法的方方面面。
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/123676#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 14 Sep 2007 00:56:07 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/123676</link>
        <guid>http://lkfnn.javaeye.com/blog/123676</guid>
      </item>
      <item>
        <title>提高软件开发生产力的秘方</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/123675" style="color:red;">http://lkfnn.javaeye.com/blog/123675</a>&nbsp;
          发表时间: 2007年09月14日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <span style="color: red">其实我并不喜欢这种颇具煽动性的题目，无奈作者起的，转载不做任何修改。确实是好文章。</span><br /><br /><em> 转自InfoQ 作者 Amr Elssamadisy and Deborah Hartmann译者 郭晓刚 发布于 2007年8月30日 上午11时55分 </em><br /><br /><strong>专业训练者和导师们一再发现：许多敏捷团队囿于同一种模式的困扰——在平庸的“照本宣科（Norming）”阶段一直徘徊，团队的成长始终没法进步到令人兴奋的“大放光彩（Performing）”的阶段[1]。我们请读者来一起思考一下，在所有的软件开发项目中，是不是存在一种共通的东西，当我们能够将之发挥到最大程度时，可以令生产力暴增。实际上，我们相信大多数成功团队（无论敏捷还是传统的），都曾经利用过这种看上去简单但常常被忽略的软件开发的“秘方”：经常地花时间反省和学习。学什么？一切：成员彼此、技术、问题域、客户，等等。学得快的团队才会成为赢家。本文将详细分析这种困扰团队表现的看不见的“学习瓶颈”。 </strong><br /><br />一项假想的实验 <br />我们要做些什么才能让软件开发变得更好？什么是软件开发中通常的瓶颈所在？这里面有什么共通性吗？ <br /><br />今天的环境已经不允许我们实际去进行实验。要进行一次真正的实验你需要把同一个项目完成两次。不幸的是，这对今天的商业环境来说，成本太高以致不可能进行。因此，要想进行真正的实验来确切地找到共通的弱点是不现实的。 <br /><br />不过，我们都是实践过软件开发的人。那就让我们来展示一个假想的情形吧： <br /><br />假设我是你的客户，我想让你和你的团队给我开发一个软件系统。于是你的团队动手实现这个软件系统。一共花了一整年时间——12个月——来完成这个运作正常的、通过测试的系统。<br /><br />于是我向团队表示感谢，接受了交付的系统，然后把它丢掉。然后我让你和你的团队给我重新开发这个系统。你的人员保持不变。需求也保持不变。工具软件也保持不变。基本上什么都没变——整个环境完全一样。<br /><br />你和你的团队要花多少时间来重新完成这个系统呢？ <br />当我们提出这个假想的情形——提问的对象中很多都有着超过二十年的软件开发经验——他们的答案多数在第一次完成时间的20%到70%之间变动。也就是说，原来要花一年时间开发的系统，重新完成一次只需要2.5到8.5个月。这是一个很大的差别！很难再找到另一个因素能够对软件开发有这么大的影响了！ <br /><br />那么，问题到底在哪里？第二轮开发中到底有什么不一样了？是团队。他们一整年都粘在一个团队中，因而互相了解。他们又理解了真正的需求——不仅仅是写下来的那些需求。他们还学会使用手中的工具，理解了在整个软件开发中浮现出来的问题领域的种种特质；基本上在创造并交付一个成功的软件解决方案之前，他们已经解决了所有未知的东西。学习过程正是软件工程的瓶颈。[2] <br /><br />学习过程的存在占据了工作量的很大比例。实际上，我们估计它消耗了30%到80%的开发时间。正是出于同样的理由，敏捷实践才如此成功——它们的主题就是识别并应对变化。 <br /><br />敏捷实践，从测试驱动开发和持续集成，到迭代和回溯，共同组成了一个帮助团队学习得更快的链条。通过反复运用种种实践，敏捷团队加速了学习的过程，直面软件工程的瓶颈。我们可称之为“科学方法”、“持续改进”或者“检验一切”。 <br /><br />以“学习过程是瓶颈”的眼光<br />检验敏捷 <br />敏捷软件开发是可行的——这已经是一个确立的事实。在过去的八年中已经记录下了数以百计的成功故事。敏捷开发中特别有趣的一点是，大多数实践都不是什么新东西——实际上它们都颇有些年纪了。敏捷仅仅是把许多最成功的软件开发实践提炼出来，并把它们放到一起。实际上，敏捷宣言并没有在创造新事物，而是在自90年代以来已经获得成功的许多轻量级方法当中寻找共通点（Jim Highsmith和Uncle Bob [4]分别对这些方法作了总结）。对大量被认为最有效的实践的反思揭示了一些非常有意思的共通点…… <br /><br />“识别并应对变化”的循环 <br />那么我们如何学习呢？我们从自身的错误中学习（只要我们留心，许多人都做不到这点）。我们怎样学得更快？更快地犯（小）错——“Fail Fast”。马上运用我们学到的东西，是巩固学习过程使之牢固的关键。通过在下一个循环中立即运用我们学到的东西，我们重复利用了学习的效果（就像复合利息一样！）。 <br /><br />我们一再遇到的一个非常典型的循环过程如下： <br /><br /> <br /><br />这个简单的循环正是敏捷开发中许多实践的基本： <br /><br />测试驱动开发（TDD）：<br />1）编写一个会失败的测试，2）编写代码以让测试通过，3）运行测试——它通过了吗？4）如果测试仍然失败则学习其原因所在然后转到2）。<br /><br />每日循环：<br />1）全体一起设立本日要完成的任务，在每日的站立会议上作报告，2）开始工作，3）第二天回来报告完成的进度和遇到的阻碍，4）在第二天的计划中应用学到的东西。<br /><br />测试驱动的需求（Test Driven Requirements，TDR）：<br />1）把需求定义成测试，2）开发软件，3）运行测试；如果通过了就算完成，如果没有就进行4）找出原因并回到2）。<br /><br />迭代：<br />1）定义一个“完成状态”，2）确定一组需求作为本次迭代的要交付的工作（Backlog），3）进行迭代完成要交付的工作，4）测试每个项目是否达到完成状态，将之标志为完成，或者将之放回未交付工作待后考虑，并据此结束迭代。<br /><br />产品演示（Demo）：<br />通常在迭代结束的时候进行，它给了客户一个机会去检验预想的需求在实现出来之后是否真的能够解决手头的问题。1）（隐含地）确定需求所针对的业务需求/价值，2）定义需求，3）在迭代中满足需求，4）按照业务上的需要来评估需求的实现。<br /><br />回顾（Retrospective）：<br />1）决定一种团队工作的方式，2）在一次迭代中运用这种方式，3）反省该实践的效果，4）采取行动，规定负责人和期限，以图创造一种更有效的过程，并实现它们。<br /><br />发布：<br />1）为本次发布设立愿景和要达成的商业目标。2）设立本次发布要交付的工作（Backlog），3）为本次发布进行若干迭代，4）部署给客户，并为下一次发布收集反馈。<br /><br />协作小组的协作小组（Scrum of Scrums）：<br />在同时应付多个项目的情况下，每个项目都有自己独立运作的循环，同时又通过定期的会议保持同步——即协作小组的协作小组（Scrum of Scrums）——在会议上每个项目的代表向其他人报告，以便从企业的层次上识别和应对变化。<br /><br />管理测试：<br />1）与产品所有者或者业务的利益相关方一起，从较高的层次上定义他们如何测量一个项目或者发布是否成功，2）保持这些“测试”对开发团队清晰可见，以便他们的交付能达到这些期望，3）把管理测试的评估过程整合进回顾（Retrospective）过程，4）团队与产品所有者一起将这些测试分配到每次迭代。 <br />这些循环中许多都可以彼此契合。测试驱动需求包含若干测试驱动开发循环。一次迭代包含若干测试驱动需求的循环。一次发布又包含若干次迭代，诸如此类…… <br /><br />更进一步，这些循环是彼此牵动的——实际上每当发生学习过程而否定了更高一级的循环，即是这些循环在持续不断地牵动彼此。测试驱动开发的循环嵌套在测试驱动需求当中`——即是说对于每项需求，要有若干测试驱动开发的循环来满足它。因此，举例来说，一个失败的测试可能意味着编码的错误，也可能意味着正在实现的需求本身并不正确，或者之前的另一项需求应该被否定。因此在测试驱动开发循环中的学习过程引发了对需求的学习过程。类似地，迭代循环嵌套在发布循环中。因此，由于无法预见的技术障碍而无法达成目标的一次迭代 ，会导致系统的一个重要部分所需的工作量大大上升。于是团队会变更部署，重新考虑这次发布要交付的工作，重新调整优先次序和目标范围来迎合新的信息。 <br /><br />循环：必要但不充分 <br />循环是“识别并应对变化”的两项基本要求之一——它提供了机会。第二项要求是沟通，通过涵盖所有的人来放大学习的效果，也就是敏捷方法中强调的“信息辐射”。 <br /><br />信息需要被传播给团队中的其他人，甚至更远。没有沟通，问题可能无法被识别出来。没有沟通，发现问题但找不到解决方案的人，就没有机会从其他不同技术背景的人那里得到解决的方案。在团队的环境中，对一个特定的问题，并不总是能够确定解决问题的最佳人选——但把问题告诉每个人等于在邀请所有感兴趣的人都提出他们的建议，而不局限于团队中的“专家”——有时候新手和外行反而能够“跳出框框”而令我们有意外之喜！ <br /><br />沟通能加速并强化学习过程，暂停工作来一次正式的反省可以确保沟通的声音不会被耳边“快点，快点！”的催促声所掩盖。暂停可以像“回顾（Retrospective）”过程那么正式，也可以像迭代结束后的聚餐那么不正式——只要学习与提高被列入议程。虽然沟通按计划应该发生在每次循环的开头（对目标的沟通）和结尾（对结果的检验），但在循环过程中的非正式、“渗透性的”[6]沟通也会有戏剧性的效果。 <br /><br />相当数量的敏捷实践直接针对沟通问题——包括正式的和“渗透性的”沟通： <br /><br />自组织的团队（Self-organizing team）： 团队成员一起工作，一起应对发生的变化。他们作为一个集体去进行构建软件所需的工作。<br /><br />同处一室的团队（Co-located team）：团队成员坐在一起，并经常性地举行小组会谈，有意无意地听到成员间的谈话，每个人都由这种信息渗透而知晓正在发生的事情。<br /><br />功能交错的团队（Cross-functional team）：受过不同训练的团队成员聚在一起工作，从头到尾地解决一个问题。通过在一起工作，他们分享彼此的专门知识。<br /><br />结对编程：两个人一起完成一项任务，是一种分享经验与专门知识的非常深入的形式。<br /><br />信息辐射体（Information radiators）：是一个“人人可见的大图表”，它存在的唯一目的就是把一些重要的数据传播给任何路过的人。<br /><br />唤起回忆的文档（Evocative documents）：敏捷团队建立文档的时候聚在一起以便能够互相交谈。以后阅读文档的时候就会唤起对整个讨论和语境的回忆。这比起传统的文档要有价值得多。传统的文档完成之后就被丢到隔壁从此事不关己，它们本质上就是用来装点门面的——也就是说，因为已经在这些文档身上投入了那么多，我们可以（虚假地）相信这些正式文档实际上就是它们所描述的对象。<br /><br />站立会议（Stand up meeting）：敏捷团队每天就刚刚完成的任务、遇到的障碍、以及第二天的任务计划进行沟通以达到同步。 <br />沟通，当它被加入循环，会使得整个团队学习得更快，并且更加成为一个整体。毕竟，软件开发是一种集体工作。如果学习过程对团队来说是一个瓶颈，那么整个团队都需要尽可能更多更快地学习。 <br /><br />如果能妥善运用这些实践，敏捷团队无论学习什么都很快——不仅是技术：设计（TDD、结对编程、功能交错的团队），需求（演示产品和TDR），产品（迭代、发布），以及人（回顾、站立会议）。 <br /><br />为什么这么重要？从理论到实践 <br />好吧，学习过程看起来是软件开发当中一个非常重要的环节。它可能比我们认为的更加重要——那又怎么样？关注学习过程云云又如何能帮助你我的团队生产出更好的软件？ <br /><br />虽然前面列举了众多“学习”方面的实践的例子，敏捷团队并不是对学习瓶颈免疫的。有时候在紧迫的任务的压力下，我们会为了短期的利益而跳过学习的步骤（“哦，我们以后会补上”）。在学习上失败的敏捷团队会表现出以下部分或全部症状： <br /><br />疲劳（没有依照可持续的节奏来工作）导致士气问题， <br />再三地无力在一次迭代中“搞定”， <br />持续地让承诺落空（甚至有些承诺的特性和用户故事根本还没动手）， <br />部署的软件持续地出现缺陷，缺陷之多使得开发计划完全脱离轨道， <br />否定回顾的价值，放弃回顾（“因为我们从来都没办法解决发现的问题”）。<br /><br /><br />让你的眼睛盯住瓶颈 <br />请看下图：在每个步骤的名称下面的括号里是一个表示速度的数值。 两个步骤速度不一致的时候就会产生积压（Inventory）——由于步骤A比步骤B快，A的过剩产品要等待B的处理。整个系统的产出总是为瓶颈所限[5]。也就是说，如果你希望提高整个系统的总体产出，你几乎只需要提高瓶颈部分的表现。在其他地方花功夫纯属浪费，甚至可能起到反作用。这意味着对于下图中的简单顺序过程（SSP），我们唯一的改进办法是调整步骤D。处理其他地方并不能改进系统产出。 <br /><br /> <br /><br />如果学习过程确实是软件开发的瓶颈，那么它应该排在我们优先队列的第一位。也就是说我们花功夫在其他地方只会得到非常有限的生产力提高。倒过来说，这意味着任何提高我们的生产力的事物都在某种程度上提高了我们的学习效率。 <br /><br />现在，当我们反思我们的过程的时候，留心观察能传递更多价值的途径，我们要记住通常我们向业务方传递以下几种价值：当然首先是正常运作的软件，另外还有可维护的、可改变的软件，以及一个快速反应的团队来继续这些工作。前两项的优先次序看业务的需要，而第三项纯粹属于职业素养上的期望，通常也掌握在开发团队的手中。最后一项价值——团队敏捷性——实际上是组织的一项资产，它会比一个个项目存续得更久，它会创造出谋取更大利益的机会，并且加快后续项目的速度。 <br /><br />敏捷团队应该保持组织的相对稳定，应当存续六个月或者更长时间，以便培育出有效的合作。建立这样的团队需要有战略眼光，而且还要一并考虑每次迭代中必须立即满足的业务需求。如果不能维持这种平衡，由于速度和质量都无法预测，再加上过程中无法排除的陷阱，团队很快就会在业务上失败。 <br /><br />为了避免产生出脆弱而不协调的团队，当我们对过程进行反思的时候，应该问自己下面两个问题： <br /><br />“这会对我们下次迭代的速度有什么影响？”<br /><br />“这会对我们的学习过程有什么影响？”（学习又会进一步影响我们在迭代和项目中的速度和响应能力。） <br />因此，与其问说“结对编程会不会降低我们的速度？”，我们应该问，“结对编程会不会降低我们的学习效率？还是它会提高学习效率？“与其问“我们真的需要每两个星期做一次产品演示吗？虽然产品所有者一个月才能来一次？”，我们应该问，“把产品演示的频率降低到一个月一次会对我们的学习有什么影响？”与其问“为了支持敏捷，我应该安装哪个工具？”，我们应该问“，工具ABC促进了我们的学习吗？还是它让我们交流得更少，因而阻碍了我们的学习？”，或者采取更好的做法，一直等到真的需要跟踪某些信息（以便从中学习）的时候，才选择一件对你来说最省事的工具[7]。 <br /><br />当我们了解到学习是会增强“团队”这项公司资产的时候，现在我们可以有理有据地向利益相关方解释我们采取学习实践的益处了。“是的，结对编程乍看上去很奢侈——让我来解释一下它是怎样抵消风险的，而且长远来说这样做是物有所值……”别忘了——相对敏捷的短周期来说，“长远”可能说的只是3到6个星期。 <br /><br />下一步何去何从 <br />我们写这篇文章只有一个目的。我们不是要分析学习的理论和机制，这个题目需要长篇大论才能解决，而且已经有了许多长篇大论。我们也不是要分类和验证各种敏捷实践——这里提到的实践只是作为简单的举例，提醒我们只要想学习，已经存在许多学习的方法。 <br /><br />我们的目标是把学习过程放在我们思考和行动的显著地位，因为我们相信它是敏捷实践成功的关键。不要仅仅潜移默化地学习，要把学习放到注意力的中心。本文的作用是提醒诸位，敏捷方法已经提供了许多学习方面的实践和机制——你是否已经在团队以及业务中有效地利用了它们？ <br /><br />用“学习过程是瓶颈”的眼光来看世界。这样可以大大减少出现“货机崇拜”[8]式的敏捷实践的机会，并让你的努力集中在如何令敏捷实践的效果最大化上面。 <br /><br /><strong>注释 </strong><br /><u>初起炉灶—暴风骤雨—照本宣科—大放光彩（Forming–Storming–Norming–Performing）的团队发展模型由Bruce Tuckman最先提出。http://en.wikipedia.org/wiki/Forming-storming-norming-performing <br />这个假想实验和“学习过程是瓶颈”的说法来自Ashley Johnson，他是Valtech Skill Development公司负责商业计划及策略的副总裁。 <br />关于早期轻量级方法的共通性的历史，请查阅Jim Highsmith 的作品 http://www.agilemanifesto.org/history.html<br /><br />另一份早期轻量级方法的共通性的历史，请查阅Uncle Bob Martin的作品 http://blog.objectmentor.com/articles/2007/07/10/the-founding-of-the-agile-alliance <br />敏捷团队中的“渗透性沟通”： http://www.agilemodeling.com/essays/communication.htm <br />熟悉约束理论（Theory of Constraints）的读者可能会觉得这个论点很熟悉，如果考虑到非线性过程，这个论点在很大程度上是过于简化了。 <br />参见 《Appropriate Agile Metrics: Using Metrics and Diagnostics to Deliver Business Value》，Deborah Hartmann and Robin Dymond，2006 http://www.berteigconsulting.com/archives/2005/01/agile_work_reso.php <br />货机崇拜：在第二次世界大战期间，许多空军基地建造在偏僻的热带岛屿上，岛上的土著还未进入工业时代。在战争期间军人在岛上建造机场和控制塔，从事各种活动，不时有大型货机降落及卸货。当地土著也会分得一些货物。战后军人撤离，土著也就不再有货物可得。于是他们按照自己的一知半解，模仿出机场跑道、控制塔等等形式，并举行仪式希望能召唤装满货物的飞机归来。于是“货机崇拜”被用来指一群人仅仅采纳了事物表面的形式而非实质，并相信这样做会带来他们期望的结果。 <br />关于作者 <br />Amr Elssamadisy是软件业里的实践者。构建软件是难的——大写的难——Amr的使命是找出开发软件的更好方法，并与社区分享他的发现。今天他在帮助他的客户成功采用敏捷实践构建出更好的软件，只要我们继续学习构建软件的更好方式，明天肯定会不一样。Amr还是《Patterns of Agile Practice Adoption: The Technical Cluster》这本书的作者，同时也是InfoQ的敏捷社区的编辑。 <br /><br />Deborah Hartmann是说两种语言的敏捷实践者、训练者和导师，她以多伦多为大本营从事国际性的工作。Deborah的使命是让工作既对业务有价值又能给团队带来乐趣。她指导过大企业和小公司转向敏捷的过程，从2006年4月起担任InfoQ的敏捷社区的主编，她还推动了XP社区的OpenSpace会议，并帮助加拿大及美国的BarCamp社区发展。 </u><br />查看英文原文：The Secret Sauce of Highly Productive Software Development
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/123675#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 14 Sep 2007 00:44:54 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/123675</link>
        <guid>http://lkfnn.javaeye.com/blog/123675</guid>
      </item>
      <item>
        <title>坚持你的梦想</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/109854" style="color:red;">http://lkfnn.javaeye.com/blog/109854</a>&nbsp;
          发表时间: 2007年08月07日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          故事的主人公叫Paul，他只是一个普通的手机业务员，他抱着自己的执着和梦想走上了选秀的舞台。评委不屑的问这个相貌在普通不过的中年人要表演什么，他的回答是“歌剧”。接下来，他的演唱让现场所有人自发的站起来暴发出雷鸣般的欢呼声和掌声。 <br />    <br />    在电脑面前的我居然也有了想哭的感觉，哭是因为他用了36年来实现自己的梦想，世上能有几个人能用30多年来坚持自己的一个梦想呢！<br />    <br />    那个评委说：“我们看到了从原石诞生璞玉的过程”。<br /><br />    视频地址：<a href="http://tv.mofile.com/RDK57GG6/" target="_blank">http://tv.mofile.com/RDK57GG6/</a>
          <br/>
          <span style="color:red;">
            <a href="http://lkfnn.javaeye.com/blog/109854#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 07 Aug 2007 21:22:39 +0800</pubDate>
        <link>http://lkfnn.javaeye.com/blog/109854</link>
        <guid>http://lkfnn.javaeye.com/blog/109854</guid>
      </item>
      <item>
        <title>软件项目的核心风险</title>
        <author>lkfnn</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://lkfnn.javaeye.com">lkfnn</a>&nbsp;
          链接：<a href="http://lkfnn.javaeye.com/blog/109078" style="color:red;">http://lkfnn.javaeye.com/blog/109078</a>&nbsp;
          发表时间: 2007年08月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作