java高级技术》课程 java ee (2) - pkusei.pku.edu.cn/~lige/java_e2.pdf ·...
TRANSCRIPT
Web Applications
A web application is a dynamic extension of a web or application server. There are two types of web applications: Presentation-oriented:
●generates interactive web pages containing various types of markup language (HTML, XML, and so on) and dynamic content in response to requests.
Service-oriented: ●A service-oriented web application implements the
endpoint of a web service.
Presentation-oriented applications are often clients of service-oriented web applications.
Servlets & JSP
Servlets vs. JSP pages Servlets are best suited for service-oriented
applications and the control functions of a presentation-oriented application, such as dispatching requests and handling data.
JSP pages are more appropriate for generating text-based markup such as HTML, Scalable Vector Graphics (SVG), Wireless Markup Language (WML), and XML.
What Is a Servlet?
Servlets are Java programming language classes that dynamically process requests and construct responses. Java Servlet components are built to handle business-
specific requests and generate responses via Web.
Servlet 是Java语言编写的运行在服务器端的小应
用程序,它能够接收Web客户端的请求,并能够
对Web客户端进行响应。
Servlet是服务器端的Applet
Servlet生命周期 四阶段生命周期
加载:在服务器启动或第一次被访问时,Web Container加载Servlet;
初始化:加载后,调用构造方法进行实例化,然后调用init()方法初始化;
执行服务:Web Container接受到请求时调用Servlet的service()方法,执行服务;
销毁:在服务器关闭或接到销毁指令时调用destroy()方法执行收尾工作;
说明:
所有请求共享一个Servlet对象,共同使用同一对象的Service()方法;
Servlet生命周期服务器加载Servlet
服务器创建Servlet类的实例
调用Servlet实例的init()方法
收到请求
调用service()方法
service()方法处理请求并将结果返回客户端
Servlet等待下一个请求或由服务器卸载
Servlet在服务器调用destroy()方法后被卸载
Servlet
每个Servlet必须实现javax.servlet.Servlet接口
Defines methods that all servlets must implement. defines
●methods to initialize a servlet
●methods to service requests
●methods to remove a servlet from the server.
provides the getServletConfig method To get any startup information, To return basic information about itself
javax.servlet.Servlet void destroy( )
Called by the servlet container to indicate to a servlet that the servlet is being taken out of service.
void init(ServletConfig config) Called by the servlet container to indicate to a servlet that the
servlet is being placed into service. void service(ServletRequest req, ServletResponse res)
Called by the servlet container to allow the servlet to respond to a request.
ServletConfig getServletConfig( ) Returns a ServletConfig object, which contains initialization and
startup parameters for this servlet. String getServletInfo( )
Returns information about the servlet, such as author, version, and copyright.
Request & Response in Servlet ServletRequest
收到请求时,Web Container将请求信息封装在实现
ServletRequest接口的对象中传送给Servlet 。public class ServletRequestWrapper
extends Object implements ServletRequest ServletResponse
Web Container使用实现ServletResponse接口的对象接收
service()方法的响应,转换为输出。
public class ServletResponseWrapperextends Object implements ServletResponse
关于请求 ServletRequest
ServletRequest接口提供了对请求数据的访问方法,用于
获得客户端发送的请求中的参数;
● Enumeration getAttributeNames()
提供了getReader()方法读取字符数据
● BufferedReader getReader() throws IOException
● public class BufferedReader extends Reader
提供了getInputStream()方法读取字节数据
● ServletInputStream getInputStream() throws IOException
● public abstract class ServletInputStream extends InputStream
关于响应 ServletResponse
ServletResponse接口提供了对响应数据进行访问的方法,实现对
响应的操作。
●setCharacterEncoding(String charset)
提供了getWriter()方法,返回Writer对象,用于将字符数据返回
给用户;
●PrintWriter getWriter()
提供了getOutputStream()方法,返回ServletOutputStream对象,用
于返回二进制数据;
● ServletOutputStream getOutputStream() throws IOException
在发送响应后关闭Writer或ServletOutputStream可让服务器知道何
时完成响应。
Servlet Classes
ServletInputStreampublic abstract class ServletInputStream
extends InputStream提供输入流用于读取来自客户端的请求的二进制数据的类。它包括方法readLine(),以此有效实现每次读取一行数据。
ServletOutputStreampublic abstract class ServletOutputStream
extends OutputStream运行方式与ServletInputStream相同,它提供输出流用于将二进制数据发送给客户端。
ServletConfig in Servlet
ServletConfig (Container塞给Servlet的指挥棒)
用于定义Servlet的配置对象。Web Container通过它
将任何信息传递给Servlet,以便初始化Servlet。
ServletContext(Container塞给Servlet的指挥棒)
定义了一组方法,Servlet使用这些方法与Servlet引擎
进行通信。
SingleThreadModel
此接口确保每次Servlet只能处理一个请求。
public interface ServletConfig
A servlet configuration object used by a servlet containerto pass information to a servlet during initialization. String getInitParameter(String name)
●Returns a String containing the value of the named initialization parameter, or null if the parameter does not exist.
Enumeration getInitParameterNames() ●Returns the names of the servlet's initialization parameters as
an Enumeration of String objects, or an empty Enumeration if the servlet has no initialization parameters.
ServletContext getServletContext() ●Returns a reference to the ServletContext in which the caller is
executing. String getServletName()
●Returns the name of this servlet instance.
Servlet
每个Servlet必须实现javax.servlet.Servlet接口
大部分Servlet通过扩展以下其中一个特殊类来实现这一接口:
GenericServlet●这是大部分非HTTPServlet扩展的类。此类定义通知、跨协议的Servlet。
HttpServlet●javax.servlet.http包
●增加了对特定于HTTP的功能的支持;
javax.servlet.http.HttpServlet
Provides an abstract class to be subclassed to create an HTTP
servlet suitable for a Web site.
HttpServlet的生命周期 初始化HttpServlet
void init(ServletConfig config) ●Called by the servlet container to indicate to a servlet
that the servlet is being placed into service. 服务器调用HttpServlet(继承)的init()方法,向新HttpServlet提供
任何有关其本身及其环境的信息;
ServletConfig对象向Servlet提供其初始化参数信息;
ServletConfig对象含有对ServletContext对象的引用,Servlet可以使用此对象完成初始化。
可以覆盖init()方法,但必须遵循下列规则:
●如果发生初始化错误,致使HttpServlet不能处理客户端请求,则抛出UnavailableException;
●不要调用System.exit()方法;
HttpServlet生命周期
客户端请求的产生
Web Container收到客户端的HTTP请求时,
将创建以下两个接口类型的对象:
●HttpServletRequest [interface]
HttpServletRequestWrapper [class]
●HttpServletResponse [interface]
HttpServletResponseWrapper [class]
并将该对象传递给service()方法;
HttpServlet生命周期
HttpServlet类的service()方法的作用
service()方法根据HttpServletRequestWrapper中所包
含的http动作类型,进行请求分发;
HttpServlet能够并发地为多个客户端服务。Web Container收到的任何请求都将被转发至Servlet的service()方法。
根据接收的Http请求类型,service()方法再将请求转
发给doGet()、doPost()、doDelete()、doOptions()、doPut()和doTrace()方法进行处理。
HttpServlet生命周期
销毁HttpServlet
要卸载HttpServlet时, Web Container将调用
HttpServlet的destroy()方法;
在destroy()方法中,HttpServlet应该释放任何
已获得的资源;
destroy()方法还使HttpServlet有机会写出其未
保存的高速缓存信息或任何在下次调用init()时应该读取的持久性信息。
Other Classes @ javax.servlet.http
Cookiepublic class Cookie extends Object implements Cloneable Cookie类可以创建Cookie ,Cookie存储了少量由Servlet发送至客
户端的信息。客户端可在稍后要访问以前访问过的网页时使用已
存储的信息。
ServletExceptionpublic class ServletException extends Exception 此异常是常规异常,由Servlet在执行过程中遇到困难时抛出。
UnavailableExceptionpublic class UnavailableException extends ServletException 此异常定义的是由Servlet抛出以表示Servlet永久或临时不可用的
异常。
import javax.servlet.*;import javax.servlet.http.*;import java.io.*;public class FirstServlet extends HttpServlet{
private static final String CONTENT_TYPE="text/html; charset=gb2312";public void doGet(HttpServletRequest request,HttpServletResponse response)
throws IOException,ServletException{
response.setContentType(CONTENT_TYPE);PrintWriter out = response.getWriter();out.println("<html>");out.println("<head><title>第一个Servlet测试程序</title></head>");out.println("<body bgcolor=\"#ffffff\">");out.println("<p>第一个Servlet测试程序</p>");out.println("</body></html>");
}public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
doGet(request, response);}
}
简单Servlet示例1
import javax.servlet.*;import javax.servlet.http.*;import java.io.*;import java.util.*;public class Login extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=GBK";//Initialize global variablespublic void init() throws ServletException { }//Process the HTTP Get requestpublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置MIME类型
response.setContentType(CONTENT_TYPE);//获取用户输入的信息
String username = request.getParameter("username");String userpass = request.getParameter("userpass");//对用户名进行编码转换
username = new String(username.getBytes("8859_1"));//得到输出流
PrintWriter out = response.getWriter();
简单Servlet示例2
PrintWriter out = response.getWriter();//对用户进行响应if(username.equals(userpass)&&username.length()!=0){
out.println("<html>");out.println("<head><title>登陆</title></head>");out.println("<body bgcolor=\"#ffffff\">");out.println("<p>登陆成功</p>");out.println("</body></html>");HttpSession session = request.getSession(true);session.setAttribute("user",username);
}else{
response.sendRedirect("login.jsp");}
}//Process the HTTP Post requestpublic void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);}//Clean up resourcespublic void destroy() { }
}
简单Servlet示例2
Request & Response @ javax.servlet.http
HttpServletRequestpublic interface HttpServletRequest
extends ServletRequest该接口定义用于处理HttpServletRequestWrapper的方法。实现该接口的对象被传递给Servlet的service()方法。
HttpServletResponsepublic interface HttpServletResponse
extends ServletResponse该接口定义服务器对HttpServletResponseWrapper进行处理的方法。实现该接口的对象将被传递至Servlet的service()方法。
HttpSession in Request
HttpServletRequestHttpSession getSession()
●Returns the current Httpsession associated with this request, or if the request does not have a session, creates one.
HttpSession getSession(boolean create) ●Returns the current HttpSession associated with
this request or, if there is no current session and create is true, returns a new session.
Session@ javax.servlet.http
HttpSessionpublic interface HttpSession
用于标识Web Container与用户之间的会话;当Web站点的请求或访
问超过一个页面时,可以用Session来标识用户及其状态。
Web Container需要实现HttpSession接口,利用该接口,Web
Container可以存储会话、跟踪会话
●Servlet可通过HttpSession接口来查看会话的相关信息,如会话标识符、创建时间和环境。
●Web Container 可以通过HttpSession接口从会话获得数据,并可以将修改后的数据返回给会话。
Request & Response @ javax.servlet.http
HttpServletRequestWrapperpublic class HttpServletRequestWrapper
extends ServletRequestWrapper implements HttpServletRequest
提供HttpServletRequest所定义的方法实现,并对
ServletRequestWrapper对象进行包装处理。
HttpServletResponseWrapper
public class HttpServletResponseWrapper
extends ServletResponseWrapper implements HttpServletResponse
提供HttpServletResponse所定义的方法实现,并对
ServletResponseWrapper对象进行包装处理。
import javax.servlet.*;import javax.servlet.http.*;import java.io.*;public class ShowBuy extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, java.io.IOException {
String[] item = { "糖果", "收音机", "练习簿" };// 获取会话对象HttpSession session = req.getSession(true);// 获取选择的商品数目Integer itemCount = (Integer) session.getAttribute("itemCount");// 如果没放入商品则数目为0if (itemCount == null) { itemCount = new Integer(0); }String itemName;String[] itemsSelected = req.getParameterValues("item");// 将选中的商品放入会话对象if (itemsSelected != null) {
for (int i = 0; i < (itemsSelected.length); i++) {itemName = itemsSelected[i];itemCount = new Integer(itemCount.intValue() + 1);session.setAttribute("Item" + itemCount, itemName);// 将商品名称定义为ItemXsession.setAttribute("itemCount", itemCount);// 将商品数量放入会话对象
}}
购物车物品列表
// Set the content type of the responseres.setContentType("text/html;charset=gb2312");PrintWriter out = res.getWriter();}// Write the page headerout.println("<html>");out.println("<head>");out.println("<title>购物袋的内容</title>");out.println("</head>");out.println("<body>");out.println("<center><h1>你放在购物袋中的商品是:</h1></center>");// 将购物袋的内容写入页面
for (int i = 1; i < itemCount.intValue(); i++) {String itemtitle = (String) session.getAttribute("Item" + i);// 取出商品名称
out.println(item[Integer.parseInt(itemtitle)]);out.println("<BR>");
}// Wrap upout.println("</body>");out.println("</html>");out.close();
}}
购物车物品列表
监视 Servlet
如何监视 Servlet 的状态? ServletRequestListener [interface]
●实现本接口的监听器用于监听并处理ServletRequest到达和离开的事件。
ServletRequestAttributeListener [interface]●实现本接口的监听器用于监听并处理ServletRequest属性被改变的事件。
ServletContextListener [interface]●实现本接口的监听器用于监听并处理Servlet应用启动和销毁的事件。
ServletContextAttributeListener [interface]●实现本接口的监听器用于监听并处理Servlet属性改变的事件,包括:增加属性、删除属性、修改属性等。
监视 Servlet
ServletRequestListenerpublic interface ServletRequestListener
extends EventListener{ void requestDestroyed(ServletRequestEvent sre)
●The request is about to go out of scope of the web application.
void requestInitialized(ServletRequestEvent sre) ●The request is about to come into scope of the web
application.
} A ServletRequestListener can be implemented by the
developer interested in being notified of requests coming in and out of scope in a web component.
监视 Servlet
ServletRequestEventpublic class ServletRequestEvent
extends java.util.EventObject{ ServletContext getServletContext()
●Returns the ServletContext of this web application.
ServletRequest getServletRequest() ●Returns the ServletRequest that is changing.
} Events of this kind indicate lifecycle events for a
ServletRequest. The source of the event is the ServletContext of this web application.
Listeners @ javax.servlet.http
HttpSessionListener实现该接口的监听器用于监听并处理Session对象的创建和销毁的事件;
HttpSessionActivationListener实现该接口的监听器用于监听并处理Session对象的激活/休眠事件;
HttpSessionAttributeListener实现该接口的监听器用于监听并处理Session对象属性被改变的事件;
HttpSessionBindingListener实现该接口的监听器用于监听并处理对象绑定至会话上或解除会话绑定的事件;
Listeners @ javax.servlet.http
HttpSessionListenerpublic interface HttpSessionListener
extends EventListener{ void sessionCreated(HttpSessionEvent se)
●Notification that a session was created.
void sessionDestroyed(HttpSessionEvent se) ●Notification that a session is about to be invalidated.
} Implementations of this interface are notified of
changes to the list of active sessions in a web application.
Listeners @ javax.servlet.http
HttpSessionEventThis is the class representing event notifications
for changes to sessions within a web application
public class HttpSessionEvent
extends EventObject{ HttpSession getSession()
●Return the session that changed.
}
import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;
public class OnlineCounterListener implements HttpSessionListener {public void sessionCreated(HttpSessionEvent hse) {
OnlineCounter.raise();}
public void sessionDestroyed(HttpSessionEvent hse) {OnlineCounter.reduce();
}}class OnlineCounter {
private static long online = 0;
public static long getOnline() {return online;
}
public static void raise() {online++;
}
public static void reduce() {online--;
}}
网站在线人数统计
会话跟踪问题 问题
HTTP没有为服务器提供识别请求序列是否来自同一
客户端的方法;
HTTP服务器不能通过连接机器的IP地址来标识客户
端;(因为报告的IP地址可能是代理服务器的地址或
是有多个用户的服务器地址)
解决方案
客户端任何时候做出新请求都必须进行自我介绍。
每个客户端都必须提供一个唯一标识以便服务器可识
别它,或者客户端必须提供某些信息以便服务器可用
于正确处理请求。
会话跟踪问题 在客户端登录后,用户名即通过HttpServletRequest接
口的getRemoteUser()提供给Servlet,用于标识服务器端的Session,同时浏览器可以保留用户名,并在用户查看站点的新页面时提交用户名和口令。Servlet通过用户名标识找到对应的Session,以此跟踪用户。
优点:
对应关系明确,容易实现;
即使用户从不同机器访问站点,Session仍然有效;
缺点:
要求每个用户注册一个独立帐户,且在每次访问站点时登录。
用户在同一站点上不能同时维护多个会话。
客户端信息存储
Cookie
Cookie是Web服务器发送至浏览器,以后可从
该浏览器上读回的少量信息;
浏览器收到Cookie后,在每次访问服务器上的
页面时都会将其发送回服务器;
由于Cookie的值可以方便地标识客户端,因此
Cookie通常用于会话跟踪。
服务器端会话跟踪 会话跟踪技术
Web服务器为站点上的每个用户创建Session对象来维
护用户状态,这些Session对象存储在服务器中,并在
服务器中进行维护;
在用户向站点做出请求时,用户就分配有一个新的
Session对象和唯一的Session ID, Session ID在后续的
请求中会将用户与Session对象进行匹配;
经过匹配的Session对象作为请求的一部分被传递至处
理请求的Servlet。 Servlet可以在Session中添加信息或读取其中的信息以
进行验证。
import javax.servlet.*;import javax.servlet.http.*;import java.io.*;import java.util.*;public class AddCookieServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=GBK";//Initialize global variablespublic void init() throws ServletException { }//Process the HTTP Get requestpublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {response.setContentType(CONTENT_TYPE);String name = request.getParameter("cookiename");String value = request.getParameter("cookievalue");Cookie cc = new Cookie(name, value);response.addCookie(cc);PrintWriter out = response.getWriter();
out.println("<html>");out.println("<head><title>AddCookieServlet</title></head>");out.println("<body bgcolor=\"#ffffff\">");out.println("<p>添加了一个新的Cookie.<a href=\"cookieServlet\">察看</a></p>");out.println("</body></html>");
}//Process the HTTP Post requestpublic void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{ doGet(request, response) }
//Clean up resourcespublic void destroy() { }
}
简单Servlet示例3添加Cookie
public class CookieServlet extends HttpServlet {private static final String CONTENT_TYPE = "text/html; charset=GBK";//Initialize global variablespublic void init() throws ServletException {}//Process the HTTP Get requestpublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{response.setContentType(CONTENT_TYPE);PrintWriter out = response.getWriter();out.println("<html>");out.println("<head><title>CookieServlet</title></head>");out.println("<body bgcolor=\"#ffffff\">");Cookie cookies[] = request.getCookies();for(int i=0;i<cookies.length;i++){
out.println(cookies[i].getName());out.println("\t");out.println(cookies[i].getValue());out.println("<br>");
}out.println("</body></html>");
}//Process the HTTP Post requestpublic void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {doGet(request, response);
}//Clean up resourcespublic void destroy() {}
}
简单Servlet示例3读取Cookie
会话交换与持久 Internet站点必须支持多个有效会话
访问一个站点的用户可能有许多个,站点必须支持多个会
话访问;
●大型站点可能同时有几百甚至几千个有效会话
由于每个会话都可以包含应用程序Servlet的任意数据对象,
所以整个系统的内存要求就很高。
session.invalidationinterval属性
该属性用于设置会话的失效时间;
如果会话的未使用时间超过正常有效时间,它就会无效,
不管会话是在内存还是在磁盘;
会话交换与持久 session.maxresidents属性的作用
内存中可以存在的会话数受到会话跟踪系统的限制;
当同时进行的会话的数量超过session.maxresidents设置的限制时,会话跟踪器会将最近最不常用的会话交换到磁盘。如果收到请求,会话将被重新载入内存。
session.swapdirectory属性
在启用了会话持久功能时,会话跟踪器将使用会话交换机制实现会话持久。
服务器关闭时,内存中的会话即被写入session.swapdirectory属性中指定的磁盘。
一旦启动服务器,写入磁盘的会话就变得有效。这就使服务器在重启时不会丢失任何现有会话。
隐藏表单字段 隐藏表单字段
指添加到HTML表单中,但不在客户端的浏览器中显示的字段。
在提交包含字段的表单时,字段将被发送至服务器。
优点
其功能强大并支持匿名。所有常用浏览器都支持隐藏字段,而且它们可用于未登录或注册的客户端。
缺点
只适用于采用动态生成表单的网页。在遇到静态文档、以电子邮件发送的文档、设计为书签的文档和浏览器关闭时,该技术失效。
URL重写 URL重写技术
在用户单击URL时将URL重写,以包括特定的附加信
息,利用该信息实现与特定Session的对应;
附加信息的形式可为附加路径信息、添加参数或某些
特定于服务器的自定义URL修改。
不同重写技术有不同特点
附加路径信息适用于所有服务器,但如果Servlet必须
使用附加路径信息作为实际路径信息时,则不适用。
添加参数适用于所有服务器,但在参数作为POST方法的目标时,可能引起参数命名冲突,方法失效。
不具有保密性,容易被盗用;
JSP
JSP——Java Server Pages是一种用于创建动态网页的Java服务器端编程技术,是
一个用于创建response报文的文档;
JSP被翻译成Servlet后执行,与Servlet共同构成J2EE的表
示层;
●Servlet用Java语言生成HTML页面;
●JSP在静态的HTML页面中嵌入了Java代码,可以在JSP中使用 Java 编程语言和类库;因而便于人们开发;
将内容与表示分离:
●HTML 用于表示页面,而 Java 代码用于访问动态内容;
JSP 页面元素 JSP 页面的元素
静态内容
表达式
Scriptlet <% Java 代码 %>
<%=Java表达式 %>
HTML静态文本
声 明
动作
<%! 函数或方法 %>
以“<jsp: 动作名 ” 开始,以“</jsp:动作名> ” 结束比如:<jsp:include page=" Filename" />
注释<!-- 这是注释,但客户端可以查看到 --><%-- 这也是注释,但客户端不能查看到 --%>
指 令以“<%@ ” 开始,以“%> ” 结束。比如:<%@ include file = " Filename" %>
JavaBean
JavaBean是符合下面设计规则的 Java 类 :
(1)JavaBean是一个public类;
(2)必须有一个无参数的构造函数;
(3)对于数据类型为“protype”的每个可读属
性Protperty,有如下的一个方法:
public proptype getProperty() { }(4)对于数据类型为“protype”的每个可写属
性Protperty ,有如下的一个方法:
public setProperty(proptype x) { }
import java.io.*;public class SimpleBean{
public SimpleBean(){}private String name;private String id;public void setName(String name){
this.name = name;}
public void setId(String id){this.id = id;
}
public String getName( ){return name;
}
public String getId( ){return id;
}}
简单JavaBean
<%@ page contentType="text/html;charset=GB2312" %><html><head><title>CH6 - SimpleBean.jsp</title></head><body><h1>JSP 使用 JavaBean 的方法</h1><jsp:useBean id="myBean" scope="page" class="SimpleBean"/><jsp:setProperty name="myBean" property="name" value="tyy" /><jsp:setProperty name="myBean" property="id" value="123"/>Hi !<font color="blue"><jsp:getProperty name="myBean"
property="name" /></font>您好<br>您的编号为:<font color="blue"><jsp:getProperty name="myBean"
property="id" /></font></body></html>
简单JSP+JavaBean
JSP+Servlet+JavaBean
浏
览
器
(控制器)Servlet
(视图)JSP
(模型)JavaBean、
EJB…
应用程序服务器
数据库
1.请求
6.响应
4.
5.
2.实例化
3.
JSP、Servlet与JavaBean结合
Servlet负责处理用户请求(控制器,创建JSP用的JavaBean,根据请求选择JSP返回)
JSP检索Servlet创建的JavaBean,从Servlet中提取内容插入静态模板,负责显示。
Struts是一个优秀的、基于MVC的Web应用框架