本系列打算从最基础开始,详细介绍j2ee开发技术的原理及相应框架;

         本文主要介绍了3个servlet的入门例子:

          例子1:最简单的servlet例子,也就是hello world;

          例子2:介绍了一个动作框架,这个是其他更复杂框架的基础思路;

          例子3:加入了数据库存储数据,同时提供了一个最基础的事务解决方案;

         最后,本文根据以上3个例子,提炼出j2ee web开发过程中可能会碰到的问题;实际上这也是大多数web开发框架需要解决的问题;后续我会带着这些问题去解析ssh框架。其中重点在spring和hibernate。

1 最简单的例子——servlet接口

1.1 项目代码

第一给例子是最简单的servlet,只有一个函数;

1、项目结构

2、HelloWorldServlet继承HttpServlet接口

public class HelloWorldServlet  extends HttpServlet{
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		//super.service(req, resp);
		 PrintWriter writer = resp.getWriter();
         writer.println("Hello, World!");
         writer.close();
	}
}

3、web.xml配置

<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.xuxm.demo.servlet.HelloWorldServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app>


1.2 分析

HttpServlet 里的三个方法:service(HttpServletRequest req, HttpServletResponse resp) ,doGet(HttpServletRequest req, HttpServletResponse resp), doPost(HttpServletRequest req, HttpServletResponse res)的区别和联系:

      在servlet中默认情况下,无论你是get还是post 提交过来 都会经过service()方法来处理,然后转向到doGet 或是doPost方法,我们可以查看HttpServlet 类的service方法:  我在tomcat的lib目录下,解压servlet-api.jar,然后用反编译软件把lib\javax\servlet\http下的HttpServlet.class反编译,看里头的service()方法的原代码;
注意,sun只是定义了servlet接口,而实现servlet接口的就是类似于tomcat的服务器,所以我是在tomcat的安装目录下找到实现的类。

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
  {
    String method = req.getMethod();

    if (method.equals("GET")) {
      long lastModified = getLastModified(req);
      if (lastModified == -1L)
      {
        doGet(req, resp);
      } else {
        long ifModifiedSince = req.getDateHeader("If-Modified-Since");
        if (ifModifiedSince < lastModified / 1000L * 1000L)
        {
          maybeSetLastModified(resp, lastModified);
          doGet(req, resp);
        } else {
          resp.setStatus(304);
        }
      }
    }
    else if (method.equals("HEAD")) {
      long lastModified = getLastModified(req);
      maybeSetLastModified(resp, lastModified);
      doHead(req, resp);
    }
    else if (method.equals("POST")) {
      doPost(req, resp);
    }
    else if (method.equals("PUT")) {
      doPut(req, resp);
    }
    else if (method.equals("DELETE")) {
      doDelete(req, resp);
    }
    else if (method.equals("OPTIONS")) {
      doOptions(req, resp);
    }
    else if (method.equals("TRACE")) {
      doTrace(req, resp);
    }
    else
    {
      String errMsg = lStrings.getString("http.method_not_implemented");
      Object[] errArgs = new Object[1];
      errArgs[0] = method;
      errMsg = MessageFormat.format(errMsg, errArgs);

      resp.sendError(501, errMsg);
    }
  }

2 动作框架

2.1 简介

在 Web 开发初期,许多专业编程人员都不得不弄清当他们继续时,如何较好地使用 servlet。最普遍的结果之一就是在服务器上暴露 servlet。每种类型的请求都有一个。

这很快就变得令人头痛,因此,编程人员开始在其 servlet 中包含条件逻辑使之更具适应性,以便处理多种类型的请求。一段时间后,这也产生了一些糟糕的代码。有一种更好的方式,称作动作 servlet(action servlet),它实现了名为模型 2 的概念。据我了解,该思想是由 David M. Geary首次写到的,但是它已经较好的用于流行的 servlet 库中了,例如 Jakarta Struts 项目。

在动作 servlet 中,并没有指示 servlet 行为的条件逻辑,而是具有动作(编程人员定义的类),servlet 授权这些类来处理不同类型的请求。大多数情况下,这个面向对象(OO)的方法要优于拥有多个 servlet,或在一个 servlet 中有多个 if 条件。

我们的示例动作 servlet 将是一个极简单的、基于浏览器的应用程序的网守(gatekeeper),该应用程序将允许我们创建、存储、查看以及删除合同列表项。这些记录项的格式都非常良好。最后,为了使用该应用程序,用户将必须登录它,但是,我们稍后将在 用户和数据 中添加这项功能。

2.2 项目代码

1、项目代码结构

2、项目测试结果

      这里实现了contact的增删功能;如果按照第一个例子中的实现,则对于每个功能(显示列表、增、删)都必须实现一个servlet,同时在web.xml中配置;但是如果有很多个功能,这样的实现显然不合理;因此就有了本例子的动作框架;

3、servlet

public class ContactsServlet  extends HttpServlet{
	
	protected ActionFactory factory = new ActionFactory();

    public ContactsServlet() {
         super();
    }
    
    protected String getActionName(HttpServletRequest request) {
        String path = request.getServletPath();
        //System.out.print("test:"+request.getServletPath());
        //System.out.print("test:"+path.substring(1, path.lastIndexOf(".")));
        return path.substring(1, path.lastIndexOf("."));
   }
    
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		ContactsAction action = factory.create(getActionName(req));
         String url = action.perform(req, resp);
         if (url != null)
              getServletContext().getRequestDispatcher(url).forward(req, resp);
		
	}
}

4、动作框架

     1)ContactsAction

public interface ContactsAction {
	 public String perform(HttpServletRequest request, HttpServletResponse response);
}

     2)BootstrapAction、AddContactAction、RemoveContactAction这三个具体动作都实现了 ContactsAction接口

public class BootstrapAction implements ContactsAction{

	@Override
	public String perform(HttpServletRequest request,
			HttpServletResponse response) {
		// TODO Auto-generated method stub
		return "/" + "contactList.jsp";
	}

}
public class AddContactAction  implements ContactsAction{

	@Override
	public String perform(HttpServletRequest request,
			HttpServletResponse response) {
		Contact newContact = createContact(request);
        
        HttpSession session = request.getSession();
        ContactList contacts = (ContactList) session.getAttribute("contacts");
        contacts.addContact(newContact);
        session.setAttribute("contacts", contacts);
        
        return "/contactList.jsp";
	}
	
	 protected Contact createContact(HttpServletRequest request) {
         Contact contact = new Contact();
         contact.setFirstname(request.getParameter(RequestParameters.FIRSTNAME));
         contact.setLastname(request.getParameter(RequestParameters.LASTNAME));
         contact.setStreet(request.getParameter(RequestParameters.STREET));
         contact.setCity(request.getParameter(RequestParameters.CITY));
         contact.setState(request.getParameter(RequestParameters.STATE));
         contact.setZip(request.getParameter(RequestParameters.ZIP));
         contact.setType(request.getParameter(RequestParameters.TYPE));
         
         return contact;
    }     

}
public class RemoveContactAction implements ContactsAction{

	@Override
	public String perform(HttpServletRequest request, HttpServletResponse response) {
        int contactId = Integer.parseInt(request.getParameter("id"));
        
        HttpSession session = request.getSession();
        ContactList contacts = (ContactList) session.getAttribute("contacts");
        contacts.removeContact(contactId);
        session.setAttribute("contacts", contacts);
        
        return "/contactList.jsp";
   }

}

  3)ActionFactory

public class ActionFactory {
	protected Map map = defaultMap();
    
    public ActionFactory() {
         super();
    }
    
    public ContactsAction create(String actionName) {
    	Class klass = (Class) map.get(actionName);
    	if (klass == null)
            throw new RuntimeException(getClass() + " was unable to find " +
            		"an action named '" + actionName + "'.");
    	ContactsAction actionInstance = null;
        try {
             actionInstance = (ContactsAction) klass.newInstance();
        } catch (Exception e) {
             e.printStackTrace();
        }

        return actionInstance;
    }
    
    protected Map defaultMap() {
        Map map = new HashMap();

        map.put("index", BootstrapAction.class);
        map.put("addContactAction", AddContactAction.class);
        map.put("removeContactAction", RemoveContactAction.class);

        return map;
   }

}

   4)jsp页面contactList.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page import="java.util.*" %>
<%@ page import="com.xuxm.demo.model.*" %>
<html>
<head>
<title>Contacts List 1.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

<style type="text/css">
     body, table, hr {
          color: black;
          background: silver;
          font-family: Verdana, sans-serif;
          font-size: x-small;
     }
</style>

</head>

<body>
     <jsp:useBean id="contacts" scope="session" 
         class="com.xuxm.demo.model.ContactList"/>
     
     <h2>Contact List 1.0</h2>
     <hr size="2"/>
     <table frame="below" width="100%">
       <tr>
         <th align="left"></th>
         <th align="left">Name</th>
         <th align="left">Street</th>
         <th align="left">City</th>
         <th align="left">State</th>
         <th align="left">Zip</th>
         <th align="left">Type</th>
       </tr>
     <%
       List list = contacts.getContacts();
       for (Iterator i = list.iterator(); i.hasNext();) {
         Contact contact = (Contact)i.next();
     %>
       <tr>
         <td width="100"><a href="removeContactAction.perform?id=<%= contact.getId()%>"
             >Delete</a></td>  
         <td width="200"><%=contact.getFirstname()%> <%=contact.getLastname()%></td>
         <td width="150"><%=contact.getStreet()%></td>
         <td width="100"><%=contact.getCity()%></td>
         <td width="100"><%=contact.getState()%></td>
         <td width="100"><%=contact.getZip()%></td>
         <td width="100"><%=contact.getType()%></td>
       </tr>
     <%
       }
     %>  
     </table>
     <br/>
     <br/>
     <br/>
     <fieldset>
          <legend><b>Add Contact</b></legend>
          <form method="post" action="addContactAction.perform">
               <table>
                    <tr>
                         <td>First Name:<td>
                         <td><input type="text" size="30" name="firstname"></td>
                    </tr>
                    <tr>
                         <td>Last Name:<td>
                         <td><input type="text" size="30" name="lastname"></td>
                    </tr>
                    <tr>
                         <td>Street:<td>
                         <td><input type="text" size="30" name="street"></td>
                    </tr>
                    <tr>
                         <td>City:<td>
                         <td><input type="text" size="30" name="city"></td>
                    </tr>
                    <tr>
                         <td>State:<td>
                         <td><input type="text" size="30" name="state"></td>
                    </tr>
                    <tr>
                         <td>Zip:<td>
                         <td><input type="text" size="30" name="zip"></td>
                    </tr>
                    <tr>
                         <td>Type:<td>
                         <td><input type="radio" size="30" name="type" value="family">
                             Family <input type="radio" size="30" name="type" 
                                value="acquaintance"
                                 checked> Acquaintance</td>
                    </tr>
               </table>
               <br/>
               <input type="submit" name="addContact" value="  Add  ">
          </form>
     </fieldset>

</body>
</html>

2.3 分析

就像以前一样,我们扩展 HttpServlet 并重载 service() 方法。在该方法中,我们:

  • 从导致调用 servlet 的 URL 中派生动作名。
  • 基于该名称实例化正确的动作。
  • 告诉该动作开始执行。
  • 将响应发送给动作所指向的 URL。

我们从导致调用 servlet 的 URL 中派生动作名,而该 servlet 是从 request.servletPath() 获得的。请记住,导致我们调用动作的所有 URL 都具有 *.perform 的形式。我们将解析该形式来获得圆点左边的字符串,该字符串就是动作名,然后将该动作名传递给 ActionFactory,以实例化正确的动作。现在,您看到我们为何告诉 Web 应用程序如何处理该形式的 URL,以及为何在 JSP 页面中使用这些“神奇”的字符串。正是因为这样,我们才可以在这里对它们进行解码,并采取对我们有利的动作。有什么替代方案?大量的 if 语句和大量的附加代码。正如我们将看到的,通过动作,需要执行的每个动作都已完全封装。

这样做很好,但是我们需要一些附加类来完成该任务。这就是动作框架要做的事。

我们的简单动作框架有 4 个主要组件:

ActionFactory。该工厂将请求中的动作名转换成 servlet 可以用来完成其工作的动作类。

Action 接口。该接口定义所有动作的极其简单的公共接口。

名为 ContactsAction 的抽象类。该类实现了所有动作共用的一个方法,并强制子类实现另一个方法(perform())。

ContactsAction 的三个子类。这些子类使 servlet 能够进行自我引导、添加新合同和删除合同。

在 servlet 的 service() 方法中,该过程以 ActionFactory 开始。

3 总结

根据以上的例子,我们可以猜测(其实是肯定)得出如下结论:

        1、所有请求的入口都是servlet;于是你会发现,不管是struct还是spring等,实际上在web.xml中配置的入口都是servlet;

        2、请求参数传递:用求某个地址时,参数是如何传入servlet的?从例子中我们可以看到参数是被包装在HttpServletRequest中了。这里会涉及到两个问题:1)验证参数,比如输入的数字不能是小数等;2)参数与对象之间的绑定;

            1)参数验证:方法主要有3种:1、前端js验证;2、获取HttpServletRequest参数时验证;3、转为类对象时验证;

            2)参与与类对象绑定:在大多数框架,会将传入参数绑定到类对象中,程序实际上处理的是类对象;

        3、动作框架:如果每个请求都配置一个servlet,过于混乱;如本文中所述设计一个动作框架,这个是比较初级的;通常框架会提供一个基础类来做这个事情,不需要用户设计;

        4、请求或者是结果处理:如果我想对请求做一些处理,比如设置编码方式,或者验证用户权限;这时候该如何处理?总不可能对每个请求处理的代码里面都加入相同的代码吧?对于这个处理,一般是用servlet的filter、或者是spring中的interceptor、或者利用aop来实现;

        5、表现层:对于用户看到的部分,使用哪种技术来实现,是jsp还是其他?对于选定的技术,实际上还需要解决,返回参数如何显示在jsp(或其他文件)上;

        6、数据存储:1)数据存储在数据库时,需要调用对应的程序,一般是通过调用jdbc来与数据交互;这里就涉及到一个重要问题,那就是事务;

                                2)数据源切换:通常的理论设计框架,都在鼓吹是否支持切换数据库,比如从oracle切换到mysql,也就是做到数据库无关性;(个人感觉这个需求似乎没那么强烈)

                                3)缓存:对实时性要求比较高的系统,所有数据都从数据库读取,显然效率太差;于是需要引入数据缓存机制,比如hibernate中ehance,或者redis等开源组件;

        7、监听:通常我们可能考虑在系统初始化时做一些事情,或者在某些事件发生时做一些事情。那么这个就需要用到监听了。实际上在web.xml中是可以配置listener的。

        8、上下文:一般的应用都要求做一些国际化等之类的事情,这个可能就需要维护上下文,用于支持国际化等;

        9、session和cookie:web开发这两个东西是免不了的了。

       10、日志:日志本质上不应该在这里提,因为它是属于实现的一部分,但是考虑到在系统bug分析、运维、用户行为分析(数据挖掘)等方面都有大用,因此对于系统日志需要做针对性考虑,到底该记哪些日志、怎么记?

        以上基本覆盖了一个web框架需要做的事情,在分析各个框架时以这个思路去考虑其对应的配置到底是做什么的,就可以一目了然了,而不是被各种框架五花八门的实现给迷惑了。当然,在一些框架中可能还用到一些比较方便的功能:

        1、bean自动装配:如果系统有很多类,这些类都要在配置文件中进行配置显然是一个很繁琐的事情,于是自动装配bean就被提出来了。这个主要是利用java的注解和反射。

        2、分布式(多线程):由于web网站有可能访问量特别大,因此需要进行扩容,这里就设计分布式(多线程);

        3、多访问方式:这里用多访问方式似乎不大妥当,但是也找不到其他词来描述了。比如一个系统,部分用户使用普通的网页方式访问而部分用户使用web service访问,这就有可能导致其基于事务处理出现问题(如果它的事务只是针对web配置的话);