5.1MVC设计模式简介
1.什么是MVC模式
MVC模式(Model-View-Controller)是软件工程中常见的一种软件架构模式,该模式把软件系统(项目)分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
2.MVC模式的优势
①简化后期对项目的修改、扩展等维护操作;
②使项目的某一部分变得可以重复利用;
③使项目的结构更加直观。
3.每部分的功能
MVC模式可以将项目划分为模型(M)、视图(V)和控制器(C)三个部分,并赋予各个部分不同的功能,方便开发人员进行分组:
a.视图(View):
负责界面的显示,以及与用户的交互功能。例如表单、网页等。
b.控制器(Controller):
可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型。
实际开发中,通常用控制器对客户端的请求数据进行封装(如将form表单发来的若干个表单字段值,封装到一个实体对象中),然后调用某一个模型来处理此请求,最后再转发请求(或重定向)到视图(或另一个控制器)。
c.模型(Model):
模型持有所有的数据、状态和程序逻辑。模型接受视图数据的请求,并返回最终的处理结果。
实际开发中,通常用封装数据的JavaBean和封装业务的JavaBean来实现模型层。
4.MVC模式的执行流程
浏览器通过视图向控制器发出请求–>
控制器接收到请求之后选择模型进行处理–>
模型处理完请求以后再转发到控制器–>
控制器再转发到视图界面的渲染并做出最终响应
在MVC模式中,视图View可以用JSP/HTML/CSS实现,模型model可以用Java Bean实现,而控制器Control就可以用Servlet来实现。
5.2 Servlet
Servlet是基于Java技术的Web组件,运行在服务器端,由Servlet容器所管理,用于生成动态网页的内容。Servlet是一个符合特定规范的Java程序,编写一个Servlet,实际上就是按照Servlet的规范编写一个Java类,Servlet主要用于处理客户端请求并做出响应。
在绝大多数的网络应用中,客户端都是通过HTTP协议来访问服务器端资源。这就要求我们编写的Servlet要适用于HTTP协议的请求和响应。我们本章讲解的Servlet,实际就是在讲解HttpServlet的相关类。
1.开发第一个Servlet程序
如果要开发一个能够处理HTTP协议的控制器Servlet,就必须要继承javax.servlet.http.HttpServlet
,并重写HttpServlet类里的doGet()方法或doPost()方法,用来处理客户端发来的get请求或post请求,方法简介如下,
方法 | 简介 |
---|---|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException | 处理get方式的请求(如表单中,method=”get”;或超链接的请求方式) |
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException | 处理post方式的请求(如表单中,method=”post”) |
doGet()和doPost()方法中的参数HttpServletRequest 对象req和HttpServletResponse 对象resp,就等价于JSP中的内置对象request和response。换句话说,JSP中的内置对象request,实际就是HttpServletRequest类型的对象;JSP内置对象response,实际就是HttpServletResponse类型的对象。因此,在doGet()和doPost()方法中,分别用req和resp处理请求和响应。
接下来新建一个Web项目,用来开发第一个Servlet程序。先建一个名为ServletProject的Web Project,并将Dynamic web module version选择为4.0,如图
之后再将该项目部署到eclipse中的tomcat里,并在WebContent下建index.jsp,代码如下:
<body>
<form action="WelcomeServlet" method="post">
<input type="submit" value="提交">
</form>
</body>
需要注意form表单的提交地址是WelcomeServlet,以及提交方式为post。
接下来,再在src下新建一个WelcomeServlet类继承javax.servlet.http.HttpServlet,并重写HttpServlet的doGet()及doPost()方法,如下代码,
public class WelcomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.print("doGet()");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.print("doPost()");
}
}
如果现在就执行index.jsp中的submit按钮,是无法通过action跳转到WelcomeServlet的。因为,在Dynamic web module version选择为2.5的情况下,要想成功的实现从JSP(或其他Servlet)跳转到某一个特定的Servlet,必须要在web.xml中的标签里,加入一些servlet配置,具体如下:
<servlet>
<!-- servlet名字和servlet-mapping名字要一致 -->
<servlet-name>welcome</servlet-name>
<!-- 全类名 -->
<servlet-class>com.lee.servlet.WelcomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- servlet名字和servlet-mapping名字要一致 -->
<servlet-name>welcome</servlet-name>
<!-- 一定要加斜杠,否则服务起不来 -->
<url-pattern>/WelcomeServlet</url-pattern>
</servlet-mapping>
需要注意,每次修改web.xml后,都必须重新启动tomcat服务。
具体的流程是:当用户点击index.jsp中的提交按钮后,程序发现action请求地址是“WelcomeServlet”,然后就会在web.xml中<servlet-mapping>
内的<url-pattern>
里寻找“WelcomeServlet”。如果匹配成功,就会根据<servlet-mapping>
中的<servlet-name>
值“welcome”,再去寻找<servlet>
中的<servlet-name>
值。如果仍然寻找成功,就会去执行<servlet>
中的<servlet-class>
里面的Servlet实现类(如com.lee.servlet.WelcomeServlet)。最后再根据请求方式,来决定执行Servlet实现类中的doGet()或doPost()方法。
运行index.jsp,并点击“提交”按钮,得到结果:
以上就是用纯手写的方式开发的第一个Servlet程序,对于初学者来说可能稍微复杂一些,但上述的原理是必须要搞清楚的。此外,我们还可以借助于Eclipse来帮助我们快速的开发Servlet程序。
补充:解决在Eclipse 左键+Ctrl看不了HttpServlet源代码的方法
如果点Ctrl+鼠标左键,想看HTTPServlet源码,出现下图,说明没导源码包
导源码包方法:
在 Tomcat官方网站 中下载对应的Tomcat的源码,选择你对应的tomacat版本:
点击change attached source 导入即可:
2.使用Eclipse快速开发Servlet程序
用Eclipse开发Servlet,会比手工方式方便很多。我们接下来就用Eclipse来开发一个Servlet(需要确保项目的Dynamic web module version为2.5),步骤如下:
①新建一个index.jsp
Index.jsp
<form action="WelcomeServlet" method="post" >
<input type="submit" value="提交" />
</form>
②再在src下,直接创建一个servlet(不再是创建class):鼠标右键src→new→Servlet→ 填入任意的Class name如图,
点击Finish之后,我们就会得到一个已经继承了HttpServlet,并重写了doGet()和doPost()方法的类(即Servlet)。我们将注释等无关代码删除之后,得到如以下代码,
WelcomeServlet.java
public class WelcomeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public WelcomeServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
上面代码中的serialVersionUID,读者暂时不用理会,可以直接先将其删除。
再观察一下web.xml,会发现Eclipse也已经为我们自动生成了<servlet>
和<servlet-mapping>
的相关配置。
也就是说,如果用Eclipse创建一个servlet,就会得到一个已经继承了HttpServlet,并重写了doGet()和doPost()方法的类,并且自动完成了web.xml的配置。因此,使用Eclipose开发Servlet,可以提高我们的开发效率。
以上就是项目的Dynamic web module version为2.5时,开发servlet程序的方法及步骤。
3.Servlet3.0简介
接下来再重新创建一个Web项目,此次将Dynamic web module version选为3.0,如图,
创建index.jsp,
index.jsp
<form action="WelcomeServlet30" method="post" >
<input type="submit" value="提交" />
</form>
这次再尝试通过Eclipse来创建一个名为WelcomeServlet30的servlet,会得到如下代码,
WelcomeServlet30.java
@WebServlet("/WelcomeServlet30")
public class WelcomeServlet30 extends HttpServlet {
private static final long serialVersionUID = 1L;
public WelcomeServlet30() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
仔细观察上面代码,会发现本次用Dynamic web module version3.0开发的servlet,比之前用Dynamic web module version为2.5开发的servlet多了一句@WebServlet(“/WelcomeServlet30WithEclipse”)
再观察web.xml,会发现web.xml中,并没有像之前那样自动生成<servlet>
和<servlet-mapping>
的配置。
然后启动服务,直接运行index.jsp,点击提交后,也能得到正确的结果。
Servlet3.0和Servlet2.5的区别
(Dynamic web module version3.0与Dynamic web module version2.5的区别):
Servlet3.0及以上不用在web.xml中配置servlet,而是直接使用@WebServlet在创建的Servlet类名前加上映射路径(相当于之前web.xml中的<url-pattern>
),如@WebServlet(“/WelcomeServlet30”)。
Servlet2.5需要配置web.xml中的servlet和servlet-mapping。
4.Servlet生命周期
Servlet是运行在服务器端的一段程序,所以Servlet的生命周期会受Servlet容器的控制。
Servlet生命周期包括加载、初始化、服务、销毁、卸载等5个部分。
通常情况,加载和卸载阶段可以由Servlet容器来处理,我们只需要关注初始化、服务、销毁三个阶段。与Servlet生命周期相关的方法,如下表,
方法 | 简介 |
---|---|
public void init() throws ServletException | Servlet初始化时调用 |
public void init(ServletConfig config) throws ServletException | init() 的重载方法,Servlet初始化时调用,并可以通过config来读取配置信息 |
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; | 提供Servlet服务的方法。此方法是抽象方法,故实际使用的是此抽象方法的实现方法doGet()或doPost()来处理get或post请求 |
public void destroy() | Servlet销毁时调用 |
①初始化
当一个Servlet被加载完毕并实例化以后,Servlet容器将调用init()方法初始化这个对象,执行一些初始化的工作,如读取资源配置信息等。如果初始化阶段发生错误,此Servlet实例将被容器直接卸载。
注意:
若init()和init(ServletConfig config)同时存在,则只加载init(ServletConfig
config)。
②服务
初始化完成以后,Servlet就会去调用service()的具体实现方法doGet()或doPost(),来处理请求;并通过ServletRequest类型的参数接收客户端的请求,以及通过ServletResponse类型的参数处理响应信息。
③销毁
Servlet实例服务完毕以后,就可以通过destroy()方法来指明哪些资源可以被系统回收(注意destroy()方法只是“指明”需要被回收的方法,并不会直接进行回收)。
下面我们通过一个例子,来看一下Servlet生命周期的执行流程:
@WebServlet("/LifeCycleServlet")
public class LifeCycleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void init() throws ServletException {
System.out.println("init()");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet(HttpServletRequest request, HttpServletResponse response)");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doPost(HttpServletRequest request, HttpServletResponse response)");
}
@Override
public void destroy() {
System.out.println("destroy()");
}
}
前面讲过,通过浏览器的地址栏访问服务器,属于GET方式的请求。现在直接访问 http://localhost:8080/ServletProject30/LifeCycleServlet ,第一次访问时,运行结果:
在执行两次后,运行结果:
关闭服务器(注意是在Servers面板中点击红色的关闭按钮,而不是在Console控制台中),可以发现Servlet容器确实执行了destroy()方法,运行结果:
不难发现,在Servlet生命周期中,初始化init()
方法只在第一次访问时执行一次;而doGet()
或doPost()
方法会在服务器每次接收请求时,都执行一次;销毁destroy()
方法只会在关闭服务时执行一次。
5.load-on-start-up
初始化方法init()默认会在客户端第一次调用servlet服务(即调用doGet()或doPost())时执行,但也可以通过配置(servlet2.5通过 web.xml配置;servlet3.0通过注解配置),让初始化init()方法在Tomcat容器启动时自动执行。具体的配置方法如下:
①如果使用servlet2.5:
在web.xml中的标签中加入,如下:
<servlet>
<servlet-name>WelcomeServlet</servlet-name>
<servlet-class>com.lee.servlet.WelcomeServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
② 如果使用servlet3.0:
在@WebServlet中加入loadOnStart属性,如下:
@WebServlet(value="/LifeCycleServlet",loadOnStartup=1)
public class LifeCycleServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("LifeCycleServlet初始化啦!");
}
@Override
public void destroy() {
System.out.println("LifeCycleServlet销毁化啦!");
}
}
配置完毕后,再次启动Tomcat服务,可以看到init()方法会在tomcat启动时自动执行,如图
扩展:表示如果有多个servlet同时配置了loadOnStartp,servlet的启动顺序是怎么样的?
答案:loadOnStartp的值越小,越先启动。
演示一下:
WelcomeServlet30.java
设置loadOnStartup=0
@WebServlet(value="/WelcomeServlet30",loadOnStartup=0)
public class WelcomeServlet30 extends HttpServlet {
@Override
public void destroy() {
System.out.println("welcome销毁!");
}
@Override
public void init() throws ServletException {
System.out.println("welcome初始化!");
}
}
LifeCycleServlet.java
设置loadOnStartup=1
@WebServlet(value="/LifeCycleServlet",loadOnStartup=1)
public class LifeCycleServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("LifeCycleServlet初始化啦!");
}
@Override
public void destroy() {
System.out.println("LifeCycleServlet销毁化啦!");
}
}
启动Tomcat服务,运行结果
关闭服务器(注意是在Servers面板中点击红色的关闭按钮,而不是在Console控制台中),可以发现Servlet容器执行了destroy()方法的顺序,运行结果:
结论:
①servlet会按照loadOnStartup的值得大小决定加载顺序,loadOnStartup越小越先加载。
②先加载的servlet先销毁。