前情回顾
截至目前,我们已经总结了,如何配置Tomcat服务器(到官网下载资源,将bin目录配置到Path环境变量中,增加CATALINA_HOME环境变量),将Tomcat lib目录下的servlet-api.jar添加到CLASSPATH环境变量中,这样我们就可以使用JAVA EE的servlet接口。我们还学习了如何编写一个Servlet程序,它需要实现五个接口,知道了Tomcat是通过web.xml文件将路由和servlet程序关联。
本节内容,是借助Idea来实现上述过程,并且进一步学习GenericServlet接口以及ServletConfig,ServletContext对象。
利用Idea来创建项目并配置相关环境
新建一个空项目
这里选择Empty Project,键入javaTest作为项目名称
创建新模块
这里不选择Jakarta EE, 而选择New Module
为模块引入框架支持
勾选Web Application选项,因为我们想要开发的是Web应用
现在可以看到目录结构按照Serlvet规范的目录给我们自动生成了,项目根路径就是web,但要注意的是这里不意味着我们的项目名称就是web,这里是虚拟的,后面在真正运行时可以配置项目名称。
配置Tomcat环境
这一步是配置Tomcat本地开发环境,这里因为是我之前配置过,所以直接呈现这个界面,否则的话需要点击Configure文件找到Tomcat的根路径
这里就是部署,我们新建一个模块部署
这里的Application text就是项目根目录名称,你可以自己配置
添加模块的包依赖
右击模块,点击Module Settings
添加jar包
这里加入的两个jar包,CTRL+ALT可以多选
成功以后你的External Libraries中除了jdk还可以看到你额外添加的包
编写Servlet程序
在src目录下创建需要的类
由于我们下面需要连接数据库,所以需要数据库相关的jdbc实现,那么就需要在lib目录下导入相关的包。
StudentList.java
package com.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;
public class StudentList implements Servlet{
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
servletResponse.setContentType("text/html");
PrintWriter writer = servletResponse.getWriter();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/test_db?useSSL=false";
String user = "root";
String passwd = "1234";
conn = DriverManager.getConnection(url, user, passwd);
String sql = "select * from student";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String s = "<p>编号: " + id + " 姓名: " +name + " 年龄: " + age +"</p>";
writer.write(s);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(rs != null){
try {
rs.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>wefwe</title>
</head>
<body>
<a href="/test/stu">student list</a>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>ttt</servlet-name>
<servlet-class>com.javaweb.servlet.StudentList</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ttt</servlet-name>
<url-pattern>/stu</url-pattern>
</servlet-mapping>
</web-app>
下面是效果
点击超链接以后进行跳转
探究Servlet的接口方法
GenericServlet
每次我们想编写一个实现Servlet接口的程序,都需要实现5个方法,而很多时候其实只会使用service一个方法,这使得代码变得冗长与丑陋。为了解决这个方法,我们可以利用适配器设计模式,编写一个GenericServlet,它实现了除service方法以外的四个方法,并将service作为抽象方法,以后我们不需要直接实现Servlet接口,而是直接继承GenericServlet。
package com.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
public abstract class GenericServlet implements Servlet{
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException;
@Override
public void init(ServletConfig servletConfig) throws ServletException {}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {}
}
对Servlet对象的探究
上面的GenericServlet有着其局限性,在改造它之前,我们先弄清楚几个问题。
Servlet对象的生命周期是怎样的?即谁来创建它,什么时候创建,又什么时候销毁?
作为客户端程序员而言,我们只是实现了service方法,从来没有自己去创建一个Servlet对象。事实上,Servlet对象是由Web服务器程序创建的,具体过程总结如下:
- 用户首次发送某个请求
- Tomcat创建对应的Servlet对象和ServletConfig对象(后续会探究ServletConfig对象)
- Servlet对象init方法被执行,ServletConfig对象作为参数传入
- service接口被调用
- 后续用户发送同一个请求,Servlet对象不再被创建,只是再次调用service方法
- 当服务器关闭时,destroy方法会被调用
我们可以发现有个ServletConfig对象被传入到了init方法中,所以说如果我们不去重写GenericServlet的init方法,ServletConfig就会丢失,所以对GenericServlet做如下改造
package com.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
public abstract class GenericServlet implements Servlet{
private ServletConfig config ;
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException;
@Override
public final void init(ServletConfig servletConfig) throws ServletException {
config = servletConfig;
this.init();
}
public void init(){}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {}
}
创建一个私用变量config, 在init方法中将ServletConfig对象保存在里面,并且利用getServletConfig接口将该对象返回。事实上这个getServletConfig接口存在的意义就在于此。
用户在继承GenericServlet时就不用重写init方法了,并且我们将其设置成final,用户也重写不了。那用户如果需要在init方法中执行一些初始化操作怎么办呢?可以看到提供了一个无参的init方法供用户重写,并且无参方法在有参方法中得到了执行。
GenericServlet在jakarta EE中也提供了实现,所以以后不用直接实现Servlet接口。
ServletConfig对象是什么东西?
每个Servlet对象可能需要一些初始化信息,而初始化信息可以通过ServletConfig对象提供。
它包含了如下四个方法
String getInitParameter(String name);
Enumeration<String> getInitParameterNames();
String getServletName();
ServletContext getServletContext();
解释这几个接口,那么就先得进一步介绍web.xml的其它配置,web.xml的作用不仅仅是将Servlet服务和路径绑定,还可以配置一些初始化信息。
<servlet>
<servlet-name>ttt</servlet-name>
<servlet-class>com.javaweb.servlet.StudentList</servlet-class>
<init-param>
<param-name>name</param-name>
<param-value>tes</param-value>
</init-param>
<init-param>
<param-name>message</param-name>
<param-value>mes</param-value>
</init-param>
</servlet>
通过init-param规定了初始化用的参数,一个name对应一个value
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
servletResponse.setContentType("text/html");
PrintWriter writer = servletResponse.getWriter();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
ServletConfig config = getServletConfig();
Enumeration<String> names = config.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String val = config.getInitParameter(name);
String init_param = "init_param: " + name + " init_value: " + val;
writer.write(init_param);
writer.write("<br>");
}
}
上面列举了getInitParameterNames和getInitParameter的大概用法。
ServletContext
ServletContext context = getServletContext();
context.getContextPath() //返回一个string,是项目的根路径名称
context.getRealPath(文件名) //获取项目文件的在服务器上的绝对路径
所有的servlet对象共享同一个ServletContext对象,它代表了全局配置信息,在web.xml中可以设置全局共享的初始化参数。
<context-param>
<param-name>size</param-name>
<param-value>10</param-value>
</context-param>
Enumeration<String> params = context.getInitParameterNames();
while(params.hasMoreElements()){
String param = params.nextElement();
writer.write("<p>" + context.getInitParameter(param)+ "</p>");
}
和ServletConfig对象一样有着上述的接口。