• 且说Tomcat、Servlet、JSP和Spring


    经过在实验室一个月Web的学习,基本上从前端到后端都熟悉了一遍,王老师是叫我们这个月月底Web结束掉,我觉得有必要将这个模块进行整理成一个比较完善的体系和逻辑方便以后再涉及这部分内容的时候继续完善这套逻辑,并不断的熟悉常用的API和组织设计。我本来是想就这几个部分的平台工程源码进行分析推理的,然后再用Github上的360商城源码展开,有时间的话再将相关的知识看是否能植入到另一个网站并进行优化,毕竟工程知识是对实践的总结,脱离了实际就什么都不是。

    刚开始呢王老师是让我们用Dreamweaver设计网页,啊然后呢后面有一段时间在弄JavaScript的ODBC连接MySQL的奇怪的组合操作,对着中国本真堂药业有限公司(www.bzt368.com)的官网从前端到后端做了一个Demo,在自己的电脑上还算顺利运行,一部署到阿里云的时候我和另一个实验室的朋友就傻了,技术栈被限制在一个很小的范围想实现功能自己想办法,然后就是各种想啊最后还是没有想出来,最后王老师指了条明路居然叫我们去弄Applet,是的就是这个:

     然后从Applet不知道怎么回事就过渡到Servlet,还真别说看起来好像还真有那么有点关系,比如说他们都运行在一个容器中,都有自己的生命周期。

    Applet

    An applet is a small program that is intended not to be run on its own, but rather to be embedded inside another application. 
    The Applet class must be the superclass of any applet that is to be embedded in a Web page or viewed by the Java Applet Viewer. The Applet class provides a standard interface between applets and their environment.
    An extended version of java.applet.Applet that adds support for the JFC/Swing component architecture.
     
    Servlet
    Defines methods that all servlets must implement.

    A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.

    To implement this interface, you can write a generic servlet that extends javax.servlet.GenericServlet or an HTTP servlet that extends javax.servlet.http.HttpServlet.

    This interface defines methods to initialize a servlet, to service requests, and to remove a servlet from the server. These are known as life-cycle methods and are called in the following sequence:

    1. The servlet is constructed, then initialized with the init method.
    2. Any calls from clients to the service method are handled.
    3. The servlet is taken out of service, then destroyed with the destroy method, then garbage collected and finalized.

    In addition to the life-cycle methods, this interface provides the getServletConfig method, which the servlet can use to get any startup information, and the getServletInfo method, which allows the servlet to return basic information about itself, such as author, version, and copyright.

    Applet的生命周期是init()、start()、stop()和destory()分别对应初始化、init()方法紧接着的步骤与当用户返回包含该Applet网页时的操作和释放资源四个步骤。Servlet呢就是上面官方文档中写的那样,具体对应的方法是init()、service()和destroy()分别对应初始化、响应客户端请求和终止释放所占用的资源。下面给出一个嗯编译通过而未经测试的结合Applet和Servlet用例,这里用Applet作为客户端向服务端由Servlet负责处理请求查询目标IP地址返回给Applet然后进行通讯:

    AppletClient2.java:

      1 import java.awt.*;
      2 import javax.swing.*;
      3 import java.awt.event.*;
      4 import java.io.*;
      5 import java.net.*;
      6 
      7 /**
      8  *1. init():初始化GUI,Socket等待连接
      9  *2. start():注册键盘和按钮监听事件
     10  *3. queryUserIP():查询用户的IP,toServerSocket连接模拟GET请求Web服务器查询用户名的IP地址,如果
     11  *在数据库查询到该用户的就返回该用户IP地址,然后关闭原来的toServerSocket连接和读写流,
     12  *替代正在等待连接的toClientSocket和读写流并目标用户建立连接
     13  *4. sendMessageFromClient():向目标用户发送信息
     14  *5. 开一个线程来监听用户发来的消息
     15  */
     16 public class AppletClient2 extends JApplet {
     17 
     18     private JLabel userNameLabel;
     19     private JTextField userNameTextField, messageJTextField;
     20     public JTextArea displayTextArea;
     21     private JButton sendJButtonToClient, sendJButtonToServer;
     22 
     23     public BufferedReader toServerReader, toClientReader;
     24     private PrintWriter toServerWriter, toClientWriter;
     25 
     26     public Socket toServerSocket, toClientSocket;
     27     private ServerSocket serverSocket;
     28     private static final String SERVER_IP = "192.168.65.32";
     29     private static final int SERVER_PORT = 8080;
     30     private String userIP = "";
     31     private int clientPort = 8888;
     32 
     33     /**
     34      *初始化GUI,Socket等待连接
     35      */
     36     @Override
     37     public void init() {
     38         EventQueue.invokeLater(() -> {
     39 
     40             //用户名标签和输入用户名的文本域
     41             this.setLayout(new BorderLayout());
     42             JPanel topPanel = new JPanel();
     43             userNameLabel = new JLabel("用户名:", SwingConstants.LEFT);
     44             topPanel.add(userNameLabel);
     45             userNameTextField = new JTextField(20);
     46             topPanel.add(userNameTextField);
     47             sendJButtonToServer = new JButton("查询用户");
     48             topPanel.add(sendJButtonToServer);
     49             this.add(topPanel, BorderLayout.NORTH);
     50 
     51             //聊天对话框
     52             JPanel chatBoxJPanel = new JPanel(new BorderLayout());
     53             displayTextArea = new JTextArea(20, 32);
     54             displayTextArea.setEditable(false);
     55             JScrollPane displayScrollPane = new JScrollPane(displayTextArea);
     56             chatBoxJPanel.add(displayScrollPane, BorderLayout.SOUTH);
     57             this.add(chatBoxJPanel, BorderLayout.CENTER);
     58 
     59             //输入文本框和发送按钮
     60             JPanel downPanel = new JPanel();
     61             messageJTextField = new JTextField(35);
     62             downPanel.add(messageJTextField);
     63 
     64             sendJButtonToClient = new JButton("发送");
     65             downPanel.add(sendJButtonToClient);
     66             this.add(downPanel, BorderLayout.SOUTH);
     67 
     68             //等待连接
     69             try {
     70                 serverSocket = new ServerSocket(clientPort);
     71                 toClientSocket = serverSocket.accept();
     72                 toClientReader = new BufferedReader(
     73                     new InputStreamReader(toClientSocket.getInputStream()));
     74                 toClientWriter = new PrintWriter(
     75                     toClientSocket.getOutputStream(), true);
     76             } catch (IOException e) {
     77                 e.printStackTrace();
     78             }
     79             new ReceiveMessageFromClient(this).start();
     80         });
     81     }
     82 
     83     /**
     84      *注册键盘和按钮监听事件
     85      */
     86     @Override
     87     public void start() {
     88 
     89         //为查询用户IP按钮条件事件监听
     90         sendJButtonToServer.addActionListener(e -> {
     91             try {
     92                 serverSocket.close();
     93                 queryUserIP();
     94             } catch (IOException ioException) {
     95                 ioException.printStackTrace();
     96             }
     97         });
     98 
     99         //为发送信息按钮添加事件监听
    100         sendJButtonToClient.addActionListener(e -> {
    101             try {
    102                 sendMessageFromClient();
    103             } catch (IOException ioException) {
    104                 ioException.printStackTrace();
    105             }
    106         });
    107 
    108         //为按键添加事件监听,如果回车就发送信息
    109         messageJTextField.addKeyListener(new KeyAdapter() {
    110             public void keyPressed(KeyEvent e) {
    111                 if(e.getKeyChar() == KeyEvent.VK_ENTER) {
    112                     try {
    113                         sendMessageFromClient();
    114                     } catch (IOException ioException) {
    115                         ioException.printStackTrace();
    116                     }
    117                 }
    118             }
    119         });
    120     }
    121 
    122     /**
    123      *stop(): 当用户离开包含该Applet的HTML页面时,浏览器调用此方法.stop方法被调用后,将立即停止所有在start()方法中启动的操作.
    124      */
    125     @Override
    126     public void stop() {}
    127 
    128     /**
    129      *destroy(): 在终止Applet运行时,调用destory()方法,以便释放Applet占用的,由本地操作系统管理的任何系统资源.
    130      *此方法执行之前,总是先调用stop()方法.
    131      */
    132     @Override
    133     public void destroy() {}
    134 
    135     /**
    136      *查询用户的IP,toServerSocket连接模拟GET请求Web服务器查询用户名的IP地址,如果
    137      *在数据库查询到该用户的就返回该用户IP地址,然后关闭原来的toServerSocket连接和读写流,
    138      *替代正在等待连接的toClientSocket和读写流并目标用户建立连接
    139      */
    140     public void queryUserIP() throws IOException, UnknownHostException {
    141 
    142         //和Web服务端创建连接
    143         toServerSocket = new Socket(SERVER_IP, SERVER_PORT);
    144         toServerReader = new BufferedReader(
    145             new InputStreamReader(toServerSocket.getInputStream()));
    146         toServerWriter = new PrintWriter(toServerSocket.getOutputStream());
    147 
    148         String userName = userNameTextField.getText();
    149 
    150         if(userName.equals("") && userName != null) {
    151             toServerWriter.println("GET /ChatServerTest/queryIP?name=" + 
    152                 userName + " HTTP/1.1
    ");
    153             toServerWriter.println("Host: " + SERVER_IP + "
    ");
    154             toServerWriter.println("
    ");
    155             toServerWriter.flush();
    156 
    157             String returnFromServerMsg = toServerReader.readLine();
    158 
    159             if(returnFromServerMsg != "NULL") {
    160                 userIP = returnFromServerMsg;
    161             } else {
    162                 JOptionPane.showMessageDialog(null,
    163                         "未查询到该用户!", "提示",
    164                         JOptionPane.INFORMATION_MESSAGE);
    165             }
    166 
    167             //关闭与Web服务端的连接
    168             toServerWriter.close();
    169             toServerReader.close();
    170             toServerSocket.close();
    171 
    172             //替代等待连接的Socket和读写流并目标用户建立连接
    173             toClientSocket = new Socket(userIP, clientPort);
    174             toClientReader = new BufferedReader(
    175                 new InputStreamReader(toClientSocket.getInputStream()));
    176             toClientWriter = new PrintWriter(
    177                 toClientSocket.getOutputStream(), true);
    178         } else {
    179             JOptionPane.showMessageDialog(null,
    180                     "你没有选择要会话的对象,请先选择当前在线的客户进行会话,", "提示",
    181                     JOptionPane.INFORMATION_MESSAGE);
    182         }
    183     }
    184 
    185     /**
    186      *向目标用户发送信息
    187      */
    188     public void sendMessageFromClient() 
    189         throws IOException, UnknownHostException {
    190 
    191         String msg = messageJTextField.getText();
    192         if (!msg.equals("")) {
    193             toClientWriter.println(msg);
    194             messageJTextField.setText("");
    195         } else {
    196             JOptionPane.showMessageDialog(null,
    197                     "你没有输入如何消息,请输入内容后再发送!", "提示",
    198                     JOptionPane.INFORMATION_MESSAGE);
    199         }
    200     }
    201 
    202 }

    AppletClient2.html:

     1 <!DOCTYPE html>
     2 <html lang="zh-CN">
     3  <head>
     4   <title>Applet</title>
     5  </head>
     6  <body>
     7   <p>
     8    <applet code="AppletClient2.class" archive="AppletClient2.jar"
     9     width="500" height="500" alt="AppletClient2">
    10    </applet>
    11   </p>
    12  </body>
    13 </html>

    ServletServer.java:

     1 import java.io.*;
     2 
     3 import javax.servlet.ServletException;
     4 import javax.servlet.http.HttpServlet;
     5 import javax.servlet.http.HttpServletRequest;
     6 import javax.servlet.http.HttpServletResponse;
     7 import java.io.*;
     8 import java.sql.*;
     9 
    10 /**
    11  *处理Applet发来的Get请求,在数据库中找到用户名对应的IP然后发给Applet
    12  */
    13 public class ServletServer extends HttpServlet {
    14     
    15     @Override
    16     protected void service(HttpServletRequest req, HttpServletResponse resp) 
    17         throws ServletException, IOException {
    18         String driver = "com.mysql.jdbc.Driver";
    19         String url = "jdbc:mysql://localhost:3306/lanbao?autoReconnect=true&useSSL=false";
    20         String userName = "root";
    21         String password = "******";
    22         String ip = "";
    23         
    24         req.setCharacterEncoding("utf-8");
    25         resp.setCharacterEncoding("utf-8");
    26         
    27         //获取请求数据
    28         String name = req.getParameter("name");
    29         
    30         try {
    31             Class.forName(driver);
    32         } catch (ClassNotFoundException e) {
    33             e.printStackTrace();
    34         }
    35 
    36         try (
    37                 Connection conn = DriverManager
    38                     .getConnection(url, userName, password);
    39         ){
    40             String sql = "select user_ip from user_ip_address where name = '" + 
    41                 name + "';";
    42             try (
    43                     Statement stmt = conn.createStatement();
    44                     ResultSet rset = stmt.executeQuery(sql)
    45                     ){
    46                 while (rset.next()) {
    47                     ip = rset.getString("user_ip");                
    48                 }
    49             } catch (Exception e) {
    50                 e.printStackTrace();
    51             }
    52         } catch (Exception e) {
    53             e.printStackTrace();
    54         }
    55         
    56         if (ip != "") {
    57             PrintWriter out = resp.getWriter();
    58             out.println(ip);
    59         } else {
    60             System.out.println("搜索的用户ip不存在!");
    61         }
    62     }    
    63     
    64 }

    当然这个是我之前写的代码,其实在Servlet中是不建议重写service()的:

    Receives standard HTTP requests from the public service method and dispatches them to the doXXX methods defined in this class. This method is an HTTP-specific version of the Servlet.service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) method. There's no need to override this method.

     1 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     2     String method = req.getMethod();
     3     long lastModified;
     4     if (method.equals("GET")) {
     5         lastModified = this.getLastModified(req);
     6         if (lastModified == -1L) {
     7             this.doGet(req, resp);
     8         } else {
     9             long ifModifiedSince = req.getDateHeader("If-Modified-Since");
    10             if (ifModifiedSince < lastModified) {
    11                 this.maybeSetLastModified(resp, lastModified);
    12                 this.doGet(req, resp);
    13             } else {
    14                 resp.setStatus(304);
    15             }
    16         }
    17     } else if (method.equals("HEAD")) {
    18         lastModified = this.getLastModified(req);
    19         this.maybeSetLastModified(resp, lastModified);
    20         this.doHead(req, resp);
    21     } else if (method.equals("POST")) {
    22         this.doPost(req, resp);
    23     } else if (method.equals("PUT")) {
    24         this.doPut(req, resp);
    25     } else if (method.equals("DELETE")) {
    26         this.doDelete(req, resp);
    27     } else if (method.equals("OPTIONS")) {
    28         this.doOptions(req, resp);
    29     } else if (method.equals("TRACE")) {
    30         this.doTrace(req, resp);
    31     } else {
    32         String errMsg = lStrings.getString("http.method_not_implemented");
    33         Object[] errArgs = new Object[]{method};
    34         errMsg = MessageFormat.format(errMsg, errArgs);
    35         resp.sendError(501, errMsg);
    36     }
    37 
    38 }

    先不去深究每一行的代码的意义,service()已经在HttpServlet这个类中已经被实现的很完善了,我们只需要重写do*()这样的接口就好,上面Applet和Servlet的交互也可以看出他们的关系,其实类似于Servlet是Server Applet(运行在服务端的小程序)。

    Servlet的作用是为Java程序提供一个统一的web应用的规范,方便程序员统一的使用这种规范来编写程序,应用容器可以使用提供的规范来实现自己的特性。

    HTTP 协议也是一个规范,定义服务请求和响应的大致式样。Java servlet 类将HTTP中那些低层的结构包装在 Java 类中,这些类所包含的便利方法使其在 Java 语言环境中更易于处理。

    正如您正使用的特定 servlet 容器的配置文件中所定义的,当用户通过 URL 发出一个请求时,这些 Java servlet 类就将之转换成一个 HttpServletRequest,并发送给 URL 所指向的目标。当服务器端完成其工作时,Java 运行时环境(Java Runtime Environment)就将结果包装在一个 HttpServletResponse 中,然后将原 HTTP 响应送回给发出该请求的客户机。在与 Web 应用程序进行交互时,通常会发出多个请求并获得多个响应。所有这些都是在一个会话语境中,Java 语言将之包装在一个 HttpSession 对象中。在处理响应时,您可以访问该对象,并在创建响应时向其添加事件。它提供了一些跨请求的语境。

    容器(如 Tomcat)将为 servlet 管理运行时环境。您可以配置该容器,定制 J2EE 服务器的工作方式,以便将 servlet 暴露给外部世界。正如我们将看到的,通过该容器中的各种配置文件,您在 URL(由用户在浏览器中输入)与服务器端组件之间搭建了一座桥梁,这些组件将处理您需要该 URL 转换的请求。在运行应用程序时,该容器将加载并初始化 servlet管理其生命周期

    Servlet

    API Document:https://docs.oracle.com/javaee/7/api/javax/servlet/Servlet.html

    Servlet的框架是由两个Java包组成的:javax.servlet与javax.servlet.http。在javax.servlet包中定义了所有的Servlet类都必须实现或者扩展的通用接口和类。在javax.servlet.http包中定义了采用Http协议通信的HttpServlet类。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这个接口。

    在Servlet接口中定义了5个方法:

    1. init(ServletConfig)方法:负责初始化Servlet对象,在Servlet的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题;
    2. service(ServletRequest req, ServletResponse res)方法:负责响应客户的请求;为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性;
    3. destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源;
    4. getServletInfo:就是字面意思,返回Servlet的描述;
    5. getServletConfig:这个方法返回由Servlet容器传给init方法的ServletConfig。
    

    ServletRequest & ServletResponse

    对于每一个HTTP请求,servlet容器会创建一个封装了HTTP请求的ServletRequest实例传递给servlet的service方法,ServletResponse则表示一个Servlet响应,其隐藏了将响应发给浏览器的复杂性。通过ServletRequest的方法你可以获取一些请求相关的参数,而ServletResponse则可以将设置一些返回参数信息,并且设置返回内容。

    ServletConfig

    ServletConfig封装可以通过@WebServlet或者web.xml传给一个Servlet的配置信息,以这种方式传递的每一条信息都称做初始化信息,初始化信息就是一个个K-V键值对。为了从一个Servlet内部获取某个初始参数的值,init方法中调用ServletConfig的getinitParameter方法或getinitParameterNames方法获取,除此之外,还可以通过getServletContext获取ServletContext对象。

    ServletContext

    ServletContext是代表了Servlet应用程序。每个Web应用程序只有一个context。在分布式环境中,一个应用程序同时部署到多个容器中,并且每台Java虚拟机都有一个ServletContext对象。有了ServletContext对象后,就可以共享能通过应用程序的所有资源访问的信息,促进Web对象的动态注册,共享的信息通过一个内部Map中的对象保存在ServiceContext中来实现。保存在ServletContext中的对象称作属性。

    GenericServlet

    前面编写的Servlet应用中通过实现Servlet接口来编写Servlet,但是我们每次都必须为Servlet中的所有方法都提供实现,还需要将ServletConfig对象保存到一个类级别的变量中,GenericServlet抽象类就是为了为我们省略一些模板代码,实现了Servlet和ServletConfig,完成了一下几个工作。

    HttpServlet

    在编写Servlet应用程序时,大多数都要用到HTTP,也就是说可以利用HTTP提供的特性,javax.servlet.http包含了编写Servlet应用程序的类和接口,其中很多覆盖了javax.servlet中的类型,我们自己在编写应用时大多时候也是继承的HttpServlet。

    JSP

     王老师在实验室的时候是不让我们搞JSP的,这里简单的整理一下原理。

    Tomcat

    我新建一个动态web工程,只是写了一个HTML,没有编写Servlet,甚至一句Java代码都没写。但是启动Tomcat后我却可以通过浏览器访问到刚才编写的haha.html。这是为何?

    我们知道,对于像Tomcat这样的Servlet容器来说,任何一个请求的背后肯定有个Servlet在默默处理:

    所以这次也不例外,肯定也有对应的Servlet处理了本次请求。既然不是我们写的,那只能是Tomcat提供的。查看Tomcat下的conf目录,除了我们熟悉的Server.xml,还有个web.xml。

    不错,我们每个动态web工程都有个web.xml,而conf里的这个,是它们的“老爹”。它里面的配置,如果动态web工程没有覆盖,就会被“继承”下来。我们会发现,conf/web.xml里配置了一个DefaultServlet:

    DefaultServlet的作用:最低级匹配,当没有对应的Servlet处理当前请求时,才轮到它处理。要么找到并响应请求的资源,要么给出404页面。

    • JspServlet:JSP的卸妆师傅

    我们都知道JSP是“化了浓妆”的Servlet,但是好不容易伪装成了一个JSP,是谁帮它卸妆的呢?另外,大家仔细想想,一般来说JavaWeb阶段我们访问资源有三种“形式”:

    localhost:8080/demo/AServlet:很明显,我们手动写了一个AServlet处理它
    localhost:8080/demo/haha.html:虽然我们没写,但是Tomcat自己准备了DefaultServlet
    localhost:8080/demo/index.jsp:我擦,谁来处理?

    对呀,细思恐极,这*.jsp的资源,谁来处理?其实就是JspServlet。它的作用简而言之就是:

    • 首先,根据请求路径找到JSP
    • 然后,将它“翻译成”Servlet

     

    刚才带大家看conf/web.xml时,我把它隐藏了,因为同时讲解DefaultServlet和JspServlet会比较乱。强烈建议大家现在暂停一下,打开本机的Tomcat找到conf/web.xml看一下。下面是JspServlet的配置:

    所以最后总结一下Tomcat处理请求的几种方式:

     MVC

    Model, View, Conctroller 

    Spring

     参考资料:

    https://zhuanlan.zhihu.com/p/54121733

    https://www.zhihu.com/question/23984162/answer/689106407

     https://zh.wikipedia.org/wiki/Spring_Framework

  • 相关阅读:
    【CQOI2015】网络吞吐量
    【SDOI2010】所驼门王的宝藏
    【NOIP2013】华容道
    【SNOI2019】通信
    【IOI2016】railroad
    【AtCoder3611】Tree MST
    【AtCoder2134】ZigZag MST
    【CF891C】Envy
    【BZOJ4883】棋盘上的守卫
    【CF888G】Xor-MST
  • 原文地址:https://www.cnblogs.com/RQfreefly/p/13907992.html
Copyright © 2020-2023  润新知