JavaWeb第4篇Web基础

基本概念

通过实现Servlet来进行动态网页响应,使用Servlet,不再是直接由Tomcat服务器发送我们编写好的静态网页内容(HTML文件),而是由我们通过Java代码进行动态拼接的结果,它能够很好地实现动态网页的返回。

普通的Java程序是通过启动JVM,然后执行main()方法开始运行。但是Web应用程序有所不同,我们无法直接运行war文件,必须先启动Web服务器,再由Web服务器加载我们编写的HelloServlet,这样就可以让HelloServlet处理浏览器发送的请求。因此,我们首先要找一个支持Servlet API的Web服务器。常用的服务器有:

  • Tomcat:由Apache开发的开源免费服务器;
  • Jetty:由Eclipse开发的开源免费服务器;
  • GlassFish:一个开源的全功能JavaEE服务器。
    还有一些收费的商用服务器,如Oracle的WebLogic,IBM的WebSphere。

配置Tomcat

Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。Tomcat8的下载地址如下:
下载地址:https://tomcat.apache.org/download-80.cgi

本地配置

需要注意的是在IDEA高版本之后,创建web项目需要选择Java Enterprise。模板选择Web application,服务器选择本地的tomcat。如果不需maven插件,可以后期删除。

maven插件

首先安装Maven helper插件。然后再pom.xml文件中添加相关的插件方便修改端口等操作。最后在pom.xml文件中可以右键选择以maven插件方式运行tomcat。

配置参考

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
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>TomcatMaven</artifactId>
<version>1.0-SNAPSHOT</version>
<name>TomcatMaven</name>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

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

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 设置编码格式 -->
<uriEncoding>UTF-8</uriEncoding>
<!-- 控制 tomcat 端口号 -->
<port>8099</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

Servlet

它是Java EE的一个标准,大部分的Web服务器都支持此标准,包括Tomcat,就像之前的JDBC一样,由官方定义了一系列接口,而具体实现由我们来编写,最后交给Web服务器(如Tomcat)来运行我们编写的Servlet。

可以通过实现Servlet来进行动态网页响应,使用Servlet,不再是直接由Tomcat服务器发送我们编写好的静态网页内容(HTML文件),而是由我们通过Java代码进行动态拼接的结果,它能够很好地实现动态网页的返回。

创建方法

1.首先先实现Servlet接口
2.可以通过注解或者web.xml方式来注册Servlet

1
2
3
4
5
//注解的方式
@WebServlet("/test")
public class TestServlet implements Servlet {
...实现接口方法
}
1
2
3
4
5
6
7
8
9
<!-- xml文件的方式 -->
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>com.example.webtest.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>

生命周期

Servlet的生命周期为:

  • 首先执行构造方法完成 Servlet 初始化
  • Servlet 初始化后调用 init () 方法
  • Servlet 调用 service() 方法来处理客户端的请求
  • Servlet 销毁前调用 destroy() 方法
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的

基本示例

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
package com.eldpepar;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/init")
public class LifeSevlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("这里是初始化的方法");
}

@Override
public ServletConfig getServletConfig() {
System.out.println("----getServletConfig");
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("----service");
//首先将其转换为HttpServletRequest(继承自ServletRequest,一般是此接口实现)
HttpServletRequest request = (HttpServletRequest) servletRequest;

System.out.println(request.getProtocol()); //获取协议版本
System.out.println(request.getRemoteAddr()); //获取访问者的IP地址
System.out.println(request.getMethod()); //获取请求方法

//转换为HttpServletResponse(同上)
HttpServletResponse response = (HttpServletResponse) servletResponse;
//设定内容类型以及编码格式(普通HTML文本使用text/html)
response.setHeader("Content-type", "text/html;charset=UTF-8");
//获取Writer直接写入内容
response.getWriter().write("我是响应内容!");
}

@Override
public String getServletInfo() {
System.out.println("----getServletInfo");
return null;
}

@Override
public void destroy() {
System.out.println("----destroy");
}
}

HttpServletRequest和HttpServletResponse

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

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

常用HttpServletRequest方法

  • 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方法

  • 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

基本示例

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

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

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getProtocol()); //获取协议版本
System.out.println(req.getRemoteAddr()); //获取访问者的IP地址
System.out.println(req.getMethod()); //获取请求方法

//设定内容类型以及编码格式(普通HTML文本使用text/html)
resp.setHeader("Content-type", "text/html;charset=UTF-8");
//获取Writer直接写入内容
resp.getWriter().write("我是响应内容!");
}
}

WebServlet注解

为了简化 Servlet 的配置,Servlet 3.0 中增加了注解支持,例如:@WebServlet、@WebInitParm 、@WebFilter 和 @WebLitener 等,这使得 web.xml 从 Servlet 3.0 开始不再是必选项了。

示例代码

1
2
3
4
5
6
7
8
9
10
11
//urlPatterns和value等价
@WebServlet(urlPatterns = "/hello")

//通配匹配
@WebServlet("/p/*")

//扩展匹配
@WebServlet("*.js")

//多个路径
@WebServlet({"/u1", "/u2"})

重定向与转发

重定向是指当浏览器请求一个URL时,服务器返回一个重定向指令,告诉浏览器地址已经变了,麻烦使用新的URL再重新发送新请求。重定向有两种:一种是302响应,称为临时重定向,一种是301响应,称为永久重定向。

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

两者区别

  • 请求转发是一次请求,重定向是两次请求
  • 请求转发地址栏不会发生改变, 重定向地址栏会发生改变
  • 请求转发可以共享请求参数 ,重定向之后,就获取不了共享参数了
  • 请求转发只能转发给内部的Servlet

示例代码

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
package com.eldpepar;
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.IOException;

@WebServlet("/user")
public class NewUrlDemo extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//重定向示例
String name = req.getParameter("name");
if (name.equals("李四")) {
resp.sendRedirect("https://bilibili.com");
}
//301永久重定向
/* if (name.equals("张三")) {
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301
resp.setHeader("Location", "/");
}*/

//转发示例
if (name.equals("张三")) {
req.getRequestDispatcher("/").forward(req,resp);
}
}
}

Cookie和Session

基于唯一ID识别用户身份的机制称为Session。每个用户第一次访问服务器后,会自动获得一个Session ID。如果用户在一段时间内没有访问服务器,那么Session会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID

HTTP cookie,简称cookie,是在用户浏览网站时由网络服务器创建并由用户的网页浏览器存放在用户计算机或其他设备上的小文本文件。Cookie使Web服务器能够在用户的设备上存储状态信息(如添加到在线商店购物车中的商品)或跟踪用户的浏览活动(如点击特定按钮、登录或记录历史)

浏览器在请求某个URL时,是否携带指定的Cookie,取决于Cookie是否满足以下所有要求:

  • URL前缀是设置Cookie时的Path;
  • Cookie在有效期内;
  • Cookie设置了secure时必须以https访问

登录注册

这里使用了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
package com.eldpepar;

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.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/")
public class IndexServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, 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=\"/login\">Sign In</a></p>");
} else {
// 已登录,显示登出链接:
pw.write("<p><a href=\"/logout\">Sign Out</a></p>");
}
pw.flush();
}
}

登录页

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
package com.eldpepar.user;

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.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

@WebServlet(urlPatterns = "/login")
public class SignInServlet extends HttpServlet {
// 模拟一个数据库:
private Map<String, String> users = new HashMap<>();

// 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=\"/login\" 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 {
users.put("zhangsan", "123456");
users.put("lisi", "123456");
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);
}
}
}

退出页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.eldpepar.user;

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

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

Filter

过滤器相当于在所有访问前加了一堵墙,来自浏览器的所有访问请求都会首先经过过滤器,只有过滤器允许通过的请求,才可以顺利地到达对应的Servlet,而过滤器不允许的通过的请求,我们可以自由地进行控制是否进行重定向或是请求转发。并且过滤器可以添加很多个

登录过滤器

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
package com.eldpepar.user;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter init...");
}

//登录过滤器,没有登录一律返回主页
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
String user = (String)req.getSession().getAttribute("user");
//2.获取请求资源路径
String requestURI = req.getRequestURI();
//3.判断是否包含登录相关资源路径,同时排除css,js,图片等
if (requestURI.contains("/login") || requestURI.contains("/") ) {
//放行
filterChain.doFilter(req, resp);
} else {
if (user != null) {
//已登录放行
filterChain.doFilter(req, resp);
} else {
//未登录,跳转登陆页面
req.getRequestDispatcher("/login").forward(req,resp);
}
}
}

@Override
public void destroy() {
Filter.super.destroy();
}
}

Listener

监听器,从字面上可以看出listener主要用来监听只用。通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。通俗的语言说就是在application,session,request三个对象创建消亡或者往其中添加修改删除属性时自动执行代码的功能组件。


JavaWeb第4篇Web基础
https://www.eldpepar.com/coding/10673/
作者
EldPepar
发布于
2022年11月2日
许可协议