技术体系
文章目录
- 一 HTML
- 1 网页的组成部分
- 2 HTML 概述
- 3 HTML 标签
- 4 常用标签
- 5 表单与表单的提交
- 二 CSS
- 1 语法格式
- 2 使用方法
- 三 JavaScript
- 1 概述
- 2 与 HTML 结合的两种方式
- 3 变量类型及特殊值
- 4 关系、逻辑运算
- 5 数组
- 6 函数
- 7 事件
- 8 DOM (Document Object Model)
- 9 DOM实例:验证用户名是否有效
- 四 Tomcat
- 五 Servlet
- 1 举例:向数据库中添加表单信息
- 2 Servlet 的继承关系
- 3 Servlet 的生命周期
- 4 HTTP 协议与 Session会话跟踪
- 5 服务器端内部转发、重定向
- 6 Servlet 保存作用域
- 7 Servlet 的 init 方法与初始化参数设置
- 8 ServletContext
- 六 Thymeleaf
- 1 配置过程
- 2 Servlet 优化 - 合并同类方法
- 3 Servlet 优化 - dispatchedServlet
- 七 MVC
- 1 概念
- 2 降低各层间的耦合 - IOC 与 DI
- 3 IOC 与 DI 的实现过程
- 八 Filter
- 1 概述
- 2 使用 Filter 实现事务的原子性
- 九 Listener
- 十 总结
一 HTML
1 网页的组成部分
- 内容(结构):在页面中可以看到的数据。我们称之为内容。一般使用 HTML 技术展示。
- 表现:内容在页面上的展示形式,比如布局,颜色,大小等等。一般使用CSS 技术实现。
- 行为:页面中元素与输入设备交互的响应。一般使用 JavaScript 技术实现。
2 HTML 概述
- Hyper Text Markup Language (超文本标记语言),网页文件本身是一种文本文件,通过在文本文件中添加标记符,可以告诉浏览器如何显示其中的内容。
- HTML 文件不需要编译,直接由浏览器进行解析执行。
- 书写规范:大致由 head 和 body 两部分组成。
<html lang="en"> <!-- 页面开始 --><head> <!-- HEAD --><meta charset="UTF-8"><title> 我的标题 </title></head><body> <!-- BODY -->hello!</body></html> <!-- 页面结束 -->
3 HTML 标签
- 标签名对大小写不敏感。
- 双标签的格式:<标签名> 封装的数据 </标签名>
单标签的格式:<标签名/> - 标签拥有自己的属性,分为基本属性和事件属性。属性必须有值,属性值必须加引号。
- 双标签必须正确关闭,不能交叉嵌套。
- 想显示“转义字符”的问题(比如想打印出左尖括号<),用实体名称解决(对应的实体名称是<)。
<body onclick="alert('警告')"> <!-- 带有事件属性 -->点击body部分会出现警告 <hr/> <!-- 单标签 --><font color="blue"> 蓝色的字体 </font> <!-- 双标签,带有基本属性 --></body>
4 常用标签
重点是超链接、表格、表单
<!--标题标签,最大是h1,最小是h6,对齐可以选择 left/ center/ right-->
<h1 align="center"> 标题 </h1><!--字体-->
<font color="red" size="7"> 字体 </font><!--超链接,可选参数 target (_self 在当前页面跳转,_blank 在新页面跳转...)-->
<a href="www.baidu.com" target="_blank"> 超链接 </a><!--无序列表(有序将ul改为ol)-->
<ul><li> item1 </li><li> item2 </li>
</ul><!--图片img标签是图片标签,用来显示图片src属性可以设置图片的路径width属性设置图片的宽度height属性设置图片的高度border属性设置图片边框大小alt属性设置当指定路径找不到图片时,用来代替显示的文本内容绝对路径的正确格式是: http://ip:port/工程名/资源路径
-->
<img src="./imgs/1.jpg" width="100" height="200"/><!--表格table 标签是表格标签border 设置表格标签width 设置表格宽度height 设置表格高度align 设置表格相对于页面的对齐方式cellspacing 设置单元格间距tr 是行标签th 是表头标签td 是单元格标签align 设置单元格文本对齐方式b 是加粗标签如果要实现跨行、跨列,改变 td 的 colspan、rowspan 属性
-->
<table align="center" border="1" width="300" height="300" cellspacing="0"><tr><th>1.1</th><th>1.2</th><th>1.3</th></tr><tr><td>2.1</td><td>2.2</td><td>2.3</td></tr><tr><td>3.1</td><td>3.2</td><td>3.3</td></tr>
</table><!--iframe标签,在原页面之上显示一个小的页面其中的 name 属性可以作为超链接的 target 属性,点击超链接后将在 iframe 中显示-->
<iframe src="1.html" width="500" height="400" name="abc"></iframe>
<a href="2.html" target="abc"> 超链接 </a>
5 表单与表单的提交
表单类型单独列出,可以使用 table 实现对齐。
<!-- 要顺利提交表单,需要为所有项加上 value 或者 name 属性 --><form action="http://localhost:8080" method="post">姓名:<input type="text" value="默认姓名"/></br>密码:<input type="password" value="default"/></br><!-- name 属性用于分组,同一组的选项互斥-->性别:<input type="radio" name="sex" checked="checked"/>男 <input type="radio" name="sex"/>女</br>爱好:<input type="checkbox" checked="checked"/>跑步 <input type="checkbox"/>跳绳</br>国籍:<select><option>--请选择--</option><option selected="selected">CHN</option><option>USA</option></select><br/>简介:<textarea rows="10" cols="20">默认简介</textarea><br/>附件:<input type="file"/></br><input type="reset" value="重置"><input type="submit" value="提交"></form>
- form 标签的 action 属性设置提交的服务器地址,method 属性设置提交的方式 GET(默认) 或 POST
- GET 请求的特点是:
- 浏览器地址栏中的地址是:服务器地址 + ? + 请求参数(请求参数的格式是 name=value&name=value)
- 不安全
- 有数据长度的限制
- POST 请求的特点:
- 浏览器地址栏中只有服务器地址(action的属性值)
- 相对于GET请求要安全
- 理论上没有数据长度的限制
- 表单提交的时候,数据没有发送给服务器的三种情况:
- 表单项没有name属性值
- 单选 radio 、复选 checkbox、下拉列表中的 option 标签 都需要添加value属性,以便发送给服务器
- 表单项不在提交的form标签中
二 CSS
1 语法格式
/* 标签名选择器,样式绑定标签 */
label_name { property1: val1;property2: val2;
}/* id选择器,样式绑定具体的id,id是人为设定的,每个实例不相同*/
#id {property1: val1;property2: val2;
}/* class选择器,样式绑定标签分配的class,可以多个实例绑定一个class */
.class class_name {property1: val1;property2: val2;
}/* 组合选择器,选择器间是并的关系 */
选择器1, 选择器2 ... {property1: val1;property2: val2;
}
2 使用方法
<head> <!-- HEAD --><meta charset="UTF-8"><title> my_title </title>/* 导入已经写好的CSS文件(推荐) */<link rel="stylesheet" type="text/CSS" href="mycss.css">/* 或者把CSS写到此处的 style 标签中 */<style type="text/css">label_name { property1: val1;property2: val2;}</style></head>
三 JavaScript
1 概述
- JS 运行在客户端,需要运行浏览器来解析执行 JavaScript 代码,和 Java 并无直接关系。
- JS 是弱类型的语言,Java 是强类型的语言。强弱的差别在于定义变量后,变量的数据类型是否可变。
- 特点是 交互性(信息的动态交互)、安全性(不允许直接访问本地硬盘)、跨平台性(只要是可以解释 JS 的浏览器都可以执行,和平台无关)。
2 与 HTML 结合的两种方式
- 在 HTML 文件中的 head 部分,用 script 标签写入;
- 在 script 标签中导入 JS 文件。
上述两种方法不能写在一个标签里。
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">alert('JS嵌入方式1');</script><script src="myjs.js"></script> <!-- 文件内容: alert('JS嵌入方式2'); -->
</head>
3 变量类型及特殊值
关键字 | 对应类型 |
---|---|
number | 数值类型 |
string | 字符串类型 |
object | 对象类型 |
bool | 布尔类型 |
function | 函数类型 |
特殊值 | 含义 |
---|---|
undefined | 未定义,所有 JS 变量未赋于初始值的时候,默认值都是 undefined |
null | 空 |
NaN | 非数字非数值 |
4 关系、逻辑运算
- ==比较两个变量的值是否相等(比如,“123”==123是成立的), ===比较两个变量的类型和值是否相等;
- JS 中所有变量都可以作为布尔值使用,0、null、undefined、空串 都认为是 false;
- ||(或) &&(与) 具有短路特性,返回值为第一个导致结果的变量。
5 数组
- 数组不会出现越界的问题。在超过原来长度的位置赋值时,会进行自动扩容(读操作不会扩容)。
// 数组定义
var arr = [1, "abc"];// 数组遍历
for (var i = 0; i < arr.length; i++) {alert(arr[i])
}// 自动扩容,下标2、3、4的元素均为undefined
arr[5] = 2;
6 函数
- JS 不允许函数重载。一个函数名只能对应一个具体实现。
function noParam() {alert('无参函数调用');}noParam();function withParam(a, b) {alert("有参函数调用" + a + b);}withParam(1, 2);function withReturn(a, b) {alert("有参有返回值函数调用");return a + b;}alert(withReturn(10, 5));
- 隐形参数 arguments 将所有实参组织为一个数组,通过下标可以访问所有参数。
function testInvisibleParams() {var res = 0;for (var i = 0; i < arguments.length; i++) {res += arguments[i];}return res;}alert(testInvisibleParams(1, 2, 3, 4));
7 事件
- 常用事件
事件 | 操作 |
---|---|
onload(加载完成) | 常用做页面 JS 代码初始化操作 |
onclick(单击) | 按钮的点击响应操作 |
onblur(失去焦点) | 输入框失去焦点后验证其输入内容是否合法 |
onchange(内容发生改变) | 下拉列表和输入框内容发生改变后操作 |
onsubmit(表单提交) | 表单提交前,验证所有表单项是否合法 |
- 事件需要进行注册才能使用:
- 静态注册:通过 HTML 标签的事件属性,直接赋于事件响应后的代码;
- 动态注册:先通过 JS 代码得到标签的 dom 对象,然后再通过 dom 对象.事件名 = function(){} 这种形式赋于事件响应后的代码。
- onload 事件
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">function onloadEvent() {alert("静态注册onload事件,需要在body标签下添加 οnlοad='onloadEvent' ")}window.onload = function () {alert("动态注册onload事件")}</script>
</head>
<body></body>
</html>
- onclick 事件
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">function onclickEvent() {alert("静态注册onclick事件")}window.onload = function () {<!-- document对象表示整个页面,通过id获取button对象 -->var btn2_obj = document.getElementById("btn2");<!-- 绑定行为 -->btn2_obj.onclick = function () {alert("动态注册onclick事件")}}</script>
</head>
<body><button onclick="onclickEvent()">静态注册的按钮</button><button id="btn2">动态注册的按钮</button>
</body>
</html>
- onblur 事件(同上)
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">function onblurEvent() {console.log("静态失去焦点");}window.onload = function () {var pw = document.getElementById("p");pw.onblur = function () {console.log("动态失去焦点");}}</script>
</head>
<body>账户:<input type="text" onblur="onblurEvent()"/>密码:<input type="password" id="p"/>
</body>
</html>
- onchange 事件,绑定的是 select 而非 option 标签
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">function onchangeEvent() {alert("静态注册onchange");}window.onload = function () {var select2 = document.getElementById("s");select2.onchange = function () {alert("动态注册onchange")}}</script>
</head>
<body>选择1:<select onchange="onchangeEvent()"><option>选项11</option><option>选项12</option></select>选择2:<select id="s"><option>选项21</option><option>选项22</option></select>
</body>
</html>
- onsubmit 事件,绑定的是表格而非按钮,返回 false 则不会提交表单
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">function onsubmitEvent() {alert("静态注册onsubmit");<!--如果发现不合法,返回false,阻止提交-->return false;}window.onload = function () {var form2 = document.getElementById("f");form2.onsubmit = function () {alert("动态注册onsubmit");<!--如果发现不合法,返回false,阻止提交-->return false;}}</script>
</head>
<body><form action="http://localhost:8080" onsubmit="return onsubmitEvent()"> <!--静态注册 return 不能少--><input type="submit" value="静态注册提交"></form><form action="http://localhost:8080" id="f"><input type="submit" value="动态注册提交"></form>
</body>
</html>
8 DOM (Document Object Model)
- 简单来说,将整个 HTML 文件视为一个 document 对象,并把其中所有的标签对象化,形成了树型结构。
- document 实例提供查询方法,使用优先级从高到低:getElementByID(返回一个实例),getElementByName(可以数组形式返回多个实例),getElementByTagName(以数组形式返回指定标签的对象)。注意:页面加载完之后才能进行查询!不仅限于放在 onload 事件中。
- document 实例提供创建方法:createElement
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript">window.onload = function () {var new_div = document.createElement("div");new_div.innerHTML = "你好";document.body.appendChild(new_div); <!-- 在加载完后执行 -->}</script>
</head>
<body></body>
</html>
9 DOM实例:验证用户名是否有效
限制用户名由数字和字母组成,长度区间为 [5, 12]
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script>window.onload = function () {var tip = document.getElementById("tip");var btn = document.getElementById("btn");var text = document.getElementById("text");btn.onclick = function () {var input = text.value;var pattern = /^\w{5,12}$/;if (pattern.test(input)) { <!-- 匹配正则表达式 -->tip.innerHTML = "通过"; <!-- innerHTML 返回的是开始和结束标签之间的值,可读写-->} else {tip.innerHTML = "不通过";}}}</script>
</head>
<body>
<input type="text" id="text">
<span id="tip" style="color: brown"></span>
<button id="btn">检查</button>
</body>
</html>
四 Tomcat
- 简单来说,Tomcat 就是一个运行JAVA的网络服务器。
- HTML 文件通过本地访问,使用的协议是 file,执行的操作是直接获取并解析;
- IDEA 2021.3.1 创建JavaWeb工程的方法
- 通过WEB服务器访问,使用的协议是 http,经过了客户端请求与服务器响应的过程,执行操作如下图:
五 Servlet
1 举例:向数据库中添加表单信息
- 客户端请求表单页面 add.html(表单指定 action=“add”(可以自定义) method=“post”),服务器返回。
- 客户端填写表单,使用 HttpRequest 实例提交给服务器。
- 服务器的具有一个类 ,继承自 HttpServlet 类,其中的 doPost() 方法定义了 post 请求的处理与返回过程,调用 DAO 将表单信息写入数据库。
//@WebServlet("/add")
public class AddServlet extends HttpServlet {@Overridepublic void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// method=post 设置编码方式防止乱码,且在获取参数的操作之前(放在首行)request.setCharacterEncoding("UTF-8");String name = request.getParameter("name");System.out.println(name);// 调用DAO// ...}
}
- 如何指定 action=“add” 与自定义类 AddServlet 的绑定?
一种方式是使用 web.xml 配置(另一种是通过在 AddServlet 注释 @WebServlet(“/add”) ):
<!--指定servlet信息--><servlet><servlet-name>AddServlet</servlet-name><servlet-class>com.atguigu.servlets.AddServlet</servlet-class></servlet><!--设定servlet映射--><servlet-mapping><servlet-name>AddServlet</servlet-name><url-pattern>/add</url-pattern> <!--action="add"--></servlet-mapping>
2 Servlet 的继承关系
- 继承关系: HttpServlet 具体实现类 -> GenericServlet 抽象类 -> Servlet 接口
- Servlet中的核心方法:service()
- 当收到请求,service 方法会自动响应(tomcat 容器调用),在 HttpServlet 中分析请求的方式:到底是get、post、head 等等,然后再决定调用具体的 doXX 的方法。
- 在 HttpServlet 中,doXX 方法默认都是405(报错信息是找不到方法实现),除非子类去实现对应的 doXX 方法,否则默认会报405错误。
3 Servlet 的生命周期
- 三个重要的方法:init、service、destory
- 默认情况下, 第一次接收请求时,Servlet 会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service());从第二次请求开始,每一次都是服务(调用service());当容器关闭时,其中的所有的 Servlet 实例会被销毁(调用destroy())。
- 如果需要提高响应速度,应该设置 Servlet 的初始化时机,在第一次请求之前执行实例化和初始化。具体做法是,在 web.xml 中的 <servlet> 中添加 <load-on-startup> 标签。
- Servlet 是单例的、线程不安全的。单例是指,对于一个确定的 Servlet 类型,所有的请求都是同一个 Servlet 实例去响应;由于 Servlet 线程不安全,所以尽量避免在其中设置成员变量,如果设置尽量不要读写。
4 HTTP 协议与 Session会话跟踪
- HTTP 协议是无状态的,需要通过会话跟踪技术保存用户的历史信息。
- HTTP 请求与响应报文的格式略。
- 常用的API:
request.getSession()
-> 获取当前的会话,没有则创建一个新的会话
request.getSession(true)
-> 效果和不带参数相同
request.getSession(false)
-> 获取当前会话,没有则返回null,不会创建新的
session.getId()
-> 获取sessionID
session.isNew()
-> 判断当前session是否是新的
session.getMaxInactiveInterval()
-> session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval()
session.invalidate()
-> 强制让会话立即失效
5 服务器端内部转发、重定向
-
服务器端内部转发对客户端是不可见的,执行方法:
request.getRequestDispatcher("...").forward(request, response)
-
重定向对客户端可见,并且会显示地更改 URL,执行方法:
response.sendRedirect("...")
6 Servlet 保存作用域
- request:一次请求响应内有效。
- session:一次会话内有效。
- application:一次应用内有效,除非关闭服务器。
public class Demo extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// request 级别req.setAttribute("property1", 1);// session 级别HttpSession session = req.getSession();session.setAttribute("property2", 2);// application 级别ServletContext servletContext = req.getServletContext();servletContext.setAttribute("property3", 3);}
}
7 Servlet 的 init 方法与初始化参数设置
- 两种 init 方法:
如果需要进行一些初始化操作,重写无参的 init 方法
// Servlet中的初始化方法有两个:init() , init(config)// 其中带参数的方法代码如下:public void init(ServletConfig config) throws ServletException {this.config = config ;init(); // *调用无参的init*}// 另外一个无参的init方法如下:public void init() throws ServletException{}
- 初始化参数设置
1. 通过 web.xml 配置<servlet><servlet-name>Demo01Servlet</servlet-name><servlet-class>com.atguigu.servlet.Demo01Servlet</servlet-class><!--设置参数--><init-param><param-name>hello</param-name><param-value>world</param-value></init-param></servlet>2. 通过注解
@Webservlet(urlPatterns = {"/demo01"} ,initParams = {@WebInitParam(name="hello",value="world")})
- 读取初始化参数:
ServletConfig config = getServletConfig(); // 获取config对象
config.getInitParameter("hello"); // 读取初始化的值
8 ServletContext
- ServletContext是一个全局的储存信息的空间,服务器开始就存在,服务器关闭才释放(application级别)
- 类似 session,可以想象成一个 map
- 多个Servlet可以通过ServletContext对象来实现数据间的共享,如果是涉及到不同用户共享数据,而这些数据量不大,同时又不希望写入数据库中,我们就可以考虑使用 ServletContext 实现
- 初始化 ServletContext 时,不能写在具体的某个 servlet 标签中,因为它是全局的
<!--写在servlet之外-->
<context-param><param-name>name</param-name><param-value>gavin</param-value>
</context-param><servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>com.gavin.servlet.MyServlet</servlet-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param>
</servlet>
六 Thymeleaf
1 配置过程
- 添加依赖, maven 中添加:
<dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf</artifactId><version>3.0.14.RELEASE</version></dependency>
- 新建一个类 ViewBaseServlet 继承 HttpServlet
public class ViewBaseServlet extends HttpServlet {private TemplateEngine templateEngine;@Overridepublic void init() throws ServletException {// 1.获取ServletContext对象ServletContext servletContext = this.getServletContext();// 2.创建Thymeleaf解析器对象ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);// 3.给解析器对象设置参数// ①HTML是默认模式,明确设置是为了代码更容易理解templateResolver.setTemplateMode(TemplateMode.HTML);// ②设置前缀String viewPrefix = servletContext.getInitParameter("view-prefix");templateResolver.setPrefix(viewPrefix);// ③设置后缀String viewSuffix = servletContext.getInitParameter("view-suffix");templateResolver.setSuffix(viewSuffix);// ④设置缓存过期时间(毫秒)templateResolver.setCacheTTLMs(60000L);// ⑤设置是否缓存templateResolver.setCacheable(true);// ⑥设置服务器端编码方式templateResolver.setCharacterEncoding("utf-8");// 4.创建模板引擎对象templateEngine = new TemplateEngine();// 5.给模板引擎对象设置模板解析器templateEngine.setTemplateResolver(templateResolver);}protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {// 1.设置响应体内容类型和字符集resp.setContentType("text/html;charset=UTF-8");// 2.创建WebContext对象WebContext webContext = new WebContext(req, resp, getServletContext());// 3.处理模板数据templateEngine.process(templateName, webContext, resp.getWriter());}
}
- 在 web.xml 配置前缀和后缀,根据逻辑视图名称得到物理视图名称。
逻辑视图名称 : index
物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
真实的视图名称: / index .html
<context-param><param-name>view-prefix</param-name><param-value>/</param-value></context-param><context-param><param-name>view-suffix</param-name><param-value>.html</param-value></context-param>
- 创建自定义的 Servlet 类,继承 ViewBaseServlet,调用
super.processTemplate("index",request,response);
定位到指定 HTML 页面。 - 对于跳转到的 HTML 页面,需要显示查询内容。设置
<html lang="en" xmlns:th="http://www.thymeleaf.org">
,使用 thymeleaf 的标签完成操作。
2 Servlet 优化 - 合并同类方法
- 之前的一个 Servlet 只提供一种方法,此处的改进是将同类的多种方法合并到一个 Servlet 中
- 使用反射代替 switch-case
@WebServlet("/fruit.do")
public class FruitServlet extends ViewBaseServlet {private final FruitDAO fruitDAO = new FruitDAOImpl();/*** 设置一个Fruit 的 meta servlet*/@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {req.setCharacterEncoding("UTF-8");String operation = (String) req.getAttribute("operation");// 简化了switch-caseMethod[] methods = this.getClass().getDeclaredMethods();for (Method method: methods) {method.setAccessible(true);if (operation.equals(method.getName())) {try {method.invoke(this, req, resp);return ;} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}throw new IOException("输入操作错误"); // 对应default}private void insertFruit(HttpServletRequest request, HttpServletResponse response) {// ...}private void selectFruit(HttpServletRequest request, HttpServletResponse response) {// ...}
}
3 Servlet 优化 - dispatchedServlet
- 相当于一个 meta 的 Servlet,称为 DispatchedServlet,继承自 ViewBaseServlet。合并所有种类的 Servlet,并将原来的 Servlet 转为 Controller,剥夺其注释 @WebServlet
- 在 src 目录下创建 applicationContext.xml,利用 bean 标签,为参数名和 Controller 建立映射
- DispatchedServlet 的 init 方法根据 xml 配置
Map<String,Object> beanMap
<?xml version="1.0" encoding="utf-8"?><beans><!-- 这个bean标签的作用是 将来servletpath中涉及的名字对应的是fruit,那么就要FruitController这个类来处理 --><bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"/>
</beans>
- 使用时,向 DispatchedServlet 传递参数,由其 service 方法负责解析,并根据 beanMap 调用指定的 Controller,具体步骤:
- 获取参数:获取即将要调用的方法的参数签名信息
- 执行方法:
Object returnObj = method.invoke(controllerBean , parameterValues);
- 视图处理:根据返回值调用重定向等
String returnStr = (String)returnObj; if(returnStr.startWith("redirect:")){ .... } else if.....
七 MVC
1 概念
MVC(Model–View–Controller)
模式是软件工程中的一种软件架构模式,它把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。- Controller 作为 Model 和 View 部分的“胶水”,本身不适合包含太多的逻辑。
2 降低各层间的耦合 - IOC 与 DI
- DI(Dependency Injection,依赖注入)是 IOC(Inversion of Control,控制反转)最常用的方法。
- IOC 是一种设计思想,核心是,将设计好的对象交给容器控制,而不是传统的在对象内部直接控制。 把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散的耦合。
- Spring 所倡导的开发方式就是如此,所有的类都会在 spring 容器中登记,告诉 spring 你是个什么东西,你需要什么东西,然后 spring 会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring 容器来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 spring 控制,所以这叫控制反转。
- IOC 容器实际上就是 map(key,value),里面存的是各种对象(在 xml 里配置的 bean 节点、service、controller、component),在项目启动的时候会读取配置文件里面的 bean 节点,根据全限定类名使用反射创建对象并放到 map 里。
通过 IOC 容器,上述过程转为:
- IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过 DI 来实现的。在类中需要使用到的对象,全部通过反射从第三方容器注入而不是自己创建。
- 比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个 Connection 对象,有了 spring 只需要告诉 spring ,A中需要一个 Connection,至于这个 Connection 怎么构造,何时构造,A不需要知道。在系统运行时,spring 会在适当的时候制造一个 Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection 才能正常运行,而这个 Connection 是由 spring 注入到A中的,依赖注入的名字就这么来的。
3 IOC 与 DI 的实现过程
- 对于
Servlet - Controller - Service - DAO
的结构,首先将用到的类,及其依赖关系,注册到applicationContext.xml
中。
<?xml version="1.0" encoding="utf-8"?><beans><!-- FruitDAO不依赖于其它组件 --><bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/><!-- FruitService依赖于FruitDAO(FruitService类包含FruitDAO属性) --><bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl"><!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值--><property name="fruitDAO" ref="fruitDAO"/></bean><!-- FruitController依赖于FruitService(FruitController类包含FruitService属性) --><bean id="fruit" class="com.atguigu.fruit.controllers.FruitController"><property name="fruitService" ref="fruitService"/></bean><!-- FruitServlet中具有 包含所有对象的 map,以BeanFactory作为 map 的包装 -->
</beans>
- 使用
BeanFactory
读取 xml 配置,填充 map,并设置依赖关系(比如 A 以 B 作为属性,执行此过程前将属性值设置为 null,在此过程将 A 的对应属性设置为 B 的实例,完成依赖注入)
BeanFactory 实现:
public class ClassPathXmlApplicationContext implements BeanFactory {private Map<String,Object> beanMap = new HashMap<>();public ClassPathXmlApplicationContext(){try {InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");//1.创建DocumentBuilderFactoryDocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();//2.创建DocumentBuilder对象DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;//3.创建Document对象Document document = documentBuilder.parse(inputStream);//4.获取所有的bean节点NodeList beanNodeList = document.getElementsByTagName("bean");for(int i = 0 ; i<beanNodeList.getLength() ; i++){Node beanNode = beanNodeList.item(i);if(beanNode.getNodeType() == Node.ELEMENT_NODE){Element beanElement = (Element)beanNode ;String beanId = beanElement.getAttribute("id");String className = beanElement.getAttribute("class");Class beanClass = Class.forName(className);//创建bean实例Object beanObj = beanClass.newInstance() ;//将bean实例对象保存到map容器中beanMap.put(beanId , beanObj) ;//到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置}}//5.组装bean之间的依赖关系for(int i = 0 ; i<beanNodeList.getLength() ; i++){Node beanNode = beanNodeList.item(i);if(beanNode.getNodeType() == Node.ELEMENT_NODE) {Element beanElement = (Element) beanNode;String beanId = beanElement.getAttribute("id");NodeList beanChildNodeList = beanElement.getChildNodes();for (int j = 0; j < beanChildNodeList.getLength() ; j++) {Node beanChildNode = beanChildNodeList.item(j);if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){Element propertyElement = (Element) beanChildNode;String propertyName = propertyElement.getAttribute("name");String propertyRef = propertyElement.getAttribute("ref");//1) 找到propertyRef对应的实例Object refObj = beanMap.get(propertyRef);//2) 将refObj设置到当前bean对应的实例的property属性上去Object beanObj = beanMap.get(beanId);Class beanClazz = beanObj.getClass();Field propertyField = beanClazz.getDeclaredField(propertyName);propertyField.setAccessible(true);propertyField.set(beanObj,refObj);}}}}} catch (ParserConfigurationException e) {e.printStackTrace();} catch (SAXException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}}@Overridepublic Object getBean(String id) {return beanMap.get(id);}
}
- 重写 Servlet 的 无参 init 方法和 service 方法。其中 init 方法负责获取 BeanMap,service 方法负责解析方法调用,执行方法调用,处理返回值。
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet{private BeanFactory beanFactory ;public DispatcherServlet(){}public void init() throws ServletException {super.init();beanFactory = new ClassPathXmlApplicationContext();}@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//设置编码request.setCharacterEncoding("UTF-8");//假设url是: http://localhost:8080/pro15/hello.do//那么servletPath是: /hello.do// 我的思路是:// 第1步: /hello.do -> hello 或者 /fruit.do -> fruit// 第2步: hello -> HelloController 或者 fruit -> FruitControllerString servletPath = request.getServletPath();servletPath = servletPath.substring(1);int lastDotIndex = servletPath.lastIndexOf(".do") ;servletPath = servletPath.substring(0,lastDotIndex);Object controllerBeanObj = beanFactory.getBean(servletPath);String operate = request.getParameter("operate");if(StringUtil.isEmpty(operate)){operate = "index" ;}try {Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();for(Method method : methods){if(operate.equals(method.getName())){//1.统一获取请求参数//1-1.获取当前方法的参数,返回参数数组Parameter[] parameters = method.getParameters();//1-2.parameterValues 用来承载参数的值Object[] parameterValues = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {Parameter parameter = parameters[i];String parameterName = parameter.getName() ;//如果参数名是request,response,session 那么就不是通过请求中获取参数的方式了if("request".equals(parameterName)){parameterValues[i] = request ;}else if("response".equals(parameterName)){parameterValues[i] = response ;}else if("session".equals(parameterName)){parameterValues[i] = request.getSession() ;}else{//从请求中获取参数值String parameterValue = request.getParameter(parameterName);String typeName = parameter.getType().getName();Object parameterObj = parameterValue ;if(parameterObj!=null) {if ("java.lang.Integer".equals(typeName)) {parameterObj = Integer.parseInt(parameterValue);}}parameterValues[i] = parameterObj ;}}//2.controller组件中的方法调用method.setAccessible(true);Object returnObj = method.invoke(controllerBeanObj,parameterValues);//3.视图处理String methodReturnStr = (String)returnObj ;if(methodReturnStr.startsWith("redirect:")){ //比如: redirect:fruit.doString redirectStr = methodReturnStr.substring("redirect:".length());response.sendRedirect(redirectStr);}else{super.processTemplate(methodReturnStr,request,response); // 比如: "edit"}}}} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}
}
八 Filter
1 概述
- 继承自 Filter 类,具有 init, doFilter, destory 方法
- 可以使用注解的形式
@WebFilter("fruit.do")
,也可以在 web.xml 中配置 - 使用注解形式,当具有多个 Filter 时,按照类名的字典序组织过滤顺序
@WebFilter("*.do")
public class Demo01Filter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("before");//放行filterChain.doFilter(servletRequest,servletResponse);System.out.println("after");}@Overridepublic void destroy() {}
}
2 使用 Filter 实现事务的原子性
- 利用 Filter 的机制,在放行之前撤销 conn 的自动提交,在返回时手动执行提交,并在捕获到异常时执行回滚
- 要求事务执行的过程中,遇到的异常要层层向外抛出,直到被 Filter 捕获,而不是就地解决
- 将
conn
绑定ThreadLocal
实例,再进一步保存到ThreadLocalMap
中,保证了多个 DAO 共用同一个数据库连接,不会因为连接的关闭而自动提交,破坏事务的原子性
九 Listener
- 监听某个组件的某种行为,当这种行为被监听到时,调用指定的方法
- 可以用于完成初始化的创建,以及结束时的销毁操作
- 一个应用实例:IOC 容器的
beanMap
,当监听到ServletContext
对象创建时,在监听器内进行初始化,而非在Servlet
的init
方法中完成
// 监听上下文启动,在上下文启动的时候去创建IOC容器,然后将其保存到 application 作用域
// 后面DispatchedServlet 再从 application 作用域中去获取IOC容器@WebListener
public class ContextLoaderListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent servletContextEvent) {//1.获取ServletContext对象ServletContext application = servletContextEvent.getServletContext();//2.获取上下文的初始化参数(bean类型以及依赖关系)String path = application.getInitParameter("contextConfigLocation");//3.创建IOC容器BeanFactory beanFactory = new ClassPathXmlApplicationContext(path);//4.将IOC容器保存到application作用域application.setAttribute("beanFactory", beanFactory);}@Overridepublic void contextDestroyed(ServletContextEvent servletContextEvent) {// ...}
}