跟随狂神学Java-31,JavaWeb
跟随狂神学Java-31,JavaWeb
joker2yue跟随狂神学Java
作者:joker2yue
链接:https://github.com/Joker2Yue/Joker2Yue-Blog
来源:Github
著作权归原作者所有。商业转载请联系原作者获得授权,非商业转载请注明出处。
第三十一:JavaWeb
“真正的危险不是计算机开始像人一样思考,而是人开始像计算机一样思考。”
基本概念
- web开发
- web,网页的意思
- 静态web
- html,css
- 提供给所有人看的数据,始终不会发生变化
- 动态web
- 会发生变化。每个人在不同时间,不同地点 看到的信息各不相同
- 常用的技术栈,servlet/JSP,ASP,PHP
在Java中,动态资源开发的技术统称为JavaWeb
web应用程序
可以提供浏览器访问的程序
- a.html , b.html…多个web资源,这些web资源可以被外界访问,对外界提供服务
- 能访问到任何一个页面或者资源,都存在于这个世界一台计算机上
- URL
- 这个统一的web资源会被放在同一个文件夹下,web应用程序–>Tomcat服务器
- 一个web应用由多个部分组成(静态web,动态web)
- html,css,js
- jsp,servlet
- Java程序
- jar包
- 配置文件(properties)
web应用程序编写完毕后,若想提供给外界访问,需要一个服务器来统一管理
静态web
*.html
,*.htm
是网页后缀静态 web 存在的缺点
- Web 页面无法动态更新 , 所有用户看到都是同一个页面
- 轮播图 , 点击特效 : 伪动态
- JavaScript [ 实际开发中 , 它用的最多 ]
- VBScript
- 无法和数据库交互(数据无法
动态web
页面会动态展示,web页面展示的效果因人而异
- 加入服务器的动态 web 资源出现了错误 , 我们需要重新编写我们的后台程序,重新发布
- 停机维护
- Web 页面可以动态更新 , 所有用户看到不是同一个页面
- 它可以和数据库交互
Web服务器
常见的Web服务器
ASP
- 微软:国内最早流行的是ASP
- 在HTML中嵌入了VB的脚本,ASP+COM
- 在ASP开发中,基本一个页面都有几千行代码,很混乱
- C#
- IIS
php
- 开发速度快,功能强大,跨平台,代码简单
- 无法承载大访问量
JSP/Servlet
B/S:浏览器和服务器
C/S:客户端和服务端
Sun公司主推的B/S架构
基于Java语言的(所有的大公司,或者一些开源的组件,都是用的Java写的)
可以承载三高问题(三高:高并发,高可用,高性能)
服务器是一种被动的 操作,用来处理用户的一些请求和给用户一些响应信息
IIS
- 微软的,跑ASP,windows自带的
Tomcat
- Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全局管理和Tomcat阀等。由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器。但是,不能将Tomcat和Apache HTTP服务器混淆,Apache HTTP服务器是用C语言实现的HTTPWeb服务器;这两个HTTP web server不是捆绑在一起的。Apache Tomcat包含了配置管理工具,也可以通过编辑XML格式的配置文件来进行配置。
请你谈谈网站是如何进行访问的
输入一个域名 ; 回车
检直本机的 C:\Window s\System32\drivers\etc\hosts 配置文件下有没有这个域名映射
有 : 直接返回对应的 ip 地址, 这个地址中 , 有我们需要访问的 web 程序 , 可以直接访问
1
127.0.0.1 www.baidu.com
没有:去 DNS 服务器找, 找到的话就返回 , 找不到就返回找不到
配置环境变量
HTTP协议
概念:
- 超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。
- 设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。
- HTTP的发展是由蒂姆·伯纳斯-李于1989年在欧洲核子研究组织(CERN)所发起。HTTP的标准制定由万维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定义了HTTP协议中现今广泛使用的一个版本——HTTP 1.1。
- 2014年12月,互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组将HTTP/2标准提议递交至IESG进行讨论[2],于2015年2月17日被批准。[3] HTTP/2标准于2015年5月以RFC 7540正式发表,取代HTTP 1.1成为HTTP的实现标准。
相关:
- HTTP ( 超文本传输协议 ) 是一个简单的请求 · 响应协议 , 它通常运行在 TCP 之上
- 文本 : html, 字符串
- 超文本 : 图片 , 音乐 , 视频 , 定位 , 地图
- 80端口
- HTTPS:安全的
- 443端口
两个时代:
- HTTP1.0
- HTTP/1.0 : 客户端可以与 web 服务器连接后 , 只能获得一个 web 资源 , 断开连接
- HTTP2.0
- HTTP/1.1:客户端可以与 web 服务器连接后 , 可以获得多个 web 资源
请求和相应:
请求:客户端-发请求-服务器
1
2
3
4请求 URL(Rquest URL):https://www.baidu.com/
请求方法(Request Method):GET
状态代码(Status Code):200 OK
远程地址(Remote Address):127.0.0.1:7879请求行
- 请求行中的请求方式:GET
- 请求方式 Get,Post,HEAD,DELETE,PUT,TRACT…
- get: 请求能够携带的参数较少,大小有限制,会在浏览器的URL地址中显示,不安全但是高效
- post: 请求能够携带的参数较多,大小没有限制,不会在浏览器的URL地址中显示,安全但是不高效
消息头
1
2
3
4
5
6Accept 告诉浏览器它所支持的数据类型
Accept-Encoding 编码格式
Accept-Language 告诉浏览器语言环境
Connection 告诉浏览器,请求完成还是保持连接
Cache-Control 缓存控制
HOST 主机
响应:服务器-响应-客户端
1
2
3
4Cache-Control max-age=0 缓存控制
Connection keep-alive 连接
Content-Encoding gzip 编码类型
Content-Type text/html; charset=utf-8响应体
1
2
3
4
5
6
7
8Accept 告诉浏览器它所支持的数据类型
Accept-Encoding 编码格式
Accept-Language 告诉浏览器语言环境
Connection 告诉浏览器,请求完成还是保持连接
Cache-Control 缓存控制
HOST 主机
Reflush 告诉客户端多久刷新一次
Location 让网页重新定位响应状态码
200:请求响应成功
3xx:重定向
404:找不到资源‘
5xx:服务器代码错误
502:网关错误
request和response的作用执行流程
Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。
当需要获取客户机提交过来的数据时,找request对象就行了。
当需要向客户机输出数据,找response对象。
当你的浏览器中止栏输入地址并回车的一瞬间,到页面能够展示出来 , 经历了什么 ?
Maven
为什么要学习Maven?
- 在Javaweb开发过程中,通常需要使用大量jar包,为了避免手动导入
- 如何能够让一个东西能够自动帮我导入需要的jar包
- Maven只是一个工具,不能说是一个jar包
Maven项目架构管理工具及其一些配置
Maven的核心思想:约定大于配置
- 有约束,不要去违反
Maven会规定好如何去编写Java代码,
配置:
1 | <mirror> |
本地仓库:
1 | <localRepository>D:\Maven\repository</localRepository> |
在IDEA中使用maven
启动IDEA
创建一个Maven项目
Maven设置
自动导入源码
项目设置
添加Tomcat服务器
为什么会出现这个警告?
我们访问一个网站,需要指定一个文件夹的名字
修改默认应用程序上下文,如果设置为
/MavenLearn_war
,那么访问时将为localhost:8080/MavenLearn_war
运行结果:
这里可以查看Maven命令
pom文件
1 | <!--maven版本和头文件--> |
Maven由于约定大于配置,之后可能会遇到我们写的配置文件无法导出或生效的问题,解决方案:在build中配置resources,防止资源导出失败
1 | <build> |
可以通过右键来显示图
遇到的坑
pom无法同步
3.6.2maven有此问题,降低版本
JDK兼容
使用JDK18
无法部署war
重新检查运行配置
我的配置:
Tomact:9.0.40
Maven:3.5.3
JDK:
将IDEA中web.xml的版本设置为与Tomcat的推荐版本一致
可以看到推荐版本为4.0
1
2
3
4
5
6
7
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"
metadata-complete="true">
</web-app>目录结构
Tomcat示例文件
可以通过这里查看示例代码
可以通过这里导入依赖 ,也可以通过Maven仓库导入
Servlet
什么是Servlet
是Sun公司用于开发动态网站的一门技术
Sun公司在这些API中提供了一个接口叫做Sevlet。如果你想开发一个Servlet,只需要完成两个步骤:
- 编写一个类,实现Servlet接口
- 把开发好的Java类部署到web服务器中
把实现了Servlet接口的Java程序叫做 Servlet
HelloServlet
创建一个普通的Maven项目,删掉里面的src目录
空的工程叫做主文件
关于Maven父子关系的理解
父项目中会多一个module
1
2
3
4<!--父模块中会有以下信息-->
<modules>
<module>Servlet-01</module>
</modules>1
2
3
4
5
6<!--子模块中会有以下信息-->
<parent>
<groupId>com.joker_yue</groupId>
<artifactId>MavenLearn</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>子项目可以直接使用父项目中的Java
1
son extend father
Maven环境优化
- 修改web.xml为最新的
- 将Maven的结构搭建完整
编写一个Servlet程序
编写一个普通类
实现Servlet接口,直接继承HttpServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class HelloServlet extends HttpServlet {
//由于get和post只是请求实现的不同的方式,可以相互调用,业务逻辑都一样
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//ServletOutputStream outputStream = resp.getOutputStream();
PrintWriter writer = resp.getWriter(); //响应流
writer.print("Hello world!");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
编写Servlet的映射
为什么需要映射:我们写的是映射,但是需要通过浏览器访问,而浏览器需要连接web服务器,所以我们需要在web服务中注册我们写的servlet,还需要给它一个浏览器能够访问的路径
1
2
3
4
5
6
7
8
9
10<!--注册Servlet-->
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.joker_yue.servlet.HelloServlet</servlet-class>
</servlet>
<!--配置servlet请求路径-->
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/joker_yue</url-pattern>
</servlet-mapping>配置Tomcat
注意:配置项目发布的路径就可以了
启动测试
Sevlet原理
servlet就是一个接口,定义了java类被浏览器访问到(被Tomcat识别)的规则,主要负责接收浏览器的请求,tomcat服务器调用servlet方法。步骤:创建 一个JavaEE项目,定义一个类实现servlet接口,重写方法,在web.xml中配置servlet。
servlet执行原理:当服务器接收到浏览器客户的请求之后,会解析请求的URL路径,获取访问的servlet的资源路径,找到项目,查找web.xml文件,是否有对应的标签体内容,如果有,则找到对应的标签内的全类名,tomcat会将字节码文件加载进内存,并且创建其对象,调用其方法。
Servlet生命周期
Mapping问题
一个Servlet可以指定一个映射路径
一个Servlet可以指定多个映射路径
1
2
3
4
5
6
7
8
9
10
11
12<!--配置servlet请求路径-->
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/joker_yue</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/joker_yue1</url-pattern>
</servlet-mapping><servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/joker_yue2</url-pattern>
</servlet-mapping>一个Servlet可以指定通用映射路径
1
2
3
4<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/joker_yue/*</url-pattern>
</servlet-mapping>注意不要把通配的时候不要包含根目录
指定一些后缀或者前缀
1
2
3
4<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>注意,
*
前面不可加任何路径优先级问题
指定了固有的映射路径优先级最高,如果找不到就走默认的处理请求
1
2
3
4
5
6
7
8
9
10<!-- 404-->
<servlet>
<servlet-name>errorServlet</servlet-name>
<servlet-class>com.joker_yue.servlet.ErrorServlet</servlet-class>
</servlet>
<!--配置servlet请求路径-->
<servlet-mapping>
<servlet-name>errorServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
ServletContext
web容器在启动的时候,他会为每个web程序都创建一个对应的ServiceContext对象,它代表了当前的web应用
共享数据
我在这个Servlet中保存的数据,可以在另一个Servlet拿到
1
2
3
4
5
6
7
8
9public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
String username = "Joker_Yue";
servletContext .setAttribute("username", username);//将一个数据保存在ServletContext中,名字为username。值,username
}
}1
2
3
4
5
6
7
8
9
10
11
12public class GetServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String username = (String) context.getAttribute("username");
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().print("名字:"+username);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.joker_yue.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>getc</servlet-name>
<servlet-class>com.joker_yue.servlet.GetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getc</servlet-name>
<url-pattern>/getc</url-pattern>
</servlet-mapping>
获取初始化参数
1 | <!-- 配置一些web应用的初始化参数 --> |
1 |
|
请求转发和重定向
这是请求转发:
这是重定向:
实现请求转发
1 | package com.joker_yue.servlet; |
面试题:重定向和转发的区别
相同:页面都会跳转
不同:
请求转发的时候,页面的URL不会发生变化:307
重定向的时候,url地址栏会发生变化:302
在Servlet中,请求转发只能定位到项目内文件,而重定向可以定位到项目之外的。
所以请求转发不需要写绝对路径,而重定向需要
比喻:
重定向:这事儿啊,你呀,问村长去
转发:这事我去给你问问村长,问到了告诉你哈
读取资源文件
Properties
- 在Java目录下新建properties
- 在resource目录下新建properties
发现:都被打包到了同一路径下:class,我们俗称为类路径
思路:需要一个文件流
1 | username=root |
1 | package com.joker_yue.servlet; |
HttpServletResponse
响应:用于客户端向浏览器传递信息
web服务器接收到客户端的Http请求,针对这个请求,分别创建一个代表请求的HttpSevletRequest对象,代表响应的一个httpServletResponse对象
- 如果我们要获取客户端请求过来的参数,找HttpServletRequest
- 如果要给客户端响应一些参数,找HttpServletResponse
简单分类
负责向浏览器发送数据的方法
1 | public ServletOutputStream getOutputStream() throws IOException; |
负责向浏览器发送响应头的方法
1 | public void setCharacterEncoding(String charset); |
一些常量,响应的状态码
1 | /* |
常见应用
向浏览器输出消息
下载文件
- 要获取下载的文件路径
- 下载的文件名
- 想办法让浏览器支持下载我们需要的东西
- 获取下载文件的输入流
- 创建缓冲区
- 获取OutputStream
- 将FileOutputStream流写入到buffer缓冲区
- 使用OutputStream将缓冲区中的数据输出到客户端
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
41package com.joker_yue.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
public class FileServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 要获取下载的文件路径
String realPath = "E:\\Program\\Idea\\JavaLearning\\src\\MavenLearn\\Servlet-Response\\src\\main\\resources\\下载.png";
System.out.println("下载的文件的路径:" + realPath);
// 2. 下载的文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
// 3. 想办法让浏览器支持(Content-Disposition)下载我们需要的东西。使用URLEncoder转换编码
resp.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));
// 4. 获取下载文件的输入流
FileInputStream in = new FileInputStream(realPath);
// 5. 创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];
// 6. 获取OutputStream
ServletOutputStream out = resp.getOutputStream();
// 7. 将FileOutputStream流写入到buffer缓冲区
while(in.read(buffer) != -1){
out.write(buffer, 0, len);
}
in.close();
out.close();
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
验证码功能
验证怎么来的
前端实现
后端实现
需要用到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
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
62package com.joker_yue.servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
public class ImageServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//如何让浏览器3s自动刷新一次
resp.setHeader("Refresh", "3");
//在内存中创建一个图片
BufferedImage image = new BufferedImage(80, 80, BufferedImage.TYPE_INT_RGB);
//得到图片
Graphics2D graphics =(Graphics2D) image.getGraphics();//笔
//设置图片的背景颜色
graphics.setColor(Color.white);
graphics.fillRect(0, 0, 80, 20);
//给图片写数据
graphics.setColor(Color.BLUE);
graphics.setFont(new Font("宋体", Font.BOLD, 20));
graphics.drawString(makeNumber(),0,20);
//告诉浏览器这个请求用图片的方式打开
resp.setContentType("image/png");
//网站存在缓存,不让浏览器缓存
resp.setDateHeader("Expires",-1);
resp.setHeader("Cache-Control","no-cache");
resp.setHeader("Pragma","no-cache");
//把图片写给浏览器
ImageIO.write(image,"png",resp.getOutputStream());
}
//生成随机数
private String makeNumber(){
Random random = new Random();
String num = String.valueOf(random.nextInt(9999999));
// StringBuffer:拼接字符串
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 7 - num.length(); i++) {
sb.append("0");
}
num= sb.toString() + num;
return num;
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
实现重定向
一个web资源收到客户端A请求后,B会通知A客户端去访问另外一个web资源C,这个过程叫做重定向
常见场景:
用户登录成功跳转到另一个界面
1
public void sendRedirect(String location) throws IOException;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.joker_yue.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RedirectServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("/down/img");//要加项目路径'/down'
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}分析:
虽然只需一句就可以实现重定向
1
resp.sendRedirect("/down/img");//要加项目路径'/down'
但是它其实做了这些:
1
2
3
4//设置了要跳转的地址
resp.setHeader("Location", "/down/img");//要加项目路径'/down'
//设置了状态码302
resp.setStatus(HttpServletResponse.SC_FOUND);用户登录
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
27package com.joker_yue.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RequestTest extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username = " + username);
System.out.println("password = " + password);
// 重定向一定要注意路径问题,否则容易404
resp.sendRedirect("/down/success.jsp");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14<html>
<body>
<h2>Hello World!</h2>
<%--这里提交的路径,需要寻找到项目的路径--%>
<%-- ${pageContext.request.contextPath}/login代表当前项目--%>
<form action="${pageContext.request.contextPath}/login" method="get">
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<input type="submit">
</form>
</body>
</html>
HttpServletRequest
请求:要想接收浏览器发送过来的参数,通过request就可以了
HttpServletRequest代表客户端的请求,用户通过Http协议访问服务器,HTTP请求中的所有信息将会被封装到HttpServletRequest
获取前端传递的参数
获取前端传递的参数,请求转发
1 | package com.joker_yue.servlet; |
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
Session、Cookie
cookie是监视器,session是会话
会话
会话:用户打开一个浏览器,点击了很多超链接,访问多个web资源,关闭浏览器。这整个过程,可以看作一个会话
有状态会话:你能如何证明你是中国人。出生证:中国给你的;居住证:中国你来过
一个网站怎么证明你来过: 服务端给客户端一个信件,客户端下次访问服务端带上信件就可以了。这个信件就是cookie。服务器登记客户端信息,下次来的时候匹配一下。这个信息就是session。
保存会话的两种技术
cookie
- 客户端技术(响应,请求)
session
- 服务器技术,利用这个技术,可以保存用户的会话信息。我们可以把信息或者数据放在session中
常见场景
- 网站登录之后,你下次就不用再登录了,第二次访问直接就上去了
Cookie
从请求中拿到Cookie
服务器响应给客户端Cookie
1
2
3
4
5
6Cookie[] cookies = req.getCookies(); // 获得cookie
cookie.getName(); // 获得cookie中的key
cookie.getValue(); // 获得cookie中的值
Cookie cookie = new Cookie("lastVisit", System.currentTimeMillis() + ""); //新建一个cookie
cookie.setMaxAge(24*60*60); // 设置Cookie有效期
resp.addCookie(cookie); //响应给客户端一个Cookie
一个网站cookie是否存在上限?
- 一个cookie 只能保存一个信息
- 一个web站点可以给浏览器发送多个cookie。最多存放20个cookie
- cookie大小有限制,4kB
- 浏览器上限300个Cookie
删除Cookie:
- 不设置有效期,关闭浏览器,自动失效
- 设置有效期0
转码解码:
1 | Cookie cookie = new Cookie("username", URLEncoder.encode("九歌", "UTF-8")); |
1 | out.write(URLDecoder.decode(cookie.getValue(), "UTF-8")); |
Session
什么是session
- 服务器会给每一个用户(浏览器)创建Session(会话)
- 一个Session独占一个浏览器,只要浏览器没关,这个Session就存在
- 用户登录之后,整个网站它都可以访问 –>保存用户的信息
cookie和session的区别
- cookie是把用户的数据写给用户的浏览器,浏览器保存(可以保存多个)
- session是把用户的数据写到独占的Session对象中,服务器端保存(保存重要信息,避免资源浪费)
- session对象由服务器创建
HttpSession类
Session在创建的时候做了什么
1 | Cookie jsessionid = new Cookie("JSESSIONID", sessionId); |
使用Session
1 | package com.joker_yue.servlet; |
1 | package com.joker_yue.servlet; |
1 | package com.joker_yue.servlet; |
1 | package com.joker_yue.servlet; |
设置Session超时
在web.xml中:
1
2
3
4
5<!-- 设置Session的超时时间 -->
<session-config>
<!-- 超时时间:15分钟 -->
<session-timeout>15</session-timeout>
</session-config>
使用场景
- 保存一个登录用户的信息
- 购物车信息
- 在整个网站中,经常会使用的数据,我们将他保存在session中
注意:
Session在浏览器打开会话的一瞬间就创建了
JSP
什么是JSP
Java Server Page:Java服务器端页面,也和Servlet一样,用于动态web技术
最大特点:
- 写jsp就像是写HTML
- 区别:
- HTML只给用户提供静态数据
- jsp页面中可以嵌入java代码,为用户提供动态数据
JSP原理
思路:jsp到底怎么执行的
代码层面没有任何问题
服务器内部工作
tomcat中有一个work目录
IDEA中使用Tomcat的会在IDEA中生成一个work目录
jsp最终会被转成Java程序
浏览器向服务器发送请求,不管访问什么资源,其实都是在访问servlet
jsp本质上就是一个Servlet
1
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage
1
2
3
4
5
6
7
8
9//初始化
public void _jspInit() {
}
//销毁
protected void _jspDestroy() {
}
//JSPService
public void _jspService(HttpServletRequest var1, HttpServletResponse var2)
执行请求
内置了一些对象
1
2
3
4
5
6
7final javax.servlet.jsp.PageContext pageContext; //页面上下文
final javax.servlet.ServletContext application; //applicationContext
final javax.servlet.ServletConfig config; //config
javax.servlet.jsp.JspWriter out = null; //out
final java.lang.Object page = this; //page:当前
HttpServletRequest var1 //请求
HttpServletResponse var2 //响应输出页面前增加的代码
1
2
3
4
5
6
7response.setContentType("text/html; charset=UTF-8"); //设置响应的页面类型
pageContext = _jspxFactory.getPageContext(this, request, response,null, false, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
out = pageContext.getOut();
_jspx_out = out;以上的这些对象我们可以直接在jsp页面中直接使用
我们可以直接在
<% %>
中使用java代码
JSP基础语法
表达式
1 | <%--JSP表达式 |
脚本片段
1 | <%-- jsp脚本片段 --%> |
JSP脚本的再实现
1 | <%--脚本片段的再实现--%> |
在代码中嵌入HTML
1 | <%--在代码中嵌入HJTML--%> |
JSP声明:会被编译到JSP生成的Java的类中,其他的会被生成到_jspService方法中
1 | <%--全局代码块--%> |
也可以使用EL表达式(ExpressLanguage)
JSP的注释不会在客户端被查看到
自定义错误页面:在页面出错的时候将会自动定位到新页面
1 | <%--定制错误页面 |
1 | <!-- 定制错误页面 |
网页拼接
@include
会将两个页面合二为一
jsp:include
本质还是三个页面
JSP九大内置对象
1 | request内置对象 |
1 | <%--内置对象--%> |
双亲委派机制:这层没找到就向上一层找,直到找到最顶层,没找到就会NULL
JSP标签,JSTL标签,EL表达式
EL表达式:${}
- 获取数据
- 执行运算
- 获取web开发的常用对象
- 调用Java方法
JSP标签
1 | <h1>Page1</h1> |
1 | <h1>Page2</h1> |
JSTL表达式
JSTL标签库的使用就是为了弥补HTML标签的不足,它自定义了许多标签,可供我们使用,标签的功能和Java代码一样
核心标签是最常用的 JSTL标签。引用核心标签库的语法如下:
1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> |
JSTL使用步骤:
导入对应的taglib
使用其中的方法
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<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--引入JSTL--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h4>if测试</h4>
<hr>
<form action="coreIf.jsp" method="get">
<input type="text" name="name" value="${param.name}">
<%-- EL表达式获取表单中的数据 ${param.参数名} --%>
<input type="submit" value="提交">
</form>
<%--判断如果提交的是管理员则登录成功,否则登录失败--%>
<%--<%--%>
<%-- if (request.getParameter("name").equals("admin")) {--%>
<%-- out.println("登录成功");--%>
<%-- }--%>
<%--%>--%>
<c:if test="${param.name == 'admin'}" var="isAdmin">
<c:out value="管理员登录成功"/>
</c:if>
<%--自闭合标签--%>
<c:out value="${isAdmin}"/>
</body>
</html>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<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--定义一个变量--%>
<c:set var="score" value="85"/>
<c:choose>
<c:when test="${score>=90}">
<c:out value="优秀"/>
</c:when>
<c:when test="${score>=80}">
<c:out value="良好"/>
</c:when>
<c:when test="${score>=70}">
<c:out value="中等"/>
</c:when>
<c:when test="${score>=60}">
<c:out value="及格"/>
</c:when>
<c:otherwise>
<c:out value="不及格"/>
</c:otherwise>
</c:choose>
</body>
</html>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<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="java.util.ArrayList" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
ArrayList<String> people = new ArrayList<>();
people.add(0,"Jack");
people.add(1,"Tom");
people.add(2,"Jerry");
people.add(3,"Sam");
people.add(4,"Lily");
people.add(5,"Bob");
request.setAttribute("list",people);
%>
<%--
var:每次遍历出来的对象
items:要遍历的对象
--%>
<c:forEach var="p" items="${list}" >
<c:out value="${p}"/> <br>
</c:forEach>
<hr>
<%--
参数解释:
var:每次遍历出来的对象
items:要遍历的对象
step:步长
begin:起始值
end:结束值
--%>
<c:forEach begin="1" end="5" step="2" var="i" items="${list}">
<c:out value="${i}"/>
</c:forEach>
</body>
</html>
JavaBean
实体类
JavaBean由特定的写法:
- 必须要有一个无参构造
- 属性必须私有化
- 必须要有对应的get/set
一般用来和数据库的字段做映射 ORM
ORM:对象关系映射
- 表–>类
- 字段–>属性
- 行记录–>对象
people表:
id | name | age | address |
---|---|---|---|
1 | Joker1 | 18 | 湖南 |
2 | Joker2 | 19 | 湖南 |
3 | Joker3 | 20 | 湖南 |
1 | class People{ |
MVC三层架构
什么是MVC:Model模型,View视图,Controller控制器
以往的开发逻辑:
用户直接访问控制层,控制层就可以直接操作数据库
1
2
3
4
5
6servlet--CRUD-->数据库
弊端:程序十分臃肿,不利于维护 servlet的代码中:处理请求、响应、视图跳转、处理JDBC、处理业务代码、逻辑代码
架构:没有什么是加一层解决不了的
数据库有很多:Mysql、Oracle、SqlServer...我们只需要实现JDBC,就可以直接操作数据库
现在的开发逻辑:
- 用户只需要访问视图层
Model
- 业务处理:业务逻辑(Service)
- 数据持久层:CRUD(Dao)
View
- 展示数据
- 提供链接发起Servlet请求(a, form, img…)
Controller(Servlet)
接收用户的请求:(req:请求参数、Session信息…)
交给业务层处理对应的代码
控制视图的跳转
1
登录 ---> 接收用户的登录请求 ---> 处理用户的请求(获取用户登录的参数)--->交给业务层处理登录业务(判断用户登录名密码是否正确) ---> Dao层查询用户名和密码是否正确 ---> Dao查询数据库
一些术语:
Dao:
- DAO (DataAccessObjects 数据存取对象)是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来。
Pojo:
- POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。
- 使用POJO名称是为了避免和EJB混淆起来, 而且简称比较直接. 其中有一些属性及其getter setter方法的类,没有业务逻辑,有时可以作为VO(value -object)或dto(Data Transform Object)来使用.当然,如果你有一个简单的运算属性也是可以的,但不允许有业务方法,也不能携带有connection之类的方法。
过滤器
Filter:过滤器,用来过滤网站的数据
处理中文乱码
登录验证
Filter开发步骤:
导包
编写过滤器
注意导包不要错,是servlet下的Filter
实现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
26
27
28
29
30
31
32
33
34
35
36
37
38package com.joker_yue.filter;
import javax.servlet.*;
import java.io.IOException;
public class CharacterEncodingFilter implements Filter {
//初始化
// Web服务器启动了,过滤器就会同时启动
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("CharacterEncodingFilter 已经初始化");
}
//过滤
// Chain:链
/*
* 1. 过滤中的所有代码,在过滤特定请求的时候都会执行
* 2. 必须要让过滤器继续通行
* */
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html;charset=UTF-8");
System.out.println("CharacterEncodingFilter 执行前...");
// 下面这行代码是固定死了的!!!不然全给你过滤了
filterChain.doFilter(servletRequest, servletResponse);//让我们的请求继续走,如果不写,程序就停止了
System.out.println("CharacterEncodingFilter 执行后...");
}
//销毁
public void destroy() {
System.out.println("CharacterEncodingFilter 已经销毁");
}
}在web.xml中配置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
26
27
28
29
30
<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">
<servlet>
<servlet-name>ShowServlet</servlet-name>
<servlet-class>com.joker_yue.filter.ShowServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ShowServlet</servlet-name>
<url-pattern>/show</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ShowServlet</servlet-name>
<url-pattern>/servlet/show</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.joker_yue.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- 只要是/servlet/*的请求都会过滤 -->
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
</web-app>
监听器
实现监听器接口:(很多种)
实现监听器接口
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
49package com.joker_yue.listener;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
// 统计网站在线人数监听:统计Session
public class OnlineCountListener implements HttpSessionListener {
// 创建Session监听:看你的一举一动
// 一旦创建Session就会触发一次这个事件
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println(httpSessionEvent.getSession().getId());
ServletContext ctx = httpSessionEvent.getSession().getServletContext();
Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
if (onlineCount == null) {
onlineCount = Integer.valueOf("1");
} else {
int count = onlineCount.intValue();
onlineCount = count + 1;
}
ctx.setAttribute("OnlineCount", onlineCount);
}
// 销毁Session监听
// 一旦销毁Session就会触发一次这个事件
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
ServletContext ctx = httpSessionEvent.getSession().getServletContext();
Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount");
if (onlineCount == null) {
onlineCount = Integer.valueOf("0");
} else {
int count = onlineCount.intValue();
onlineCount = count - 1;
}
ctx.setAttribute("OnlineCount", onlineCount);
}
/*
* Session销毁方案
* 1. 手动销毁 getSession().invalidate();
* 2. 自动销毁 在web.xml中配置Session自动过期时间
* */
}在web.xml中注册监听器
1
2
3
4<!-- 注册监听器 -->
<listener>
<listener-class>com.joker_yue.listener.OnlineCountListener</listener-class>
</listener>看情况是否使用
1
<h1>当前有<span style="color: red"><%=getServletConfig().getServletContext().getAttribute("OnlineCount")%></span>人在线</h1>
过滤器、监听器常见应用
监听器:图形化(GUI)编程中常使用
1 | package com.joker_yue.listener; |
用户登录之后才能进入首页,用户注销之后就不能进入首页了
用户登录之后,向Session中放入用户的数据
进入主页的之后要判断用户是否已经登录。要求:在过滤器中实现
1
2
3
4
5
6
7
8
9
10
11
12
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.getSession().setAttribute("USER_SESSION", request.getSession().getId());
if(request.getSession().getAttribute("USER_SESSION") == null){
response.sendRedirect(request.getContextPath()+"/error.jsp");
}
filterChain.doFilter(servletRequest, servletResponse);
}
JDBC
什么是JDBC?Java连接数据库
导入数据库依赖
1 | <dependency> |
JDBC固定步骤
- 加载驱动
- 连接数据库
- 向数据库发送SQL的对象Statement。现在statement做CRUD
- 根据业务编写不同的sql
- 执行sql
- 关闭连接
1 | package com.joker_yue.test; |
预编译SQL
1 | package com.joker_yue.test; |
事务
要么都成功,要么都失败
ACID原则:保证数据安全
1 | 开启事务 |
Junit单元测试
依赖:
1 | <!-- 单元测试 --> |
简单使用:
@Test
注解只有在方法上有效,只要加了这个注解的方法就可以直接运行
1 | package com.joker_yue.test; |
SMBMS
超市订单管理系统
项目如何搭建
考虑使不使用Mavne
项目搭建准备工作
搭建一个maven web项目
配置Tomcat
测试项目是否能够跑起来
导入项目中需要的jar包
创建项目包结构
编写实体类
ORM映射:表-类映射
编写基础公共类
数据库配置文件
1
2
3
4driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306?useUnicode=ture&characterEncoding=UTF-8
user=root
password=root编写数据库的公共类
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99package com.joker_yue.dao;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
// 操作数据库的公共类
public class BaseDao {
private static String driver;
private static String url;
private static String username;
private static String password;
// 静态代码块,类加载的时候就初始化
static {
// 通过反射:类加载器读取对应的资源
InputStream is = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
try {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
}
// 获取数据库的链接
public static Connection getConnection() {
Connection connection = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
return connection;
}
// 编写查询公共类、
public static ResultSet execute(Connection connection, String sql, Object[] params, ResultSet resultSet, PreparedStatement preparedStatement) throws SQLException {
// 预编译的sql,在后面直接执行就可以了
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
// setObject,占位符从1开始,而数组从0开始
preparedStatement.setObject(i + 1, params[i]);
}
resultSet = preparedStatement.executeQuery();
return resultSet;
}
// 编写增删改公共方法
public static int execute(Connection connection, String sql, Object[] params) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
// setObject,占位符从1开始,而数组从0开始
preparedStatement.setObject(i + 1, params[i]);
}
int updateRows = preparedStatement.executeUpdate();
return updateRows;
}
// 关闭连接,释放资源
public static boolean closeResource(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) {
boolean flag = true;
if (resultSet != null) {
try {
// 关闭结果集
resultSet.close();
} catch (SQLException e) {
flag = false;
throw new RuntimeException(e);
}
}
if (preparedStatement != null) {
try {
// 关闭预编译
preparedStatement.close();
} catch (SQLException e) {
flag = false;
throw new RuntimeException(e);
}
}
if (connection != null) {
try {
// 关闭连接
connection.close();
} catch (SQLException e) {
flag = false;
throw new RuntimeException(e);
}
}
return flag;
}
}编写字符编码过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.joker_yue.filter;
import javax.servlet.*;
import java.io.IOException;
public class CharacterEncodingFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
public void destroy() {
Filter.super.destroy();
}
}1
2
3
4
5
6
7
8
9<!--字符编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.joker_yue.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
登录功能实现
编写前端页面
设置首页
1
2
3
4<!-- 设置欢迎页面 -->
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>编写dao层得到用户登录的接口
1
2
3
4
5
6
7
8
9
10
11package com.joker_yue.dao.user;
import com.joker_yue.pojo.User;
import java.sql.Connection;
import java.sql.SQLException;
public interface UserDao {
//得到登录的用户
public User getLoginUser(Connection connection,String userCode) throws SQLException;
}编写dao接口的实现类
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
47package com.joker_yue.dao.user;
import com.joker_yue.dao.BaseDao;
import com.joker_yue.pojo.User;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDaoImpl implements UserDao {
// 得到登录的用户
public User getLoginUser(Connection connection, String userCode) throws SQLException {
PreparedStatement prst = null;// 预编译
ResultSet resultSet = null;// 结果集
User user = null; // 要返回的实体类
if (connection != null) {
String sql = "select * from smbms_user where userCode=?";
Object[] params = {userCode};
resultSet = BaseDao.execute(connection, prst, resultSet, sql, params);
if (resultSet.next()) {
user = new User();
user.setId(resultSet.getInt("id"));
user.setUserCode(resultSet.getString("userCode"));
user.setUserName(resultSet.getString("userName"));
user.setUserPassword(resultSet.getString("userPassword"));
user.setGender(resultSet.getInt("gender"));
user.setBirthday(resultSet.getDate("birthday"));
user.setPhone(resultSet.getString("phone"));
user.setAddress(resultSet.getString("address"));
user.setUserRole(resultSet.getInt("userRole"));
user.setCreatedBy(resultSet.getInt("createdBy"));
user.setCreationDate(resultSet.getTimestamp("creationDate"));
user.setModifyBy(resultSet.getInt("modifyBy"));
user.setModifyDate(resultSet.getTimestamp("modifyDate"));
}
BaseDao.closeResource(null, prst, resultSet);
}
return user;
}
}业务层接口
1
2
3
4
5
6
7
8
9
10package com.joker_yue.service.user;
import com.joker_yue.pojo.User;
public interface UserService {
// 用户登录
public User login(String userCode, String userPassword);
}业务层实现类
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
37package com.joker_yue.service.user;
import com.joker_yue.dao.BaseDao;
import com.joker_yue.dao.user.UserDao;
import com.joker_yue.dao.user.UserDaoImpl;
import com.joker_yue.pojo.User;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
public class UserServiceImpl implements UserService {
// 业务层都会调用Dao层,所以我们要引入Dao层
private UserDao userDao;
public UserServiceImpl() {
userDao = new UserDaoImpl();
}
public User login(String userCode, String userPassword) {
Connection connection = null;
User user = null;
try {
connection = BaseDao.getConnection();
// 通过业务层调用对应的Dao层
user = userDao.getLoginUser(connection, userCode);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
BaseDao.closeResource(connection, null, null);
}
return user;
}
}编写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
43
44
45
46
47
48package com.joker_yue.servlet.user;
import com.joker_yue.pojo.User;
import com.joker_yue.service.user.UserServiceImpl;
import com.joker_yue.util.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginServlet extends HttpServlet {
// Servlet: 控制层调用业务层代码
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("LoginServlet...start");
// 获取用户名和密码
String userCode = req.getParameter("userCode");
String userPassword = req.getParameter("userPassword");
// 和数据库中的密码进行对比,调用业务层
UserServiceImpl userService = new UserServiceImpl();
User user = userService.login(userCode, userPassword); // 已经把登录的人查出来了
if (user != null) { //查到了,可以登录
// 将用户的信息放在Session中
req.getSession().setAttribute(Constants.USER_SESSION, user);
//登录成功后跳转到内部主页
resp.sendRedirect("jsp/frame.jsp");
} else {
// 登录失败
// 跳转到登录页面,顺便提示用户名或者密码错误
req.setAttribute("error", "用户名或者密码错误");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}注册Servlet
1
2
3
4
5
6
7
8
9<!-- Servlet -->
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.joker_yue.servlet.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login.do</url-pattern>
</servlet-mapping>测试访问
登录功能优化
注销功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package com.joker_yue.servlet.user;
import com.joker_yue.util.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LogoutServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//移除Session
req.getSession().removeAttribute(Constants.USER_SESSION);
//重定向至登录
resp.sendRedirect(req.getContextPath()+"/login.jsp");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}1
2
3
4
5
6
7
8<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>com.joker_yue.servlet.user.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/jsp/logout.do</url-pattern>
</servlet-mapping>权限访问过滤
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
36package com.joker_yue.filter;
import com.joker_yue.pojo.User;
import com.joker_yue.util.Constants;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SysFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//过滤器,从Session中获取用户
User user = (User)httpServletRequest.getSession().getAttribute(Constants.USER_SESSION);
if(user==null){//已经被移除或者注销
httpServletResponse.sendRedirect(httpServletRequest.getContextPath()+"/error.jsp");
}else {
chain.doFilter(request,response);
}
}
public void destroy() {
Filter.super.destroy();
}
}1
2
3
4
5
6
7
8
9<!-- 权限访问过滤器 -->
<filter>
<filter-name>SysFilter</filter-name>
<filter-class>com.joker_yue.filter.SysFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SysFilter</filter-name>
<url-pattern>/jsp/*</url-pattern>
</filter-mapping>
密码修改
UserDao接口
1
2//修改用户密码
public int updatePwd(Connection connection,int id,int pwd)throws SQLException;UserDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
public int updatePwd(Connection connection, int id, int pwd) throws SQLException {
PreparedStatement prst = null;
int execute=0;
if (connection != null) {
String sql = "update smbms.smbms_user set userPassword = ? where id =?";
Object params[] = {pwd, id};
execute = BaseDao.execute(connection, prst, sql, params);
BaseDao.closeResource(null, prst, null);
}
return execute;
}UserService层
1
2// 根据UserId修改密码
public boolean updatePwd(int id,int password);UserService实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public boolean updatePwd(int id, int password) {
Connection connection = null;
boolean flag = false;
// 修改密码
try {
connection = BaseDao.getConnection();
if (userDao.updatePwd(connection, id, password) > 0) {
flag = true;
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
BaseDao.closeResource(connection, null, null);
}
return flag;
}UserServlet
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
46package com.joker_yue.servlet.user;
import com.joker_yue.pojo.User;
import com.joker_yue.service.user.UserServiceImpl;
import com.joker_yue.util.Constants;
import com.mysql.cj.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从Session中获取用户Id
Object user = req.getSession().getAttribute(Constants.USER_SESSION);
String newPassword = req.getParameter("newpassword");
boolean flag = false;
System.out.println("已经进入修改密码");
System.out.println("user = " + (User) user);
if (user != null && !StringUtils.isNullOrEmpty(newPassword)) {
UserServiceImpl userService = new UserServiceImpl();
flag = userService.updatePwd(((User) user).getId(), newPassword);
if (flag) {
req.setAttribute("message", "修改密码成功,请退出,使用新密码登录");
// 密码修改成功,移除Session
req.getSession().removeAttribute(Constants.USER_SESSION);
} else {
req.setAttribute("message", "修改密码失败");
}
} else {
req.setAttribute("message", "新密码有问题");
}
req.getRequestDispatcher("pwdmodify.jsp").forward(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}记得实现复用,需要提取出方法
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
76package com.joker_yue.servlet.user;
import com.joker_yue.pojo.User;
import com.joker_yue.service.user.UserServiceImpl;
import com.joker_yue.util.Constants;
import com.mysql.cj.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 用户操作
* 实现servlet复用
*
* @author Joker
* @version 1.0
* @date 2023/7/18 13:38
*/
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getParameter("method");
if(method.equals("savepwd") && method!=null){
this.updatePwd(req,resp);
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
/**
* <p> 更新用户密码的封装 </p>
* <p> 2023/7/18,14:12 </p>
*
* @param req 传入
* @param resp 响应
*/
public void updatePwd(HttpServletRequest req, HttpServletResponse resp) {
// 从Session中获取用户Id
Object user = req.getSession().getAttribute(Constants.USER_SESSION);
String newPassword = req.getParameter("newpassword");
boolean flag = false;
System.out.println("已经进入修改密码");
System.out.println("user = " + (User) user);
if (user != null && !StringUtils.isNullOrEmpty(newPassword)) {
UserServiceImpl userService = new UserServiceImpl();
flag = userService.updatePwd(((User) user).getId(), newPassword);
if (flag) {
req.setAttribute("message", "修改密码成功,请退出,使用新密码登录");
// 密码修改成功,移除Session
req.getSession().removeAttribute(Constants.USER_SESSION);
} else {
req.setAttribute("message", "修改密码失败");
}
} else {
req.setAttribute("message", "新密码有问题");
}
try {
req.getRequestDispatcher("pwdmodify.jsp").forward(req, resp);
} catch (ServletException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
优化修改密码使用Ajax
1 | package com.joker_yue.servlet.user; |
用户管理实现
导入分页的工具类
用户列表页面导入
userlist.jsp
rollpage.jsp
获取用户数量
UserDao
1
2// 查询用户数量
public int getUserCount(Connection connection,String userName,int userRole) throws SQLException;UserDaoImpl
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/**
* <p> 根据用户名或角色查询用户 </p>
* <p> 2023/7/18,16:51 </p>
*
* @param connection 连接
* @param userName 用户名
* @param userRole 用户角色
* @return int
*/
public int getUserCount(Connection connection, String userName, int userRole) throws SQLException {
PreparedStatement prst = null;
ResultSet resultSet = null;
int count = 0;
if (connection != null) {
StringBuffer sql = new StringBuffer();
sql.append("select count(1) as count from smbms.smbms_user u,smbms.smbms_role r where u.userRole = r.id");
ArrayList<Object> list = new ArrayList<>();
if (!StringUtils.isNullOrEmpty(userName)) {
sql.append(" and u.userName like ?");
list.add("%" + userName + "%"); // index 0
}
if (userRole > 0) {
sql.append(" and u.userRole = ?");
list.add(userRole); // index 1
}
// 怎么把List转换成数组
Object[] params = list.toArray();
// 输出sql语句
System.out.println("This is in UserDaoImpl, sql is:" + sql.toString());
resultSet = BaseDao.execute(connection, prst, resultSet, sql.toString(), params);
if (resultSet.next()) {
count = resultSet.getInt("count"); // 从结果集中获取数量
}
BaseDao.closeResource(null, prst, resultSet);
}
return count;
}UserService
1
2// 查询记录数
public int getUserCount(String userName,int userRole);UserServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* <p> 查询记录数 </p>
* <p> 2023/7/18,17:20 </p>
*
* @param userName 用户名
* @param userRole 用户角色
* @return int
*/
public int getUserCount(String userName, int userRole) {
Connection connection = null;
int count = 0;
try {
connection = BaseDao.getConnection();
count = userDao.getUserCount(connection, userName, userRole);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
BaseDao.closeResource(connection, null, null);
}
return count;
}UserServiceImpl测试
1
2
3
4
5
6
public void Test() {
UserServiceImpl userService = new UserServiceImpl();
int userCount = userService.getUserCount(null, 0);
System.out.println(userCount);
}
获取用户列表
UserDao
1
2// 获取用户列表
List<User> getUserList(Connection connection, String userName, int userRole, int currentPageNo, int pageSize) throws Exception;UserDaoImpl
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/**
* <p> 获取用户列表 </p>
* <p> 2023/7/18,19:19 </p>
*
* @param connection 连接
* @param userName 用户名
* @param userRole 用户角色
* @param currentPageNo 当前页
* @param pageSize 页面大小
* @return java.util.List<com.joker_yue.pojo.User>
*/
public List<User> getUserList(Connection connection, String userName, int userRole, int currentPageNo, int pageSize) throws Exception {
PreparedStatement pstm = null;
ResultSet rs = null;
List<User> userList = new ArrayList<User>();
if (connection != null) {
StringBuffer sql = new StringBuffer();
sql.append("select u.*,r.roleName as userRoleName from smbms_user u,smbms_role r where u.userRole = r.id ");
List<Object> list = new ArrayList<Object>();
if (!StringUtils.isNullOrEmpty(userName)) {
sql.append(" and u.userName like ?");
list.add("%" + userName + "%");
}
if (userRole > 0) {
sql.append(" and u.userRole =?");
list.add(userRole);
}
// 在数据库中,分页使用 limit,参数有 startIndex,pagesize;总数
// 当前页 (当前页-1)*页面大小
// 0, 5 1页从0开始 显示01234
// 5, 5 2页从5开始 显示26789
// 10, 5 3页从10开始 显示
sql.append(" order by creationDate DESC limit ?,?");
currentPageNo = (currentPageNo - 1) * pageSize;
list.add(currentPageNo);
list.add(pageSize);
Object[] params = list.toArray();
// System.out.println("sql---->" + sql.toString());
rs = BaseDao.execute(connection, pstm, rs, sql.toString(), params);
while (rs.next()) {
User _user = new User();
_user.setId(rs.getInt("id"));
_user.setUserCode(rs.getString("userCode"));
_user.setUserName(rs.getString("userName"));
_user.setGender(rs.getInt("gender"));
_user.setBirthday(rs.getDate("birthday"));
_user.setPhone(rs.getString("phone"));
_user.setUserRole(rs.getInt("userRole"));
_user.setUserRoleName(rs.getString("userRoleName"));
userList.add(_user);
}
BaseDao.closeResource(null, pstm, rs);
}
return userList;
}UserService
1
2// 根据条件查询用户列表
List<User> getUserList(String queryUserName, int queryUserRole, int currentPageNo, int pageSize);UserSeviceImpl
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
/**
* <p> 根据条件查询用户列表 </p>
* <p> 2023/7/18,19:22 </p>
*
* @param queryUserName 要查询的用户名
* @param queryUserRole 要查询的用户角色
* @param currentPageNo 当前页
* @param pageSize 每页显示多少条
* @return java.util.List<com.joker_yue.pojo.User>
*/
public List<User> getUserList(String queryUserName, int queryUserRole, int currentPageNo, int pageSize) {
Connection connection = null;
List<User> userList = null;
try {
connection = BaseDao.getConnection();
userList = userDao.getUserList(connection, queryUserName, queryUserRole, currentPageNo, pageSize);
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
BaseDao.closeResource(connection, null, null);
}
return userList;
}
获取角色操作
==为了我们职责统一,可以把角色的操作单独放在一个包中,和pojo类对应==
RoleDao
1
2// 获取角色列表
public List<Role> getRoleList(Connection connection) throws SQLException;RoleDaoImpl
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/**
* <p> 获取角色列表 </p>
* <p> 2023/7/18,19:36 </p>
*
* @param connection 连接
* @return java.util.List<com.joker_yue.pojo.Role>
*/
public List<Role> getRoleList(Connection connection) throws SQLException {
PreparedStatement pstm = null;
ResultSet resultSet = null;
ArrayList<Role> roleList = new ArrayList<Role>();
if (connection != null) {
String sql = "select * from smbms_role";
Object[] params = {};
BaseDao.execute(connection, pstm, resultSet, sql, params);
while (resultSet.next()) {
Role _role = new Role();
_role.setId(resultSet.getInt("id"));
_role.setRoleName(resultSet.getString("roleName"));
_role.setRoleCode(resultSet.getString("roleCode"));
_role.setCreatedBy(resultSet.getInt("createdBy"));
_role.setCreationDate(resultSet.getTimestamp("creationDate"));
_role.setModifyBy(resultSet.getInt("modifyBy"));
_role.setModifyDate(resultSet.getTimestamp("modifyDate"));
roleList.add(_role);
}
BaseDao.closeResource(null, pstm, resultSet);
}
return roleList;
}RoleService
1
2// 获取角色列表
public List<Role> getRoleList() throws SQLException;RoleServiceImpl
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
30public class RoleServiceImpl implements RoleService {
// 引入Dao
private RoleDao roleDao;
public RoleServiceImpl() {
roleDao = new RoleDaoImpl();
}
/**
* <p> 获取角色列表 </p>
* <p> 2023/7/18,19:50 </p>
*
* @param connection 连接
* @return java.util.List<com.joker_yue.pojo.Role>
*/
public List<Role> getRoleList() throws SQLException {
Connection connection = null;
List<Role> roleList = null;
try {
connection = BaseDao.getConnection();
roleList = roleDao.getRoleList(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
BaseDao.closeResource(connection, null, null);
}
return roleList;
}
}
用户显示的Servlet
- 获取用户前端的数据(查询)
- 判断请求是否需要执行,看参数的值判断
- 为了实现分页,需要计算出当前页和总页面、页面大小
- 用户列表展示
- 返回前端
1 | /** |
小黄鸭调试法 - 维基百科:自言自语
文件上传
UUID:时间戳+网卡状态+随机id
1.注意事项:
表单必须如下格式:
1
<form action="" method="post" enctype="multipart/form-data">
get: 上传文件大小有限制,post: 上传文件大小无限制
为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
要限制上传文件的最大值。
可以限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
2. 使用类介绍
【需要用到的类详解】
ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象, 在使用ServletFileUpload对象解析请求时需要DiskFileItemFactory对象。所以,我们需要在进行解析工作前构造好DiskFileItemFactory对象,通过==ServletFileUpload对象的构造方法或setFileItemFactory()==方法设置ServletFileUpload对象的fileItemFactory属性。
FileItem类
在HTML页面input 必须有 name <input type="file" name="filename">
表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为multipart/form-data
浏览器表单的类型如果为multipart/form-data , 在服务器端想获取数据就要通过流。
1 | <%-- |
【常用方法介绍】
1 | //isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单 |
ServletFileUpload 类
ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中 . 使用其parseRequest(HttpServletRequest) 方法可以将通过表单中每一个HTML标签提交的数据封装成一个FileItem对象,然后以List列表的形式返回。使用该方法处理上传文件简单易用。
3. 代码编写
UploadServlet
1 | public class UploadServlet extends HttpServlet { |
upload.jsp
1 | 1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
msg.jsp
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
web.xml
1 |
|
邮件发送
要在网络上实现网络邮件功能,必须要有专门的邮件服务器
这些邮件服务器类似于现实生活中的邮局,它主要负责接收用户投递过来的邮件,并把邮件递到邮件接收者的电子邮箱中。
SMTP 服务器地址:一般是 smtp.xxx.com ,比如 163 邮箱是 smtp.163.com , qq 邮箱是 smtp.qq.com
发送邮件:SMTP;接收邮件:POP3
使用 Java 发送 E-mail 十分简单,但是首先你应该准备 JavaMail API 和 Java Activation Framework 。
得到两个 jar 包: mail.jar, activation.jar
JavaMail 是 sun 公司(现已被甲骨文收购)为方便 Java 开发人员在应用程序中实现邮件发送和接收功能而提供的一套标准开发包,它支持一些常用的邮件协议,如 SMTP , POP3 , IMAP, 还有 MIME(多用途互联网邮件扩展类型,即附件) 等。我们在使用 JavaMail 编写邮件时,无须考虑邮件的底层实现细节,只要调用 JavaMail 开发包中相应的 API类就可以了。
主要有4个核心类,我们在编写程序时,记住这四个核心类,就很容易编写出 Java 邮件处理程序。
无附件邮件发送
1 | package com.joker_yue.mail; |
有附件邮件发送
MIME (多用途互联网邮件扩展类型)
MimeBodyPart 类
- javax.mail.internet.MimeBodyPart 类表示的是一个 MIME 消息,MimeMuItipart 类它和 MimeMessage 类一样都是从 part 接口继承过来。
MimeMultipart 类
- javax.mail.internet.MimeMultipart 是抽象类 Multipart 的实现子类,它用代表 MIME 消息的 MimeBodyPart 对象。来组合多个 MIME消息。一个 MimeMultipart 对象可以包含多个代表 MIME 消息的 MimeBodyPart 对象。
代码
1 | package com.joker_yue.mail; |
1 | image.setFileName("bg.jpg"); //以附件形式发送 |
测试结果