posts - 9,  comments - 1,  trackbacks - 0
  2006年9月7日
<SCRIPT language=javascript1.2>
function showsubmenu(sid)
{
whichEl = eval("submenu" + sid);
if (whichEl.style.display == "none")
{
eval("submenu" + sid + ".style.display=\"\";");
}
else
{
eval("submenu" + sid + ".style.display=\"none\";");
}
}
</SCRIPT>


用法:

<tr>
            <td height="26" background="../image/menu.gif" id="menuTitle1" onClick="showsubmenu(1)" style="cursor:hand;">&nbsp;&nbsp;&nbsp;
            <font color="#FF9900">■&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#FFFFFF"><strong>知识库分类</strong></font>
            </td>
          </tr>
          <tr>
<td style="display:none" id='submenu1'>
     <table cellspacing="0" align="center" width="100%">
                <tr>
                  <td class="nav">· <a href="<%=request.getContextPath()%>/knowledge/knowledgeCategoryList.htm?order=list" target="mainFrame">编辑知识库分类</a></td>
                </tr>
                <tr>
                  <td class="nav">· <a href="<%=request.getContextPath()%>/knowledge/knowledgeCategoryList.htm?order=edit" target="mainFrame">新增知识库分类</a></td>
                </tr>
              </table>
posted @ 2006-10-25 17:37 lovetq 阅读(462) | 评论 (0)编辑 收藏
在很多的列表中,后面的操作一栏会有删除链接,并且会让你确认。此处的代码: 
<a href='javascript:deleteCategory("<%=article.getId() %>")'>删除</a>
     

javascript函数
<script>
 function deleteCategory(articleId)
 {
  if(confirm('是否真的要删除该记录'))
   window.location='<%=request.getContextPath()%>/info/infoArticleEdit.htm?order=delete&articleId='+articleId;
 }
</script>

一些链接:
<input class="button3" type=button value="返回" onclick="javascript:window.location='<%=request.getContextPath() %>/info/infoArticleList.htm'">

<td>文件名:<a href='<%=request.getContextPath()%>/info/infoArticleEdit.htm?order=download&fileId=<%=accessory.getId() %>'><%=accessory.getFileExt() %></a>
     
<c:url value="........" />
  
一些传值
<input type=hidden name=order value=save>
<input type=hidden name=id value="<c:out value="${infoArticle.id}" />">
<input type=hidden name=navId value="<%=request.getParameter("navId") %>">
<input type=hidden name=fileCount value=1>
posted @ 2006-10-25 16:32 lovetq 阅读(174) | 评论 (0)编辑 收藏

页面代码:
<!--  这个是显示的多选框页面 已有的角色要打勾,可以重新选择角色-->
<form action="<c:url value="/sys/userRolesEdit.htm"/>" method="post" >

      <table class="grid" width="100%" cellspacing="1">
     
    <tr>
       <td colspan="6" class="title">
          角色列表
       </td>
    </tr>
    <tr>
      <td class="column"></td>
       <td class="column">角色ID</td>
       <td class="column">角色名</td>
       <td class="column">角色代码</td>
       <td class="column">角色描述</td>      

    </tr>

    <!-- 循环显示列表userRoles的每一项,并判断如果数据库中已有的打勾<c:if test标签正确才执行中间的代码-->
    <c:forEach items="${roles}" var="item">    
   <tr>      
     <td>
     <input type="checkbox" name="rolesId" <c:forEach items="${userRoles}" var = "selectedRoles">
     <c:if test="${item.srolId == selectedRoles.srolId}">checked="true"</c:if></c:forEach>
      value="<c:out value="${item.srolId}"/>" />
     </td>
   
     <td><c:out value="${item.srolId}"/></td>
        <td><c:out value="${item.srolName}"/> </td>
                 <td><c:out value="${item.srolCode}"/></td>
                 <td><c:out value="${item.srolDesc}"/></td>                
     <tr>       
    </c:forEach>   
   
    <tr>

       <td colspan="6" class="nav">&nbsp;
           <input type="hidden" name="userID"
   value=<c:out value="${userInfo.suinId}"/>>
      <input type="submit" class="button3" value="提交"/>
      <INPUT type="button" class="button2" value="返回" onclick="history.back()" />
     
       </td>
    </tr>
   </table> 
   </form>

控制器代码:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
  // TODO Auto-generated method stub
  
  Map result = new HashMap();
  
  SysUserinfo userInfo = null;
        String id = request.getParameter("userID");

        System.out.println(id);
        if (id != null && !id.equals(""))
        {
            userInfo = (SysUserinfo) sysService.get(SysUserinfo.class,
                    new Long(id));
           
        }
        else
        {
           
        }
       
        result.put("userInfo", userInfo);
        result.put("roles", sysService.getSupportData("SysRole", null));
        if (userInfo != null)
        {
         result.put("userRoles", sysService.getUserRoles(userInfo));
        }
       
 
       
        SysUserRole sysUserRole = null;
        SysRole sysRole = null;
        String roleId = null;
       
        // 记录父列表ID
        result.put("parentlistID", request.getParameter("parentlistID"));
        result.put("userInfo", userInfo);
        
         //遍历所有map里的key
        Iterator it = result.keySet().iterator();
        while (it.hasNext())
        {
            String key = (String) it.next();
            request.setAttribute(key, result.get(key));
        }
        
         //这里就是从列表多选框传过来的数值。根据name属性获得所有的value
        String[] rolesId = null;
        rolesId = request.getParameterValues("rolesId");
        System.out.println("------------------------" + rolesId + "-------------------");
       
        if (rolesId == null || rolesId.equals(null))
        {
         return new ModelAndView(formView);
        }
       
        else
        {     
         List userRoles = (List)sysService
          .getUsersUserRoles(userInfo.getSuinId());


      //先删除数据库中所有的,再插入所选的值
         for (int k = 0; k < userRoles.size(); k++)
      {
       SysUserRole userRole = (SysUserRole)userRoles.get(k);      
       sysService.delete(userRole);
       
      }
         
         for (int i = 0; i < rolesId.length; i++)
         {
          roleId = rolesId[i];
          
          sysRole = (SysRole)sysService.get(SysRole.class, new Long(roleId));
          sysUserRole = new SysUserRole();
          sysUserRole.setUser(userInfo);
          sysUserRole.setRole(sysRole);
          sysService.save(sysUserRole);        
         }

         //如果在###-servlet.xml配置文件中,successView不能传递参数,可在这里直接有response导向别的页面
         response.sendRedirect(request.getContextPath()
           + "/sys/userRolesList.htm?userID=" + userInfo.getSuinId().toString());
         return null;
        }

 }


posted @ 2006-10-25 09:27 lovetq 阅读(1777) | 评论 (0)编辑 收藏
StringTokenizer:字符串分隔解析类型
属于:java.util包。

1、构造函数。

    1. StringTokenizer(String str) :构造一个用来解析str的StringTokenizer对象。java默认的分隔符是“空格”、“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)”。
    2. StringTokenizer(String str, String delim) :构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符。
    3. StringTokenizer(String str, String delim, boolean returnDelims) :构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符,同时,指定是否返回分隔符。


2、方法。
说明:
1. 所有方法均为public;
2. 书写格式:[修饰符] <返回类型> <方法名([参数列表])>

如:
static int parseInt(String s) 表示:此方法(parseInt)为类方法(static),返回类型为(int),方法所需参数为String类型。


    1. int countTokens() :返回nextToken方法被调用的次数。如果采用构造函数1和2,返回的就是分隔符数量(例2)。
    2. boolean hasMoreTokens() :返回是否还有分隔符。
    3. boolean hasMoreElements() :结果同2。
    4. String nextToken() :返回从当前位置到下一个分隔符的字符串。
    5. Object nextElement() :结果同4。
    6. String nextToken(String delim) :与4类似,以指定的分隔符返回结果。


例子:
		String s = new String("The Java platform is the ideal platform for network computing");
StringTokenizer st = new StringTokenizer(s);
System.out.println( "Token Total: " + st.countTokens() );
while( st.hasMoreElements() ){
System.out.println( st.nextToken() );
           }
结果为:
Token Total: 10
The
Java
platform
is
the
ideal
platform
for
network
computing

例2:
		String s = new String("The=Java=platform=is=the=ideal=platform=for=network=computing");
StringTokenizer st = new StringTokenizer(s,"=",true);
System.out.println( "Token Total: " + st.countTokens() );
while( st.hasMoreElements() ){
System.out.println( st.nextToken() );
}
结果为:
Token Total: 19
The
=
Java
=
platform
=
is
=
the
=
ideal
=
platform
=
for
=
network
=
computing
posted @ 2006-10-08 17:33 lovetq 阅读(260) | 评论 (0)编辑 收藏
jsp
JSP语法(9)——jsp:getProperty

<jsp:getProperty>

获取Bean的属性值,用于显示在页面中

JSP 语法

<jsp:getProperty name="beanInstanceName" property="propertyName" />

例子

<jsp:useBean id="calendar" scope="page" class="employee.Calendar" />

<h2>

Calendar of <jsp:getProperty name="calendar" property="username" />

</h2>

描述

这个<jsp:getProperty>元素将获得Bean的属性值,并可以将其使用或显示在JSP页面中.在你使用<jsp:getProperty>之前,你必须用<jsp:useBean>创建它.

<jsp:getProperty>元素有一些限制:

你不能使用<jsp:getProperty>来检索一个已经被索引了的属性

你能够和JavaBeans组件一起使用<jsp:getProperty>,但是不能与Enterprise

Bean一起使用。

属性

name="beanInstanceName"
bean的名字,由<jsp:useBean>指定

property="propertyName"
所指定的Bean的属性名。

技巧:

在sun的JSP参考中提到,如果你使用<jsp:getProperty>来检索的值是空值,那么NullPointerException将会出现,同时如果使用程序段或表达式来检索其值,那么在浏览器上出现的是null(空).



jsp:getProperty Action

语法:

  <jsp:getProperty name="beanInstanceName" property="propertyName" />

  这个属性检索出bean的属性的值并将之转化为一个字符串,然后将之插入到输出。它有两个必选属性:name,在之前用jsp:useBean引入的名称,property,必须被插入值的属性。

posted @ 2006-09-21 13:46 lovetq 阅读(151) | 评论 (0)编辑 收藏

java.util.*;

public class ShowDate {

public static void main(String[] args) {
Calendar calendar = new GregorianCalendar();
Date trialTime = new Date();
calendar.setTime(trialTime);

// print out a bunch of interesting things
System.out.println("ERA: " + calendar.get(Calendar.ERA));
System.out.println("YEAR: " + calendar.get(Calendar.YEAR));
System.out.println("MONTH: " + calendar.get(Calendar.MONTH));
System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR));
System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH));
System.out.println("DATE: " + calendar.get(Calendar.DATE));
System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR));
System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK));
System.out.println("DAY_OF_WEEK_IN_MONTH: " + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH));
System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));
System.out.println("HOUR: " + calendar.get(Calendar.HOUR));
System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY));
System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE));
System.out.println("SECOND: " + calendar.get(Calendar.SECOND));
System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND));
System.out.println("ZONE_OFFSET: " + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000)));
System.out.println("DST_OFFSET: " + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000)));

System.out.println("Current Time, with hour reset to 3");
calendar.clear(Calendar.HOUR_OF_DAY); // so doesn't override
calendar.set(Calendar.HOUR, 3);
System.out.println("ERA: " + calendar.get(Calendar.ERA));
System.out.println("YEAR: " + calendar.get(Calendar.YEAR));
System.out.println("MONTH: " + calendar.get(Calendar.MONTH));
System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR));
System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH));
System.out.println("DATE: " + calendar.get(Calendar.DATE));
System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR));
System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK));
System.out.println("DAY_OF_WEEK_IN_MONTH: " + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH));
System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));
System.out.println("HOUR: " + calendar.get(Calendar.HOUR));
System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY));
System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE));
System.out.println("SECOND: " + calendar.get(Calendar.SECOND));
System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND));
System.out.println("ZONE_OFFSET: " + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000))); // in hours
System.out.println("DST_OFFSET: " + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000))); // in hours
}

}


import java.text.DateFormat;
import java.util.*;

public class JspCalendar {
    Calendar  calendar = null;

    public JspCalendar() {
 calendar = Calendar.getInstance();
 Date trialTime = new Date();
 calendar.setTime(trialTime);
    }

    public int getYear() {
 return calendar.get(Calendar.YEAR);
    }
   
    public String getMonth() {
 int m = getMonthInt();
 String[] months = new String [] { "January", "February", "March",
     "April", "May", "June",
     "July", "August", "September",
     "October", "November", "December" };
 if (m > 12)
     return "Unknown to Man";
 
 return months[m - 1];

    }

    public String getDay() {
 int x = getDayOfWeek();
 String[] days = new String[] {"Sunday", "Monday", "Tuesday", "Wednesday",
          "Thursday", "Friday", "Saturday"};

 if (x > 7)
     return "Unknown to Man";

 return days[x - 1];

    }
   
    public int getMonthInt() {
 return 1 + calendar.get(Calendar.MONTH);
    }

    public String getDate() {
 return getMonthInt() + "/" + getDayOfMonth() + "/" +  getYear();

    }

    public String getTime() {
 return getHour() + ":" + getMinute() + ":" + getSecond();
    }

    public int getDayOfMonth() {
 return calendar.get(Calendar.DAY_OF_MONTH);
    }

    public int getDayOfYear() {
 return calendar.get(Calendar.DAY_OF_YEAR);
    }

    public int getWeekOfYear() {
 return calendar.get(Calendar.WEEK_OF_YEAR);
    }

    public int getWeekOfMonth() {
 return calendar.get(Calendar.WEEK_OF_MONTH);
    }

    public int getDayOfWeek() {
 return calendar.get(Calendar.DAY_OF_WEEK);
    }
    
    public int getHour() {
 return calendar.get(Calendar.HOUR_OF_DAY);
    }
   
    public int getMinute() {
 return calendar.get(Calendar.MINUTE);
    }


    public int getSecond() {
 return calendar.get(Calendar.SECOND);
    }

    public static void main(String args[]) {
 JspCalendar db = new JspCalendar();
 p("date: " + db.getDayOfMonth());
 p("year: " + db.getYear());
 p("month: " + db.getMonth());
 p("time: " + db.getTime());
 p("date: " + db.getDate());
 p("Day: " + db.getDay());
 p("DayOfYear: " + db.getDayOfYear());
 p("WeekOfYear: " + db.getWeekOfYear());
 p("era: " + db.getEra());
 p("ampm: " + db.getAMPM());
 p("DST: " + db.getDSTOffset());
 p("ZONE Offset: " + db.getZoneOffset());
 p("TIMEZONE: " + db.getUSTimeZone());
    }

    private static void p(String x) {
 System.out.println(x);
    }


    public int getEra() {
 return calendar.get(Calendar.ERA);
    }

    public String getUSTimeZone() {
 String[] zones = new String[] {"Hawaii", "Alaskan", "Pacific",
           "Mountain", "Central", "Eastern"};
 
 return zones[10 + getZoneOffset()];
    }

    public int getZoneOffset() {
 return calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000);
    }


    public int getDSTOffset() {
 return calendar.get(Calendar.DST_OFFSET)/(60*60*1000);
    }

   
    public int getAMPM() {
 return calendar.get(Calendar.AM_PM);
    }
}

posted @ 2006-09-21 11:45 lovetq 阅读(636) | 评论 (0)编辑 收藏
<jsp:useBean id=“calculator“ scope=“request“ class=“com.jspdev.SimpleCalculator“>
<jsp:setProperty name=“calculator“ property=“*“/>
</jsp:useBean>

(1)property=”*”
设置Bean 属性的快捷方式,在Bean 中属性的名字,类型必须和request对象中的参数名称相匹配。由于表单中传过来的数据类型都是String 类型的,Jsp内在机制会把这些参数转化成Bean属性对应的类型。
(2)property=“propertyName“
使用request对象中的一个参数值来指定Bean中的一个属性值。在这个语法中,property指定Bean 的属性名,而且Bean 属性和request参数的名字应相同。也就是说,如果在Bean 中有setUserName(String userName)方法,那么,propertyName的值就是“userName“

<jsp:setProperty name="Name" property="*" />
根据已提交表单中的数据,设置这个javaBean中相应的属性值。
*:JavaBean属性的名称和表单对象的名称相同时才能够赋值
<jsp:setProperty name="Name" property="propertyName" value="PropertyValue" | param="ParameterName" />
把JavaBean指定的属性设为指定的值

jsp:setProperty用来设置Bean的属性值。
有两种方法使用
1。在jsp:useBean之后2。在jsp:useBean实体这中间有区别的第一种情况jsp:setProperty
不管是新生成的实例还是原来的对象,都会进行
2。只有在新生成的情况下才可以执行
<jsp:setProperty>中的name 应该和jsp:useBean中的id一样的
property="*" 表示用用户输入的所有值来匹配Bean中的属性,当然你也可以指定的

posted @ 2006-09-21 11:34 lovetq 阅读(564) | 评论 (0)编辑 收藏
 
 理解spring(转的别人的)


最近研究Spring,她包含的编程思想让我耳目一新。所以写下这篇入门级文章供新手参考。我不是什么Spring的资深研究人员,我只是现学现卖。所以文章也只能是肤浅单薄,错误难免,还请见谅。
一、    Spring诞生
Spring是一个开源框架,目前在开源社区的人气很旺,被认为是最有前途的开源框架之一。她是由Rod Johnson创建的,她的诞生是为了简化企业级系统的开发。说道Spring就不得不说EJB,因为Spring在某种意义上是EJB的替代品,她是一种轻量级的容器。用过EJB的人都知道EJB很复杂,为了一个简单的功能你不得不编写多个Java文件和部署文件,他是一种重量级的容器。也许你不了解EJB,你可能对“轻(重)量级”和“容器”比较陌生,那么这里我简单介绍一下。
1、什么是容器
“容器”,这个概念困扰我好久。从学习Tomcat开始就一直对此感到困惑。感性的来讲,容器就是可以用来装东西的物品。那么在编程领域就是指用来装对象(OO的思想,如果你连OO都不了解,建议你去学习OO先)的对象。然而这个对象比较特别,它不仅要容纳其他对象,还要维护各个对象之间的关系。这么讲可能还是太抽象,来看一个简单的例子:
代码片断1:
  1. public class Container 
  2. {
  3.     public void init()
  4.     {
  5.     Speaker s = new Speaker();
  6.     Greeting g = new Greeting(s);
  7.     }
  8. }

可以看到这里的Container类(容器)在初始化的时候会生成一个Speaker对象和一个Greeting对象,并且维持了它们的关系,当系统要用这些对象的时候,直接问容器要就可以了。这就是容器最基本的功能,维护系统中的实例(对象)。如果到这里你还是感到模糊的话,别担心,我后面还会有相关的解释。

2、轻量级与重量级
所谓“重量级”是相对于“轻量级”来讲的,也可以说“轻量级”是相对于重量级来讲的。在Spring出现之前,企业级开发一般都采用EJB,因为它提供的事务管理,声明式事务支持,持久化,分布计算等等都“简化”了企业级应用的开发。我这里的“简化”打了双引号,因为这是相对的。重量级容器是一种入侵式的,也就是说你要用EJB提供的功能就必须在你的代码中体现出来你使用的是EJB,比如继承一个接口,声明一个成员变量。这样就把你的代码绑定在EJB技术上了,而且EJB需要JBOSS这样的容器支持,所以称之为“重量级”。
相对而言“轻量级”就是非入侵式的,用Spring开发的系统中的类不需要依赖Spring中的类,不需要容器支持(当然Spring本身是一个容器),而且Spring的大小和运行开支都很微量。一般来说,如果系统不需要分布计算或者声明式事务支持那么Spring是一个更好的选择。

二、    几个核心概念
在我看来Spring的核心就是两个概念,反向控制(IoC),面向切面编程(AOP)。还有一个相关的概念是POJO,我也会略带介绍。
1、POJO
我所看到过的POJO全称有两个,Plain Ordinary Java Object,Plain Old Java Object,两个差不多,意思都是普通的Java类,所以也不用去管谁对谁错。POJO可以看做是简单的JavaBean(具有一系列Getter,Setter方法的类)。严格区分这里面的概念没有太大意义,了解一下就行。
2、    IoC
IoC的全称是Inversion of Control,中文翻译反向控制或者逆向控制。这里的反向是相对EJB来讲的。EJB使用JNDI来查找需要的对象,是主动的,而Spring是把依赖的对象注入给相应的类(这里涉及到另外一个概念“依赖注入”,稍后解释),是被动的,所以称之为“反向”。先看一段代码,这里的区别就很容易理解了。
代码片段2:
  1. public void greet()
  2. {
  3. Speaker s = new Speaker();
  4. s.sayHello();
  5. }

代码片段3:
  1. public void greet()
  2. {
  3. Speaker s = (Speaker)context.lookup("ejb/Speaker");
  4. s.sayHello();
  5. }

代码片段4:
  1. public class Greeting 
  2. {
  3.     public Speaker s;
  4.     public Greeting(Speaker s)
  5.     {
  6.         this.s = s;
  7.     }
  8.     public void greet()
  9.     {
  10.         s.sayHello();
  11.     }
  12. }

我们可以对比一下这三段代码。其中片段2是不用容器的编码,片段3是EJB编码,片段4是Spring编码。结合代码片段1,你能看出来Spring编码的优越之处吗?也许你会觉得Spring的编码是最复杂的。不过没关系,我在后面会解释Spring编码的好处。
这里我想先解释一下“依赖注入”。根据我给的例子可以看出,Greeting类依赖Speaker类。片段2和片段3都是主动的去获取Speaker,虽然获取的方式不同。但是片段4并没有去获取或者实例化Speaker类,而是在greeting函数中直接使用了s。你也许很容易就发现了,在构造函数中有一个s被注入(可能你平时用的是,传入)。在哪里注入的呢?请回头看一下代码片段1,这就是使用容器的好处,由容器来维护各个类之间的依赖关系(一般通过Setter来注入依赖,而不是构造函数,我这里是为了简化示例代码)。Greeting并不需要关心Speaker是哪里来的或是从哪里获得Speaker,只需要关注自己分内的事情,也就是让Speaker说一句问候的话。
3、    AOP
AOP全称是Aspect-Oriented Programming,中文翻译是面向方面的编程或者面向切面的编程。你应该熟悉面向过程的编程,面向对象的编程,但是面向切面的编程你也许是第一次听说。其实这些概念听起来很玄,说到底也就是一句话的事情。
现在的系统往往强调减小模块之间的耦合度,AOP技术就是用来帮助实现这一目标的。举例来说,假如上文的Greeting系统含有日志模块,安全模块,事务管理模块,那么每一次greet的时候,都会有这三个模块参与,以日志模块为例,每次greet之后,都要记录下greet的内容。而对于Speaker或者Greeting对象来说,它们并不知道自己的行为被记录下来了,它们还是像以前一样的工作,并没有任何区别。只是容器控制了日志行为。如果这里你有点糊涂,没关系,等讲到具体Spring配置和实现的时候你就明白了。
假如我们现在为Greeting系统加入一个Valediction功能,那么AOP模式的系统结构如下:
G|RET|TIN|G
V|ALE|DIT|ION
|   |   |
日志 安全 事务

这些模块是贯穿在整个系统中的,为系统的不同的功能提供服务,可以称每个模块是一个“切面”。其实“切面”是一种抽象,把系统不同部分的公共行为抽取出来形成一个独立的模块,并且在适当的地方(也就是切入点,后文会解释)把这些被抽取出来的功能再插入系统的不同部分。
从某种角度上来讲“切面”是一个非常形象的描述,它好像在系统的功能之上横切一刀,要想让系统的功能继续,就必须先过了这个切面。这些切面监视并拦截系统的行为,在某些(被指定的)行为执行之前或之后执行一些附加的任务(比如记录日志)。而系统的功能流程(比如Greeting)并不知道这些切面的存在,更不依赖于这些切面,这样就降低了系统模块之间的耦合度。

三、    Spring初体验
这一节我用一个具体的例子Greeting,来说明使用Spring开发的一般流程和方法,以及Spring配置文件的写法。
首先创建一个Speaker类,你可以把这个类看做是POJO。
代码片段5:
  1. public class Speaker 
  2. {
  3.     public void sayHello()
  4.     {
  5.         System.out.println("Hello!");
  6.     }
  7. }
再创建一个Greeting类。
代码片段6:
  1. public class Greeting 
  2. {
  3.     private Speaker speaker;
  4.     public void setSpeaker(Speaker speaker)
  5.     {
  6.         this.speaker = speaker;
  7.     }
  8.     public void greet()
  9.     {
  10.         speaker.sayHello();
  11.     }
  12. }

然后要创建一个Spring的配置文件把这两个类关联起来。
代码片段7(applicationContext.xml):
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
  3.     "http://www.springframework.org/dtd/spring-beans.dtd">
  4. <beans>
  5.     <bean id="Speaker" class="Speaker"></bean>
  6.     <bean id="Greeting" class="Greeting">
  7.         <property name="speaker">
  8.             <ref bean="Speaker"/>
  9.         </property>
  10.     </bean>
  11. </beans>

要用Spring Framework必须把Spring的包加入到Classpath中,我用的是Eclipse+MyEclipse,这些工作是自动完成的。推荐用Spring的配置文件编辑器来编辑,纯手工编写很容易出错。我先分析一下这个xml文件的结构,然后再做测试。从<beans>节点开始,先声明了两个<bean>,第二个bean有一个speaker属性(property)要求被注入,注入的内容是另外一个bean Speaker。这里的命名是符合JavaBean规范的,也就是说如果是speaker属性,那么Spring容器就会调用setSpeaker()来注入这个属性。<ref>是reference的意思,表示引用另外一个bean。
下面看一段简单的测试代码:
代码片段8:
  1. public static void main(String[] args) 
  2. {
  3.         ApplicationContext context = 
  4.             New ClassPathXmlApplicationContext("applicationContext.xml");
  5.         Greeting greeting = (Greeting)context.getBean("Greeting");
  6.         greeting.greet();
  7. }

这段代码很简单,如果你上文都看懂了,那么这里应该没有问题。值得注意的是Spring有两种方式来创建容器(我们不再用上文我们自己编写的Container),一种是ApplicationContext,另外一种是BeanFactory。ApplicationContext更强大一些,而且使用上两者没有太大区别,所以一般说来都用ApplicationContext。Spring容器帮助我们维护我们在配置文件中声明的Bean以及它们之间的依赖关系,我们的Bean只需要关注自己的核心业务。

四、    面向接口的编程
看了这么多,也许你并没有觉得Spring给开发带来了很多便利。那是因为我举的例子还不能突出Spring的优越之处,接下来我将通过接口编程来体现Spring的强大。
假如现在要求扩展Greeting的功能,要让Speaker用不同的语言来问候,也就是说有不同的Speaker,比如ChineseSpeaker, EnglishSpeaker。那么对上文提到的三种编码方式(代码片段2、3、4)分别加以修改,你会发现很麻烦。假如下次又要加入一个西班牙语,又得重复劳动。很自然的会考虑到使用一个ISpeaker接口来简化工作,,更改后的代码如下(这里没有列出接口的相关代码,我想你应该明白怎么写):         

代码片段9:
  1. public void greet()
  2. {
  3. ISpeaker s = new ChineseSpeaker();
  4. s.sayHello();
  5. }

代码片段10:
  1. public void greet()
  2. {
  3. ISpeaker s = (ISpeaker)context.lookup("ejb/ChineseSpeaker");
  4. s.sayHello();
  5. }

代码片段11:
  1. public class Greeting 
  2. {
  3.     public ISpeaker s;
  4.     public Greet(ISpeaker s)
  5.     {
  6.         this.s = s;
  7.     }
  8.     public void greet()
  9.     {
  10.         s.sayHello();
  11.     }
  12. }
对比三段代码,你会发现,第一种方法还是把具体的Speaker硬编码到代码中了,第二中方法稍微好一点,但是没有本质改变,而第三种方法就不一样了,代码中并没有关于具体Speaker的信息。也就是说,如果下次还有什么改动的话,第三种方法的Greeting类是不需要修改,编译的。根据上文Spring的使用介绍,只需要改动xml文件就能给Greeting注入不同的Speaker了,这样代码的扩展性是不是提高了很多?
关于Spring的接口编程还有很多东西可以去挖掘,后文还会提到有关Spring Proxy的接口编程,我这里先介绍这么多,有兴趣话可以去google更多的资料。

五、    应用Spring中的切面
Spring生来支持AOP,首先来看几个概念:
1、    切面(Aspect):切面是系统中抽象出来的的某一个功能模块,上文已经有过介绍,这里不再多说。
2、    通知(Advice):通知是切面的具体实现。也就是说你的切面要完成什么功能,具体怎么做就是在通知里面完成的。这个名称似乎有点让人费解,等后面看了代码就明白了。
3、    切入点(Pointcut):切入点定义了通知应该应用到系统的哪些地方。Spring只能控制到方法(有的AOP框架可以控制到属性),也就是说你能在方法调用之前或者之后选择切入,执行额外的操作。
4、    目标对象(Target):目标对象是被通知的对象。它可以是任何类,包括你自己编写的或者第三方类。有了AOP以后,目标对象就只需要关注自己的核心业务,其他的功能,比如日志,就由AOP框架支持完成。
5、    代理(Proxy):简单的讲,代理就是将通知应用到目标对象后产生的对象。Spring在运行时会给每个目标对象生成一个代理对象,以后所有对目标对象的操作都会通过代理对象来完成。只有这样通知才可能切入目标对象。对系统的其他部分来说,这个过程是透明的,也就是看起来跟没用代理一样。
我为了简化,只介绍这5个概念。通过这几个概念应该能够理解Spring的切面编程了。如果需要深入了解Spring AOP的话再去学习其他概念也很快的。
下面通过一个实际的例子来说明Spring的切面编程。继续上文Greeting的例子,我们想在Speaker每次说话之前记录Speaker被调用了。
首先创建一个LogAdvice类:
代码片段12:
  1. public class LogAdvice implements MethodBeforeAdvice 
  2. {
  3.     public void before(Method arg0, Object[] arg1, Object arg2)throws Throwable 
  4.     {
  5.         System.out.println("Speaker called!");
  6.     }
  7. }
这里涉及到一个类,MethodBeforeAdvice,这个类是Spring类库提供的,类似的还有AfterReturningAdvice等等,从字面就能理解它们的含义。先不急着理解这个类,我稍后解释。我们继续看如何把这个类应用到我们的系统中去。
代码片段13:
  1. <beans>
  2.     <bean id="Speaker" class="Speaker"/>
  3.     <bean id="Greeting" class="Greeting">
  4.         <property name="speaker">
  5.             <ref bean="SpeakerProxy"/>
  6.         </property>
  7.     </bean>
  8.     <bean id="LogAdvice" class="LogAdvice"/>
  9.     <bean id="SpeakerProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  10.         <property name="proxyInterfaces">
  11.             <value>ISpeaker</value>
  12.         </property>
  13.         <property name="interceptorNames">
  14.             <list>
  15.                 <value>LogAdvice</value>
  16.             </list>
  17.         </property>
  18.         <property name="target">
  19.             <ref local="Speaker"/>
  20.         </property>
  21.     </bean>
  22. </beans>

可以看到我们的配置文件中多了两个bean,一个LogAdvice,另外一个SpeakerProxy。LogAdvice很简单。我着重分析一下SpeakerProxy。这个Bean实际上是由Spring提供的ProxyFactoryBean实现。下面定义了三个依赖注入的属性。
1、    proxyInterfactes:这个属性定义了这个Proxy要实现哪些接口,可以是一个,也可以是多个(多个的话,要用list标签)。我前面讲过Proxy是在运行是动态创建的,那么这个属性就告诉Spring创建这个Proxy的时候实现哪些接口。
2、    interceptorNames:这个属性定义了Proxy被切入了哪些通知,这里只有一个LogAdvice。
3、    target:这个属性定义了被代理的对象。在这个例子中target是Speaker。
这样的定义实际上约束了被代理的对象必须实现一个接口,这与上文讲的面向接口的编程有点类似。其实可以这样理解,接口的定义可以让系统的其他部分不受影响,以前用ISpeaker接口来调用,现在加入了Proxy还是一样的。但实际上内容已经不一样了,以前是Speaker,现在是一个Proxy。而target属性让proxy知道具体的方法实现在哪里。Proxy可以看作是target的一个包装。当然Spring并没有强制要求用接口,通过CGLIB(一个高效的代码生成开源类库)也可以直接根据目标对象生成子类,但这种方式并不推荐。
我们还像以前一样的测试我们的Greeting系统,测试代码和代码片段8是一样的。运行结果如下:
Speaker called!
Hello!

看到效果了吧!而且你可以发现,我们加入Log功能并没有改变以前的代码,甚至测试代码都没有改变,这就是AOP的魅力所在!我们更改的只是配置文件。
下面解释一下刚才落下的MethodBeforeAdvice。关于这个类我并不详细介绍,因为这涉及到Spring中的另外一个概念“连接点(Jointpoint)”,我详细介绍一个before这个方法。这个方法有三个参数arg0表示目标对象在哪个点被切入了,既然是MethodBeforeAdvice,那当然是在Method之前被切入了。那么arg0就是表示的那个Method。第二个参数arg1是Method的参数,所以类型是Object[]。第三个参数就是目标对象了,在Greeting例子中arg2的类型实际上是Speaker。
在Greeting例子中,我们并没有指定目标对象的哪些方法要被切入,而是默认切入所有方法调用(虽然Speaker只有一个方法)。通过自定义Pointcut,可以控制切入点,我这里不再介绍了,因为这并不影响理解Spring AOP,有兴趣的话去google一下就知道了。

六、实战Spring
虽然这部分取名为“实战Spring”,但实际上我并不打算在这里介绍实际开发Spring的内容,因为我写这篇文章的目的是介绍Spring的概念和用Spring开发的思路,而不是有关Spring的实践和详细介绍。文中介绍的内容和用Spring做实际开发还相去甚远。之所以取名“实战Spring”是我觉得理解了上文讲的内容以后,可以开始为深入学习Spring和学习如何在项目中应用Spring了。
要系统的学习Spring还是需要阅读一本详细介绍Spring的书,我推荐Spring in Action,因为我看的就是这本书。希望这篇文章能为你系统的学习Spring扫除一些障碍。


posted @ 2006-09-07 14:22 lovetq 阅读(177) | 评论 (0)编辑 收藏