2000年7月26日星期三

MVC构架渐行渐进

 MVC:模型-视图-控制器结构,这种构架在VC中我们可以体会得更深一些。在JAVA中实现这种构架的目的是实现网页制作人员和开发人员的分工。然而这一知识点并不容易掌握,所需要读者了解的知识点尤其是对servlet的理解一定要深刻,所以这里我采用渐行渐进的步骤,从点到面逐步引导大家掌握这一技术。

 首先,大家要看懂以下几个类的作用。每个类都有相应的说明,和导读内容。 

Action接口

示例:/WEB-INF/classes/actions/Action.java

package actions;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Action
{
public ActionRouter perform(HttpServlet servlet,
HttpServletRequest req,HttpServletResponse res)
throws java.io.IOException,javax.servlet.ServletException;
}

Action接口定义了一个perform的方法,它向操作servlet、HTTP请求响应传递引用。

ActionFactory类

示例:/WEB-INF/classes/actions/ActionFactory.java

package actions;

import java.util.Hashtable;
public class ActionFactory
{
private Hashtable actions=new Hashtable();
public Action getAction(String classname,ClassLoader loader)
throws ClassNotFoundException,IllegalAccessException,InstantiationException
{
Action action=(Action)actions.get(classname);

if (action==null) //如果该操作未存储在哈希表中,操作库将首先创建它
{
Class klass=loader.loadClass(classname);//获取对象类型
action=(Action)klass.newInstance();//对象初始化
actions.put(classname,action);//存储到哈希表中
}
return action;
}
}

在操作库中保持操作的哈希表,该库的getAction方法由操作的Servlet调用,并返回对应于类名的操作。如果该操作未存储在哈希表中,操作库将首先创建它,然后再存储到哈希表中。以后遇到相同的操作请求,操作库将仅仅从哈希表中返回一个引用。

导读:GetClass():方法返回为一个Class类型的对象。例:



private LoginDB loginDB;

Class pt=loginDB.getClass();

System.out.println(“The Beans name is:”+pt.getName());



输出内容:The Beans name is: bean.LoginDB

大多数情况下定义一个Class的对象的目的是要引用getClass方法。

newInstance():这个方法将调用类的默认的构造器,由当前的Class对象表示,并把生成的对象作为Object类型返回。如果你想把该类型保存在某一类型的变量里,则需要将它转换

为相应的类型,否则将直接把结果存储在一个Object类型的变量里。这个方法会抛出两个异常:IllegalAccessException,InstantiationException

ActionRouter类

示例:/WEB-INF/classes/actions/ActionFactory.java

package actions;

import javax.servlet.GenericServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ActionRouter
{
private final String url;
private final boolean isForward;
public ActionRouter(String url)
{
this(url,true);
}

public ActionRouter(String url,boolean isForward)
{
this.url=url;
this.isForward=isForward;
}

public void route(GenericServlet servlet,HttpServletRequest req,HttpServletResponse res)
{
try
{
if (isForward)
{
//req.getRequestDispatcher(res.encodeURL(url)).forward(req,res);
//req.getRequestDispatcher(res.encodeURL("/index.jsp")).forward(req,res);
servlet.getServletContext().getRequestDispatcher(res.encodeURL(url)).forward(req,res);
}
else
{
res.sendRedirect(res.encodeRedirectURL(url));
}
}
catch(Exception e)
{
System.out.print(e);
}
}
}

操作路径选择器功能是转发或重定向请求。(所有类均可在/Tomcat 4.1/common/lib/ servlet.jar包中找到。)

导读:
前提知识:Servlet的软件包:打开Tomcat 4.1下的Servlet.jar我们发现Servlet包主要由javax.Servlet和javax.Servlet.http和javax.Servlet.jsp三个包组成,javax.Servlet.jsp这里就不计论。前两种包的所包含的方法和接口如下表所示:

  包      提供的接口    提供的方法

javax.Servlet RequestDispatcher接口Servlet接口ServletConfig接口ServletContext接口ServletRequest接口ServletResponse接口SingleThreadModel接口 GenericServlet类ServletInputStream类ServletOutputStream类ServletException类UnavailableException类

javax.Servlet.http HttpServletRequest接口HttpServletResponse接口HttpSession接口HttpSessionContext接口HttpSessionBindingListener接口 Cookie类HttpServlet类HttpSessionBindingEvent类HttpUtils类

继承关系示例:

java.lang.Object

|

+--javax.servlet.GenericServlet

|

+--javax.servlet.http.HttpServlet

|

+--org.apache.struts.action.ActionServlet


1、GenericServlet类

Public abstract class GenericServlet implants Servlet

此类提供了servlet接口的基本实现部分,其service()方法被申明为abstract,因此需要被派生。init(ServletConfig conf)方法把servletConfig对象存储在一个private transient(私有临时)实例变量里,getServletConfig()方法返回指向本对象的指针,如果你重载此方法,将不能使用getServletConfig来获得ServletConfig对象,如果确实想重载,记住要包含对super.config的调用。2.1版的API提供一个重载的没有参数的init()方法。现在在init(ServletConfig)方法结束时有一个对init()的调用,尽管目前它是空的。2.1版API里面,此类实现了ServletConfig接口,这使得开发者不用获得ServletConfig对象情况下直接调用ServletConfig的方法,这些方法是:

getInitParameter(),getInitParameterNames(),getServletContext()。此类还包含两个写日志的方法,它们实际上调用的是ServletContext上的对应方法。log(String msg)方法将servlet的名称和msg参数写到容器的日志中,log(String msg,Throwable cause)除了包含servlet外还包含一个异常。

2、HttpServlet类:该类扩展了GenericServlet类并对servlet接口提供了为处理 HTML 表单提供了专门的方法。例:

service():

  protected void service(HttpServletRequest req,HttpServletResponse res)

         throws ServletException,IOException

    public void service(HttpServletRequest req,HttpServletResponse res)

        throws ServletException,IOException

该方法作为HTTP请求的分发器,这个方法在任何时候都不能被重载。当请求到来时,service()方法决定请求的类型(GET,POST,HEAD,OPTIONS,DELETE,PUT,TRACE),并把请求分发给相应的处理方法(doGet(),doPost(),doHead(),doOptions(),doDelete(),doPut(),doTrace())每个do方法具有和第一个service()相同的形式。为了响应特定类型的HTTP请求,我们必须重载相应的do方法。如果servlet收到一个HTTP请求而你没有重载相应的do方法,它就返回一个说明此方法对本资源不可用的标准HTTP错误。

   HttpServlet类的init()方法、service()方法和destroy()方法,这三种方法表示了了一个servlet 的生命周期。即初始化时期、执行时期、结束时期。

3、HttpServletRequest接口:所有实现此接口的对象(例如从servlet容器传递的HTTP请求对象)都能让servlet通过自己的方法访问所有请求的数据。下面是一些用来获取表单数据的基本方法。其中定义的主要方法有:

a、 getParameter() =>public String getParameter(String key)

b、 getParameterValues()=> public String[] getParameterValues(String key) 如果一个参数可以返回多个值,比如复选框集合,则可以用此方法获得对应参数的所有值。如果请求信息中没有指定参数,则返回null。

c、 GetParameterNames()=> Public Enumeration getParameterNames()此方法返回一个Enumeration对象,包含对应请求的所有参数名字列表。

d、 获得传入路径的方法:例:

http://localhost:7001/myservlet/somepath/test?someparam=somevaluerequest.getPathInfo():返回/somepath/test

request.getRequestURL():http://localhost:7001/myservlet/somepath/test

request.getRequestURI():返回/myservlet/somepath/test

request.getServletPath():返回/myservlet

request.getQueryString():返回someparam=somevalue

4、HttpServletResponse接口:servlet容器提供一个实现该接口的对象并通过service()方法将它传递给servlet。通过此对象及其方法,servlet可以修改响应头并返回结果。其中定义的主要方法有:

a、 setContentType()=> public void setContentType(String type) 在给调用者发回响应前,必须用此方法来设置HTTP响应的MIME类型。可以是任何有效的MIME类型,当给浏览器返回HTML是就是”text/html”类型。

b、 getWriter()=>public PrintWriter getWriter()throws IOException此方法将返回PrintWriter对象,把servlet的结果作为文本返回给调用者。PrintWriter对象自动把Java内部的UniCode编码字符转换成正确的编码以使客户端能够阅读。

c、 getOutputStream()=>public ServletOutputStream getOutputStream() throws IOException此方法返回ServletOutputStream对象,它是java.io.OutputStream的一个子类。此对象向客户发送二进制数据。

d、 setHeader()=>public void setHeader(String name,String value) 此方法用来设置送回给客户的HTTP响应头。有一些快捷的方法用来改变某些常用的响应头,但有时也需要直接调用此方法。

5、javax.servlet.ServletContext接口: 安装在一个服务器中的一个特定URL名字空间(比如,/myapplication)下的所有Servlet,JSP,JavaBean等Web部件的集合构成了一个Web的应用,每一个Web应用(同一JVM),容器都会有一个背景对象,而javax.servlet.ServletContext接口就提供了访问这个背景对象的途径。

Servlet实例的getServletContext方法: 得到该Servlet运行其中的这个背景对象。从这个背景对象中你可以访问如下信息或资源:(注意该方法不是ServletContext的方法而是获取背景对象的方法由于HttpServlet继承Servlet的关系GenericServlet类和HttpServlet类同时具有该方法)

· 初始化参数  ServletContext.getInitParameter(String name)。

· 存储在背境中的对象 context.getAttribute(String name)

· 与本背景关联的资源 ServletContext.getResource(String path)

· 日志 ServletContext.log(String msg)

以上所示方法均为ServletContext所提供,值得一提的是对于存储在背境中的对象访问方法常用的还有:

 context.setAttribute(String name, Object object);将特定名字绑定的任意类型的对象上。将把object对象绑定到名字name,存放在Servlet背景中,可供同一背景中的其他Servlet共享。其他Servlet可以通过context.getAttribute(String name),得到一个背景中的对象,或通过context.removeAttribute(String name)在背景中移除一个对象。

getRequestDispatcher方法:它的作用将一个包含路径的String传递给其它资源。该路径是相对于ServletContext的根路径的。.方法示例:

RequestDispatcher rd = request.getRequestDispatcher("SecondServlet");

rd.forward(request, response);.

该方法主要用于服务器重定向技术上,servlet中重定向的方法主要有两种分别由ServletContext接口和ServletRequest接口提供但二者提供的同名方法getRequestDispatcher却有很大的不同。在ActionRouter类示例中//req.getRequestDispatcher(res.encodeURL(url)).forward(req,res);表示的用ServletRequest接口提供方法进行重定向的。

操作Servlet[/b:9e5a30927b]

action

ActionServlet

action

*.do


部署信息把以.do结尾的URL映射到操作的servlet. JSP页面在引用的方法为:

action="">

用Servlet把URL映射到操作类

示例:ActionServlet类   /WEB-INF/classes/ ActionServlet

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import actions.Action;
import actions.ActionRouter;
import actions.ActionFactory;

public class ActionServlet extends HttpServlet
{
private ActionFactory factory=new ActionFactory();
public void init(ServletConfig config) throws ServletException
{
/*根据ActionRouter类导读内容中对HttpServlet类的描述来看,HttpServlet规定了必须执行的方法,该时期调用了init()方法,当Servlet被Servlet引擎载入后,接下来就会执行init()这个方法,因此我们可以重载这个方法以做一些我们自己的初始化的工作。在Servlet的生命期中,init()方法仅在服务器装入Servlet时被执行一次,此后无论有多少客户机访问这个Servlet,init()都不会被重复执行。*/
}

public void service(HttpServletRequest req, HttpServletResponse res)
throws java.io.IOException, ServletException
{
/*在Servlet被载入后,主要通过service()方法对外响应,该方法可以被同时、多次地呼叫。*/
try
{
Action action=factory.getAction(getClassname(req),getClass().getClassLoader());
ActionRouter router=action.perform(this,req,res);
router.route(this,req,res);
}
catch(Exception e)
{
throw new ServletException(e);
}
}

public String getClassname(HttpServletRequest req)
{
String Path=req.getServletPath();
int beginPos=Path.lastIndexOf("/");
int endPos=Path.lastIndexOf(".");
if (beginPos>-1 && endPos>beginPos)
{
Path=Path.substring(beginPos+1,endPos);
}
return Path;
}
}

该类的service方法实现了:从操作库中获取操作,然后调用接口的perform方法。由perform的实现返回一个操作路径,最后由ActionRouter类的route方法进行重定向操作。达到页面跳转的作用。

导读:getClassname(req)将获取操作类名,过程如下:

由req.getServletPath()获取servlet的路径为/ actions.LoginAction.do通过截取得到类名:actions.LoginAction。

getClass().getClassLoader():getClass()方法是类的一个方法,主要用于返回一个类型为Class的对象。该例中返回为:class  ActionServlet

GetClassLoader()是Class类的一个方法,返回为ClassLoader对象。本例返回为:sun.misc.Launcher$AppClassLoader@92e78c。这说明加载ActionServlet类的类的加载者(classLoader的翻译)为AppClassLoader.那么AppClassLoader又是什么呢?下面我们来解答该问题:

首先要明确java虚拟机上所有的类,必须要加载才能运行。JVM在运行时会产生三个ClassLoader, 它们分别是Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader:

ClassLoader      作    用

Bootstrap ClassLoader 加载核心类库static const char classpathFormat[] ="%/lib/rt.jar:""%/lib/i18n.jar:""%/lib/sunrsasign.jar:""%/lib/jsse.jar:""%/lib/jce.jar:""%/lib/charsets.jar:""%/classes";这里我们可看到为什么在classpath里为什么不加载这些类

Extension ClassLoader 加载扩展类,即/lib/ext中的类。

AppClassLoader 加载Classpath中指定的类。

从上面可以看出,所有web应用程序的类都是AppClassLoader来加载的,三者的关系为:AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是AppClassLoader。ClassLoader这种加载类的模型被称为是委托模型。

下面我们要了解的问题是动态加载类原基本原理?或者说为什么我们要这么做?
测试页面testmvc.jsp[/b:d01b749036]示例:
/testmvc.jsp

Pleasa Login

用户名:
密 码:

测试页面:welcome.jsp
示例: /welcome.jsp

you are welcome


说明:这里我偷了个懒,该页面只写这句话。

BEAN:USER类

示例: /WEB-INF/Classes/beans/User.java

package bean;

public class User implements java.io.Serializable
{
private final String userName,password,hint;
public User(String userName,String password,String hint)
{
this.userName=userName;
this.password=password;
this.hint=hint;
}

public String getUserName()
{
return userName;
}

public String getPassWord()
{
return password;
}

public String getHint()
{
return hint;
}

public boolean equals(String uname,String pwd)
{
return getUserName().equals(uname)&& getPassWord().equals(pwd);
}
}

该类表示了一个用户,并提供了一个equals的方法,当用户名和口令匹配的时候,返回true值。

BEAN:LoginDB类

示例: /WEB-INF/Classes/beans/User.java

package bean;

import java.util.Iterator;
import java.util.Vector;
import java.io.*;

public class LoginDB implements Serializable
{
private Vector users=new Vector();

public void addUser(String uname,String pwd,String hint)//添加用户的方法
{
users.add(new User(uname,pwd,hint));
}

public User getUser(String uname,String pwd)//检索用户的方法
{
Iterator it=users.iterator();
User bean=null;
synchronized (users){
while(it.hasNext())
{
bean=(User)it.next();
if (bean.equals(uname,pwd))
return bean;
}
}
return null;
}

public String getHint(String uname)//对指定的用户提供返回口令提示的方法
{
Iterator it=users.iterator();
User bean=null;
synchronized (users)
{
while(it.hasNext())
{
if (bean.getUserName().equals(uname))
return bean.getHint();
}
}
return null;
}
}

LoginServlet类:

示例: /WEB-INF/Classes/LoginServlet.java

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.*;
import bean.*;

public class LoginServlet extends HttpServlet
{
private LoginDB loginDB;
public void init(ServletConfig config) throws ServletException
{
loginDB=new LoginDB();
}

public void service(HttpServletRequest req, HttpServletResponse res)
throws java.io.IOException, ServletException
{
loginDB.addUser("long","long","long");
User user=loginDB.getUser(req.getParameter("userName"),req.getParameter("PassWord"));
System.out.println("The name of loginDB is"+loginDB.getClass().getName());
//String user=req.getParameter("userName");
//System.out.println("get user name:"+user);
/*getServletContext().getRequestDispatcher(res.encodeURL("/index.jsp")).forward(req,res);*/
/*要注意getServletContext()和req两个对象的区别,经过实验应用getServletContext()进行重定向*/
/*总是不行,而应用req则可以*/
if (user!=null)
{
req.getRequestDispatcher(res.encodeURL("/welcome.jsp")).forward(req,res);
}
else
{
req.getRequestDispatcher(res.encodeURL("/adduser.jsp")).forward(req,res);
}
}
}

当testmvc.jsp的表单提交时,请求被发送到登录的Servlet,这段代码我没什么好说的,在使用mvc构架之前,我们喜欢用隐藏帧来处理表单提交的内容,实际LoginServlet.java就是替代了隐藏帧而已。

到此为止,请读者将所有的示例,按示例所示的路径存储好所有的类和jsp页面。下一步我们将讨论这个东东的玩法和原理。

对了,忘了告诉大家,我的测试环境是:win2000server,tomcat 4.1,jdk1.4,没有数据库

没有评论:

发表评论