学习顺便翻译:理解jsp模式2架构——MVC设计模式探险

2024-02-15 15:18

本文主要是介绍学习顺便翻译:理解jsp模式2架构——MVC设计模式探险,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文

理解jsp模式2架构



MVC设计模式探险


摘要:
通过开发一个熟悉的基于web的购物店,你将学到如何工具化mvc设计模式并且真正地在使用jsp的时候分离内容和表现。Govind Seshadri 会向你展示这是多么的容易(2000字(原文字数))。

作者:Govind Seshadri

尽管相对抛开最近的相关介绍而言,jsp技术正在很好地以自己的方式成为卓越的创建提供动态web内容的应用程序的java技术。java开发者因为许多不同的理由喜爱jsp。一些人喜欢它给交互式web页面带来了“一次编写,到处运行”的变化这个事实;另一些人欣赏它易学易用并且帮助人们把java作为服务器端脚本使用。但是都公认一件事——使用jsp最大的好处是能够有效地分离内容与表现。在这篇文章里,我来提供一个深入的看法,关于如何使用jsp模式2架构获得最佳的内容与表现的分离。这个模式也可以被看作流行的mvc设计模式在服务端的实现。请注意在开始之前你应该熟悉jsp和servlet编程,因为我不会在这篇文章中讨论语法问题。

那么,servlet有什么问题?

既然jsp用来提供动态web内容并且对于从表现层中分离内容很不错,一些人也许想知道为什么servlet要从jsp中脱离出来与它并列。servlet的功用没有问题。它对于服务端处理干得很好,而且,由于它重要的已安装基础,就适合这个。实际上,从结构上说,你可以把jsp看作实现为servlet 2.1 api的扩展的servlet高级抽象。仍然不应该不加区别地使用servlet;它可能不会适用于每一个人。举个例子来说,尽管页面设计者能够很容易地使用常规html或者xml工具编写jsp页面,而servlet通常更适合后台开发者,他们通常使用某种IDE——一个通常需要高层次的编程专门知识的过程。当发布servlet时,即使开发者也必须留意和确认在内容和表现之间没有紧耦合。通常,你可以通过加入第三方的html封装包比如htmlkona来做这个。即使这样做了,尽管带来了一些简单的对于屏幕变化的伸缩性,仍然不能为你防止免受表现格式自身的变化的影响。例如,如果你的表现形式从html转变到dhtml,你将仍然需要确认你的封装包是否兼容这种新格式。在最坏的情况下,如果封装包不能用了,你可能最终会在动态内容内部硬编码表现形式。那么,解决办法是什么?就像你你将要看到的,一个办法将会同时使用jsp和servlet来创建应用系统。

差异哲学

早期的jsp规范主张两种使用jsp技术创建应用的哲学思路。这两种思路,用术语来说就是jsp模式1和模式2,本质上的区别在于大部分请求的处理发生的位置。在模式1架构中,如图1所示,jsp页面独立地负责处理请求和发送反馈给客户端。这里仍然有内容和表现的分离,因为所有的数据访问是使用bean完成的。尽管模式1架构应该很适合简单应用,但是对于复杂的实现是不可取的。这种结构的任意使用通常会导致大量的脚本和java代码嵌入到jsp页面中,特别是在有大量的请求需要处理的情况下。尽管这可能对java开发者来说不是一个大问题,但是却无疑是一个问题,如果你的jsp页面是由设计师创建和维护的话——在大项目中通常如此。最终,这个问题甚至会导致角色定义和责任分配的混乱,引起本可以轻松避免的项目管理的麻烦。


图1:jsp模式1结构

模式2架构如图2所示,是一个为动态内容服务的混合方案,因为它同时使用了servlet和jsp。它利用了两种技术的优势,使用jsp产生表现层而servlet负责执行敏感任务。在这里,servlet扮演控制器的角色,负责请求处理和产生jsp要使用的bean和对象,以及根据客户的动作决定下一步转发到哪一个jsp页面。特别要注意的是jsp页面内部并没有处理逻辑;它只是简单地负责取得可能是servelet事先创建的对象和bean,并为在了静态模版中插入从servlet释放出动态内容。我的观点是,这个办法一般会形成最干净彻底的表现与内容的分离,使得你的开发团队里的开发者和页面设计师的角色与责任能够清晰。实际上,你的应用越复杂,使用模式2带来的好处就越多。


图2:jsp模式2结构



为了弄清模式2背后的概念,我们来参观一个细化的具体实现:一个叫做音乐无界的在线音乐商店样品。


了解音乐无界

主视图,或者说表现层,对于我们的音乐无界由jsp页面EShop.jsp产生(见清单1)。你会注意到这个页面几乎仅仅处理这个应用的主要用户界面,而且没有做任何处理工作——一个最佳的jsp脚本。也注意一下另一个jsp页面,cart.jsp(见清单2),通过指令<jsp:include page="Cart.jsp" flush="true" />包含在EShop.jsp之内。

清单1

EShop.jsp

<%@ page session="true" %>
<html>
<head>
<title>Music Without Borders</title>
</head>
<body bgcolor="#33CCFF">
<font face="Times New Roman,Times" size="+3">
  Music Without Borders
</font>
<hr><p>
<center>
<form name="shoppingForm"
   action="/examples/servlet/ShoppingServlet"
   method="POST">
<b>CD:</b>
<select name=CD>
  <option>Yuan | The Guo Brothers | China | $14.95</option>
  <option>Drums of Passion | Babatunde Olatunji | Nigeria | $16.95</option>
  <option>Kaira | Tounami Diabate| Mali | $16.95</option>
  <option>The Lion is Loose | Eliades Ochoa | Cuba | $13.95</option>
  <option>Dance the Devil Away | Outback | Australia | $14.95</option>
  <option>Record of Changes | Samulnori | Korea | $12.95</option>
  <option>Djelika | Tounami Diabate | Mali | $14.95</option>
  <option>Rapture | Nusrat Fateh Ali Khan | Pakistan | $12.95</option>
  <option>Cesaria Evora | Cesaria Evora | Cape Verde | $16.95</option>
  <option>Ibuki | Kodo | Japan | $13.95</option>
</select>
<b>Quantity: </b><input type="text" name="qty" SIZE="3" value=1>
<input type="hidden" name="action" value="ADD">
<input type="submit" name="Submit" value="Add to Cart">
</form>
</center>
<p>
<jsp:include page="Cart.jsp" flush="true" />
</body>
</html>

 

清单2

Cart.jsp

<%@ page session="true" import="java.util.*, shopping.CD" %>
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
if (buylist != null && (buylist.size() > 0)) {
%>
<center>
<table border="0" cellpadding="0" width="100%" bgcolor="#FFFFFF">
<tr>
<td><b>ALBUM</b></td>
<td><b>ARTIST</b></td>
<td><b>COUNTRY</b></td>
<td><b>PRICE</b></td>
<td><b>QUANTITY</b></td>
<td></td>
</tr>
<%
  for (int index=0; index < buylist.size();index++) {
   CD anOrder = (CD) buylist.elementAt(index);
%>
<tr>
  <td><b><%= anOrder.getAlbum() %></b></td>
  <td><b><%= anOrder.getArtist() %></b></td>
  <td><b><%= anOrder.getCountry() %></b></td>
  <td><b><%= anOrder.getPrice() %></b></td>
  <td><b><%= anOrder.getQuantity() %></b></td>
  <td>
   <form name="deleteForm"
    action="/examples/servlet/ShoppingServlet"
    method="POST">
   <input type="submit" value="Delete">
   <input type="hidden" name= "delindex" value='<%= index %>'>
   <input type="hidden" name="action" value="DELETE">
  </form>
      </td>
    </tr>
    <% } %>
  </table>
  <p>
  <form name="checkoutForm"
    action="/examples/servlet/ShoppingServlet"
    method="POST">
    <input type="hidden" name="action" value="CHECKOUT">
    <input type="submit" name="Checkout" value="Checkout">
  </form>
  </center>
<% } %>

这里,Cart.jsp处理基于session的购物车的表现形式,它指定了我们的MVC结构中的模型。观察Cart.jsp开头这一段脚本:
<%
  Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
  if (buylist != null && (buylist.size() > 0)) {
%>
基本上,这段脚本从session中提出了购物车。如果购物车为空或者还未创建,它不会显示任何东西;因此,当用户第一次访问的时候,他见到的页面如图3。


图3:音乐无界,主视图


如果购物车不是空的,那么已选中的物品会一次一个地从购物车中被提出,像下面的脚本示范的那样:

<%
  for (int index=0; index < buylist.size(); index++) {
    CD anOrder = (CD) buylist.elementAt(index);
%>
一旦描述物品的变量已创建,它们就简单地被JSP表达式插入到静态HTML模版中去。图4显示了用户已经放了一些东西到购物车里去是的情况。


图4:音乐无界,购物车视图

这里要注意的重要的一件事是对所有动作的处理既不发生在EShop.jsp也不在Cart.jsp里,而是由控制器servlet,ShoppingServlet.java处理,见清单3:

清单3

ShoppingServlet.java

import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import shopping.CD;
public class ShoppingServlet extends HttpServlet {
  public void init(ServletConfig conf) throws ServletException  {
    super.init(conf);
  }
  public void doPost (HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    HttpSession session = req.getSession(false);
    if (session == null) {
      res.sendRedirect("http://localhost:8080/error.html");
    }
    Vector buylist=
      (Vector)session.getValue("shopping.shoppingcart");
    String action = req.getParameter("action");
    if (!action.equals("CHECKOUT")) {
      if (action.equals("DELETE")) {
        String del = req.getParameter("delindex");
        int d = (new Integer(del)).intValue();
        buylist.removeElementAt(d);
      } else if (action.equals("ADD")) {
        //any previous buys of same cd?
        boolean match=false;
        CD aCD = getCD(req);
        if (buylist==null) {
          //add first cd to the cart
          buylist = new Vector(); //first order
          buylist.addElement(aCD);
        } else { // not first buy
          for (int i=0; i< buylist.size(); i++) {
            CD cd = (CD) buylist.elementAt(i);
            if (cd.getAlbum().equals(aCD.getAlbum())) {
              cd.setQuantity(cd.getQuantity()+aCD.getQuantity());
              buylist.setElementAt(cd,i);
              match = true;
            } //end of if name matches
          } // end of for
          if (!match)
            buylist.addElement(aCD);
        }
      }
      session.putValue("shopping.shoppingcart", buylist);
      String url="/jsp/shopping/EShop.jsp";
      ServletContext sc = getServletContext();
      RequestDispatcher rd = sc.getRequestDispatcher(url);
      rd.forward(req, res);
    } else if (action.equals("CHECKOUT"))  {
      float total =0;
      for (int i=0; i< buylist.size();i++) {
        CD anOrder = (CD) buylist.elementAt(i);
        float price= anOrder.getPrice();
        int qty = anOrder.getQuantity();
        total += (price * qty);
      }
      total += 0.005;
      String amount = new Float(total).toString();
      int n = amount.indexOf('.');
      amount = amount.substring(0,n+3);
      req.setAttribute("amount",amount);
      String url="/jsp/shopping/Checkout.jsp";
      ServletContext sc = getServletContext();
      RequestDispatcher rd = sc.getRequestDispatcher(url);
      rd.forward(req,res);
    }
  }
  private CD getCD(HttpServletRequest req) {
    //imagine if all this was in a scriptlet...ugly, eh?
    String myCd = req.getParameter("CD");
    String qty = req.getParameter("qty");
    StringTokenizer t = new StringTokenizer(myCd,"|");
    String album= t.nextToken();
    String artist = t.nextToken();
    String country = t.nextToken();
    String price = t.nextToken();
    price = price.replace('$',' ').trim();
    CD cd = new CD();
    cd.setAlbum(album);
    cd.setArtist(artist);
    cd.setCountry(country);
    cd.setPrice((new Float(price)).floatValue());
    cd.setQuantity((new Integer(qty)).intValue());
    return cd;
  }
}
每次用户在EShop.jsp中添加一件物品,请求都被发送到这个控制器servlet。它依次决定合适的动作,然后处理相应要添加的物品的请求参数。它就实例化一个新的CD bean(见清单4)代表这个选择的物品,接着在把这个bean放进session之前处理购物车的更新。

清单4

CD.java

package shopping;
public class CD {
  String album;
  String artist;
  String country;
  float price;
  int quantity;
  public CD() {
    album="";
    artist="";
    country="";
    price=0;
    quantity=0;
  }
  public void setAlbum(String title) {
    album=title;
  }
  public String getAlbum() {
    return album;
  }
  public void setArtist(String group) {
    artist=group;
  }
  public String getArtist() {
    return artist;
  }
  public void setCountry(String cty) {
    country=cty;
  }
  public String getCountry() {
    return country;
  }
  public void setPrice(float p) {
    price=p;
  }
  public float getPrice() {
    return price;
  }
  public void setQuantity(int q) {
    quantity=q;
  }
  public int getQuantity() {
    return quantity;
  }
}

注意我们在这个servlet中还包括了额外的智能,因此它能够知道如果选择了一张已在购物车中的CD,那么应该简单地增加session中CD bean的计数。它也处理从Cart.jsp中触发的动作,比如用户从购物车中删除物品,或是继续去收银台结帐。注意控制器总是对哪个资源应该被调用来对特定的动作产生回馈有完全的控制权。例如,对购物车状态的改变,像增加和删除,会引起控制器将请求处理后转发给EShop.jsp页面。这样引起该页面依照已更新的购物车依次重新显示主视图。如果用户决定结帐,则请求被处理后转发给Checkout.jsp(见清单5),通过后面的请求分配器,象下面显示的这样:

String url="/jsp/shopping/Checkout.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req,res);

清单5

Checkout.jsp

<%@ page session="true" import="java.util.*, shopping.CD" %>
<html>
<head>
<title>Music Without Borders Checkout</title>
</head>
<body bgcolor="#33CCFF">
<font face="Times New Roman,Times" size=+3>
  Music Without Borders Checkout
</font>
<hr><p>
<center>
<table border="0" cellpadding="0" width="100%" bgcolor="#FFFFFF">
<tr>
<td><b>ALBUM</b></td>
<td><b>ARTIST</b></td>
<td><b>COUNTRY</b></td>
<td><b>PRICE</b></td>
<td><b>QUANTITY</b></td>
<td></td>
</tr>
<%
  Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
  String amount = (String) request.getAttribute("amount");
  for (int i=0; i < buylist.size();i++) {
   CD anOrder = (CD) buylist.elementAt(i);
%>
<tr>
<td><b><%= anOrder.getAlbum() %></b></td>
<td><b><%= anOrder.getArtist() %></b></td>
<td><b><%= anOrder.getCountry() %></b></td>
<td><b><%= anOrder.getPrice() %></b></td>
<td><b><%= anOrder.getQuantity() %></b></td>
</tr>
<%
  }
  session.invalidate();
%>
<tr>
<td>     </td>
<td>     </td>
<td><b>TOTAL</b></td>
<td><b>$<%= amount %></b></td>
<td>     </td>
</tr>
</table>
<p>
<a href="/examples/jsp/shopping/EShop.jsp">Shop some more!</a>
</center>
</body>
</html>

Checkout.jsp仅仅从session中提出购物车并为此请求提取出总金额,然后显示选中的物品和他们的总价格。图5显示了结算时的用户视图。一旦用户去结帐,删除session对象是同样重要的。这个由页面末端的session.invalidate()调用来完成。有两个理由必须这样做。第一,如果没有使session无效,用户的购物车不会重新初始化;如果用户结帐后试图开始新一轮的采购,她的购物车会继续保存着已经付过钱的物品。第二,如果用户结帐后仅仅是离开了网站,这个session对象不会被垃圾收集机制回收而是继续占用宝贵的系统资源直到租约到期。因为缺省的session租约时间是大约三十分钟,在一个大容量系统上这将会很快导致系统内存耗尽。当然,我们都知道对一个耗尽了系统内存的应用程序会发生什么。


图5:结账视图

 

注意这个应用所有的资源都是session相关的,因为这里的模式存储在session里。因此,你必须确保用户不会因为某些原因甚至由于错误直接访问控制器。你可以在控制器检测到缺少有效session的时候让客户端自动转向到一个错误页面(见列表6),来避免这种情况的发生。

列表6

error.html

<html>
<body>
<h1>
  Sorry, there was an unrecoverable error! <br>
  Please try <a href="/examples/jsp/shopping/EShop.jsp">again</a>.
</h1>
</body>
</html>

部署音乐无界
我假定你正在使用来自sun的最新版本的JavaServer Web Development Kit (JSWDK)来运行这个例子。如果不是,参看资源小节去看看到哪里取得它。假设服务器安装在/jswdk-1.0.1,这是Microsoft Windows系统下的缺省路径,可以象下面这样部署音乐无界应用:

Create shopping directory under /jswdk-1.0.1/examples/jsp
Copy EShop.jsp to /jswdk-1.0.1/examples/jsp/shopping
Copy Cart.jsp to /jswdk-1.0.1/examples/jsp/shopping
Copy Checkout.jsp to /jswdk-1.0.1/examples/jsp/shopping
Compile the .java files by typing javac *.java
Copy ShoppingServlet.class to /jswdk-1.0.1/webpages/Web-Inf/servlets
Create shopping directory under /jswdk-1.0.1/examples/Web-Inf/jsp/beans
Copy CD.class to /jswdk-1.0.1/examples/Web-Inf/jsp/beans/shopping
Copy error.html to /jswdk-1.0.1/webpages
Once your server has been started, you should be able to access the application using http://localhost:8080/examples/jsp/shopping/EShop.jsp as the URL
只要你的服务器启动,你应该可以使用URL http://localhost:8080/examples/jsp/shopping/EShop.jsp来访问这个应用程序。

利用jsp和servlet
着这个例子里,我们从细节上检查了控制的层次和模式2架构提供的灵活性。实际上,我们已经看到了servlet和jsp页面最好的特性是如何开发出最大化的内容与表现的剥离。只要正确地应用,模式2架构会将所有的处理逻辑集中到控制器servlet手里,而jsp页面只负责视图或者说表现层的工作。然而,使用模式2结构的阻力在于它的复杂性。因此,对于简单应用使用模式1也是可以接受的。

这篇关于学习顺便翻译:理解jsp模式2架构——MVC设计模式探险的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/711745

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss