JAVA JAVA JavaWeb Nuyoah 2023-01-22 2023-02-20 HTML
frameset-iframe
frameset
作用:如果一个网页中包含很多个子网页,我们可以使用frameset来进行区分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <html > <head > </head > <frameset row ="20%,*" > <frame src ="top.html" /> <frameset clos ="15%,*" > <frame src ="left.html" /> <frameset row ="80%, *" > <frame src ="main.html" /> <frame src ="bottom.html" /> </frameset > </frameset > </frameset > </html >
iframe
在页面中嵌入一个页面
1 2 3 4 5 6 <html > <head > </head > <body > "页面"<iframe src ="top.html" /> </body > </html >
CSS
盒子模型
IE 浏览器:实际尺寸 = width
chrome浏览器:实际尺寸 = width+左右borderwidth + padding
css盒子模型:
border 边框
margin 间距
padding 填充
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <html > <head > <style type ="text/css" > #div1 { width :400px ; height :400px ; background-color :green; border-width :4px ; border-style :dotted; border-color :blue; } </style > </head > <body > <div id ="div1" > </div > </body > </html >
CSS 布局
absolute和relative都会使元素脱离文档流,但不同的是,absolute脱离文档流后不会占用原来的位置,而relative会在原来的位置上留下一个副本占用原来文档流的位置
absolute的父元素设有position时 ,top,left,bottom,right会忽略父级元素 的padding值,即始终与父级元素的左上角进行定位 ,且其层级会始终比父级高,无论父级设置多大的z-index,但relative的定位会受父元素padding值影响
absolute是以第一个设置了position的父元素或祖先元素进行定位 ,而relative定位的层总是相对于其最近的父元素 ,无论其父元素是何种定位方式
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 <html > <head > <style type ="text/css" > #div1 { width :200px ; height :200px ; background-color :green; position :absolute; left :100px ; top :100px ; } #div2 { width :200px ; height :200px ; background-color :pink; position :relative; top :100px ; } </style > </head > <body > <div id ="div1" > </div > <div id ="div2" > </div > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <style type ="text/css" > body { margin : 0 0 ; height : 100% ; width : 100% ; position : absolute; } div { position :relative; } #div_top { height :20% ; width : 100% ; background-color :orange; } #div_left { height :80% ; width : 20% ; float : left; background-color :green; } #div_main { background-color :yellow; height :70% ; width : 80% ; float : left; } #div_bottom { background-color :pink; height :10% ; float : left; width : 80% ; } </style > </head > <body > <div id ="div_top" > </div > <div id ="div_left" > </div > <div id ="div_main" > </div > <div id ="div_bottom" > </div > </body > </html >
水果库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <style type ="text/css" > body { margin :0 ; padding : 0 ; background-color : cadetblue; } #div_container { height :100% ; width :80% ; background-color : antiquewhite; position : absolute; margin-left : 10% ; } #tbl_fruit { border-style : solid; width : 60% ; height : 60% ; position : absolute; border-collapse : collapse; margin-top : 10% ; margin-left : 20% ; } #tbl_fruit tr , #tbl_fruit th ,#tbl_fruit td { border :1px solid; width : 20% ; text-align : center; } .dekImg { width : 20px ; height : 20px ; } </style > </head > <body > <div id ="div_container" > <div id ="div_furit_list" > <table id ="tbl_fruit" > <tr > <th > 名称</th > <th > 单价</th > <th > 数量</th > <th > 小计</th > <th > 操作</th > </tr > <tr > <td > 苹果</td > <td > 5</td > <td > 50</td > <td > 250</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 梨</td > <td > 6</td > <td > 60</td > <td > 360</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 香蕉</td > <td > 7</td > <td > 70</td > <td > 490</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 菠萝</td > <td > 8</td > <td > 80</td > <td > 640</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 总计</td > <td colspan =4 > 300</td > </tr > </table > </div > </div > </body > </html >
JS
javascript :客户端的一个脚本语言
js是一门弱类型的语言,变量的数据类型由后面赋的值的类型决定
JavaScript的起源
在1995 年时,由Netscape 公司的Brendan Eich ,在网景导航者浏览器上首次设计实现而成。Netscape在最初将其脚本语言命名为LiveScript,因为Netscape与Sun合作,网景公司管理层希望蹭Java的热度,因此取名为JavaScript。
JavaScript总共分成三部分: ECMAScript(基本语法)、BOM(浏览器对象模型)、DOM(文档对象模型)
JavaScript的特性
脚本语言
JavaScript是一种解释型的脚本语言。不同于C、C++、Java等语言先编译后执行, JavaScript不会产生编译出来的字节码文件,而是在程序的运行过程中对源文件逐行进行解释。
基于对象
JavaScript是一种基于对象的脚本语言,它不仅可以创建对象,也能使用现有的对象。但是面向对象的三大特性:『封装』、『继承』、『多态』中,JavaScript能够实现封装,可以模拟继承,不支持多态,所以它不是一门面向对象的编程语言。
弱类型
JavaScript中也有明确的数据类型,但是声明一个变量后它可以接收任何类型的数据,并且会在程序执行过程中根据上下文自动转换类型。
事件驱动
JavaScript是一种采用事件驱动的脚本语言,它不需要经过Web服务器就可以对用户的输入做出响应。
跨平台性
JavaScript脚本语言不依赖于操作系统,仅需要浏览器的支持。因此一个JavaScript脚本在编写后可以带到任意机器上使用,前提是机器上的浏览器支持JavaScript脚本语言。目前JavaScript已被大多数的浏览器所支持。
入门程序
功能效果图
代码实现
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > JS的入门程序</title > </head > <body > <button type ="button" id ="helloBtn" > SayHello</button > <script type ="text/javascript" > var btn = document .getElementById ("helloBtn" ); btn.onclick = function ( ) { alert ("hello world" ) } </script > </body > </html >
JavaScript的基本语法
JavaScript的引入方式
内部脚本方式
JavaScript代码要写在script标签内
script标签可以写在文档内的任意位置
为了能够方便查询或操作HTML标签(元素)script标签可以写在body标签后面
在我们的入门程序中使用的就是内部脚本方式引入的JavaScript
外部脚本方式
在script标签内通过src属性指定外部xxx.js文件的路径即可。但是要注意以下两点:
引用外部JavaScript文件的script标签里面不能写JavaScript代码
先引入,再使用
script标签不能写成单标签
引入方式如下:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > JS的引入方式</title > <script src ="../js/outer.js" > </script > </head > <body > <script type ="text/javascript" > showMessage () alert ("hello world" ) </script > </body > </html >
声明和使用变量
JavaScript数据类型
基本数据类型
数值型number:JavaScript不区分整数、小数
字符串string:JavaScript不区分字符、字符串;单引号、双引号意思一样。
布尔型boolean:true、false
在JavaScript中,其他类型和布尔类型的自动转换。
true:非零的数值,非空字符串,非空对象
false:零,空字符串,null,undefined
例如:"false"放在if判断中
1 2 3 4 5 6 if ("false" ){ alert ("true" ); }else { alert ("false" ); }
引用类型
所有new出来的对象
用[]声明的数组
用{}声明的对象
变量
函数(重点)
内置函数
内置函数就是JavaScript中内置好的函数,我们可以直接使用
1 2 3 4 var result = confirm ("确定要删除吗?" );if (result) { }
用户点击『确定』返回true,点击『取消』返回false
1 2 3 4 5 6 var result = confirm ("老板,你真的不加个钟吗?" );if (result) { console .log ("老板点了确定,表示要加钟" ); }else { console .log ("老板点了确定,表示不加钟" ); }
声明函数
声明函数就是使用者自己定义一个函数,它有两种写法:
写法1:
1 2 3 function sum (a, b ) { return a+b; }
写法2:
1 2 3 var total = function ( ) { return a+b; };
写法2可以这样解读:声明一个函数,相当于创建了一个『函数对象』,将这个对象的『引用』赋值给变量total。如果不给这个对象赋值,我们可以将其作为匿名函数使用(在后续学习内容中会用到)
调用函数
JavaScript中函数本身就是一种对象,函数名就是这个『对象』 的『引用』 。而调用函数的格式是:函数引用() 。
1 2 3 4 5 6 function sum (a, b ) { return a+b; } var result = sum (2 , 3 );console .log ("result=" +result);
或:
1 2 3 4 5 6 var total = function ( ) { return a+b; } var totalResult = total (3 ,6 );console .log ("totalResult=" +totalResult);
对象(重点)
JavaScript中没有『类』的概念,对于系统内置的对象可以直接创建使用。
使用new关键字创建对象
1 2 3 4 5 6 7 8 9 10 var obj01 = new Object ();obj01.stuName = "tom" ; obj01.stuAge = 20 ; obj01.stuSubject = "java" ; console .log (obj01);
使用{}创建对象(常用)
1 2 3 4 5 6 7 8 9 var obj02 = { "soldierName" :"john" , "soldierAge" :35 , "soldierWeapon" :"gun" }; console .log (obj02);
给对象设置函数属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var obj01 = new Object ();obj01.stuName = "tom" ; obj01.stuAge = 20 ; obj01.stuSubject = "java" ; obj01.study = function ( ) { console .log (this .stuName + " is studying" ); }; console .log (obj01);obj01.study ();
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var obj02 = { "soldierName" :"john" , "soldierAge" :35 , "soldierWeapon" :"gun" , "soldierShoot" :function ( ){ console .log (this .soldierName + " is using " + this .soldierWeapon ); } }; console .log (obj02);obj02.soldierShoot ();
this关键字
this关键字只有两种情况:
在函数外面:this关键字指向window对象(代表当前浏览器窗口)
在函数里面:this关键字指向调用函数的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 console .log (this );function getName ( ) { console .log (this .name ); } var obj01 = { "name" :"tom" , "getName" :getName }; var obj02 = { "name" :"jerry" , "getName" :getName }; obj01.getName (); obj02.getName ();
数组(重点)
使用new关键字创建数组
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 var arr01 = new Array ();arr01.push ("apple" ); arr01.push ("orange" ); arr01.push ("banana" ); arr01.push ("grape" ); for (var i = 0 ; i < arr01.length ; i++) { console .log (arr01[i]); } arr01.reverse (); for (var i = 0 ; i < arr01.length ; i++) { console .log (arr01[i]); } var arrStr = arr01.join ("," );console .log (arrStr);var arr02 = arrStr.split ("," );for (var i = 0 ; i < arr02.length ; i++) { console .log (arr02[i]); } var ele = arr01.pop ();console .log (ele);
使用[]创建数组(常用)
1 2 3 var arr03 = ["cat" ,"dog" ,"tiger" ];console .log (arr03);
JSON(最重点)
JSON格式的用途
在开发中凡是涉及到『跨平台数据传输』 ,JSON格式一定是首选。
JSON格式的说明
JSON数据两端要么是{} ,要么是[]
{} 定义JSON对象
[] 定义JSON数组
JSON对象的格式是:
1 { key: value, key: value, ..., key: value}
key的类型固定是字符串
value的类型可以是:
基本数据类型
引用类型:JSON对象或JSON数组
正因为JSON格式中value部分还可以继续使用JSON对象或JSON数组,所以JSON格式是可以『多层嵌套』 的,所以JSON格式不论多么复杂的数据类型都可以表达。
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 var person1 = { "name" : "张三疯" , "age" : 189 , "address" : "武当山" } var person2 = { "name" : "张三疯" , "age" : 189 , "address" : "武当山" , "wife" : { "name" : "小花" , "age" : 18 , "address" : "武当山下的小村庄" } } var person3 = { "name" : "张三疯" , "age" : 189 , "address" : "武当山" , "wife" : { "name" : "小花" , "age" : 18 , "address" : "武当山下的小村庄" } , "sons" : [ { "name" : "奥巴马" , "age" : 1 , "address" : "武当山" } , { "name" : "奥拉夫" , "age" : 2 , "address" : "少林寺" } ] }
JSON对象和JSON字符串互转
1 2 3 4 5 var jsonObj = {"stuName" :"tom" ,"stuAge" :20 };var jsonStr = JSON .stringify (jsonObj);console .log (typeof jsonObj); console .log (typeof jsonStr);
1 2 jsonObj = JSON .parse (jsonStr); console .log (jsonObj);
JavaScript的DOM(最重点)
DOM的概念
DOM是D ocument O bject M odel的缩写,意思是『文档对象模型』 ——将HTML文档抽象成模型,再封装成对象方便用程序操作。
这是一种非常常用的编程思想:将现实世界的事物抽象成模型,这样就非常容易使用对象来量化的描述现实事物,从而把生活中的问题转化成一个程序问题,最终实现用应用软件协助解决现实问题。而在这其中『模型』 就是那个连通现实世界和代码世界的桥梁。
DOM树的概念
浏览器把HTML文档从服务器上下载下来之后就开始按照『从上到下』 的顺序『读取HTML标签』 。每一个标签都会被封装成一个『对象』 。
而第一个读取到的肯定是根标签html,然后是它的子标签head,再然后是head标签里的子标签……所以从html标签开始,整个文档中的所有标签都会根据它们之间的『父子关系』 被放到一个『树形结构』 的对象中。
这个包含了所有标签对象的整个树形结构对象就是JavaScript中的一个可以直接使用的内置对象 :document 。
例如,下面的标签结构:
会被解析为:
各个组成部分的类型
整个文档中的一切都可以看做Node。各个具体组成部分的具体类型可以看做Node类型的子类。
其实严格来说,JavaScript并不支持真正意义上的『继承』,这里我们借用Java中的『继承』概念,从逻辑上来帮助我们理解各个类型之间的关系。
组成部分
节点类型
具体类型
整个文档
文档节点
Document
HTML标签
元素节点
Element
HTML标签内的文本
文本节点
Text
HTML标签内的属性
属性节点
Attr
注释
注释节点
Comment
父子关系
先辈后代关系
DOM操作
由于实际开发时基本上都是使用JavaScript的各种框架来操作,而框架中的操作方式和我们现在看到的原生操作完全不同,所以下面罗列的API仅供参考,不做要求。
在整个文档范围内查询元素节点
功能
API
返回值
根据id值查询
document.getElementById(“id值”)
一个具体的元素节
根据标签名查询
document.getElementsByTagName(“标签名”)
元素节点数组
根据name属性值查询
document.getElementsByName(“name值”)
元素节点数组
根据类名查询
document.getElementsByClassName(“类名”)
元素节点数组
在具体元素节点范围内查找子节点
功能
API
返回值
查找子标签
element.children
返回子标签数组
查找第一个子标签
element.firstElementChild 【W3C考虑换行,IE≤8不考虑】
标签对象
查找最后一个子标签
element.lastElementChild 【W3C考虑换行,IE≤8不考虑】
节点对象
查找指定元素节点的父节点
功能
API
返回值
查找指定元素节点的父标签
element.parentElement
标签对象
查找指定元素节点的兄弟节点
功能
API
返回值
查找前一个兄弟标签
node.previousElementSibling 【W3C考虑换行,IE≤8不考虑】
标签对象
查找后一个兄弟标签
node.nextElementSibling 【W3C考虑换行,IE≤8不考虑】
标签对象
扩展内容(根据选择器查找标签)
功能
API
返回值
根据选择器查找一个标签
document.querySelector(“选择器”)
标签对象
根据选择器查找多个标签
document.querySelectorAll(“选择器”)
标签数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > DOM查找节点</title > </head > <body > <input type ="text" id ="username" > <input type ="text" class ="c1" > <input type ="text" class ="c1" > <div > <div > </div > <div > </div > </div > <script > var elementById = document .getElementById ("username" ); var elementsByClassName = document .getElementsByClassName ("c1" ); var elementsByTagName = document .getElementsByTagName ("input" ); var children = document .getElementsByTagName ("body" )[0 ].children ; var parentNode = document .getElementById ("username" ).parentNode ; var previousElementSibling = document .getElementById ("username" ).previousElementSibling ; var nextElementSibling = document .getElementById ("username" ).nextElementSibling ; var ipt1 = document .querySelector ("#username" ); var ipts = document .querySelectorAll ("body div" ); var i1 = document .querySelector ("#username+input" ); var i2 = document .querySelectorAll ("#username~input" ); console .log (i2) </script > </body > </html >
属性操作
需求
操作方式
读取属性值
元素对象.属性名
修改属性值
元素对象.属性名=新的属性值
标签体的操作
需求
操作方式
获取或者设置标签体的文本内容
element.innerText
获取或者设置标签体的内容
element.innerHTML
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 操作标签的属性和文本</title > </head > <body > <input type ="text" id ="username" name ="username" /> <div id ="d1" > <h1 > 你好世界</h1 > </div > <script > var ipt = document .getElementById ("username" ); ipt.value = "张三" var value = ipt.value ; console .log (value) var innerText = document .getElementById ("d1" ).innerText ; console .log (innerText) var innerHTML = document .getElementById ("d1" ).innerHTML ; console .log (innerHTML) document .getElementById ("d1" ).innerHTML = "<h1>hello world</h1>" </script > </body > </html >
DOM增删改操作
API
功能
document.createElement(“标签名”)
创建元素节点并返回,但不会自动添加到文档中
document.createTextNode(“文本值”)
创建文本节点并返回,但不会自动添加到文档中
element.appendChild(ele)
将ele添加到element所有子节点后面
parentEle.insertBefore(newEle,targetEle)
将newEle插入到targetEle前面
parentEle.replaceChild(newEle, oldEle)
用新节点替换原有的旧子节点
element.remove()
删除某个标签
element.innerHTML
读写HTML代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 创建和删除标签</title > </head > <body > <ul id ="city" > <li id ="bj" > 北京</li > <li id ="sh" > 上海</li > <li id ="sz" > 深圳</li > <li id ="gz" > 广州</li > </ul > <script > var liElement = document .createElement ("li" ); liElement.id = "cs" liElement.innerText = "长沙" var cityUl = document .getElementById ("city" ); var szElement = document .getElementById ("sz" ); cityUl.innerHTML = "" </script > </body > </html >
JavaScript的事件驱动(很重要)
事件的概念
HTML 事件是发生在 HTML 元素上的“事情”, 是浏览器或用户做的某些事情
事件通常与函数配合使用,这样就可以通过发生的事件来驱动函数执行。
常见事件
属性
此事件发生在何时…
onclick
当用户点击某个对象时调用的事件句柄。
ondblclick
当用户双击某个对象时调用的事件句柄。
onchange
域的内容被改变。
onblur
元素失去焦点。
onfocus
元素获得焦点。
onload
一张页面或一幅图像完成加载。
onsubmit
确认按钮被点击;表单被提交。
onkeydown
某个键盘按键被按下。
onkeypress
某个键盘按键被按住。
onkeyup
某个键盘按键被松开。
onmousedown
鼠标按钮被按下。
onmouseup
鼠标按键被松开。
onmouseout
鼠标从某元素移开。
omouseover
鼠标移到某元素之上。
onmousemove
鼠标被移动。
事件绑定的方式
普通函数方式
说白了设置标签的属性
1 <标签 属性 ="js代码,调用函数" > </标签 >
匿名函数方式
1 2 3 4 5 <script > 标签对象.事件属性 = function ( ){ } </script >
事件的使用介绍
点击事件
需求: 没点击一次按钮 弹出hello…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <input type="button" value="按钮" onclick="fn1()" > <input type ="button" value ="另一个按钮" id ="btn" > <script > function fn1 ( ) { alert ("我被点击了..." ) } let btn = document .getElementById ("btn" );btn.onclick = function ( ) { console .log ("点击了另外一个按钮" ) } </script >
1 2 3 4 5 6 7 8 9 10 11 var ipt = document .getElementById ("ipt" );ipt.onfocus = function ( ) { console .log ("获取焦点了..." ) } ipt.onblur = function ( ) { console .log ("失去焦点了..." ) }
内容改变(onchange)
需求: 给select设置内容改变事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <body > <select onchange ="changeCity(this)" > <option value ="bj" > 北京</option > <option value ="sh" > 上海</option > <option value ="sz" > 深圳</option > </select > </body > <script > function changeCity (obj ) { console .log ("城市改变了" +obj.value ); } </script >
键盘相关的, 键盘按下(onkeydown) 键盘抬起(onkeyup)
1 2 3 4 5 6 7 8 9 10 11 ipt.onkeydown = function () { } ipt.onkeyup = function () { console.log(ipt.value) }
鼠标相关的, 鼠标在xx之上(onmouseover ), 鼠标按下(onmousedown),鼠标离开(onmouseout)
1 2 3 4 5 6 7 8 ipt.onmouseover = function () { console.log("鼠标移入了..." ) } ipt.onmouseout = function () { console.log("鼠标移出了..." ) }
综合案例
需求
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 综合案例</title > <style > table ,tr ,td ,th { border : 1px solid black; width : 500px ; text-align : center; margin : auto; } div { text-align : center; } </style > </head > <body > <table cellspacing ="0" id ="tb" > <tr > <th > 序号</th > <th > 用户名</th > <th > 性别</th > <th > 操作</th > </tr > <tr > <td > 1</td > <td > 张三</td > <td > 男</td > <td > <button onclick ="deletePerson(this)" > 删除</button > </td > </tr > <tr > <td > 2</td > <td > 李四</td > <td > 女</td > <td > <button onclick ="deletePerson(this)" > 删除</button > </td > </tr > <tr > <td > 3</td > <td > 王五</td > <td > 男</td > <td > <button onclick ="deletePerson(this)" > 删除</button > </td > </tr > </table > <div > <form action ="#" > 序号<input type ="text" name ="num" id ="num" > <br /> 用户<input type ="text" name ="username" id ="username" /> <br /> 性别<input type ="text" name ="gender" id ="gender" /> <br /> <input type ="button" value ="添加用户" onclick ="addPerson()" /> </form > </div > <script > function deletePerson (obj ) { obj.parentElement .parentElement .remove () } function addPerson ( ) { var numElement = document .getElementById ("num" ); var num = numElement.value ; var usernameElement = document .getElementById ("username" ); var username = usernameElement.value ; var genderElement = document .getElementById ("gender" ); var gender = genderElement.value ; var trElement = document .createElement ("tr" ); trElement.innerHTML = "<td>" +num+"</td>\n" + " <td>" +username+"</td>\n" + " <td>" +gender+"</td>\n" + " <td>\n" + " <button onclick=\"deletePerson(this)\">删除</button>\n" + " </td>" var tb = document .getElementById ("tb" ); tb.appendChild (trElement) numElement.value = "" usernameElement.value = "" genderElement.value = "" } </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script language ="javascript" > var str = "hello world" ; var person = new Object (); person.name = "jntm" ; person.id = 12 ; function hello (num1, num2, num3 ){ if (num1>num2){ return "jntm" ; }else { alter ("jntm" ); } } hello (); hello (1 ); hello (1 ,2 ); hello (1 ,2 ,3 ,4 ); </script > </head > <body > </body > </html >
水果摊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 body { margin :0 ; padding : 0 ; background-color : cadetblue; } #div_container { height :100% ; width :80% ; background-color : antiquewhite; position : absolute; margin-left : 10% ; } #tbl_fruit { border-style : solid; width : 60% ; position : absolute; border-collapse : collapse; margin-top : 10% ; margin-left : 20% ; } #tbl_fruit tr , #tbl_fruit th ,#tbl_fruit td { border :1px solid; width : 20% ; height : 40px ; text-align : center; } .dekImg { width : 20px ; height : 20px ; } #div_add_furit { width : auto; margin-top : 40% ; margin-left :40% ; border :1px solid; float : left; text-align : center; } .button { width : 80px ; height : 24px ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 window .onload =function ( ) { var fruitTbl = document .getElementById ("tbl_fruit" ); var rows = fruitTbl.rows ; for (var i = 1 ; i < rows.length -1 ; i++){ var tr = rows[i]; tr.onmouseover = showBGcolor; tr.onmouseout = clearBGColor; var cells = tr.cells ; var pirceID = cells[1 ]; var delImg = cells[4 ].firstChild ; pirceID.onmouseover = showHand; pirceID.onclick = editPrice; if (delImg && delImg.tagName =="IMG" ){ delImg.onclick = delData; } } var fruitAdd = document .getElementById ("submit" ); fruitAdd.onclick = addFruit; var fruitReset = document .getElementById ("reset" ); fruitReset.onclick = resetFruit; } function resetFruit ( ){ if (event && event.srcElement && event.srcElement .tagName =="INPUT" ){ var name = document .getElementById ("fname" ); var price = document .getElementById ("fprice" ); var number = document .getElementById ("fnumber" ); name.value = null ; price.value =null ; number.value =null ; } } function addFruit ( ){ if (event && event.srcElement && event.srcElement .tagName =="INPUT" ){ var name = document .getElementById ("fname" ); var price = document .getElementById ("fprice" ); var number = document .getElementById ("fnumber" ); if (name.value && price.value && number.value ){ if (isNumber (price.value ) && isNumber (number.value )){ var fruitNum = document .getElementById ("tbl_fruit" ); var tr = fruitNum.insertRow (fruitNum.rows .length -1 ); var fname = tr.insertCell (); fname.innerText = name.value ; var fprice = tr.insertCell (); fprice.innerText = price.value ; var fnumber = tr.insertCell (); fnumber.innerText = number.value ; var fxj = tr.insertCell (); fxj.innerText = parseInt (number.value )*parseInt (price.value ); var fcaozuo = tr.insertCell (); fcaozuo.innerHTML = "<img src='C:\\Users\\Nuyoah\\Downloads\\cha.png' class='dekImg'>" ; tr.onmouseover = showBGcolor; tr.onmouseout = clearBGColor; var cells = tr.cells ; var pirceID = cells[1 ]; var delImg = cells[4 ].firstChild ; pirceID.onmouseover = showHand; pirceID.onclick = editPrice; if (delImg && delImg.tagName =="IMG" ){ delImg.onclick = delData; } updateZJ (); }else { alert ("水果价格或数量格式有误请从新输入" ); } } else { alert ("信息输入不全,请补全之后在提交" ) } } } function showBGcolor ( ){ if (event && event.srcElement && event.srcElement .tagName =="TD" ){ var td = event.srcElement ; var tr = td.parentElement ; tr.style .backgroundColor ="navy" ; var tds = tr.cells ; for (var i = 0 ; i < tds.length ; i++){ tds[i].style .color = "white" ; } } } function clearBGColor ( ) { if (event && event.srcElement && event.srcElement .tagName =="TD" ){ var td = event.srcElement ; var tr = td.parentElement ; tr.style .backgroundColor ="transparent" ; var tds = tr.cells ; for (var i = 0 ; i < tds.length ; i++){ tds[i].style .color = "black" ; } } } function showHand ( ){ if (event && event.srcElement && event.srcElement .tagName =="TD" ){ var td = event.srcElement ; td.style .cursor = "pointer" ; } } function editPrice ( ){ if (event && event.srcElement && event.srcElement .tagName =="TD" ){ var pirceID = event.srcElement ; if (pirceID.firstChild && pirceID.firstChild .nodeType ==3 ){ var oldPrice = pirceID.innerText ; pirceID.innerHTML = "<input type='text' size=4 />" var input = pirceID.firstChild ; if (input.tagName == "INPUT" ) { input.value = oldPrice; input.select (); input.onblur =updatePrice; } } } } function updatePrice (oldPrice ){ if (event && event.srcElement && event.srcElement .tagName =="INPUT" ){ var input = event.srcElement ; var newPrice = input.value ; if (isNumber (newPrice)){ var td = input.parentElement ; td.innerText = newPrice; var tr = td.parentElement ; updateXJ (tr); updateZJ (); }else { alert ("价格格式错误" ); } } } function updateXJ (tr ){ if (tr && tr.tagName =="TR" ){ var tds = tr.cells ; var total = tds[3 ]; total.innerText = parseInt (tds[1 ].innerText ) * parseInt (tds[2 ].innerText ); } } function updateZJ ( ){ var fruitTbl = document .getElementById ("tbl_fruit" ); var trs = fruitTbl.rows ; var num = 0 ; for (var i = 1 ; i < trs.length -1 ; i++){ num += parseInt (trs[i].cells [3 ].innerText ); } trs[trs.length -1 ].cells [1 ].innerText = num; } function delData ( ){ if (event && event.srcElement && event.srcElement .tagName =="IMG" ){ if (window .confirm ("是否确定删除" )){ var img = event.srcElement ; var tr = img.parentElement .parentElement ; var fruitTbl = document .getElementById ("tbl_fruit" ); fruitTbl.deleteRow (tr.rowIndex ); updateZJ (); } } } function isNumber (num ){ if (isNaN (Number (num, 10 ))){ return false ; }else { return true ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <body > <div id ="div_container" > <div id ="div_furit_list" > <table id ="tbl_fruit" > <tr > <th > 名称</th > <th > 单价</th > <th > 数量</th > <th > 小计</th > <th > 操作</th > </tr > <tr > <td > 苹果</td > <td onmouseover ="showHand()" > 5</td > <td > 50</td > <td > 250</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 梨</td > <td > 6</td > <td > 60</td > <td > 360</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 香蕉</td > <td > 7</td > <td > 70</td > <td > 490</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 菠萝</td > <td > 8</td > <td > 80</td > <td > 640</td > <td > <img src ="C:\Users\Nuyoah\Downloads\cha.png" class ="dekImg" > </td > </tr > <tr > <td > 总计</td > <td colspan =4 > 300</td > </tr > </table > </div > <div id ="div_add_furit" > <table id ="tbl_add" > <tr > <td > 名称:</td > <td > <input type ="text" id ="fname" value ="橙子" /> </td > </tr > <tr > <td > 单价:</td > <td > <input type ="text" id ="fprice" value ="8" /> </td > </tr > <tr > <td > 数量:</td > <td > <input type ="text" id ="fnumber" value ="30" /> </td > </tr > <tr > <td colspan ="2" > <input type ="button" class ="button" id ="submit" value ="添加" /> <input type ="button" class ="button" id ="reset" value ="重填" /> </td > </tr > </table > </div > </div > </body >
WEB
CS与BS异同
CS:客户端服务器架构模式
BS:浏览器服务器架构模式
优点:不需要安装,升级成本小
缺点:所有的计算和存储任务都放在服务器端执行,服务器的负荷比较重,在服务器端完成计算之后再将结果传回浏览器端,因此客户端服务器端会进行非常频繁的数据通信,从而网络负荷较重。
TOMCAT
tomcat是本地服务器
目录说明:
bin:存放可执行文件的目录
conf:存放配置文件的目录
lib:存放第三方包的目录
logs:存放日志文件的目录
webapps:项目部署的目录
work:工作目录
temp:临时目录
配置环境变量:TOMCAT是用java和C来写的,需要配置JAVA_HOME
WebAPP最基本的文件夹有两个:1.项目文件夹 2.WEB-INF文件夹
项目执行过程
客户端发起请求 -> 服务器端处理请求
以form表单为例
用户发起请求, action=add
在项目的web.xml文件夹中找到url-pattern = /add
找到add对应的servlet-name的对应的servlet-class
根据用户发送的是post请求还是get请求,从而调用对应java类中的doPost还是doGet方法
模块创建过程
新建空项目
在项目中新建模块
在模块中添加WEB包
创建Artifact-部署包
lib-artifact
现有artifact再有的lib的各种jar包,但是这时候这个jar包并没有添加到部署包中,我们在projectSettings中有Problem会提示,点击Fix选择add to…即可,另外我们也可以直接把lib文件夹新建到WEB-INF下,这样的话这个lib只能是当前这个模块所享有,如果我们需要新建模块的话则需要再重新新建包
在部署的时候,修改applicationContext,然后再回到server选项卡,检查url的值,url的值指的是tomcat启动完成之后自动打开你指定的浏览器,并且访问这个网址,如果我们的网址是:http://localhost:8080/pro-01 则我们默认访问index.html,index.htm,index.jsp,如果没有这个三个资源的话则会报404错误,默认访问页面可以通过< welcome-file-list >中设置
空指针一般是,数据取不到,或者是参数名不对应造成的
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 public class AddServlet extends HttpServlet { @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String fname = req.getParameter("fname" ); String fpriceStr = req.getParameter("fprice" ); int fprice = Integer.parseInt(fpriceStr); String fcountStr = req.getParameter("fcount" ); int fcount = Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); String idStr = req.getParameter("fid" ); int fid = Integer.parseInt(idStr); FruitDAO fruitDAO = new FruitImp (); Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } fruitDAO.insert(connection, new Fruit (fid, fname,fprice, fcount, remark)); } @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } }
中文乱码
POST
通过设置req.setCharacterEncoding(“UTF-8”)即可
GET
tomcat8之后不用设置编码问题
tomcat8之前
先将字符串转换成二进制的形式byte[] bytes = fname.getBytes(“ISO-8859-1”);
将字节数组按照指定的编码重新组装成字符串 fname = new String(bytes, “UTF-8”);
继承关系
继承关系
javax.servlet.Servlet接口
javax.servlet.GenericServlet抽象类
javax.servlet.http.HttpServlet 抽象子类
相关方法
javax.servlet.Servlet接口:
void init(config) -初始化方法
void service(request, response) -服务方法
void destory() - 销毁方法
javax.servlet.GenericServlet抽象类:
void service(request, response) - 仍是抽象方法
javax.servlet.http.HttpServlet 抽象子类
void service(request, response) - 不是抽象的的
String method = req.getMethod(); 获取请求的方式
各种if判断,根据请求方式不同,决定去调用不同的do方法
在HTTPServlet这个抽象类中,do方法都差不多
小结
继承关系HttpServlet - > GenericServlet -> Servlet
Servlet中的核心方法:init(), service(), destory()
service()方法:当有请求过来的时候都要经过这个方法,
在HttpServlet中我们回去分析请求的方式,到底是get,post,head等等
通过不同的方法,调用不同的do…()方法
在HttpServlet中这些方法默认是405
因此我们在新建Servlet时,我们才会去考虑请求方法,从而决定重写那个do方法
生命周期
生命周期:从出生到死亡的过程就是生命周期,对应着Servlet的中的三个方法:init(), service(), destory()
默认情况下:
第一次接受请求时:这个Servlet会进行实例化(调用构造方法),初始化(调用init()),然后服务(调用service)
从第二次请求开始,每一次都是服务,不必再进行实例化
当容器关闭的时候,其中所有的servlet实例会被销毁,调用销毁方法
通过案例发现
servlet实例,tomcat只会创建一个,所有请求都是这个实例 去相应
默认情况下,第一次请求时,tomcat才回去实例化,初始化,然后服务,这样做的好处是什么? 提高系统启动速度
因此得出结论:如果需要提高系统启动速度,当前默认情况就是这样,如果需要提高响应速度,我们应该设置Servlet的启动时机
Servlet的启动时机
Servlet在容器的中是单例的,线程不安全的
单例:所有请求都是同一个实例去相应,因为servlet实例tomcat只会创建一次
线程不安全的:一个线程需要根据这个实例中的某个成员变量的值去做逻辑判断,但是在中间的某个时机,另一个线程改变了这个之,就会导致不能达到预期结果
我们尽可能不要再Servlet中定义成员变量,如果必须要定义的话,便不要根据成员变量的值去做一些逻辑判断
.
HTTP协议
Http称之为超文本传输协议
Http是无状态的
Http请求相应包含两个部分:请求和相应
Session会话
Http是无状态的
Http无状态:如果有两个请求发送过来,服务器无法区分这两个请求是否是同一个用户发送过来的
通过会话跟踪技术来解决这个问题,给这个会话一个标识,通过这个标识来判断是否是同一个用户
会话跟踪技术
客户端第一次发请求给服务器,服务器获取session,获取不到,则创建新的,然后响应给客户端
下次客户端给服务器法请求的时候,会把sessionID带给服务器,服务器通过sessionID判断这次会话和那一次的是同一个用户
常用API:
request.getSession -> 获取当前会话,没有则创建一个新的会话
request.getSession(true) -> 作用和上面的一样
request.getSession(false) -> 获取当前会话,没有则返回null
session.getId() -> 获取sessionID
session.isNew() -> 判断当前session是否是第一次访问
session.getMaxInactiveInterval() -> session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval()
session.invalidate() -> 强制性会话立即失效
session保存作用域
session保存作用域是和具体的某一个session对应的, 作用域就是本session内部
常用的API
session.setAttribute(k, v)
session.getAttribute(k)
session.removeAttribute(k)
服务器内部转发以及客户端重定向
服务器内部转发:request.getRequestDispatcher("…").forward(request,response);
当浏览器向服务器请求的时候,服务器内部的servlet1 直接跳转到servlet2中执行,最终浏览器请求接受的是servlet1,但实际处理的确实servlet2
一次请求相应的过程,对于客户端而言,内部经过了多少次的转发,客户端并不知晓
地址栏的URL并不会改变
客户端重定向: response.sendRedirect("…")
当浏览器向服务器发送请求的时候,服务器内部的servlet1 返回给浏览器,让浏览器去请求servlet02,然后浏览器在从新请求servlet2
多次请求相应的过程,对于客户端而言,知道url变化了几次
地址栏的URL会发生改变
Thymeleaf-视图模板技术
添加thymeleaf的jar包
新建一个Servlet类ViewBaseServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.hgu.myssm.myspringmvc;import org.thymeleaf.TemplateEngine;import org.thymeleaf.context.WebContext;import org.thymeleaf.templatemode.TemplateMode;import org.thymeleaf.templateresolver.ServletContextTemplateResolver;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ViewBaseServlet extends HttpServlet { private TemplateEngine templateEngine; @Override public void init () throws ServletException { ServletContext servletContext = this .getServletContext(); ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver (servletContext); 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" ); templateEngine = new TemplateEngine (); templateEngine.setTemplateResolver(templateResolver); } protected void processTemplate (String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html;charset=UTF-8" ); WebContext webContext = new WebContext (req, resp, getServletContext()); templateEngine.process(templateName, webContext, resp.getWriter()); } }
在web.xml文件中添加配置
前缀:view-prefix
后缀:view-suffix
1 2 3 4 5 6 7 8 9 <context-param > <param-name > view-prefix</param-name > <param-value > /WEB-INF/view/</param-value > </context-param > <context-param > <param-name > view-suffix</param-name > <param-value > .html</param-value > </context-param >
使我们的Servlet继承ViewBaseServlet
TH名称空间
表达式语法
修改标签文本值
代码示例:
1 <p th:text ="标签体新值" > 标签体原始值</p >
th:text作用
不经过服务器解析,直接用浏览器打开HTML文件,看到的是『标签体原始值』
经过服务器解析,Thymeleaf引擎根据th:text属性指定的『标签体新值』去替换 『标签体原始值』
字面量
『字面量』是一个经常会遇到的概念,我们可以对照『变量』来理解它的含义。
1 2 3 int a = 100 ;System.out.println("a = " + a);
变量:变量名字符串本身不是它的值,它指向的才是它的值
字面量:它就是字面上的含义,我们从『字面』上看到的直接就是它的值
现在我们在th:text属性中使用的就是『字面量』,它不指代任何其他值 。
修改指定属性值
代码示例:
1 <input type ="text" name ="username" th:value ="文本框新值" value ="文本框旧值" />
语法:任何HTML标签原有的属性,前面加上『th:』就都可以通过Thymeleaf来设定新值。
解析URL地址
基本语法
代码示例:
1 <p th:text ="@{/aaa/bbb/ccc}" > 标签体原始值</p >
1
经过解析后得到:
/view/aaa/bbb/ccc
所以@{}的作用是在字符串前附加『上下文路径』
这个语法的好处是:实际开发过程中,项目在不同环境部署时,Web应用的名字有可能发生变化。所以上下文路径不能写死。而通过@{}动态获取上下文路径后,不管怎么变都不怕啦!
首页使用URL地址解析
如果我们直接访问index.html本身,那么index.html是不需要通过Servlet,当然也不经过模板引擎,所以index.html上的Thymeleaf的任何表达式都不会被解析。
解决办法:通过Servlet访问index.html,这样就可以让模板引擎渲染页面了:
进一步的好处:
通过上面的例子我们看到,所有和业务功能相关的请求都能够确保它们通过Servlet来处理,这样就方便我们统一对这些请求进行特定规则的限定。
给URL地址后面附加请求参数
参照官方文档说明:
直接执行表达式
Servlet代码:
1 request.setAttribute("reqAttrName" , "<span>hello-value</span>" );
页面代码:
1 2 <p > 有转义效果:[[${reqAttrName}]]</p > <p > 无转义效果:[(${reqAttrName})]</p >
执行效果:
1 2 <p > 有转义效果:< span> hello-value< /span> </p > <p > 无转义效果:<span > hello-value</span > </p >
基本语法:访问域对象
域对象
请求域
在请求转发的场景下,我们可以借助HttpServletRequest对象内部给我们提供的存储空间,帮助我们携带数据,把数据发送给转发的目标资源。
请求域:HttpServletRequest对象内部给我们提供的存储空间
会话域
应用域
PS:在我们使用的视图是JSP的时候,域对象有4个
pageContext
request:请求域
session:会话域
application:应用域
所以在JSP的使用背景下,我们可以说域对象有4个,现在使用Thymeleaf了,没有pageContext。
在Servlet中将数据存入属性域
操作请求域
Servlet中代码:
1 2 3 4 String requestAttrName = "helloRequestAttr" ;String requestAttrValue = "helloRequestAttr-VALUE" ;request.setAttribute(requestAttrName, requestAttrValue);
Thymeleaf表达式:
1 <p th:text ="${helloRequestAttr}" > request field value</p >
操作会话域
Servlet中代码:
1 2 3 4 5 HttpSession session = request.getSession();session.setAttribute("helloSessionAttr" , "helloSessionAttr-VALUE" );
Thymeleaf表达式:
1 <p th:text ="${session.helloSessionAttr}" > 这里显示会话域数据</p >
操作应用域
Servlet中代码:
1 2 3 4 5 ServletContext servletContext = getServletContext();servletContext.setAttribute("helloAppAttr" , "helloAppAttr-VALUE" );
Thymeleaf表达式:
1 <p th:text ="${application.helloAppAttr}" > 这里显示应用域数据</p >
获取请求参数
具体来说,我们这里探讨的是在页面上(模板页面)获取请求参数。底层机制是:
一个名字一个值
页面代码:
1 <p th:text ="${param.username}" > 这里替换为请求参数的值</p >
页面显示效果:
一个名字多个值
页面代码:
1 <p th:text ="${param.team}" > 这里替换为请求参数的值</p >
页面显示效果:
如果想要精确获取某一个值,可以使用数组下标。页面代码:
1 2 <p th:text ="${param.team[0]}" > 这里替换为请求参数的值</p > <p th:text ="${param.team[1]}" > 这里替换为请求参数的值</p >
页面显示效果:
分支与迭代
分支
if和unless
让标记了th:if、th:unless的标签根据条件决定是否显示。
示例的实体类:
1 2 3 4 5 public class Employee { private Integer empId; private String empName; private Double empSalary;
示例的Servlet代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Employee> employeeList = new ArrayList <>(); employeeList.add(new Employee (1 , "tom" , 500.00 )); employeeList.add(new Employee (2 , "jerry" , 600.00 )); employeeList.add(new Employee (3 , "harry" , 700.00 )); request.setAttribute("employeeList" , employeeList); super .processTemplate("list" , request, response); }
示例的HTML代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <table > <tr > <th > 员工编号</th > <th > 员工姓名</th > <th > 员工工资</th > </tr > <tr th:if ="${#lists.isEmpty(employeeList)}" > <td colspan ="3" > 抱歉!没有查询到你搜索的数据!</td > </tr > <tr th:if ="${not #lists.isEmpty(employeeList)}" > <td colspan ="3" > 有数据!</td > </tr > <tr th:unless ="${#lists.isEmpty(employeeList)}" > <td colspan ="3" > 有数据!</td > </tr > </table >
if配合not关键词和unless配合原表达式效果是一样的,看自己的喜好。
switch
1 2 3 4 5 6 7 <h3 > 测试switch</h3 > <div th:switch ="${user.memberLevel}" > <p th:case ="level-1" > 银牌会员</p > <p th:case ="level-2" > 金牌会员</p > <p th:case ="level-3" > 白金会员</p > <p th:case ="level-4" > 钻石会员</p > </div >
迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <h3 > 测试each</h3 > <table > <thead > <tr > <th > 员工编号</th > <th > 员工姓名</th > <th > 员工工资</th > </tr > </thead > <tbody th:if ="${#lists.isEmpty(employeeList)}" > <tr > <td colspan ="3" > 抱歉!没有查询到你搜索的数据!</td > </tr > </tbody > <tbody th:if ="${not #lists.isEmpty(employeeList)}" > <tr th:each ="employee : ${employeeList}" > <td th:text ="${employee.empId}" > empId</td > <td th:text ="${employee.empName}" > empName</td > <td th:text ="${employee.empSalary}" > empSalary</td > </tr > </tbody > </table >
在迭代过程中,可以参考下面的说明使用迭代状态:
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 <h3 > 测试each</h3 > <table > <thead > <tr > <th > 员工编号</th > <th > 员工姓名</th > <th > 员工工资</th > <th > 迭代状态</th > </tr > </thead > <tbody th:if ="${#lists.isEmpty(employeeList)}" > <tr > <td colspan ="3" > 抱歉!没有查询到你搜索的数据!</td > </tr > </tbody > <tbody th:if ="${not #lists.isEmpty(employeeList)}" > <tr th:each ="employee,empStatus : ${employeeList}" > <td th:text ="${employee.empId}" > empId</td > <td th:text ="${employee.empName}" > empName</td > <td th:text ="${employee.empSalary}" > empSalary</td > <td th:text ="${empStatus.count}" > count</td > </tr > </tbody > </table >
保存作用域
远视情况下,保存作用域有四个:page(页面级别,几乎不用),request(一起请求响应范围), session(一次回话范围),application(一次应用程序范围)
request,只能保留一次请求
1 2 request.setAttribute("k" ,"v" ); request.getAttribute("k" );
session,可以保留多次请求,但是只局限于同一个Session,换浏览器就不行了
1 2 request.getSession().setAttribute("k" , "v" ); request.getSession().getAttribute("k" );
application,可以保留多次请求,贯穿于整个服务器之间,换浏览器也能获取到参数
1 2 3 ServletContext application = request.getServletContext();application.setAttribute("k" , "v" ); application.getAttribute("k" );
路径问题
相对路径和绝对路径
绝对路径
1 2 3 <base herf ="路径" > <link th:href ="@{/css/shpping.css}" >
Servlet优化
减少Servlet数量
一个客户端对着多个同类的Servlet
客户端对应着的都是fruit相关的Servlet
项目结构:
客户端只对应着一个同类Servlet
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 package com.hgu.servlets;import com.hgu.fruit.dao.FruitImp;import com.hgu.fruit.dao.inter.FruitDAO;import com.hgu.fruit.pojo.Fruit;import com.hgu.myssm.myspringmvc.ViewBaseServlet;import com.hgu.utils.JDBCUtils;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.sql.Connection;import java.util.ArrayList;@WebServlet("/fruit.do") public class FruitServlet extends ViewBaseServlet { private FruitImp fruitImp = new FruitImp (); @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String operator = req.getParameter("operator" ); if (JDBCUtils.isEmpty(operator)){ operator = "search" ; } Method[] methods = this .getClass().getDeclaredMethods(); for (Method m : methods) { String methodName = m.getName(); if (methodName.equals(operator)){ try { m.invoke(this , req, resp); } catch (Exception e) { throw new RuntimeException (e); } } } } private void update (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String fidStr = req.getParameter("fid" ); int fid = Integer.parseInt(fidStr); String fname = req.getParameter("fname" ); String fpriceStr = req.getParameter("fprice" ); Double fprice = Double.parseDouble(fpriceStr); String fcountStr = req.getParameter("fcount" ); int fcount = Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } fruitImp.update(connection, new Fruit (fid, fname, fprice, fcount, remark)); resp.sendRedirect("fruit.do" ); } private void edit (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String fidStr = req.getParameter("fid" ); Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } Fruit fruit = fruitImp.getFruitByID(connection, Integer.parseInt(fidStr)); req.setAttribute("fruit" , fruit); super .processTemplate("edit" , req, resp); } private void del (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } String fidStr = req.getParameter("fid" ); int fid = Integer.parseInt(fidStr); fruitImp.deleteByID(connection, fid); resp.sendRedirect("fruit.do" ); } private void add (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String fname = req.getParameter("fname" ); String fpriceStr = req.getParameter("fprice" ); int fprice = Integer.parseInt(fpriceStr); String fcountStr = req.getParameter("fcount" ); int fcount = Integer.parseInt(fcountStr); String remark = req.getParameter("remark" ); FruitDAO fruitDAO = new FruitImp (); Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } fruitDAO.insert(connection, new Fruit (1 , fname,fprice, fcount, remark)); resp.sendRedirect("fruit.do" ); } private void search (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String tag = req.getParameter("tag" ); String kv = "" ; if (JDBCUtils.isNotEmpty(tag)){ kv = req.getParameter("kv" ); req.getSession().setAttribute("kv" ,kv); } if (JDBCUtils.isEmpty((String) req.getSession().getAttribute("kv" ))){ kv = "" ; }else { kv = (String) req.getSession().getAttribute("kv" ); } int pageNumber = 1 ; String pageNumberStr = req.getParameter("pageNumber" ); HttpSession session = req.getSession(); Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } if (JDBCUtils.isNotEmpty(pageNumberStr)){ pageNumber = Integer.parseInt(pageNumberStr); }else { int count = fruitImp.getKeyValueCount(connection, kv); session.setAttribute("pageEndNumber" , count/5 + 1 ); } session.setAttribute("pageNumber" , pageNumber); ArrayList<Fruit> fruitList = fruitImp.getKeyValueFruit(connection, kv, pageNumber); session.setAttribute("fruitList" ,fruitList); super .processTemplate("search" , req, resp); } }
添加DispatcherServlet
我们在执行项目的时候需要些很多的Servlet,例如上面的Fruit,User,Shopping等等,但是这又出现一个问题,我们在访问的时候需要访问不同的Servlet,这时候我们可以创建一个控制器,让网页中所有 的Servlet请求都经过这个控制器,然后通过这个控制器,来分析请求的网址,并对应上各自的Servlet
所以我们需要实现Dispatcher类
这里有个坑:我们在使用完中央控制器之后,原本的Servlet类里面的父类中的init便不会被调用,因为我们删掉了原本Servlet类的注册信息,所以不会再执行Servlet中的init方法,需要我们手动给他执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 package com.myssm.myspringmvc;import com.utils.JDBCUtils;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;@WebServlet("*.do") public class DispatcherServlet extends HttpServlet { private Map<String, Object> beanMap = new HashMap <>(); public DispatcherServlet () { } @Override public void init () throws ServletException { InputStream is = getClass().getClassLoader().getResourceAsStream("applicationContext.xml" ); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(is); NodeList beans = document.getElementsByTagName("bean" ); for (int i = 0 ; i < beans.getLength(); i++) { Node item = beans.item(i); if (item.getNodeType() == Node.ELEMENT_NODE){ Element beanElement = (Element)item; String id = beanElement.getAttribute("id" ); String aClass = beanElement.getAttribute("class" ); Class controllerBeanClass = Class.forName(aClass); Object beanObj = controllerBeanClass.newInstance(); ServletContext servletContext = this .getServletContext(); Method setServletContext = controllerBeanClass.getDeclaredMethod("setServletContext" ,ServletContext.class); setServletContext.setAccessible(true ); setServletContext.invoke(beanObj,servletContext); beanMap.put(id,beanObj); } } } catch (Exception e) { throw new RuntimeException (e); } } @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String servletPath = req.getServletPath(); servletPath = servletPath.substring(1 ); int i = servletPath.lastIndexOf(".do" ); servletPath = servletPath.substring(0 , i); Object controllerBeanObj = beanMap.get(servletPath); String operate = req.getParameter("operate" ); if (JDBCUtils.isEmpty(operate)){ operate="search" ; } Method[] declaredMethods = controllerBeanObj.getClass().getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { String name = declaredMethod.getName(); if (name.equals(operate)){ try { declaredMethod.setAccessible(true ); declaredMethod.invoke(controllerBeanObj, req, resp); } catch (Exception e) { throw new RuntimeException (e); } } } } }
FruitServlet 中改动的地方
添加一个调用父类的init方法
1 2 3 4 private void setServletContext (ServletContext servletContext) throws ServletException { super .init(servletContext); }
ViewBaseServlet中改动的地方
将Init改成带参数的了,将processTemplate里面的getServletContext()方法改成了参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.myssm.myspringmvc;import org.thymeleaf.TemplateEngine;import org.thymeleaf.context.WebContext;import org.thymeleaf.templatemode.TemplateMode;import org.thymeleaf.templateresolver.ServletContextTemplateResolver;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ViewBaseServlet extends HttpServlet { private ServletContext servletContext; private TemplateEngine templateEngine; public void init (ServletContext servletContext) throws ServletException { this .servletContext = servletContext; ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver (servletContext); 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" ); templateEngine = new TemplateEngine (); templateEngine.setTemplateResolver(templateResolver); } protected void processTemplate (String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html;charset=UTF-8" ); WebContext webContext = new WebContext (req, resp, this .servletContext); templateEngine.process(templateName, webContext, resp.getWriter()); } }
减少Controller里面的相同代码
减少跳转代码
我们发现FruitController里面每一个方法都需要跳转或者重定向到其他方法或页面中,我们可以把这一部分提取出来,放到中央控制器中来进行统一的跳转,通过返回值来确定跳转的地方
FruitController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 private String search (HttpServletRequest req) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String tag = req.getParameter("tag" ); String kv = "" ; if (JDBCUtils.isNotEmpty(tag)){ kv = req.getParameter("kv" ); req.getSession().setAttribute("kv" ,kv); } if (JDBCUtils.isEmpty((String) req.getSession().getAttribute("kv" ))){ kv = "" ; }else { kv = (String) req.getSession().getAttribute("kv" ); } int pageNumber = 1 ; String pageNumberStr = req.getParameter("pageNumber" ); HttpSession session = req.getSession(); Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } if (JDBCUtils.isNotEmpty(pageNumberStr)){ pageNumber = Integer.parseInt(pageNumberStr); }else { int count = fruitImp.getKeyValueCount(connection, kv); session.setAttribute("pageEndNumber" , count/5 + 1 ); } session.setAttribute("pageNumber" , pageNumber); ArrayList<Fruit> fruitList = fruitImp.getKeyValueFruit(connection, kv, pageNumber); session.setAttribute("fruitList" ,fruitList); return "search" ; }
DispatcherServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 package com.myssm.myspringmvc;import com.utils.JDBCUtils;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;@WebServlet("*.do") public class DispatcherServlet extends ViewBaseServlet { private Map<String, Object> beanMap = new HashMap <>(); public DispatcherServlet () { } @Override public void init () throws ServletException { super .init(); InputStream is = getClass().getClassLoader().getResourceAsStream("applicationContext.xml" ); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(is); NodeList beans = document.getElementsByTagName("bean" ); for (int i = 0 ; i < beans.getLength(); i++) { Node item = beans.item(i); if (item.getNodeType() == Node.ELEMENT_NODE){ Element beanElement = (Element)item; String id = beanElement.getAttribute("id" ); String aClass = beanElement.getAttribute("class" ); Class controllerBeanClass = Class.forName(aClass); Object beanObj = controllerBeanClass.newInstance(); beanMap.put(id,beanObj); } } } catch (Exception e) { throw new RuntimeException (e); } } @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String servletPath = req.getServletPath(); servletPath = servletPath.substring(1 ); int i = servletPath.lastIndexOf(".do" ); servletPath = servletPath.substring(0 , i); Object controllerBeanObj = beanMap.get(servletPath); String operate = req.getParameter("operate" ); if (JDBCUtils.isEmpty(operate)){ operate="search" ; } Method[] declaredMethods = controllerBeanObj.getClass().getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { String name = declaredMethod.getName(); if (name.equals(operate)){ try { declaredMethod.setAccessible(true ); String methodReturnStr = "" ; Object methodReturn = declaredMethod.invoke(controllerBeanObj, req); if (methodReturn != null ){ methodReturnStr = (String) methodReturn; } if (methodReturnStr.startsWith("redirect" )){ String method = methodReturnStr.substring("redirect" .length()); resp.sendRedirect(method); }else { super .processTemplate(methodReturnStr,req, resp); } } catch (Exception e) { throw new RuntimeException (e); } } } } }
减少参数代码
这里有个前提是我们需要使用JDK 8.0以上的JAVA版本
因为JDK 8.0有一个新特性,就是我们在通过反射获取对应类中的参数名的时候,不再显示arg0, arg1,具体设置方法:
IEAD:
添加-parameters参数即可
MAVEN
在bulid中设置compilerArgument参数
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 <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > org.example</groupId > <artifactId > QQZone</artifactId > <packaging > war</packaging > <version > 1.0-SNAPSHOT</version > <name > QQZone Maven Webapp</name > <url > http://maven.apache.org</url > <dependencies > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 3.8.1</version > <scope > test</scope > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.30</version > </dependency > <dependency > <groupId > org.thymeleaf</groupId > <artifactId > thymeleaf</artifactId > <version > 3.0.14.RELEASE</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 4.0.1</version > </dependency > </dependencies > <build > <finalName > QQZone</finalName > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.1</version > <configuration > <compilerArgument > -parameters</compilerArgument > <encoding > UTF-8</encoding > <source > 1.9</source > <target > 1.9</target > </configuration > </plugin > </plugins > </build > </project >
FruitController
将FruitController中所有的getParameter,都去掉,换成参数传进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 public class FruitController { private FruitImp fruitImp = new FruitImp (); private String update (Integer fid, String fname, Double fprice, Integer fcount, String remark) { Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } fruitImp.update(connection, new Fruit (fid, fname, fprice, fcount, remark)); return "redirect:fruit.do" ; } private String edit (Integer fid, HttpServletRequest req) { Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } Fruit fruit = fruitImp.getFruitByID(connection, fid); req.setAttribute("fruit" , fruit); return "edit" ; } private String del (Integer fid) { Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } fruitImp.deleteByID(connection, fid); return "redirect:fruit.do" ; } private String add (String fname, Double fprice, Integer fcount, String remark) { FruitDAO fruitDAO = new FruitImp (); Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } fruitDAO.insert(connection, new Fruit (1 , fname,fprice, fcount, remark)); return "redirect:fruit.do" ; } private String search (String tag, String kv, HttpSession session, Integer pageNumber) { String keyValue = "" ; if (JDBCUtils.isNotEmpty(tag)){ keyValue = kv; session.setAttribute("kv" ,keyValue); } if (JDBCUtils.isEmpty((String) session.getAttribute("kv" ))){ keyValue = "" ; }else { keyValue = (String) session.getAttribute("kv" ); } Connection connection = null ; try { connection = JDBCUtils.getConnection(); } catch (Exception e) { throw new RuntimeException (e); } if (pageNumber == null ) { int count = fruitImp.getKeyValueCount(connection, keyValue); session.setAttribute("pageEndNumber" , count / 5 + 1 ); pageNumber = 1 ; } session.setAttribute("pageNumber" , pageNumber); ArrayList<Fruit> fruitList = fruitImp.getKeyValueFruit(connection, keyValue, pageNumber); session.setAttribute("fruitList" ,fruitList); return "search" ; } }
DispatcherServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); String servletPath = req.getServletPath(); servletPath = servletPath.substring(1 ); int i = servletPath.lastIndexOf(".do" ); servletPath = servletPath.substring(0 , i); Object controllerBeanObj = beanMap.get(servletPath); String operator = req.getParameter("operator" ); if (JDBCUtils.isEmpty(operator)){ operator="search" ; } Method[] declaredMethods = controllerBeanObj.getClass().getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { String name = declaredMethod.getName(); if (name.equals(operator)){ Parameter[] parameters = declaredMethod.getParameters(); Object[] parametersActual = new Object [parameters.length]; String parameterName = "" ; String type = "" ; for (int j = 0 ; j < parameters.length; j++) { parameterName = parameters[j].getName(); if ("req" .equals(parameterName)){ parametersActual[j] = req; } else if ("resp" .equals(parameterName)) { parametersActual[j] = resp; } else if ("session" .equals(parameterName)) { parametersActual[j] = req.getSession(); }else { type = parameters[j].getType().toString(); String parameter = req.getParameter(parameterName); if (JDBCUtils.isNotEmpty(parameter)){ switch (type){ case "class java.lang.String" -> parametersActual[j] = parameter; case "class java.lang.Integer" -> parametersActual[j] = Integer.parseInt(parameter); case "class java.lang.Double" -> parametersActual[j] = Double.parseDouble(parameter); } } } } try { declaredMethod.setAccessible(true ); String methodReturnStr = "" ; Object methodReturn = declaredMethod.invoke(controllerBeanObj, parametersActual); if (methodReturn != null ){ methodReturnStr = (String) methodReturn; } if (methodReturnStr.startsWith("redirect:" )){ String method = methodReturnStr.substring("redirect:" .length()); resp.sendRedirect(method); }else { super .processTemplate(methodReturnStr,req, resp); } } catch (Exception e) { throw new RuntimeException (e); } } } }
Review
最初的做法是:同类的请求对应着不同的Servlet,那么如果类多了起来的话,那么Servlet将会特别多
我们将同类的Servlet合并到一个Servlet中,例如关于Fruit的增删改查都放到FruitServlet中,通过operate来获取我们要执行的那个方法
我们在通过operator获取需要执行的的函数的时候,需要通过switch case 来进行依次匹配,那么如果我们需要增加方法的话,switch case 的代码还需要更改,这时候就很不方便
我们通过反射来获取对应的Servlet中的所有方法,通过反射调用对应的operate的方法,前提是operate需要和方法名相同
再进一步,我们发现如果有很多不同的类,例如fruit,user,shopping,等等我们每一个类都需要进行反射来获取对应的方法,并通过反射调用,这时候未免显得有些代码冗余
我们通过中央控制器DispatcherServlet来统一调用反射,DispatcherServlet主要需要实现两部分功能
通过我们访问的网址,获取对应的Controller类
从URL中提取到我们需要访问的Servlet类的别名例如从这个网址中http://localhost:8080/fruit/fruit.do 获取到fruit
将fruit和FruitController对应起来,通过applicationContext.xml,通过DOM技术获取到bean里面的对应关系
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8" ?> <beans > <bean id ="fruit" class ="com.fruit.controller.FruitController" /> </beans >
调用Controller组件的方法
获取参数
通过method.getParameters()获取到该方法中的所有参数
通过method.getName() 获取到所有的参数名称
通过method.getType()获取到所有的参数类型 用来强转
执行方法
Object returnObj = method.invoke(controllerBean, paramerValues);
视图处理
通过返回来来确定,该网页需要跳转到哪一步
1 2 3 4 5 6 if (methodReturnStr.startsWith("redirect:" )){ String method = methodReturnStr.substring("redirect:" .length()); resp.sendRedirect(method); }else { super .processTemplate(methodReturnStr,req, resp); }
Servlet IOC实现
ServletAPI
Servlet生命周期:实例化,初始化,服务,销毁
Servlet中初始化方法有两个:init(), init(ServletConfig config)
1 2 3 4 5 6 7 public void init (ServletConfig config) throws ServletException { this .config = config; this .init(); } public void init () throws ServletException {}
如果想要在Servlet初始化的时候进行一些操作,我们可以重写空参的Init方法
我们可以在配置Servlet的时候配置初始化参数
1 2 3 4 5 6 7 8 9 10 11 12 <servlet > <servlet-name > Demo01</servlet-name > <servlet-class > com.fruit.controller.Demo01Servlet</servlet-class > <init-param > <param-name > hello</param-name > <param-value > word</param-value > </init-param > </servlet > <servlet-mapping > <servlet-name > Demo01</servlet-name > <url-pattern > /demo01</url-pattern > </servlet-mapping >
也可以通过注解的方式进行配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @WebServlet(urlPatterns = {"/demo01"}, initParams = { @WebInitParam(name="hello",value="word"), @WebInitParam(name="uname", value="zjh") } ) public class Demo01Servlet extends HttpServlet { @Override public void init () throws ServletException { ServletConfig servletConfig = getServletConfig(); String hello = servletConfig.getInitParameter("hello" ); System.out.println(hello); } }
可以通过以下方法获取初始化参数
获取config对象:ServletConfig config = getServletConfig();
获取初始化参数值:config.getInitParameter(key);
学习Servlet中的ServletContext和< context - param >
获取ServletContext的方法
在初始化方法中:ServletContext servletContext = getServletContext();
在服务方法中:ServletContext servletContext = req.getServletContext(); ServletContext servletContext = req.getSession().getServletContext();
获取初始化值
1. servletContext.getInitParameter(key);
设置初始化值
1 2 3 4 5 6 7 8 <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 >
Service
Model和Model2
MVC:Model(模型), View(视图),Controller(控制器)
视图层:用于做数据展示和用户交互的一个界面
控制层:用于接受客户端请求,具体的业务功能还是借助模型来实现的
模型层:模型分为很多种;有比较简单的pojo/vo(value object),有业务层组件,有数据访问组件
pojo/vo:值对象,对应数据库里面一中类
DAO:数据访问对象
BO:业务对象
区别业务对象和数据访问对象
DAO中的方法称之为单精度方法或称之为细粒度方法。什么是单精度方法?一个方法只考虑一个操作,比如添加,就是Insert
BO中的方法属于业务方法,实际的业务比较复杂,因此业务方法比较粗
例如注册功能:注册功能属于业务功能,也就是说注册这个方法属于业务方法
这个业务方法中包括了多个DAO方法,需要多个DAO方法组合来完成注册功能
注册:
检查用户名是否已经被注册 — DAO中的Select操作
向用户表添加一条记录 — DAO中的 Insert操作
向用户积分表添加一条记录: —DAO中的insert操作
…
在库存系统中添加业务层组件
实现Service
FruitService 接口
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 package com.fruit.biz.inter;import com.fruit.pojo.Fruit;import java.util.ArrayList;public interface FruitService { ArrayList<Fruit> getFruitList (String keyValue, Integer pageNumber) ; void addFruit (Fruit fruit) ; Fruit getFruitByID (Integer id) ; void delFruitByID (Integer id) ; Integer getAllPageCount (String keyValue) ; void update (Fruit fruit) ; }
FruitService 实现类
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 package com.fruit.biz;import com.fruit.biz.inter.FruitService;import com.fruit.dao.FruitDAOImp;import com.fruit.pojo.Fruit;import com.utils.JDBCUtils;import java.sql.Connection;import java.util.ArrayList;import java.util.Collection;public class FruitServiceImp implements FruitService { private FruitDAOImp fruitDAOImp = new FruitDAOImp (); @Override public ArrayList<Fruit> getFruitList (String keyValue, Integer pageNumber) { Connection connection = JDBCUtils.getConnection(); return fruitDAOImp.getKeyValueFruit(connection, keyValue, pageNumber); } @Override public void addFruit (Fruit fruit) { Connection connection = JDBCUtils.getConnection(); fruitDAOImp.insert(connection,fruit); } @Override public Fruit getFruitByID (Integer id) { Connection connection = JDBCUtils.getConnection(); return fruitDAOImp.getFruitByID(connection,id); } @Override public void delFruitByID (Integer id) { Connection connection = JDBCUtils.getConnection(); fruitDAOImp.deleteByID(connection,id); } @Override public Integer getAllPageCount (String keyValue) { Connection connection = JDBCUtils.getConnection(); int keyValueCount = fruitDAOImp.getKeyValueCount(connection, keyValue); return keyValueCount/5 +1 ; } @Override public void update (Fruit fruit) { Connection connection = JDBCUtils.getConnection(); fruitDAOImp.update(connection,fruit); } }
FruitController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package com.fruit.controller;import com.fruit.biz.FruitServiceImp;import com.fruit.dao.FruitDAOImp;import com.fruit.dao.inter.FruitDAO;import com.fruit.pojo.Fruit;import com.utils.JDBCUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import java.sql.Connection;import java.util.ArrayList;public class FruitController { private FruitServiceImp fruitServiceImp = new FruitServiceImp (); private String update (Integer fid, String fname, Double fprice, Integer fcount, String remark) { fruitServiceImp.update(new Fruit (fid, fname, fprice, fcount, remark)); return "redirect:fruit.do" ; } private String edit (Integer fid, HttpServletRequest req) { Fruit fruit = fruitServiceImp.getFruitByID(fid); req.setAttribute("fruit" , fruit); return "edit" ; } private String del (Integer fid) { fruitServiceImp.delFruitByID(fid); return "redirect:fruit.do" ; } private String add (String fname, Double fprice, Integer fcount, String remark) { fruitServiceImp.addFruit(new Fruit (1 , fname,fprice, fcount, remark)); return "redirect:fruit.do" ; } private String search (String tag, String kv, HttpSession session, Integer pageNumber) { String keyValue = "" ; if (JDBCUtils.isNotEmpty(tag)){ keyValue = kv; session.setAttribute("kv" ,keyValue); } if (JDBCUtils.isEmpty((String) session.getAttribute("kv" ))){ keyValue = "" ; }else { keyValue = (String) session.getAttribute("kv" ); } if (pageNumber == null ) { int count = fruitServiceImp.getAllPageCount(keyValue); session.setAttribute("pageEndNumber" , count); pageNumber = 1 ; } session.setAttribute("pageNumber" , pageNumber); ArrayList<Fruit> fruitList = fruitServiceImp.getFruitList(keyValue, pageNumber); session.setAttribute("fruitList" ,fruitList); return "search" ; } }
IOC实现
耦合/依赖
指的是一个类依靠另一个类,即:一个类中有另一个类的实例,如果另一个类消失的话,那么这个类也就不能使用
在软件系统中,层与层之间是存在依赖的,我们也称为耦合
在系统架构或者是设计的一个原则是:高内聚,低耦合
层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况是0耦合
我们想要做到的:当下层删除之后上层不会报错,例如FruitService 删除之后FruitController不会报错
实现方法:
为什么下一层删除之后上一层会报错?
因为FruitController里面有这样一条代码,它的成功编译就是依赖于FruitServiceImp是存在的
1 private FruitService fruitServiceImp = new FruitServiceImp ();
解决问题1:我们将代码改成:
1 private FruitService fruitServiceImp = null ;
这样FruitServiceImp删除之后FruitController就不会报错,但是者会出现一个新的问题:
我们下面代码在使用fruitServiceImp这个属性的时候会报错,我们还需要给他赋值
赋值方法
我们可以创建一个容器,将上面所需要的类都放在容器里面,需要的时候直接取出来即可,我么可以通过XML文件创建一个Bean容器
通过读取XML文件赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="utf-8" ?> <beans > <bean id ="fruit" class ="com.hgu.fruit.controller.FruitController" /> <bean id ="fruitDAO" class ="com.hgu.fruit.dao.FruitDAOImp" /> <bean id ="fruitService" class ="com.hgu.fruit.service.FruitServiceImp" /> </beans >
我们先创建一个BeanFactory接口,写入getBean方法
在创建它的实现类ClassPathXmlApplicationContext实现这个方法
通过Map获取这个类的对象
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 public class ClassPathXmlApplicationContext implements BeanFactory { private Map<String, Object> beanMap = new HashMap <>(); public ClassPathXmlApplicationContext () { InputStream is = getClass().getClassLoader().getResourceAsStream("applicationContext.xml" ); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(is); NodeList beans = document.getElementsByTagName("bean" ); for (int i = 0 ; i < beans.getLength(); i++) { Node item = beans.item(i); if (item.getNodeType() == Node.ELEMENT_NODE){ Element beanElement = (Element)item; String id = beanElement.getAttribute("id" ); String aClass = beanElement.getAttribute("class" ); Class beanClass = Class.forName(aClass); Object beanObj = beanClass.newInstance(); beanMap.put(id,beanObj); } } } catch (Exception e) { throw new RuntimeException (e); } } @Override public Object getBean (String id) { return beanMap.get(id); } }
现在我们已经拥有了我们所需要的Object对象的bean容器,那么现在我们如何给我们上面所设置的null的属性赋值那?
还是通过XML指明赋值对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8" ?> <beans > <bean id ="fruit" class ="com.hgu.fruit.controller.FruitController" > <property name ="fruitServiceImp" ref ="fruitService" /> </bean > <bean id ="fruitDAO" class ="com.hgu.fruit.dao.FruitDAOImp" /> <bean id ="fruitService" class ="com.hgu.fruit.service.FruitServiceImp" > <property name ="fruitDAOImp" ref ="fruitDAO" /> </bean > </beans >
实现通过代码实现他们之间的关联
XML文件本身就是一个节点,其中还包括 空节点,元素节点,文本节点
我门在上面已经获取到了所有的Bean节点中的类的实例,现在我们要做的就是将他们之间的关系依照XML文件中所述的将他们关联起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (int j = 0 ; j < childNodes.getLength(); j++) { Node childItem = childNodes.item(j); if (childItem.getNodeType() == Node.ELEMENT_NODE && "property" .equals(childItem.getNodeName())){ Element childBeanItem = (Element) childItem; String name = childBeanItem.getAttribute("name" ); String ref = childBeanItem.getAttribute("ref" ); Object refObj = beanMap.get(ref); Field field = beanObj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(beanObj, refObj); } }
控制反转 和 依赖注入
控制反转
在之前Servlet中,我们创建Servlet对象的时候,在Servlet内部有一个成员变量是 FruitService fruitService = new FruitServiceImp();,如果这行代码出现在方法内部的话,那么这个fruitService的作用域(生命周期)就是这个方法级,如果出现在Servlet类中,那么fruitService的作用域就是Servlet实例级别
我们在applicationContext.xml中定义了fruitService。然后通过解析xml,产生fruitService,存放在beanMap,这个beanMap存在于BeanFactory中,因此我们改变了之前的Service实例,dao实例等等,他们的生命周期,存放到了beanMap中,控制权从程序员转换到了BeanFactory中,这个BeanFactory被称为IOC容器
依赖注入
之前控制层出现代码:FruitService fruitService = new FruitServiceImp();这行代码表明控制层和Service层存在耦合
之后我们将代码改成FruitService fruitService = null;
然后在配置文件中配置
1 2 3 <bean id ="fruit" class ="FruitController" > <property name ="fruitService" ref ="fruitService" /> </bean >
Filter 过滤器
从客户端发来的请求,都要经过过滤器的处理,然后才能到达Servlet层,过滤器中必须有放行,否则过滤器会一直拿着请求,不穿送给Servlet层,等Servlet处理完请求要返回的时候,还是需要经过过滤器,然后才能到达客户端
实现方法:
Servlet
1 2 3 4 5 6 7 8 @WebServlet("/demo01.co") public class Demo01Servlet extends HttpServlet { @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("demo01.service" ); req.getRequestDispatcher("demo01.html" ).forward(req,resp); } }
Filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Demo01Filter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { Filter.super .init(filterConfig); } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("HelloA" ); filterChain.doFilter(servletRequest, servletResponse); System.out.println("HelloA1" ); } @Override public void destroy () { Filter.super .destroy(); } }
XML配置文件
1 2 3 4 5 6 7 8 <filter > <filter-name > Demo01Filter</filter-name > <filter-class > com.hug.filters.Demo01Filter</filter-class > </filter > <filter-mapping > <filter-name > Demo01Filter</filter-name > <url-pattern > "/demo01.do"</url-pattern > </filter-mapping >
Filter也属于Servlet规范
Filter开发步骤:新建类实现Filter接口,然后实现其中三个方法:init,doFilter,destory,配置Filter,可以使用注解@WebFilter,也可以使用XML文件< filter >< filter-mapping >
Filter在配置的时候和Servlet一样,也可以使用通配符,例如@WebFilter("*.do")表示拦截所有以.do结尾的请求
过滤器链:
如果采用的是注解的方式进行配置,那么过滤器拦截的先后顺序是按照全类名的先后顺序来排序的
如果采用的是XML的方式进行配置,那么按照配置的先后顺序进行排序
事务
事务的本质就是:将多个DAO事件绑到一起,要么同时成功,要么同时回滚,我们需要设置自动提交为false,让我们手动的给他提交
方式一:在DAO层设置事务
但是这样出现的情况会出现,DAO01 成功, DAO02失败, DAO03成功,那么这样我们无法确定Service是否成功进行,所以我们不能再DAO层设置事务,我们需要在Service层设置,将多个DAO绑定在一起,要么同时成功,要么同时回滚
方式二:在Service层使用事务
方式三:在过滤器层使用
我们需要使用同一个Connection才能够将service层中的DAO对象绑定到一起
实现:
过滤器OpenSessionInViewFilter
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 @WebFilter("*.do") public class OpenSessionInViewFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { Filter.super .init(filterConfig); } @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) { throw new RuntimeException (ex); } } } @Override public void destroy () { Filter.super .destroy(); } }
事务管理:TransactionManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.hgu.myssm.trans;import com.hgu.myssm.utils.JDBCUtils;import java.sql.Connection;import java.sql.SQLException;public class TransactionManager { public static void beginTrans () throws SQLException { JDBCUtils.getConnection().setAutoCommit(false ); } public static void commit () throws SQLException { JDBCUtils.getConnection().commit(); JDBCUtils.closeConnection(); } public static void rollback () throws SQLException { JDBCUtils.getConnection().rollback(); JDBCUtils.closeConnection(); } }
JDBCUtils创建获取连接
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 public static Connection createConnection () { try { InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties" ); Properties pros = new Properties (); pros.load(is); String user = pros.getProperty("user" ); String password = pros.getProperty("password" ); String url = pros.getProperty("url" ); String driverClass = pros.getProperty("driverClass" ); Class clazz = Class.forName(driverClass); return DriverManager.getConnection(url, user, password); } catch (Exception e) { throw new RuntimeException (e); } } public static Connection getConnection () { Connection connection = threadLocal.get(); if (connection == null ){ connection = createConnection(); threadLocal.set(connection); } return threadLocal.get(); }
监听器
监听器︰专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。
Servlet监听器:Servlet规范中定义的一种特殊类,它用于监听Web应用程序中的ServletContext,HttpSession和HttpServletRequest等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。
ServletContextListener – 监听ServletContext对象的创建和销毁过程
1 2 contextInitialized(ServletContextEvent sce); contextDestroyed(ServletContextEvent sce);
HttpSessionListener — 监听Session对象的创建和销毁过程
1 2 sessionInitialized(ServletContextEvent sce); sessionDestroyed(ServletContextEvent sce);
ServletRequestListener — 监听ServletRequest对象的创建和销毁过程
1 2 requestInitialized(ServletContextEvent sce); requestDestroyed(ServletContextEvent sce);
ServletContextAttributeListener — 监听ServletContext中属性的创建,修改和销毁
1 2 3 attributeAdded(ServletContextAttributeEvent scab); attributeRemoved(ServletContextAttributeEvent scab); attributeReplaced(ServletContextAttributeEvent scab);
HttpSessionAttributeListener — 监听HttpSession中属性的创建,修改和销毁
1 2 3 sessionAdded(ServletContextAttributeEvent scab); sessionRemoved(ServletContextAttributeEvent scab); sessionReplaced(ServletContextAttributeEvent scab);
ServletRequestAttributeListener — 监听ServletContext中属性的创建,修改和销毁
1 2 3 requestAdded(ServletContextAttributeEvent scab); requestRemoved(ServletContextAttributeEvent scab); requestReplaced(ServletContextAttributeEvent scab);
HttpSessionBindingListener — 监听某个对象在Session域中的创建与移除
1 2 valueBound(HttpSessionBindingEvent event); valueUnbound(HttpSessionBindingEvent event);
HttpSessionActivationListener — 监听对象在Session中的序列化与反序列化
1 2 sessionWillPassivate(HttpSessionEvent se); sessionDidActivate(HttpSessionEvent se);
创建:
1 2 3 4 5 6 7 8 9 10 11 12 13 @WebListener public class MyServletContextListener implements ServletContextListener { @Override public void contextInitialized (ServletContextEvent sce) { ServletContextListener.super .contextInitialized(sce); } @Override public void contextDestroyed (ServletContextEvent sce) { ServletContextListener.super .contextDestroyed(sce); } }
配置:
使用标签配置:@WebListener
使用配置文件配置:
1 2 3 <listener > <listener-class > com.hug.listener.MyServletContextListener</listener-class > </listener >
QQZone
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 1.熟悉QQZone业务需求 1) 用户登录 2) 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表 3) 查看日志详情: - 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期) - 回复列表(回复者的头像、昵称、回复内容、回复日期) - 主人回复信息 4) 删除日志 5) 删除特定回复 6) 删除特定主人回复 7) 添加日志、添加回复、添加主人回复 8) 点击左侧好友链接,进入好友的空间 2.数据库设计 1) 抽取实体 : 用户登录信息、用户详情信息 、 日志 、 回贴 、 主人回复 2) 分析其中的属性: - 用户登录信息:账号、密码、头像、昵称 - 用户详情信息:真实姓名、星座、血型、邮箱、手机号..... - 日志:标题、内容、日期、作者 - 回复:内容、日期、作者、日志 - 主人回复:内容、日期、作者、回复 3) 分析实体之间的关系 - 用户登录信息 : 用户详情信息 1:1 PK - 用户 : 日志 1:N - 日志 : 回复 1:N - 回复 : 主人回复 1:1 UK - 用户 : 好友 M : N 3.数据库的范式: 1) 第一范式:列不可再分 2) 第二范式:一张表只表达一层含义(只描述一件事情) 3) 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖 4.数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况做一个选择: - 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率 - 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能 5.QQZone登录功能实现出现的四个错误: 1) URL没修改,用的还是fruitdb 2) 3)rsmd.getColumnName() 和 rsmd.getColumnLabel() 4)Can not set com.atguigu.qqzone.pojo.UserBasic field com.atguigu.qqzone.pojo.Topic.author to java.lang.Integer 5) left.html页面没有样式,同时数据也不展示,原因是:我们是直接去请求的静态页面资源,那么并没有执行super.processTemplate(),也就是thymeleaf没有起作用 (之前的表单也是这个原因) 解决方法: - 新增PageController,添加page方法: public String page(String page){ return page ; // frames/left } 目的是执行super.processTemplate()方法,让thymeleaf生效 昨日内容: 1. top.html页面显示登录者昵称、判断是否是自己的空间 1)显示登录者昵称: ${session.userBasic.nickName} 2)判断是否是自己的空间 : ${session.userBasic.id!=session.friend.id} 如果不是期望的效果,首先考虑将两者的id都显示出来 2. 点击左侧的好友链接,进入好友空间 1) 根据id获取指定userBasic信息,查询这个userBasic的topicList,然后覆盖friend对应的value 2) main页面应该展示friend中的topicList,而不是userBasic中的topicList 3) 跳转后,在左侧(left)中显示整个index页面 - 问题:在left页面显示整个index布局 - 解决:给超链接添加target属性: target="_top" 保证在顶层窗口显示整个index页面 4) top.html页面需要修改: "欢迎进入${session.friend}" top.html页面的返回自己空间的超链接需要修改: <a th:href ="@{|/user.do?operate=friend&id=${session.userBasic.id}|}" target ="_top" > 3. 日志详情页面实现 1) 已知topic的id,需要根据topic的id获取特定topic 2) 获取这个topic关联的所有的回复 3) 如果某个回复有主人回复,需要查询出来 - 在TopicController中获取指定的topic - 具体这个topic中关联多少个Reply,由ReplyService内部实现 4) 获取到的topic中的author只有id,那么需要在topicService的getTopic方法中封装,在查询topic本身信息时,同时调用userBasicService中的获取userBasic方法,给author属性赋值 5) 同理,在reply类中也有author,而且这个author也是只有id,那么我们也需要根据id查询得到author,最后设置关联 4. 添加回复 5. 删除回复 1) 如果回复有关联的主人回复,需要先删除主人回复 2) 删除回复 Cannot delete or update a parent row: a foreign key constraint fails (`qqzonedb`.`t_host_reply`, CONSTRAINT `FK_host_reply` FOREIGN KEY (`reply`) REFERENCES `t_reply` (`id`)) 我们在删除回复表记录时,发现删除失败,原因是:在主人回复表中仍然有记录引用待删除的回复这条记录 如果需要删除主表数据,需要首先删除子表数据 6. 删除日志 1) 删除日志,首先需要考虑是否有关联的回复 2) 删除回复,首先需要考虑是否有关联的主人回复 3) 另外,如果不是自己的空间,则不能删除日志 今日内容: 1. 日期和字符串之间的格式化 /* // String -> java.util.Date String dateStr1 = "2021-12-30 12:59:59"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { Date date1 = sdf.parse(dateStr1); } catch (ParseException e) { e.printStackTrace(); } // Date -> String Date date2 = new Date(); String dateStr2 = sdf.format(date2); */ 2. thymeleaf中使用#dates这个公共的内置对象 ${#dates.format(topic.topicDate ,'yyyy-MM-dd HH:mm:ss')} 3. 系统启动时,我们访问的页面是: http://localhost:8080/pro23/page.do?operate=page&page=login 为什么不是: http://localhost:8080/pro23/login.html ? 答: 如果是后者,那么属于直接访问静态页面。那么页面上的thymeleaf表达式(标签)浏览器是不能识别的 我们访问前者的目的其实就是要执行 ViewBaseServlet中的processTemplete() 4. http://localhost:8080/pro23/page.do?operate=page&page=login 访问这个URL,执行的过程是什么样的? 答: http:// localhost :8080 /pro23 /page.do ?operate=page&page=login 协议 ServerIP port context root request.getServletPath() query string 1) DispatcherServlet -> urlPattern : *.do 拦截/page.do 2) request.getServletPath() -> /page.do 3) 解析处理字符串,将/page.do -> page 4) 拿到page这个字符串,然后去IOC容器(BeanFactory)中寻找id=page的那个bean对象 -> PageController.java 5) 获取operate的值 -> page 因此得知,应该执行 PageController中的page()方法 6) PageController中的page方法定义如下: public String page(String page){ return page ; } 7) 在queryString: ?operate=page&page=login 中 获取请求参数,参数名是page,参数值是login 因此page方法的参数page值会被赋上"login" 然后return "login" , return 给 谁?? 8) 因为PageController的page方法是DispatcherServlet通过反射调用的 method.invoke(....) ; 因此,字符串"login"返回给DispatcherServlet 9) DispatcherServlet接收到返回值,然后处理视图 目前处理视图的方式有两种: 1.带前缀redirect: 2.不带前缀 当前,返回"login",不带前缀 那么执行 super.processTemplete("login",request,response); 10) 此时ViewBaseServlet中的processTemplete方法会执行,效果是: 在"login"这个字符串前面拼接 "/" (其实就是配置文件中view-prefixe配置的值) 在"login"这个字符串后面拼接 ".html" (其实就是配置文件中view-suffix配置的值) 最后进行服务器转发 5. 目前我们进行javaweb项目开发的“套路”是这样的: 1. 拷贝 myssm包 2. 新建配置文件applicationContext.xml或者可以不叫这个名字,在web.xml中指定文件名 3. 在web.xml文件中配置: 1) 配置前缀和后缀,这样thymeleaf引擎就可以根据我们返回的字符串进行拼接,再跳转 <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 > 2) 配置监听器要读取的参数,目的是加载IOC容器的配置文件(也就是applicationContext.xml) <context-param > <param-name > contextConfigLocation</param-name > <param-value > applicationContext.xml</param-value > </context-param > 4. 开发具体的业务模块: 1) 一个具体的业务模块纵向上由几个部分组成: - html页面 - POJO类 - DAO接口和实现类 - Service接口和实现类 - Controller 控制器组件 2) 如果html页面有thymeleaf表达式,一定不能够直接访问,必须要经过PageController 3) 在applicationContext.xml中配置 DAO、Service、Controller,以及三者之间的依赖关系 4) DAO实现类中 , 继承BaseDAO,然后实现具体的接口, 需要注意,BaseDAO后面的泛型不能写错。 例如: public class UserDAOImpl extends BaseDAO<User > implements UserDAO{} 5) Service是业务控制类,这一层我们只需要记住一点: - 业务逻辑我们都封装在service这一层,不要分散在Controller层。也不要出现在DAO层(我们需要保证DAO方法的单精度特性) - 当某一个业务功能需要使用其他模块的业务功能时,尽量的调用别人的service,而不是深入到其他模块的DAO细节 6) Controller类的编写规则 ① 在applicationContext.xml中配置Controller <bean id ="user" class ="com.atguigu.qqzone.controllers.UserController" /> 那么,用户在前端发请求时,对应的servletpath就是 /user.do , 其中的“user”就是对应此处的bean的id值 ② 在Controller中设计的方法名需要和operate的值一致 public String login(String loginId , String pwd , HttpSession session){ return "index"; } 因此,我们的登录验证的表单如下: <form th:action ="@{/user.do}" method ="post" > <inut type ="hidden" name ="operate" value ="login" /> </form > ③ 在表单中,组件的name属性和Controller中方法的参数名一致 <input type ="text" name ="loginId" /> public String login(String loginId , String pwd , HttpSession session){ ④ 另外,需要注意的是: Controller中的方法中的参数不一定都是通过请求参数获取的 if("request".equals...) else if("response".equals....) else if("session".equals....){ 直接赋值 }else{ 此处才是从request的请求参数中获取 request.getParameter("loginId") ..... } 7) DispatcherServlet中步骤大致分为: 0. 从application作用域获取IOC容器 1. 解析servletPath , 在IOC容器中寻找对应的Controller组件 2. 准备operate指定的方法所要求的参数 3. 调用operate指定的方法 4. 接收到执行operate指定的方法的返回值,对返回值进行处理 - 视图处理 8) 为什么DispatcherServlet能够从application作用域获取到IOC容器? ContextLoaderListener在容器启动时会执行初始化任务,而它的操作就是: 1. 解析IOC的配置文件,创建一个一个的组件,并完成组件之间依赖关系的注入 2. 将IOC容器保存到application作用域 6. 修改BaseDAO,让其支持properties文件以及druid数据源连接池 讲解了两种方式: 1) 直接自己配置properties,然后读取,然后加载驱动..... 2) 使用druid连接池技术,那么properties中的key是有要求的
项目源码:
QQZone
Cookie
Session和Cookie的区别与联系
共同之处:
cookie和session都是用来跟踪浏览器用户身份的会话方式。
工作原理:
cookie的工作原理:
浏览器端第一次发送请求到服务器端
服务器端创建Cookie,该Cookie中包含用户的信息,然后将该Cookie发送到浏览器端
浏览器端再次访问服务器端时会携带服务器端创建的Cookie
服务器端通过Cookie中携带的数据区分不同的用户
Session的工作原理
浏览器端第一次发送请求到服务器端,服务器端创建一个Session,同时会创建一个特殊的Cookie(name为JSESSIONID的固定值,value为session对象的ID),然后将该Cookie发送至浏览器端
浏览器端发送第N(N>1)次请求到服务器端,浏览器端访问服务器端时就会携带该name为JSESSIONID的Cookie对象
服务器端根据name为JSESSIONID的Cookie的value(sessionId),去查询Session对象,从而区分不同用户。
name为JSESSIONID的Cookie不存在(关闭或更换浏览器),返回1中重新去创建Session与特殊的Cookie
name为JSESSIONID的Cookie存在,根据value中的SessionId去寻找session对象
value为SessionId不存在**(Session对象默认存活30分钟)**,返回1中重新去创建Session与特殊的Cookie
value为SessionId存在,返回session对象
区别:
cookie数据保存在客户端,session数据保存在服务端.
session
简单的说,当你登陆一个网站的时候,如果web服务器端使用的是session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话sessionid,服务器根据当前sessionid判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造。
cookie
sessionid是服务器和客户端连接时候随机分配的,如果浏览器使用的是cookie,那么所有数据都保存在浏览器端,比如你登陆以后,服务器设置了cookie用户名,那么当你再次请求服务器的时候,浏览器会将用户名一块发送给服务器,这些变量有一定的特殊标记。服务器会解释为cookie变量,所以只要不关闭浏览器,那么cookie变量一直是有效的,所以能够保证长时间不掉线。
区别对比:
cookie数据存放在客户的浏览器上,session数据放在服务器上
cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE
单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
所以:将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在COOKIE中
Cookie创建
创建客户端对象
1 Cookie cookie = new Cookie (key, value);
在客户端保存Cookie
设置Cookie有效时长
1 2 3 cookie.setMaxAge(30 ); cookie.setDomain(pattern); cookie.setPath(uri);
Cookie的应用
记住用户名和密码几天, setMaxAge(60* 60 * 24 * n)
几天免登陆, 当用户访问某个页面的时候,服务器端可以从cookie中获取用户名和密码,并获取用户上一次是否选择了免登陆,如果选择了,则直接登录
验证码Kaptcha
引入Kaptcha包
1 2 3 4 5 <dependency > <groupId > com.github.penggle</groupId > <artifactId > kaptcha</artifactId > <version > 2.3.2</version > </dependency >
配置Servlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <servlet > <servlet-name > Kaptcha</servlet-name > <servlet-class > com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class > <init-param > <param-name > kaptcha.border.color</param-name > <param-value > red</param-value > </init-param > <init-param > <param-name > kaptcha.textproducer.char.string</param-name > <param-value > abcdefg</param-value > </init-param > </servlet > <servlet-mapping > <servlet-name > Kaptcha</servlet-name > <url-pattern > /kaptcha.jpg</url-pattern > </servlet-mapping >
在页面上的img标签中引入我们配置的图片
1 <img src ="kaptcha.jpg" />
验证码中的各个参数属性在Constants中
KaptchaServlet在生成验证码图片的时候,同时会将验证码信息保存到session中 name = KAPTCHA_SESSION_KEY ,在上面的Constants类中有写
正则表达式
使用三步骤:
定义正则表达式对象
定义待校验的字符串
校验
创建正则表达式对象
对象形式:var reg = new RegExp("正则表达式")
当正则表达式中有"/"那么就使用这种
直接量形式:var reg = /正则表达式/
一般使用这种声明方式
正则表达式入门案例
模式验证: 校验字符串中是否包含’o’字母
正则对象.test(目标字符串) === 寻找目标字符串中是否含有正则对象,如果含有的话,返回true,否则返回false
1 2 3 4 5 6 7 8 var reg = /o/ ;var str = 'Hello World!' ;console .log ("字符串中是否包含'o'=" +reg.test (str));
正则表达式的匹配模式
全局查找
匹配读取: 读取字符串中的所有’o’
match方法,获取我们匹配成功的字符串,放到数组中并返回
通过给正则表达式后面添加一个g,来表示全局搜索,g == global
1 2 3 4 5 6 7 var reg2 = /l/g var arr = str.match (reg2);console .log (arr)
忽略大小写
在正则表达式后面添加一个i,来表示忽略大小写,i ==== ignore
1 2 3 4 5 6 7 8 9 10 11 12 13 var targetStr = 'Hello WORLD!' ;var reg = /o/gi ;var resultArr = targetStr.match (reg);console .log ("resultArr.length=" +resultArr.length );for (var i = 0 ; i < resultArr.length ; i++){ console .log ("resultArr[" +i+"]=" +resultArr[i]); }
多行查找
不使用多行查找模式,目标字符串中不管有没有换行符都会被当作一行
在正则表达式后面添加一个m,表示多行查找 m ===== many
正则表达式中在最后添加一个$ 表明要查询以此,正则表达式结尾字符串
1 2 3 4 5 6 7 8 9 ar targetStr01 = 'Hello\nWorld!' ; var targetStr02 = 'Hello' ;var reg = /Hello$/m ;console .log (reg.test (targetStr01));console .log (reg.test (targetStr02));
元字符
在正则表达式中被赋予特殊含义的字符,不能被直接当做普通字符使用。如果要匹配元字符本身,需要对元字符进行转义,转义的方式是在元字符前面加上“\”,例如:^
常用元字符
代码
说明
.
匹配除换行字符以外的任意字符。
\w
匹配字母或数字或下划线等价于[a-zA-Z0-9_]
\W
匹配任何非单词字符。等价于[^A-Za-z0-9_]
\s
匹配任意的空白符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]。
\S
匹配任何非空白字符。等价于[^\f\n\r\t\v]。
\d
匹配数字。等价于[0-9]。
\D
匹配一个非数字字符。等价于[^0-9]
\b
匹配单词的开始或结束
^
匹配字符串的开始,但在[]中使用表示取反
$
匹配字符串的结束
例子一
\s 匹配任意空白字符
1 2 3 4 5 6 var str = 'one two three four' ;var reg = /\s/g ;var newStr = str.replace (reg,'@' ); console .log ("newStr=" +newStr);
例子二
\d 表示匹配数组,\d+,表示匹配多个字符
1 2 3 4 5 var str = '今年是2014年' ;var reg = /\d+/g ;str = str.replace (reg,'abcd' ); console .log ('str=' +str);
例子三
\ ^ xxx,表示匹配以xxx开头的字符
1 2 3 4 5 6 7 var str01 = 'I love Java' ;var str02 = 'Java love me' ;var reg = /^Java/g ;console .log ('reg.test(str01)=' +reg.test (str01)); console .log ("<br />" );console .log ('reg.test(str02)=' +reg.test (str02));
例子四
xxx$表示匹配以xxx结尾的字符
1 2 3 4 5 6 7 var str01 = 'I love Java' ;var str02 = 'Java love me' ;var reg = /Java$/g ;console .log ('reg.test(str01)=' +reg.test (str01)); console .log ("<br />" );console .log ('reg.test(str02)=' +reg.test (str02));
字符集合
语法格式
示例
说明
[字符列表]
正则表达式:[abc] 含义:目标字符串包含abc中的任何一个字符 目标字符串:plain 是否匹配:是 原因:plain中的“a”在列表“abc”中
目标字符串中任何一个字符出现在字符列表中就算匹配。
[^字符列表]
[^abc] 含义:目标字符串包含abc以外的任何一个字符 目标字符串:plain 是否匹配:是 原因:plain中包含“p”、“l”、“i”、“n”
匹配字符列表中未包含的任意字符。
[字符范围]
正则表达式:[a-z] 含义:所有小写英文字符组成的字符列表 正则表达式:[A-Z] 含义:所有大写英文字符组成的字符列表
匹配指定范围内的任意字符。
正则表达式 - 表示范围, 0-9,表示0至 9
1 2 3 4 5 6 var str01 = 'Hello World' ;var str02 = 'I am Tom' ;var reg = /[abc]/g ;console .log ('reg.test(str01)=' +reg.test (str01));console .log ('reg.test(str02)=' +reg.test (str02));
出现次数
代码
说明
*
出现零次或多次
+
出现一次或多次
?
出现零次或一次
{n}
出现n次
{n,}
出现n次或多次
{n,m}
出现n到m次
1 2 3 console .log ("/[a]{3}/.test('aa')=" +/[a]{3}/g .test ('aa' )); console .log ("/[a]{3}/.test('aaa')=" +/[a]{3}/g .test ('aaa' )); console .log ("/[a]{3}/.test('aaaa')=" +/[a]{3}/g .test ('aaaa' ));
在正则表达式中表达『或者』
使用符号:|
1 2 3 4 5 6 7 var str01 = 'Hello World!' ;var str02 = 'I love Java' ;var reg = /World|Java/g ;console .log ("str01.match(reg)[0]=" +str01.match (reg)[0 ]);console .log ("str02.match(reg)[0]=" +str02.match (reg)[0 ]);
Ajax(了解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 第一步: 客户端发送异步请求;并绑定对结果处理的回调函数 1) <input type ="text" name ="uname" onblur ="ckUname()" /> 2) 定义ckUname方法: - 创建XMLHttpRequest对象 - XMLHttpRequest对象操作步骤: - open(url,"GET",true) - onreadyStateChange 设置回调 - send() 发送请求 - 在回调函数中需要判断XMLHttpRequest对象的状态: readyState(0-4) , status(200) 0 - (未初始化)还没有调用send ()方法 1 -(载入)已调用send ()方法,正在发送请求 2 -(载入完成 ) send ()方法执行完成,已经接收到全部响应内容 3 -(交互)正在解析响应内容 4 -(完成)响应内容解析完成,可以在客户端调用了 第二步:服务器端做校验,然后将校验结果响应给客户端
js代码
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 var xmlHttpRequest;function createXMLHttpRequest ( ){ if (window .XMLHttpRequest ){ xmlHttpRequest = new XMLHttpRequest (); }else if (window .ActiveXObject ){ try { xmlHttpRequest = new ActiveXObject ("Microsoft.XMLHTTP" ); }catch (e){ xmlHttpRequest = new ActiveXObject ("Msxm12.XMLHTTP" ); } } } function ckUname (uname ) { createXMLHttpRequest (); var url="user.do?operate=ckUname&uname=" +uname; xmlHttpRequest.open ("GET" ,url, true ); xmlHttpRequest.onreadystatechange = ckUnameCB; xmlHttpRequest.send (); } function ckUnameCB ( ){ if (xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200 ){ if (xmlHttpRequest.responseText === "{'uname':'1'}" ){ alert ("用户名已经被注册了!!!" ); }else { alert ("用户名可以使用" ); } }
服务器端代码
1 2 3 4 5 6 7 8 9 10 public String ckUname (String uname) { Boolean aBoolean = userService.ckUname(uname); if (aBoolean){ return "json:{'uname':'0'}" ; }else { return "json:{'uname':'1'}" ; } }
VUE(了解)
绑定数据
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ msg :"Hello World!!!" } }); } </script > </head > <body > <div id ="div0" > <span > {{msg}}</span > </div > </body > </html >
v-bind绑定数据
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ msg :"Hello World!!!" , uname :"你好" } }); } </script > </head > <body > <div id ="div0" > <span > {{msg}}</span > <input type ="text" v-bind:value ="uname" /> </div > </body > </html >
v-model绑定数据
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ msg :"Hello World!!!" , uname :"你好" } }); } </script > </head > <body > <div id ="div0" > <span > {{msg}}</span > <input type ="text" v-bind:value ="uname" /> <input type ="text" :value ="uname" /> <input type ="text" v-model:value ="msg" /> <input type ="text" v-model ="msg" /> </div > </body > </html >
trim去除首位空标签
v-if v-else
使用v-if和v-else来进行标签的显隐
并且v-if和v-else之间不能有其他的节点
如果v-if不成立的话,则该标签就直接删除了
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ msg :"文本标签" , num :"Hello World!!!" } }); } </script > </head > <body > <div id ="div0" > <span v-if ="num%2==0" > if成立</span > <span v-else ="num%2==0" > if不成立</span > </div > </body > </html >
v-show
使用v-show来控制标签的显隐,如果表达式不成立的话,改标签不会消失,而是会加上一个display:none的数据
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ msg :"文本标签" , num :"Hello World!!!" } }); } </script > </head > <body > <div id ="div0" > <span v-show ="num%2==0" > if成立</span > </div > </body > </html >
列表渲染
v-for,循环遍历数据
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ fruitList :[ {fname :"苹果" , fprice :5 , fcount :100 ,remark :"苹果很好吃" }, {fname :"香蕉" , fprice :5 , fcount :100 ,remark :"香蕉很好吃" }, {fname :"菠萝" , fprice :5 , fcount :100 ,remark :"菠萝很好吃" }, {fname :"西瓜" , fprice :5 , fcount :100 ,remark :"西瓜很好吃" }, ] } }); } </script > </head > <body > <div id ="div0" > <table > <tr > <th > 名称</th > <th > 单价</th > <th > 库存</th > <th > 备注</th > </tr > <tr v-for ="fruit in fruitList" > <td > {{fruit.fname}}</td > <td > {{fruit.fprice}}</td > <td > {{fruit.fcount}}</td > <td > {{fruit.remark}}</td > </tr > </table > </div > </body > </html >
事件驱动
v-on 标签
v-on:click;
v-on:blur;
v-on:mousemove;
反转字符串
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ msg :"hello world" }, methods :{ myReverse :function ( ){ this .msg = this .msg .split ("" ).reverse ().join ("" ); } } }); } </script > </head > <body > <div id ="div0" > <span > {{msg}}</span > <input type ="button" value ="反转" v-on:click ="myReverse" > </div > </body > </html >
侦听属性
watch,当属性改变的时候触发这个方法
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ num1 :0 , num2 :1 , num3 :2 , }, watch :{ num1 :function (newValue ){ this .num3 = parseInt (newValue)+parseInt (this .num2 ); }, num2 :function (newValue ){ this .num3 = parseInt (this .num1 )+parseInt (newValue); } } }); } </script > </head > <body > <div id ="div0" > <input type ="text" v-model ="num1" size ="2" > + <input type ="text" v-model ="num2" size ="2" > = <span > {{num3}}</span > </div > </body > </html >
生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script language ="JavaScript" src ="script/vue.js" > </script > <script language ="JavaScript" > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ msg :1 }, methods :{ changeMsg :function ( ){ this .msg = this .msg + 1 ; } }, beforeCreate :function ( ){ console .log ("beforeCreate:vue对象创建之前---------------" ); console .log ("msg:" +this .msg ); }, created :function ( ){ console .log ("created:vue对象创建之后---------------" ); console .log ("msg:" +this .msg ); }, beforeMount :function ( ){ console .log ("beforeMount:数据装载之前---------------" ); console .log ("span:" +document .getElementById ("span" ).innerText ); }, mounted :function ( ){ console .log ("mounted:数据装载之后---------------" ); console .log ("span:" +document .getElementById ("span" ).innerText ); }, beforeUpdate :function ( ){ console .log ("beforeUpdate:数据更新之前---------------" ); console .log ("msg:" +this .msg ); console .log ("span:" +document .getElementById ("span" ).innerText ); }, updated :function ( ){ console .log ("Updated:数据更新之后---------------" ); console .log ("msg:" +this .msg ); console .log ("span:" +document .getElementById ("span" ).innerText ); } }); } </script > </head > <body > <div id ="div0" > <span id ="span" > {{msg}}</span > <br /> <input type ="button" value ="改变msg的值" @click ="changeMsg" /> </div > </body > </html >
AXIOS
Axios是一个ajax的框架,简化ajax的操作
Axios操作步骤
添加axios.js文件
客户端向服务器异步发送普通参数值
基本格式:axios().then().catch()
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 axios01 :function ( ){ axios ({ method :"post" , url :"axios01.do" , params :{ uname :vue.uname , pwd :vue.pwd } }).then (function (value ){ console .log (value); }).catch (function (reason ) { console .log (reason); }) }
示例1:客户端向服务器端异步发送普通参数值
HTML文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script src ="script/axios.min.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ uname :"Nuyoah" , pwd :"OK" }, methods :{ axios01 :function ( ){ axios ({ method :"post" , url :"axios01.do" , params :{ uname :vue.uname , pwd :vue.pwd } }).then (function (value ){ console .log (value); }).catch (function (reason ) { console .log (reason); }) } } }); } </script > </head > <body > <div id ="div0" > <input type ="text" v-model ="uname" > <br /> <input type ="text" v-model ="pwd" > <br /> <input type ="button" @click ="axios01" value ="提交" > </div > </body > </html >
服务器端文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @WebServlet("/axios01.do") public class Axios01Servlet extends HttpServlet { @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uname = req.getParameter("uname" ); String pwd = req.getParameter("pwd" ); resp.setCharacterEncoding("utf-8" ); resp.setContentType("text/html;charset=utf-8" ); PrintWriter writer = resp.getWriter(); writer.print(uname+"_" +pwd); } }
示例二:客户端向服务器端发送JSON格式的数据
我们只需要将params改为data即可
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script src ="script/axios.min.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ uname :"Nuyoah" , pwd :"OK" }, methods :{ axios01 :function ( ){ axios ({ method :"post" , url :"axios02.do" , data :{ uname :vue.uname , pwd :vue.pwd } }).then (function (value ){ var data = value.data ; vue.uname = data.uname ; console .log (vue.uname ); vue.pwd = data.pwd ; }).catch (function (reason ) { console .log (reason); }) } } }); } </script > </head > <body > <div id ="div0" > <input type ="text" v-model ="uname" > <br /> <input type ="text" v-model ="pwd" > <br /> <input type ="button" @click ="axios01" value ="提交" > </div > </body > </html >
示例三:服务器端接受处理JSON并返回给客户端JSON格式的代码
服务器端可以通过req.getReader()函数来进行读取客户端传来的JSON格式的代码
服务器端可以通过Gson来将,客户端传来的JSON格式的代码,转换为JAVA对象,也可以将JAVA对象转换成JSON的格式
服务器端代码
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 package com.hgu.axios;import com.google.gson.Gson;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedReader;import java.io.IOException;import java.io.PrintWriter;@WebServlet("/axios02.do") public class Axios02Servlet extends HttpServlet { @Override protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { StringBuffer stringBuffer = new StringBuffer (); BufferedReader reader = req.getReader(); String str = null ; if ((str = reader.readLine())!= null ){ stringBuffer.append(str); } str = stringBuffer.toString(); System.out.println(str); Gson gson = new Gson (); User user = gson.fromJson(str, User.class); String s = gson.toJson(user); resp.getWriter().write(s); } }
客户端代码
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <script src ="script/vue.js" > </script > <script src ="script/axios.min.js" > </script > <script > window .onload =function ( ){ var vue = new Vue ({ "el" :"#div0" , data :{ uname :"Nuyoah" , pwd :"OK" }, methods :{ axios01 :function ( ){ axios ({ method :"post" , url :"axios02.do" , data :{ uname :vue.uname , pwd :vue.pwd } }).then (function (value ){ var data = value.data ; vue.uname = data.uname ; console .log (vue.uname ); vue.pwd = data.pwd ; }).catch (function (reason ) { console .log (reason); }) } } }); } </script > </head > <body > <div id ="div0" > <input type ="text" v-model ="uname" > <br /> <input type ="text" v-model ="pwd" > <br /> <input type ="button" @click ="axios01" value ="提交" > </div > </body > </html >
JS处理JSON
Object - > String ==== JSON.stringify(object)
String - > Ojbect ==== JSON.parse(str)
书城
第一阶段,未使用vue和Axios渲染的
Book01
第二阶段:使用vue和Axios渲染的
Book02