JavaEE笔记
这几天学习JavaEE时做了一个记录。
JavaEE笔记
作者:光和影子
我的博客
前言
阿里来我们学校搞实习招聘了,想投个Java开发岗但奈何自己已经完全忘记了本科所学,上一次碰Java还是大二时的课程作业,hhh。希望能够在简历投递截止之前尽量准备吧。
这几天学习JavaEE时做了一个记录,主要是参考廖雪峰老师的Java教程。
我觉得廖老师讲的很好,将整个Java Web的来龙去脉讲的很清楚,没有深陷细节的泥潭。这个笔记也基本上是参照廖老师的教程逻辑来的,不过用我自己的语言和理解进行了重述,更加精简一些。
概述
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应用层的事情,只需要专注业务逻辑重写doGet
和doPost
方法就行了.即当浏览器发来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
未完待续…