「课件」原创 => Java WEB【转载请标明出处】
第一章·包名更改的 坑 ~
javax.*
=> jakarta.*
感兴趣的可以了解一下为什么名称被修改了:
Eclipse基金会在2019年对 Java EE 标准的每个规范进行了重命名,阐明了每个规范在Jakarta EE平台未来的角色。
新的名称Jakarta EE是Java EE的第二次重命名。2006年5月,“J2EE”一词被弃用,并选择了Java EE这个名称。在YouTube还只是一家独立的公司的时候,数字2就就从名字中消失了,而且当时冥王星仍然被认为是一颗行星。同样,作为Java SE 5(2004)的一部分,数字2也从J2SE中删除了,那时谷歌还没有上市。
因为不能再使用javax名称空间,Jakarta EE提供了非常明显的分界线。
- Jakarta 9(2019及以后)使用jakarta命名空间。
- Java EE 5(2005)到Java EE 8(2017)使用javax命名空间。
- Java EE 4使用javax命名空间。
我们可以将项目直接打包为war包(默认),打包好之后,放入webapp文件夹,就可以直接运行我们通过Java编写的Web应用程序了,访问路径为文件的名称。
1. pom.xml
<?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>org.example</groupId>
<artifactId>WebTest</artifactId>
<version>1.0-SNAPSHOT</version>
<name>WebTest</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>
<junit.version>5.9.2</junit.version>
</properties>
<dependencies>
<!--tomcat 10 用的是这个-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>
2. 更改项目访问的 URL
第二章·手动让 Tomcat 服务程序 加载 war 包
- 先 package 双击,然后打包出 war 包
-
war 包复制到 Tomcat 的 webapp 目录下
-
双击 bin 下面的 startup.bat 启动 Tomcat 服务
-
然后 访问 http://localhost:8080/manager/html 查看下,我们写的 WEB 程序是否已经加载!
第三章·简单编写 Servlet 程序
1. 注册 Servlet 程序
1.1 web.xml 配置方式
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--全局初始化参数 可以在这里设置,然后Java代码使用 context.getInitParameter("NBLOVE") 进行获取-->
<context-param>
<param-name>NBLOVE</param-name>
<param-value>我是全局初始化参数</param-value>
</context-param>
<!--下方仅仅是在不使用 注解注册时 进行的配置,如果使用了注解 请删除!-->
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>org.example.webtest.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
继承 Servlet 方式
package org.example.webtest;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import java.io.IOException;
public class TestServlet implements Servlet {
// 请求发过来时,进行的初始化操作写在这里
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
// 专门设置 Servlet 配置的
@Override
public ServletConfig getServletConfig() {
return null;
}
// 进行信息传输的核心方法,拿到 请求方对象 和 浏览器响应对象
// 然后去分析 相关的请求方 拿到的东西,进而 去用 响应对象在 网页上进行数据展示等响应
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
}
// 设置 Servlet 信息打印
@Override
public String getServletInfo() {
return null; // null 就是使用默认
}
// 连接中断后,我们要做什么事情
@Override
public void destroy() {
}
}
1.2 web.xml 隐藏配置(实现了 可以直接访问 .jsp 静态资源的原因)
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
1.3 注解方式(主推)=> 以继承 Servlet 举例
package org.example.webtest;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import java.io.IOException;
// @WebServlet("/test") 把 /test 是注册到 Web 服务器
// 然后你用 根URL/test 即 http://localhost:8080/hello/ 配置项 的值test 就可以访问
@WebServlet(value = "/test",initParams = {
/*@WebInitParam 是初始化参数,一般放一些 配置项 的值*/
@WebInitParam(name = "test", value = "我是一个默认的初始化参数")
})
public class TestServlet implements Servlet {
public TestServlet(){
// 构造方法是在初始化之前执行的
System.out.println("我是构造方法");
}
// 请求发过来时,进行的初始化操作写在这里
@Override
public void init(ServletConfig servletConfig) throws ServletException {
getInitParameter("test"); // 拿到 注解处设置的 初始化参数,其实是一个很鸡肋的东西
System.out.println("初始化");
}
// 专门设置 Servlet 配置的
@Override
public ServletConfig getServletConfig() {
System.out.println("ServletConfig");
return null;
}
// 进行信息传输的核心方法,拿到 请求方对象 和 浏览器响应对象
// 然后去分析 相关的请求方 拿到的东西,进而 去用 响应对象在 网页上进行数据展示等响应
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 如果加载静态资源的话,可能还会去进行请求
System.out.println("service");
}
// 设置 Servlet 信息打印
@Override
public String getServletInfo() {
System.out.println("getServletInfo");
return null; // null 就是使用默认
}
// 连接中断后,我们要做什么事情
@Override
public void destroy() {
System.out.println("destroy");
}
}
- 首先执行构造方法完成 Servlet 初始化
- Servlet 初始化后调用 init () 方法。
- Servlet 调用 service() 方法来处理客户端的请求。
- Servlet 销毁前调用 destroy() 方法。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
2. 可能需要观看的流程图
3. 继承 HttpServlet 方式
3.1 关于 @WebServlet 注解
我们接着来看WebServlet注解,我们前面已经得知,可以直接使用此注解来快速注册一个Servlet,那么我们来想细看看此注解还有什么其他的玩法。
首先name属性就是Servlet名称,而urlPatterns和value实际上是同样功能,就是代表当前Servlet的访问路径,它不仅仅可以是一个固定值,还可以进行通配符匹配:
@WebServlet("/test/*")
上面的路径表示,所有匹配/test/随便什么
的路径名称,都可以访问此Servlet,我们可以在浏览器中尝试一下。
也可以进行某个扩展名称的匹配:
@WebServlet("*.js")
这样的话,获取任何以js结尾的文件,都会由我们自己定义的Servlet处理。
那么如果我们的路径为/
呢?
@WebServlet("/")
此路径和Tomcat默认为我们提供的Servlet冲突,会直接替换掉默认的,而使用我们的,此路径的意思为,如果没有找到匹配当前访问路径的Servlet,那么久会使用此Servlet进行处理。
我们还可以为一个Servlet配置多个访问路径:
@WebServlet({"/test1", "/test2"})
我们接着来看loadOnStartup属性,此属性决定了是否在Tomcat启动时就加载此Servlet,默认情况下,Servlet只有在被访问时才会加载,它的默认值为-1,表示不在启动时加载,我们可以将其修改为大于等于0的数,来开启启动时加载。并且数字的大小决定了此Servlet的启动优先级。
@Log
@WebServlet(value = "/test", loadOnStartup = 1)
public class TestServlet extends HttpServlet {
@Override
public void init() throws ServletException {
super.init();
log.info("我被初始化了!");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h1>恭喜你解锁了全新玩法</h1>");
}
}
其他内容都是Servlet的一些基本配置,这里就不详细讲解了。
4. ServletContext 对象
ServletContext全局唯一,它是属于整个Web应用程序的,我们可以通过getServletContext()
来获取到此对象。有些数据就应该放在 一个公共的容器当中进行保存,然后让所有的请求都能够拿到!
- 赋值
ServletContext context = getServletContext();
context.setAttribute("test", "我是重定向之前的数据");
resp.sendRedirect("time");
- 拿到 webapp 根目录下的 资源文件
ServletContext context = getServletContext();
InputStream inputStream = context.getResourceAsStream("/path/to/resource");
需要注意的是,路径参数应该以斜杠 /
开头,表示相对于 webapp 根目录的路径。如果资源文件位于 WEB-INF 目录下,可以使用 /WEB-INF/
开头的路径。
想要 拿到 resource 目录下资源文件的话,就用 Resources.getResourceAsStream()
第四章·关于下载和上传
1. 下载
- 下载链接的写法
<hr>
<a href="file" download="icon.png">点我下载高清资源</a>
@WebServlet("/file")
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("image/png");
OutputStream outputStream = resp.getOutputStream();
InputStream inputStream = Resources.getResourceAsStream("icon.png");
// 直接使用copy方法完成转换
// 其实 这个工具类 我们都可以自己写,就是对IO 进一步的封装,仅此而已
IOUtils.copy(inputStream, outputStream);
}
}
2. 上传
<form method="post" action="file" enctype="multipart/form-data">
<div>
<input type="file" name="test-file">
</div>
<div>
<button>上传文件</button>
</div>
</form>
@MultipartConfig
@WebServlet("/file")
public class FileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try(FileOutputStream stream = new FileOutputStream("/Users/nagocoler/Documents/IdeaProjects/WebTest/test.png")){
Part part = req.getPart("test-file");
IOUtils.copy(part.getInputStream(), stream);
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("文件上传成功!");
}
}
}
第五章·发送 xhr 请求向后端
我推荐大家直接学这个js中原生态的 xhr,而不是 jquery 中二次封装的 ajax(而且现在主流也不用它了,什么兼容IE真的变为了行外人的一个梗了~ 都什么年代了,你看现在哪家公司还搞这个??在手机用户为主流的时代,IE??你脑袋抽了?),咳咳 ~ 因为 ajax 其实并没有添附什么其他的东西和代码上的优化,仅仅只是 做了 类似于语法糖的处理。就是你用起来方便了。。
在早期的时候,我们真的就是 通过 JS 的发送 xhr 请求向后端,来实现 页面与用户 交互的 !所以千万别瞧不起这种方式!!
1. js 代码的 xhr 发送 => 代码模板
function updateTime() {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
document.getElementById("time").innerText = xhr.responseText
}
};
xhr.open('GET', '访问的url', true);
xhr.send();
}
2. 前端代码模板
<hr>
<div id="time"></div>
<br>
<button onclick="updateTime()">更新数据</button>
<script>
updateTime()
</script>
3. java 后端 代码模板
@WebServlet("/time")
public class TimeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String date = dateFormat.format(new Date());
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write(date);
}
}
第六章·重定向和请求转发
重定向:跳转(重新请求)到某个页面(根据提供url地址),此时响应的状态码会被设置为302!并且响应头中添加了一个Location属性,此属性表示,需要重定向到哪一个网址。
resp.sendRedirect("time");
请求转发:服务器后端程序内部进行的跳转,即把当前的 Servlet 请求,转发给 其他的 Servlet 请求去进行处理,然后返回结果。所以请求转发,是会携带着所有参数 转发到另一个 Servlet 的过程!
req.getRequestDispatcher("/time").forward(req, resp);
我们可以注意到的是,在转发的时候,我们把 当前的请求者对象、响应对象 全都传过去了。所以当然是携带着所有信息过去的。
最后总结,两者的区别为:
- 请求转发是一次请求,重定向是两次请求
- 请求转发地址栏不会发生改变, 重定向地址栏会发生改变
- 请求转发可以共享请求参数 ,重定向之后,就获取不了共享参数了
- 请求转发只能转发给内部的Servlet
第七章·Cookie、Session
1. Cookie
- cookie:保存在浏览器中的一些信息,就是放到 cookie 里面的。
// resp 响应过去一些 cookie 让浏览器保存
Cookie cookie = new Cookie("test", "yyds");
resp.addCookie(cookie);
resp.sendRedirect("time");
// 获取 req 携带过来的 cookie 都是个啥
for (Cookie cookie : req.getCookies()) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}
我们可以观察一下,在HttpServletResponse
中添加Cookie之后,浏览器的响应头中会包含一个Set-Cookie
属性,同时,在重定向之后,我们的请求头中,会携带此Cookie作为一个属性,同时,我们可以直接通过HttpServletRequest
来快速获取有哪些Cookie信息。
Cookie包含哪些信息:
- name - Cookie的名称,Cookie一旦创建,名称便不可更改
- value - Cookie的值,如果值为Unicode字符,需要为字符编码。如果为二进制数据,则需要使用BASE64编码
- maxAge - Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为-1。
- secure - 该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。
- path - Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。
- domain - 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。
- comment - 该Cookie的用处说明,浏览器显示Cookie信息的时候显示该说明。
- version - Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范
我们发现,最关键的其实是name
、value
、maxAge
、domain
属性。
我们通常 是用这玩意,去保存一些 登陆过的用户名和密码,就是 前端页面 单选框选中的 "请记住我" 然后人家就把你记住了,浏览器记住之后,每次访问页面发送请求的时候,后端的服务器程序 先判断 Cookie 中是否有 这些信息。
<div>
<label>
<input type="checkbox" placeholder="记住我" name="remember-me">
记住我
</label>
</div>
- 登陆成功后 => 让浏览器保存的 Cookie 信息
Map<String, String[]> parameterMap = req.getParameterMap();
if(parameterMap.containsKey("remember-me")){ //若勾选了勾选框,那么会此表单信息
Cookie cookie_username = new Cookie("username", username);
cookie_username.setMaxAge(30);
Cookie cookie_password = new Cookie("password", password);
cookie_password.setMaxAge(30);
resp.addCookie(cookie_username);
resp.addCookie(cookie_password);
}
- 请求登录的时候 => 判断 Cookie 的模板
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie[] cookies = req.getCookies();
if(cookies != null){
String username = null;
String password = null;
for (Cookie cookie : cookies) {
if(cookie.getName().equals("username")) username = cookie.getValue();
if(cookie.getName().equals("password")) password = cookie.getValue();
}
if(username != null && password != null){
//登陆校验
try (SqlSession sqlSession = factory.openSession(true)){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUser(username, password);
if(user != null){
resp.sendRedirect("time");
return; //直接返回
}
}
}
}
req.getRequestDispatcher("/").forward(req, resp); //正常情况还是转发给默认的Servlet帮我们返回静态页面
}
2. Session
通常用来 判断用户是否用该浏览器访问过我们的网页,进行过登录等请求。如果一次都没有进行过,然后还有 cookie,那我们可以怀疑 并不是 用户用浏览器进行的正经的访问,可能是爬虫。。
由于HTTP是无连接的,那么如何能够辨别当前的请求是来自哪个用户发起的呢?Session就是用来处理这种问题的,每个用户的会话都会有一个自己的Session对象,来自同一个浏览器的所有请求,就属于同一个会话。
它会给浏览器设定一个叫做 JSESSIONID
的 Cookie,这个值是一个随机的排列组合,也是用这个Cookie 来判断你属于哪一个会话,只要我们的浏览器携带此Cookie访问服务器,服务器就会通过Cookie的值进行辨别,得到对应的Session对象,因此,这样就可以追踪到底是哪一个浏览器在访问服务器。
需要强调的是:Session 并不是存放在浏览器中的,而是存放在服务器端的。它只不过是在与 浏览器建立会话的时候 把这个会话的 唯一标识 Session ID 保存到了 名字为 "JSESSIONID" 的 Cookie 里面!
而后端代码层面上,我们要想拿到 这个 存储到 服务器端的 Session 对象,就需要 用 请求者 通过 保存的 Session ID 去进行获取。
代码就变成了这样:HttpSession session = req.getSession();
所以不要感觉这个代码看起来很怪,它并不奇怪,它的意思是说 我们依赖于 保存在 浏览器上面的 Session ID 在我们的 后端服务器程序上,拿到 内存里面 的 Session 建立对应会话的 Session 对象。(这里很多人都讲不清楚呀~ 然后还出来误人子弟。。)
- 拿到 session 并添加 信息
HttpSession session = req.getSession();
session.setAttribute("user", user);
- 判断 Session 中是否有 保存的信息
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
if(user == null) {
resp.sendRedirect("login");
return;
}
Session并不是永远都存在的,它有着自己的过期时间,默认时间为30分钟,若超过此时间,Session将丢失,我们可以在配置文件中修改过期时间:
<session-config>
<session-timeout>1</session-timeout>
</session-config>
我们也可以在代码中使用invalidate
方法来使Session立即失效:
session.invalidate();
现在,通过Session,我们就可以更好地控制用户对于资源的访问,只有完成登陆的用户才有资格访问首页。
第八章·过滤器 Filter、监听器 Listener
在设计之初的时候,我们发现只要通过Session,就可以很好地控制用户的登陆验证了,即只有授权的用户,才可以访问一些页面,但是我们需要一个一个去进行配置,还是太过复杂,能否一次性地过滤掉没有登录验证的用户呢?
Filter 过滤器就出现了,它的本质实际上也是 一个 Servlet,原理是说,我们无论什么样的请求,都需要先经过它这个 Servlet 去进行一些特判,然后 再决定是否 转发到 目标的 Servlet。
添加一个过滤器非常简单,只需要实现Filter接口,并添加@WebFilter
注解即可:
@WebFilter("/*")
public class MainFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String url = req.getRequestURL().toString();
//判断是否为静态资源
if(!url.endsWith(".js") && !url.endsWith(".css") && !url.endsWith(".png")){
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
//判断是否未登陆
if(user == null && !url.endsWith("login")){
res.sendRedirect("login");
return;
}
}
//交给过滤链处理
chain.doFilter(req, res);
}
}
1. 存在多个 Filter 过滤器
由于我们整个应用程序可能存在多个过滤器,那么这行代码 chain.doFilter(req, res);
的意思实际上是将此请求继续传递给下一个过滤器,当没有下一个过滤器时,才会到达对应的Servlet进行处理,我们可以再来创建一个过滤器看看
@WebFilter("/*")
public class TestFilter2 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("我是2号过滤器");
filterChain.doFilter(servletRequest, servletResponse);
}
}
切记:过滤器的过滤顺序是按照类名的自然排序进行的,因此我们要把这些过滤器的名字都要进行个调整,才能非常顺利的按照我们想要的过滤顺序去执行。
2. Listener
在开发中,如果我们希望,在应用程序加载的时候,或是Session创建的时候,亦或是在Request对象创建的时候进行一些操作,那么这个时候,我们就可以使用监听器来实现。
默认为我们提供了很多类型的监听器,我们这里就演示一下监听Session的创建即可:
@WebListener
public class TestListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("有一个Session被创建了");
}
}