Servlet 详解
Servlet 核心接口和类
在 Servlet 体系结构中,除了实现 Servlet 接口,还可以通过继承 GenericServlet 或者 HttpServlet 类完成编写
Servlet 接口
在 Servlet API 中最重要的是 Servlet 接口,搜索 Servlet 都会直接或者间接与该接口发生联系
该接口有以下五个方法:
- void init(ServletConfig servletConfig)
- ServletConfig getServletConfig()
- void service(ServletRequest servletRequest, ServletResponse servletResponse)
- String getServletInfo()
- void destroy()
主要用到的方法是 service 方法
GenericServlet
GenericServlet 使编写 Servlet 变得更加容易,它提供了对 Servlet 接口方法的简单实现,只需重写 service 方法即可
例子:
public class GenServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("my first genericServlet");
}
}
HttpServlet
HttpServlet 继承自 GenericServlet,是 GenericServlet 的进一步扩展
它是一个抽象类,其子类必须至少重写以下方法之一:
- doGet,用于 HTTP Get 请求
- doPost,用于 HTTP Post 请求
- doPut,用于 HTTP Put 请求
- doDelete,用于 HTTP Delete 请求
Servlet 两种创建方式
实现 Servlet 接口
该方式比较麻烦,需要实现 Servlet 接口的所有方法
public class MyServlet 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 {
System.out.println("my first servlet web project");
System.out.println(new Date());
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
继承 HttpServlet 抽象类(推荐)
常见错误
Servlet 两种配置方式
使用 web.xml 配置文件
其中 url-pattern 的定义方式有:
load-on-startup 表示对应 servlet 加载的时机
使用注解(Servlet 3.0 之后可以使用)
在 Servelet 实现类上方加上注解
@WebServlet
常用属性如下
Servlet 应用
request 对象
在 Servlet 中处理客户端的 GET/POST 请求的具体内容都包含在 request 对象中
get 和 post 的区别
get 请求
- get 提交的数据会放在 URL 之后,以 ? 分割 URL 和传输数据,参数之间通过 & 相连
- get 方式明文传递,数据量小,不安全
- 效率高,浏览器默认请求方式就是 GET
- 对应 Servlet 方法是 doGet
post 请求
- post 方法把提交的数据放在 HTTP 包的 Body 中
- 密文传输,数据量大,安全
- 效率不如 get
- 对应 Servlet 方法是 doPost
request 主要方法
request 应用
- 编写注册页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>
<form action="/servletDemo1_war_exploded/rs" method="get">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="注册"/>
</form>
</body>
</html>
- 编写对应的 Servlet 类
@WebServlet(value = "/rs")
public class RegisterServlet extends HttpsServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取用户请求发送的数据
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("提交的数据:" + username + "\t" + password);
}
}
- 测试
运行项目,输入路径
http://localhost:8080/servletDemo1_war_exploded/register.html
填写表单,输出结果为
同时,浏览器的路径变为
get 中文乱码问题
解决方案:
post 中文乱码问题
response 对象
response 对象用于响应客户端请求并向客户端输出信息
主要方法
案例
在上一个应用加上以下内容
然后提交 post 请求,输出结果为
输出结果为乱码,这是因为
中文乱码解决
- 设置服务端响应的编码格式
- 设置客户端响应内容的头内容的文件类型以及编码格式
resp.setCharacterEncoding("utf-8"); // 设置服务器编码方式
resp.setHeader("Content-Type", "text/html;charset=utf-8"); // 设置客户端解析方式
上面的方式要复杂一点,还有更简洁的方式可以同时设置服务端的编码格式和客户端响应的文件类型及响应时的编码格式
resp.setContentType("text/html;charset=utf-8");
Servlet + JDBC
- 创建数据库
- 添加 jar 包到 lib 文件夹中,add as library
- 创建配置文件
- 创建实体层、dao 层、service 层、utils 工具类
utils 工具类
public class DbUtils {
private static DruidDataSource dataSource;
private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();
// 初始化
static {
Properties properties = new Properties();
InputStream inputStream = DbUtils.class.getResourceAsStream("/database.properties");
try {
properties.load(inputStream);
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取当前数据库连接
public static Connection getConnection() {
Connection connection = THREAD_LOCAL.get();
try {
if (connection == null) {
connection = dataSource.getConnection();
THREAD_LOCAL.set(connection);
}
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
// 开启事务
public static void begin() {
Connection connection = null;
try {
connection = getConnection();
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
// 提交事务
public static void commit() {
Connection connection = null;
try {
connection = getConnection();
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
closeALl(connection, null, null);
}
}
// 事务回滚
public static void rollback() {
Connection connection = null;
try {
connection = getConnection();
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
closeALl(connection, null, null);
}
}
// 关闭连接
public static void closeALl(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
THREAD_LOCAL.remove();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
实体层,主要为数据库表对应属性以及属性对应的 set 和 get 方法以及构造器
dao 层接口和实现类
public interface UserDao {
public int insert(User user);
public int delete(String username);
public int update(User user);
public User select(String username);
public List<User> selectAll();
}
public class UserDaoImpl implements UserDao {
private QueryRunner queryRunner = new QueryRunner();
@Override
public int insert(User user) {
return 0;
}
@Override
public int delete(String username) {
return 0;
}
@Override
public int update(User user) {
return 0;
}
@Override
public User select(String username) {
String sql = "select * from t_user where username = ?;";
try {
User user = queryRunner.query(DbUtils.getConnection(), sql, new BeanHandler<>(User.class), username);
return user;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public List<User> selectAll() {
String sql = "select * from t_user;";
try {
List<User> list = queryRunner.query(DbUtils.getConnection(), sql, new BeanListHandler<>(User.class));
return list;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
service 层接口和实现类
public interface UserService {
public User login(String username, String password);
public List<User> showAllUser();
}
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public User login(String username, String password) {
User res = null;
try {
DbUtils.begin();
User user = userDao.select(username);
if (user != null) {
if (user.getPassword().equals(password)) {
res = user;
}
}
DbUtils.commit();
} catch (Exception e) {
DbUtils.rollback();
e.printStackTrace();
}
return res;
}
@Override
public List<User> showAllUser() {
List<User> res = null;
try {
DbUtils.begin();
res = userDao.selectAll();
DbUtils.commit();
} catch (Exception e) {
DbUtils.rollback();
e.printStackTrace();
}
return res;
}
}
- 创建 servlet 类
显示所有用户的 servlet 类
@WebServlet(value = "/showall")
public class ShowAllUserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
UserService userService = new UserServiceImpl();
List<User> users = userService.showAllUser();
PrintWriter writer = resp.getWriter();
if (users != null) {
// 响应给客户端结果页面
writer.println("<html>");
writer.println("<head>");
writer.println("<meta charset='UTF-8'>");
writer.println("<title>结果页面</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("<table>");
writer.println(" <tr>");
writer.println(" <td>username</td>");
writer.println(" <td>password</td>");
writer.println(" <td>phone</td>");
writer.println(" <td>address</td>");
writer.println(" </tr>");
for (User user : users) {
writer.println(" <tr>");
writer.println(" <td>" + user.getUsername() + "</td>");
writer.println(" <td>" + user.getPassword() + "</td>");
writer.println(" <td>" + user.getPhone() + "</td>");
writer.println(" <td>" + user.getAddress() + "</td>");
writer.println(" </tr>");
}
writer.println("</table>");
writer.println("</body>");
writer.println("</html>");
} else {
// 响应给客户端结果页面
writer.println("<html>");
writer.println("<head>");
writer.println("<meta charset='UTF-8'>");
writer.println("<title>结果页面</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("<h3>当前没有用户!</h3>");
writer.println("</body>");
writer.println("</html>");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
在刚刚的 Servlet 中,业务逻辑和效果显示在同一个类中,就会产生设计问题,不符合单一职能原则,也不利于维护
应开进行分离
于是可以将刚刚的 ShowAllUserServlet 分为
-
ShowAllUserController 用来处理业务逻辑
-
ShowAllAdminJSP 用来显示页面(代码过多不展示)
不过这时候会出现问题
- 如何从业务逻辑 Servlet 跳转到显示页面 Servlet
- 显示页面要展示的数据在业务逻辑 Servlet 中,那么显示页面 Servlet 如何获取数据?
这时候就可以使用转发解决第一个问题
转发
转发用在服务器端,将请求发送给服务器端的其他资源,以共同完成一次请求的处理
可以通过以下代码实现页面跳转
req.getRequestDispatcher(url).forward(req, resp);
其中 url 表示要转发的路径
注意,forward 跳转是服务器内部跳转
数据传递
forward 表示一次请求,是在服务器内部跳转,可以共享同一次 request 作用域中的数据
-
request 作用域:拥有存储数据的容器,作用范围是该次请求内有效(一次请求可以多次转发)。所以可以将数据存入 request,然后在请求过程中的任意位置进行获取
-
存数据
request.setAttribute(key, value);
- 取数据
request.getAttribute(key);
于是,之前的 Servlet 为
转发特点
重定向
可以在 Servlet 中通过以下方法实现重定向
response.sendRedirect(URI);
数据传递
使用重定向时,地址栏发生改变,属于两次请求
可以看到重定向只能传递字符串类型数据