JavaEE笔记

JavaEE笔记

作者:光和影子
我的博客

前言

阿里来我们学校搞实习招聘了,想投个Java开发岗但奈何自己已经完全忘记了本科所学,上一次碰Java还是大二时的课程作业,hhh。希望能够在简历投递截止之前尽量准备吧。
这几天学习JavaEE时做了一个记录,主要是参考廖雪峰老师的Java教程
我觉得廖老师讲的很好,将整个Java Web的来龙去脉讲的很清楚,没有深陷细节的泥潭。这个笔记也基本上是参照廖老师的教程逻辑来的,不过用我自己的语言和理解进行了重述,更加精简一些。
image

概述

JavaEE即Java企业平台,其实就是Java SE(标准版Java)的基础上加上一些支持包,这些包的作用是帮助我们更方便地构建应用。其中最核心的是基于Servlet的Web服务器。

JavaEE并不是一个软件产品,它更多的是一种软件架构和设计思想。我们可以把JavaEE看作是在JavaSE的基础上,开发的一系列基于服务器的组件、API标准和通用架构。
-- 廖雪峰,Java教程

一. 没有JavaEE时应该怎么做web服务

Web服务响应过程

所谓web服务,即浏览器通过TCP链接向服务程序发来HTTP协议的信息,服务程序收到信息后对HTTP文本进行检查、解析并通过TCP向浏览器发送HTTP响应。

Web服务器细节

所以我们需要有一个deamon对TCP的某一端口(如80)进行监听,一旦有请求就产生一个线程对这个请求进行处理(这个线程负责这个Socket)。这个线程首从socket中获取输入流和输出流,根据输入流的请求信息将输出内容通过输出流发送给浏览器。
以上过程可以使用如下代码实现(代码来源于廖雪峰,Java教程):

展开查看
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;


/*
没有javaEE时,使用TCP模拟一个web服务器
浏览器访问: http://127.0.0.1:8080/
 */
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8080); // 监听指定端口
        System.out.println("server is running...");
        for (;;) {
            Socket sock = ss.accept();
            System.out.println("connected from " + sock.getRemoteSocketAddress());
            Thread t = new Handler(sock);
            t.start();
        }
    }
}

class Handler extends Thread {
    Socket sock;

    public Handler(Socket sock) {
        this.sock = sock;
    }

    public void run() {
        try (InputStream input = this.sock.getInputStream()) {
            try (OutputStream output = this.sock.getOutputStream()) {
                handle(input, output);
            }
        } catch (Exception e) {
            try {
                this.sock.close();
            } catch (IOException ioe) {
            }
            System.out.println("client disconnected.");
        }
    }

    private void handle(InputStream input, OutputStream output) throws IOException {
        System.out.println("Process new http request...");
        var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        // 读取HTTP请求:
        boolean requestOk = false;
        String first = reader.readLine();
        if (first.startsWith("GET / HTTP/1.")) {
            requestOk = true;
        }
        for (;;) {
            String header = reader.readLine();
            if (header.isEmpty()) { // 读取到空行时, HTTP Header读取完毕
                break;
            }
            System.out.println(header);
        }
        System.out.println(requestOk ? "Response OK" : "Response Error");
        if (!requestOk) {
            // 发送错误响应:
            writer.write("HTTP/1.0 404 Not Found\r\n");
            writer.write("Content-Length: 0\r\n");
            writer.write("\r\n");
            writer.flush();
        } else {
            // 发送成功响应:
            String data = "<html><body><h1>Hello, world!</h1></body></html>";
            int length = data.getBytes(StandardCharsets.UTF_8).length;
            writer.write("HTTP/1.0 200 OK\r\n");
            writer.write("Connection: close\r\n");
            writer.write("Content-Type: text/html\r\n");
            writer.write("Content-Length: " + length + "\r\n");
            writer.write("\r\n"); // 空行标识Header和Body的分隔
            writer.write(data);
            writer.flush();
        }
    }
}

在浏览器输入http://local.liaoxuefeng.com:8080/可以看到返回页面。

二. Servlet入门

专注业务逻辑,让Servlet处理HTTP底层内容

上一节介绍了如何web服务器的相应过程,然而上面的服务器功能不完善.一个服务器需要有以下几个功能:

  • 识别正确和错误的HTTP请求;
  • 识别正确和错误的HTTP头;
  • 复用TCP连接;
  • 复用线程;
  • IO异常处理;
  • 等...

为了更加专注于业务逻辑而非服务器的编写,JavaEE提供的Servlet API将封装了解析HTTP协议这些底层的服务器该做的工作,开发人员只需要遵循Servlet API的规范进行编程就行了.这一系列的类在package javax.servlet包中.

一个简单的Servlet例子

一个简单的Servlet如下:

展开查看
package com.itranswarp.learnjava;

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.*;


// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 设置响应类型:
        resp.setContentType("text/html");
        // 获取输出流:
        PrintWriter pw = resp.getWriter();
        // 写入响应:
        pw.write("<h1>Hello, world!</h1>");
        // 最后不要忘记flush强制输出:
        pw.flush();
    }
}

如上,开发人员完全不用去管HTTP应用层的事情,只需要专注业务逻辑重写doGetdoPost方法就行了.即当浏览器发来GET请求时应该如何相应和当浏览器发来POST请求时应该如何相应.上面代码没有实现HttpServlet接口doPost方法,所以不能处理浏览器发来POST请求.

工程目录

吃人嘴短,用人手短.我们选择使用Servlet包,就需要遵循Servlet定的规矩.
除了编写servlet之外,网站项目的工程结构还需要像下面这样:

web-servlet-hello
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── itranswarp
        │           └── learnjava
        │               └── servlet
        │                   └── HelloServlet.java
        ├── resources
        └── webapp
            └── index.jsp
            └── WEB-INF
                └── web.xml //对网站的配置文件

Servlet容器

是不是没有发现主程序,那网站应用怎么运行起来呢?我们需要一个Servlet容器来帮忙.顾名思义,和其他容器一样,Servlet容器就是用来装东西的,不过它装的是Servlet.
Servlet容器启动后会加载我们编写的Servlet并为每个Servlet创建一个实例,然后根据浏览器的请求路径将不同的请求分发(dispatch)给对应的Servlet.

支持Servlet API的Web服务器。常用的服务器有:

  • Tomcat:由Apache开发的开源免费服务器
  • Jetty:由Eclipse开发的开源免费服务器
  • GlassFish:一个开源的全功能JavaEE服务器
  • WebLogic:Oracle的商用服务器
  • WebSphere:IBM的商用服务器

注意:Servlet容器只会给每个Servlet类创建唯一实例,而Servlet容器会使用多线程执行doGet()或doPost()方法,在Servlet中定义的实例变量会被多个线程同时访问,要注意线程安全;

三. Servlet调试

Tomcat其实就是一个程序而已.Tomcat的启动过程如下:

  • 启动JVM并执行Tomcat的main()方法;
  • 加载war并初始化Servlet;
  • 正常服务。
    为了方便调试,我们可以使用嵌入式的tomcat包,然后自己编写主程序.

主程序如下:

展开查看
public class Main {
    public static void main(String[] args) throws Exception {
        // 启动Tomcat:
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(Integer.getInteger("port", 8080));
        tomcat.getConnector();
        // 创建webapp:
        Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());
        WebResourceRoot resources = new StandardRoot(ctx);
        resources.addPreResources(
                new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));
        ctx.setResources(resources);
        tomcat.start();
        tomcat.getServer().await();
    }
}

四. Servlet详解

路径注解

每个Servlet负责处理一个路径,一个web app由多个Servlet组成,如:

展开查看
@WebServlet(urlPatterns = "/hello") // 通过注解说明自己能处理的路径
public class HelloServlet extends HttpServlet {
    ...
}

@WebServlet(urlPatterns = "/signin")
public class SignInServlet extends HttpServlet {
    ...
}

@WebServlet(urlPatterns = "/")
public class IndexServlet extends HttpServlet {
    ...
}

注意:早期的Servlet需要在web.xml中配置映射路径,但最新Servlet版本只需要通过注解就可以完成映射。

根据请求路径分配Servlet图解

浏览器发出的HTTP请求总是由Web Server先接收,然后,根据Servlet配置的映射,不同的路径转发到不同的Servlet:

              ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

               │            /hello    ┌───────────────┐│
                          ┌──────────>│ HelloServlet  │
               │          │           └───────────────┘│
┌───────┐    ┌──────────┐ │ /signin   ┌───────────────┐
│Browser│───>│Dispatcher│─┼──────────>│ SignInServlet ││
└───────┘    └──────────┘ │           └───────────────┘
               │          │ /         ┌───────────────┐│
                          └──────────>│ IndexServlet  │
               │                      └───────────────┘│
                              Web Server
               └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

这种根据路径转发的功能我们一般称为Dispatch。映射到/IndexServlet比较特殊,它实际上会接收所有未匹配的路径,相当于/*

HttpServletRequest

我们通过HttpServletRequest提供的接口方法可以拿到HTTP请求的几乎全部信息,常用的方法有:

展开查看
  • getMethod():返回请求方法,例如,"GET","POST";
  • getRequestURI():返回请求路径,但不包括请求参数,例如,"/hello";
  • getQueryString():返回请求参数,例如,"name=Bob&a=1&b=2";
  • getParameter(name):返回请求参数,GET请求从URL读取参数,POST请求从Body中读取参数;
  • getContentType():获取请求Body的类型,例如,"application/x-www-form-urlencoded";
  • getContextPath():获取当前Webapp挂载的路径,对于ROOT来说,总是返回空字符串"";
  • getCookies():返回请求携带的所有Cookie;
  • getHeader(name):获取指定的Header,对Header名称不区分大小写;
  • getHeaderNames():返回所有Header名称;
  • getInputStream():如果该请求带有HTTP Body,该方法将打开一个输入流用于读取Body;
  • getReader():和getInputStream()类似,但打开的是Reader;
  • getRemoteAddr():返回客户端的IP地址;
  • getScheme():返回协议类型,例如,"http","https";

此外,HttpServletRequest还有两个方法:etAttribute()getAttribute(),可以给当前HttpServletRequest对象附加多个Key-Value.

HttpServletResponse

HttpServletResponse封装了一个HTTP响应。
由于HTTP响应必须先发送Header,再发送Body,所以,操作HttpServletResponse对象时,必须先调用设置Header的方法,最后调用发送Body的方法。
常用的设置Header的方法有:

展开查看
  • 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()获取字符流,二者只能获取其中一个。

重定向(Redirect)

重定向是指当浏览器请求一个URL时,服务器返回一个重定向指令,告诉浏览器地址已经变了,麻烦使用新的URL再重新发送新请求。

例如,我们已经编写了一个能处理/hello的HelloServlet,如果收到的路径为/hi,希望能重定向到/hello,可以再编写一个RedirectServlet:

展开查看
@WebServlet(urlPatterns = "/hi")
public class RedirectServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 构造重定向的路径:
        String name = req.getParameter("name");
        String redirectToUrl = "/hello" + (name == null ? "" : "?name=" + name);
        // 发送重定向响应:
        resp.sendRedirect(redirectToUrl);
    }
}

重定向时浏览器现后发送两次请求,过程如下:

┌───────┐   GET /hi     ┌───────────────┐
│Browser│ ────────────> │RedirectServlet│
│       │ <──────────── │               │
└───────┘   302         └───────────────┘


┌───────┐  GET /hello   ┌───────────────┐
│Browser│ ────────────> │ HelloServlet  │
│       │ <──────────── │               │
└───────┘   200 <html>  └───────────────┘

服务器内部转发(Forward)

Forward是指内部转发。当一个Servlet处理请求的时候,它可以决定自己不继续处理,而是转发给另一个Servlet处理。

例如,我们已经编写了一个能处理/hello的HelloServlet,继续编写一个能处理/morning的ForwardServlet:

展开查看
@WebServlet(urlPatterns = "/morning")
public class ForwardServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //收到请求后,并不自己发送响应,而是把请求和响应都转发给路径为/hello的Servlet
        req.getRequestDispatcher("/hello").forward(req, resp);
    }
}
后续请求的处理实际上是由HelloServlet完成的。这种处理方式称为转发(Forward),转发在服务器内部进行,浏览器并不知道发生过转发,浏览器地址也仍然为转发前的地址.其过程如下:
                          ┌────────────────────────┐
                          │      ┌───────────────┐ │
                          │ ────>│ForwardServlet │ │
┌───────┐  GET /morning   │      └───────────────┘ │
│Browser│ ──────────────> │              │         │
│       │ <────────────── │              ▼         │
└───────┘    200 <html>   │      ┌───────────────┐ │
                          │ <────│ HelloServlet  │ │
                          │      └───────────────┘ │
                          │       Web Server       │
                          └────────────────────────┘

Session

未完待续...

image

posted @ 2021-02-09 23:46  Oliver-,-  阅读(180)  评论(0编辑  收藏  举报