框架1:Java Web - 后端

Java网页应用开发。它是【基于请求和响应】来开发的。
JavaSE中的结构是C/S Client-Server。
而JavaEE中Web是B/S Browser-Server。(浏览器不同,会有兼容问题,例如很多标签IE不兼容)
● 请求:客户端(浏览器)给服务器发送数据,叫请求Request;
● 响应:服务器给客户端(浏览器)回传数据,叫响应Response

1、JavaEE三层架构
JavaEE三层架构

项目中的包结构(分成多个包为了解耦,即降低代码的耦合度,方便项目后期的升级和维护):
● web/servlet/controller 接受请求和响应的web包
● service Service接口包
 ● service.impl Service接口实现类包
● dao Dao接口包
 ● dao.impl Dao接口实现类包
● pojo/entity/domain/bean JavaBean类包
● test 测试包
● utils 工具包

2、web资源的分类

web资源按照实现的技术和呈现的效果,可以分为静态资源和动态资源。
● 静态资源:html、css、js、txt文本、jpg图片、mp4视频...
● 动态资源:【Java中的】:jsp页面、Servlet程序

3、⭕ 创建Java Web项目:

(1) 新建一个项目(Web)。可以是静态Web等...

eclipse示例

(2) 创建并书写相应的文件
(3) 浏览器运行

4、web中的【/】意义

在web中,【/】是一种绝对路径。
● 浏览器解析时,得到的地址是:http://ip:port/
● 服务器解析是,得到的地址是:http://ip:port/工程名/

 但注意,服务器中使用response.sendRedirect("/");时,会将该内容发送给浏览器重定向,服务器不解析,而是浏览器解析,得到http://ip:port/

● XML
● JSON
● Tomcat
● Servlet
● Filter
● Listener
● JSP
● EL
● JSTL
● Cookie
● Session
● AJAX

一【HTML和CSS】、二【JavaScript】、三【JQuery】

参考:框架1:Java Web - 前端 - 简书 (jianshu.com)

四、XML

XML全称为Extensible Markup Language,可扩展的标记性语言。

● 主要作用:
(1) 保存数据,而且这些数据具有自我描述性;

e.g 
Java中有Student对象:
[ Student[id=1, name="张三"], Student[id=2, name="李四"] ]  

students.xml
<students>
  <student>
    <id>1</id>
    <name>张三</name>
  </student>
  <student>
    <id>2</id>
    <name>李四</name>
  </student>
</students>

注意:XML文件必须要有且仅有一个根元素,即没有父标签的元素。
(2) 作为配置文件
(3) 也可以作为网络传输数据的格式。(现在主要使用json文件)

1.1 文档声明

<?xml version="1.0" encoding="utf-8" ?>
以上内容就是xml的声明,其中:
● version 表示xml的版本
● encoding 表示xml文件的编码方式

1.2 元素(标签)

从(且包括)开始标签直到(且包括)结束标签的部分。
元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。
和html的元素(标签)定义一样,与之不同在于xml元素可以自命名。

xml命名规则

xml命名规则

xml元素属性
属性可以提供元素的额外信息。
和html一样,每个属性的值必须使用引号引起来

xml注释

< !-- 注释内容 -- > 【去掉空格箭号左右空格】

文本区域(CDATA区)

当要显示特殊字符时(例如【<】),除了可以像html一样,用特殊字符【& lt;】(去掉多余空格)。
还可以使用文本区域语法,告诉浏览器,无需解析xml,直接显示成文本。

<![CDATA[
  显示的文本内容 
]]> 

1.3 xml解析技术

不管是html文件,还是xml文件,他们都是标记型文档,都可以使用w3c组织制定的dom技术来解析。

dom技术会将整个文档视作一个document对象

(1) 历史背景
早期JDK为我们提供了两种xml解析技术DOM和SAX【已经过时,但我们需要了解一下】
dom解析技术最初是由W3C组织制定的,而所有的编程语言都结合自身特点对该项技术进行实现,java也不例外。
● sun公司在JDK1.5时对dom解析技术进行了升级:SAX(Simple API for XML)
SAX解析,和W3C制定的解析不太一样。它是以类似事件机制通过回调告诉用户当前正在解析的内容。也就是说,它不是一次性创建大量dom对象,而是用到时一行一行的解析。因此在内存和性能使用上,都优于DOM解析。
● 第三方解析:
 jdom 在dom基础上进行了封装;
dom4j 在jdom的基础上进行了封装;
 pull 主要用在安卓手机开发,与sax非常类似,都是事件机制解析xml文件。

(2) dom4j解析技术 (\color{red}{重点})
很多框架基于dom4j来解析xml文件。可以去dom4j官网下载jar包。

● dom4j 解析步骤

I. 项目中创建一个lib目录,并添加dom4j的jar包,然后添加到项目类库中。


添加到项目类库

II. 创建SAXReader输入流,读取xml配置文件,生成Document对象。

public class test{
  public static void main(String args[]) throws Exception{
    SAXReader saxReader = new SAXReader();
    Document document = saxReader.read("xml文件路径");
  }
}

III. 通过document对象得到根元素通过根元素得到对象元素(标签)。

Element rootElement = document.getRootElement();

IV. 遍历将对象元素(标签)进行处理,创建成我们想要的Java对象。

IV(1) 传入标签名,返回标签元素的集合
List<Element> elements= rootElement.elements("标签名"); 
for(Element e: elements){  
  IV(2) 传入标签名,返回想要的标签元素
  Element subElement = e.element("标签名");
  IV(3) 通过Element对象的getText()方法,得到标签内容 
  String content = subElement.getText(); 
  IV(4) 也可以直接通过Element对象的elementText("标签名")方法,得到标签内容
  String content2 = subElement.elementText("标签名");
  
  IV(5) 通过Element对象的attributeValue()方法,得到属性值
  String attributeValue = subElement.attributeValue("属性名");
  ...根据得到的数据,创建Java对象...
}

五、Tomcat

由Apache组织提供的一款Web服务器,提供对jsp和Servlet的支持。它是一种轻量级的javaWeb容器(服务器),也是当前应用最广的JavaWeb服务器(免费)。

● 补充:tomcat的端口号默认8080;而http协议的默认端口号是80,省略不显示【看不到端口号的address就是80端口】。

1、安装和启动

直接找到需要用的Tomcat版本对应的zip(windows)/tar.gz(Linux)压缩包,解压到需要安装的目录即可。

● 文件目录
| - Tomcat
  | - bin 可执行文件
  | - conf 配置文件
   可以通过【server.xml】文件,修改默认的8080端口号,然后重启tomcat服务器,才生效。
  | - lib jar包
  | - logs 运行时输出的日志信息
  | - temp 运行时产生的临时数据
  | - webapps 部署的Web工程。一个目录对应一个工程!
  | - work 工作目录,用来存放运行时jsp翻译为Servlet的源码,以及Session钝化(对象序列化)的目录

Session的"钝化"和"活化"
当用户在一段时间内没有与Web应用程序进行交互时,服务器可能会将该session视为“不活动”,并将其保存在内存或磁盘中,这就是所谓的"Session钝化"。这样可以释放内存资源,避免空闲的session占用太多服务器资源。但是,一旦用户再次与应用程序进行交互,服务器就会重新“激活”这个session,重新加载之前保存的用户数据,并让用户可以继续他们之前的操作,这就是所谓的"Session活化"

● 启动和关闭tomcat服务器

【终端输入】
1、启动tomcat服务器
>sh ./startup.sh 【也可以去指定sh文件双击】
2、关闭tomcat服务器
>sh ./shutdown.sh 【也可以去指定sh文件双击】

(windows下是双击bat批处理文件,并且要保持小黑框一直打开)

测试是否成功启动tomcat:
在浏览器中输入任意一项地址:
(1) http://localhost:8080
(2) http://127.0.0.1:8080
(3) http://真实ip地址:8080

MacOS安装、启动和关闭 参考链接:Mac-Tomcat安装教程小白教学mac 安装tomcat爱吃Java的猴子的博客-CSDN博客

2、部署web工程到tomcat

部署后可以实现网络访问。

● 部署前工作【程序员的工作】:告诉tomcat,要部署哪个工程
 ● 方式一:将web工程拷贝到tomcat的webapps目录下。
 ● 方式二:配置文件映射。(这种方式不要求项目必须存在于webapps下)
   在【tomcat/conf/Catalina/localhost】目录下新建xml文件。【一般一个工程一个文件】
   ● Context表示一个工程上下文
     ● 属性path表示工程在浏览器中的访问路径,属性docBase表示你的工程目录实际存放的位置。

mytest.xml
<? xml version="1.0" encoding="utf-8" ?>
<Context path="/browserDir" docBase="/home/usrname/myProject"/>

 ● 访问webapps的工程:http://ip:port/工程名/目录/html文件
  ● http://localhost:8080 实质是搜索tomcat的webapps目录
  ● http://localhost:8080(没有工程名)实质是访问到tomcat的webapps目录下的Root工程
  ● http://localhost:8080/工程名(没有资源名)实质是访问到对应工程的index.html

Tomcat中的web.xml中的欢迎页面设置

● 部署时工作【IDE工作】:IDE拷贝了一份Tomcat【副本】,在【副本】中部署了【编译后的工程路径】(含有原工程的所有资源文件+class文件),映射方式是上面提到的方式二。
 理解:相当于有一份tomcat备份,一份工程备份(不含java源文件,仅含编译后的class文件)。拷贝一份tomcat,是为了不影响源tomcat。程序员做了一次映射,IDE又做了一次映射,项目运行时,实际跑的是IDE的映射环境。\color{blue}{底层理解}

将页面文件拖入浏览器 vs 访问部署到tomcat的页面文件 区别
(1) 将文件拖入浏览器:走的是【file】协议,直接访问本地文件管理器,拿到文件后浏览器进行解析,不走网络。
(2) 访问部署到tomcat的页面文件:走的协议是【http】,通过网络向服务器发送请求,tomcat响应回传页面文件,客户端拿到后再使用浏览器进行解析显示。

3、tomcat部署到IDE

(1) IntelliJ IDEA
IntelliJ IDEA > Settings... > Build, Execution, Deployment > Application Servers
(2) Eclipse
Window > Preferences > Server > Runtime Environments

4、动态的web工程

与上面的创建web工程、部署tomcat到IDE两步走不同,
创建动态web工程,会一次性将服务器等准备好,并且项目目录也会不一样。

(1) 目录介绍
动态WEB工程目录的介绍

有时候,WEB-INF里面是没有lib目录的,我们习惯上将其创建出来,存放第三方的jar包。

(2) 动态web工程 vs 静态web工程

I. 静态 WEB
静态 WEB指的以*.htm、*.html 为后缀的网页,这些网页的访问只是从服务器上读取这些内容,然后返回给客户端浏览器解析呈现在用户面前。静态WEB【缺点】在于:所有用户看到的效果一样,无法实现与用户动态交互:不能登录连接数据库。
此外,静态资源(如html文件)可以直接手动拖入浏览器,进行显示。

II. 动态WEB
动态 WEB是指利用某些技术实现连接数据库,能够与用户交互,使 WEB的展示效果“因时因人而变”。它的好处是能够连接数据库,实现与用户的交互。强调一点,不是网站中有动态的效果就是动态WEB,动态WEB是指的是客户端与用户能够进行交互。
此外,动态资源(如jsp文件)无法拖入浏览器直接显示,它是需要服务器动态拼接内容
主要是将请求先转交给WEB Container(WEB服务器的容器),在WEB Container中连接数据库,从数据库中取出数据等一系列操作后动态拼凑页面的展示内容,拼凑页面的展示内容后,把所有的展示内容交还给WEB服务器,之后通过WEB服务器将内容发送回客户端浏览器进行解析执行

(3) 热部署

一般服务器(比如tomcat,jboss等)启动以后,我们还需要进一步修改java代码,或者是jsp代码。一般来说,改完重启以后才会生效。但如果配置了服务器的热部署,就可以改完代码后立即生效,而不是重启服务器再生效。这样就会节省大量时间!

● 如何热部署
一般有两个选项:

I. On Update action:当代码改变的时候,需要IDEA为你做什么
-Update resources:如果发现有更新,而且更新的是资源文件(*.jsp,*.xml等,不包括java文件),就会立刻生效
-Update classes and resources【推荐】:如果发现有更新,这个是同时包含java文件和资源文件的,就会立刻生效
⭕ 注意:在运行模式下,修改java文件时不会立刻生效的;而debug模式下,修改java文件时可以立刻生效的。当然,两种运行模式下,修改resources资源文件都是可以立刻生效的。
-Redploy:重新部署,只是把原来的war删掉(项目重新部署),不重启服务器
-Restart:重启服务器

II. On Frame deactivation:当失去焦点(比如最小化了IDEA窗口),需要IDEA为你做什么
-Do nothing【推荐】: 不做任何事 (一般推荐这个,因为失去焦点的几率太大)
-Update resources: 失去焦点后,修改的resources文件都会立刻生效
-Update classes and resources:失去焦点后,修改的java ,resources文件都会立刻生效(与On update action中的Update classes and resources一样,也是运行模式修改的java文件不会生效,debug模式修改的java文件会立刻生效)

参考链接:IDEA 服务器热部署详解(On Update action/On frame deactivation)_王溺码的博客-CSDN博客

5、使用tomcat统一的错误页面配置

在web.xml配置<error-page>标签,出错后,会自动跳转对应页面。

web.xml
<web-app ...>
  ...
  <error-page>
    <error-code>错误响应码(500、404...)</error-code>
    <location>出错后,对应跳转的错误页面路径</location>
  </error-page>
</web-app>

⭕ 一定要将异常一直抛到最外层,抛给Tomcat容器,如果在内部捕获后不抛出,Tomcat无法感知。


六、Servlet

Servlet是JavaEE规范之一,即接口

它是运行在服务器上的一个Java小程序(Server applet)功能:用来接收客户端发送的请求,并响应数据给客户端
Servlet是JavaWeb三大组件【Servlet程序、Filter过滤器、Listener监听器】之一。

Tomcat服务器和Servlet版本的对应关系

1、编写类实现Servlet接口+接入动态WEB 流程

(0) 前期工作:动态web项目的 web/WEB-INF 目录下 (web/WEB-INF/lib) 导入额外的jar包 ——【serverlet-api.jar】(这个包在tomcat的lib目录下)
(1) 编写Java类,实现Servlet接口

public class myServlet implements Servlet{
  @Override
  public void init(ServletConfig servletConfig) throws ServletException{
    super.init(config); //必须保留,不然config没有传递进去。【GenericServlet类才持有ServletConfig对象】
  }
  @Override
  public ServletConfig getServletConfig(){
  }
  @Override
  public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{
    该方法非常重要!它是专门用来处理请求和响应的!
  }
  @Override
  public String getServletInfo(){
    return null;
  }
  @Override
  public void destroy(){
  }
}

(2) 编写web.xml文件,将编写好的Servlet实现类映射到浏览器访问路径

<?xml version="1.0" encoding=""utf-8?>
<web-app ....> <!-- 默认的配置,不要去动 -->
  <!-- 配置servlet项 -->
  <servlet>
    <servlet-name>自定义servlet别名(一般和实现类名保持一致)</servlet-name>
    <servlet-class>servlet实现类的全类名(含包名)</servlet-class>
  </servlet>
  <!-- 配置servlet和浏览器访问路径的映射项 -->
  <servlet-mapping>
    <servlet-name>需要映射的servlet名字(和上面保持一致)</servlet-name>
    <url-pattern>资源访问路径(例如/hello)</url-pattern>
  </servlet-mapping>
</web-app>

成对配置,一个是配置servlet本身,另一个是配置servlet和浏览器的访问路径的映射。
● <url-pattern>中的资源访问路径,必须要以【/】打头,它表示项目的根目录。
● 此后,当浏览器访问 http://ip:port/工程路径/资源访问路径 时,会默认调用实现类中的service方法。

2、Servlet的生命周期

2.1 执行servlet的构造器   (单例,仅执行一次)
2.2 执行init初始化方法   (仅执行一次)
2.3 执行service方法   (每次刷新对应的访问路径,都会被调用)
2.4 执行destroy销毁方法   (web工程停止时调用,仅执行一次)

3、GET方法和POST方法的分发

(1) 自定义Servlet类,实现Servlet接口。
一般客户端向服务器发送get请求和post请求,所要实现的功能不一样。
而servlet中只有service方法,因此需要进行分发处理。

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{
  3.1 向下转型为HttpServletRequest,使用其中的getMethod方法
  HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
  3.2 通过method进行分发
  String method = httpServletRequest.getMethod();
  if("GET".equals(method)){
    ...
  }else if("POST".equals(method)){
    ...
  }
}

(2) 自定义Servlet类,继承HttpServlet类。【常用】
根据业务需要,重写HttpServlet的doGet()和doPost()方法。

public class myServlet2 extends HttpServlet{
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    ...
  }
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    ...
  }
}

4、Servlet的继承体系

Servlet继承体系

5、ServletConfig

Servlet程序的配置信息接口一个Servlet内含有对应的一个ServletConfig实现类对象。
\color{red}{注意:Servlet程序和ServletConfig都是由Tomcat负责创建,我们仅负责使用。}

● 作用:
(1) 获取servlet在web.xml中配置的name
(2) 获取servlet在web.xml中配置的init-param(键值对),通过键取值。
(3) 获取servlet的上下文对象ServletContext

public class HelloServlet implements Servlet{
  @Override
  public void init(ServletConfig servletConfig)throws ServletException{
    super.init(config); //必须保留,不然config没有传递进去。【GenericServlet类才持有ServletConfig对象】
    System.out.println("HelloServlet程序的别名:" + servletConfig.getServletName());
    System.out.println("HelloServlet程序的初始化参数username值是:" + servletConfig.getInitParameter("username"));
    System.out.println("HelloServlet程序的初始化参数url值是:" + servletConfig.getInitParameter("url"));
    System.out.println("HelloServlet程序的上下文对象是:" + servletConfig.getServletContext());
  }
}

web.xml
<? xml version="1.0" encoding="utf-8" ?>
<web-app ...>

  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>HelloServlet全类名</servlet-class>
      <!-- 初始化参数,里面含有一个键值对 -->
    <init-param>
      <param-name>username</param-name>
      <param-value>root</param-value>
    </init-param>
    <init-param>
      <param-name>url</param-name>
      <param-value>jdbc:mysql://localhost:3306/test</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet>

</web-app>

6、ServletContext

接口,它表示Servlet上下文对象。
\color{red}{注意:【一个web工程,只有一个ServletContext实例】(不管有多少个Servlet)。在web工程部署时创建,在web工程停止时销毁。}

● ServletContext是一个域对象
域对象,是可以像Map一样存取数据的对象。这里的域,是指存取数据的操作范围,即整个web工程

Map vs 域对象

● 如何获得ServletContext对象
(1) 可以通过Servlet内的ServletConfig对象得到。
(2) 调用servlet的getServletContext()得到。【本质:GenericServlet中封装的方法,内部还是用方法(1)!!!】。

在Servlet实现类中:
方法一:
ServletConfig servletConfig = getServletConfig();
ServletContext context = servletConfig.getServletContext();
方法二:【本质还是方法一】
ServletContext context = getServletContext();

● 作用:
(1) 获取在web.xml中配置的context-param(键值对),通过键取值。

web.xml
<? xml version="1.0" encoding="utf-8" ?>
<web-app ...>
  <context-param>
    <param-name>...</param-name>
    <param-value>...</param-value>
  </context-param>
  <context-param>
    <param-name>...</param-name>
    <param-value>...</param-value>
  </context-param>

  <servlet>
    ...
  </servlet>
  <servlet-mapping>
    ...
  </servlet-mapping>
</web-app>

Java
String value = context.getInitParameter("属性名");

(2) 获取当前的工程路径格式:【/工程名】
(3) 获取工程部署后在服务器硬盘上的绝对路径。(提供一个相对路径,【/】代表当前工程目录)

System.out.println("工程部署的路径是:" + context.getContextPath()); 
System.out.println("工程下css目录的绝对路径是:" + context.getRealPath("/")); 

(4) 可以像Map一样存取数据。

7、Http协议

● 协议:双方或多方,相互约定好,大家都需要遵守的规则。
● HTTP协议:客户端(浏览器)和服务器之间通信时,发送的数据【又称报文】,需要遵守的规则。

7.1 请求

客户端给服务器发送的数据叫【请求】;

(0) Http协议格式:
● 请求行【3部分组成】
   (a) 请求方式:GET/POST
   (b) 请求url路径:url路径[?参数名1=参数值1&参数名2=参数值2&...]
   \color{blue}{注意:对应配置的servlet-mapping路径}
   (c) 协议及版本号:http/1.1
● 请求头
   格式【key:value】
● 空行
● 请求体

Http协议格式

(1) GET请求
HTTP 协议没有为 GET 请求的 body 赋予语义,也就是即不要求也不禁止 GET 请求带 body。一些实现会禁止,一些允许。一般来说,GET请求没有请求体,即把应该放在请求体的内容放到了请求行的后置参数中。

举例

什么时候会用到GET请求:【常用】
a) form标签中有属性mothod=get
b) a标签、img标签引入图片、iframe引入html页面
c) link标签引入css文件、script标签引入js文件
d) 在浏览器地址栏中输入地址敲回车
\color{blue}{注意}

(2) POST请求
一般来说,POST请求体中存放了发送给服务器的数据内容。格式为【参数名1=参数值1&参数名2=参数值2&...】

举例

什么时候会用到POST请求:
form标签中有属性mothod=post

7.2 响应

服务器给客户端回传的数据叫【响应】。

(0) Http协议格式:(和请求格式类似,区别在于响应行和响应头的一些字段)
● 响应行【3部分组成】
   (a) 协议及版本号:http/1.1
   (b) 响应状态码:200
   (c) 响应状态码描述:OK
● 响应头
   格式【key:value】
● 空行
● 响应体

举例

(1) 常用的响应码
● 200:请求成功。
● 302:请求重定向。
● 404:服务器已经收到了请求,但是数据不存在。
● 500:服务器已经收到了请求,但是服务器内部错误(代码)。

7.3 MIME类型

MIME(Multipurpose Internet Mail Extensions)多功能Internet邮件扩充服务。
它是HTTP协议中的数据类型。MIME类型的格式是“大类型/小类型”,并与某一种文件的扩展名相对应。

常见的MIME类型

8、HttpServletRequest

只要每次有请求进入Tomcat服务器,它会将请求的HTTP协议信息解析,封装到Request对象中。
我们就可以在servlet相关类中的service方法(或是doGet和doPost方法)中使用Request对象。

8.1 常用的方法
1、getRequestURI();
获取资源路径(e.g /myProject/helloServlet)
2、getRequestURL();
获取统一资源定位符(e.g http://localhost:8080/myProject/helloServlet)
3、getRemoteHost();
获取客户端的ip地址
4、getHeader("请求的键名");
获取请求头中对应键的值
5、getMethod();
获取请求方式GET或POST
6-1、String getParameter("参数名") 如果复选框、下拉框等选中了多个值,只会返回一个值【重要!!!】
6-2、String[] getParameterValues("参数名") 用于获取复选框、下拉框等选中多个值的情况【重要!!!】
获取用户提交表单中的信息,参数名对应html中标签的name属性。
7、setCharacterEncoding("字符集(e.g UTF-8)")【解决post请求的中文乱码问题!!!】
设置请求体中的字符集【注意:要在获取请求参数方法(即上面的6)调用前使用,才能生效!!】
8.2 请求转发 \color{red}{区分请求重定向}

有时候多个servlet资源协作完成一个业务,就需要用到请求转发。
从一个servlet的service(或者doGet/doPost)方法跳到另一个servlet的service(或者doGet/doPost)方法。

● 步骤
(1) 获取RequestDispatcher对象;
RequestDispatcher requestDispatcher = request对象.getRequestDispatcher("以【/】打头的资源路径");
(2) 可以使用Request域对象进行数据传递;
request对象.setAttribute("键", "值");
request对象.getAttribute("键");
(3) 调用RequestDispatcher的forward方法进行转发。
requestDispatcher.forward(request对象, response对象);

e.g
class Servlet1 extends HttpServlet{
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp){
    System.out.println("在Servlet1中查看参数:" + username);
    req.setAttribute("key", "servlet1的数据");
    RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servlet2");
    requestDispatcher.forward(req, resp);
  }
}
class Servlet2 extends HttpServlet{
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp){
    System.out.println("在Servlet2中查看参数:" + username);
    String value = req.getAttribute("key");
    System.out.println("处理servlet2的业务逻辑");
  }
}

● 特点:
(1) 浏览器的地址不会随着转发而跳转
引发的问题:由于转发时浏览器地址不会跟着跳转,所以容易导致各种相对路径混乱
      解决方案:① 写绝对路径; ② 使用<base href="路径(如果最后是目录,不能省略【/】)"/>标签【base标签往往放在head标签内,title标签下】。

<head>
  ...
  <title></title>
  <base href="..." />
</head>

\color{blue}{相对路径在解析时,如果没有base标签,则默认参照浏览器的地址栏;如果有base标签,优先参照base标签地址}
\color{red}{此外,我们往往不写死base中的href,动态获取,以防止服务器的动态变化。(这里用到了jsp代码脚本和表达式脚本)}

jsp文件
<%@ page contentType="text/html; charset=utf-8" language="java" %>
<head>
  ...
  <title></title>
  <%
    String basePath = request.getScheme() + "://" 
                      + request.getServerName() + ":" + request.getServerPort 
                      + request.getContextPath() + "/";
  %>
  <base href="<%= basePath %>" />
</head>

(2) 用户在地址栏回车后,尽管可能进行了多次转发,但是仍然是一次请求和一次响应。这就意味着这几个协作的servlet资源共享Request对象
(3) 可以转发到WEB-INF目录下,该目录无法使用浏览器直接访问。
(4) 只允许访问工程下的资源。

9、HttpServletResponse

类似HttpServletRequest,只要每次有请求进入Tomcat服务器,它会创建一个Response对象传递给Servlet程序。
我们就可以在servlet相关类中的service方法(或是doGet和doPost方法)中使用Response对象,来设置返回给客户端的信息。

9.1 两种输出流

● 类似request,response的默认字符集为“ISO-8859-1”,我们可以通过setCharacterEncoding("字符集");对服务器进行字符集的设置,且要在获取流对象设置!!!。但是\color{green}{要注意,当浏览器的字符集和服务器不一致时,浏览器显示也会出现乱码。因此要告诉浏览器响应的内容类型。}

方式一:
response.setHeader("Content-Type", "text/html; charset=UTF-8");
方式二:
response.setContentType("text/html; charset=UTF-8");

(1) 字节输出流:PrintStream printStream = response.getOutputStream();
常用于二进制传输。
(2) 字符输出流:PrintWriter printWriter = response.getWriter();【常用】
常用于字符传输,效率高。

使用里面的write()或者print()【其本质也是write】写入新的页面(如html字符串),回传发送给浏览器。

\color{blue}{注意:这两种流使用时,只能选其一!}原因:在某种输出流使用完成后,会自动(当然也可以手动关闭)帮我们关闭输出流。由于是一次响应,因此不能再使用输出流来进行操作!

9.2 请求重定向 \color{red}{区分请求转发}

重新让浏览器跳转到另一个地址。

● 重定向方式一
response.setStatus(302);
response.setHeader("Location", "新的浏览器访问地址");
● 重定向方式二 【推荐】
response.sendRedirect("新的浏览器访问地址");

● 特点:
(1) 浏览器的地址会发生跳转
(2) 用户在地址栏回车后,由于重定向,发生了两次请求和两次响应。这就意味着彼此不共享Request对象
(3) 不可以转发到WEB-INF目录下。本质还是地址栏访问WEB-INF目录。
(4) 允许访问非工程下的资源。

10、ServletContextListener

Listener监听器:JavaEE的规范,即接口。JavaWeb的三大组件【Servlet程序、Filter过滤器、Listener监听器】之一。共有八大监听器,其中ServletContextListener比较有用,用的比较多
● 监听器的作用:监听某种事物的变化,然后通过回调函数,反馈后方便做一些业务处理。

ServletContextListener 它可以监听 ServletContext对象的创建和销毁。
ServletContext对象在web工程启动的时候创建,在web工程停止的时候销毁。

public interface ServletContextListener extends EventListener{
  1. 监听ServletContext【创建后】,调用
  public void contextInitialized(ServletContextEvent sce);
  2. 监听ServletContext【销毁后!!!】,调用
  public void contextDestroyed(ServletContextEvent sce);
}

● 使用步骤
(1) 编写ServletContextListener的实现类;
(2) 实现这两个回调方法;
(3) 到web.xml中配置监听器。

web.xml
<? xml version="1.0" encoding="utf-8" ?>
<web-app ...>
  <context-param>
    ...
  </context-param>
  <servlet>
    ...
    <init-param>
      ...
    </init-param>
  </servlet>
  <servlet-mapping>
    ...
  </servlet-mapping>
  <listener>
    <listener-class>监听器实现类的全类名</listener-class>
  </listener>
</web-app>

⭕ JavaWeb三大组件的执行顺序:ServletContextListener → Filter → Servlet


七、JSP

jsp(java server pages)是Java服务器页面。
它的主要作用是代替Servlet程序回传html页面的数据。
【传统做法:通过response对象获取输出流,利用print/write方法回传html字符串。】

1、jsp文件的存放和访问

jsp页面和html页面一样,都是存放在web目录下。访问也是跟html页面一样。

2、jsp文件的本质:servlet程序

当我们第一次访问jsp文件时,Tomcat服务器会帮我们把jsp文件翻译成一个对应的java源文件【它间接继承了HttpServlet】,及对应的class字节码文件。 (\color{blue}{注意})

e.g 假定我们有一个a.jsp文件
(1) 将其访问目录放入浏览器地址栏
(2) 在文件管理器打开:tomcat新拷贝的环境(实际部署环境)下的work/Catalina/localhost/工程名
(3) 打开该目录的资源,发现 a_jsp.java 和 a_jsp.class文件。
打开a_jsp.java源文件
间接继承了HttpServlet
该翻译后的java源文件回传页面的部分代码

3、jsp语法

3.1 page指令

page指令可以修改jsp页面中的一些重要属性,或行为。

<%@ page contentType="text/html; charset=utf-8" language="java" %>

以上page指令可以写多个
● language属性:表示jsp翻译后是什么语言文件。暂时只支持java。
● contentType属性:表示jsp返回的数据类型是什么。对应了源码中的response.setContentType()。
● pageEncoding属性:表示当前jsp页面文件本身的字符集,默认“utf-8”。【不建议改】
● import属性:对应java源代码中的导包。
● autoFlush属性:设置当输出流out缓冲区满了之后,是否自动刷新缓冲区,默认是true。【不建议改】
● buffer属性:设置输出流out缓冲区大小,默认是8kb。【不建议改】
● errorPage属性:设置当jsp页面运行出错时,自动跳转的错误页面路径。
● isErrorPage属性:设置当前jsp页面是否是错误信息页面,默认是false。如果是true可以获取异常信息。
● session:设置访问当前jsp页面,是否会创建HttpSession对象,默认是true。【不建议改】
● extends:设置jsp翻译出来的java类默认继承哪个类。

3.2 jsp脚本

(1) 声明脚本【极少使用】

● 格式:
<%!
  声明类的属性、方法、静态方法、内部类等等...
%>
可以写多个。

e.g
<%-- 声明类的属性 --%>
<%!
  private Integer id;
  private String name;
%>
<%-- 声明类的方法 --%>
<%!
  public int abc(){
    return 12;
  }
%>
<%-- 声明类的静态代码块 --%>
<%!
  static{
    id = 3;
    name = "3";
  }
%>
<%-- 声明类的内部类 --%>
<%!
  class myInnerClass{
    private int id = 3;
    private double price = 1.1;
  }
%>

(2) 表达式脚本【较多使用】

● 格式:
<%=
  变量,字符串,对象...
%>

● 特点:
I. 所有的表达式脚本都会被翻译到java源文件的_jspService()方法中。也就是说,_jspService()中的对象都可以直接使用。
II. 表达式脚本会被翻译成out.print("表达式脚本的内容"),即会直接渲染到页面上。另外,表达式脚本不能以分号结束。

加了分号的错误示例

(3) 代码脚本【较多使用】
在jsp页面中,编写我们自己需要的功能。
● 如果写的是【java语句】,翻译后直接写入_jspService()方法里。即当访问该jsp文件,会执行这些java语句。
● 如果写的是【html语句】,解析后渲染到页面上
也就是说在代码脚本中,可以实现java+html的混用。

● 格式:
<%
  ...
%>
举例
3.3 jsp注释

jsp支持三种注释。
(1) html注释(翻译后在_jspService()方法中用out.write("html注释")输出)
(2) java注释(翻译后在_jspService()方法中,以普通的注释形式存在)
(3) jsp注释(不会被翻译)

4、jsp九大内置对象

jsp中的内置对象,是指tomcat在翻译jsp页面称为servlet源代码后,内部提供的九大对象,称为内置对象。

(1) HttpServletRequest request
(2) HttpServletResponse response
(3) ServletContext application
(4) ServletConfig servletConfig
(5) HttpSession session:会话对象
(6) PageContext pageContext:page上下文对象
(7) Throwable exception 对象:异常对象(用page指令,将isErrorPage="true"时出现)
(8) JspWriter out:输出流对象
(9) Object page:指当前jsp页面对象。

5、jsp四大域对象 (\color{red}{重要})

域对象是可以像Map一样存取数据的对象。四个域对象的功能一样,不同的是它们对数据的存取范围。

(1) PageContext对象:pageContext,【当前jsp页面】有效
(2) HttpServletRequest对象:request,【一次请求】内有效
(3) HttpSession对象:session,【一次会话】内有效(打开浏览器访问服务器,直到关闭浏览器)
(4) ServletContext对象:application,整个【web工程内】有效(随着工程的销毁而销毁)

使用优先顺序

6、jsp中的out输出流对象 vs response.getWriter()输出流对象

6.1 显示到浏览器的先后顺序

问题:如果在jsp代码脚本中,同时用以上两种输出流输出,先后顺序如何?
结果是:先显示response的输出流对象的内容,再显示out输出流对象的内容。
⭕ 原理
jsp的输出流out对象,持有一个自身的【缓冲区】;
response对象的输出流,也持有一个自身的【缓冲区】。
当out对象缓冲区进行刷新flush()时,才会把自身缓冲区的内容,全部追加到response对象的输出流缓冲区中!
当回传响应给客户端时,response对象将其输出流缓冲区的内容全部回传给客户端解析显示。

6.2 如何使用

由于jsp翻译之后,底层源代码都是使用out来进行输出。
为避免页面输出内容的顺序出现错乱,一般情况下,我们统一在jsp页面中使用out来输出。

● out.write():会将内容强制转换成char类型输出。(因此如果放入int类型,会打印出对应的ASCII码值)
● out.print():先进行字符串转换,即String.valueOf("内容"),然后再进行输出。⭐【推荐使用】

7、jsp常用标签

7.1 包含

当有成千上万个页面中的部分内容相同时,我们想要将相同的内容抽取出来,只维护一份。此时就需要用到包含。

(1) 静态包含【使用的比较多,因为现在jsp文件主要是用来输出页面数据,不会做太多复杂功能】

● 格式:
<% include file="/被包含的页面资源路径"  %>
静态包含-例子

● 特点
I. 不会对被包含的页面进行翻译,即不生成servlet程序;
II. 会把被包含的页面的html内容,拷贝到当前需要的页面。

(2) 动态包含

● 格式:
<jsp:include page="/被包含的页面资源路径">
  <jsp:param name="键1" value="值1"/>
  <jsp:param name="键2" value="值2"/>
  ...
</jsp:include>
动态包含-例子

● 特点
I. 对被包含的页面进行翻译,即生成servlet程序;
II. 可以【通过request域对象】传递参数给被包含的页面。

7.2 标签-转发

功能对应于request.getRequestDispatcher("资源路径").forward(request, response);

● 格式:
<jsp:forward page="资源路径"></jsp:forward>

八、EL表达式(jsp扩展)

EL(Expression Language)表达式语言。用于简化jsp的表达式脚本,进行数据输出。

1、语法

${ 表达式 }
可以直接使用域对象的key来获取对应的值

e.g 
pageContext.setAttribute("key", "value");
${ key }$

注意:EL表达式在输出null值的时候,输出的是空串(不显示内容)。而jsp表达式脚本输出null值的时候,输出的是"null"字符串。

1.1 表达式搜索域对象的顺序

当jsp中四个域对象均有相同的key时,EL表达式会按照四个域从小到大的顺序获取【pageContext→request→session→application】

1.2 使用 "."运算"[]"运算

${ Bean对象 }:会调用该类的toString()方法。
${ Bean对象.XXX }:一般用于获取对象的属性或者map中键对应的值。如果是获取对象的属性,底层会调用该类的公有读【getXXX() / isXXX()】方法。
\color{blue}{注意:如果想要获得属性,而该属性没有对应的公有读方法【getXXX/isXXX】,则不能用EL表达式的点运算获得对应的值。}
\color{red}{注意:对于一个类的boolean类型的属性成员,在生成读方法时,默认为isXXX()。而一般我们常看到的是非boolean类型的getXXX()!!!}
${ 对象[下标 / "键"] }:用于获取有序集合中某个下标的值,或者获取map中的键对应的值

2、运算

(1) 关系运算:==、!=、<、>、<=、>=
(2) 逻辑运算:&&、||、!
(3) 算术运算:+、-、*、/、%、三元运算符( exp1 ? exp2 : exp3 )
(4) 点"."运算和中括号"[]"运算
(5) empty运算:判断是否为空,返回true或者false
判断为空的几种情况:
 ● null
 ● ""(空串)
 ● 数组大小为0
 ● 集合大小为0

3、EL表达式的11个隐含对象

EL表达式中的11个隐含对象,是EL表达式中自己定义的,可以直接使用。
(1) 变量 pageContext:类型为pageContextImpl
  它存储了jsp中的九大内置对象。

常常使用该对象获取如下信息:
1、获取协议
<%= request.getScheme() %>
${ pageContext.request.scheme }
2、获取服务器ip
<%= request.getServerName() %>
${ pageContext.request.serverName }
3、获取服务器端口
<%= request.getServerPort() %>
${ pageContext.request.serverPort }
4、获取工程路径
<%= request.getContextPath() %>
${ pageContext.request.contextPath }
5、获取请求方法
<%= request.getMethod() %>
${ pageContext.request.method }
6、获取客户端IP地址
<%= request.getRemoteHost() %>
${ pageContext.request.remoteHost }
7、获取会话的ID编号
<%= session.getId() %>
${ pageContext.session.id }

(2) 变量 pageScope:类型为HashMap<String, Object>
  它存储了pageContext域数据。
(3) 变量 requestScope:类型为HashMap<String, Object>
  它存储了request域数据。
(4) 变量 sessionScope:类型为HashMap<String, Object>
  它存储了session域数据。
(5) 变量 applicationScope:类型为HashMap<String, Object>
  它存储了application域数据。
(6) 变量 param:类型为HashMap<String, String>
  它存储了请求参数,但一个键只对应一个值。
(7) 变量 paramValues:类型为HashMap<String, String[]>
  当其中一个请求参数存在一个键对应多个值时,使用。
(8) 变量 initParam:类型为HashMap<String, String>
  它存储了在web.xml中配置的<context-param>上下文参数。
(9) 变量 header:类型为HashMap<String, String>
  它存储了该请求的头部信息,但一个键只对应一个值。
(10) 变量 headerValues:类型为HashMap<String, String[]>
  当其中一个请求头信息存在一个键对应多个值时,使用。
(11) 变量 cookie:类型为HashMap<String, Cookie>
  它存储了当前请求的Cookie信息。

Cookie类


九、JSTL标签库(jsp扩展)

JSTL(JSP Standard Tag Library),全称JSP标准标签库。用于替换jsp中的代码脚本

JSTL五大标签库

1、使用标签库步骤

1.1 导入jstl标签库的jar包;
1.2 使用taglib指令引入标签库;
(1) CORE标签库
<%@ tablib prefix="c" uri="http://java.sum.com/jsp/jstl/core" %>
(2) FMT标签库
<%@ tablib prefix="fmt" uri="http://java.sum.com/jsp/jstl/fmt" %>
(3) FUNCTIONS标签库
<%@ tablib prefix="fn" uri="http://java.sum.com/jsp/jstl/functions" %>
(4) XML标签库
<%@ tablib prefix="x" uri="http://java.sum.com/jsp/jstl/xml" %>
(5) SQL标签库
<%@ tablib prefix="sql" uri="http://java.sum.com/jsp/jstl/sql" %>
1.3 使用jstl标签书写

2、CORE核心库的常用标签

(1) set标签:给域对象设置键值对
<c:set scope="page" var="键" value="值" />
● scope属性:指明是哪个域对象。可选值:page、request、session、application
● var属性:键
● value属性:值

(2) if标签:如果test值为true,则会输出标签里的内容
<c:if test="${ 12 == 12 }">
  <h1>12==12</h1>
</c:if>
● test属性:表达式【用EL书写】,为真时输出标签里的内容。

(3) choose+when+otherwise标签:类似于switch...case...default (区别在于:switch组需要手动break)
<c:choose>
  <c:when test="表达式1">输出内容1</c:when>
  <c:when test="表达式2">输出内容2</c:when>
  <c:when test="表达式3">输出内容3</c:when>
  <c:otherwise>输出内容4</c:otherwise>
</c:choose>
注意:otherwise标签也可以不写。此外,执行的顺序从上到下,要满足其中一条就跳出。

(4) forEach标签
● begin属性:开始值(索引)
● end属性:结束值(索引)【包括自身】
● items属性:需要被遍历的Object数组对象
● var属性:遍历的变量
● step属性:递增(递减)的增量
● varStatus:var的状态,它的实现类有很多方法可以调用。(详情见下图)
<c:forEach begin="1" end="10" var="i">
  ${ i }
</c:forEach>
<%
  request.setAttribute("arr", new String[]{"1234", "5678", "91011"});
  HashMap<String, String> map = new HashMap<>();
  map.put("key1", "value1");
  map.put("key2", "value2");
  map.put("key3", "value3");
  request.setAttribute("map ", map);
%>
<c:forEach items=${ requestScope.arr } var="item">
  ${ item } <br />
</c:forEach>
<c:forEach items=${ requestScope.map } var="entry">
  键:${ entry.key } ,值:${ entry.value } <br />
</c:forEach>
【forEach标签的varStatus属性】varStatus的实现类Status实现了LoopTagStatus接口

十、文件上传和下载 \color{red}{重点}

1、文件上传

文件上传的HTTP协议请求内容
步骤

● 导包
(0) 导入【commons-fileup.jar包】(它依赖于【commons-io.jar】),以便解析多段表单项内容。
● 前端
(1) 使用form标签method属性设置为"post"encType属性设置为"multipart/form-data"
\color{blue}{注意:如果我们要自己解析,需要使用req.getInputStream()的二进制流来接收!建议使用上面的第三方工具jar包帮助解析。}
(2) 在form标签内,使用input标签,input标签的type属性设置为"file"
● 后端
(3) 在服务器端使用【ServletFileUpload】类来解析上传的数据,表单项被封装成【FileItem】。编写代码实现文件上传功能。

1、boolean static isMultipartContent(HttpServletRequest request)
判断当前上传的数据格式是否是多段表单项的格式
2、public List<FileItem> servletFileUpload对象.parseRequest(HttpServletRequest request)
解析上传的数据
3、boolean fileItem对象.isFormField()
判断是否为普通的表单项。如果是,则返回true;否则,是文件,返回false
4、String fileItem对象.getFieldName()
获得当前fileItem的name属性值
5、String  fileItem对象.getString("编码方式(e.g UTF-8)")
获取当前fileItem的内容
6、String  fileItem对象.getName()
获取上传的文件名
7、void fileItem对象.write(File file)
将上传的文件写入磁盘

2、文件下载

● 步骤

(1) 服务器端准备好对应的文件,用ServletContext对象获取资源并转化成二进制流

ServletContext context = request.getServletContext();
InputStream is = context.getResourceAsStream("文件路径");

(2) 在response对象中,设置好响应头。相关属性有:【"Content-Type"、"Content-Disposition"】
● 其中content-Type可以通过context对象的getMimeType方法动态获取。
content-disposition是为了告诉用户不要显示在浏览器,这是一个附件,应该下载;并给出下载时的文件名。

String mimeType = context.getMimeType("文件路径");
response.setContentType(mimeType);
response.setHeader("Content-Disposition", "attachment; filename=文件名");

⭕ 如果文件名是中文需要进行编码。不同的浏览器支持的编码格式不同。
【可以通过request请求头的"User-Agent"来查看浏览器的信息】
I. IE或者谷歌支持URL编码。可以使用URLEncoder.encode("编码内容", "字符集")【java.net包】;
II. 早期的火狐不支持URL编码,支持BASE64编码【需要在编码后的字符串用=??=包裹,并告诉浏览器使用的是BASE64编码(B),即=?字符集?B?编码串?=】。可以使用base64Encoder对象.encode(byte[]);
返回给浏览器时,浏览器会对应的解析。如果我们自己想解析看看,可以使用对应的decode("编码后内容")来查看)

if(request.getHeader("User-Agent").contains("Firefox")){
  BASE64Encoder base64Encoder = new BASE64Encoder();
  String filename = "=?" + base64Encoder.encode("中国.jpg".getBytes("utf-8")) + "?=";
}else{
  String filename = URLEncoder.encode("中国.jpg", "utf-8");
}

(3) 从response中获取输出流,并将资源流写入输出流。这里可自己书写,也可使用common-io.jar包的IOUtils类API。

OuputStream os = response.getOutputStream();
IOUtil.copy(is, os);

十一、Cookie

Cookie是Servlet发送到Web浏览器的少量信息,浏览器将其保存下来,后续可发送回服务器。
\color{blue}{由服务器通知客户端保存键值对的一种技术}

(1) Cookie的值可以唯一的标识客户端,因此Cookie常用于【会话管理】。
(2) 当浏览器有Cookie值时,每次请求都会发送给服务器。
(3) 每个Cookie的大小不能超过4KB。

1、Cookie的使用

1.1 Cookie的创建

服务器端的web层的servlet程序负责创建。

● 构造器仅有一个,必须传入对应的键和值
Cookie cookie = new Cookie(String name, String value);
1.2 Cookie的添加

servlet程序通过reponse对象调用addCookie方法。就可以将Cookie发送给浏览器,让其保存。
\color{blue}{本质:设置到响应头的Set-Cookie}

response.addCookie(cookie);
1.3 Cookie的获取

当浏览器发送请求时,如果携带了Cookie信息,服务器的servlet程序就可以通过request对象调用getCookies方法获取Cookie数组
\color{blue}{本质:获取到请求头的Cookie}

Cookie[] cookies = request.getCookies();

⭕ 如果想要获取其中某一个特定的Cookie,一般来说都是遍历寻找。我们往往【编写一个CookieUtils工具类】,通过传入对应的Cookie键名,来获取该Cookie,如果没有就返回null

CookieUtils.java
public class CookieUtils{
  public static Cookie findCookieByName(String name, Cookie[] cookies){
    if(name == null || "".equals(name) || cookies == null || cookies.length == 0) return null;
    for(Cookie cookie : cookies){
      if(name.equals(cookie.getName())){
        return cookie;
      }
     }
     return null;
  }
}
1.4 Cookie的修改

(1) servlet中通过创建新的Cookie,并调用response对象的addCookie(cookie)方法来覆盖之前旧的Cookie。
(2) servlet先获取cookie数组,再找到对应的Cookie,并调用sertValue(String newValue)方法来修改旧值
\color{red}{注意:Cookie值不支持中文,及一些在不同浏览器表现的行为不一致的字符(空格、方括号、双引号....)}
\color{red}{如果想要使用,则需要用到Base64编码。格式:【=?字符集?b?经过BASE64Encoder编码的字符串?=】}

2、Cookie的属性

2.1 maxAge属性

该属性表示cookie对象的存活时间单位:s

(1) 当值为正数时,表示经过多少秒后该cookie失效
(2) 当值为负数时,表示关闭浏览器时,cookie失效。【默认】
(3) 当值为时,表示该cookie立即失效

● 可以通过get方法读取,set方法写入。

2.2 path属性

该属性可以用于浏览器请求地址时,对cookie的过滤。即有些cookie可以被显示并使用,有一些被隐藏不能使用。
当浏览器的访问地址中【没有包含cookie的path】,那么该cookie就被过滤

Cookie的path属性举例

● 可以通过get方法读取,set方法写入。


十二、Session

接口【HttpSession】。Session就是会话,它用来维护一个客户端和服务器之间的关联。
\color{red}{注意:Cookie是保存在客户端的,而Session是保存在服务器的。}

(1) 每个客户端都会有自己的一个Session会话。
(2) 我们经常用Session会话保存用户登陆之后的信息。(而Cookie只是客户端请求携带过去的信息)

1、创建和获取Session

创建一个Session,和获取当前Session,API是一样的。
当还没有Session时,会创建Session会话;如果有会话了,会直接返回前面创建好的session
● 可以使用【boolean session.isNew()】来判断是否是新创建的会话。
每一个Session都有一个唯一的标识,即ID,可以通过【session.getId()】获取。

HttpSession session = request.getSession();
boolean isNew = session.isNew();
String id = session.getId();

2、Session域数据的存取

session.setAttribute(String key, Object value);
Object value = session.getAttribute(String key);

3、Session的属性

3.1 maxInactiveInterval属性

该属性表示session对象的【指闲置的时间,两次请求访问的间隔最大时长】存活时间,单位:s。【默认:1800s,即30min】
\color{red}{区分Cookie的存活时间!Session的过期时间,每一次请求后会自动重置为设置的时长!}

(1) 当值为正数时,表示两次请求间隔多少秒后该session失效
(2) 当值为负数时,表示session永不超时。(极少使用)
(3) 不能通过设置"0",让会话马上失效。但可以通过调用session的以下API,来使其立马失效。

session.invalidate();

\color{blue}{30min的Session会话超时是tomcat指定好的,尽量不要改。}
\color{blue}{如果我们想要在整个项目中自定义,那么我们可以自己的web.xml中模拟tomcat的web.xml对Session进行配置。}

<web-app ...>
  <servlet>
    ...
  </servlet>
  <servlet-mapping>
    ...
  </servlet-mapping>
  <!-- 当前项目的session会话超时时间,单位:分钟。 -->
  <session-config>
    <session-timeout>20</session-timeout>
  </session-config>
</web-app>

● 可以通过get方法读取,set方法写入。
如果只想修改个别session,而不是整个项目的,可以使用session.setMaxInactiveInterval("秒数");

4、浏览器和Session的关联

之前我们谈到,【关闭浏览器会使得一次会话结束】
⭕ 当关闭浏览器时,如果Session还没有失效,为什么下一次打开浏览器,就拿不到之前的Session了呢?

Cookie和Session的协同

\color{red}{即,Session是基于Cookie中保存的JSESSIONID来找到对应的会话。如果Cookie中没有携带,则创建新的Session。}
\color{blue}{关闭浏览器 → Cookie失效(默认) → 没有携带Cookie(JSESSIONID)请求 → 找不到之前的会话【会话结束】 → 创建新的会话。}


十三、Filter

Filter过滤器是JavaEE的规范,即【接口】。它是JavaWeb的三大组件(Servlet、Listener、Filter)之一。

1、为什么需要Filter?

⭕ 现在有一个需求
现在我们访问工程目录下的资源(如:html文件、jsp文件、jpg文件、mp4文件...)都可以直接访问。
而我们只希望,登录过的用户访问工程目录下的admin目录的资源文件。如何实现?——【Filter】进行控制。

● Filter的作用是:拦截请求【常用】、过滤响应。
拦截请求的常见应用场景:
  (1) 权限检查
  (2) 日记操作
  (3) 事务管理
  (4) ...等等

\color{red}{用户请求获取文件资源 → 实现了Filter接口的类程序 → 特定资源(html、jsp等)→ 实现了Filter接口的类程序 → 返回给用户}
\color{blue}{注意:由于浏览器可能有缓存,所以存在一些资源(图片等)不会走服务器,因此不会走上面的流程。解决方案:清缓存/随机参数。}

2、Filter的使用步骤

2.1 编写实现了Filter接口的myFilter类,并重写doFilter方法,处理自己的过滤逻辑。
public class AdminFilter implements Filter{
  @Override
  public boolean init(FilterConfig filterConfig) throws SevletException{
    ...
  }
  @Override
  public boolean doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws SevletException{
    HttpServletRequest req = (HttpServletRequest) servletRequest;
    HttpSession session = req.getSession();
    ...
    if(验证通过){
      fileterChain.doFilter(servletRequest, servletResponse);
    }else{
      servletRequest.getRequestDispatcher("转发地址")forward(servletRequest, servletResponse);
    }
  }
  @Override
  public void destroy(){
    ...
  }
}

● 重要方法:【filterChain.doFilter(servletRequest对象, servletResponse对象);】
 I. 如果有下一个Filter,则调用下一个Filter的doFilter方法继续进行验证;
 II. 如果没有Filter,验证通过后,则进入资源文件

2.2 在web.xml中配置filter类和拦截路径。 (\color{blue}{和servlet配置类似})
web.xml

<web-app ...>
  <servlet>
    ...
  </servlet>
  <servlet-mapping>
    ...
  </servlet-mapping>
  <filter>
    <filter-name>自定义Filter名字</filter-name>
    <filter-class>实现了Filter接口的实现类的全类名</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>自定义Filter名字</filter-name>
    <url-pattern>拦截的路径</url-pattern>
  </filter-mapping>
</web-app>

(1) 拦截路径的三种写法:
a) 精确拦截:格式:【/资源】
b) 目录拦截:格式:【/目录/*】
c) 后缀名拦截:格式:【*.后缀】 \color{red}{注意:后缀名拦截时,不能加前面的【/】}

e.g
a) <url-pattern>/test.jsp</url-pattern>
b) <url-pattern>/admin/*</url-pattern>
c) <url-pattern>*.html</url-pattern>

(2) 当要给同一个filter,配置多个拦截路径时,可以使用多个<url-pattern>标签

<filter>
    <filter-name>myFilter</filter-name>
    <filter-class>com.example.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/path1/*</url-pattern>
    <url-pattern>/path2/*</url-pattern>
    <url-pattern>/path3/*</url-pattern>
</filter-mapping>

3、Filter的生命周期

3.1 构造器方法【web工程启动后】
3.2 init方法:进行初始化操作。【web工程启动后】
3.3 doFilter方法:进行拦截操作。【每次有需要被拦截的请求,就会执行】
3.4 destroy方法:进行Filter的销毁。【web工程停止】

4、FilterConfig类

FilterConfig是Filter的配置文件类
Tomcat每次创建Filter的时候,会对应给它创建一个FilterConfig类,记录Filter的配置信息。

● 作用:
(1) 获取对应Filter的名字(在web.xml配置的)

String filterName = filterConfig.getFilterName();

(2) 获取在web.xml配置的Filter的初始参数。

web.xml

<web-app ...>
  ...
  <filter>
    ...
    <init-param>
      <param-name>...</param-name>
      <param-value>...</param-value>
    </init-param>
  <filter>
  <filter-mapping>
    ...
  </filter-mapping>
</web-app>

String value = filterConfig.getInitParameter("参数名");

(3) 获取servletContext对象

ServletContext context = filterConfig.getServletContext();

5、FilterChain类

过滤器链,即处理多个过滤器运转的类。

5.1 多个Filter使用的流程

\color{red}{如果同一资源有多个Filter去拦截,会按照web.xml中filter-mapping从上到下的配置依次调用它们的doFilter。}

多个Filter的执行流程图
5.2 多个Filter过滤器的特点

(1) 所有共同执行的filter和目标资源【默认】都执行在同一个线程中
(2) 所有共同执行的filter和目标资源都使用同一个Request对象


十四、JSON

JSON(JavaScript Object Notation)是一种轻量级(跟XML相比)的数据交换格式
JSON采用完全独立于语言的文本格式,很多语言都提供了对json的支持,这就使得JSON成为理想的数据交换(指客户端和服务器的数据传递)语言。

1、在JavaScript中的格式【客户端】

1.1 定义
jsonObj = {
  key1:value1,
  key2:value2,
  ...
}
1.2 方法
(1) 获取
var value = jsonObj.key1;
(2) 设置
jsonObj.key1 = new_value1;
(3) 将当前json对象转化为json字符串
var jsonObjStr = JSON.stringify(json对象);
(4) 将当前json字符串转化为json对象
var jsonObj = JSON.parse(json字符串);

● 一般json对象是用来访问和存取数据的,而json字符串是用来做数据交换(数据传输)的。

2、在Java中的格式【服务器端】

导入json的jar包,并部署到项目中。

java使用json需要的导入的库

下面的示例以【gson-2.2.4.jar】为例。

2.1 JavaBean和json的相互转换
import com.google.gson.Gson;
Person person = new Person(1, "Mary");
Gson gson = new Gson();
String personJsonString = gson.toJson(person); // {"id":"1", "name":"Mary"}
Person newPerson = gson.fromJson(personJsonString, Person.class); // Person{id=1, name='Mary'}
2.2 List / Map 和json的相互转换

List

import com.google.gson.Gson;

// public class PersonListType extends TypeToken<ArrayList<Person>>{ // TypeToken是jar包中的类
// }

List<Person> personList = new ArrayList<>();
personList.add(new Person(1, "Mary"));
personList.add(new Person(2, "John"));
personList.add(new Person(3, "Tom"));
Gson gson = new Gson();
String personJsonString = gson.toJson(personList ); 
// List<Person> newPersonList = gson.fromJson(personJsonString, new PersonListType().getType()); 
List<Person> newPersonList = gson.fromJson(personJsonString, new TypeToken<ArrayList<Person>>().getType());  // 使用匿名内部类

Map

import com.google.gson.Gson;

// public class PersonMapType extends TypeToken<HashMap<Integer, Person>>{ // TypeToken是jar包中的类
// }

Map<Integer, Person> personMap = new HashMap<>();
personMap.put(1, new Person(1, "Mary"));
personMap.put(2, new Person(2, "John"));
personMap.put(3, new Person(3, "Tom"));
Gson gson = new Gson();
String personJsonString = gson.toJson(personMap); 
Map<Integer, Person> newPersonMap = gson.fromJson(personJsonString, new TypeToken<HashMap<Integer, Person>>().getType());  // 使用匿名内部类

● 对象转化为json字符串,普通JavaBean、List、Map都可以直接转化;
● 但是如果是json字符串转化成对象:
 ● 普通JavaBean对象 = gson对象.toJson("Json字符串", JavaBean.class);
 ● List<T>或者Map<T>对象 = gson对象.toJson("Json字符串", 继承了TypeToken类(书写了泛型类型)的对象.getType());


十五、AJAX

AJAX(ASynchronized JavaScript And XML),异步JavaScript和XML(现在主要是JavaScript + Json联动做数据传输)。是一种创建交互式网页应用的网页开发技术。\color{red}{注意:AJAX是一种【浏览器通过js异步发起请求】,【局部更新页面】的技术。}

1、AJAX请求的特点

(1) 异步请求

异步和同步是相对而言的。
● 【同步】是指下一个操作要等待上一个操作执行完毕后才能开始执行。
● 【异步】是指无需等待上一个操作执行完毕,继续往下执行。上一个操作执行完毕后,执行相应的回调函数

(2) 局部更新页面

a) 原来地址栏的内容没有改变
b) 页面不做刷新操作,仅仅改变新的内容,没有更改的内容不发生变化
 没有使用ajax技术时,会将整个页面全部从服务器再次加载。

2、原生的AJAX请求步骤

2.1 js客户端中创建XMLHttpRequest对象
var xmlHttpRequest = new XMLHttpRequest();
2.2 xmlHttpRequest对象调用open方法,设置请求参数
xmlHttpRequest.open(请求的方法("GET"或"POST"), 资源路径(可以携带请求参数), 是否异步(true或false));

⭕ 注意:AJAX可以默认发送"GET"请求,一般我们发送"GET"、"POST"请求。而对于其他HTTP请求,如"PUT"、"DELETE"请求,部分浏览器不支持。

2.3 绑定xmlHttpRequest对象的onreadystatechang事件,获取服务器发送回来的数据,并处理请求完成后的操作
xmlHttpRequest对象需要绑定的onreadystatechange事件
xmlHttpRequest.onreadystatechange = function(){
  // ajax请求已经成功响应,状态200。且服务器已经将请求完成。
  if(xmlHttpRequest.state == 200 && xmlHttpRequest.readyState == 4){
      var jsonObj = JSON.parse(xmlHttpRequest.responseText); // 获取文本格式数据
      document.getElementById("div01").innerHTML = "编号:" + jsonObj.id + ", 名字:" + jsonObj.name; //设置到标签元素中
  }
}
接收服务器回传的数据
2.4 xmlHttpRequest对象调用send方法,发送请求
xmlHttpRequest.send();

\color{red}{注意:绑定事件应该在send方法调用之前。}

3、JQuery封装的AJAX请求

● 常用方法

3.1 ajax
$.ajax(
  url:"资源路径",【不携带请求参数】
  type:"GET"或"POST",
  data:"请求参数", 【格式有两种,无需前置的"?"】
        (1) "key1=value1&key2=value2..."
        (2) {key1:value1, key2:value2...} (会自动帮我们解析成(1))
  success:自定义回调函数,【可以处理成功或者失败的情况】
          function(data){
            必须写上参数名,装载了服务器回传的数据!如果dataType为"json",会帮我们自动转化成json对象,无需我们再手动处理。
          }
  dataType:预期服务器回传的数据
           常用的有:"text"、"json"、"xml"
);

3.2 get / post
$.get/post(
  url:"资源路径",
  data:"请求参数", 
  callback:自定义回调函数,【仅处理成功情况!!!】
  type:预期服务器回传的数据
);

3.3 getJSON
$.getJSON( 【请求方法:"GET"】
  url:"资源路径",
  data:"请求参数", 
  callback:自定义回调函数,【仅处理成功情况!!!】
);

3.4 serialize 【重要】
可以把表单中的所有表单项的内容都获取到,并且以name1=value1&name2=value2的形式帮我们拼接好了。
var formContent = $("表单选择器").serialize();

为什么需要用到serialize?
● 传统做法是,通过表单中的submit提交按钮(依靠<form>标签action属性跳转),浏览器会自动将表单中的数据打包为 HTTP 请求并将其发送到服务器。服务器可以使用 request.getParameter() 方法来获取表单数据。
● 但是要想实现不刷新页面情况下异步更新页面,或者处理更复杂的表单逻辑时,服务器并不能感知客户端发送了AJAX请求,所以需要我们手动使用serialize方法,将表单数据发送给服务器。服务器再使用request.getParameter()方法来获取表单项得值。


贯穿始终的:(软件架构思想)MVC \color{red}{重要!重要!重要!}

MVC全称:model模型、View视图、Controller控制器
● Model:将与业务逻辑相关的数据封装为具体的JavaBean类,其作用是处理数据——JavaBean / domain / entity / pojo。
 ● 实体类Bean:专门存储业务数据,如Student、User等...
 ● 业务处理Bean:指Service对象或Dao对象,专门用于处理业务逻辑和数据访问。
● View:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。
● Controller:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。

\color{blue}{MVC是一种思想,将代码拆分成多个组件,单独开发,组合使用,目的为了降低耦合度,便于维护。}

⭕ 实际开发,优化思路:

(1) 一个模块,一个公共的servlet。
● 与之协作的html / jsp文件,使用input标签【type="hidden" name="action" value="..."】,以便公共servlet通过action来分辨哪一个功能。

(2) 若干个模块,有若干的公共servlet,抽取公共BaseServlet。
● 各个模块的公共servlet仅书写若干个功能方法
● BaseServlet实现的功能是通过获取隐藏表单项的值,以此知道具体要调用哪个功能。我们约定,这个值和方法名相同,然后通过【反射】来调用对应的方法

(3) 数据封装,并抽取BeanUtils。
功能方法中,往往会做相同的动作:从表单中获取参数值,并封装成JavaBean对象。
借助第三方工具包【commons-beanutils-1.8.0.jar,它的依赖包:commons-logging-1.1.1.jar】的BeanUtils类的populate(JavaBean, Map)方法,来帮助我们将获取的表单值,注入到对象中,返回给我们使用。我们可以封装成一个WebUtils工具类
\color{red}{方法本质是利用【反射】,找到对应的类,调用对应类的写方法set,将map中的值写入bean对象的属性(属性和map中键对应)}
\color{blue}{(调用该方法后,map中可能存在一些键,不能和bean中的属性对应,那么就会忽略他,只注入能对应上的键的值)}

class WebUtils{
  public static <T> T copyParamToBean(Map map, T bean){
    try{
      BeanUtils.populate(bean, map);
    }catch(Exception e){
      e.printStackTrace();
    }
    return bean;
  }
}

e.g 用户模块中有功能:"登录"和"注册"。以往的做法是写两个Servlet,分别重写doPost()方法。

● 前端
<input type="hidden" name="action" value="login" />
<input type="hidden" name="action" value="register" />
● 后端
abstract class BaseServlet extends HttpServlet{
  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp){
    // 获取隐藏表单项的值,
    String action = req.getParameter("action");
    // 利用反射调用对应方法
    Method method = this.class.getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
    method.invoke(this, req, resp);
  }
}
class UserServlet extends BaseServlet{
    // 登录
    protected void login(HttpServletRequest req, HttpServletResponse resp){
      User user = WebUtils.copyParamToBean(req.getParameterMap(), new User());
      ...
    }
    // 注册
    protected void register(HttpServletRequest req, HttpServletResponse resp){
      User user = WebUtils.copyParamToBean(req.getParameterMap(), new User());
      ...
    }
}

⭐ 其他注意细节

1、web层的同一个servlet功能中,一般区分前台【普通用户】和后台【管理员】。我们往往在web.xml中的<servlet-mapping>标签中的<url-pattern>通过【/client/XXXServlet 和 /manager/XXXServlet】进行不同逻辑处理。

2、避免表单的重复提交!!!

【表单重复提交】 常见三种情况:
● 情况一:提交完表单后。服务器使用请求转发进行页面跳转。这时,如果用户按下功能键F5,就会发起最后一次请求。造成表单重复提交的问题。**
 >解决方案:使用【重定向】来进行跳转。\color{blue}{用户提交完请求(可能若干次请求),浏览器会记录下最后一次请求的全部信息。当F5触发后,就会发起浏览器记录的最后一次请求。}
● 情况二:用户正常提交,但是由于网络延迟等原因,迟迟未收到服务器的响应。这个时候用户以为提交失败,多点了几次提交操作,也会造成表单重复提交。
 >解决方案:使用【验证码】。
● 情况三:用户正常提交服务器,服务器也没有延迟。但是提交完成后,用户回退提交页面,重新提交。这也会造成表单重复提交。
 >解决方案:使用【验证码】。

验证码如何防止表单的重复提交?
a) 客户端第一次访问表单时(拉取表单页面内容),服务器就要给表单生成一个随机的验证码字符串,并把它保存到Session域中,同时要把验证码生成为图片一起发送给浏览器,显示在浏览器页面中。
b) 客户端填写表单和验证码,提交。
c) 服务器先获取Session域中的验证码,并在Session域中删除\color{blue}{【可以理解为一次性凭证】}。然后与用户提交的验证码进行比对:如果相同,则正常执行;如果不同,则拒绝执行
d) 当用户出现上面情况二、三的表单重复提交时,服务器依旧获取Session域中的验证码,此时为null,比对失败,拒绝执行

3、在进行filter拦截时,既要对页面资源拦截,也要对配置的servlet地址拦截。(结合"细节1")

4、i18n国际化【了解】

国际化(Internationalization)指的是同一个网站可以支持多种不同的语言,以方便不同国家的用户进行访问。
由于拼写过长,所以简写成"i18n"。

a) 方法一:为不同的国际创建不同的网站。
 比如苹果公司,英文网站是http://www.apple.com,而中国官网是http://www.apple.com/cn
b) 方法二:Locale + Properties配置文件 + ResourceBundle

i18n国际化

验证码的使用(使用谷歌API,它能够自动创建验证码图片,并把验证码内容保存到session)

(1) 导入谷歌验证码jar包,并加到工程的库中。【kaptcha-2.3.2.jar】
(2) 在web.xml中配置jar包中对应的servlet,并将<servlet-mapping>标签下的<url-pattern>路径后缀改为jpg!!!(因为我们只需要该servlet下的图片) (\color{blue}{注意})

示例

(3) 在表单页面中,加入验证码所需标签:<img src="上面的jpg路径" />
(4) 在自己的servlet中获取session域中的验证码值,然后立即删除,和用户输入的验证码进行比对,如果成功,才处理对应的业务逻辑。

session域中验证码的获取和删除

(5) 可以新增刷新验证码【是指重新向jar中的servlet发起请求,而不是刷新页面】的功能。
● 注意浏览器的缓存问题【每次发起请求,判断你的请求是否和之前一样(缓存里面找),一样的话,不走服务器,直接拿之前的数据】。这就导致了不会重新向jar中的servlet发起请求,验证码无法更新。 (\color{blue}{注意})
● 解决方案:在发起请求中【加入随机参数,例如:时间戳,能保证每次都不一样】,使得与前一次请求不一样,必须从服务器获取。(因为我们只需要该servlet下的图片)

示例

【事务安全】Filter + ThreadLocal

1、问题

在web应用程序中,Servlet容器通常使用【线程池的技术】来处理请求。
线程在被使用完成后,会被线程池回收,但在这种情况下,线程上下文的数据仍然会保留在线程栈中,线程池不会清空线程栈中的数据。(\color{blue}{注意}

● 【多用户情况下】:某一用户发起请求后,会从线程池中获取一个空闲的线程,进行请求处理。在使用前可能就已经被其他用户请求使用过了,它可以继续访问线程栈中的数据,产生数据安全问题。\color{red}{同一个线程可能会被不同用户请求使用!}
● 【同一用户情况下】:可能会发起多次请求,也就是说2次请求有可能会获取同一个线程。尽管是同一个线程,第二次请求可以访问到第一次请求的数据,产生数据安全问题。\color{red}{同一个线程可能会被同一用户的多次请求使用!}

2、解决

在javaweb中,当我们需要保证一个业务是数据安全的,我们需要使用数据库的事务

\color{red}{事务是【基于同一个连接数据库对象connect】!!!}
● 所以我们需要保证当前请求有自己的连接对象,并在访问数据库时始终用同一个,且该连接对象不能被其他请求影响

(1) 无法使用request域来保存连接对象。

不同的web容器,在客户端发起请求时,获取HttpServletRequest对象和HttpServletResponse对象可能会不一样。
但一般都会【使用对象池来管理HttpServletRequest对象和HttpServletResponse对象】。在Tomcat中,例如,在处理HTTP请求时,Tomcat会从一个请求处理线程池中获取一个线程,然后将HTTP请求和响应对象传递给该线程来处理。如果没有空闲线程,则请求会被放入等待队列中,直到有线程可用。当一个请求被处理完成后,Tomcat会将其线程返回到线程池中,同时将其HttpServletRequest和HttpServletResponse对象返回到对象池中,以便可以在下一个请求中重用它们。因此,Tomcat会尽可能地重用对象,从而提高性能和效率。\color{red}{注意:request对象和response对象,它们存在于线程上下文中!}

假设现在我们使用request域存储连接对象,我们可以分以下两种情况讨论:
 ● 在多用户同时请求时,处理的线程是不一样的,这就意味着它们的request对象不会冲突(注意是同时请求),所以此时将连接对象放到request域中可以满足。
 ● 但是,对于同一用户的多次请求,是有可能用到同一个线程的,而线程在放回线程池时,是不会清除上一次请求的数据,所以下一次请求在使用时,就有可能取到上一次请求的数据

因此在request域存储连接对象是数据不安全的。

(2) 无法使用session域来保存连接对象。
 ● session域是跨线程跨请求的,如果用它存储连接对象,类似request,存在线程不安全的问题。
 ● 并且session对象的存活周期长,会造成不必要的浪费

(3) 将连接对象封装到ThreadLocal中使用!——【可解决】

ThreadLocal不仅可以解决多线程访问同一数据的安全问题,也可以解决web应用中同一线程的多次请求访问同一数据的安全问题!

⭕ ThreadLocal实现数据安全的原理

核心:ThreadLocalMap

每个线程内都有一个ThreadLocalMap,即使是使用了同一线程的情况下,每一次新的请求时,都会创建新的ThreadLocal对象,作为该map的键,值对应于数据的副本。每次请求所对应的 ThreadLocal 对象和数据副本都是互相独立且线程安全的。

3、Filter在事务中的作用

在事务执行结束后,没有异常,说明成功,应该把当前connect对象的事务进行提交;反之,在事务执行过程中出现异常,应该把当前connect对象的事务进行回滚。

(1) 对每一个servlet中的doGet()/doPost/service()方法(对应每一个service的功能方法)对进行【try-catch块】的包裹

try{
  Connection connect = JDBCTransUtilsByDruid.getConnection(); // 获取连接对象
  ...
  JDBCTransUtilsByDruid..commit(); // 提交
}catch(exception e){
  JDBCTransUtilsByDruid.rollback(); // 回滚
  throw new RuntimeException(e);
}finally{
  JDBCTransUtilsByDruid.close(); // 关闭
}

(2) 由于访问servlet资源前可以先经过filter,我们利用filter的doFilter()方法,【一次性】实现这种包裹。⭐

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain){
  try{
    filterChain.doFilter(servletRequest, servletResponse);
    JDBCTransUtilsByDruid.commit(); // 提交
  }catch(Exception e){
    JDBCTransUtilsByDruid.rollback(); // 回滚
    throw new RuntimeException(e);
  }finally{
    JDBCTransUtilsByDruid.close();  // 关闭
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容