1、基本概念
1.1、前言
百度百科对于Javaweb给出的解释:
Java Web,是用Java技术来解决相关web互联网领域的技术栈。web包括:web服务端和web客户端两部分。Java在客户端的应用有java applet,不过使用得很少,Java在服务器端的应用非常的丰富,比如Servlet,JSP和第三方框架等等。Java技术对Web领域的发展注入了强大的动力。
web开发:
web 就是网页的意思,其中web又分为静态web和动态web
- 静态web
- html,css编写的静态页面
- 提供给每个人的页面都是相同的,不会因为用户请求的不同而作出改变
- 动态web
- 几乎所有网站都是动态web
- 提供给每个人的页面都是不同的,会根据每个用户的请求而返回不同的响应,展示不同的页面效果
- 技术栈:Servlet/jsp, ASP, PHP
- 在Java中,动态web资源开发的技术统称为Javaweb
1.2、web应用程序
web应用程序:由客户端(Client)与服务器端(Server)两个部分组成,客户端基本是浏览器(Browser),服务器端则是HTTP服务器,浏览器会请求服务器上放置的文件或资源。服务器上的文件或资源必须产生HTML。
- a.html、b.html……多个web资源,可以被外界访问,对外界提供服务
- 能在网上访问到的任何一个页面或者资源,都能在世界上的某台计算机上找到本地文件
- URL:统一资源定位系统(uniform resource locator;URL)是因特网的万维网服务程序上用于指定信息位置的表示方法。
- 这些统一的web资源都会存放于同一个文件夹下,web应用程序==>Tomcat服务器
- 一个web应用程序由多个部分组成(静态web, 动态web)
- html, css, js
- jsp, servlet
- Java程序
- jar包
- 配置文件(Properties)
1.3、HTTP
HTTP是一种通信协议,指架构在TCP/IP之上的应用层的一种协议。
按照不同的联机方式与所使用的网络服务,会有不同的通信协议。如
- SMTP:发送信件
- FTP:传输文件
- POP3:下载信件
- HTTP:浏览器与web服务器之间的沟通方式
HTTP的两个重要特性:
- 基于请求(Request)/响应(Response)模型
- 无状态(Stateless)通信协议
HTTP是一种基于请求/响应的通信协议,客户端对服务器发出一个取得资源的请求,服务器将要求的资源响应给客户端,每次联机只做一次请求/响应,是一种很简单的通信协议,没有请求就不会有响应。
在HTTP协议下,服务器响应完客户端之后,就不会保存客户端的信息,更不会去维护与客户端的有关状态,因此HTTP又称为无状态(Stateless)的通信协议。
浏览器使用HTTP发出请求的几种请求方法:
- GET
- POST
- HEAD
- PUT
- DELETE
对于编写Servlet与JSP来说,比较常用的是GET与POST请求。
1. GET请求
向服务器取得(GET)指定的资源,在发出GET请求时,必须一并告诉服务器所请求资源的URL,以及一些标头(Header)信息。
请求参数通常是用户发送给服务器的必要信息,这个信息通常利用窗体来进行发送,服务器必须有这些信息才可以进一步针对用户的请求作出正确的响应。请求参数是在URL之后跟随一个问号(?),然后是请求参数名称(name)与请求参数值(value),中间以等号(=)表示成对关系。若有多个请求参数,则以&字符连接。
使用GET方式发送请求时,浏览器的地址栏上会出现请求的参数信息。
GET请求可以发送的请求参数长度有限,对于太大量的数据并不适合用GET方式来进行请求,这时可以改用POST。
2.POST请求
在请求时发布(POST)信息给服务器,对于大量或复杂的信息发送(如文件上传),基本上会采用POST方式来进行发送。
使用POST时,请求参数会移至信息体,地址栏上不会出现请求参数。所以对于一些敏感信息,通常会使用POST方式发送。
3.如何选用GET或POST
从功能面上
- 过长的请求参数,或者文件上传这类的大量数据,应该使用POST请求
- 敏感性或有安全性考虑的请求参数,应该使用POST请求
- POST请求的请求参数不会出现在浏览器的地址栏上,所以无法加入浏览器的书签(Bookmark)中。如果希望用户可以设定书签,以便可以直接点击书签浏览对应的页面,应该使用GET请求
- 浏览器一般会缓存(Cache)网址数据,如果网址是相同的URL,则不会重新发送请求到服务器查询最新数据,而是直接从缓存中取出旧数据。如果不希望这样,可以使用POST请求。(使用GET请求来避免缓存时,可以在网址上附加时间戳,让每次GET请求的URL都不同)
从非功能面上
- GET请求属于等幂操作。因为GET请求纯粹是获取资源,而不改变服务器上的数据或状态。
- POST请求属于非等幂操作。使用POST发送数据,可能会影响服务器上的数据或状态,例如对数据库的内容进行增删改查。如果请求会改变服务器的数据或状态,应该使用POST请求。
请求和响应的属性
下面是b站一次请求和响应的部分属性
General
// 请求URL
Request URL: https://www.bilibili.com/
// 请求方式为GET
Request Method: GET
// 状态码,200表示成功
Status Code: 200
// 最终请求服务器地址
Remote Address: 110.43.34.72:443
// Referrer 报头传递政策,此时no-referrer-when-downgrade指的是:当发生降级(比如从 https:// 跳转到 // http:// )时,不传递 Referrer 报头。但是反过来的话不受影响。通常也会当作浏览器的默认安全策略。
Referrer Policy: no-referrer-when-downgrade
Request Headers
// 客户端能接受的响应类型
accept: text/html,application/xhtml+xml,application/xml;q=0.9, image/webp,image/apng, */ *;q=0.8,application/signed-exchange;v=b3;q=0.9
// 客户端可接受的编码列表
accept-encoding: gzip, deflate, br
// 客户端语言
accept-language: zh-CN,zh;q=0.9
// 缓存控制:用于指定请求-响应链上所有缓存机制必须遵循的指令。
// 这里的缓存周期为0s
cache-control: max-age=0
Response Headers
// 缓存控制:这里的缓存周期为30s,超过30s则缓存过期
cache-control: max-age=30
// 返回的数据中使用的编码类型
content-encoding: gzip
// 返回内容的MIME类型
content-type: text/html; charset=utf-8
// 发送消息的日期和时间
date: Wed, 29 Apr 2020 09:35:19 GMT
// 状态码
status: 200
如果要查看所有请求响应属性的详细信息,可参考https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
响应状态码
响应状态码是服务器端返回的一个参数,用来告知客户端本次请求的处理结果。
HTTP的状态码有以下几种:
- 100-199 :信息响应,一般表示服务器正在处理请求
- 200-299 :成功响应,一般表示本次请求处理成功
- 300-399 :重定向,表示让客户端重新发起一个新的请求到别处
- 400-499 :客户端响应,一般表示客户端的请求信息有误,如请求不存在的资源
- 500-599 :服务端响应,一般表示服务端出现问题,如服务端抛出异常,路由出错等
几种常见的状态码:
- 200 OK :请求成功
- 303 See Other :对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源。这个方法的存在主要是为了允许由脚本激活的POST请求输出重定向到一个新的资源。
- 304 Not Modified :如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。
- 404 Not Found :请求失败,请求所希望得到的资源未被在服务器上发现。
- 500 Internal Server Error :服务器遇到了不知道如何处理的情况。一般是服务端抛出异常。
更多详细的状态码信息可参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
1.4、静态web和动态web
静态web
所谓静态web指的是请求服务器上的网页时,服务器不对网页文件作任何处理,读取文件之后就直接当作响应传给浏览器。
静态web存在的缺点
- web页面无法动态地刷新,所有用户看到的都是相同页面
- 可以使用一些技术实现伪动态效果,如轮播图、点击特效等
- JavaScript(日常开发中使用最多)
- VBScript
- 可以使用一些技术实现伪动态效果,如轮播图、点击特效等
- 无法与数据库进行绑定,数据无法持久保存
动态web
所谓动态web,指的是服务器在响应之前,可能先依客户端的请求参数、标头或者实际服务器上的状态,以程序的方式动态产生响应内容,再传回给用户。
动态web的优点
- web页面可以动态地刷新,所有用户看到的都是不同页面
- 可以与数据库进行绑定,数据可持久保存
静态web和动态web的不同的网络请求过程
静态web的请求只是单纯地从服务器上获取到静态页面,然后返回给用户。
动态web的请求会根据请求的数据作出相应的处理后再返回给用户。
1.5 Servlet
1.5.1 概念
这里引用维基百科的解释:
Servlet(Server Applet),全称Java Servlet,未有中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。
Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
1.5.2 在一个Maven项目中编写Servlet
首先new一个名为HelloServlet.java的类,使其继承HttpServlet
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 响应流
PrintWriter out = resp.getWriter();
out.println("<h1>Hello,Servlet!</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
我们编写的Servlet应该实现Servlet接口,为什么只需要继承HttpServlet就可以了呢?
因为HttpServlet类继承了GenericServlet类,而GenericServlet类实现了Servlet接口。
然后,我们需要在当前项目下的src\main\webapp\WEB-INF文件夹下,找到web.xml文件
打开web.xml,在里面的标签内添加
<!--注册Servlet-->
<servlet>
<servlet-name>hello</servlet-name>
<!--Servlet类名,如果Servlet在包中,需要在前面加上包名-->
<servlet-class>HelloServlet</servlet-class>
</servlet>
<!--一个Servlet对应一个mapping-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<!--请求地址-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
然后就要配置Tomcat了,点击下图中箭头所示的位置,若还未配置Tomcat,则该位置只会出现Edit Configurations… 点击它
然后点击左上角的加号,选择本地的Tomcat Server
然后把基本的属性设置好,此时最下方会报错
这时,只需要选择Deployment,点击右边的加号,选择Artifact
有时会出现没有Artifact的情况,如果遇到了这种情况,请往下翻
只需要在弹出框内选择war文件即可
没有Artifact的情况只需要点击右边的Maven面板,然后点击Reimport,再进行上面的步骤添加即可
把Tomcat配置好后,点击Run按钮就可以运行啦
1.5.3 mapping
mapping是映射的意思,Servlet需要映射来指定路径
一个Servlet对应一个映射路径
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.applepieme.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
一个Servlet也可以对应多个映射路径,走不同的路径都会走这个Servlet的请求
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.applepieme.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello3</url-pattern>
</servlet-mapping>
在这里,无论路径是/hello1还是/hello2还是/hello3,都会走名为hello的Servlet
一个Servlet可以指定通用映射路径
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.applepieme.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
在这里,无论路径/hello/后面的是什么,都会走名为hello的Servlet
默认映射路径
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.applepieme.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
当不存在其他的Servlet注册,且只有一个路径为/*的映射时,启动Tomcat时会直接跳过index.jsp的页面进入到该Servlet所展示的页面
后缀
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.applepieme.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>*.applepieme</url-pattern>
</servlet-mapping>
这里,访问路径如果带有后缀.applepieme,则都会进入名为hello的Servlet
自己写一个简单的404页面
创建一个新的Servlet
public class ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter out = resp.getWriter();
out.println("404,页面走丢了");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
在web.xml中注册
<servlet>
<servlet-name>error</servlet-name>
<servlet-class>com.applepieme.servlet.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>error</servlet-name>
<!-- /* 的优先级问题,存在其他固定路径的映射时,/* 的优先级较它们较低 -->
<!-- 但是只要有 /* 存在,启动Tomcat时仍然会直接跳过index.jsp页面直接进入error页面 -->
<url-pattern>/*</url-pattern>
</servlet-mapping>
启动Tomcat时,因为有 /* 存在,会直接跳过index.jsp页面直接进入error页面
可以访问/hello,因为前面有一个固定的hello的映射路径
访问其他路径会进入该404页面
1.5.4 使用注解来注册Servlet
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署描述,简化开发流程。
使用注解来注册Servlet的前提:
- Servlet 3.0 及以上版本
- Tomcat 7 及以上版本
版本支持使用注解来注册Servlet时,则可以在每个Servlet加上以下代码
import javax.servlet.annotation.WebServlet;
// 使用注解来注册该Servlet
// name属性可以省略
// value 或者 urlPatterns都表示访问路径不可省略
@WebServlet(name = "hello", value = "/hello")
1.5.5 ServletContext
web容器在启动的时候,会为每个web应用创建一个ServletContext对象,用来代表当前的web应用
使用ServletContext对象共享数据
先创建一个Servlet用来设置ServletContext对象对应的属性值,属性名为username
@WebServlet(value = "/set")
public class SetContext extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = "测试人员";
// 获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 给ServletContext对象定义一个username的属性,并且赋值为字符串username的值
servletContext.setAttribute("username", username);
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("<h1>设置属性成功!</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
接下来创建一个Servlet来获取ServletContext对象中的username属性
@WebServlet(value = "/get")
public class GetContext extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 用username来存放获取到的ServletContext对象的username属性
String username = (String) servletContext.getAttribute("username");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("<h1>username : " + username + "</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
测试结果
启动Tomcat,此时进入index.jsp页面
先去get页面看看有没有数据,此时username的值为null
去set页面给ServletContext对象设置一个名为username的属性值
再去get页面看看变化,这时候username的值变成了我们实现定义好赋值给ServletContext对象的值