JavaWeb基础

JavaWeb技术体系

前端知识学习目录

技术简介
HTML结构,页面骨架,静态页面
CSS表现,负责页面画面美化渲染
JavaScript行为,负责页面行为,使网页可以交互,如验证之类的
jQueryJavaScript 库,方便操作BOM与DOM的功能
JSON一种文件格式,字符串
Ajax异步请求
Node.js不是一门编程语言,是一个执行 JavaScript 代码的工具,提供了前端程序的运行环境
可以把Node.js理解成是运行前端程序的服务器,等同于Java中的Tomcat
webpack前端模块打包机,基于 Node.js 开发出来的打包工具,是目前前端项目工程化的具体解决方案,类似Java中的Maven
Vue前端MVVM框架,免除原生JavaScript中的DOM操作,简化书写

B/S架构与C/S架构

B/S架构(Browser/Server)

B/S架构(Browser/Server Architecture)是指浏览器/服务器架构,也被称为Web架构。在B/S架构中,应用程序的前端界面(通常是浏览器)与后端服务器进行交互。

  • 交互流程:在B/S架构中,前端界面是通过浏览器上的Web页面来呈现给用户,通常使用HTML、CSS和JavaScript等技术来实现。用户通过浏览器发送请求(如点击链接、填写表单等),请求会被发送到服务器端进行处理。服务器端进行相应的业务逻辑处理,并返回数据或页面给浏览器,浏览器再将这些数据或页面展示给用户。

  • 适应场景:B/S架构适用于跨平台和分布式环境,因为它只需要一个浏览器作为客户端界面,不需要安装额外的客户端软件

  • 示例应用:常见的网站和在线应用一般采用B/S架构,如电子商务网站、新闻网站等

C/S架构(Client/Server)

C/S架构(Client/Server Architecture)是指客户端/服务器架构。在C/S架构中,应用程序被分为客户端和服务器两个部分。

  • 交互流程:在C/S架构中,客户端是安装在用户本地的软件,负责用户界面的显示和用户交互,用户可以在客户端上进行各种操作,客户端会将这些操作转换为请求,发送给服务器。服务器则负责接收客户端的请求,进行相应的处理,并返回结果给客户端。
  • 适应场景:客户端软件需要在各个平台上进行开发和维护,适用于对性能和用户体验要求较高的应用场景。
  • 示例应用: 桌面应用程序和移动应用程序采用C/S架构,如QQ、迅雷等APP。

在浏览器输入网址后请求的过程

在浏览器输入一个网址后,以下是请求的一般过程:

  1. DNS解析:浏览器首先会解析输入的网址中的域名部分,将域名解析为对应的IP地址。浏览器会向本地DNS服务器发送查询请求,如果本地DNS服务器没有缓存该域名对应的IP地址,则会向根DNS服务器逐级查询,最终获取到目标服务器的IP地址。
  2. 建立TCP连接:浏览器使用HTTP协议与目标服务器建立TCP连接。TCP连接的建立需要进行”三次握手”,即客户端向服务器发送连接请求,服务器回复确认连接请求,最后客户端再次回复确认。
  3. 向服务器发起HTTP请求:建立TCP连接后,浏览器会构建HTTP请求报文,包括请求行、请求头和请求体等信息。请求行中包含了请求方法(GET、POST等)、请求URL和协议版本等。请求头中包含了一些附加的信息,如Cookie、Referer、User-Agent等。请求体中通常包含了一些表单数据或上传的文件等内容。
  4. 服务器处理请求:目标服务器接收到浏览器发送的HTTP请求后,会进行请求的处理。服务器根据请求的URL找到对应的资源(如静态文件或动态程序),执行相应的操作。这可能涉及到数据库查询、业务逻辑处理等。
  5. 服务器发送HTTP响应:服务器处理完请求后,会生成HTTP响应报文,包括响应行、响应头和响应体等信息。响应行中包含了响应状态码(如200表示成功、404表示资源未找到等)和协议版本等。响应头中包含了一些附加的信息,如响应的内容类型、长度、缓存策略等。响应体中包含了服务器返回给浏览器的数据或页面内容。
  6. 接收响应并渲染页面:浏览器接收到服务器发送的HTTP响应后,会根据响应的内容进行页面渲染。如果响应是一个HTML页面,浏览器会解析HTML代码,并加载其中引用的其他资源(如CSS、JavaScript文件等)。最终,浏览器将页面展示给用户。
  7. 关闭TCP连接:当页面全部加载完成后,浏览器会关闭与服务器的TCP连接,释放资源。在后续的操作中,如果需要再次请求同一服务器的资源,浏览器会重新建立TCP连接。

Tomcat

常用的 Web 服 务 器

web服务器简介
TomcatApache组织提供的一种web服务器,提供对jsp和servlet的支持。轻量级的javaweb容器(服务器),当前应用最广的javaweb服务器(免费)
Jboss遵从javaEE规范的、开放源码的、纯java的ELB服务器,支持所有的javaEE规范(免费)
GlassFishOracle公司开发的一款Javaweb服务器,是一款强健的商业服务器,达到产品级质量(应用很少)

Tomcat简介

Tomcat(全名为Apache Tomcat)是一个开源的、轻量级的Web应用服务器,由Apache软件基金会开发和维护。Tomcat可以作为Servlet容器、JSP容器和WebSocket容器

Tomcat特点

Tomcat具有以下几个主要特点:

  1. 轻量级:Tomcat是一个相对轻量级的Servlet容器,它具有较小的内存占用和快速启动的特点。由于其设计简单,它在资源消耗方面比一些其他大型JavaEE服务器(如WebLogic、WebSphere)更加节省。
  2. 独立性:Tomcat是一个独立的服务,可以独立于其他Web服务器(如Apache HTTP Server)运行。它可以作为独立的Servlet容器使用,也可以与其他Web服务器配合使用,通过反向代理或负载均衡实现更复杂的部署架构。
  3. 开源性:Tomcat是一个开源项目,代码完全公开,并且遵循Apache许可证。这意味着任何人都可以查看、使用和修改Tomcat的源代码,可以根据自己的需求进行定制和扩展,使得它具有较好的灵活性和可定制性。
  4. 可扩展性:Tomcat提供了可插拔的架构,允许用户通过添加或配置插件(例如Valve、Realm、Servlet等)来扩展其功能。这使得开发人员可以根据需要增加新的功能组件,或者替换默认的组件实现,以满足特定的需求。
  5. 易用性:Tomcat提供了一个简单易用的管理界面(Tomcat Manager),可以通过该界面对Web应用程序进行部署、启停和监控。它还支持热部署特性,在不重启服务器的情况下更新Web应用程序,方便开发和调试。无论是小型的个人网站还是大型的企业级应用,都可以选择Tomcat作为其运行环境。同时,许多Java Web框架都默认支持Tomcat,使得开发人员更容易上手和开发应用程序。
  6. 多协议支持:Tomcat不仅支持HTTP协议,还支持其他常用的网络协议,如HTTPS、AJP(Apache JServ Protocol)等。这使得Tomcat可以与其他应用程序和Web服务器进行协同工作,提供更灵活的部署方式。
  7. 多线程支持:Tomcat使用多线程技术来处理并发请求,提高系统的并发性能。它为每个请求创建一个独立的线程,这样可以同时处理多个请求,提升Web应用程序的吞吐量。
  8. 安全性支持:Tomcat提供了多种安全措施,例如基于角色的访问控制、SSL/TLS加密等。它通过配置文件和管理界面来管理用户认证、授权和安全设置,以确保Web应用程序的安全性。
  9. JSP支持:Tomcat还支持JavaServer Pages(JSP)技术。JSP是用于创建动态Web页面的Java标准,可以在其中嵌入Java代码和HTML标记。Tomcat可以编译和执行JSP页面,将其转化为Servlet并进行处理,从而生成最终的HTML响应。
  10. Java WebSocket支持:Tomcat对Java WebSocket API提供了支持,可以加载和执行WebSocket相关的类和方法,从而实现WebSocket通信功能。开发人员可以使用Tomcat来构建实时的双向通信应用程序。
  11. Servlet容器:Tomcat作为一个Servlet容器,它能够加载、实例化和管理Servlet组件。它接收来自客户端的HTTP请求,并将请求转发给相应的Servlet进行处理,然后将处理结果返回给客户端。Tomcat负责管理Servlet的生命周期、线程处理和请求-响应的过程。

Tomcat作用

Tomcat作为一个Servlet容器,在Java Web开发中具有以下主要作用:

  1. 提供Servlet容器环境:Tomcat是一个Servlet容器,遵循Java Servlet规范,能够运行Java编写的Servlet代码。
  2. 提供Servlet管理:Tomcat可以管理和处理Servlet的生命周期,包括初始化、加载、调用和销毁。
  3. 提供连接池管理:Tomcat可以管理数据库连接池,提供了一组API和配置选项,用于管理数据库连接的创建、复用和回收,从而提高数据库访问的效率和性能。
  4. URL映射:Tomcat接收来自客户端的HTTP请求,URL映射将客户端请求转发给相应的Servlet进行处理。这样,开发人员可以根据URL的不同将请求分发到不同的Servlet,实现灵活的请求处理机制。
  5. 运行JSP页面:Tomcat内置了JSP引擎,可以将JSP页面编译成Servlet,并在运行时动态地生成HTML响应。开发人员可以使用JSP编写动态的Web页面,通过Tomcat来解析和执行这些JSP页面。
  6. 处理静态资源:除了动态生成的Servlet和JSP页面,Web应用程序还可能包含静态资源,如HTML、CSS、JavaScript、图片等。Tomcat可以直接处理这些静态资源的访问,并将其发送给客户端,无需额外的配置或处理。
  7. Web应用部署:Tomcat提供了一个简单易用的管理界面(Tomcat Manager),允许用户对Web应用程序进行部署、启停和监控。开发人员可以通过Tomcat Manager来管理多个Web应用程序,进行版本管理和灰度发布等操作。

Tomcat目录介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Tomcat
├─ bin # 存放Tomcat的可执行文件
│ ├─ catalina.sh # Tomcat启动脚本(Unix/Linux)
│ ├─ catalina.bat # Tomcat启动脚本(Windows)
│ └─ ...
├─ conf # 存放Tomcat的配置文件
│ ├─ server.xml # Tomcat服务器配置文件
│ ├─ web.xml # 全局Web应用程序配置文件
│ └─ ...
├─ lib # 存放Tomcat的库文件
│ ├─ catalina.jar # Tomcat容器核心库
│ ├─ servlet-api.jar # Java Servlet API库
│ └─ ...
├─ logs # 存放Tomcat日志文件
│ ├─ catalina.out # Tomcat运行日志
│ └─ ...
├─ webapps # 存放Web应用程序
│ ├─ ROOT # 默认的根应用程序
│ ├─ myapp # 自定义的Web应用程序
│ └─ ...
├─ work # 存放Tomcat的工作目录
│ └─ ...
└─ ...

IDEA添加Tomcat

准备工作

(1)使用maven骨架创建web项目,选中Create from archetype,搜索webapp

(2)生成的目录并不是完整的,可以自己添java目录、resources目录、test目录

(3)在pom.xml文件添加打包方法

1
2
<!--<packaging>:打包方式标签(war表示是web项目,默认值是jar)-->
<packaging>war</packaging>

打成war包后目录结构变化

1
2
1、编译后的java字节码文件和resources的资源文件,会放到WEB-INF下的classes目录下
2、pom.xml中依赖坐标对应的jar包,会放入WENF下的lib目录下

方式一:集成本地Tomcat

(1)安装tomcat省略

(2)集成本地Tomcat到IDEA

其中级别update classes and resources最低,restart最高

模式简介
Update resources修改html、css、js后,执行更新
Redeploy修改后台Java代码后,在不重新启动服务器的情况下,重建和重新部署应用程序构件,一般在更改java文件时使用。
Update classes and resources重新编译所有更改的Java类(仅在debugging时)+更新所有更改过的资源,一般在更改.jsp文件时使用
Restart server修改服务器配置(即配置文件)后,执行更新

(3)点击deployment,点右边的+的artifacts,选择war exploded,将开发项目部署项目到Tomcat中

模式简介
xxx.war发布模式,将项目打成war包,把war包发布到Tomcat服务器上, war模式部署成功后,Tomcat的webapps目录下会有部署的项目内容,这个一般是成品项目,也就是整个项目完成后才去做的
xxx.war exploded展开部署,将WEB工程以当前文件夹的位置关系发布到Tomcat服务器上 war exploded模式部署成功后,Tomcat的webapps目录下没有,而使用的是项目的target目录下的内容进行部署 这个更适合我们测试的时候使用,所以虚拟目录应该用这个选项

方式二:Maven添加Tomcat插件

(1)在pom.xml中添加Tomcat插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<build>   
<plugins>
<!--Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port><!--访问端口号 -->
<!--项目访问路径
未配置访问路径: http://localhost:80/tomcat-demo2/test.html
配置/后访问路径: http://localhost:80/test.html
如果配置成 /hello,访问路径会变成: http://localhost:80/hello/test.html
-->
<path>/</path><!--项目访问路径-->
</configuration>
</plugin>
</plugins>
</build>

(2)使用Maven Helper插件快速启动项目

添加Tomcat报错解决

在右下角提示Error:Duplicate context path ‘/‘报错,原因是Tomcat下加入两个maven项目,同一个项目的war和exploded两个版本,因为不可能存在几个项目都发布到同一个地址下这里是’/‘,所以需要修改其他项目的 Application context即可

JavaWeb三大组件

JavaWeb 的三大组件分别是Servlet 程序、Filter过滤器、Listener 监听器

Servlet程序

Servlet简介

Servlet在Java Web开发中扮演着重要的角色,是一种用于在Web服务器上运行的Java程序组件

  • 狭义Servlet:指Java语言中的javax.servlet.Servlet接口,这个接口定义了处理客户端请求和生成响应的方法
  • 广义Servlet:指任何实现了javax.servlet.Servlet接口的类,包括通过实现该接口或继承已有实现的类来创建的Servlet类。

Servlet运行在服务器端,主要作用是使用Java语言与Servlet容器(如Tomcat)进行交互,处理Web请求和生成Web响应。

  1. 处理Web请求:Servlet可以接收来自客户端的HTTP请求,并根据请求的内容进行相应的处理。它可以读取请求参数、处理表单数据、解析请求头等。通过编写Servlet类,开发者可以定义自己的业务逻辑来处理不同类型的请求。
  2. 生成Web响应:Servlet可以根据业务逻辑和请求内容动态生成HTTP响应。它可以生成HTML页面、JSON数据、XML文档等各种形式的响应内容。开发者可以通过设置响应状态码、头信息和内容体等来构建合适的响应,以满足客户端的需求。

Servlet容器简介

  1. Servlet是一种用于在Web服务器上基于Java语言编写的服务器端程序,本身不能独立运行,需要在支持Servlet规范的容器中才能被加载和执行
  2. Servlet容器,也称为Web容器或Servlet引擎,提供了运行和管理Servlet的环境,可以加载和执行Java编写的Servlet代码
  3. 常见的Servlet容器有Apache Tomcat、Jetty、IBM WebSphere和Oracle WebLogic等。这些容器实现了Servlet规范,可以加载和执行Java编写的Servlet代码

Servlet与Tomcat关系

Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户,而Servlet运行在服务器端,主要作用是使用Java语言与Servlet容器(如Tomcat)进行交互,处理Web请求和生成Web响应。Servlet需要依赖Tomcat才能运行,Tomcat 服务器和Servlet 版本的对应关系

Tomcat版本Servlet/jsp版本javaEE版本运行环境
4.12.3/1.21.3Jdk1.3
5.02.4/2.01.4Jdk1.4
5.5/6.02.5/2.15.0Jdk5.0
7.03.0/2.26.0Jdk6.0
8.03.1/2.37.0Jdk7.0

Servlet继承关系

关系简介
javax.servlet.Servlet接口Servlet体系根接口,有5个方法
javax.servlet.Generic抽象类Servlet抽象实现类,对不常用的方法进行了空实现,未对service方法进行实现
javax.servlet.http.HttpServlet抽象子类继承Generic抽象实现类,对serivice方法重写,主要针对表单提交方式的重写

Servlet接口的五个方法

核心方法是init()、service()、destroy(),另外两个方法getServletInfo()和getServletConfig()使用的不是很多,了解即可

方法简介
init()初始化方法,在Servlet被创建时执行,只执行一次
getServletConfig()获取ServletConfig对象
service()服务方法,每次客户端发送请求,Servlet被访问都会调用该方法
getServletInfo()该方法用来返回Servlet的相关信息,没有什么太大的用处,一般返回一个空字符串即可
destroy()销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servlet,只调用一次

Servlet生命周期

生命周期:指一个对象从被创建到被销毁的整个过程,对应Servlet中的三个方法:init()、service()、destroy()

生命周期简介
加载和实例化(实例化)默认情况下,当Servlet第一次被访问时,Servlet调用构造方法(构造器)进行实例化
初始化(初始化)在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象
init()方法仅调用一次,用来加载配置文件、创建连接等初始化的工作
请求处理(服务)每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理
service()方法访问一次就会调用一次
服务终止(销毁)当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放
destroy()方法仅调用一次

Servlet的初始化时机

(1)Servlet的初始化时机默认是第一次接收请求时实例化、初始化,之后每次请求都会调用服务,直到销毁,如果创建Servlet比较耗时的话,那么第一个访问的人等待的时间就比较长,用户的体验就比较差

(2)可以设置初始化时机,解决这个问题

在web.xml中,设置初始化时机(xml方式)

1
2
<!-- 指定servlet被创建的时机,设为负数则在第一次访问时创建、0或正数则在servlet加载进servlet容器时创建,且越小先被创建 -->
<load-on-startup>1</load-on-startup>

使用@WebServlet注解中的loadOnstartup属性设置初始化时机(注解方式)

1
2
// 指定servlet被创建的时机,设为负数则在第一次访问时创建、0或正数则在servlet加载进servlet容器时创建,且越小先被创建
@WebServlet(loadOnStartup = 1)

Servlet注解开发

Servlet的配置是从3.0版本后开始支持注解配置(@WebServlet),3.0版本前只支持XML配置文件的配置方法,XML配置方式和注解比起来,麻烦很多,了解即可,建议使用注解来开发

@WebServlet注解中的属性描述
name指定Servlet 的 name 属性,等价于 <servlet-name>
如果没有显式指定,则该 Servlet 的取值即为类的全限定名
value该属性等价于 urlPatterns 属性。两个属性不能同时使用。
urlPatterns指定一组 Servlet 的 URL 匹配模式。等价于<url-pattern>标签。
loadOnStartup指定 Servlet 的加载顺序,等价于 <load-on-startup>标签。
设为负数则在第一次访问时创建、0或正数则在servlet加载进servlet容器时创建,且越小先被创建
initParams指定一组 Servlet 初始化参数,等价于<init-param>标签。
asyncSupported声明 Servlet 是否支持异步操作模式,等价于<async-supported> 标签。
description该 Servlet 的描述信息,等价于 <description>标签。
displayName该 Servlet 的显示名,通常配合工具使用,等价于 <display-name>标签。

(1)使用Maven创建Web项目,pom文件导入Servlet依赖坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependencies>    
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--
此处为什么需要添加provided标签?
provided指的是在编译和测试过程中有效,最后生成的war包时不会加入
因为Tomcat的lib目录中已经有servlet-api这个jar包,
如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错
-->
<scope>provided</scope>
</dependency>
</dependencies>

(2)定义一个类,实现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
//添加loadOnStartup属性并修改为0或者正整数,则会在服务器启动的时候调用init()方法
@WebServlet(urlPatterns = "/ServletTest",loadOnStartup = 1)
public class ServletTest implements Servlet {
// init()方法:初始化方法,在Servlet被创建时执行,只执行一次
// init()方法调用时机:默认情况下,Servlet被第一次访问时调用
@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

//getServletConfig():获取ServletConfig对象
@Override
public ServletConfig getServletConfig() {
return null;
}

//service()方法:提供服务方法,每次Servlet被访问,都会调用该方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("servlet hello world");
}

//getServletInfo():用来返回Servlet的相关信息,没有什么太大的用处,一般我们返回一个空字符串即可
@Override
public String getServletInfo() {
return null;
}

//destroy():当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servlet,只调用一次
@Override
public void destroy() {

}
}

(3)启动Tomcat,浏览器中输入URL地址访问该Servlet

1
http://localhost:8080//项目名称/@WebServlet注解路径

过滤器(Filter)

过滤器简介

过滤器(Filter)用于对请求和响应进行预处理和后处理。当客户端发送请求时,过滤器会先执行,然后再将请求传递给目标Servlet进行处理。同样,在目标Servlet处理完请求后,过滤器还可以对响应进行处理,最后将响应返回给客户端。

过滤器三要素

三要素简介
拦截过滤器之所以能够对请求进行预处理,关键是对请求进行拦截,把请求拦截下来才能够做后续的操作。 而且对于一个具体的过滤器,它必须明确它要拦截的请求,而不是所有请求都拦截。
过滤根据业务功能实际的需求,看看在把请求拦截到之后,需要做什么检查或什么操作,写对应的代码即可。
放行过滤器完成自己的任务或者是检测到当前请求符合过滤规则,那么可以将请求放行。 所谓放行,就是让请求继续去访问它原本要访问的资源。

过滤器链

(1)多个Filter的拦截范围如果存在重合部分,那么这些Filter会形成Filter链。

(2)过滤器链执行顺序

1
2
3
1、浏览器请求重合部分对应的目标资源时,会依次经过Filter链中的每一个Filter
2、如果采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的
3、如果采取的是xml的方式进行配置,那么按照配置的filter-mapping先后顺序进行排序

过滤器生命周期

生命周期阶段执行时机执行次数
创建对象Web应用启动时一次
初始化创建对象后一次
拦截请求接收到匹配的请求多次
销毁Web应用卸载前一次

过滤器开发步骤

(1)定义类,实现javax.servlet.Filter接口,实现其中的三个方法:init、doFilter、destroy

(2)配置Filter,可以通过xml文件或注解配置,推荐注解

(3)在doFilter()方法中执行过滤,满足过滤条件使用 chain.doFilter(request, response);放行,如果不满足过滤条件转发或重定向请求

过滤器匹配规则

规则简介
/index.html拦截具体的资源,只有访问index.html时才会被拦截
/test/*目录拦截,访问/test下的所有资源,都会被拦截
*.png后缀名拦截,访问后缀名为png的资源,都会被拦截 如果前杠后缀,星号在中间,/.png配置启动Web应用时会抛出异常 java.lang.IllegalArgumentException: Invalid /.png in filter mapping
/*拦截所有,访问所有资源,都会被拦截

过滤器使用案例

需求:请求参数message是否等于monster,等于则使用 chain.doFilter(request, response);放行,不等于则将请求跳转到另外一个页面

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
/**
* 案例:请求参数message是否等于monster,
* 等于则使用 chain.doFilter(request, response);放行,
* 不等于则将请求跳转到另外一个页面
*/
// 通过注解过滤以.test结尾的路由
@WebFilter(urlPatterns="*.test")
public class FilterTest implements Filter{// 实现javax.servlet.Filter接口,重写init()、doFilter()、destroy()方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 打印一句话表明Filter执行了
System.out.println("过滤器执行--------------------------");

// 获取请求参数
String message = request.getParameter("message");

// 检查是否满足过滤条件,请求参数是否等于monster
if ("monster".equals(message)) {
System.out.println("执行放行-------------------------");
// 执行放行
// FilterChain对象代表过滤器链,chain.doFilter(request, response)方法效果:
// 将请求放行到下一个Filter,如果当前Filter已经是最后一个Filter了,那么就将请求放行到原本要访问的目标资源
chain.doFilter(request, response);

}else{
System.out.println("没有放行-------------------------");
// 跳转页面
request.getRequestDispatcher("/test.html").forward(request, response);

}

}

@Override
public void destroy() {

}
}

监听器(Listener)

监听器简介

Listener监听器用于监听Web应用中的事件,并在事件发生时执行相应的操作。它实现了一种观察者模式,用于观察和响应特定的事件。

监听器分类

(1)Servlet规范中定义了9个监听器接口,可以用来监听ServletContext、HttpSession 和 ServletRequest 对象的生命周期和属性变化事件

监听器简介
ServletContext监听器监听ServletContext对象的创建与销毁、以及属性的变化
HttpSession监听器监听HttpSession对象的创建与销毁、以及属性的变化
HttpServeltRequest监听器监听HttpServeltRequest对象的创建与销毁、以及属性的变化

(2)监听器Listener按照监听的事件可以分成3大类,分别为:监听对象创建和销毁的监听器、监听对象中属性变更的监听器、监听 HttpSession 中的对象状态改变的监听器

监听对象创建和销毁的监听器

事件源监听器监听器描述
ServletContextServletContextListener用于监听 ServletContext 对象的创建与销毁过程
HttpSessionHttpSessionListener用于监听 HttpSession 对象的创建和销毁过程
ServletRequestServletRequestListener用于监听 ServletRequest 对象的创建和销毁过程

监听对象中属性变更的监听器

事件源监听器监听器描述
ServletContextServletContextAttributeListener用于监听 ServletContext 对象的属性新增、移除和替换
HttpSessionHttpSessionAttributeListener用于监听 HttpSession 对象的属性新增、移除和替换
HttpServletRequestServletRequestAttributeListener用于监听 HttpServletRequest 对象的属性新增、移除和替换

监听HttpSession中的对象状态改变的监听器

监听器简介
HttpSessionBindingListener用于监听 JavaBean 对象绑定到 HttpSession 对象和从 HttpSession 对象解绑的事件
HttpSessionActivationListener用于监听 HttpSession 中对象活化和钝化的过程

四大作用域

JavaWeb的四大作用域为PageContext页面域、ServletRequest请求域、HttpSession会话域、ServletContext应用域(全局域)

作用域简介
PageContext作用范围是整个JSP页面,是四大作用域中最小的一个;生命周期是当对JSP的请求时开始,当响应结束时销毁
ServletRequest作用范围是一次请求响应范围
HttpSession作用范围是一次会话
ServletContext作用范围是整个Web应用,一次应用程序范围有效,全局

PageContext页面域

在使用的视图是JSP的时候,域对象有4个,使用Thymeleaf的时候,域对象有3个,没有pageContext

页面域简介
生命周期访问jsp页面时创建,访问结束时销毁
作用范围当前jsp页面
功能在整个jsp页面共享数据

ServletRequest请求域

请求域简介
生命周期一次请求开始时创建,结束时销毁
作用范围一次请求响应范围
功能在整个请求链共享数据

HttpSession会话域

会话域简介
生命周期创建、销毁(超时销毁、主动杀死)、意外身亡(服务器宕机)
作用范围整个会话
功能在整个会发范围内共享数据

ServletContext应用域

应用域简介
生命周期web应用启动时创建,web应用销毁时销毁
作用范围整个web应用
功能在整个web应用中共享数据

请求与响应

请求(request)

HTTP请求数据总共分为三部分内容,分别是请求行、请求头、请求体

获取请求行数据

方法描述
getMethod()获取请求的HTTP方法
getRequestURI()获取请求的URI路径部分
getRequestURL()获取请求的完整URL
getProtocol()获取请求使用的协议
getScheme()获取请求的协议
getQueryString()获取请求的查询字符串部分
getServletPath()获取请求的Servlet路径部分

获取请求头数据

方法描述
getHeader(String name)获取指定名称的请求头部的值
getHeaders(String name)获取指定名称的请求头部的所有值
getIntHeader(String name)获取指定名称的请求头部的整数值
getDateHeader(String name)获取指定名称的请求头部的日期值
getContentType()获取请求的Content-Type头部的值
getContentLength()获取请求的Content-Length头部的值

获取请求参数

方法描述
getParameter(String name)获取指定名称的请求参数的值(单个值)
getParameterValues(String name)获取指定名称的请求参数的所有值(数组)
getParameterMap()获取所有请求参数的映射(Map集合)

获取请求体数据

方法简介
getInputStream()获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法
getReader()获取字符输入流,如果前端发送的是纯文本数据,则使用该方法

获取Cookie

方法描述
getCookies()获取请求中的所有Cookie

获取远程地址

方法描述
getRemoteAddr()获取客户端的IP地址
getRemoteHost()获取客户端的主机名
getRemotePort()获取客户端的连接端口
getRemoteUser()获取客户端的用户名(如果有认证)

获取会话

方法描述
getSession(boolean create)获取会话对象,如果不存在是否创建
getSession()获取会话对象,如果不存在则创建一个新的会话

获取认证

方法描述
getUserPrincipal()获取请求的用户主体(已认证的用户)
isUserInRole(String role)检查请求的用户是否具有指定角色

获取其他

方法描述
getLocale()获取请求的语言环境
getLocales()获取请求支持的所有语言环境

request请求域

方法简介
setAttribute(String name,Object value);存(怎么向ServletContext请求域中存数据)
getAttribute(String name);取(怎么从ServletContext请求域中取数据)
removeAttribute(String name);删(怎么删除ServletContext请求域中的数据)

请求转发相关方法

方法简介
getRequestDispatcher(String path)根据转发的资源路径path得到转发器RequestDispatcher

响应(response)

响应消息就是服务器响应给客户端的消息内容, 也叫做响应报文,主要由响应行、响应头部和响应体3个部分组成

设置响应头

方法简介
addHeader(String name, String value)设置HTTP响应头字段,name指定字段名称,value指定字段值,可以增加同名的响应头字段
setHeader(String name, String value)设置HTTP响应头字段,name指定字段名称,value指定字段值,会覆盖同名的头字段
setContentLength(int len)设置响应消息的实体内容的大小,单位为字节,即设置Content-Length字段的值
setContentType(String type)设置Servlet输出内容的MIME类型,即设置Content-Type字段的值
setCharacterEncoding(String charset)设置输出内容字符编码,即设置Content-Type字段的值,该方法优先级比setContentType的高
sendRedirect(String location)Servlet请求重定向

响应体

通过输出流, 通过Response得到网络输出流

方法简介
getOutputStream()得到响应字节输出流,万能流
getWriter()得到响应字符输出流,输出字符

响应状态码

方法简介
setStatus(int sc)设置响应消息状态码,Web服务器默认产生一个状态码为200的状态行
sendError(int sc)发送表示错误信息的状态码
sendError(int sc, String msg)发送表示错误信息的状态码和错误提示信息

Response响应字符数据案例

创建一个类响应字符数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebServlet("/response")
public class ResponseTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应的数据格式及数据的编码(设置后可以不设置content-type)
response.setContentType("text/html;charset=utf-8");
//获取字符输出流
PrintWriter writer = response.getWriter();
//设置content-type,告诉浏览器返回的数据类型是HTML类型数据,这样浏览器才会解析HTML标签
//response.setHeader("content-type","text/html");

//通过字符输出流写数据
writer.write("你好,response");
writer.write("<h1>你好,response</h1>");
//细节:流不需要关闭
//一次请求响应结束后,response对象就会被销毁掉,所以不要手动关闭流。

}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}

Response响应字节数据案例

(1)pom.xml添加依赖

1
2
3
4
5
6
<!-- 处理IO的工具类包 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
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
@WebServlet("/response")
public class ResponseTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 读取文件
FileInputStream fis = new FileInputStream("E:\\图片、视频、音乐\\图片\\静态图片\\1.png");

//2. 获取response字节输出流
ServletOutputStream os = response.getOutputStream();

//3. 完成流的复制(copy)
//byte[] buff = new byte[1024];
//int len = 0;
//while ((len = fis.read(buff)) != -1) {
// os.write(buff, 0, len);
//}
//使用工具类,简化流的复制(copy)
IOUtils.copy(fis,os);

//4.关闭流
fis.close();

}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}

请求转发与重定向

转发与重定向简介
请求转发一次请求,一次响应,浏览器地址不发生变化,参数不会丢失,只能访问内部资源,由于只创建了一次客户端和服务器的链接,相对而言转发会比较节省网络资源
请求重定向二次请求,二次响应,浏览器地址发生变化,参数会丢失,可以定向到任何资源

请求转发(forward)

请求转发是一种在服务器内部的资源跳转方式,本质是转交,在请求的处理过程中,Servlet完成了自己的任务,需要把请求转交给下一个资源继续处理

请求转发的流程

(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A处理完请求后将请求发给资源B
(3)资源B处理完后将结果响应给浏览器
(4)请求从资源A到资源B的过程就叫请求转发

请求转发的特点

  1. 浏览器地址栏路径不发生变化,且只能转发到当前服务器的内部资源,不能从一个服务器通过转发访问另一台服务器
  2. 一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的

请求转发的实现方式

方法简介
request.getRequestDispatcher(“资源B路径”).forward(request,response);将请求转发到B路径

请求转发的案例

(1)创建一个类,将数据存入request域对象中,然后转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//添加loadOnStartup属性并修改为0或者正整数,则会在服务器启动的时候调用init()方法
@WebServlet(urlPatterns = "/Servlet_5_请求转发", loadOnStartup = 1)
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("获取请求...");
// 打印request
System.out.println(request);
// 存储数据到request域中(范围,数据是存储在request对象)
request.setAttribute("key","hello world");
// 将请求转发到指定路径
request.getRequestDispatcher("/ServletB").forward(request,response);
}
}

(2)创建另一个类,接收转发并从request域对象中获取数据,并将数据打印到控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//添加loadOnStartup属性并修改为0或者正整数,则会在服务器启动的时候调用init()方法
@WebServlet(urlPatterns = "/ServletB", loadOnStartup = 1)
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("请求已经被转发...");
//再次打印request
System.out.println(request);

// 根据key获取value值
Object msg = request.getAttribute("key");
System.out.println("获取转发的数据:"+msg);
}
}

(3)启动Tomcat,访问http://localhost:8080/ServletA ,路径没有变化,控制台打印如下

1
2
3
4
5
获取请求...
org.apache.catalina.connector.RequestFacade@68b412fb
请求已经被转发...
org.apache.catalina.core.ApplicationHttpRequest@432fbf29
获取转发的数据:hello world

请求重定向(redirect)

Response重定向是一种资源跳转方式,本质是一种特殊的响应,在请求的处理过程中,Servlet完成了自己的任务,然后以一个响应的方式告诉浏览器:“要完成这个任务还需要你另外再访问下一个资源”

请求重定向的流程

(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的路径
(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B
(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向

请求重定向的特点

(1)浏览器地址栏路径发生变化,客户端可以知道请求URL有变化,当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化

(2)可以重定向到任何位置的资源(服务内容、外部均可),因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。

(3)两次请求响应的过程,不能在多个资源使用request共享数据,因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据

请求重定向的实现方式

对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录,但对于重定向,路径最终是由浏览器来发送请求,需要添加虚拟目录

1
2
3
4
5
6
// 动态获取虚拟目录(项目根路径)
String contextPath = request.getContextPath();
// 设置重定向
resposne.sendRedirect(contextPath+"资源B的访问路径")
// 设置状态码
resposne.setStatus(302);

请求重定向的案例

(1)创建一个类设置重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//添加loadOnStartup属性并修改为0或者正整数,则会在服务器启动的时候调用init()方法
@WebServlet(urlPatterns = "/ServletRedirect", loadOnStartup = 1)
public class ServletRedirect extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("设置重定向....");

// 动态获取虚拟目录(项目目录)
String contextPath = request.getContextPath();
// 设置重定向
response.sendRedirect(contextPath+"/ServletTest");

// 重定向到 远方の博客
// response.sendRedirect("https://wen53231323.github.io/");
}
}

(2)另一个类接收重定向

1
2
3
4
5
6
7
8
//添加loadOnStartup属性并修改为0或者正整数,则会在服务器启动的时候调用init()方法
@WebServlet(urlPatterns = "/ServletTest", loadOnStartup = 1)
public class ServletTest extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("Response重定向重定向成功....");
}
}

(3)启动Tomcat,访问http://localhost:8080/ServletRedirect,会发现路径发生变化,重定向为http://localhost:8080/ServletTest,同时控制台打印如下

1
2
设置重定向....
Response重定向重定向成功....

MVC与三层架构

架构思想

架构的分类

对于“架构”来讲,理论上划分了5种架构视图,分别是:逻辑架构、开发架构、运行架构、物理架构、数据架构。

分类简介
逻辑架构逻辑架构关注的是功能,包含用户直接可见的功能,还有系统中隐含的功能,偏向日常所理解的“分层”,把一个项目分为“表现层、业务逻辑层、数据访问层”这样经典的“三层架构”。
开发架构开发架构则更关注程序包,不仅仅是我们自己写的程序,还包括应用程序依赖的SDK、第三方类库、中间价等
运行架构应用程序运行中可能出现的一些问题,例如:高并发、多线程
物理架构系统、网络、服务器等基础设施
数据架构数据持久化和存储层面的问题,关系型数据库与非关系型数据库的选择问题,例如:数据的分布、复制、同步等

为什么需要架构

(1)以前系统简单,一个应用部署在一台服务器上,且大部分开发工作是CRUD,应用结构简单,且易于维护

(2)随着系统业务复杂度越来越高,功能模块越来越庞大,耦合度也越来越高,导致系统的复杂度越来越不可控

(3)为了更好的降低应用及模块间的耦合度,诞生了一些熟知的网络架构(分布式微服务)和应用架构(三层架构、MVC架构),使用框架的好处就是结构清晰易于维护

MVC架构

MVC设计模式简介

MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分

  • 模型(Model):模型表示应用程序的数据和业务逻辑
  • 视图(View):视图负责展示模型中的数据给用户,并接受用户的输入
  • 控制器(Controller):控制器作为模型和视图之间的中介,处理用户的请求并协调模型和视图的交互

MVC的工作流程

  1. 用户发送请求:用户在浏览器中输入URL或者与应用程序进行交互,发送请求给服务器。
  2. 控制器接收请求:服务器接收到请求后,控制器(通常是一个Servlet)负责接收并处理请求。控制器根据请求的类型和参数,选择合适的处理方法。
  3. 模型处理请求:控制器根据请求的类型和参数,调用相应的模型来处理数据。模型负责处理业务逻辑,包括数据的获取、处理、验证、存储等操作。
  4. 视图展示数据:模型处理完数据之后,将处理结果返回给控制器。控制器根据处理结果选择适当的视图来展示数据。视图负责将数据渲染成可视化的界面供用户查看。
  5. 用户与视图交互:用户在浏览器中与视图进行交互,例如填写表单、点击按钮等。用户的操作可能会触发新的请求。

三层架构

三层架构的简介

为了符合高内聚低耦合思想,可以把各个功能模块划分为了表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构。三层架构是一种软件设计模式,将应用程序按照功能和责任划分为三个独立的层次,每个层次都有自己的职责和功能,且彼此之间独立操作,提高了系统的可维护性、可扩展性和可测试性。通常由以下三个层级组成:

  • 表示层(Presentation Layer):表示层是系统与用户进行交互的界面部分,包括用户界面(如Web页面、移动应用界面、桌面应用界面等)和用户输入处理逻辑。主要作用是将用户的请求传递给业务逻辑层,并将处理结果展示给用户
  • 业务逻辑层(Business Logic Layer):业务逻辑层是应用程序的核心部分,包含了应用程序的业务规则、算法、流程等。主要作用是处理来自表示层的请求,调用数据访问层提供的数据实现系统的业务逻辑
  • 数据访问层(Data Access Layer):数据访问层是与数据存储系统(如数据库或文件系统)交互的部分,包括数据的存储、检索、更新等操作。主要作用是与数据存储系统(如数据库或文件系统)交互进行交互,执行对数据的增删改查等操作,并将操作结果返回给业务逻辑层

(2)在三层架构程序设计中,采用面向接口编程。各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体

(3)层是一种弱耦合结构,层与层之间的依赖是向下的,上层对下层的调用,是通过接口实现的,而真正提供服务的是下层的接口实现类。服务标准接口是相同的,而实现类是可以替换的,这样就实现了层与层间的解耦

三层架构的关系

三层架构的关系:控制层 调用 业务层 处理业务逻辑,业务层调用 Dao 持久化数据,简单调用流程如下

用户访问浏览器向服务端发送请求,浏览器收到响应的数据展示页面(前端页面)

​ ↓ ↑

表现层Controller接收请求,向下调用业务逻辑层Service处理业务逻辑,向上封装结果返回给客户端

​ ↓ ↑

业务层Service处理业务逻辑,向下调用数据访问层Dao操作数据,向上为表现层提供处理结果

​ ↓ ↑

数据访问层Dao向下操作数据库数据,向上为业务逻辑层提供数据

三层架构对应的包和框架

三层架构对应包对应框架
表现层controller包或web包SpringMVC、Struts2
业务逻辑层service包Spring
数据访问层dao包或mapper包Mybatis

Servlet案例回顾

JavaWeb时做的水果商店案例回顾,基础代码省略,仅保留核心思想与过程

基础水果商店

基础版本中,使用了多个Servlet,一个请求对应一个Servlet

1
IndexServlet、AddServlet、EditServlet、DelServlet、UpdateServlet

优化水果商店

(1)由于Servlet太多,可以将多个Servlet合并成一个FruitServlet

1
IndexServlet、AddServlet、EditServlet、DelServlet、UpdateServlet -> 合并成FruitServlet

(2)合并成一个FruitServlet后,需要前端多传递一个operate参数与方法名对应,后端接收到operate的值是什么,表明需要调用对应的方法进行响应,如果找不到对应的方法,则抛异常。可以使用switch-case,通过一个operate的值来决定调用FruitServlet中的哪一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取前端传递的operate
String operate = request.getParameter("operate");
switch (operate) {
case "index":
index(request, response);
break;
case "add":
add(request, response);
break;
case "update":
update(request, response);
break;
case "edit":
edit(request, response);
break;
case "del":
del(request, response);
break;
}

(3)随着项目的业务规模扩大,会有很多的Servlet,意味着会有很多的switch-case,从而造成代码冗余,此时可以使用反射技术,根据方法名的对象,调用类中对应的方法

1
2
3
4
5
6
7
// 获取前端传递的operate
String operate = request.getParameter("operate");
// 获取方法对象
// getDeclaredMethod(String name, Class<?>… parameterTypes):根据指定的方法名称和参数,匹配类中的方法,返回Method对象
Method methodName = getClass().getDeclaredMethod(operate, HttpServletRequest.class, HttpServletResponse.class);
// this表示当前类的对象,调用类中对应方法
methodName.invoke(this, request, response);

引入中央控制器DispatcherServlet

虽然使用了反射技术,但每一个servlet中都有类似的反射技术的代码,因此可以继续抽取,设计中央控制器类:DispatcherServlet

根据url定位controller组件

(1)假设有多个xxxServlet,可以将xxxServlet改名为xxxController,可以创建一个xml文件,在bean标签中,以id属性对应方法名,class属性对应指定包下的类

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<beans>
<!-- 作用:将来servletpath中涉及的名字对应的是fruit,那么就要FruitController这个类来处理 -->
<bean id="fruit" class="com.wen.controller.FruitController"/>
<!-- 也可以添加其他Controller控制器,例如UserController,涉及用户的增删改查 -->
</beans>

(2)创建中央控制器DispatcherServlet类,继承HttpServlet,在初始化方法init()中,通过DOM技术解析XML文件,将解析到的id和class属性对应类的实例对象存放到Map集合类型的beanMap容器中

1
2
3
4
5
6
7
8
9
10
11
12
// 定义Map容器
private Map<String, Object> beanMap = new HashMap<>();
// 获取xml配置文件中的id
String beanId = beanElement.getAttribute("id");
// 获取xml配置文件中的class(类的全类名)
String className = beanElement.getAttribute("class");
// 根据 类的全类名,获取 类的实例
Class controllerBeanClass = Class.forName(className);
// 加载的类的实例对象
Object beanObj = controllerBeanClass.newInstance();
// 将 id 和 对应类的实例 保存到map集合
beanMap.put(beanId, beanObj);

(3)从url中提取路径servletPath,对应xml文件中的id(/fruit.do —> fruit)

1
2
3
4
5
6
7
8
// 获得请求servlet服务的路径(/xxx.do)
String servletPath = request.getServletPath();
// 截取/,剩下xxx.do
servletPath = servletPath.substring(1);
// 获取.do的下标,lastIndexOf():获取下标
int lastDotIndex = servletPath.lastIndexOf(".do");
// 获取到xxx,substring():获取指定下标的字符
servletPath = servletPath.substring(0, lastDotIndex);

(4)从beanMap容器中根据路径fruit(id)获取到对应的组件实例FruitController(class)

1
2
// 根据路由名称(对应xml中的id),获取对应类的实例。即在Map集合中,根据key获取value
Object controllerBeanObj = beanMap.get(servletPath);

(5)通过反射,根据组件实例获取实例中的所有方法

1
2
// getDeclaredMethods():返回类中(类自身)所有的实例方法,包含public、protected和private方法。
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();

(6)获取前端的operate的值,遍历所有的实例方法,若方法与operate相同则进行后续处理,否则抛出异常

1
2
3
4
5
6
7
8
9
//  获取前端传递的operate(用于区分方法)
String operate = request.getParameter("operate");
// 循环遍历方法
for (Method methodName : methods) {
// 如果前端传递的参数方法 与 类中的方法一致则执行
if (operate.equals(methodName.getName())) {
……(此处省略了调用Controller组件中的方法过程)
}
}

调用Controller组件中的方法

(1)由于已经遍历了所有的方法,所以可以统一获取请求参数,并做统一处理(获取即将要调用的方法的参数信息,存放到参数数组)此处只考虑了具体逻辑,另外需要考虑参数的类型问题,详见具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 统一获取请求参数:获取即将要调用的方法的参数签名信息,返回参数数组
Parameter[] parameters = methodName.getParameters();

// parameterValues数组用来存放 获取到的每个参数的值
Object[] parameterValues = new Object[parameters.length];

// 遍历参数数组
for (int i = 0; i < parameters.length; i++) {
// 获取一个参数
Parameter parameter = parameters[i];
// 获取参数名
String parameterName = parameter.getName();
// 获取Object型的参数值
Object parameterObj = parameterValue;
// 存储获取的参数
parameterValues[i] = parameterObj;
}

(2)执行对应的方法

1
2
// invoke(Object obj, Object… args):组件中的方法调用,方法对象.invoke(对象,方法参数)
Object returnObj = methodName.invoke(controllerBeanObj, parameterValues);

(3)视图处理

1
2
3
4
5
6
// 根据执行方法的返回值,做出对应的重定向或页面定位
String methodReturnStr = (String) returnObj;
// 获取的参数是redirect:开头的(比如:redirect:fruit.do),截取redirect:(比如:剩下fruit.do)
String redirectStr = methodReturnStr.substring("redirect:".length());
// 重定向到对应页面
response.sendRedirect(redirectStr);

引入IOC控制反转和DI依赖注入(工厂模式)

由于层与层之间调用时,都需要使用new创建对象,导致层与层之间具有耦合性,可以使用IOC控制反转和DI依赖注入实现解耦

(1)创建BeanFactory接口用于根据配置文件获取bean对象

1
2
3
4
5
6
/**
* 根据配置文件获取bean对象
*/
public interface BeanFactory {
Object getBean(String id);
}

(2)让中央控制器类实现BeanFactory接口重写getBean()

1
2
3
4
@Override
public Object getBean(String id) {
return beanMap.get(id);
}

(3)在中央控制器的init()初始化bean工厂

1
2
3
4
5
6
7
8
// 注入
private BeanFactory beanFactory;

@Override
public void init() throws ServletException {
// 初始化bean工厂
beanFactory = new ClassPathXmlApplicationContext();
}

(4)使用bean工厂造指定类的对象

1
beanFactory.getBean(xxx);

引入事务管理(代理模式)

事务是逻辑上的一组操作,要么都执行,要么都不执行

(1)定义工具类,用来创建MySQL连接对象、获取MySQL连接对象、关闭MySQL连接对象

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
/**
* 工具类:创建MySQL连接对象、获取MySQL连接对象、关闭MySQL连接对象
*/
public class ConnUtil {

// 本地线程变量,用于事务控制,为每一个进程创建一个connection对象
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//private static ThreadLocal<Object> threadLocal2 = new ThreadLocal<>();
//private static ThreadLocal<Object> threadLocal3 = new ThreadLocal<>();

public static final String DRIVER = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
public static final String USER = "root";
public static final String PWD = "123456";

// 创建连接对象
private static Connection createConn() {
try {
//1.加载驱动
Class.forName(DRIVER);
//2.通过驱动管理器获取连接对象
return DriverManager.getConnection(URL, USER, PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}

// 获取连接对象
public static Connection getConn() {
// threadLocal.get():获取线程本地变量的内容
Connection conn = threadLocal.get();
if (conn == null) {
conn = createConn();
threadLocal.set(conn);
}
return threadLocal.get();
}

// 关闭连接对象
public static void closeConn() throws SQLException {
Connection conn = threadLocal.get();
if (conn == null) {
return;
}
if (!conn.isClosed()) {
conn.close();
// threadLocal.set(null):设置线程本地变量的内容为null
threadLocal.set(null);
}
}
}

(2)使用工具类实现MySQL的开启事务、提交事务、回滚事务

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
/**
* 使用工具类实现MySQL的开启事务、提交事务、回滚事务
*/
public class TransactionManager {

// 开启事务
public static void beginTrans() throws SQLException {
ConnUtil.getConn().setAutoCommit(false);
}

// 提交事务
public static void commit() throws SQLException {
// 获取mysql连接对象
Connection conn = ConnUtil.getConn();
// 提交事务
conn.commit();
ConnUtil.closeConn();
}

// 回滚事务
public static void rollback() throws SQLException {
Connection conn = ConnUtil.getConn();
conn.rollback();
ConnUtil.closeConn();
}
}

(3)定义事务过滤器,用来在将一个业务逻辑作为整体,要么都执行,要么都不执行

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
/**
* 过滤器:拦截请求,开启事务,执行放行
* 若未出现错误则提交事务,若出现错误则回滚
*/
@WebFilter("*.do")
public class OpenSessionInViewFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
// 开启事务
TransactionManager.beginTrans();
System.out.println("开启事务....");
// 执行放行
filterChain.doFilter(servletRequest, servletResponse);
// 提交事务
TransactionManager.commit();
System.out.println("提交事务...");
} catch (Exception e) {
e.printStackTrace();
try {
// 回滚事务
TransactionManager.rollback();
System.out.println("回滚事务....");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}

@Override
public void destroy() {

}
}