1.一些回顾
对于Tomcat部署中 我们有一些补充的点需要在此说明一下
1.如果我们想要查询MINEType的话 可以到TOMCAT_HOME/conf/web.xml中进行查询 里面记录了不同类型对应的MINEType
2.我们客户端发送请求数据给服务器之后 服务器会调用父类中的service方法 然后在内部决定调用doGet还是doPost方法 我们也可以重写service方法自定义service的内部逻辑
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/hello3/test" method="get">
<div>账号 <input type="text" name="username"></div>
<div>密码 <input type="text" name="password"></div>
<div><button type="submit">登录</button></div>
</form>
</body>
</html>
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this + "_service");
}
}
3.通过response获取的字符输出流PrintWriter不需要手动关闭 当service调用完毕以后就会自动关闭该输出流
4.alt+d启动tomcat之后的默认操作中 常用的有两个:update classes and resourses、redeploy 前者主要为针对只修改的servlet进行重新加载到项目的操作 而后者则为针对添加成员的servlet进行重新加载到项目的操作
2.Servlet
所谓Servlet 其实就是server(服务) applet(小程序)的简称 即小服务程序的意思
当一个请求数据发送到服务器之后 交由一个servlet处理 他会调用其中的service方法 然后在决定要调用doGet还是doPost方法
默认情况下 一个servlet类 只会被服务器创建一个实例对象 而且是发生在第一次处理客户端的请求数据时(不同的对象哈希值对应着不同的对象 我们可以通过对象哈希值的角度来验证一下这个结论)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
public TestServlet(){
System.out.println(this + "_构造方法");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this + "_service");
}
}
以下是第一次发送数据以后的结果
接着我们修改了service的代码 然后重新加载到项目中 查看哈希值是否改变
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
public TestServlet(){
System.out.println(this + "_构造方法");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this + "_service123");
}
}
从结果中就可以看出 哈希值并没有改变 说明两次发送数据用的对象是同一个对象 而且对象创建的时机是在第一次接收客户端请求数据之时(我可以通过内存层面解释一下这个问题:修改代码修改的是代码区中的内容 而对象存放的位置是堆空间 修改代码区并不会影响堆空间的东西 既然对象没有发生改变 那么就一直沿用即可)
但是如果我们是为servlet类增删成员而通过redeploy的方式重新加载servlet类的话 那么每一次的redeploy中服务器都会销毁旧的servlet对象 同时创建出新的servlet对象 所以如果是每一次的redeploy的话 那么就会根据同一个servlet类创建出不同的对象
1.注意
有些人可能会这么想 就是既然服务器只会根据一个servlet类创建一个实例的话 那么该servlet类采用的就是单例模式 这种想法是错误的
我们先来看一下单例模式的实现
public class Person {
// 构造方法私有化
private Person(){}
// 提供一个私有静态的对象 静态是为了确保程序运行过程中只有一份内存 私有则是为了不被外界访问
private static Person instance = new Person();
// 提供一个共有静态的获取实例方法 静态是为了保证通过类访问
public static Person getInstance(){
return instance;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = Person.getInstance();
Person p2 = Person.getInstance();
System.out.println(p1);// Person@1b6d3586
System.out.println(p2);// Person@1b6d3586
}
}
从结果证明 上述代码确实实现了一个单例模式 所谓单例模式就是根据一个类只能创建一个实例 我们就可以查看一下servlet类是否可以创建多个实例即可证明servlet类是否采用了单例模式
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
public TestServlet(){
System.out.println(this + "_构造方法");// TestServlet@39ac9b6d_构造方法
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
TestServlet ts1 = new TestServlet();// TestServlet@2c4c84b_构造方法
TestServlet ts2 = new TestServlet();// TestServlet@3282dd6a_构造方法
System.out.println(ts1);// TestServlet@2c4c84b
System.out.println(ts2);// TestServlet@3282dd6a
}
}
从ts1和ts2打印的结果不同来看 就可以证明servlet并非采取单例模式 而是可以根据一个该类创建出多个实例
当然 你也可以为同一个servlet类起多个不同的别名
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet({"/test1", "/test2"})
public class TestServlet extends HttpServlet {
public TestServlet(){
System.out.println(this + "_构造方法");// TestServlet@39ac9b6d_构造方法
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this + "_service");
}
}
这样 你可以通过不同的请求路径和服务器进行交互 并且如果只是修改代码重新加载的程度 服务器也只会创建一次servlet实例 而且时机为第一次接收请求数据的时候
还有一个需要注意的地方就是:不要在servlet中定义可写(可修改)的成员变量 这样会引发线程安全问题
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet({"/test1", "/test2"})
public class TestServlet extends HttpServlet {
private int age = 10;
public TestServlet(){
System.out.println(this + "_构造方法");// TestServlet@39ac9b6d_构造方法
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
age++;
}
}
如果同时有多个请求数据发送给服务器的话 并且同时执行到age++;语句 假设同时发送两次 那么实际的结果11可能和预期结果12是不一致的 这就是所谓的线程安全问题 解决的方案就是将成员变量从可写改成可读 即声明为常量即可
还有一个值得注意的地方就是:http请求的默认端口号为80 如果你的tomcat服务器端口号设置为80的话 那么客户端发送请求过程中就可以省略端口号(会自动添加) 我们重新加载的方式选择restart server 这样才能保留端口号的修改
3.crm项目
所谓crm 就是customer relationship management的简称 即用户关系管理
我们想要实现的一个项目的效果其实就是当我们点击登录 如果登录成功 就会跳转到展示用户信息的页面 如果登录失败 就会提醒我们重新登陆 并且点击可以实现跳转到登录界面的效果
而且在servlet代码中 我们需要单独封装登录成功、登录失败以及获取储存用户信息的功能
并且我们可以直接创建一个servlet类 而并非class类 该类已经有了基本的框架 包含doGet、doPost以及WebServlet等信息
我们接下来就来实现一下该项目
LoginServlet
package com.mj.servlet;
import com.mj.bean.Customer;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置请求数据的解码方式为UTF-8
request.setCharacterEncoding("UTF-8");
// 接收请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 然后设置响应数据的内容格式 由于展示的为html页面 因此MINEType为text/html 而编码方式设置为UTF-8
response.setContentType("text/html;charset=utf-8");
// 然后定义字符输出流 用于输出响应数据
PrintWriter out = response.getWriter();
// 判断 如果登录成功 执行登录成功的逻辑 登陆失败则执行登录失败的逻辑
if(username.equals("123") && password.equals("123")){
success(out);
}else{
fail(out);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
// 封装获取储存用户信息的逻辑
private List<Customer> getCustomers(){
// 定义一个list集合 用于存放用户信息
List<Customer> customers = new ArrayList<>();
// 假设我们拥有的用户信息数量为10个
for(int i = 0; i < 10; ++i){
// 性别奇数为男 偶数为女
customers.add(new Customer("张三", "10086", ((i & 1) == 1 ? "男" : "女")));
}
return customers;
}
// 封装登录成功的逻辑
private void success(PrintWriter out){
// 先展示登录成功信息
out.write("<h1>登录成功</h1>");
out.write("<table>");
out.write("<thead>");
out.write("<tr>");
out.write("<th>姓名</th>");
out.write("<th>电话</th>");
out.write("<th>年龄</th>");
out.write("</tr>");
out.write("</thead>");
out.write("<tbody>");
// 获取存放用户信息的列表
List<Customer> customers = getCustomers();
for(Customer customer: customers){
out.write("<tr>");
out.write("<td>" + customer.getName() + "</td>");
out.write("<td>" + customer.getPhone() + "</td>");
out.write("<td>" + customer.getGender() + "</td>");
out.write("</tr>");
}
out.write("</tbody>");
out.write("</table>");
}
// 封装登录失败的逻辑
private void fail(PrintWriter out){
// 先展示登录失败信息
out.write("<h1>登录失败</h1>");
// 在展示重新登录 并且跳转到登录界面
out.write("<a href=\"http://localhost:8080/crm/login.html\">重新登录</a>");
}
}
Customer
package com.mj.bean;
public class Customer {
// 定义三个私有成员变量 分别为:姓名、电话以及性别
private String name;
private String phone;
private String gender;
// 定义无参和有参的构造方法
public Customer(String name, String phone, String gender) {
this.name = name;
this.phone = phone;
this.gender = gender;
}
public Customer() {
}
// 定义每一个成员变量的getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
login
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/crm/login" method="post">
<div>账号 <input type="text" name="username"></div>
<div>密码 <input type="text" name="password"></div>
<div><button type="submit">登录</button></div>
</form>
</body>
</html>
文件架构
测试之后 可以发现 确实能够满足我们的需求 但是如果要将网页作为响应数据返回给客户端的话 那么就会出现大量可读性极差、难以维护的字符串拼接代码 而且我们的用户信息都是我们自己写死的(一般的解决思路就是将他们放置在数据库中)