• Java笔记22


    • JavaEE: Java Platform Enterprise Edition Java企业平台
    • 并不是一个软件产品, 更多的是一种软件架构和设计思想. 可以把JavaEE看作是在JavaSE的基础上, 开发的一系列基于服务器的组件, API标准和通用架构.
    • JavaEE最核心的组件就是基于Servlet标准的Web服务器, 开发者编写的应用程序是基于Servlet API并运行在Web服务器内部的.

    Web基础

    • 访问网站, 使用App时, 都是基于Web这种Browser/Server模式, 简称BS架构.
      • 只需要浏览器, 应用程序的逻辑和数据都存在服务器.
      • 浏览器只需要请求服务器, 获取Web页面, 并把Web页面展示给用户即可.
      • Web页面由HTML编写, 具有极强的交互性和表现力.
      • 服务器升级后, 客户端无需任何部署便可以得到新版本.

    Http协议

    • 在Web应用中, 浏览器请求一个URL, 服务器就把生成的HTML网页发送给浏览器, 浏览器和服务器之间的传输协议是HTTP

    • HTTP是一种基于TCP协议之上的请求-响应协议.

    • 请求页面流程:

      1. 与服务器建立TCP连接;
      2. 发送HTTP请求
      3. 收取HTTP响应, 然后把网页在浏览器中显示出来
    • 看请求

      • 第一行表示使用GET请求获取路径为/的资源, 并使用的协议版本.
      • 第二行开始以Header: Value形式表示的HTTP头. 常见的.
      • Host: 表示请求的主机名, 因为一个服务器上可能运行着多个网站, 因此Host表示浏览器正在请求的域名.
      • User-Agent: 表示客户端本身. 可以知道浏览器的不同版本.
      • Accept: 表示浏览器能接受的类型, 例如text/*, image/*等.
      • Accept-language: 表示浏览器偏好的语言, 服务器可以据此返回不同语言的网页.
      • Accept-Encoding: 表示浏览器可以支持的压缩类型, 例如gzip, br
    • 看响应

      • 第一行表示: 版本号+响应代码+响应文本, 常见响应代码:
        • 200 OK: 表示成功
        • 301 Moved Permanently: 表示URL已经永久重定向
        • 302 Found: 表示URL需要临时重定向
        • 304 Not Modified: 表示该资源没有更改, 可以使用本地缓存的版本.
        • 400 Bad Request: 客户端发送了一个错误的请求, 例如无效的参数.
        • 401 Unauthorized: 表示客户端因为身份未验证而不允许访问该URL
        • 403 Forbidden: 表示服务器因为权限问题拒绝了客户端的请求.
        • 404 Not Found: 表示客户端请求了一个不存在的资源.
        • 500 Internal Server Error: 表示服务器处理时内部出错, 例如无法连接数据库.
        • 503 Service Unavailable: 表示服务器此刻暂时无法处理请求.
      • 第二行开始, 每一行返回HTTP头
      • Content-Type: 表示该响应内容的类型, 例如text/html
      • Content-Length: 表示响应内容的长度
      • Content-Encoding: 表示响应压缩算法.
      • Cache-Control: 指客户端如何缓存, 例如: max-age=300表示最多缓存300毫秒.
    • Http请求和响应都由HTTP Header和HTTP Body构成, 其中HTTP Header每行都以 结束.

    • 如果遇到两个连续的 , 那么后面就是HTTP Body.

    Servlet入门

    • 编写完整的HTTP服务非常复杂
    • 在JavaEE平台上, 处理TCP连接, 解析HTTP协议这些底层工作统统扔给现成的Web服务器处理
    • 我们只需要把自己的应用程序跑在Web服务器上
    • JavaEE提供了Servlet API. 我们使用Servlet API编写自己的Servlet来处理HTTP请求.
    • Web服务器实现Servlet API接口, 实现底层功能.
                     ┌───────────┐
                     │My Servlet │
                     ├───────────┤
                     │Servlet API│
    ┌───────┐  HTTP  ├───────────┤
    │Browser│<──────>│Web Server │
    └───────┘        └───────────┘
    
    • 编写的Servlet并不是直接运行, 而是由Web服务器加载后创建实例运行.
    • Servlet容器中的Servlet有以下特点:
      • 无法在代码中直接通过new创建Servlet实例, 必须由Servlet容器自动创建Servlet实例
      • Servlet容器只会给每个Servlet类创建唯一实例
      • Servlet容器会使用多线程执行doGet()doPost()方法.
    • 结合对线程
      • 在Servlet中定义的实例变量会被多个线程同时访问, 要注意线程安全
      • HttpServletRequestHttpServletResponse实例是由Servlet容器传入的局部变量, 它们只能被当前线程访问, 不存在多个线程访问的问题
      • doGet()doPost()方法中, 如果使用了ThreadLocal, 但没有清理, 那么它的状态很可能影响到下次的某个请求. 因为Servlet很可能用线程池实现线程复用
    • 编写正确的Servlet, 要清晰理解Java多线程模型, 需要同步访问的必须同步访问

    Servlet开发

    • 启动Tomcat的流程

      • 启动JVM并执行Tomcat的main()方法
      • 加载war并初始化Servlet;
      • 正常服务.
    • 直接vscode一把梭, 以后遇到情况再说

    Servlet进阶

    • 一个Web App由一个或多个Servlet组成, 每个Servlet通过注解说明自己能处理的路径.
    • 处理不同的请求方法使用覆写doGet()
    • 如果请求的方法没有进行处理, 会直接返回405或者500错误.
    • 一个Servlet如果映射到hello, 那么所有的请求方法都会被这个Servlet处理, 至于能不能返回200, 需要看方法有没有被覆写.
    @WebServlet(urlPatterns = "/hello") 
    public class HelloServlet extends HttpServlet {
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp ) {
        // ...
      }
    }
    
    • 浏览器发出的请求首先被Web Servlet先接受.
    • 然后根据Servlet映射, 不同路径转发到不同的Servlet:
                   ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
    
                   │            /hello    ┌───────────────┐│
                              ┌──────────>│ HelloServlet  │
                   │          │           └───────────────┘│
    ┌───────┐    ┌──────────┐ │ /signin   ┌───────────────┐
    │Browser│───>│Dispatcher│─┼──────────>│ SignInServlet ││
    └───────┘    └──────────┘ │           └───────────────┘
                   │          │ /         ┌───────────────┐│
                              └──────────>│ IndexServlet  │
                   │                      └───────────────┘│
                                  Web Server
                   └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
    
    • 根据路径进行转发的功能, 称为Dispatch
    • /比较特殊, 会匹配所有路径, 所以使用伪代码进行匹配, else的最后一个走路径

    HttpServletRequest

    • 封装了HTTP请求, 从Servlet集成而来.

    • 除了HTTP请求外, 没有其他协议会使用Servlet处理. 这是一个过度设计

    • 常用方法:

    • getMethod(): 请求方法

    • getRequestUrl(): 请求路径, 不包括请求参数

    • getQueryString(): 请求参数

    • getParameter(name): 请求参数, get方法从url中读取, post从body中读取

    • getContentType(): Body的类型

    • getContentPath(): 获取Webapp挂载的路径

    • getCookies(): 请求携带的所有Cookie

    • getHeader(name): 获取指定header, 对Header名称不区分大小写

    • getHeaderNames(): 返回所有的Header名称

    • getInputStream(): 如果请求带有HTTP Body, 打开一个输入流读取Body

    • getReader():

    • getRemoteAddr(): 返回客户端的ip地址

    • getScheme(): 返回协议类型

    HttpServletResponse

    • 封装了一个HTTP响应.

    • HTTP响应必须先发送Header, 再发送Body.

    • 所以, 必须先设置Header方法, 再调用发送Body方法

    • 常用方法:

    • setStatus(sc): 设置响应代码. 默认200

    • setContentType(type): 设置body类型, 例如text/html

    • setCharacterEncoding(charset): 设置字符编码, 例如UTF-8

    • setHeader(name, value): 设置一个Header的值

    • addCookie(cookie): 给响应添加一个Cookie

    • addHeader(name, value): 给响应添加一个Header, 因为HTTP协议允许有多个相同的Header

    • 写入响应时, 需要通过getOutputStream()获取写入流, 或者通过getWriter()获取字符流

    • 写入响应前, 无需设置setContentLength(), 因为底层服务器会根据写入字节数自动设置.

    • 写入的数量小, 会先写入缓存区, 如果写入的数据量大, 服务器会自动采用Chunked编码让浏览器能识别数据结束符而不需要设置Content-Length头

    • 写入完毕后, 调用flush()是必须的.

    • 写入完毕后, 不能调用close()方法, 因为会复用TCP()连接, 关闭写入流, 将关闭TCP连接.

    • 使用HttpServletRequest, HttpServletResponse, 两个高级接口, 我们不需要直接处理HTTP协议.

    • 具体的实现类由服务器提供, 我们编写的Web应用程序只关心接口方法, 不关系具体实现的子类.

    Servlet多线程模型

    • 一个Servlet类在服务器中只有一个实例. 对于每个HTTP请求, Web服务器会使用多线程执行请求.
    • 因此, 一个Servlet的doGet(), doPost()等方法是多线程并发执行的.
    • 如果Servlet中定义了字段, 要注意多线程并发访问的问题.
    • HttpServletRequest, HttpServletResponse是唯一实例, 只在当前线程中有效, 总是局部变量, 不存在多线程共享的问题

    重定向和转发

    Redirect

    • 重定向: 当浏览器请求一个URL时, 服务器返回一个重定向指令, 告诉浏览器地址已经变了, 麻烦使用新的URL再重新发送请求
    • 302: 临时重定向.
    • 301: 永久重定向. 浏览器会缓存重定向的关联.

    Forward

    • 内部转发. 当一个Servlet处理请求的时候, 可以决定自己不继续处理, 而是转发给另一个Servlet处理
    req.getRequestDispatcher("/hello").forward(req, resp);
    
    • 自己不发送响应, 而是把请求和响应都转发给路径为hello的Servlet
    • 转发和重定向的区别在于: 转发在服务器内部完成, 对浏览器来说只是一个HTTP请求

    使用Session和Cookie

    • 在Web应用程序中, 我们需要跟踪用户身份.
    • HTTP是一个无状态协议. Web应用程序无法区分收到两个HTTP请求是否同一个浏览器发出.
    • 为了跟踪用户状态, 服务器可以向浏览器分配一个唯一ID, 并以Cookie的形式发送到浏览器.
    • 浏览器再后续访问时总是附带此Cookie, 这样, 浏览器就可以识别用户身份了.

    Session

    • Session: 基于唯一ID识别用户身份的机制.

    • 每个用户第一次访问服务器, 会自动获得一个Session ID.

    • 如果用户在一段时间内没有访问服务器. Session ID自动失效, 下次及时带着上次分配的Session ID访问, 服务器也会认为是新用户, 重新分配Session ID

    • Servlet内置了对Session支持.

    • 我们总是通过HttpSession访问当前Session.

    • Web服务器自动维护了一个id到HttpSession的映射表

    • 服务器识别Session依靠一个名为JSESSIONID的Cookie.

    • 第一次调用getSession()的时候, 自动创建一个Session ID, 然后通过JSESSIONIDCookie发送给浏览器

    • JESSIONID是由Servlet容器自动创建的, 目的是维护一个浏览器会话, 和我们的登录逻辑没有关系

    • 登录和登出的业务逻辑是我们自己根据HttpSession是否存在一个user的key判断的, 登出后, SessionId并不会改变.

    • 即使没有登录功能, 仍然可以使用HttpSession追踪用户, 例如, 放入一些用户配置信息等

    • 使用Session时, 服务器会把所有用户的Session都存储在内存中, 如果遇到内存不足的情况, 就需要把部分不活动的Session序列化到磁盘上. 会降低服务器运行效果, 因此, 放入的Session要小, 通常放入一个简单的User就足够了.

    public class User {
      public long id; // 唯一标识
      public String email;
      public String name;
    }
    
    • 使用多台服务器构成集群时, 使用Session会遇到一些额外的问题. 通常, 多台服务器集群使用反向代理作为网站入口.
    • 如果多台Web Sever采用无状态集群, 那么反向代理总是以轮询的方式依次将请求转发给每台Web Server.
    • 这会造成一个用户Web Sever1存储的Session信息, 在Web Server2和Web Server3上并不存在.
    • 如果登录了1机器, 那么后续请求转发到2和3上, 仍然是无状态的
    • 解决方法:
      • 每一天Web Server之间进行Session复制, 会严重消耗网路带宽, 并且内存使用率低
      • 采用粘置会话(Sticky Session)机制, 即反向返回代理在转发请求的时候, 总是根据JSESSIONID的值判断, 相同的JSESSIONID总是转发到固定的Web Server, 需要反向代理支持.
      • 无论使用哪种方法, Session机制会使得Web Sever的集群很难扩展. 中小型Web应用使用Session, 大型WEB应用程序避免使用Session机制
    • Cookie我们可以任意使用
    
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String lang = req.getParameter("lang");
        if (LANGUAGE.contains(lang)) {
          // 创建一个新的Cookie
          Cookie cookie = new Cookie("lang", lang);
          // 该Cookie生效的范围路径:
          cookie.setPath("/");
          // 设置有效期
          cookie.setMaxAge(10000);
          // cookie添加到响应
          resp.addCookie(cookie);
        }
        resp.sendRedirect("/");
      }
    
    • 是否携带Cookie, 取决于以下条件:
      • URL前缀是设置Cookie时的Path
      • Cookie在有效期
      • Cookie设置了secure时, 必须以https访问

    JSP开发

    • Java Server Page: 文件放到/src/main/webapp下

    • 文件名以.jsp结尾

    • 包含在<%-- --%>之间的是JSP注释

    • 包含在<% %>之间的是Java代码, 可以编写任意Java代码

    • 如果使用<%= xxx%>可以快捷输出一个变量

    • 内置变量: 可以直接使用

      • out: 表示HttpServletResponse的PrintWriter
      • session: 表示当前HttpSession对象
      • request: 表示HttpServletRequest对象
    • 使用完整路径, 直接访问

    • JSP和Servlet没有任何区别, jsp文件首先被编译成一个Servlet.

    • 可以在tomcat临时目录下找到这个文件

    JSP高级功能

    • jsp指令非常复杂
    <%@ page import="java.io.*" %>
    <%@ page import="java.util.*" %>
    
    • 使用include指令可以引入另一个JSP文件

    MVC开发

    • Model-View-Controller
    • Controller专注业务处理, 处理的结果就是Model
    • Model可以是JavaBean, 也可以是一个包含多个对象的Map, Controller负责把Model传递给View
    • View只是负责吧Model给渲染出来.

    MVC高级开发

    • 希望MVC框架可以做的事情
      • 如果是GET方法, 直接把URL参数按照方法参数对应起来然后传入
      • 如果是POST方法, 直接把POST参数变成一个JavaBean后通过方法传入
      • Controller的方法, 在需要使用HttpServletRequest, HttpServletResponse, HttpSession这些实例时, 只要有方法参数定义, 就可以自动传入
       HTTP Request    ┌─────────────────┐
    ──────────────────>│DispatcherServlet│
                       └─────────────────┘
                                │
                   ┌────────────┼────────────┐
                   ▼            ▼            ▼
             ┌───────────┐┌───────────┐┌───────────┐
             │Controller1││Controller2││Controller3│
             └───────────┘└───────────┘└───────────┘
                   │            │            │
                   └────────────┼────────────┘
                                ▼
       HTTP Response ┌────────────────────┐
    <────────────────│render(ModelAndView)│
                     └────────────────────┘
    
    • 这一节涉及太多东西了, 没有搞明白, 会再单独学习一次

    使用Filter

    • Filter插件, 过滤器, 在HTTP请求到到Servlet之前, 可以被一个或多个Filter预处理.
    • 例如: 打印日志, 登录检查等
    @WebFilter(urlPatterns = "/*")
    public class EncodingFilter implements Filter {
    
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("EncodingFilter:doFilter");
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        chain.doFilter(request, response);
      }
    }
    
    • 使用@WebFilter注解标注Filter需要过滤的URL
    • 无法对标注的Filter规定顺序. 只能在web.xml文件中, 再对这些Filter再标注一遍
    @WebFilter(urlPatterns = "/user/*")
    public class AuthFilter implements Filter {
    
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {
        System.out.println("AuthFilter: check authentication");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (req.getSession().getAttribute("name") == null) {
          // 未登录, 跳转到登录页
          System.out.println("AuthFilter: not signin!");
          resp.sendRedirect("/signin");
        } else {
          // 已登录, 继续处理
          chain.doFilter(request, response);
        }
      }
    
    }
    
    • 可以对指定路径进行拦截
    • 如果没有进行chain.doFilter(), 返回200+空白页
    • 统一的Servlet入口, 配合多个Controller, Filter仍然正常工作. 可以针对不同的请求进行拦截.

    修改请求

    @WebFilter(urlPatterns = "/upload/*")
    public class ValidateUploadFilter implements Filter {
    
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        // 获取客户端传入的签名方法和签名:
        String digest = req.getHeader("Signature-Method");
        String signature = req.getHeader("Signature");
        if (digest == null || digest.isEmpty() || signature.isEmpty()) {
          sendErrorPage(resp, "Missing signature");
          return;
        }
        // 读取request的body, 并验证签名
        MessageDigest md = getMessageDigest(digest);
        InputStream input = new DigestInputStream(request.getInputStream(), md);
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        for (;;) {
          int len = input.read(buffer);
          if (len == -1) break;
          output.write(buffer, 0, len);
        }
        String actual = toHexString(md.digest());
        if (!signature.equals(actual)) {
          sendErrorPage(resp, "Invalid signature");
          return;
        }
    
        // 验证成功后, 继续处理:
        chain.doFilter(new ReReadableHttpServletRequest(req, output.toByteArray()), response);
    
      }
      
      // 将byte[] 转换为hex string:
      private String toHexString(byte[] digest) {
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
          sb.append(String.format("%02x", b));
        }
        return sb.toString();
      }
    
      private MessageDigest getMessageDigest (String name) throws ServletException {
        try {
          return MessageDigest.getInstance(name);
        } catch (Exception e) {
          throw new ServletException(e);
        }
      }
    
      private void sendErrorPage(HttpServletResponse response, String errorMessage) throws IOException {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        PrintWriter pw = response.getWriter();
        pw.write("<html><body><h1>");
        pw.write(errorMessage);
        pw.write("</h1></body></html>");
        pw.flush();
      }
    
    }
    

    修改响应

    @WebFilter("/slow/*")
    public class CacheFilter implements Filter {
    
      // Path到byte[]的缓存:
      private Map<String, byte[]> cache = new ConcurrentHashMap<>();
    
    
      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        // 获取path
        String url = req.getRequestURI();
        // 获取缓存内容
        byte[] data = this.cache.get(url);
        resp.setHeader("X-Cache-Hit", data == null ? "No" : "Yes");
        if (data == null) {
          // 缓存未找到, 构造一个伪造的Response
          CachedHttpServletResponse wrapper = new CachedHttpServletResponse(resp);
          // 让上下游组件写入数据到伪造的Response
          chain.doFilter(request, wrapper);
          // 从伪造的Response中读取写入的内容并放入缓存
          data = wrapper.getContent();
          cache.put(url, data);
        }
        // 写入到原始的Response
        ServletOutputStream output = resp.getOutputStream();
        output.write(data);
        output.flush();
      }
    
    }
    

    使用Listener

    监听器, 最常用的: ServletContextListener

    @WebListener
    public class AppListener implements ServletContextListener{
      @Override
      public void contextInitialized(ServletContextEvent sce) {
        System.out.println("Webapp initialized");
      }
    
      @Override
      public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Webapp destroyed");
      }
    }
    
    • 任何标注了@WebListener, 且实现了特定接口的类, 都会被Web服务器自动初始化.

    • 会在Web程序启动的时候, 和Web程序关闭的时候, 进行启动

    • 其他的监听器

      • HttpSessionListener: 监听HttpSession的创建和销毁
      • ServletListener: 监听ServletRequest的创建和销毁
      • ServletRequestAttributeListener: 监听ServletRequest请求的属性变化事件. (即调用ServletRequests.setAttribute)
      • ServletContextAttributeListener: 监听ServletContext的属性变化事件. (即调用ServletContext.setAttribute)

    ServletContext

    • 一定Web服务器可以运行一个或者多个WebApp, 对于每个WebApp, Web服务器都会创建一个全局的唯一的ServletContext实例, 我们在AppListener调用的方法, 实际上是ServletContext的创建和销毁
    • ServletRequest, HttpSession等对象, 通过getServletContext()获取到的是同一个.
    • 最大的作用是设置和共享全局信息
    • 还可以动态的添加Servlet, Filter, Listener方法

    部署

    • Servlet中的映射是会自动屏蔽, 静态文件的Servlet映射
                 ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
    
                 │  /static/*            │
    ┌───────┐      ┌──────────> file
    │Browser├────┼─┤                     │    ┌ ─ ─ ─ ─ ─ ─ ┐
    └───────┘      │/          proxy_pass
                 │ └─────────────────────┼───>│  Web Server │
                           Nginx
                 └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘    └ ─ ─ ─ ─ ─ ─ ┘
    
    • Nginx服务器用作反向代理和静态服务器
  • 相关阅读:
    窗口总在最前的时候信息框弹出解决
    IP地址与子网掩码知识
    六类线、五类线、超五类线有什么区别?
    如何删除internet网关连接图标
    密码输入框用“●”做遮盖符
    万象2004数据库说明
    客户机绑定路由例子bat
    命令行下导入、导出注册表
    网管维护常用命令
    hadoop shell 操作命令
  • 原文地址:https://www.cnblogs.com/zhangrunhao/p/14195204.html
Copyright © 2020-2023  润新知