0%

JAVA-SE笔记5(Web)

JAVA WEB部分

Web开发

端口

  • 不同的进程有不同的端口号,用来区分不同的软件
  • 范围:0~65535
  • TCP,UDP分别由65536个端口号,两个协议的端口不冲突
  • 端口分类:
    • 公有端口0~1023:
      • http:80
      • https:443
      • ftp:21
      • telent:23
    • 程序注册端口:1024~49151
      • Tomcat:8080
      • MySQL:3306
      • Oracle:1521
    • 动态、私有:49152~65535
1
2
3
netstat -ano # 查看所有端口
netstat -ano|findstr "5900" #查看指定端口
tasklist|findstr "8696" #查看指定端口的进程

Web基础

对于Browser来说,请求页面的流程如下:

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

浏览器发送的HTTP请求如下:

1
2
3
4
5
GET / HTTP/1.1
Host: www.sina.com.cn
User-Agent: Mozilla/5.0 xxx
Accept: */*
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8

其中,第一行表示使用GET请求获取路径为/的资源,并使用HTTP/1.1协议,从第二行开始,每行都是以Header: Value形式表示的HTTP头,比较常用的HTTP Header包括:

  • Host: 表示请求的主机名,因为一个服务器上可能运行着多个网站,因此,Host表示浏览器正在请求的域名;
  • User-Agent: 标识客户端本身,例如Chrome浏览器的标识类似Mozilla/5.0 ... Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...) like Gecko
  • Accept:表示浏览器能接收的资源类型,如text/*image/*或者*/*表示所有;
  • Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
  • Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate, br

HTTP目前有多个版本,1.0是早期版本,浏览器每次建立TCP连接后,只发送一个HTTP请求并接收一个HTTP响应,然后就关闭TCP连接。由于创建TCP连接本身就需要消耗一定的时间,因此,HTTP 1.1允许浏览器和服务器在同一个TCP连接上反复发送、接收多个HTTP请求和响应,这样就大大提高了传输效率。

我们注意到HTTP协议是一个请求-响应协议,它总是发送一个请求,然后接收一个响应。能不能一次性发送多个请求,然后再接收多个响应呢?HTTP 2.0可以支持浏览器同时发出多个请求,但每个请求需要唯一标识,服务器可以不按请求的顺序返回多个响应,由浏览器自己把收到的响应和请求对应起来。可见,HTTP 2.0进一步提高了传输效率,因为浏览器发出一个请求后,不必等待响应,就可以继续发下一个请求。

HTTP 3.0为了进一步提高速度,将抛弃TCP协议,改为使用无需创建连接的UDP协议,目前HTTP 3.0仍然处于实验阶段。

TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

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();
}
}
}

聊天应用

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServerDemo1 {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(9999)) {
try (Socket socket = serverSocket.accept()) {
try (InputStream inputStream = socket.getInputStream()) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();) {
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
String s = byteArrayOutputStream.toString();
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;

public class TcpClientDemo1 {
public static void main(String[] args) {
try {
InetAddress iadress = InetAddress.getByName("127.0.0.1");
int port = 9999;
Socket socket = new Socket(iadress, port);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("欢迎你".getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}

传文件

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package mmm.tcp;

import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServerDemo2 {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(9999)) {
try(Socket socket = serverSocket.accept()){
try(InputStream inputStream = socket.getInputStream()){
try(FileOutputStream fileOutputStream = new FileOutputStream("test.iml")){
inputStream.transferTo(fileOutputStream);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package mmm.tcp;

import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class TcpClientDemo2 {
public static void main(String[] args) {
try (Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999)) {
try (OutputStream outputStream = socket.getOutputStream()) {
try (FileInputStream fileInputStream = new FileInputStream("kuangDemo.iml")) {
fileInputStream.transferTo(outputStream);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

UDP

消息发送

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UdpClient {
public static void main(String[] args) {
try (DatagramSocket datagramSocket = new DatagramSocket()) {
String msg = "你好啊";
InetAddress inetAddress = InetAddress.getByName("localhost");
int port = 9090;
DatagramPacket packet = new DatagramPacket(msg.getBytes(StandardCharsets.UTF_8), 0, msg.getBytes(StandardCharsets.UTF_8).length, inetAddress, port);
datagramSocket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Server

1
2
3
4
5
6
7
8
9
10
11
12
public class UdpServer {
public static void main(String[] args) {
try(DatagramSocket datagramSocket = new DatagramSocket(9090)){
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
datagramSocket.receive(datagramPacket);
System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
}catch (Exception e){
e.printStackTrace();
}
}
}

聊天

TalkReiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TalkReceiver implements Runnable{
DatagramSocket datagramSocket = null;
private int port;
private String msgFrom;

public TalkReceiver(int port,String msgFrom) {
this.port = port;
this.msgFrom = msgFrom;
try {
datagramSocket = new DatagramSocket(this.port);
} catch (SocketException e) {
e.printStackTrace();
}
}

@Override
public void run() {
try{
while (true) {
byte[] buffer = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, 0, buffer.length);
datagramSocket.receive(datagramPacket);
System.out.println(msgFrom+":"+new String(datagramPacket.getData()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

TalkSender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class TalkSender implements Runnable {
DatagramSocket datagramSocket = null;
BufferedReader bufferedReader = null;
int fromPort;
int toPort;
String toIp;


public TalkSender(int fromPort, String toIp, int toPort) {
this.fromPort = fromPort;
this.toPort = toPort;
this.toIp = toIp;

try {
datagramSocket = new DatagramSocket(this.fromPort);
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}

@Override
public void run() {
try {
while (true) {
String data = bufferedReader.readLine();
byte[] datas = data.getBytes(StandardCharsets.UTF_8);
DatagramPacket datagramPacket = new DatagramPacket(datas, 0, datas.length, InetAddress.getByName(this.toIp), this.toPort);
datagramSocket.send(datagramPacket);
}
}catch (Exception e){
e.printStackTrace();
}
}
}

Student

1
2
3
4
5
6
public class Student {
public static void main(String[] args) {
new Thread(new TalkSender(4444,"localhost",6666)).start();
new Thread(new TalkReceiver(8888,"老师")).start();
}
}

Teacher

1
2
3
4
5
6
public class Teacher {
public static void main(String[] args) {
new Thread(new TalkSender(9999,"localhost",8888)).start();
new Thread(new TalkReceiver(6666,"老师")).start();
}
}

URL

1
2
3
4
5
6
7
8
9
10
11
12
public class UrlDown {
public static void main(String[] args) throws Exception {
URL url = new URL("http://xuexuan.site/images/avatar.png");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try (InputStream inputStream = urlConnection.getInputStream()) {
try(FileOutputStream os = new FileOutputStream("avatar.png")){
inputStream.transferTo(os);
}
}
urlConnection.disconnect();
}
}

Servlet

基础

1.Maven:注意打包方式为war,dependencies中的scope为provided,确保只在编译时使用,不会打包到war文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>web-servlet-hello</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
</properties>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>hello</finalName>
</build>
</project>

2.最简单的Servlet:类必须继承自HttpServlet,然后通过覆写doGet或doPost来实现请求处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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();
}
}

3.之后,需要在工程目录下src/main/webapp/WEB-INF目录下,创建一个web.xml描述文件。文件内容可以固定如下:

1
2
3
4
5
6
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>

4.需要先启动web服务器,然后运行war,让服务器加载Servlet来处理请求。

常用的支持Servlet API的Web服务器有:Tomcat,Jetty,GlassFish

下载Tomcat后,解压,将打包好的hello.war复制到webapps目录下,然后进入bin目录,执行startup文件。

通过localhost:8080/hello便可以进行网站的访问。

环境快速搭建

Idea 可以直接在Configuration中进行配置

Tomcat实际也是Java程序,启动流程如下:

  1. 启动JVM并执行Tomcat的main()方法;
  2. 加载war并初始化Servlet;
  3. 正常服务。

可以将Tomcat的jar包全部引进来,然后自己编写一个main方法,先启动Tomcat,然后加载webapp

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itranswarp.learnjava</groupId>
<artifactId>web-servlet-embedded</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
<tomcat.version>9.0.26</tomcat.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

Tomcat中包含了Servlet API引用

main方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
}
}

下载

1

进阶

HttpServletRequest

HttpServletRequest封装了一个HTTP请求,它实际上是从ServletRequest继承而来。最早设计Servlet时,设计者希望Servlet不仅能处理HTTP,也能处理类似SMTP等其他协议,因此,单独抽出了ServletRequest接口,但实际上除了HTTP外,并没有其他协议会用Servlet处理,所以这是一个过度设计。

我们通过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"

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;

写入完毕后调用flush是必须的,因为大部分Web服务器都基于HTTP/1.1协议,会复用TCP连接。

写入完毕后不要调用close

重定向与转发

重定向
1
2
3
4
5
6
7
8
9
10
@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);
}
}

重定向有两种:一种是302响应,称为临时重定向,一种是301响应,称为永久重定向。两者的区别是,如果服务器发送301永久重定向响应,浏览器会缓存/hi/hello这个重定向的关联,下次请求/hi的时候,浏览器就直接发送/hello请求了。

redirect()方法用于实现302重定向。

301重定向的实现方式:

1
2
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
resp.setHeader("Location","/hello");
Forward

Forward指内部转发。一个Servlet将请求转发给另一个Servlet。

1
2
3
4
5
6
@WebServlet(urlPatterns="/morning")
public class ForwardServlet extends HttpServlet{
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
req.getRequestDispatcher("/hello").forward(req, resp);
}
}

使用Session和Cookie

Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@WebServlet(urlPatterns="/signin")
public class SignInServlet extends HttpServlet{
// 模拟一个数据库:
private Map<String, String> users = Map.of("bob", "bob123", "alice", "alice123", "tom", "tomcat");

// GET请求时显示登录页:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Sign In</h1>");
pw.write("<form action=\"/signin\" method=\"post\">");
pw.write("<p>Username: <input name=\"username\"></p>");
pw.write("<p>Password: <input name=\"password\" type=\"password\"></p>");
pw.write("<p><button type=\"submit\">Sign In</button> <a href=\"/\">Cancel</a></p>");
pw.write("</form>");
pw.flush();
}

// POST请求时处理用户登录:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
String password = req.getParameter("password");
String expectedPassword = users.get(name.toLowerCase());
if (expectedPassword != null && expectedPassword.equals(password)) {
// 登录成功:
req.getSession().setAttribute("user", name);
resp.sendRedirect("/");
} else {
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
}

@WebServlet(urlPatterns = "/")
public class IndexServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从HttpSession获取当前用户名:
String user = (String) req.getSession().getAttribute("user");
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.setHeader("X-Powered-By", "JavaEE Servlet");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Welcome, " + (user != null ? user : "Guest") + "</h1>");
if (user == null) {
// 未登录,显示登录链接:
pw.write("<p><a href=\"/signin\">Sign In</a></p>");
} else {
// 已登录,显示登出链接:
pw.write("<p><a href=\"/signout\">Sign Out</a></p>");
}
pw.flush();
}
}

@WebServlet(urlPatterns = "/signout")
public class SignOutServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从HttpSession移除用户名:
req.getSession().removeAttribute("user");
resp.sendRedirect("/");
}
}

JSP开发

JSP(Java Server Pages),文件必须放到/src/main/webapp下,文件名必须以.jsp结尾,文件与HTML的差别不大,但是需要插入变量,或者动态输出的地方,使用特殊指令<%...%>

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<title>Hello World - JSP</title>
</head>
<body>
<%-- JSP Comment --%>
<h1>Hello World!</h1>
<p>
<%
out.println("Your IP address is ");
%>
<span style="color:red">
<%= request.getRemoteAddr() %>
</span>
</p>
</body>
</html>

整个JSP的内容实际上是一个HTML,但是稍有不同:

  • 包含在<%----%>之间的是JSP的注释,它们会被完全忽略;
  • 包含在<%%>之间的是Java代码,可以编写任意Java代码;
  • 如果使用<%= xxx %>则可以快捷输出一个变量的值。

JSP页面内置的几个变量:

  • out:表示HttpServletResponse的PrintWriter
  • session:表示当前的HttpSession对象。
  • request:表示HttpSevletRequest对象。

JSP可以通过page指令

1
2
<%@ page import="java.io.*" %>
<%@ page import="java.util.*" %>

这样后续的Java代码才能引用简单类名而不是完整类名。

使用include指令可以引入另一个JSP文件:

1
2
3
4
5
6
<html>
<body>
<%@ include file="header.jsp"%>
<h1>Index Page</h1>
<%@ include file="footer.jsp"%>
</body>

MVC开发

  • Servlet适合编写Java代码,实现各种复杂的业务逻辑,但不适合输出复杂的HTML;
  • JSP适合编写HTML,并在其中插入动态内容,但不适合编写复杂的Java代码。

将两者结合起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class User{
public long id;
public String name;
public School school;
}

public class School{
public String name;
public String address;
}

@WebServlet(urlPatterns = "/user")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 假装从数据库读取:
School school = new School("No.1 Middle School", "101 South Street");
User user = new User(123, "Bob", school);
// 放入Request中:
req.setAttribute("user", user);
// forward给user.jsp:
req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp);
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page import="com.itranswarp.learnjava.bean.*"%>
<%
User user = (User) request.getAttribute("user");
%>
<html>
<head>
<title>Hello World - JSP</title>
</head>
<body>
<h1>Hello <%= user.name %>!</h1>
<p>School Name:
<span style="color:red">
<%= user.school.name %>
</span>
</p>
<p>School Address:
<span style="color:red">
<%= user.school.address %>
</span>
</p>
</body>
</html>
  • user.jsp放到/WEB-INF/目录下,是因为WEB-INF是一个特殊目录,Web Server会阻止浏览器对WEB-INF目录下任何资源的访问,这样就防止用户通过/user.jsp路径直接访问到JSP页面;

我们把UserServlet看作业务逻辑处理,把User看作模型,把user.jsp看作渲染,这种设计模式通常被称为MVC:Model-View-Controller,即UserServlet作为控制器(Controller),User作为模型(Model),user.jsp作为视图(View),整个MVC架构如下:

1
2
3
4
5
6
7
8
9
10
11
12
                   ┌───────────────────────┐
┌────>│Controller: UserServlet│
│ └───────────────────────┘
│ │
┌───────┐ │ ┌─────┴─────┐
│Browser│────┘ │Model: User│
│ │<───┐ └─────┬─────┘
└───────┘ │ │
│ ▼
│ ┌───────────────────────┐
└─────│ View: user.jsp │
└───────────────────────┘

使用MVC模式的好处是,Controller专注于业务处理,它的处理结果就是Model。Model可以是一个JavaBean,也可以是一个包含多个对象的Map,Controller只负责把Model传递给View,View只负责把Model给“渲染”出来,这样,三者职责明确,且开发更简单,因为开发Controller时无需关注页面,开发View时无需关心如何创建Model。

MVC模式广泛地应用在Web页面和传统的桌面程序中,我们在这里通过Servlet和JSP实现了一个简单的MVC模型,但它还不够简洁和灵活,后续我们会介绍更简单的Spring MVC开发。

MVC高级开发

https://www.liaoxuefeng.com/wiki/1252599548343744/1337408645759009

直接把MVC搭在Servlet和JSP之上还是不太好,原因如下:

  • Servlet提供的接口仍然偏底层,需要实现Servlet调用相关接口;
  • JSP对页面开发不友好,更好的替代品是模板引擎;
  • 业务逻辑最好由纯粹的Java类实现,而不是强迫继承自Servlet。

通过普通的Java类实现MVC的Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserController {
@GetMapping("/signin")
public ModelAndView signin() {
...
}

@PostMapping("/signin")
public ModelAndView doSignin(SignInBean bean) {
...
}

@GetMapping("/signout")
public ModelAndView signout(HttpSession session) {
...
}
}

需要进一步的深入研究

使用Filter