JSP的本质就是Servlet,开发者把编写好的JSP也main部署在Web容器中后,Web容器会将JSP编译成对应的Servlet。但直接使用Servlet的坏处是:Servlet的开发效率很低,特别是当使用Servlet生成表现层页面时,页面中所有的HTML标签,都需采用Servlet输出流来输出页面的开发。这一系列的问题,都阻碍了Servlet作为表现层的使用。
自MVC规范出现后,Servlet的责任开始明确下来,仅仅作为控制器使用,不再需要生成页面标签,也不再作为视图层角色使用。
1.Servlet的开发
Servlet通常被称为服务器端小程序,是运行在服务器端的程序,用于处理及响应客户端的请求。
Servlet是个特殊的Java类,这个Java类必须继承HttpServlet。每个Servlet可以响应哭都断的请求。Servlet提供不同的方法用于相应客户端请求。
- doGet:用于响应客户端的GET请求
- doPost:用于响应客户端的POST请求
- doPut:用于响应客户端的PUT请求
- doDelete:用于响应客户端的DELETE请求
事实上,客户端的请求通常只有GET和POST两种,Servlet为了响应这两种请求,必须重写doGet()和doPost()方法。如果为了响应4个方式的请求,则四个方法都要重写。
大部分时候,Servlet对于所有请求的响应都是一样的。此时,可以采用重写一个方法来代替上面的几个方法:只需重写service()方法即可响应客户端的所有请求。
另外,HttpServlet还包含两个方法。
- init(ServletConfig config):创建Servlet实例时,调用该方法的初始化Servlet资源。
- destroy():销毁Servlet实例时,自动调用该方法回收资源。
通常无须重写init()和destroy()两个方法,除非需要在初始化Servlet时,完成某些资源初始化的方法,才考虑重写init方法。如果需要在销毁Servlet之前,先完成某些资源的回收,比如关闭数据库连接等,才需要重写destroy方法。
注意
不用为Servlet类编写构造器,如需要对Servlet执行初始化操作,应将初始化操作放在Servlet的init()方法中定义。而且,如果重写了init(ServletConfig config)方法,则应在重写该方法的第一行调用super.init(config)。即调用HttpServlet的init方法。
下面提供一个Servlet示例,该Servlet将获取表单请求参数,并将请求参数显示给客户端。
//Servlet必须继承HttpServlet类
@WebServlet(name="firstServlet"
, urlPatterns={"/firstServlet"})
public class FirstServlet extends HttpServlet
{
//客户端的响应方法,使用该方法可以响应客户端所有类型的请求
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,java.io.IOException
{
//设置解码方式
request.setCharacterEncoding("GBK");
response.setContentType("text/html;charSet=GBK");
//获取name的请求参数值
String name = request.getParameter("name");
//获取gender的请求参数值
String gender = request.getParameter("gender");
//获取color的请求参数值
String[] color = request.getParameterValues("color");
//获取country的请求参数值
String national = request.getParameter("country");
//获取页面输出流
PrintStream out = new PrintStream(response.getOutputStream());
//输出HTML页面标签
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet测试</title>");
out.println("</head>");
out.println("<body>");
//输出请求参数的值:name
out.println("您的名字:" + name + "<hr/>");
//输出请求参数的值:gender
out.println("您的性别:" + gender + "<hr/>");
//输出请求参数的值:color
out.println("您喜欢的颜色:");
for(String c : color)
{
out.println(c + " ");
}
out.println("<hr/>");
out.println("您喜欢的颜色:");
//输出请求参数的值:national
out.println("您来自的国家:" + national + "<hr/>");
out.println("</body>");
out.println("</html>");
}
}
上面的Servlet类继承了HttpServlet类,表明他可以作为一个Servlet使用。其中的service方法用来响应用户请求。该Servlet和之前的九个内置对象那篇中的request1.jsp页面的效果完全相同,都通过HttpServletRequest获取客户端的form请求参数,并显示请求参数的值。
JSP是Servlet的一种简化,使用JSP只需要输出到客户端的内容,至于JSP脚本如何嵌入一个类中,有JSP容器完成。而Servlet则是一个完整的Java类,这个类的service()方法用于响应客户端的请求。 普通Servlet类里的service()方法的作用,等同于JSP生成Servlet类的_jspService()方法。因此原JSP页面的JSP脚本、静态HTML内容,在普通Servlet里都应该转换成service()方法的代码或输出语句。原JSP声明中的内容,对应为在Servlet中定义的成员变量或成员方法。Servlet和JSP的区别在于
1.Servlet中没有内置对象,原来JSP中的内置对象都必须由程序显示创建
2.对于静态的HTML标签,Servlet都必须使用页面输出流逐行输出。
提示
上面Servlet类中的@WebServlet属于Servlet3.0的Annotation
2.Servlet的配置
编辑好的Servlet源文件并不能响应应用请求,还必须将其编译成class文件。江边以后的FirstServlet.class文件放到WEB-INF/classes路径下,如果Servlet有包,还应该将class文件放在对应的包路径下(如:WEB-INF/classes/com.lee)。为了让Servlet能相应用户请求,还必须将Servlet配置在Web应用中。配置Servlet时,需要修改web.xml文件。注意
如果需要直接采用javac命令来编译Servlet类,则必须将Servlet API接口和类添加到系统的CLASSPATH环境变量里。即Tomcat安装目录下lib目录中的servlet-api.jar和jsp-api.jar添加到CLASSPATH环境变量中。
上面开发Servlet类时使用了@WebServlet Annotation修饰该Servlet类,使用@WebServlet时可指定以下属性:从Servlet3.0开始,配置Servlet有两种方式:
- 在Servlet类中使用WebServlet Annotation进行配置
- 通过在web.xml文件中进行配置
属性 | 是否必需 | 说明 |
---|---|---|
asyncSupported | 否 | 指定该Servlet是否支持异步操作模式 |
displayName | 否 | 指定该Servlet的显示名 |
initParams | 否 | 用于为该Servlet配置参数 |
loadOnStartup | 否 | 用于将该Servlet配置成load-on-startup的Servlet |
name | 否 | 指定该Servlet的名称 |
urlPatterns/value | 否 | 这两个属性的作用完全相同,douzhidinggaiServlet处理的URL |
**如果打算使用Annotation来配置,注意以下两点:** - 不要在web.xml文件的根元素`
<!-- 配置Servlet的名字 -->
<servlet>
<!-- 指定Servlet的名字,
相当于指定@WebServlet的name属性 -->
<servlet-name>firstServlet</servlet-name>
<!-- 指定Servlet的实现类 -->
<servlet-class>lee.FirstServlet</servlet-class>
</servlet>
<!-- 配置Servlet的URL -->
<servlet-mapping>
<!-- 指定Servlet的名字 -->
<servlet-name>firstServlet</servlet-name>
<!-- 指定Servlet映射的URL地址,
相当于指定@WebServlet的urlPatterns属性-->
<url-pattern>/aa</url-pattern>
</servlet-mapping>
上面指定了该Servlet的URL为/aa。如果在Servlet类上使用WEbServlet Annotation照样起作用。以下是运行结果图: ![这里写图片描述](https://img-blog.csdn.net/20170922002943841?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxNDIxMjk4MQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) form.jsp请求后,URL显示的是**aa**。在这种情况下,Servlet与JSP的作用效果完全相同。
3.JSP/Servlet的生命周期
JSP的本质就是Servlet,开发者编写的JSP页面将由Web容器编译成对应的Servlet,当Servlet在容器中运行时,其实例的创建和销毁等都不是由程序员决定的,而是由Web容器进行控制的。创建Servlet实例有两个时机:
1. 客户端第一次请求某个Servlet时,系统创建该Servlet的实例:大部分的Servlet都是这种方式创建。
2. Web应用启动时立即创建Servlet实例,即使用load-on-startup配置
每个Servlet的运行都遵循如下生命周期:
1. 创建Servlet实例。
2. Web容器调用Servlet的init方法,对Servlet进行初始化
3. Servlet初始化后,将一直存在于容器中,用于响应客户端请求。如果客户端发送GET请求,容器调用Servlet的doGet方法处理并响应请求;如果客户端发送POST请求,容器调用Servlet的doPost方法处理并响应请求。或者统一使用service()放啊处理来响应用户请求。
4. Web容器决定销毁Servlet时,先调用Servlet的destroy方法,通常在关闭Web应用之时销毁Servlet。
4.load-on-startup Servlet
创建Servlet实例有两个时机:用户请求之时或应用启动之时。应用启动时就创建Servlet,通常是用于某些后台服务的Servlet,或者需要拦截很多请求的Servlet:这种Servlet通常作为应用的基础Servlet使用,提供重要的后台服务。 配置load-on-starup的Servlet有两种方式:- 在web.xml文件中通过
<servlet.../>
元素的<load-on-startup.../>
子元素进行配置。 - 通过@WebServlet Annotation的loadOnStartup属性 指定。
<load-on-startup.../>
元素或loadOnStartup属性都只接受一个整型值,该整型值越小,优先级别就越高,Servlet就越优先实例化。
下面是一个简单的Servlet,该Servlet不响应用户请求,仅仅实现计时器功能,每隔一段时间在控制台打印出当前时间。
@WebServlet(loadOnStartup=1)
public class TimerServlet extends HttpServlet
{
public void init(ServletConfig config)throws ServletException
{
super.init(config);
Timer t = new Timer(1000,new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println(new Date());
}
});
t.start();
}
}
这个Servlet没有提供service()方法,这表明它不能响应用户请求,所以无须为它配置URL映射。由于他不能接收用户请求,所以只能在应用启动时实例化。
以上程序中Annotation即可将该Servlet配置了load-on-startup Servlet。除此之外,还可以在web.xm这里写代码片段如下:
<servlet>
<!-- Servlet名 -->
<servlet-name>timerServlet</servlet-name>
<!-- Servlet的实现类 -->
<servlet-class>lee.TimerServlet</servlet-class>
<!-- 配置应用启动时,创建Servlet实例,相当于指定@WebServlet的loadOnStartup属性-->
<load-on-startup>1</load-on-startup>
</servlet>
以上配置片段中,制定了Web应用启动时,Web容器将会实例化该Servlet,且该Servlet不能响应用户请求,将一直作为后台如无执行:每隔1秒输出一次系统时间。
5.访问Servlet的配置参数
配置Servlet时,还可以增加额外的配置参数。通过使用配置参数,可以实现提供更好的可移植性,避免将参数以硬编码方式写在代码中。
为Servlet配置参数有两种方式:
- 通过@WebServlet的initParams属性来指定。
- 通过在web.xml文件的
<servlet.../>
元素中添加<init-param.../>
子元素来指定。
第二种方式与为JSP配置初始化参数极其相似,因为JSP的实质就是Servlet,而且配置JSP的实质就是把JSP当Servlet使用。
访问Servlet配置参数通过ServletConfig对象完成,ServletConfig提供如下方法。
- java.lang.String getInitParameter(java.lang.String name):用于获取初始化参数。
注意
JSP的内置对象config就是此处的ServletConfig
下面的Servlet将会连接数据库,并执行SQL查询,通过Annotation和配置web.xml两种方式来配置连接数据库的信息。
ServletConfig获取配置参数的方法和ServletContext获取配置参数的方法完全一样,只是ServletConfig是取得当前Servlet的配置参数,而ServletContext是获取整个Web应用的配置参数。
@WebServlet(name="testServlet"
, urlPatterns={"/testServlet"}
, initParams={
@WebInitParam(name="driver", value="com.mysql.jdbc.Driver"),
@WebInitParam(name="url", value="jdbc:mysql://localhost:3306/javaee"),
@WebInitParam(name="user", value="root"),
@WebInitParam(name="pass", value="1234")})
public class TestServlet extends HttpServlet
{
}
以上程序中粗体字@WebServlet中的initParams属性用于为该Servlet配置参数,initParams属性值的每个@WebInitParam配置一个初始化参数,每个@WebInitParam可指定如下两个属性。
- param-name : 指定配置参数名
- param-value : 指定配置参数值
下面是Servlet在web.xml文件中的配置片段
<servlet>
<!-- 配置Servlet名 -->
<servlet-name>testServlet</servlet-name>
<!-- 指定Servlet的实现类 -->
<servlet-class>lee.TestServlet</servlet-class>
<!-- 配置Servlet的初始化参数:driver -->
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</init-param>
<!-- 配置Servlet的初始化参数:url -->
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/javaee</param-value>
</init-param>
<!-- 配置Servlet的初始化参数:user -->
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<!-- 配置Servlet的初始化参数:pass -->
<init-param>
<param-name>pass</param-name>
<param-value>lzc717LZC</param-value>
</init-param>
</servlet>
运行效果如下:
6.使用Servlet作为控制器
使用Servlet作为表现层的工作量太大,所有的HTML标签都需要使用页面输出流生成。因此,使用Servlet作为表现层有如下劣势:
- 开发效率地,所有的HTML标签都需要使用页面输出流完成。
- 不利于团队协作开发,美工人员无法参与Servlet界面的开发。
- 程序可维护性查,及时修改一个按钮的标题,都必须重新编辑代码,并重新编译。
在标准的MVC模式中,Servlet仅作为控制器使用。Java EE应用架构正式遵循MVC模式的,对于遵循MVC模式的Java EE应用而言,JSP仅作为表现层(View)技术,其作用有以下两点:
- 负责收集用户请求参数
- 将应用的处理结果、状态数据呈现给用户
Servlet则仅充当控制器(Controller)角色,所有业务逻辑、数据访问逻辑都在Model中实现。实际上Model下还可以有很多丰富的组件,例如DAO组件、领域对象等。
下面介绍一个使用Servlet作为控制器的MVC应用,来演示一个简单的登陆验证。
下面是登陆页面代码:
<%
if (request.getAttribute("err") != null)
{
out.println(request.getAttribute("err") + "<br/>");
}
%>
</span>
请输入用户名和密码:
<!-- 登录表单,该表单提交到一个Servlet -->
<form id="login" method="post" action="login">
用户名:<input type="text" name="username"/><br/>
密  码:<input type="password" name="pass"/><br/>
<input type="submit" value="登录"/><br/>
</form>
运行效果如下:
上面的if判断是用来响应request输出错误提示,该页面其实是一个简单的表单页,用于收集用户名及密码,并将请求提交到制定Servlet,该Servlet充当控制器角色。
注意
根据严格的MVC规范,上面的login.jsp页面也不应该被客户端直接访问,客户的请求应先发送到指定Servlet,然后由Servlet将请求forward到该JSP页面。
控制器Servlet的代码如下:
@WebServlet(name="login"
, urlPatterns={"/login"})
public class LoginServlet extends HttpServlet
{
//响应客户端请求的方法
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,java.io.IOException
{
String errMsg = "";
//Servlet本身并不输出响应到客户端,因此必须将请求转发
RequestDispatcher rd;
//获取请求参数
String username = request.getParameter("username");
String pass = request.getParameter("pass");
try
{
//Servlet本身,并不执行任何的业务逻辑处理,它调用JavaBean处理用户请求
DbDao dd = new DbDao("com.mysql.jdbc.Driver",
"jdbc:mysql://localhost:3306/javaee","root","1234");
//查询结果集
ResultSet rs = dd.query("select pass from user "
+ "where name = ?", username);
if (rs.next())
{
//用户名和密码匹配
if (rs.getString("pass").equals(pass))
{
//获取session对象
HttpSession session = request.getSession(true);
//设置session属性,跟踪用户会话状态
session.setAttribute("name" , username);
//获取转发对象
rd = request.getRequestDispatcher("/welcome.jsp");
//转发请求
rd.forward(request,response);
}
else
{
//用户名和密码不匹配时
errMsg += "您的用户名密码不符合,请重新输入";
}
}
else
{
//用户名不存在时
errMsg += "您的用户名不存在,请先注册";
}
}
catch (Exception e)
{
e.printStackTrace();
}
//如果出错,转发到重新登录
if (errMsg != null && !errMsg.equals(""))
{
rd = request.getRequestDispatcher("/login.jsp");
request.setAttribute("err" , errMsg);
rd.forward(request,response);
}
}
}
控制器负责接收客户端的请求,它既不直接对客户端输出响应,也不处理用户请求,值调用JavaBean来处理用户请求;JavaBean处理及术后,Servlet根据处理结果,调用不同的JSP页面向浏览器呈现处理结果。
上面Servlet使用@WebServlet Annotation为该Servlet配置了URL为/login,因此向/login发送的请求将会交给该Servlet处理。
下面是DbDao主要代码:
public class DbDao
{
private Connection conn;
private String driver;
private String url;
private String username;
private String pass;
public DbDao()
{
}
public DbDao(String driver , String url
, String username , String pass)
{
this.driver = driver;
this.url = url;
this.username = username;
this.pass = pass;
}
//省略成员属性的setter和getter方法...
//获取数据库连接
public Connection getConnection() throws Exception
{
if (conn == null)
{
Class.forName(this.driver);
conn = DriverManager.getConnection(url,username,
this.pass);
}
return conn;
}
//插入记录
public boolean insert(String sql , Object... args)
throws Exception
{
PreparedStatement pstmt = getConnection().prepareStatement(sql);
for (int i = 0; i < args.length ; i++ )
{
pstmt.setObject( i + 1 , args[i]);
}
if (pstmt.executeUpdate() != 1)
{
return false;
}
pstmt.close();
return true;
}
//执行查询
public ResultSet query(String sql , Object... args)
throws Exception
{
PreparedStatement pstmt = getConnection().prepareStatement(sql);
for (int i = 0; i < args.length ; i++ )
{
pstmt.setObject( i + 1 , args[i]);
}
return pstmt.executeQuery();
}
//执行修改
public void modify(String sql , Object... args)
throws Exception
{
PreparedStatement pstmt = getConnection().prepareStatement(sql);
for (int i = 0; i < args.length ; i++ )
{
pstmt.setObject( i + 1 , args[i]);
}
pstmt.executeUpdate();
pstmt.close();
}
//关闭数据库连接的方法
public void closeConn()
throws Exception
{
if (conn != null && !conn.isClosed())
{
conn.close();
}
}
}
下面是MVC中各个角色的对应组件
M: Model,即模型,对应JavaBean
V:View,即视图,对应JSP页面
C:Controller,即控制器,对应Servlet