JavaWeb学习笔记

Tomcat + Servlet

浏览器请求资源:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bdU6yNl3-1688545785576)(C:\Users\cxz\Desktop\笔记图片\web-UML1.jpg)]

web机制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MFOnPTfg-1688545785577)(C:\Users\cxz\Desktop\笔记图片\web机制.jpg)]

Servlet基本使用(补充)

service方法:

/**
 * Tomcat把http请求的数据封装成实现ServletRequest接口的request对象
 * servletResponse 用于返回数据给tomcat -> 浏览器
 */
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
        throws ServletException, IOException {

}

Tomcat维护一个大HashMap<id,Servlet>

Servlet 流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KR8IaIw-1688545785577)(C:\Users\cxz\Desktop\笔记图片\Servlet流程.jpg)]

Servlet注解方式开发

@WebServlet(urlPatterns = {"/ok1", "/ok2"})
public class OkServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("doPost ----");

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("doGet -----");

    }
    
}

匹配优先级:精确路径 > 目录路径 > 扩展名路径 > / > /*

Http协议

问题:一个html中有一个图片,问访问该html页面时,浏览器一共发出几次http请求?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>图片</h1>
<img src="images/640.png" width="300px">
</body>
</html>

答案:

三次!

1.第一次请求html

2.浏览器解析该html页面,发现其中有<img src="images/640.png" width="300px">

3.浏览器会继续向服务器发请求,要照片

Get请求返回数据:

 protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //1.给回送数据设置编码
        //2.text/html告诉浏览器返回的数据是text下的html格式MIME
        response.setContentType("text/html;charset=utf-8");
        
        PrintWriter writer = response.getWriter();
        writer.print("<h1>登录成功</h1>");
    }
URL编码

url编码:Content-Type: application/x-www-form-urlencoded

%E9%99%88%E5%85%88%E6%B3%BD ----》 “陈先泽”

什么时候出现304状态码?

1.浏览器在访问资源时如果使用的是之前缓存的静态资源,就会报304状态码

2.浏览器如果访问的资源修改过了,就会访问新的更新的资源,并返回200状态码,并有Last-Modified:记录上次

修改资源时间

Last-Modified: Wed, 28 Jun 2023 02:43:10 GMT

当再次访问该静态资源时,就会使用缓存,就会返回304状态码

什么时候出现302状态码?

302状态码表示:临时性的重定向,即要访问url时,被重定向到另一个URL

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    //重定向到hi.html
    response.sendRedirect("/hi.html");
}

访问的url的相应头中会指定location,即重定向的URL:Location: /hi.html

HTTP/1.1 302
Location: /hi.html
Content-Length: 0
Date: Wed, 28 Jun 2023 02:52:21 GMT
Keep-Alive: timeout=20
Connection: keep-alive

Servlet核心

ServletConfig

配置config的初始化信息:

<servlet>
<servlet-name>DBServlet</servlet-name>
<servlet-class>com.cxz.servlet.DBServlet</servlet-class>
<!--配置信息-->
<init-param>
    <param-name>username</param-name>
    <param-value>cxz</param-value>
</init-param>
<init-param>
    <param-name>password</param-name>
    <param-value>1234567</param-value>
</init-param>

得到初始化信息:

protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    //得到ServletConfig getServletConfig:
    ServletConfig servletConfig = getServletConfig();
    //获取init-parameter中的信息:
    String username = servletConfig.getInitParameter("username");
    String password = servletConfig.getInitParameter("password");

    System.out.println("username:"+username+" ,password:"+password);

}

注意

如果要重写init()方法,如果在其他方法中想通过getServletConfig()获取ServletConfig,

一定要调用super.init(config)

@Override
public void init(ServletConfig config) throws ServletException {
    System.out.println("init:config = "+config);
    super.init(config);
}
ServletContext

如何获取ServletContext对象?

ServletContext servletContext = getServletContext();

实际是通过ServletConfig对象拿到的

ServletContext可以实现什么?

1 可以被多个Servlet共享(在一个web应用中只有一个ServletContext)

2 数据存储格式 k-v 类似map

3 可以实现多个Servlet的通信

ServletContext可以做什么?

1.获取web.xml中配置的上下文参数 context-param (是属于整个web应用的)

<!--配置整个网站的信息-->
<context-param>
    <param-name>website</param-name>
    <param-value>http://www.cxz.com</param-value>
</context-param>
<context-param>
    <param-name>company</param-name>
    <param-value>cxz</param-value>
</context-param>
String website = servletContext.getInitParameter("website");
String company = servletContext.getInitParameter("company");

2.获取工程路径 : /工程路径

String contextPath = servletContext.getContextPath();

3.获取工程部署在硬盘上的绝对路径:

String realPath = servletContext.getRealPath("/");

ServletContext实现网站计数器

public class WebUtils {

public static Integer visitCount(ServletContext servletContext){

    Object visit_count = servletContext.getAttribute("visit_count");

    if (visit_count == null){
        // 说明是第一访问
        servletContext.setAttribute("visit_count",1);
        visit_count=1;

    }else {
        //第二次及以后访问
        visit_count = Integer.parseInt(visit_count + "") + 1;
        //放回到ServletContext
        servletContext.setAttribute("visit_count",visit_count);

    }

    return Integer.parseInt(visit_count+"");

}
    
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    ServletContext servletContext = getServletContext();
    Integer count = WebUtils.visitCount(servletContext);

    response.setContentType("text/html;charset=utf-8");
    PrintWriter writer = response.getWriter();
    writer.print("<h1>网站被访问的次数是:"+count+"</h1>");
}
HttpServletRequest

是什么?

HttpServletRequest对象代表客户端的请求

常用方法:

1.获取URI

System.out.println("URI =="+request.getRequestURI());

2.获取URL

System.out.println("URL =="+request.getRequestURL());

3.获取客户端主机IP

System.out.println("ip =="+request.getRemoteAddr());

4.获取请求头

System.out.println("HOST =="+request.getHeader("HOST"));

5.获取请求参数

//解决中文乱码问题
request.setCharacterEncoding("utf-8");

System.out.println("username = " + request.getParameter("username"));
System.out.println("pwd = " + request.getParameter("pwd"));

6.获取请求参数(多个值时)

//一组数据
String[] hobbies = request.getParameterValues("hobby");
for (String hobby : hobbies) {
    System.out.println("hobby = "+hobby);
}

7.设置域数据

request.setAttribute("name",request.getParameter("username"));

8.获取域对象

request.getAttribute("name")

9.获取请求转发对象

10.获取请求方式

System.out.println("method =="+request.getMethod());

请求转发

请求转发示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-72PqxKVI-1688545785578)(C:\Users\cxz\Desktop\笔记图片\请求转发.jpg)]

需求:login ------> CheckServlet(检查提交的用户名并判断,并转发到下一个Servlet) ----------> ManagerServlet(根据转发来的request取出数据并显示)

前端请求

<form action="/cxz/check" method="post">
     u : <input type="text" name="username"><br/>
    <input type="submit" value="login">
</form>

第一个Servlet:

@WebServlet(urlPatterns = {"/check"})
public class CheckServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        System.out.println("CheckServlet~~~");
        // 注意:如果在同一个request对象,那么可以在不同的Servlet中,用getParameter取出参数
        String username = request.getParameter("username");
        if (username.equals("Tom")){
            request.setAttribute("role","管理员");
        }else{
            request.setAttribute("role","普通用户");
        }
        // 获取分发器:解析为 localhost:8080/cxz/manager
        RequestDispatcher requestDispatcher =
                request.getRequestDispatcher("/manager");
        // forward表示把当前request对象和response对象传递给下一个servlet使用
        requestDispatcher.forward(request,response);

    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req,resp);
    }
}

第二个Servlet:

@WebServlet(urlPatterns = {"/manager"})
public class ManagerServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("ManagerServlet~~");
        // 取出用户名:
        String username = request.getParameter("username");
        String role = (String) request.getAttribute("role");
        System.out.println("role -- "+request);

        //输出信息:
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("username = "+username+"<br/>");
        writer.print("role = "+(String)role);
    }
}

说明:为什么第二个Servlet能取出第一个Servlet里的参数(Parameter)和域对象(getAttribute) ?

解答:因为如果参数和域值在同一个request中,那么可以在不同的Servlet中取出,

​ 而第一个Servlet通过forward(),把自己的request和response转发到了第二个Servlet中。

​ 所以始终是同一个request对象!!

ServletResponse

什么是ServletResponse?

答:每次Http请求,Tomcat会创建一个HttpServletResponse对象传递给Servlet程序去使用

​ 表示所有响应信息。

乱码:

将服务端和客户端都设置为UTF-8

 response.setContentType("text/html;charset=utf-8");

将服务端设置为UTF-8

request.setCharacterEncoding("utf-8");

请求重定向

原理示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RGjJjM0p-1688545785578)(C:\Users\cxz\Desktop\笔记图片\请求重定向.jpg)]

示例:

前端请求:

<body>
<h1>下载文件</h1>
<a href="http://localhost:8080/cxz/down">下载文件!</a>
</body>

第一个Servlet,请求重定向至:/cxz/new:

注意:重定向地址要加项目名:/cxz/new

重定向也可以直接写资源名: response.sendRedirect(“new”);注意前面不要带斜杠!

public class DownServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 请求重定向
        // 要带上项目路径 ==> /cxz
        // 返回302 和 Location:/cxz/new 由浏览器解析,所以它不知道项目路径:/cxz
        // 浏览器解析成 :http://localhost:8080/cxz/new
        response.sendRedirect("/cxz/new");
       
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doPost(request,response);
    }
}

第二个Servlet:/cxz/new

public class DownServletNew extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType(("text/html;charset=utf-8"));
        PrintWriter writer = response.getWriter();
        writer.print("ok");
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doPost(request,response);
    }
}

注意事项:

1.重定向后最后的URL地址是重定向的最后的Servlet的URL

2.重定向过程中Request对象是不同的,因为每一次请求都是一次线程

3.无法重定向至WEB-INF目录内的文件

4.可以重定向至web项目以外的网站,如百度

5.可以重定向到web目录内的页面不过要加上 /项目名

response.sendRedirect("/cxz/Redirect.html");

Tomcat底层机制

Tomcat整体架构

整体架构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xKL18yQ0-1688545785578)(C:\Users\cxz\Desktop\笔记图片\tomcat.jpg)]

Tomcat底层实现:

第一版本的Tomcat:

Tomcat V1

/**
* 第一版Tomcat
* 完成客户端的请求并返回信息
*/
public class HspTomcatV1 {

public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(8080);
    System.out.println("======MyTomcat在8080监听=======");
    while (!serverSocket.isClosed()){

        //等待浏览器的连接
        //如果有连接过来 就创建一个socket
        //即服务器和客户端的连接
        Socket socket = serverSocket.accept();

        //接受浏览器发送的数据
        //inputStream ---> BuffedReader
        InputStream inputStream = socket.getInputStream();
        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

        String mes = null;
        System.out.println("=======接收到浏览器的发送数据==========");
        while ((mes=bufferedReader.readLine()) != null){
            // 判断mes
            if (mes.length()==0){
                break;
            }
            System.out.println(mes);
        }

        //Tomcat回送-http
        OutputStream outputStream = socket.getOutputStream();
        //构建一个http响应消息头
        String respHeader = "HTTP/1.1 200\r\n" +
                "Content-Type: text/html;charset=utf-8\r\n\r\n";
        //响应体resp:
        String resp = respHeader + "hi,CXZ";

        System.out.println("==========我们的Tomcat给浏览器回送的数据==========");
        System.out.println(resp);

        outputStream.write(resp.getBytes());

        outputStream.flush();
        outputStream.close();
        inputStream.close();
        socket.close();

    }
}
}
第二版本的Tomcat

目标:实现BIO线程模型,支持多线程:

多线程类 HspRequestHandler.java:

/**
* HspRequestHandler对象是一个线程对象
* 处理http请求的
*/
public class HspRequestHandler implements Runnable{

//定义Socket
private Socket socket = null;

public HspRequestHandler(Socket socket) {
    this.socket = socket;
}

@Override
public void run() {

    try {
        InputStream inputStream = socket.getInputStream();

        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

        // 不同线程在和浏览器交互
        System.out.println("当前线程 = "+Thread.currentThread().getName());

        System.out.println("========hspTomcatv2接收的数据如下==========");
        String msg = null;
   
        while ((msg = bufferedReader.readLine()) != null){
            if (msg.length()==0){
                break;
            }
            System.out.println(msg);
        }

        //构建一个http响应消息头
        String respHeader = "HTTP/1.1 200\r\n" +
                "Content-Type: text/html;charset=utf-8\r\n\r\n";
        String resp = respHeader + "<h1>hi CXZ v2</h1>";
        
        System.out.println("==========myTomcat V2 返回的数据是============");
        System.out.println(resp);
        
        //返回数据给浏览器  封装成http响应
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(resp.getBytes());

        outputStream.flush();
        outputStream.close();
        inputStream.close();
        socket.close();

    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  }
}

**Tomcat V2 **

/**
 * V2版本Tomcat
 */
public class HspTomcatV2 {

    public static void main(String[] args) throws IOException {
        
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("========MyTomcat V2 正在监听========");

        // 等待客户端的连接
        while (!serverSocket.isClosed()){
            // 接受到一个浏览器的连接,成功后就会得到socket
            // socket是客户端和服务器之间的通道
            Socket socket = serverSocket.accept();
            // 创建一个线程对象,并把socket给该线程
            HspRequestHandler hspRequestHandler =
                    new HspRequestHandler(socket);

            new Thread(hspRequestHandler).start();
        }
    }
}
处理Servlet

1.自定义Request

作用:封装http请求的数据

/**
 * 作用:封装http请求的数据:
 * get /hspCalServlet?num1=1&num2=3
 * 比如 method get/post uri 参数列表
 * HspRequest 等价于原生的Servlet中的HttpServletRequest
 */
public class HspRequest {

    private String method;
    private String uri;
    private HashMap<String,String> parameterMapping =
            new HashMap<>();
    private InputStream inputStream = null;

    //构造器  对http请求进行封装
    public HspRequest(InputStream inputStream){
        this.inputStream = inputStream;
        // 完成对http请求数据的封装
        init();
    }

    private void  init(){
        System.out.println("init 被调用");
        try {
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
            // get /hspCalServlet?num1=1&num2=3
            String requestLine = bufferedReader.readLine();

            //分割
            String[] requestLineArr = requestLine.split(" ");
            method = requestLineArr[0];
            //解析得到 /hspCalServlet 看看有没有参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1){
                uri = requestLineArr[1];
            }else{
                uri = requestLineArr[1].substring(0,index);

                //获取参数列表:
                String parameters = requestLineArr[1].substring(index + 1);
                //分割成:num1=1 、 num2=3  .....
                String[] parametersPair = parameters.split("&");
                if (parametersPair!=null && !"".equals(parametersPair)){
                    // 分割:
                    for (String parameterPair : parametersPair) {
                        // parameterVal: {"num1","1"}
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length == 2){
                            //放入到HashMap
                            parameterMapping.put(parameterVal[0],parameterVal[1]);
                        }
                    }
                }
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //方法: getParameter(String name):
    public String getParameter(String name){
        if (parameterMapping.containsKey(name)){
            String s = parameterMapping.get(name);
            return s;
        }else {
            return null;
        }
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

}

使用:

HspRequest hspRequest = new HspRequest(inputStream);
    String num1 = hspRequest.getParameter("num1");
    String num2 = hspRequest.getParameter("num2");
    System.out.println("请求num1 = "+num1);
    System.out.println("请求num2 = "+num2);

2. 自定义Response

/**
 * 封装OutputStream 和socket关联
 * 通过HspResponse对象 返回http响应给浏览器
 */
public class HspResponse {

    private OutputStream outputStream = null;

    //http响应头
    public static final String respHeader = "HTTP/1.1 200\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    // 创建HspResponse时 outputStream是和Socket关联的
    public HspResponse(OutputStream outputStream) {
        this.outputStream = outputStream;

    }

    //当需要给浏览器返回数据时,可以通过HspResponse的输出流完成
    public OutputStream getOutputStream() {
        return outputStream;
    }
}
Servlet规范

HspServlet接口:

/**
 * 简化版的Servlet接口
 */
public interface HspServlet {

    void init(ServletConfig var1) throws Exception;

    void service(HspRequest request, HspResponse response)
            throws IOException;

    void destroy();
}

HspHttpServlet

public abstract class HspHttpServlet implements HspServlet{

    @Override
    public void service(HspRequest request, HspResponse response) throws IOException {
        if ("GET".equalsIgnoreCase(request.getMethod())){
            this.doGet(request,response);
        }else if("POST".equalsIgnoreCase(request.getMethod())){
            this.doPost(request,response);
        }
    }

    // 抽象模板设计模式
    // 让HspHttpServlet子类 HspCalServlet实现!
    public abstract void doGet(HspRequest request,HspResponse response);

    public abstract void doPost(HspRequest request,HspResponse response);
}

HspCalServlet

public class HspCalServlet extends HspHttpServlet{

    @Override
    public void doGet(HspRequest request, HspResponse response) {
        doPost(request,response);
    }

    @Override
    public void doPost(HspRequest request, HspResponse response) {
        int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
        int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);
        int res = num1+num2;

        // 返回数据
        OutputStream outputStream = response.getOutputStream();
        String respMes = HspResponse.respHeader+"<h1>"+num1+"+"+num2+"="+res+" HSP Tomcat3</h1>";
        try {
            outputStream.write(respMes.getBytes());
            outputStream.flush();
            outputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void init(ServletConfig var1) throws Exception {}
    
    @Override
    public void destroy() {}
    
}
※ 最终版 ※:

整体思路总结:

一:实现Tomcat的Http请求和Http响应

自定义HttpRequest类:

说明:该类实现了InputStream 和 Socket的关联!

/**
 * 作用:封装http请求的数据:
 * get /hspCalServlet?num1=1&num2=3
 * 比如 method get/post uri 参数列表
 * HspRequest 等价于原生的Servlet中的HttpServletRequest
 */
public class HspRequest {

    private String method;
    private String uri;
    private HashMap<String,String> parameterMapping =
            new HashMap<>();
    private InputStream inputStream = null;

    //构造器  对http请求进行封装
    public HspRequest(InputStream inputStream){
        this.inputStream = inputStream;
        // 完成对http请求数据的封装
        init();
    }

    private void  init(){
        System.out.println("init 被调用");
        try {
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
            // get /hspCalServlet?num1=1&num2=3
            String requestLine = bufferedReader.readLine();
            //分割
            String[] requestLineArr = requestLine.split(" ");
            method = requestLineArr[0];
            //解析得到 /hspCalServlet 看看有没有参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1){
                uri = requestLineArr[1];
            }else{
                uri = requestLineArr[1].substring(0,index);

                // 获取参数列表:
                String parameters = requestLineArr[1].substring(index + 1);
                // 分割成:num1=1 、 num2=3  .....
                String[] parametersPair = parameters.split("&");
                if (parametersPair!=null && !"".equals(parametersPair)){
                    // 分割:
                    for (String parameterPair : parametersPair) {
                        // parameterVal: {"num1","1"}
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length == 2){
                            //放入到HashMap
                            parameterMapping.put(parameterVal[0],parameterVal[1]);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    //方法: getParameter(String name):
    public String getParameter(String name){
        if (parameterMapping.containsKey(name)){
            String s = parameterMapping.get(name);
            return s;
        }else {
            return null;
        }
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

}

自定义HttpResponse类:

说明:该类实现了将OutputStream和socket的关联!

/**
 * 封装OutputStream 和socket关联
 * 通过HspResponse对象 返回http响应给浏览器
 */
public class HspResponse {

    private OutputStream outputStream = null;

    //http响应头
    public static final String respHeader = "HTTP/1.1 200\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    // 创建HspResponse时 outputStream是和Socket关联的
    public HspResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    //当需要给浏览器返回数据时,可以通过HspResponse的输出流完成
    public OutputStream getOutputStream() {
        return outputStream;
    }
}

二:自定义Tomcat

说明:通过dom4j和反射, 解析web.xml 将servlet标签和servlet-mapping标签中的信息放入容器中

/**
 * 第三版Tomcat
 */
public class HspTomcatV3 {

    // 存放容器
    public static final ConcurrentHashMap<String, HspHttpServlet>
            servletMapping = new ConcurrentHashMap<>();

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    public static void main(String[] args) {
        HspTomcatV3 hspTomcatV3 = new HspTomcatV3();
        hspTomcatV3.init();
        //启动
        hspTomcatV3.run();
    }

    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("==========hspTomcatV3在8080监听========");
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                HspRequestHandler hspRequestHandler = new HspRequestHandler(socket);
                new Thread(hspRequestHandler).start();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void init() {
        //读取web.xml
        // 得到web.xml路径
        String path = HspTomcatV3.class.getResource("/").getPath();
        //dom4j读取
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println(document);
            //得到根元素
            Element rootElement = document.getRootElement();
            // 得到根元素下的所有元素
            List<Element> elements = rootElement.elements();
            //遍历并过滤
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    //反射 将该Servlet实例放入ServletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (HspHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());

                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //反射 放入ServletUrlMapping
                    Element servletName = element.element("servlet-name");
                    Element urlPattern = element.element("url-pattern");
                    servletUrlMapping.put(urlPattern.getText(), servletName.getText());
                }
            }


        } catch (Exception e) {
            e.printStackTrace();
        } /*catch (Exception e) {
            e.printStackTrace();
        }*/

        System.out.println(servletMapping);
        System.out.println(servletUrlMapping);
    }

}

三:通过创建线程对象实现http的请求和http的响应

public class HspRequestHandler implements Runnable {

    //定义Socket:作为线程处理类的属性
    private Socket socket = null;

    public HspRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        try {
            
            // 通过HspRequest对象对浏览器发起请求
            // 当创建HspRequest类时,便会调用init(),实现对提交数据的解析并将parameter
            // 放入其parameterMapping
            HspRequest hspRequest = new HspRequest(socket.getInputStream());
            // 通过HSpResponse对象返回数据给浏览器
            HspResponse hspResponse = new HspResponse(socket.getOutputStream());

            //创建Servlet
            String uri = hspRequest.getUri();
            String servletName = HspTomcatV3.servletUrlMapping.get(uri);
            if (servletName == null){
                servletName = "";
            }
            // uri -> servletName -> servlet实例
            // 编译类型是父类 HspHttpServlet 真正的运行类型是其子类:HspCalServlet
            HspHttpServlet hspHttpServlet =
                    HspTomcatV3.servletMapping.get(servletName);
            //调用service,通过动态绑定机制 调用运行类型的doGet/doPost
            if (hspHttpServlet != null) {
                hspHttpServlet.service(hspRequest, hspResponse);
            } else {
                //没有
                String resp = HspResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = hspResponse.getOutputStream();
                // 通过response对象的outputStream将信息响应给浏览器
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();

            }
            
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

自定义Servlet接口:

/**
 * 简化版的Servlet接口
 */
public interface HspServlet {
    void init(ServletConfig var1) throws Exception;
    void service(HspRequest request, HspResponse response)
            throws IOException;
    void destroy();
}

自定义HttpServlet抽象类:

说明:

方法service()被继承该类的子类调用时,会动态绑定选择doGet/doPost

public abstract class HspHttpServlet implements HspServlet{

    @Override
    public void service(HspRequest request, HspResponse response) throws IOException {
        if ("GET".equalsIgnoreCase(request.getMethod())){
            this.doGet(request,response);
        }else if("POST".equalsIgnoreCase(request.getMethod())){
            this.doPost(request,response);
        }
    }
    // 抽象模板设计模式
    // 让HspHttpServlet子类 HspCalServlet实现!
    public abstract void doGet(HspRequest request,HspResponse response);

    public abstract void doPost(HspRequest request,HspResponse response);
}

自定义Servlet类并实现自定义HttpServlet类

public class HspCalServlet extends HspHttpServlet{

    @Override
    public void doGet(HspRequest request, HspResponse response) {
        doPost(request,response);
    }

    @Override
    public void doPost(HspRequest request, HspResponse response) {
        int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
        int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);
        int res = num1+num2;

        // 返回数据
        OutputStream outputStream = response.getOutputStream();
        String respMes = HspResponse.respHeader+"<h1>"+num1+"+"+num2+"="+res+" HSP Tomcat3 反射!</h1>";
        try {
            outputStream.write(respMes.getBytes());
            outputStream.flush();
            outputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig var1) throws Exception {}
    @Override
    public void destroy() {}
}

Web路径

相对路径
<form action="ok" method="post">
    u : <input type="text" name="username"/><br/>
    <input type="submit" value="注册用户"/>
</form>

使用相对路径:“ok”

会默认为:http://localhost:8080/工程名/ + ok(资源)

注意:资源名不能带 “/” 否则会解析成:http://localhost:8080/d1/d2/b.html 失去项目名!!

<a href="/d1/d2/b.html">跳转到d1/d2/b.html</a>

相对路径存在的问题:

当一个资源在多级目录下时,如 /views/user/User.html :

相对路径便会默认localhost:8080/cxz/views/user/

这时若请求的url不在该目录下,就会出错!

可以在该页内使用 标签解决!

使用base标签

位于标签中

<!--<base>表示;
    当前页面访问的所有资源都是以"http://localhost:8080/cxz/"为参考路径
-->
<base href="http://localhost:8080/cxz/">

这样该页面的请求的所有资源路径都可以只带资源名

也可以简写为:

<!--浏览器解析 :
浏览器在解析第一个 / 的时候 都会解析成 http://localhost:8080/ -->
<base href="/cxz/">
转发的路径问题
//在服务器端 解析到第一个“/"时被解析为:http://localhost:8080/项目名/
System.out.println("Servlet 转发");
request.getRequestDispatcher("/d1/d2/b.html").forward(request,response);
总结

编写资源路径时需注意:

1.这个路径前面有没有 “ / ”

2.这个“/ ”在哪被解析?

如果有 / 并且是在浏览器端,就被解析成:http://localhost:8080/(会丢失项目路径!)

如果有 / 并且在服务器端,就被解析成:/工程路径/

3.如果路径没有“ / ”:

如果在浏览器端被解析,则以浏览器当前的地址栏 去掉 资源部分,作为一个默认的相对路径

比如:http://localhost:8080/cxz/views/user/User.html 的默认相对路径是 :

​ http://localhost:8080/cxz/views/user/

4.重定向的资源路径算是被 浏览器 解析的!

注意!重定向路径前可以有两种形式:

方式一:/项目路径/资源名

如:

response.sendRedirect("/cxz/new");

方式二:资源名

如:前面不要带 “ / ”

response.sendRedirect("views/user/User.html");

会话技术 Cookie+Session

Cookie

Cookie介绍:

1.Cookie数据是保存在浏览器的

2.服务器在需要的时候可以从客户端/浏览器读取

Cookie的创建和读取

Cookie示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dM45wYVi-1688545785579)(C:\Users\cxz\Desktop\笔记图片\cookie.jpg)]

Cookie的创建:

//1.创建cookie对象
// username是惟一的。是该Cookie的名字
// CXZ是该cookie的值
// 此时 cookie在服务器端
Cookie cookie = new Cookie("username", "CXZ");

response.setContentType("text/html;charset=utf-8");
// 将该cookie发送给浏览器,保存起来
response.addCookie(cookie);

PrintWriter writer = response.getWriter();
writer.print("<h1>创建Cookie成功</h1>");

Cookie的读取

//通过request读取所有Cookie信息:
Cookie[] cookies = request.getCookies();
//遍历Cookie信息
if (cookies != null && cookies.length!=0){
    for (Cookie cookie : cookies) {
        System.out.println("cookie name = "+cookie.getName()+
                " value = "+cookie.getValue());
    }
}

读取指定Cookie

1.新建Cookie读取工具类

public class CookieUtils {

    // 编写方法 返回指定名字的cookie
    public static Cookie readCookieByName(String name, Cookie[] cookies) {

        if (name == null || "".equals(name) || cookies == null || cookies.length == 0) {
            return null;
        }
        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie;
            }
        }
        return null;
    }
}

2.读取指定Cookie:

protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // 得到指定Cookie的值:
    Cookie[] cookies = request.getCookies();
    Cookie email = CookieUtils.readCookieByName("email", cookies);
    if (email != null){
        System.out.println("email = "+email.getValue());
    }else {
        System.out.println("没有找到!");
    }


    response.setContentType("text/html;charset=utf-8");
    PrintWriter writer = response.getWriter();
    writer.print("<h1>读取成功</h1>");

}
修改Cookie
String name = "email";
Cookie[] cookies = request.getCookies();
// 从Cookie数组中读取指定name的cookie
Cookie cookie = CookieUtils.readCookieByName(name, cookies);

if (cookie == null){
    System.out.println("没有找到Cookie");
}else{
    // 修改cookie的值
    cookie.setValue("qwe@qq.com");
}

// 给浏览器返回修改后的cookie
if (cookie!=null){
    response.addCookie(cookie);
}

另外:

如果创建一个同名Cookie,则会覆盖掉原先的同名Cookie!

Cookie的生命周期

介绍:

1、Cookie的生命周期指的是管理Cookie什么时候被销毁

2、setMaxAge():

​ 正数:表示在指定的秒数后过期

​ 负数:表示浏览器关闭,Cookie就会被删除

​ 0 :表示马上删除Cookie

设置Cookie在指定时间后无效:

// 创建Cookie 声明周期为60S
Cookie cookie = new Cookie("job", "java");
// 浏览器根据创建的时间计时 60S后无效
// 当该Cookie无效,那么浏览器在发出http请求时 就不会携带该Cookie
cookie.setMaxAge(60);
// 保存到浏览器
response.addCookie(cookie);

删除Cookie

// 删除Cookie
// 先得到username
Cookie[] cookies = request.getCookies();
Cookie usernameCookie = CookieUtils.readCookieByName("username", cookies);
//设置声明周期为 0 等价于让浏览器删除该Cookie
usernameCookie.setMaxAge(0);
// 通知浏览器保存
response.addCookie(usernameCookie);
Cookie的有效路径

介绍:

path属性决定过滤哪些 Cookie 可以发送给浏览器

Cookie cookie1 = new Cookie("k1", "v1");
Cookie cookie2 = new Cookie("k2", "v2");

// 设置不同的有效路径
cookie1.setPath(request.getContextPath());
// /cxz/aaa
cookie2.setPath(request.getContextPath() + "/aaa");
//如果没有设置cookie的有效路径,默认是 /工程路径名

response.addCookie(cookie1);
response.addCookie(cookie2);

Session

介绍:

1、服务器在运行时为每一个用户的浏览器创建一个其独享的session对象/集合

2、session可以看做是HashMap : String - Object

Session底层机制

示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iu8hzjPB-1688545785579)(C:\Users\cxz\Desktop\笔记图片\session.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3Um5I90-1688545785580)(C:\Users\cxz\Desktop\笔记图片\session2.jpg)]

Session生命周期

可以设置Session的生命周期:

session.setMaxInactiveInterval(60);

注意:

1、如果在Session没有过期的情况下,操作Session,则重新计时生命周期

2、Session的计时是在 服务器端 管理的

如何删除Session?(删除的是整个Session)

session.invalidate();

删除某个Session的具体属性:

session.removeAttribute( "xx" );

监听器Listener + 过滤器Filter

监听器 Listener

ServletContextListener:

作用监听 ServletContext,即生命周期的监听。

创建监听器: 创建类并实现 ServletContextListener 并重写它的方法

作用:可以监听ServletContext的创建和销毁。

示例:

/**
 * web启动时,就会产生 ServletContextEvent事件
 * 程序员通过ServletContextEvent事件对象来获取信息 进行业务处理
 *
 */
public class HspServletContextListener implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) { 
        // 启动web时调用
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 销毁web时调用
    }
}

需要在web.xml中配置监听器,Tomcat就知道有监听器的存在了

过滤器 Filter

原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmAwKyKl-1688545785580)(C:\Users\cxz\Desktop\笔记图片\Filter.jpg)]

Filter的使用:

编写类实现Filter接口并重写方法:

public class ManageFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }
    
    @Override
    public void destroy() {

    }
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    }
}

并在web.xml中配置:

<filter>
    <filter-name>ManageFilter</filter-name>
    <filter-class>com.cxz.filter.ManageFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ManageFilter</filter-name>
    <url-pattern>/manage/*</url-pattern>
</filter-mapping>
FilterConfig:

介绍:

FilterConfig是Filter的配置类 Tomcat创建Filter时就会创建一个FilterConfig对象,包含了Filter配置文件的

配置信息。

使用:

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    System.out.println("======= init 执行 =======");
    
    // 通过filterConfig获取相关参数
    String filterName = filterConfig.getFilterName();

    String ip = filterConfig.getInitParameter("ip");

    ServletContext servletContext = filterConfig.getServletContext();

    // 获取所有配置信息参数名
    Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
    // 遍历枚举
    while (initParameterNames.hasMoreElements()){
        System.out.println("name = "+initParameterNames.nextElement());
    }
}
FilterChain 过滤器链

执行顺序:

两个Filter AFilter 、 BFilter

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2PfTDtt-1688545785580)(C:\Users\cxz\Desktop\笔记图片\image-20230702170912465.png)]

注意:

1、多个filter和目标资源在一次Http请求,在同一线程中

2、多个Filter对象是同一个 request 对象

3、chain.doFilter() 将执行下一个过滤器的doFilter(), 如果后面没有过滤器了,就执行目标资源。

JSON + Ajax

JSON

JSON快速入门:

格式:

var 变量名 = {
“k1":"v1",
"k2":123,
"k3":[1,"abc",3],
"k4":{"age":12,"name":"cxz"}
}
<script type="text/javascript">

    window.onload = function(){

        var cxz = {
            "k1": "cxz",
            "k2": 123,
            "k3": [123, "abc"],
            "k4": {"age": 12, "name": "cxz"}
        };
        
        // 获取json对象值
        console.log("k1 = ",cxz.k2);
    }
</script>

JSON和字符串之间的转化:

JSON.stringify( Json );

JSON.parse( String );

<script type="text/javascript">
window.onload = function () {

    //将JSON对象转为String
    var Jsonperson = {
        "name":"jack",
        "age":20
    }

    var strPerson = JSON.stringify(Jsonperson);
    console.log("strPerson" , strPerson)

    //将String转为Json
    var strDog = "{\"name\":\"狗\",\"age\":2}";
    var jsonDog = JSON.parse(strDog);
    console.log(jsonDog)
}
</script>
Java和JSON之间的转换

JavaBean和JSON之间的转换

前提:引入gson.jar

public class JavaJson {

    public static void main(String[] args) {

        //创建gson作为工具
        Gson gson = new Gson();

        // Javabean和JSON字符的转换
        Book book1 = new Book(100, "book1");

        // javabean - > json字符串
        String strBook = gson.toJson(book1);

        // json字符串 - > javabean
        // 第一个参数: json字符串
        // 第二个参数: 转成某个类的对象的类
        Book book2 = gson.fromJson(strBook, Book.class);
 
    }
}

List对象和JSON字符串之间的转换

// List - 》json字符串
ArrayList<Book> bookList = new ArrayList<>();
bookList.add(new Book(123,"三国演义"));
bookList.add(new Book(222,"西游记"));

String strBookList = gson.toJson(bookList);
System.out.println("strBookList = "+strBookList);

//JSON字符串 -- 》 list
/**
 * 需要使用TypeToken 然后通过实例指定需要转换成的类型
 */
Type type = new TypeToken<List<Book>>() {
}.getType();
List<Book> bookList2 = gson.fromJson(strBookList, type);
System.out.println("bookList2= "+bookList2);

Map对象和JSON之间的转化

// Map --》 JSON
HashMap<String, Book> bookMap = new HashMap<>();
bookMap.put("k1",new Book(300,"天龙八部"));
bookMap.put("k2",new Book(444,"红楼梦"));

String strBookMap = gson.toJson(bookMap);
System.out.println("strBookMap = "+strBookMap);

//json --> map
Map<String,Book> bookMap2= gson.fromJson(strBookMap, 
        new TypeToken<Map<String, Book>>() {}.getType());

System.out.println("bookMap2 = "+bookMap2);

Ajax

介绍:

Ajax是浏览器异步发起请求(指定哪些数据),局部更新 的技术

Ajax原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M590mrsY-1688545785580)(C:\Users\cxz\Desktop\笔记图片\xml.jpg)]

Ajax应用:

onreadystatechange事件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opPk1nqV-1688545785581)(C:\Users\cxz\Desktop\笔记图片\onreadstatechange.png)]

<script type="text/javascript">
window.onload = function () {
    var checkButton = document.getElementById("checkButton");

    checkButton.onclick = function () {

        //创建XMLHttpRequest对象[ajax引擎]
        var xhr = new XMLHttpRequest();

        //获取提交数据
        var uname = document.getElementById("uname").value;

        //发送指定数据
        // "/cxz/check?username=" + uname --> URL
        // true : 异步处理
        xhr.open("GET", "/cxz/check?uname=" + uname, true);
        // send调用前 给xhr绑定一个事件
        // onreadystatechange表示可以去指定一个函数,当数据变化时,会触发
        xhr.onreadystatechange = function () {
            // 如果请求已完成 且响应已就绪 并且状态码: 200
            if (xhr.readyState == 4 && xhr.status == 200) {
                document.getElementById("div1").innerHTML = xhr.responseText
                var responseText = xhr.responseText;
                // console.log("responseText = ",responseText);
                if (responseText != "") {
                    document.getElementById("myres").value = "用户名不可用"
                } else {
                    document.getElementById("myres").value = "用户名可用"
                }
            }
        }

        // 正式发送AJAX 请求:
        // 如果是POST请求 在()内填写 请求数据
        xhr.send();


    }
}
</script>

ThreadLocal

介绍:

1、作用:可以实现在同一个线程数据共享,从而解决多线程数据安全问题。

2、使用ThreadLocal + Filter 实现事务安全

应用

创建:

public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();

使用

public static class Task implements Runnable{
    @Override
    public void run() {
        Dog dog = new Dog();
        Pig pig = new Pig();
        System.out.println("放入的dog : "+dog);

        // 放入ThreadLocal
        threadLocal1.set(dog);
        threadLocal2.set(pig);
        
        new T1Service().update();
    }
}

public static void main(String[] args) {
    new Thread(new Task()).start();
}

取出当前线程的ThreadLocal:Object o = T1.threadLocal1.get();

注意:

threadLocal1.set(dog); 一个ThreadLocal中只能放一个数据,第二次set() 会使得ThreadLocal中

数据被替换。

局部更新* 的技术

Ajax原理

[外链图片转存中…(img-M590mrsY-1688545785580)]

Ajax应用:

onreadystatechange事件:

[外链图片转存中…(img-opPk1nqV-1688545785581)]

<script type="text/javascript">
window.onload = function () {
    var checkButton = document.getElementById("checkButton");

    checkButton.onclick = function () {

        //创建XMLHttpRequest对象[ajax引擎]
        var xhr = new XMLHttpRequest();

        //获取提交数据
        var uname = document.getElementById("uname").value;

        //发送指定数据
        // "/cxz/check?username=" + uname --> URL
        // true : 异步处理
        xhr.open("GET", "/cxz/check?uname=" + uname, true);
        // send调用前 给xhr绑定一个事件
        // onreadystatechange表示可以去指定一个函数,当数据变化时,会触发
        xhr.onreadystatechange = function () {
            // 如果请求已完成 且响应已就绪 并且状态码: 200
            if (xhr.readyState == 4 && xhr.status == 200) {
                document.getElementById("div1").innerHTML = xhr.responseText
                var responseText = xhr.responseText;
                // console.log("responseText = ",responseText);
                if (responseText != "") {
                    document.getElementById("myres").value = "用户名不可用"
                } else {
                    document.getElementById("myres").value = "用户名可用"
                }
            }
        }

        // 正式发送AJAX 请求:
        // 如果是POST请求 在()内填写 请求数据
        xhr.send();


    }
}
</script>

ThreadLocal

介绍:

1、作用:可以实现在同一个线程数据共享,从而解决多线程数据安全问题。

2、使用ThreadLocal + Filter 实现事务安全

应用

创建:

public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();

使用

public static class Task implements Runnable{
    @Override
    public void run() {
        Dog dog = new Dog();
        Pig pig = new Pig();
        System.out.println("放入的dog : "+dog);

        // 放入ThreadLocal
        threadLocal1.set(dog);
        threadLocal2.set(pig);
        
        new T1Service().update();
    }
}

public static void main(String[] args) {
    new Thread(new Task()).start();
}

取出当前线程的ThreadLocal:Object o = T1.threadLocal1.get();

注意:

threadLocal1.set(dog); 一个ThreadLocal中只能放一个数据,第二次set() 会使得ThreadLocal中

数据被替换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值