廖雪峰java-Web开发 1

廖雪峰java-Web开发 1

1
2
3
java ee
jave se
jave me
1
2
j2ee
servlet

Web基础

1
B/S

HTTP协议

1
2
3
4
5
6
7
8
html
tcp
HTTP/1.1

服务器的响应、浏览器发送的HTTP请求
遇到两个连续的\r\n
2xx表示成功,3xx表示重定向,4xx表示客户端引发的错误,5xx表示服务器端引发的错误
编写服务器程序来处理客户端请求通常就称之为Web开发。

编写HTTP Server

1
2
3
4
5
6
7
8
9
我们来看一下如何编写HTTP Server。一个HTTP Server本质上是一个TCP服务器,我们先用TCP编程的多线程实现的服务器端框架:

实现一个最简单的Servlet
通过Maven来引入它
war
Servlet版本 4.0 5.0
Tomcat版本 9.0 10.0
整个工程结构
支持Servlet API的Web服务器

Servlet入门

1
2
3
4
5
6
7
8
9
10
最简单的Servlet
Servlet API是一个jar包,我们需要通过Maven来引入它
war

整个工程结构
支持Servlet API的Web服务器

Servlet版本 4.0 5.0
Tomcat版本 9.0 10.0
类似Tomcat这样的Web服务器也称为Servlet容器

Servlet开发

1
2
3
如何在IDE中启动Tomcat并加载webapp
不必引入Servlet API,因为引入Tomcat依赖后自动引入了Servlet API

Servlet进阶

1
2
3
4
多个Servlet
HttpServletRequest
HttpServletResponse
Servlet多线程模型

重定向与转发

Redirect
1
2
302响应
301响应
Forward

JSP开发

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

MVC开发

1
UserServlet作为控制器(Controller),User作为模型(Model),user.jsp作为视图(View)

MVC高级开发

controller

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
public class UserController {

private Map<String, User> userDatabase = new HashMap<>() {
{
List<User> users = List.of( //
new User("bob@example.com", "bob123", "Bob", "This is bob."), new User("tom@example.com", "tomcat", "Tom", "This is tom."));
users.forEach(user -> {
put(user.email, user);
});
}
};

@GetMapping("/signin")
public ModelAndView signin() {
return new ModelAndView("/signin.html");
}

@PostMapping("/signin")
public ModelAndView doSignin(SignInBean bean, HttpServletResponse response, HttpSession session) throws IOException {
User user = userDatabase.get(bean.email);
if (user == null || !user.password.equals(bean.password)) {
response.setContentType("application/json");
PrintWriter pw = response.getWriter();
pw.write("{\"error\":\"Bad email or password\"}");
pw.flush();
} else {
session.setAttribute("user", user);
response.setContentType("application/json");
PrintWriter pw = response.getWriter();
pw.write("{\"result\":true}");
pw.flush();
}
return null;
}

DispatcherServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {
private Map<String, GetDispatcher> getMappings = new HashMap<>();
private Map<String, PostDispatcher> postMappings = new HashMap<>();
private ViewEngine viewEngine;

@Override
public void init() throws ServletException {

@Override
protected void doGet(

@Override
protected void doPost(

private void process(

GetDispatcher

1
2
3
4
5
abstract class AbstractDispatcher {

public abstract ModelAndView invoke(HttpServletRequest request, HttpServletResponse response)
throws IOException, ReflectiveOperationException;
}
1
2
3
4
5
6
class GetDispatcher extends AbstractDispatcher {

@Override
public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response){
return (ModelAndView) this.method.invoke(this.instance, arguments);
}

PostDispatcher

1
2
3
4
5
class PostDispatcher extends AbstractDispatcher {
@Override
public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response)
throws IOException, ReflectiveOperationException {
return (ModelAndView) this.method.invoke(instance, arguments);

ModelAndView

1
2
3
4
public class ModelAndView {

Map<String, Object> model;
String view;

ViewEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ViewEngine {

private final PebbleEngine engine;

public ViewEngine(ServletContext servletContext) {
var loader = new Servlet5Loader(servletContext);
loader.setCharset("UTF-8");
loader.setPrefix("/WEB-INF/templates");
loader.setSuffix("");
this.engine = new PebbleEngine.Builder().autoEscaping(true).cacheActive(false) // no cache for dev
.loader(loader).build();
}

public void render(ModelAndView mv, Writer writer) throws IOException {
PebbleTemplate template = this.engine.getTemplate(mv.view);
template.evaluate(writer, mv.model);
}
}
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
好的,结合两次提供的代码,我可以为你详细解释GetDispatcher类及其invoke方法的功能。

GetDispatcher 类
这个类似乎是为了简化或自动化对控制器(Controller)方法的调用过程。它存储了控制器实例、要调用的方法、以及方法的参数名称和类型。

invoke 方法
invoke方法是这个类的核心。它接受一个HttpServletRequest和一个HttpServletResponse作为参数,然后尝试调用之前存储的控制器方法,并返回ModelAndView对象(这通常是Spring MVC框架中用于封装视图和模型数据的类)。

方法流程
初始化参数数组:
方法首先创建了一个与参数类型数组parameterClasses长度相同的Object数组arguments,用于存储将要传递给控制器方法的参数值。

遍历参数:
然后,方法遍历每个参数的类型和名称。

参数值设置:
对于每个参数,方法会检查其类型,并根据类型从HttpServletRequest中提取相应的值或设置默认值。例如:

如果参数类型是HttpServletRequest或HttpServletResponse,方法会直接使用传入的request或response对象。
如果参数类型是HttpSession,方法会从request中获取会话对象。
如果参数类型是int、long或boolean,方法会尝试从request的参数中获取对应的字符串值,并将其转换为相应的基本数据类型。如果找不到参数,会使用默认值(如"0"或"false")。
如果参数类型是String,方法会直接从request中获取对应的字符串参数值,如果没有找到则使用空字符串。
如果参数类型不是上述任何一种,方法会抛出一个运行时异常,因为没有为这种类型提供处理逻辑。
调用方法:
一旦所有参数都设置好,方法就会使用Method类的invoke方法来调用之前存储的控制器方法。这个方法需要两个参数:第一个是控制器实例(this.instance),第二个是一个包含所有方法参数的数组(arguments)。

返回结果:
方法调用完成后,invoke方法会将结果转换为ModelAndView类型并返回。如果控制器方法返回的不是ModelAndView类型,这里可能会抛出ClassCastException。

getOrDefault 方法
getOrDefault方法是一个辅助方法,用于从HttpServletRequest中获取指定名称的参数值。如果找不到该参数,它会返回提供的默认值。

总结
GetDispatcher类及其invoke方法似乎是为了简化从Web请求到控制器方法调用的过程。它允许你预先配置要调用的方法和其参数类型,然后在运行时根据请求动态地填充参数并调用方法。这种机制在某些框架或自定义Web应用中可能会很有用,特别是当你想要将HTTP请求直接映射到Java方法调用时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
this.method.invoke(this.instance, arguments); 这一行代码是Java反射(Reflection)API的一个典型用法。

在Java中,反射允许程序在运行时检查类、接口、字段和方法的信息,并可以动态地调用方法、创建对象等。Method类中的invoke方法是反射API中的一个重要方法,它允许你动态地调用一个对象的方法。

具体来说,this.method.invoke(this.instance, arguments); 这行代码做了以下几件事:

this.method:这部分表示当前GetDispatcher对象存储的一个Method对象,该对象代表了要调用的控制器方法。

invoke:这是Method类的一个方法,用于调用该Method对象代表的方法。

this.instance:这是invoke方法的第一个参数,表示要调用方法的目标对象实例。在这个上下文中,this.instance应该是某个控制器类的实例。

arguments:这是invoke方法的第二个参数,是一个Object数组,包含了要传递给被调用方法的参数值。这些参数值是根据请求中的参数和invoke方法之前的逻辑来设置的。

当这行代码执行时,它会动态地在this.instance(控制器实例)上调用this.method(控制器方法),并传递arguments数组作为参数。调用方法的结果(即方法的返回值)会被invoke方法返回,然后可以在invoke方法中进一步处理或使用。

需要注意的是,invoke方法可能会抛出多种异常,包括IllegalAccessException(如果调用方法不可访问)、InvocationTargetException(如果调用方法本身抛出了异常)等。因此,在实际使用中,通常需要对这些异常进行处理。

此外,由于反射涉及到在运行时解析和操作类型信息,它通常比直接调用方法要慢一些。因此,在性能敏感的应用中,应谨慎使用反射。

设计MVC框架

实现渲染

使用Filter

修改请求

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
@WebServlet(urlPatterns = "/upload/file")
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 读取Request Body:
InputStream input = req.getInputStream();
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (;;) {
int len = input.read(buffer);
if (len == -1) {
break;
}
output.write(buffer, 0, len);
}
// TODO: 写入文件:
// 显示上传结果:
String uploadedText = output.toString(StandardCharsets.UTF_8);
PrintWriter pw = resp.getWriter();
pw.write("<h1>Uploaded:</h1>");
pw.write("<pre><code>");
pw.write(uploadedText);
pw.write("</code></pre>");
pw.flush();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
InputStream input = req.getInputStream();:从请求中获取输入流,用于读取请求体中的数据。
ByteArrayOutputStream output = new ByteArrayOutputStream();:创建一个字节数组输出流,用于暂存从输入流中读取的数据。
byte[] buffer = new byte[1024];:定义一个字节数组作为缓冲区,用于从输入流中读取数据。
循环读取输入流中的数据,直到读取完毕(read()方法返回-1)。每次读取的数据都会写入到output输出流中。

// TODO: 写入文件:
这是一个待完成的部分。目前代码只是读取了上传的数据并暂存到内存中,但还没有将其写入到磁盘上的文件中。通常,你会在这里添加代码,将数据写入到服务器的某个目录中。

String uploadedText = output.toString(StandardCharsets.UTF_8);:将暂存在output输出流中的数据转换为UTF-8编码的字符串。
PrintWriter pw = resp.getWriter();:获取响应的写入器,用于向客户端发送响应数据。
使用pw.write()方法,向响应中写入HTML格式的文本,显示上传的内容。
pw.flush();:确保所有缓冲的输出都被发送到客户端。
1
2
3
4
5
6
7
8
9
10
11
在这段代码中,byte[] buffer = new byte[1024]; 创建了一个字节数组 buffer,其大小为 1024 字节。这里是详细的解释:

byte[]:这是Java中的一个数据类型,表示一个字节数组。数组是一个可以存储多个同类型元素的集合,而byte是Java的基本数据类型之一,用于存储8位有符号二进制数,即一个字节。

new byte[1024]:new 关键字用于在Java中创建对象。在这里,它用于创建一个大小为1024的byte数组。数组的大小是固定的,一旦创建,就不能改变。

buffer:这是给这个新创建的字节数组变量命名的变量名。在后续的代码中,你可以使用这个变量名来引用和操作这个数组。

在文件上传的上下文中,这个字节数组buffer用作一个缓冲区。当从输入流input中读取数据时,数据不会一次性全部读入,而是分块读取。每次读取操作都会将最多1024字节的数据从输入流中读入到buffer数组中。然后,这些数据会被写入到输出流output中。这个过程会一直重复,直到输入流中的所有数据都被读取完毕。

使用缓冲区的好处是可以减少内存消耗,特别是在处理大文件时。如果一次性将整个文件读入内存,可能会导致内存溢出。通过使用缓冲区,我们可以一次只处理文件的一小部分,从而有效地管理内存使用。

使用Listener

1
2
3
4
5
ServletContextListener:它会在整个Web应用程序初始化完成后,以及Web应用程序关闭后获得回调通知。
HttpSessionListener:监听HttpSession的创建和销毁事件;
ServletRequestListener:监听ServletRequest请求的创建和销毁事件;
ServletRequestAttributeListener:监听ServletRequest请求的属性变化事件(即调用ServletRequest.setAttribute()方法);
ServletContextAttributeListener:监听ServletContext的属性变化事件(即调用ServletContext.setAttribute()方法);

ServletContext

部署