求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
 
Java网络编程精解之基于MVC和RMI的分布式应用一

2010-07-20 作者:孙卫琴 来源:51cto.com

 

MVC是Model-View-Controller的简称,即模型-视图-控制器。MVC是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已被广泛使用。

本章首先介绍MVC设计模式的概念,然后创建一个基于MVC的Java应用,并且在这个Java应用中引入RMI框架,把模型作为远程对象分布到服务器端,把视图和控制器分布到客户端,从而创建分布式的Java应用。

13.1  MVC设计模式简介

MVC把应用程序分成3个核心模块:模型(Model)、视图(View)和控制器(Controller),它们分别担当不同的任务。如图13-1所示显示了这几个模块各自的功能及它们的相互关系。

 

图13-1  MVC设计模式

1.视图

视图是用户看到并与之交互的界面。视图向用户展示用户感兴趣的业务数据,并能接收用户的输入数据,但是视图并不进行任何实际的业务处理。视图可以向模型查询业务数据,但不能直接改变模型中的业务数据。视图还能接收模型发出的业务数据更新事件,从而对用户界面进行同步更新。

2.模型

模型是应用程序的主体部分。模型表示业务数据和业务逻辑。一个模型能为多个视图提供业务数据。同一个模型可以被多个视图重用。

3.控制器

控制器接收用户的输入并调用模型和视图去完成用户的请求。当用户在视图上选择按钮或菜单时,控制器接收请求并调用相应的模型组件去处理请求,然后调用相应的视图来显示模型返回的数据。

如图13-2所示,MVC的3个模块也可以看做软件的3个层次,最上层为视图层,中间为控制器层,下层为模型层。总地说来,层与层之间为自上而下的依赖关系,下层组件为上层组件提供服务。视图层与控制器层依赖模型层来处理业务逻辑和提供业务数据。此外,层与层之间还存在两处自下而上的调用,一处是控制器层调用视图层来显示业务数据,另一处是模型层通知客户层同步刷新界面。为了提高每个层的独立性,应该使每个层对外公开接口,封装实现细节。

 

图13-2  MVC的3个模块也可以看做软件的3个层次

4.MVC处理过程

如图13-3所示,首先用户在视图提供的界面上发出请求,视图把请求转发给控制器,控制器调用相应的模型来处理用户请求,模型进行相应的业务逻辑处理,并返回数据。最后控制器调用相应的视图来显示模型返回的数据。

 

图13-3  MVC的处理过程

5.MVC的优点

首先,多个视图能共享一个模型。在MVC设计模式中,模型响应用户请求并返回响应数据,视图负责格式化数据并把它们呈现给用户,业务逻辑和数据表示分离,同一个模型可以被不同的视图重用,所以大大提高了模型层的程序代码的可重用性。

其次,模型是自包含的,与控制器和视图保持相对独立,因此可以方便地改变应用程序的业务数据和业务规则。如果把数据库从MySQL移植到Oracle,或者把RDBMS数据源改变成LDAP数据源,只需改变模型即可。一旦正确地实现了模型,不管业务数据来自数据库还是LDAP服务器,视图都会正确地显示它们。由于MVC的3个模块相互独立,改变其中一个不会影响其他两个,所以依据这种设计思想能构造良好的松偶合的组件。

此外,控制器提高了应用程序的灵活性和可配置性。控制器可以用来连接不同的模型和视图去完成用户的需求,控制器为构造应用程序提供了强有力的重组手段。给定一些可重用的模型和视图,控制器可以根据用户的需求选择适当的模型进行业务逻辑处理,然后选择适当的视图将处理结果显示给用户。

6.MVC的适用范围

使用MVC需要精心的设计,由于它的内部原理比较复杂,所以需要花费一些时间去理解它。将MVC运用到应用程序中,会带来额外的工作量,增加应用的复杂性,所以MVC不适合小型应用程序。

但对于开发存在大量用户界面,并且业务逻辑复杂的大型应用程序,MVC将会使软件在健壮性、代码重用和结构方面上一个新的台阶。尽管在最初构建MVC框架时会花费一定的工作量,但从长远角度看,它会大大提高后期软件开发的效率。

13.2  store应用简介

本章介绍的Java应用实现了一个商店的客户管理系统,本书把此应用简称为store应用。store应用包含以下用例(Use Case):

  • 创建新客户
  • 删除客户
  • 更新客户的信息
  • 根据客户ID查询特定客户的详细信息
  • 列出所有客户的清单

store应用使用MySQL数据库服务器,它的永久业务数据都存放在STOREDB数据库,其中CUSTOMERS表用来存放客户信息,它的定义如下:

create table CUSTOMERS (
  ID bigint not null auto_increment primary key,
  NAME varchar(16) not null,
  AGE INT,
  ADDRESS varchar(255)
);

STOREDB数据库的创建过程可参见本书第12章的12.2节(安装和配置MySQL数据库)。如图13-4所示是store应用的类框图。其中ConnectionPool接口、ConnectionPoolImpl2类、ConnectionProvider类和PropertyReader类都来自于本书第12章。ConnectionPool接口表示连接池,负责为模型提供数据库连接。StoreException类是异常类,如例程13-1所示是它的源程序:

 

图13-4  store应用的类框图

例程13-1  StoreException.java

package store;
public class StoreException extends Exception{
  public StoreException() {
    this("StoreException");
  }
  public StoreException(String msg) {
    super(msg);
  }
}

当模型层处理业务逻辑时出现错误,就会抛出StoreException,例如:

public void deleteCustomer(Customer cust) throws StoreException,RemoteException{
  try{
    if(!idExists(cust.getId())){
      throw new StoreException("Customer "+cust.getId()+" not found");
    }
    String sql="delete from CUSTOMERS where ID="+cust.getId();
    dbService.modifyTable(sql);
    fireModelChangeEvent(cust);
    }catch(Exception e){
      e.printStackTrace();
      throw new StoreException("StoreDbImpl.deleteCustomer\n"+e);
    }
}

Customer类与数据库中的CUSTOMERS表对应,它表示store应用的业务数据。模型层负责把Customer对象保存到数据库中,以及从数据库中加载特定的Customer对象。视图层则负责在图形界面上展示Customer对象的信息,以及接收用户输入的Customer对象的信息。如例程13-2所示是Customer类的源程序。

例程13-2  Customer.java

package store;
import java.io.*;
public class Customer implements Serializable {
  private long id;
  private String name="";
  private String addr="";
  private int age;
  public Customer(long id,String name,String addr,int age) {
    this.id=id;
    this.name=name;
    this.addr=addr;
    this.age=age;
  }
 
  public Customer(long id){
    this.id=id;
  }
  public Long getId(){
    return id;
  }

  public String getName(){
    return name;
  }

  public void setName(String name){
    this.name=name;
  }
  …
  public String toString(){
    return "Customer: "+id+" "+name+" "+addr+" "+age;
  }
}

在分布式运行环境中,Customer对象会从服务器端传送到客户端,也会从客户端传送到服务器端。Customer类实现了java.io.Serializable接口,从而保证Customer对象可以在网络上传输。

  • store应用包括3个核心接口。
  • StoreView接口:视图层的接口,负责生成与用户交互的图形界面。
  • StoreController接口:控制器层的接口,负责调用模型和视图。
  • StoreModel接口:模型层的接口,负责处理业务逻辑,访问数据库。

如例程13-3所示是StoreView接口的源程序。它包括以下3个方法。

  • addUserGestureListener(StoreController ctrl)方法:在视图中注册处理各种用户动作(比如用户按下【查询客户】按钮)的控制器,参数ctrl指定控制器。
  • showDisplay(Object display)方法:在图形界面上显示数据,参数display指定待显示的数据。
  • handleCustomerChange()方法:当模型层修改了数据库中某个客户的信息时,同步刷新视图的图形界面。

例程13-3  StoreView.java

package store;
import java.rmi.*;
public interface StoreView extends Remote{
  /** 注册处理用户动作的监听器,即StoreController控制器 */
  public void addUserGestureListener(StoreController ctrl)
throws StoreException,RemoteException;

  /** 在图形界面上显示数据,参数display表示待显示的数据 */
  public void showDisplay(Object display)throws StoreException,RemoteException;

  /** 当模型层修改了数据库中某个客户的信息时,同步刷新视图层的图形界面 */
  public void handleCustomerChange(Customer cust)throws StoreException,RemoteException;
}

以上StoreView接口的handleCustomerChange()方法由模型调用,在分布式运行环境中,模型位于服务器层,视图位于客户层。为了使模型能回调StoreView对象的handleCustomerChange()方法,特地把StoreView接口设计为远程接口,handleCustomer- Change()方法是远程方法,声明抛出RemoteException。本章13.7节的图13-14展示了模型对视图的回调过程。

如例程13-4所示是StoreController接口的源程序。用户在视图提供的图形界面上会执行各种操作,比如按下【查询客户】、【添加客户】、【删除客户】和【更新客户】按钮,StoreController接口中声明了一系列handleXXX()方法,它们分别响应用户在图形界面做出的某种动作。

例程13-4  StoreController.java

package store;
public interface StoreController {
  /** 处理根据ID查询客户的动作 */
  public void handleGetCustomerGesture(long id);
  /** 处理添加客户的动作 */
  public void handleAddCustomerGesture(Customer c);
  /** 处理删除客户的动作 */
  public void handleDeleteCustomerGesture(Customer c);
  /** 处理更新客户的动作 */
  public void handleUpdateCustomerGesture(Customer c);
  /** 处理列出所有客户清单的动作 */
  public void handleGetAllCustomersGesture();
}

如例程13-5所示是StoreModel接口的源程序。StoreModel接口中声明了操纵数据库的一系列方法,这些方法用于添加、更新、删除和查询数据库中的客户信息。此外,StoreModel接口的addChangeListener(StoreView sv)方法用于在模型中注册视图,当模型修改了数据库中的客户信息时,就可以回调所有注册过的视图的handleCustomer- Change(Customer cust)方法,以便同步刷新所有的视图。

例程13-5  StoreModel.java

package store;
import java.rmi.*;
import java.util.*;
public interface StoreModel extends Remote {
  /** 注册视图,以便当模型修改了数据库中的客户信息时,可以回调视图的刷新界面的方法 */
  public void addChangeListener(StoreView sv) throws StoreException,RemoteException;
  /** 向数据库中添加一个新的客户 */
  public void addCustomer(Customer cust) throws StoreException,RemoteException;
  /** 从数据库中删除一个客户 */
  public void deleteCustomer(Customer cust) throws StoreException,RemoteException;
  /** 更新数据库中的客户 */
  public void updateCustomer(Customer cust) throws StoreException,RemoteException;
  /** 根据参数id检索客户 */
  public Customer getCustomer(long id) throws StoreException,RemoteException;
  /** 返回数据库中所有的客户清单 */
  public Set getAllCustomers() throws StoreException,RemoteException;
}

如图13-5所示显示了store应用根据用户指定的ID查询客户详细信息的时序图。

 

图13-5  根据用户指定的ID查询客户详细信息的时序图

用户在视图的图形界面上输入ID,然后按下【查询客户】按钮,StoreView调用StoreController的handleGetCustomerGesture(id)方法处理用户的请求,StoreController调用StoreModel的getCustomer(id)方法从数据库中获得相应的客户信息。StoreController接着调用StoreView的showDisplay(customer)方法在图形界面上显示客户信息。

13.3  创建视图

视图包括StoreView接口、StoreViewImpl类和StoreGui类。StoreGui类利用Swing组件生成图形用户界面。StoreViewImpl类实现了StoreView接口,StoreViewImpl类依赖StoreGui类生成图形界面,并且委托StoreController来处理StoreGui界面上产生的事件。

如图13-6和图13-7所示是store应用的图形用户界面,图13-6显示单个客户的详细信息,图13-7显示所有客户的清单。

图13-6  显示单个客户详细信息的图形界面

 

图13-7  显示所有客户清单的图形界面

store应用的图形界面主要包括以下面板。

  • 选择面板selPan:位于界面的最顶端,包括两个按钮,【客户详细信息】按钮和【所有客户清单】按钮。【客户详细信息】按钮使界面的中央区域显示custPan面板,【所有客户清单】按钮使界面的中央区域显示allCustPan面板。
  • 单个客户面板custPan:输出或者输入单个客户的详细信息,并且包括4个按钮,【查询客户】、【更新客户】、【添加客户】和【删除客户】。
  • 所有客户面板allCustPan:用javax.swing.JTable组件来显示所有客户的清单。
  • 日志面板logPan:显示操作失败时的错误信息。

StoreGui类负责生成如图13-6和图13-7所示的图形界面。如例程13-6所示是StoreGui类的源程序。

例程13-6  StoreGui.java

package store;
//此处省略import语句

public class StoreGui {

  //界面的主要窗体组件
  protected JFrame frame;
  protected Container contentPane;
  protected CardLayout card=new CardLayout();
  protected JPanel cardPan=new JPanel();

  //包含各种按钮的选择面板上的组件
  protected JPanel selPan=new JPanel();
  protected JButton custBt=new JButton("客户详细信息");
  protected JButton allCustBt=new JButton("所有客户清单");

  //显示单个客户的面板上的组件
  protected JPanel custPan=new JPanel();
  protected JLabel nameLb=new JLabel("客户姓名");
  protected JLabel idLb=new JLabel("ID");
  protected JLabel addrLb=new JLabel("地址");
  protected JLabel ageLb=new JLabel("年龄");

  protected JTextField nameTf=new JTextField(25);
  protected JTextField idTf=new JTextField(25);
  protected JTextField addrTf=new JTextField(25);
  protected JTextField ageTf=new JTextField(25);
  protected JButton getBt=new JButton("查询客户");
  protected JButton updBt=new JButton("更新客户");
  protected JButton addBt=new JButton("添加客户");
  protected JButton delBt=new JButton("删除客户");

  //列举所有客户的面板上的组件
  protected JPanel allCustPan=new JPanel();
  protected JLabel allCustLb=new JLabel("所有客户清单",SwingConstants.CENTER);
  protected JTextArea allCustTa=new JTextArea();
  protected JScrollPane allCustSp=new JScrollPane(allCustTa);

  String[] tableHeaders={"ID","姓名","地址","年龄"};
  JTable table;
  JScrollPane tablePane;
  DefaultTableModel tableModel;

  //日志面板上的组件
  protected JPanel logPan=new JPanel();
  protected JLabel logLb=new JLabel("操作日志",SwingConstants.CENTER);

  protected JTextArea logTa=new JTextArea(9,50);
  protected JScrollPane logSp=new JScrollPane(logTa);
 
  /** 显示单个客户面板 custPan */
  public void refreshCustPane(Customer cust){
    showCard("customer");

    if(cust==null || cust.getId()==-1){
      idTf.setText(null);
      nameTf.setText(null);
      addrTf.setText(null);
      ageTf.setText(null);
      return;
    }
    idTf.setText(new Long(cust.getId()).toString());
    nameTf.setText(cust.getName().trim());
    addrTf.setText(cust.getAddr().trim());
    ageTf.setText(new Integer(cust.getAge()).toString());
  }
 
  /** 显示所有客户面板 allCustPan */
  public void refreshAllCustPan(Set custs){
    showCard("allcustomers");
    String newData[][];
    newData=new String[custs.size()][4];
    Iterator it=custs.iterator();
    int i=0;
    while(it.hasNext()){
      Customer cust=it.next();
      newData[i][0]=new Long(cust.getId()).toString();
      newData[i][1]=cust.getName();
      newData[i][2]=cust.getAddr();
      newData[i][3]=new Integer(cust.getAge()).toString();
      i++;
    }
    tableModel.setDataVector(newData,tableHeaders);
  }
 
  /** 在日志面板logPan中添加日志信息 */
  public void updateLog(String msg){
    logTa.append(msg+"\n");
  }

  /** 获得客户面板custPan上用户输入的ID */
  public long getCustIdOnCustPan(){
     try{
       return Long.parseLong(idTf.getText().trim());
     }catch(Exception e){
       updateLog(e.getMessage());
       return -1;
     }
  }
 
  /** 获得单个客户面板custPan上用户输入的客户信息 */
  public Customer getCustomerOnCustPan(){
    try{
      return new Customer(Long.parseLong(idTf.getText().trim()),
        nameTf.getText().trim(),addrTf.getText().trim(),
        Integer.parseInt(ageTf.getText().trim()));
    }catch(Exception e){
      updateLog(e.getMessage());
      return null;
    }
  }
 
  /** 显示单个客户面板custPan或者所有客户面板allCustPan */
  private void showCard(String cardStr){
    card.show(cardPan,cardStr);
  }
 
  /** 构造方法 */
  public StoreGui(){
    buildDisplay();
  }
 
  /** 创建图形界面 */
  private void buildDisplay(){
   frame=new JFrame("商店的客户管理系统");
   buildSelectionPanel();
   buildCustPanel();
   buildAllCustPanel();
   buildLogPanel();
  
   /** carPan采用CardLayout布局管理器,包括custPan和allCustPan两张卡片 */
   cardPan.setLayout(card);
   cardPan.add(custPan,"customer");
   cardPan.add(allCustPan,"allcustomers");

   //向主窗体中加入各种面板
   contentPane=frame.getContentPane();
   contentPane.setLayout(new BorderLayout());
   contentPane.add(cardPan,BorderLayout.CENTER);
   contentPane.add(selPan,BorderLayout.NORTH);
   contentPane.add(logPan,BorderLayout.SOUTH);

   frame.pack();
   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   frame.setVisible(true);
  }
 
  /** 创建选择面板selPan */
  private void buildSelectionPanel(){…}

  /** 为选择面板selPan中的两个按钮注册监听器 */
  public void addSelectionPanelListeners(ActionListener a[]){
   int len=a.length;
   if(len!=2){ return;}

   custBt.addActionListener(a[0]);
   allCustBt.addActionListener(a[1]);
 }
 
  /** 创建单个客户custPan面板 */
  private void buildCustPanel(){…}
 
  /** 为单个客户面板custPan中的4个按钮注册监听器 */
  public void addCustPanelListeners(ActionListener a[]){
    int len=a.length;
    if(len!=4){ return;}

   getBt.addActionListener(a[0]);
   addBt.addActionListener(a[1]);
   delBt.addActionListener(a[2]);
   updBt.addActionListener(a[3]);
  }
 
  /** 创建所有客户allCustPan面板 */
  private void buildAllCustPanel(){
    allCustPan.setLayout(new BorderLayout());
    allCustPan.add(allCustLb,BorderLayout.NORTH);
    allCustTa.setText("all customer display");

    tableModel=new DefaultTableModel(tableHeaders,10);
    table=new JTable(tableModel);
    tablePane=new JScrollPane(table);

    allCustPan.add(tablePane,BorderLayout.CENTER);

    Dimension dim=new Dimension(500,150);
    table.setPreferredScrollableViewportSize(dim);
  }
 
  /** 创建日志面板*/
  private void buildLogPanel(){…}
}

StoreGui类中的public类型的方法可分为3类。

(1)让图形界面展示数据的方法

  • refreshCustPane(Customer cust):在单个客户面板 custPan上显示参数cust指定的特定客户的信息。
  • refreshAllCustPan(Set custs):在所有客户面板 allCustPan上显示参数custs指定的所有客户的信息。
  • public void updateLog(String msg):在日志面板上显示参数msg指定的日志信息。

(2)从图形界面上读取数据的方法

  • getCustIdOnCustPan():读取单个客户面板custPan上用户输入的ID。
  • getCustomerOnCustPan():读取单个客户面板custPan上用户输入的客户信息。

(3)为图形界面上的按钮注册监听器的方法

  • addSelectionPanelListeners(ActionListener a[]):为选择面板selPan中的两个按钮注册监听器。
  • addCustPanelListeners(ActionListener a[]):为单个客户面板custPan中的4个按钮注册监听器。

StoreViewImpl类实现了StoreView接口。一个StoreViewImpl对象与一个StoreModel对象、一个StoreGui对象,以及若干StoreController对象关联。如例程13-7所示是StoreViewImpl类的源程序。

例程13-7  StoreViewImpl.java

package store;
//此处省略import语句

public class StoreViewImpl extends UnicastRemoteObject
implements StoreView,Serializable{
  private transient StoreGui gui;
  private StoreModel storemodel;
  private Object display;

  private ArrayList storeControllers=
new ArrayList(10);

  public StoreViewImpl(StoreModel model)throws RemoteException {
    try{
       storemodel=model;
       model.addChangeListener(this);  //向model注册自身
    }catch(Exception e){
      System.out.println("StoreViewImpl constructor "+e);
    }

    gui=new StoreGui();
    //向图形界面注册监听器
    gui.addSelectionPanelListeners(selectionPanelListeners);
    gui.addCustPanelListeners(custPanelListeners);
  }
 
  /** 注册控制器*/
  public void addUserGestureListener(StoreController b)
throws StoreException,RemoteException{
    storeControllers.add(b);
  }
  /** 在图形界面上展示参数display指定的数据 */
  public void showDisplay(Object display) throws StoreException,RemoteException{
    if(!(display instanceof Exception))this.display=display;

    if(display instanceof Customer){
       gui.refreshCustPane((Customer)display);
    }
    if(display instanceof Set){
       gui.refreshAllCustPan((Set)display);
    }
    if(display instanceof Exception){
       gui.updateLog(((Exception)display).getMessage());
    }
  }

  /** 刷新界面上的客户信息*/
  public void handleCustomerChange(Customer cust)throws StoreException,RemoteException{
     long cIdOnPan=-1;

    try{
      if(display instanceof Set){
         gui.refreshAllCustPan(storemodel.getAllCustomers());
         return;
      }
      if(display instanceof Customer){
          cIdOnPan=gui.getCustIdOnCustPan();
          if(cIdOnPan!=cust.getId())return;

          gui.refreshCustPane(cust);
      }
    }catch(Exception e){
      System.out.println("StoreViewImpl processCustomer "+e);
    }
  }
 
  /** 监听图形界面上【查询客户】按钮的ActionEvent的监听器 */
  transient ActionListener custGetHandler=new ActionListener(){
     public void actionPerformed(ActionEvent e){
        StoreController sc;
        long custId;
        custId=gui.getCustIdOnCustPan();

        for(int i=0;i          sc=storeControllers.get(i);
          sc.handleGetCustomerGesture(custId);
        }
     }
  };
 
  /** 监听图形界面上【添加客户】按钮的ActionEvent的监听器 */
  transient ActionListener custAddHandler=new ActionListener(){…};

  /** 监听图形界面上【删除客户】按钮的ActionEvent的监听器 */
  transient ActionListener custDeleteHandler=new ActionListener(){…};

  /** 监听图形界面上【更新客户】按钮的ActionEvent的监听器 */
  transient ActionListener custUpdateHandler=new ActionListener(){…};

  /** 监听图形界面上【客户详细信息】按钮的ActionEvent的监听器 */
  transient ActionListener custDetailsPageHandler=new ActionListener(){
     public void actionPerformed(ActionEvent e){
        StoreController sc;
        long custId;
        custId=gui.getCustIdOnCustPan();
        if(custId==-1){
          try{
            showDisplay(new Customer(-1));
          }catch(Exception ex){ex.printStackTrace();}
        }else{
          for(int i=0;i            sc=storeControllers.get(i);
            sc.handleGetCustomerGesture(custId);
          }
        }
     }
  };

  /** 监听图形界面上【所有客户清单】按钮的ActionEvent的监听器 */
  transient ActionListener allCustsPageHandler=new ActionListener(){…};
 
  /** 负责监听单个客户面板custPan上的所有按钮的ActionEvent事件的监听器 */ 
  transient ActionListener custPanelListeners[] ={custGetHandler,custAddHandler,
   custDeleteHandler,custUpdateHandler};

  /** 负责监听选择面板selPan上的所有按钮的ActionEvent事件的监听器 */   
  transient ActionListener selectionPanelListeners[]={
     custDetailsPageHandler,allCustsPageHandler};
}  

在StoreViewImpl类中定义了6个ActionListener监听器,它们分别监听图形界面上的6个按钮发出的ActionEvent事件。例如,以下custGetHandler是【查询客户】按钮发出的ActionEvent事件的监听器:

transient ActionListener custGetHandler=new ActionListener(){
   public void actionPerformed(ActionEvent e){
      StoreController sc;
      long custId;
      custId=gui.getCustIdOnCustPan();

      for(int i=0;i        sc=storeControllers.get(i);
        sc.handleGetCustomerGesture(custId);
      }
   }
};

 

在以上actionPerformed()方法中,先从界面中读取用户输入的ID,然后调用StoreController的handleGetCustomerGesture()方法进行处理。由此可见,视图本身并不处理具体业务逻辑,仅负责输入和输出数据,用户的请求则由控制器来处理。从本章13.4节(创建控制器)的控制器实现中可以看出,控制器实际上也不处理业务逻辑,而是调用模型来处理。



Java 中的中文编码问题
Java基础知识的三十个经典问答
玩转 Java Web 应用开发
使用Spring更好地处理Struts
用Eclipse开发iPhone Web应用
插件系统框架分析
更多...   


Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术


Struts+Spring+Hibernate/EJB+性能优化
华夏基金 ActiveMQ 原理与管理
某民航公司 Java基础编程到应用开发
某风电公司 Java 应用开发平台与迁移
日照港 J2EE应用开发技术框架与实践
某跨国公司 工作流管理JBPM
东方航空公司 高级J2EE及其前沿技术
更多...