JavaWeb
1、基本概念
1.1、前言
Web开发:
- 静态Web
- html,css
- 提供给所有人看的数据始终不会发生变化
- 动态web
- 淘宝,几乎所有的网站
- 每个人在不同的时间不同的地点看到的信息各不相同
- 常用的技术栈:Servlet/JSP, ASP, PHp
在Java中,动态web资源开发的技术栈叫JavaWeb
1.2、web应用程序
Web应用程序:可以提供给浏览器访问的程序
- a.html、b.html…多个web资源可以被外界访问,对外界提供服务;
- 能访问的任何一个资源都存在在世界上某一个角落的计算机上
- URL
- 这些统一的web资源会被放在统一的文件夹下,web应用程序–> Tomcat:服务器
- 一个web应用由多部分组成(静态web,动态web)
- html,css,js
- jsp,servlet
- Java程序
- jar包
- 配置文件(Properties)
web应用程序编写完毕后,如果想提供给外界访问,需要一个服务器进行统一管理:
1.3、静态Web
- *.html 都是网页的后缀,如果服务器上一直都存在这些东西,我们就可以进行静态读取。
- 静态web存在的缺点
- Web页面无法进行动态更新,所有用户看到的都是同一个页面
- 轮播图,点击特效:伪动态
- JavaScript[实际开发中,用得最多]
- 它无法和数据库交互(数据无法持久话,用户无法交互)
1.4、动态Web
- Web页面展示的效果因人而异
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-od9xlHbs-1587810422900)(JavaWeb.assets/截屏2020-04-18 上午12.55.05.png)]
缺点:
- 假如服务器的动态web资源出现了错误,我们需要重新编写后台程序,重新发布;
- 网站停机维护
优点:
- Web页面可以动态更新,千人千面
- 它可以与数据库交互(数据持久化)
2、web服务器
2.1、技术讲解
实现web手段:
- ASP
- 属于微软,国内最早流行
- 在html中嵌入了VB的脚本,asp+com;
- 在ASP开发中,基本一个页面,每个页面几千行代码,维护成本高
- C#
- IIS模型
- PHP
- php开发速度快,功能强大,跨平台
- 无法承载大访问量的情况(有局限性)
- JSP/servlet:
B/S:浏览器和服务器
C/S:客户端和服务器
- Sun公司主推的B/S架构
- 可以基于Java语言的(所有的大公司,或者一些开源组件,都是用Java写的)
- 可以承载三高问题带来的影响
- 语法像ASP, ASP --> JSP,加强市场强度
2.2、Web服务器
服务器是一种被动的操作,用来处理用户的一些请求和用户的一些相应信息
IIS:windows自带
Tomcat:技术先进性能稳定的免费Web服务器,实际上运行的JSP页面以及Servlet,具有处理HTML页面的功能,另外它还是一个Serlvet和JSP容器,独立的Servlet容器是Tomcat的基本模式
2.3、Tomcat
2.3.1、文件目录
-
bin
:启动、关闭的脚本文件 -
conf
:配置 -
lib
:依赖的jar包 -
logs
:日志文件,输出 -
webapps
:存放网站
2.3.2、服务器核心文件
Server.xml
- 可以更改服务器端口
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
常用的端口号:
- MySQL:3306
- Tomcat:8080
- http:80
- https:443
- 可以配置主机名称:
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"></Host>
<Host name = "localhost">
服务器在访问域名时,会去查看host文件的映射关系,之后再去DNS服务器那里进行转换,比如这里的localhost换一个其他域名,需要在系统的host文件夹更改映射关系。
面试题:网站是如何进行访问的?
- 输入一个域名,点击回车
- 检查本机的Host配置文件下有没有这个域名映射
- 如果有,直接返回域名的IP地址
- 没有:去DNS服务器找。 DNS管理全世界的域名
2.3.3、发布一个web网站
- 将自己的网站,放在服务器(tomcat)指定的web应用文件夹下(webapps),打开
localhost:8080/项目名
就可以访问了 - 网站应该有的结构
--webapps /* Tomcat服务器目录*/
-ROOT
-NeilStudy: /*网站的目录名*/
-index.html /*默认的首页*/
-WEB-INF
-classes /*Java程序*/
-lib /*web应用所依赖的jar包*/
-web.xml. /*网站配置文件*/
- static /*静态文件*/
-css
-style.css
-js
-img
3、HTTP
3.1、什么是http
http(超文本传输协议)是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。
- 文本:html,字符串
- 超文本:图片,音乐,视频,定位,地图…
3.2、两个时代
- http1.0
- HTTP/1.0:客户端可以与web服务器连接后,只能获得一个web资源,断开链接
- Http2.0
- HTTP/1.1:客户端与web服务器连接后,可以获得多个web资源
3.3、HTTP请求
- 客户端–发送请求(request)—服务器
百度:
Request URL: https://www.baidu.com/
Request Method: GET
Status Code : 200 状态码
Remote Address:14.215.177.39:443
3.4、HTTP响应
- 服务器—响应— 客户端
百度
Cache-Control: private //缓存控制
Connection: keep-alive //tcp长链接
Content-Encoding: gzip //编码
Content-Type: text/html;charset=utf-8 //类型
3.4.1、状态相应码
- 200:状态请求成功
- 3xx:请求重定向
- 404:找不到资源
- 5xx:服务器代码错误
- 502:网关错误
5、Maven
为什么要学习这个技术
- JavaWeb中需要使用大量的Jar包,需要手动去导入
- 需要一个工具自动导入Jar包
由此,maven诞生了!
5.1、Maven项目架构管理工具
Maven核心思想:约定大于配置
- 有约束,不要去违反
Maven会规定好如何去编写Java代码,必须按照规范
5.2、阿里云镜像
- 镜像:mirrors
- 作用:加速国内的下载
- 国内建议使用阿里云的镜像,在conf/setting更改
案例:
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf>
<name>Nexus maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
5.3、本地仓库
建立一个本地仓库:localRepository
<localRepository>相对路径,如.\apache-maven-3.6.3\maven-repo</localRepository>
5.4、IDEA中启动Maven
- 启动IDAR
- 创建一个Maven项目(可以选择结构)
- 观察仓库结构
- 创建java文件夹
- 标记成为资源路径(project structure 可以标记资源)
5.5、在IDEA中配置Tomcat
- 点击 add configuration ,点击Tomcat
- 点击deployment,点击+添加war包,部署完成点击ok
5.6、pom文件可能遇到的问题
标准的Maven项目都会有一个resources目录来存放我们所有的资源配置文件,但是我们往往在项目中不仅仅会把所有的资源配置文件都放在resources中,同时我们也有可能放在项目中的其他位置,那么默认的maven项目构建编译时就不会把我们其他目录下的资源配置文件导出到target目录中,就会导致我们的资源配置文件读取失败,从而导致我们的项目报错出现异常,比如说尤其我们在使用MyBatis框架时,往往Mapper.xml配置文件都会放在dao包中和dao接口类放在一起的,那么执行程序的时候,其中的xml配置文件就一定会读取失败,不会生成到maven的target目录中,所以我们要在项目的pom.xml文件中进行设置,并且我建议大家,每新建一个maven项目,就把该设置导入pom.xml文件中,以防不测!!!
<build>
.......
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
</excludes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
......
</build>
6、Servlet
6.1、简介
- Servlet是sun公司开发动态web的一门技术
- Sun在这些API中提供了一个接口:Serlvet
- 编写一个类,实现Servlet接口
- 把开发好的Java类部署到web服务器中
把实现了Servlet接口的Java程序叫做,Servlet
6.2、HelloServlet
Servelt在Sun公司有两个默认实现类:HttpServlet
- 新建一个普通的maven项目,删掉src目录
- 在pom里面添加Servlet和jsp的依赖
- 关于Maven父子工程的理解:
- 父项目中会有
<modules>
<module>servlet-01</module>
</modules>
- 子项目会有
<parent>
<artifactId>javaWeb_servlet</artifactId>
<groupId>com</groupId>
<version>1.0-SHAPSHOT</version>
</parent>
- 父项目中的java子项目可以直接使用
- Maven环境优化
- 修改web.xml环境最优化
- 将maven结构搭建完整
- 编写一个Servlet程序
- 编写一个普通类
- 实现Serlvet接口HttpServlet
public class HelloServlet extends HttpServlet {
//由于get或post只是请求实现的不同方式,可以互相调用
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter(); //响应流
writer.print("Hello, Servlet");
}
}
- 在web.xml编写Servlet的映射
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.neil.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
- 配置Tomcat,启动测试
6.3、Servlet原理
- Servlet
6.4、Mapping
- 一个Servlet可以指定一个或者多个映射路径
<!--指定一个路径-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.neil.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet01</url-pattern>
</servlet-mapping>
<!--增加后缀-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/servlet01/*</url-pattern>
</servlet-mapping>
<!--可以自定义后缀实现映射,*前面不能加/-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>*.neil</url-pattern>
</servlet-mapping>
- Servlet优先级为固有路径最高,如果没有就走默认路径
<!--自定义404 Servlet-->
<servlet>
<servlet-name>error</servlet-name>
<servlet-class>com.neil.servlet.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>error</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
6.5、ServletContext
Web容器在启动的时候,它会为每个web程序都创建一个对应的ServletContext对象,它代表了当前的web应用
应用:
6.5.1、共享数据
在一个Servlet中保存的数据,可以在另外一个Servlet中拿到
- ServletContext凌驾于所有Servlet之上,所以所有的Serlvet都可以使用ServletContext
放置:
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();//获取上下文
String username = "Neil"; //数据
servletContext.setAttribute("username",username); //将一个数数据保存在了servletContext中,用key value形式存储
System.out.println("hello");
}
}
读取:
public class getServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
String username = (String) this.getServletContext().getAttribute("username");//通过Key获取Value
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().print("名字" + username);
}
}
web.xml
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.neil.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/neil</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>getc</servlet-name>
<servlet-class>com.neil.getServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getc</servlet-name>
<url-pattern>/getc</url-pattern>
</servlet-mapping>
6.5.2、获取初始化参数
public class GetInitParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
//通过key-value获取配置在web.xml的初始化参数
String url = servletContext.getInitParameter("url");
System.out.println(url);
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().print("URL + " + url);
}
}
Web.xml
<!--配置初始化参数-->
<context-param>
<param-name>url</param-name>
<param-value>jdbc::mysql://localhost:3306/mybatis</param-value>
</context-param>
<servlet>
<servlet-name>GetInitParam</servlet-name>
<servlet-class>com.neil.GetInitParameter</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GetInitParam</servlet-name>
<url-pattern>/initParam</url-pattern>
</servlet-mapping>
6.5.3、请求转发
- 请求转发
A请求B的资源,B请求C的资源,C返回资源到B,B返回资源到A - 请求重定向
A请求B的资源,B返回给A需要去请求C的资源,A请求C的资源(url地址会发生改变)
public class RequestDispatcher extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();//获取上下文
System.out.println("进入了请求转发的方法,url路径是不变的");
//请求转发的路径
javax.servlet.RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/initParam");
//请求转发执行
requestDispatcher.forward(req,resp);
}
}
web.xml
<servlet>
<servlet-name>RequestDispatcher</servlet-name>
<servlet-class>com.neil.RequestDispatcher</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RequestDispatcher</servlet-name>
<url-pattern>/requestDispatcher</url-pattern>
</servlet-mapping>
6.5.4、读取资源文件
Properties
- 在Java目录下新建Java
- 在resources目录下新建perperties
发现:都被打包了到了同一个路径下:classes,我们俗称这个路径为classpath:
步骤
- 在resources里面建立db.properties文件
username="Neil"
password="123567"
- 建立ServletResources类
public class ServletResources extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//在target目录下找到这个db.properties文件
//关键是在于要用流的形式把它读取出来
InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
//新建一个properties对象并且把这个输入流读取进去
Properties properties = new Properties();
properties.load(resourceAsStream);
String username = properties.getProperty("username");
String password = properties.getProperty("password");
resp.getWriter().print("username +" + username );
resp.getWriter().print("password +" + password );
}
}
- 建立web.xml设置路径
<servlet>
<servlet-name>ServletResources</servlet-name>
<servlet-class>com.neil.ServletResources</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletResources</servlet-name>
<url-pattern>/getProperties</url-pattern>
</servlet-mapping>
6.6、HttpServletResponse
Web服务器接收到客户端的http请求,会针对这个请求分别创建请求一个代表请求的Request对象和代表响应的HttpServletResponse对象
- 如果要获取客户端请求过来的参数:找HttpServletRequest
- 如果要给客户端响应一些信息,找HttpServletResponse
简单分类(包括HttpServletResponse和它的父类ServletResponse)
负责向浏览器发送数据的方法
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
负责向浏览器发送响应头的方法
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentType(String var1);
void setDateHeader(String var1, long var2);
void addDateHeader(String var1, long var2);
void setHeader(String var1, String var2);
void addHeader(String var1, String var2);
void setIntHeader(String var1, int var2);
void addIntHeader(String var1, int var2);
void setStatus(int var1);
int getStatus();
响应状态码也在HttpServletResponse里面
常见应用
- 向浏览器输出消息
6.6.1、实现下载文件
- 获取下载文件的路径
- 下载的文件名是啥
- 想办法让浏览器支持下载我们需要的东西
- 获取下载文件的输入流
- 创建缓冲区
- 获取OutPutStream对象
- 将FileOutPutStream流写入到buffer缓冲区
- 将OutpuStream缓冲区的应用输出到客户端
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取下载文件的路径
String realPath = "/Users/kuifengyuan/Desktop/Java-learn/JavaWeb_servlet/response/target/classes/dongma.jpeg";
System.out.println("下载文件路径 + " + realPath);
// 2. 下载的文件名是啥
String filename = realPath.substring(realPath.lastIndexOf("\\") + 1);
// 3. 想办法让浏览器支持下载我们需要的东西, 让文件变成中文下载
resp.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(filename,"UTF-8"));
// 4. 获取下载文件的输入流
FileInputStream fileInputStream = new FileInputStream(realPath);
// 5. 创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];
// 6. 获取OutPutStream对象
ServletOutputStream outputStream = resp.getOutputStream();
// 7. 将FileOutPutStream流写入到buffer缓冲区,使用OutPutStream将缓冲区的数据输出到客户端
while ((len = fileInputStream.read(buffer)) > 0){
outputStream.write(buffer,0,len);
}
outputStream.close();
fileInputStream.close();
// 8. 将OutpuStream缓冲区的应用输出到客户端
}
}
6.6.2、实现验证码功能
- 前端实现
- 后端实现,需要用到Java图片类,生成一个图片
public class ImageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//如何让浏览器5秒自动刷新一次?
resp.setHeader("refresh","3");
//内存中创建图片
BufferedImage bufferedImage = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
//得到图片
Graphics2D graphics = (Graphics2D)bufferedImage.getGraphics();
//设置图片的背景颜色
graphics.setColor(Color.WHITE);
graphics.fillRect(0,0,80,20);
//给图片写数据
graphics.setColor(Color.BLUE);
graphics.setFont(new Font(null,Font.BOLD,20));
graphics.drawString(makeNum(),0,18);
//告诉浏览器这个请求用图片的方式打开
resp.setContentType("image/jpeg");
//网站存在缓存,不让浏览器缓存
resp.setDateHeader("expires",-1);
resp.setHeader("Cache-Control","no-cache");
resp.setHeader("Pragma","no-cache");
//把图片写给浏览器
ImageIO.write(bufferedImage,"jpg",resp.getOutputStream());
}
public String makeNum(){
Random random = new Random();
String num = random.nextInt(9999999) + "";
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 7 - num.length(); i++) {
sb.append("0");
}
String s = sb.toString() + num;
return num;
}
}
6.6.3、实现重定向
常见场景:
- 用户登陆
void sendRedirect(String var1) throws IOException;
- 代码实现
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Location","/response_war/image");
resp.setStatus(302);
resp.sendRedirect("/response_war/image");
}
}
- 实现原理:
setHeader(Location,address)
setStatus(int StatusCode)
6.7、HttpServletRequest
HttpServletRequest代表客户端的请求,用户通过Http协议访问服务器,Http请求中的所有信息都会封装到 httpServletRequest里面,通过该方法可以获取客户端的所有信息
6.7.1、获取前端传递的参数
request.getParaemter(String s)
request.getParameterValues(String s)
后端代码:
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbies = req.getParameterValues("hobby");
System.out.println("===============");
//后台接收中文乱码问题
System.out.println(username);
System.out.println(password);
System.out.println(Arrays.toString(hobbies));
System.out.println("===============");
//通过请求转发
req.getRequestDispatcher("/success.jsp").forward(req,resp);
}
}
前端代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Helloworld</title>
</head>
<body>
<h1>Hello Jsp</h1>
<div style="text-align: center">
<%--用post方式提交表单,提交我们的login请求--%>
<form action="${pageContext.request.contextPath}/login" method="post">
用户名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"><br>
爱好:
<input type="checkbox" name="hobby" value="女孩">女孩
<input type="checkbox" name="hobby" value="唱歌">唱歌
<input type="checkbox" name="hobby" value="电影">电影
<input type="checkbox" name="hobby" value="代码">代码
<input type="submit">
</form>
</div>
</body>
</html>
面试题:重定向和转发的区别
相同点:
- 页面都会实现跳转
不同点:
- 请求转发的时候,url不会发生变化 307
- 重定向的时候,rul地址栏会发生变化 302
7、Cookie、Session
7.1、会话
会话:用户打开一个浏览器,点击了很多超链接,访问了多个web资源,关闭浏览器,这个过程可以称之为会话
有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学曾经来过,称之为有状态会话
一个网站如何证明来过?
客户端 服务端
- 服务端给客户端一个新建,客户端下次访问服务端带上信件就可以了 --> Cookie
- 服务器登记你来过了,喜爱你来的时候我来匹配你
7.2、保存会话的两种技术
- Cookie
- 客户端技术(通过响应,请求)
- Session
- 服务器技术,利用这个技术可以保存用户的会话信息,我们可以把信息或者数据放在Session中
常见场景:网站登陆后,你下次不用再登陆了,第二次访问直接上去
7.3、Cookie
- 从请求中拿到cookie信息
- 服务器响应给客户端cookie
/*常用cookie方法*/
//Cookie,服务器端从客户端获取
Cookie[] cookies = req.getCookies(); //这里返回数组,表示cookies可能存在多个
cookie.getName(); //获得cookie中的key
cookie.getValue(); //获得cookie中的value
//服务器给客户端发一个cookies
Cookie cookie = new Cookie("lastLoginTime",System.currentTimeMillis()+"");
//设置成一天的有效期
cookie.setMaxAge(24*60*60);
resp.addCookie(cookie);
//保存用户上一次访问的时间
public class cookie extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//服务器,告诉你,你来的时间,把这个时间封装成为一个信件,你下次带着来,我就知道你来了
//解决中文乱码问题
req.setCharacterEncoding("GBK");
resp.setCharacterEncoding("GBK");
PrintWriter out = resp.getWriter();
//Cookie,服务器端从客户端获取
Cookie[] cookies = req.getCookies(); //这里返回数组,表示cookies可能存在多个
//判断cookies是否存在
if(cookies != null){
out.write("你上一次访问的时间是:");
for (Cookie cookie : cookies) {
//获取cookies的名字
String name = cookie.getName();
System.out.println("这个cookies的名字是 " + name);
if(name.equals("lastLoginTime")){
//获取cookies的值
System.out.println(cookie.getValue() + "");
long l = Long.parseLong(cookie.getValue());
Date date = new Date(l);
out.write(String.valueOf(date));
}
}
}else {
out.write("这是您第一次访问本站");
}
//服务器给客户端发一个cookies
Cookie cookie = new Cookie("lastLoginTime",System.currentTimeMillis()+"");
//设置成一天的有效期
cookie.setMaxAge(24*60*60);
resp.addCookie(cookie);
}
}
cookie:一般会保存在本地的用户目录下的appdata下
- 细节问题
- 一个cookie只能保存一个信息
- 一个web站点可以给浏览器发送多个cookie,最多存放20个cookie
- Cookie大小有限制,4kb
- Cookie的浏览器存储上限为300个
- 删除Cookie:
- 不设置有效期,关闭浏览器,自动失效
- 设置有效期为0
- 数据传输过程中的中文乱码问题:
/*使用URLEncoder.encode(String s, String type)*/
URLEncoder.encode("钢铁侠","utf-8"); //会返回一个中文编码过的字符串
/*解码*/
URLDecoder.decode(cooie.getValue,"utf-8"); //会解码成一个中文编码的字符串
7.4、Session
什么是Session:
- 服务器会给每个一个用户(浏览器)创建一个Session对象
- 一个Session独占一个浏览器,只要浏览器没有关闭,这个Session就存在
- 用户登陆之后,整个网站它都可以访问 --> 保存用户、购物车信息
Session与Cookie的区别:
- Cookie是把用户的数据写给用户浏览器,浏览器保存(可以保存多个)
- Session是把用户的数据写到用户独占的Session,是在服务器保存(保存重要信息,减少服务器资源浪费)
- Session由服务器端创建
两种设置session过去的方式:
- 后端写
session.invalidate();
- web.xml
<!--设置Session默认的失效时间-->
<session-config>
<!--1分钟之后session自动生效-->
<session-timeout>1</session-timeout>
</session-config>
使用场景:
- 保存一个登陆用户的信息
- 购物车信息
- 在整个网站中经常会使用的数据,我们将它保存在Session中
案例:
public class SessionDemo01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
resp.setCharacterEncoding("utf-8");
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
//从req里面得到Session
HttpSession session = req.getSession();
//给Session中存东西
session.setAttribute("name",new Person("Neill",24));
//获取Session的ID
String id = session.getId();
//判断session是不是新创建的
if( session.isNew() ){
resp.getWriter().write("session创建成功,Id为:" + id);
}else {
resp.getWriter().write("已经在服务器存在session,Id为:" + id);
}
//session创建的时候做了什么事情?创建了一个cookie保存了session的id
Cookie cookie = new Cookie("JSESSIONID",id);
//获取session
Person getPerson = (Person)session.getAttribute("name");
//手动注销Session
session.invalidate();
}
}
7、JSP
7.1、什么是JSP
Java Server Pages:Java服务器端页面,也和Servlet一样用于开发动态Web技术
最大的特点:
- 写JSP就像在写html
- 区别:
- html只给用户提供动态资源
- JSP页面可以嵌入Java代码,给用户提供动态数据
7.2、JSP原理
思路:jsp到底如何执行的!
- 浏览器向服务器发送请求,不管访问什么资源,都是访问Servlet
- 而JSP继承了HttpJspBase这个类,而HttpJspBase这个类继承了HttpServlet
- JSP最终会转换成一个java类得出结论:
得出结论:JSP本质上就是servlet
7.3、JSP内置对象
输出页面前增加的代码
response.setContentType("text/html"); //设置响应的页面类型
pageContext = _jspxFactory.getPageContext(this,request,response,null,true,8192,true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
以上对象可以在jsp页面中直接使用
在JSP中,只要是Java代码就会被原封不动的输出,如果是HTML代码,就会被转换成
out.write("<html>\r\n");
输出到前端
7.4、JSP基础语法
任何语言都有自己的语法。JSP作为java技术的一种应用,它拥有一些自己扩充的语法,它拥有一些自己扩充的语法(了解即可),java所有语法都支持!
JSP表达式
<%--JSP表达式: 一般用来将程序输出到客户端 <%= new 变量或者表达式%>--%>
<%= new java.util.Date()%>
脚本片段
<%--JSP脚本片段:--%>
<%
int sum = 0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
out.println("<h1>" + sum + "<h1>");
%>
在代码中嵌入HTML元素
<%--在代码中嵌入HTML元素--%>
<%
for (int i = 0; i < 5; i++) {
%>
<h1>Hello Neil</h1>
<%
}
%>
JSP声明
<%!
static {
System.out.println("123123")
}
private int golabalvar = 0;
public void Neil(){
System.out.println("进入了这个方法")
}
%>
JSP声明:会被编译到JSP生成Java类中,其他的会生成到_jspServeice_方法之中
7.5、JSP指令
<%@ page errorPage="error/500.jsp" %> <!--在头部定义错误跳转-->
<%@ page isErrorPage="true" %> <!--显示定义这是一个错误页面-->
或者在web.xml定义
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
引用外部页面
<!--@include会将两个页面合二为一,可能会产生两个jsp页面的参数重名等问题-->
<%@include file ="common/header.jsp"%>
<!--推荐使用这个-->
<!--jsp标签,jsp:include:拼接页面,本质还是三个-->
<jsp:include page ="common/header.jsp">
7.6、九大内置对象
-
PageContext
存东西:保存的数据只在一个页面中有效,但是可以设置作用域,如下例 -
Request
:存东西:客户端向服务端产生的请求,产生的数据客户看完就没用的,比如新闻 Response
-
Session
存东西:产生的数据,用户用完了需要保存在服务器,比如购物车 -
Application
(实际是ServletContext
) 存东西:用户用完了其他用户还可能使用 -
Config
(实际是ServletConfig
) Out
Page
Exception
<%
pageContext.setAttribute("name1","Neil1"); //保存的数据只在一个页面中有效
request.setAttribute("name2","Neil222"); //保存的数据只在一次请求中有效,请求转发会携带这个对象
session.setAttribute("name3","Neil3333"); //在一次会话中有效,session为了用户服务,所以每一个用户有一个session从打开浏览器到关闭浏览器
application.setAttribute("name4","Neil44444"); //凌驾于所有之上,保存的数据在服务器中有效,从打开服务器到关闭服务器
%>
<%
/*脚本片段中的代码会被生成到jsp.java中*/
/*通过寻找方式取*/
String name1 = (String)pageContext.findAttribute("name1");
String name2 = (String)pageContext.findAttribute("name2");
String name3 = (String)pageContext.findAttribute("name3");
String name4 = (String)pageContext.findAttribute("name4");
String name5 = (String)pageContext.findAttribute("name5");
%>
<%--使用EL表达式输出 ${}}--%>
<h1>取出的值为:</h1>
<h3>${name1}</h3>
<h3>${name2}</h3>
<h3>${name3}</h3>
<h3>${name4}</h3>
<%
/*
* public void setAttribute(String name, Object attribute, int scope) {
switch(scope) {
case 1:
this.mPage.put(name, attribute);
break;
case 2:
this.mRequest.put(name, attribute);
break;
case 3:
this.mSession.put(name, attribute);
break;
case 4:
this.mApp.put(name, attribute);
break;
default:
throw new IllegalArgumentException("Bad scope " + scope);
}
public static final int PAGE_SCOPE = 1;
public static final int REQUEST_SCOPE = 2;
public static final int SESSION_SCOPE = 3;
public static final int APPLICATION_SCOPE = 4;
}*/
//第三个为作用域,可以设置作用域
pageContext.setAttribute("name","Neil",SESSION_SCOPE);
pageContext.forward("/index");
%>
7.7、JSP标签、JSTL标签、EL表达式
EL表达式
- 获取数据
${param.username}
- 执行运算
- 获取web开发的常用对象
- 调用java方法
JSP标签
<jsp:forward page="jsptag2.jsp">
<jsp:param name="name" value="Neil"/>
<jsp:param name="age" value="12"/>
</jsp:forward>
JSTL标签
jstl的标签库是为了弥补html标签的不足,它自定义了许多的标签,标签的功能可以供我们使用
菜鸟文档:
8、JavaBean
实体类
JavaBean有特定的写法:
- 必须有一个无参构造
- 属性必须私有化
- 必须有对应的get和set方法
一般用来和数据库的字段做映射:ORM
ORM:对象关系映射
- 表 --> 类
- 字段 --> 属性
- 行记录 --> 对象
<jsp:useBean id="people1" class="com.neil.entity.People" scope="page"/>
<jsp:setProperty name="people1" property="address" value="重庆"/>
<jsp:setProperty name="people1" property="id" value="1"/>
<jsp:setProperty name="people1" property="age" value="3"/>
<jsp:setProperty name="people1" property="name" value="Neillll"/>
地址:<jsp:getProperty name="people1" property="address" />
id:<jsp:getProperty name="people1" property="id" />
年龄:<jsp:getProperty name="people1" property="age"/>
名字:<jsp:getProperty name="people1" property="name"/>
9、MVC三层架构
什么是MVC:模型(Model)、视图(View)、控制器(Controller)
9.1、早些年的架构
用户直接访问控制层,控制层就可以直接操作数据库
servlet–CRUD–>数据库
弊端:
- 程序十分臃肿,不利于维护
- Servlet代码中:处理请求、响应、视图跳转,处理JDBC、处理业务代码、处理逻辑代码
9.2、如今的三层架构
Model
- 业务逻辑:业务逻辑(service)
- 数据持久层:CRUD (DAO)
View
- 展示数据
- 提供链接发器Servlet请求(a,form,img…)
Controller(Servlet)
- 接收用户的请求
- 交给业务层处理对应的代码
- 控制视图的跳转
登陆--> 接收用户的请求 --->处理用户请求以及获得登陆的参数---> 交给业务层处理登陆业务(判断用户名密码是否正确:事务) ----> Dao层查询用户名和密码是否正确 ---> 数据库
10、Filter
Filter:过滤器,用来过滤网站的数据
- 处理中文乱码
- 登陆验证
Filter开发步骤:
- 导包
- 编写过滤器
- 过滤器在项目初始化的时候被调用,web服务器关闭的时候过滤器被销毁
- 多个过滤器会根据chain的位置递归调用,按照xml的配置顺序
public class CharacterEncodingFilter implements Filter {
//初始化
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("已经初始化");
}
//Chain:过滤器中的所有代码在过滤特定请求的时候都会执行
//必须要让过滤器继续通行
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("CharacterEncoding执行前....");
filterChain.doFilter(servletRequest,servletResponse); //让我们的请求继续走,如果不写,程序到这里就被拦截停止了
System.out.println("CharacterEncoding执行后....");
}
//销毁
public void destroy() {
System.out.println("已经销毁");
}
}
Web.xml
<filter>
<filter-name>CharacterEncoding</filter-name>
<filter-class>com.neil.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncoding</filter-name>
<!--只要经过/servlet/*的任何请求都要经过过滤器-->
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
11.2、监听器的应用
用户登陆之后才能进入主页!用户注销之后就不能进入主页了
- 用户登陆后,向session中放入用户的数据
- 进入主页时候要判断是否用户已经登陆
public class SysFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
Object user_session = httpServletRequest.getSession().getAttribute(USER_SESSION);
if(user_session == null){
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/error.jsp");
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
web.xml
<filter>
<filter-name>SysFilter</filter-name>
<filter-class>com.neil.filter.SysFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SysFilter</filter-name>
<url-pattern>/sys/*</url-pattern>
</filter-mapping>
项目结构
11、监听器
实现一个监听器接口:(有N种)
//统计网站在线人数,统计session
public class OnlineCountListener implements HttpSessionListener {
//一旦创建一个session,就会出发一次这个事件
@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext servletContext = se.getSession().getServletContext();
Integer onlineCount =(Integer) servletContext.getAttribute("OnlineCount");
if(onlineCount==null){
onlineCount = 1;
}else {
int count = onlineCount + 1;
}
servletContext.setAttribute("onlineCount",onlineCount);
}
//一旦销毁就会触发这个事件
//两种session销毁方式:
//1. 手动销毁 session.invalidate();
//2. 自动销毁,在web.xml配置自动销毁时间
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext servletContext = se.getSession().getServletContext();
Integer onlineCount =(Integer) servletContext.getAttribute("OnlineCount");
if(onlineCount==null){
onlineCount = 0;
}else {
int count = onlineCount - 1;
}
servletContext.setAttribute("onlineCount",onlineCount);
}
}
Web.xml
<listener>
<listener-class>com.neil.listener.OnlineCountListener</listener-class>
</listener>
12、文件上传和下载
13.1、准备工作
对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器的。
一般采用apache的开源工具common-fileupload这个文件上传组件
common-fileupload依赖于common-io包
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
13.2、使用类介绍
【文件上传的注意事项】
- 为保成服务器安全,上传文件应该放在外界无法直接访问的目录下,比如WEB-INF目录下
- 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
- 时间戳
- UUID
- MD5
- 位运算
- 要限制上传文件的最大值
- 限制上传文件的类型,收到文件名是,判断后缀名是否合法
【需要用到的类】
ServletFileUpload
负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem
对象,在使用ServletFileUpload
对象解析请求时需要DiskFileItemFactory
对象。随意我们需要进行工作前需要构建好DiskFileItemFacotry
对象,通过ServletFileUpload
对象的构造方法或setFileItemFactory()
方法设置ServletFileUpload
对象的fileItemFactory
属性。
【FileItem类】
在html页面input必须有name<input type="file" name="filename">
**表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为multipart/form-data **
<form action="${pageContext.request.contextPath}/upload.do" enctype="multipart/form-data" method="post">
<input type="text" name="username">
<input type="file" name="file1">
<input type="file" name="file2">
<input type="submit" value="提交">
</form>
【常用方法介绍】
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<h2>Hello World!</h2>
<%--通过表单上传文件--%>
<%--GET: 上传文件大小有限制 POST:没有限制--%>
<form action="" method="post" enctype="multipart/form-data">
上传用户:<input type="text" name="userName">
<p><input type="file" name="file1"></p>
<button type="submit">提交</button> | <button type="reset" >重制</button>
</form>
</body>
</html>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--JSP依赖-->
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
14、邮件发送
14.1、传输协议
SMTP协议
发送邮件:
我们通常把处理用户smtp请求(邮件发送请求)的服务器称之为SMTP服务器(邮件发送服务器)
POP3协议
接收邮件:
我们通常把处理用户pop3请求(邮件接收请求)的服务器叫做pop3服务器(邮件接收服务器)
流程图:
14.2、Java实现简单邮件发送
需要准备:
-
JavaMail APi
和Java Activation Framework
public class EmailDemo01 {
//简单邮件:没有附件和图片
//复杂邮件:有附件和图片
//要发送邮件,需要获得协议的支持:开启pop3/SMTP服务器
//获得一个授权码
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.host","smtp.qq.com"); //设置qq邮件服务器
prop.setProperty("mail.transport.protocol","smtp"); //邮件发送协议
prop.setProperty("mail.smtp.auth","true"); //需要验证用户名密码
//关于QQ邮箱,还要设置SSL加密,加上一下代码即可,其他邮箱不需要
MailSSLSocketFactory mailSSLSocketFactory = new MailSSLSocketFactory();
mailSSLSocketFactory.setTrustAllHosts(true);
prop.put("mail.smtp.ssl.enable","true");
prop.put("mail.smtp.ssl.socketFactory",mailSSLSocketFactory);
//使用JavaMail的五个步骤
//1、创建定义整个应用所需要的环境信息的session对象
Session session = Session.getDefaultInstance(prop, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("xxx@163.com","授权码");
}
});
session.setDebug(Boolean.TRUE);
//2、通过session对象获得transport对象
Transport transport = session.getTransport();
//3、使用邮箱的用户名和授权码连上邮件服务器
transport.connect("smtp.qq.com","xxx@qq.com","授权码");
//4、创建邮件,MimeMessage是Message的实现类
Message message = new MimeMessage(session);
message.setSubject("Hello, this is a simple email from Java");//收件人
message.setFrom(new InternetAddress("xxx@qq.com")); //发件人
message.setRecipient(Message.RecipientType.TO, new InternetAddress("xxx@163.com"));//设置邮件的收件人
message.setContent("<h1 style='color:red'>This is the content!!</h1>","text/html;charset=utf-8");
//5、发送邮件
transport.sendMessage(message,message.getAllRecipients());
//6、关闭链接
transport.close();
}
}
14.3、Java实现带附件和图片的邮件发送
MIME(多用途互联网邮件扩展类型)
MimeBodyPart
class表示一个MIME信息,它和MimeMessage类一样都是从Part接口继承过来。
MimeMultiPart
class是抽象类Multipart的实现子类,用来组合多个MIME信息。一个MimeMultipart
对象可以包含多个代表MIME
消息的 MimeBodyPart
对象
public class EmailDemo01 {
//简单邮件:没有附件和图片
//复杂邮件:有附件和图片
//要发送邮件,需要获得协议的支持:开启pop3/SMTP服务器
//获得一个授权码
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.host","smtp.qq.com"); //设置qq邮件服务器
prop.setProperty("mail.transport.protocol","smtp"); //邮件发送协议
prop.setProperty("mail.smtp.auth","true"); //需要验证用户名密码
//关于QQ邮箱,还要设置SSL加密,加上一下代码即可,其他邮箱不需要
MailSSLSocketFactory mailSSLSocketFactory = new MailSSLSocketFactory();
mailSSLSocketFactory.setTrustAllHosts(true);
prop.put("mail.smtp.ssl.enable","true");
prop.put("mail.smtp.ssl.socketFactory",mailSSLSocketFactory);
//使用JavaMail的五个步骤
//1、创建定义整个应用所需要的环境信息的session对象
Session session = Session.getDefaultInstance(prop, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("some@163.com","授权码");
}
});
session.setDebug(Boolean.TRUE);
//2、通过session对象获得transport对象
Transport transport = session.getTransport();
//3、使用邮箱的用户名和授权码连上邮件服务器
transport.connect("smtp.qq.com","some@qq.com","授权码");
//4、创建邮件,MimeMessage是Message的实现类
Message message = new MimeMessage(session);
message.setSubject("Hello, this is a complex email from Java. Pay attention to the attachment");//收件人
//准备图片数据
message.setFrom(new InternetAddress("email@qq.com")); //发件人
message.setRecipient(Message.RecipientType.TO, new InternetAddress("xxx@qq.com"));//设置邮件的收件人
//===================================简单邮件设置内容=============================================
//message.setContent("<h1 style='color:red'>This is the content!!</h1>","text/html;charset=utf-8");
//===================================带内嵌资源的变更=============================================
MimeBodyPart MimeBodyPartImage = new MimeBodyPart();
//图片需要经过数据处理
DataHandler dataHandler = new DataHandler(new FileDataSource("source"));
MimeBodyPartImage.setDataHandler(dataHandler); //在我们的主体中放入我们处理的图片数据
MimeBodyPartImage.setContentID("Mashaladi"); //给图片设置一个ID,在后面可以使用
//===================================设置附件的变更=============================================
MimeBodyPart MimeBodyPartAttachment = new MimeBodyPart();
DataHandler dataHandler2 = new DataHandler(new FileDataSource("source"));
MimeBodyPartAttachment.setDataHandler(dataHandler2); //在我们的主体中放入我们处理的图片数据
MimeBodyPartAttachment.setFileName("MarkDown Notes"); //给附件设置一个名字,该名字会显示在接收方附件中国呢
//===================================设置附件的变更=============================================
//准备正文数据
MimeBodyPart MimeBodyPartText = new MimeBodyPart();
//cid:contentID,对应以上的setContentID
MimeBodyPartText.setContent("Hello World!! 这封附件是你喜欢的哟,这是给你的礼物~🎁 <img src='cid:Mashaladi'>","text/html;charset=utf-8");
//===================================数据拼装邮件正文内容=============================================
//提供描述关系
MimeMultipart mimeMultipart = new MimeMultipart();
mimeMultipart.addBodyPart(MimeBodyPartImage);
mimeMultipart.addBodyPart(MimeBodyPartText);
//设置描述关系,这里可以设置成related,内嵌,参照上图。如果需要附件资源,直接写mixed
mimeMultipart.setSubType("mixed");
MimeBodyPart contentText = new MimeBodyPart();
contentText.setContent(mimeMultipart);
//拼装附件
MimeMultipart completeFile = new MimeMultipart();
completeFile.addBodyPart(contentText);
completeFile.addBodyPart(MimeBodyPartAttachment);
//设置到消息中,保存修改
message.setContent(completeFile);
message.saveChanges();
//===================================带内嵌资源的变更=============================================
//5、发送邮件
transport.sendMessage(message,message.getAllRecipients());
//6、关闭链接
transport.close();
}
}