学习路线:前端路线—>后端技术—>后端高级—>前端框架

Alt text

HTML语言是解释性语言,不是编译型语言,浏览器是容错的。学习HTML语言,先来学习HTML标签。HTML是决定页面的展示内容的部分。

HTML基础标签

1.html语言的标签一般成对出现,信息编辑在标签中间,可以嵌套使用

1
2
3
4
html页面中由一对<html>标签组成:
<html>为开始标签
</html>为结束标签
<body>body标签里面是网页的所有内容</body>

2.title标签

1
<title>标签表示网页的标题

3.meta标签

1
<meta>标签可以设置编码的字符集

4.br标签

1
<br/>表示换行。br标签是一个单标签。单标签:开始标签和结束标签是同一个,斜杠放在单词后面

5.p标签

1
<p>标签表示段落标签

6.img标签

1
2
3
4
<img>标签表示图片标签,有以下4种属性
src属性表示图片文件的路径(绝对路径和相对路径都行)
width和height表示图片的大小,width是图片的长度,height是图片的宽度。
alt表示图片的提示

7.h1-h6标签

1
<h1-h6>:标题标签 ,数字越小字体越大,一级标题最大。

8.ol和ul列表标签,两者都包含li子标签

1
2
<ol></ol>有序列表,含<li></li>子元素标签,属性:start表示从数字*开始,type显示的类型:A  a  I  i  1(default)。(也就是选项是英文字母和罗马字符和数字)
<ul></ul>无序列表,含<li></li>子元素标签,属性:type显示的类型: disc(default) , circle , square

9.字体标签

1
<b>加粗标签  , <I>斜体标签  , <u>下划线标签  ,<sub>下标标签  ,  <sup>上标标签

10.span标签

1
2
<span>标签被用来组合文档中的行内元素。
注释:span 没有固定的格式表现。当对它应用样式时,它才会产生视觉上的变化。

11.a超链接href(hytertext reference)

1
2
3
<a>标签里可以添加href超链接属性,后面接域名,
还有target属性,target的值有四个:—self在本窗口打开 , —blank在新窗口打开 —parent在父窗口打开 —top在顶层窗口打开
注释:span 没有固定的格式表现。当对它应用样式时,它才会产生视觉上的变化。

12.div标签

1
<div></div>可以定义分区或节

13.hr标签创建水平线

1
<hr>水平分隔线(horizontal rule)可以在视觉上将文档分隔成各个部分。

14.HTML表格常用标签及其属性值
Alt text

15.HTML表单常用标签及其属性值
Alt text

1
2
3
form有两个重要属性<form action ="要发送的html域名" , method="post">  action指定发送的域名,method指定发送表单的方式,post最安全,get会暴露私人信息。
input type里必须指定name属性,value为用户填写的值。
input type=“radio”时要自己填写value值

16.HTML字符实体常用表
Alt text


CSS (Cascading Style Sheets,层叠样式表),是一种用来为结构化文档(如 HTML 文档或 XML 应用)添加样式(字体、间距和颜色等)的计算机语言,CSS文件扩展名为 .css。

Alt text

1.CSS最基本的分类:标签样式表(格式:标签名+{},使用该标签就会自动使用)、类样式表(格式:. +类名+{},使用class=“类名”才会引用)、ID样式表(格式:# + ID名+{},使用id=”ID名”才会引用)

1
2
3
4
CSS从位置上的分类:
嵌入式样式表(在标签里使用style css语言)、
内部样式表(在成对<style>标签里的css语言)、
外部样式表(通过单标签<link>和超链接href使用外部style样式)。如果对同一位置使用了多种样式,遵循就近原则。

2.CSS盒子模型

2.1 border边框

2.2 margin间距

2.3 padding填充

IE浏览器:实际尺寸=width
Chrome浏览器:实际尺寸=width+左右borderwidth+padding
如果要设置div紧贴网页的话可以在CSS里设置body标签的margin=0且padding=0。

CSS中的position属性有两种:absolute和relative。
absolute要搭配坐标进行使用,默认水平向右为X轴(使用left:+ 位移像素进行向右位移),竖直向下为Y轴(使用top:+ 位移像素进行向下位移)。
float属性:左/右浮动为float:left/right 设置左右浮动能消除间隙也能修改参照物

CSS属于前端需要掌握内容,后端只需要了解即可,需要使用的时候可以查询CSS参考文档。


JavaScript (简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。可以看做是客户端的一个脚本语言,虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式。

1
2
引用js脚本-外部链接方式-示范
<script> type="text/javascript" src="存储路径"> </script>

1.js是一种弱类型的语言,数据类型由变量后面所赋的值决定。

2.js的基本数据类型:

2.1数值型:js不区分整数、小数

2.2字符串:不区分字符、字符串。单引号和双引号的意思是一样的。

2.3布尔型:true、false。

在js中,其他类型和布尔类型的自动转换。
true:非零的数值、非空字符串、非空对象。
false:零、空字符串、null、undefined。

3.js的引用类型

3.1所有new出来的对象

3.2用[]声明的数组

3.3用{}声明的对象

4.变量

关键字:var
数据类型:js变量可以接收任意数据类型的数据
标识符:严格区分大小写
变量使用规则:
1.如果使用了一个没有声明的变量,那么会在运行时报错
2.如果声明一个变量没有初始化,那么这个变量的值就是underfined

5.函数

内置函数:系统已经声明好了可以直接使用的函数

5.1弹出警告框

alert(“警告框内容”);

5.2弹出确认框

Alt text

5.3在控制台打印日志

Alt text

5.4声明函数

Alt text

5.5调用函数

Alt text

JAVAWEB前端开发一般将html和css和JavaScript分开
html负责网页文档 css负责网页样式 JavaScript负责网页脚本
三者分开写,不要一个文档里什么都写,后期维护很乱,不方便统一管理。

网页开发的循序渐进:Java—>Database—>JDBC | HTML—>CSS—>JS

网页开发架构模式:CS和BS

CS:(Client-Server)客户端和服务器架构模式

优点:充分利用客户端机器的资源,减轻服务器的负荷。(一部分安全要求不高的计算任务放在客户端执行,不需要把所有的计算和存储都放在服务器执行,从而能够减轻服务器的压力,也能够减轻网络负荷)
缺点:需要安装客户端,升级维护成本较高。(现在维护已经很方便了,大多数游戏都是采用CS模式。)

BS:(Browser-Server)浏览器服务器架构模式

优点:不需要安装客户端;维护成本较低。
缺点:所有的计算和存储服务都是放在服务器端的,服务器的负荷较重;在服务端计算完成之后再把结果传输给客户端,因此客户端和服务器端会进行非常频繁的数据通信,从而网络负荷较重。(比如B站之类的)

Tomcat  服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应HTML标准通用标记语言下的一个应用)页面的访问请求。实际上Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。

当配置正确时,Apache 为HTML页面服务,而Tomcat 实际上运行JSP 页面和Servlet。另外,Tomcat和IIS等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache服务器。

如今JSP已被淘汰,目前大中公司都是前后端分离的,前端用Nginx,后端Tomcat,只有小公司还在用Tomcat然后写jsp这种小架构网页,开发需要前后端一起开发,出bug问题难以归咎。

Tomcat目录结构说明:

bin——可执行文件目录
conf——配置文件目录
lib——存放library的目录
logs——日志文件目录
webapps——项目部署目录
work——工作目录
temp——临时目录

配置环境变量

Tomcat也是用java和C来写的,所以需要jdk环境。配置好jdk环境后建议还要配置CATALINA_HOME环境变量,再来配置Path环境变量到bin目录下,方便在命令行窗口使用startup.bat启动Tomcat。

新建部署WEB项目——将网页所需文件转移到webapps目录下,新建WEB-INF文件夹即可。启动startup.bat部署tomcat,填入http:IP地址+端口号8080+webapps目录下的html文件路径即可进行访问。

Tomcat在idea的部署步骤:配置好jdk环境后,新建项目右键add framework support

添加web application(4.0),接着配置!!!项目jdk一致、路径和依赖!!!添加完后在WEB-INF下创建classes和lib文件夹,然后点击file——>project structure,项目的SDK和language level需要一致,然后点击模块,点击使用模块编译输出路径,把路径配置到classes文件夹。接着配置依赖jar包:点击+号,选择jar或目录,选择自己项目的lib目录,添加Jar Directoryr然后勾选lib路径按确定。
然后配置点击Run选择edit configuration,点击+,添加新配置模板选择Tomcat服务器local,点击server配置tomcat文件夹路径,jre选择自己安装的jdk对应的版本,接着点击deployment部署工件,下面的application content最好填自己的web项目工程名称。这样方便之后直接添加html文件进行快速访问网页。到这里已经部署完毕。可以点击Run——>Run——>选择tomcat服务器即可启动tomcat。

Servlet 是 Server Applet 的缩写,译为“服务器端小程序”,是一种使用 Java 语言来开发动态网站的技术。

严格来说,Servlet 只是一套 Java Web 开发的规范,或者说是一套 Java Web 开发的技术标准(接口)。只有规范并不能做任何事情,必须要有人去实现它。所谓实现 Servlet 规范,就是真正编写代码去实现 Servlet 规范提到的各种功能,包括类、方法、属性等。
Servlet 规范是开放的,除了 Sun 公司,其它公司也可以实现 Servlet 规范,目前常见的实现了 Servlet 规范的产品包括 Tomcat、Weblogic、Jetty、Jboss、WebSphere 等,它们都被称为“Servlet 容器”。Servlet 容器用来管理程序员编写的 Servlet 类。

JavaWeb三大组件:Servlet程序,Filter过滤器,Listener监听器。

Servlet是运行在服务器上的一个java小程序,它可以接收客户端发送过来的请求,并相应数据给客户端。

手动实现servlet的步骤

1.编写一个类去实现servlet接口或者继承HttpServlet类

2.重写service方法或者重写dopost方法,接收客户端处理请求,并响应数据。

3.到web.xml中去配置servlet程序的访问地址:虚拟地址(本质就是映射)

web.xml中配置servlet程序访问地址——>以便之后填写的html文件可以向此程序发送请求。
Alt text

Alt text

Tomcat细节

1.Tomcat设置编码字符集

Tomcat8之后的版本只需要对post请求设置编码字符集,使用以下代码即可。此代码必须在获取参数动作之前。建议一般放在第一行。
request.setCharacterEncoding(“UTF-8”);

2.Servlet的继承关系

javax.servlet.Servlet接口
javax.servlet.GenericServlet抽象类
javax.servlet.http.HttpServlet抽象子类

3.相关方法

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) - 不是抽象的
源码解析:![[Pasted image 20230103212604.png]]

小结:

1)继承关系:HttpServlet——>GenericServlet——>Servlet

2) Servlet中的核心方法:Init初始化方法、Service服务方法、Destory销毁方法。

3) Service()方法:当有请求过来时,service方法会自动响应(其实是Tomcat自动调用),

在HttpServlet中我们会去分析请求的方式:到底是GET、POST、等……再决定调用的是哪个do开头的方法,那么在HttpServlet中这些do方法默认都是405的实现风格——要我们新建Servlet继承HttpServlet并且重写do方法!!!不重写就会调用HttpServlet的do方法,然后报405错误。

4)因此,我们在新建Servlet的时候,我们才会考虑请求方法,从而决定重写的do方法。

4.Servlet的生命周期

1)生命周期:从出生到死亡的过程就是生命周期。对应Servlet的三大方法:Init()、Service()、Destroy()。
2)默认情况下:
第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用Init方法)、然后服务(调用service方法)。
从第二次请求开始,每一次都是服务。
当容器关闭时,其中所有的servlet实例都会销毁,(调用destroy方法)。
3)Servlet实例Tomcat只会创建一个,所有的请求都是这个实例去响应。
默认情况下,第一次请求时,Tomcat才会去实例化、初始化、然后再服务。这样的好处是什么?提高系统的启动速度。 这样的缺点是什么?第一次请求时,耗时较长。
因此得出结论:如果需要提高系统的启动速度,当前默认情况就是这样;如果想要提高响应速度,我们应该设置servlet的初始化时机。
Tomcat对于Servlet实例构造也是底层通过反射调用构造方法,默认情况下是不能访问私有的构造方法的Tomcat底层没实现暴力反射,如果要访问私有的构造方法,就必须setaccessible=true才行。
4)Servlet的初始化时机

1
2
默认都是第一次接收请求时,实例化,初始化
我们可以通过<load-on-startup>来设置Servlet的启动先后顺序,数字越小,启动越靠前,最小值0。这样的坏处?当servlet程序一旦躲起来,启动很耗时,但是一旦启动完成,即可马上提供响应服务。

5)Servlet在容器中是:单例的、线程不安全的
单例:所有的请求都是同一个实例去响应
线程不安全:资源共享并且没有加锁,如果某一线程修改资源内容后,将引发另一线程的执行路径发生变化。
因此,尽量不要再servlet中定义成员变量。如果不得不定义成员变量,千万不要修改成员变量的值(人多需要注释一下),千万不要使用成员变量做一些逻辑判断。
6)Servlet3.0开始支持注解:@WebServlet

5.Http协议(Hyper Text Transfer Protocol超文本传输协议)

第六章 HTTP协议 | 代码重工 (gitee.io)
Http最大的作用是确定了请求和响应数据的格式。
浏览器发送给服务器的数据:请求报文。
服务器返回给浏览器的数据:响应报文。

1)Http 称之为超文本传输协议

2)Http 是无状态的

3)Http请求响应包含两个部分:请求和响应

-请求:
请求包含三个部分:1.请求行 ; 2.请求消息头 ; 3.请求体
1)请求行包含三个信息:1.请求的方式 ; 2.请求的URL ; 3.请求的协议(一般都是HTTP1.1)
2)请求消息头中包含了很多客户端需要告诉服务器的信息,比如浏览器型号,版本,我能接收的内容的类型,我给你发送的内容的类型
3)请求体,三种情况
get方式,没有请求体,但是有一个queryString
post方式,有请求体,form data
json格式,有请求体,request payload

-响应:
响应也包含三个部分: 1.响应行 ; 2.响应头 ; 3.响应体
1)响应行包含三个信息:1.协议 2.响应状态码(200) 3.响应状态(ok)
2)响应头:包含了服务器的信息;服务器发送给浏览器的信息(内容的媒体类型、编码、内容长度等)
3)响应体:响应的实际内容 (比如请求add.html页面时,响应页面就是我们自己写的网页内容。)

6.会话

1)Http是无状态的

 -HTTP无状态:服务器无法判断这两次请求是否是同一个客户端发过来的,还是不同客户端发过来的。无法区分客户端的是否连续行为。
 -无状态带来的现实问题:第一次请求时添加商品到购物车,第二次请求时结账;如果不能实现逻辑的连续就会发生帮其他客户端结账的错误。
 -通过会话跟踪技术来解决无状态的问题。

2) 会话跟踪技术

 -客户端第一次发送请求个服务器时,服务器获取session,获取不到就通过cookies方式创建新sessionID给客户端使用。
 -下次客户端再给服务器发送请求时,服务其获取客户端的session,通过获取到的sessionID就能知道来自哪一个客户端的请求了。即给客户端一个指定ID来区分。
 -常用的API:
     request.getSession()——>获取当前会话,没有则创建新的会话
     request.getSession(true)——>效果同上
     request.getSession(false)——>获取当前会话,如果没有则返回null,不会创建新会话
     session.getId()——>获取当前SessionID(全局唯一的)
     session.isNew()——>判断当前session是否是新的,第一次是true,第二次为false
     session.getMaxInactiveInterval()——>当前session最大非激活间隔时长:默认1800秒 
     session.invalidate()——>强制性使会话立即无效
     session.getCreateTime()——>获取会话创建时间(返回的为时间戳)
     session.getLastAccessedTime()——>获取会话最近连接时间(返回的为时间戳)

3)session保存作用域

 -session保存作用域是和具体的某一个session对应的
 -常用的API:
     void session.setAttribute(k,v)
     object session.getAttribute(k)
     void removeAttribute(k)

7.服务器端内部转发以及客户端重定向

1)服务器内部转发:request.getRequestDispatcher(“…..”).forward(request,response);
-一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的。
-地址栏没有变化
2) 客户端重定向:response.sendRedirect(“…….”);
-两次请求响应的过程。客户端肯定知道请求URL的变化。
-地址栏有变化
-即第一次请求后,服务端让客户端给另一个地址栏再发一次请求。

8. Thymeleaf - 视图模板技术

使用Thymeleaf的操作步骤

//1.添加Jar包

Alt text

//2.配置上下文参数

1.添加web.xml文件
Alt text
2.配置参数
Alt text

//3.创建Servlet子类 

Alt text
Alt text

//4.创建servlet子类继承第三步的类(ViewBaseServlet)并重写dopost、doget等方法,并创建index.html(名字随意)网页添加该子类的超链接,点击超链接就会跳转thymeleaf页面

上述配置完成后,接下来是如何使用

1.创建servlet子类并在web.xml配置映射访问地址,后期可以注解替换web.xml配置

Alt text

2.继承第三步的类(ViewBaseServlet),间接继承HttpServlet类,这样两个类的功能都能用(可以重写doGet又可以重写processTemplate)。重写doGet等响应方法然后使用重写processTemplate方法跳转到thymeleaf页面(可以是接下来要渲染的网页)简单来说,即通过这个TestThymeleafServlet重写所要用的doget、dopost等方法,然后在重写方法里使用processTemplate方法来渲染我们要接下来访问的网页页面

Alt text

3.新建index.html然后在里面配置上面类的超链接,一旦点击超链接就会跳转到thymeleaf网页。重点!!!如果thymeleaf页面最后跳转到index网页上而不是另起的新网页的话,两个两页的资源会整合。我们称之为thymeleaf渲染index网页。index网页(有死数据和活数据),thymeaf可以获取动态数据渲染index网页的所需要活数据。这样静态资源和动态资源就可以整合到同一个网页上。网页框架是静态的,网页数据是动态的。

Alt text
Alt text

Servlet中的保存作用域

保存作用域有4个:page(jsp用的多,现在几乎不用了)、request(一次请求有效,只要是一次请求内都行,可以在网页内使用转发多次转发响应,重定向不可用)、session(一次会话有效,可以多次获取)、application(一次应用程序范围有效,只要第一次存入了该数据,在当前应用中其他网页都能获取该数据)。

//request获取保存作用域的值,可以在网页内转发后响应,不可重定向。
request.setAttribute(k,v);
request.getRequestDispatcher("the name of html").forward(request,response);
Object obj=request.getAttribute(k);
System.out.println(obj);


//session获取保存作用域的值,同一个会话(同一个客户端一段时间内)就可以获得,不管是否重定向。
request.getSession.setAttribute(k,v);
request.getRequestDispatcher("the name of html").forward(request,response);
Object obj=request.getSession.getAttribute(k);
System.out.println(obj)


//application获取保存作用域的值,只要第一次请求设置了该键值对,其他客户端都可以获取,但只在当前应用程序有效。即应用程序共享。
ServletContext application=request.getServletContext();
application.setAttribute(k,v);
request.getRequestDispatcher("the name of html").forward(request,response);
Object obj=application.getAttribute(k);
System.out.println(obj) 

Thymeleaf基本语法

第八章 Thymeleaf | 代码重工 (gitee.io)
必须先引入Thymeleaf名称空间的声明,才可以使用Thymeleaf语法
Alt text

1.修改标签文本值
Alt text

2.修改指定属性的值
Alt text

3.解析URL地址
Alt text
Alt text
Alt text
Alt text

4.直接执行表达式
Alt text

总结Thymeleaf的作用

Thymeleaf是用来渲染我们的网页获取动态数据的,一个正常的网页有部分是静态数据,一部分是动态数据,我们如果直接访问自己写的index.html网页的话,数据是静态的,不灵活的,为了获取动态的数据,我们可以通过Servlet来调用Thymeleaf渲染技术(而且还能通过Servlet的保存作用域来转发数据给Thymeleaf技术引用,继而渲染在网页上)来渲染我们的index.html网页并响应给请求端。

Thymeleaf访问servlet域对象
Alt text
Alt text
Alt text

在Servlet中将数据存入属性域
Alt text
Alt text
Alt text

Thymeleaf语法:获取请求参数
Alt text
Alt text
Alt text

做单表查询项目Index网页的总结:

review:

1. 最初的做法是: 一个请求对应一个Servlet,这样存在的问题是servlet太多了

2. 把一系列的请求都对应一个Servlet来统筹管理, IndexServlet/AddServlet/EditServlet/DelServlet/UpdateServlet -> 合并成FruitServlet

通过一个operate的值来决定调用FruitServlet中的哪一个方法
使用的是switch-case

3. 在上一个版本中,Servlet中充斥着大量的switch-case,试想一下,随着我们的项目的业务规模扩大,那么会有很多的Servlet,也就意味着会有很多的switch-case,这是一种代码冗余

因此,我们在servlet中使用了反射技术,我们规定operate的值和方法名一致,那么接收到operate的值是什么就表明我们需要调用对应的方法进行响应,如果找不到对应的方法,则抛异常

4. 在上一个版本中我们使用了反射技术,但是其实还是存在一定的问题:每一个servlet中都有类似的反射技术的代码。因此继续抽取,设计了中央控制器类:DispatcherServlet

DispatcherServlet这个类的工作分为两大部分:

1.根据url定位到能够处理这个请求的controller组件:

1)从url中提取servletPath : /fruit.do -> fruit
2)根据fruit找到对应的组件:FruitController , 这个对应的依据我们存储在applicationContext.xml中
  <bean id="fruit" class="com.atguigu.fruit.controllers.FruitController/>
  通过DOM技术我们去解析XML文件,在中央控制器中形成一个beanMap容器,用来存放所有的Controller组件
3)根据获取到的operate的值定位到我们FruitController中需要调用的方法

2.调用Controller组件中的方法:

1) 获取参数
   获取即将要调用的方法的参数签名信息: Parameter[] parameters = method.getParameters();
   通过parameter.getName()获取参数的名称;
   准备了Object[] parameterValues 这个数组用来存放对应参数的参数值
   另外,我们需要考虑参数的类型问题,需要做类型转化的工作。通过parameter.getType()获取参数的类型
2) 执行方法
   Object returnObj = method.invoke(controllerBean , parameterValues);
3) 视图处理
   String returnStr = (String)returnObj;
   if(returnStr.startWith("redirect:")){
      response.sendRedirect(returnStr);
   }else{
     super.processTemplate(returnStr,request,response);
   }

Servlet的初始化研究

1. 再次学习Servlet的初始化方法

  1. Servlet生命周期:实例化、初始化、服务、销毁
  2. Servlet中的初始化方法有两个:init() , init(config)
    1
    2
    3
    4
    5
    6
    7
    8
    其中带参数的方法代码如下:
    public void init(ServletConfig config) throws ServletException {
    this.config = config ;
    init();
    }
    另外一个无参的init方法如下:
    public void init() throws ServletException{
    }
    如果我们想要在Servlet初始化时做一些准备工作,那么我们可以重写init方法
    我们可以通过如下步骤去获取初始化设置的数据
  • 获取config对象:ServletConfig config = getServletConfig();
  • 获取初始化参数值: config.getInitParameter(key);
  1. 在web.xml文件中配置Servlet
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <servlet>
    <servlet-name>Demo01Servlet</servlet-name>
    <servlet-class>com.atguigu.servlet.Demo01Servlet</servlet-class>
    <init-param>
    <param-name>hello</param-name>
    <param-value>world</param-value>
    </init-param>
    <init-param>
    <param-name>uname</param-name>
    <param-value>jim</param-value>
    </init-param>
    </servlet>
    <servlet-mapping>
    <servlet-name>Demo01Servlet</servlet-name>
    <url-pattern>/demo01</url-pattern>
    </servlet-mapping>
  2. 也可以通过注解的方式进行配置:
    1
    2
    3
    4
    5
    @WebServlet(urlPatterns = {"/demo01"} ,
    initParams = {
    @WebInitParam(name="hello",value="world"),
    @WebInitParam(name="uname",value="jim")
    })

2. 学习Servlet中的ServletContext和context-param

1
2
3
4
5
6
7
1) 获取ServletContext,有很多方法
在初始化方法中: ServletContext servletContext = getServletContext();
在服务方法中也可以通过request对象获取,也可以通过session获取:
request.getServletContext(); session.getServletContext()
2) 获取初始化值:
servletContext.getInitParameter();
3) 通过xml配置文件设置<context-param>上下文的参数

MVC

MVC:Model-View-Controller(SpringMVC框架入门)
M:Model 模型;V:view 视图 ; C:Controller 控制器 ;
讲项目分成三个方面:Model层、View层、Controller层。
Nodel有很多种类:数据访问模型(DAO);业务逻辑模型(BO);值对象模型(POJO);数据传输对象(DTO)等。。。
View:视图主要负责用于和用户方便进行数据交互和操作。
Controller:控制器主要负责Servlet的转发和调用,根据传入数据进行解析,决定调用对应的下层控制器,下层控制器又调用Service层执行业务操作,Service层又调用一种或多种DAO层的单精度方法来实现业务操作并与数据库进行关联,最终实现业务。

IOC和DI

IOC:Inversion of Control(控制反转)
DI:Dependence Injection

控制反转:

1) 之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl();
   这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别;
   如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别;
   程序员写对象的位置控制了对象的生命周期(作用域)。
2) 之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中,BeanFactory就是一个IOC容器。我们将程序员控制对象的作用域的权利转移到了IOC容器里,由容器来控制对象的作用域。
   因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转。

依赖注入:

1) 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();
   那么,控制层和service层存在耦合。
2) 之后,我们将代码修改成FruitService fruitService = null ;
   然后,在配置文件中配置:
   <bean id="fruit" class="FruitController">
        <property name="fruitService" ref="fruitService"/>
  </bean>

以前我们总是在类中主动创建新的依赖对象,但是这样两个类的耦合度太高,好的项目是高内聚低耦合的,为了降低类与类之间的耦合度,需要创建依赖对象并设置初始值为null,然后通过配置文件properties和反射技术,把依赖对象的值重写为依赖对象。

Filter过滤器

过滤器Filter

  1. Filter也属于Servlet规范(接口)。
  2. Filter开发步骤:新建类实现Filter接口,然后实现其中的三个方法:init、doFilter、destroy方法
    配置Filter:可以用注解@WebFilter,也可以使用xml文件 filter标签 + filter-mapping标签来进行配置(与servlet配置相似)
  3. Filter在配置时,和servlet一样,也可以配置通配符,例如 @WebFilter(“*.do”)表示拦截所有以.do结尾的请求。完整过程为:接收到请求时第一次拦截,符合放行规则就放行,放行后与Servlet进行交互后响应数据返回时第二次拦截,符合放行规则就放行,响应数据给客户端。
  4. 过滤器链
    1)执行的顺序依次是: A B C demo03 C2 B2 A2
    2)如果采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的
    3)如果采取的是xml的方式进行配置,那么按照配置的先后顺序进行排序

事务管理

1 涉及到的组件:

 - OpenSessionInViewFilter
 - TransactionManager
 - ThreadLocal
 - ConnUtil
 - BaseDAO

解析:这部分是JDBC与WEB联系起来的关键。
JDBC是Java与数据库连接的桥梁。Filter是JDBC和WEB连接的桥梁。
BaseDAO里是对数据库进行增删改查的单精度方法。
ConnUtil是获取连接的工具类,可以通过加载驱动,也可以通过配置文件来创建对数据库的连接。ThreadLocal是线程本地工具类,为了保证整个事务过程中使用的连接务必是同一个。

2 ThreadLocal再研究

 - get() , set(obj)
 - ThreadLocal称之为本地线程 。 我们可以通过set方法在当前线程上存储数据、通过get方法在当前线程上获取数据
 - set方法源码分析:
 public void set(T value) {
     Thread t = Thread.currentThread(); //获取当前的线程
     ThreadLocalMap map = getMap(t);    //每一个线程都维护各自的一个容器(ThreadLocalMap)
     if (map != null)
         map.set(this, value);          //这里的key对应的是ThreadLocal,因为我们的组件中需要传输(共享)的对象可能会有多个(不止Connection)
     else
         createMap(t, value);           //默认情况下map是没有初始化的,那么第一次往其中添加数据时,会去初始化
 }
 -get方法源码分析:
 public T get() {
     Thread t = Thread.currentThread(); //获取当前的线程
     ThreadLocalMap map = getMap(t);    //获取和这个线程(企业)相关的ThreadLocalMap(也就是工作纽带的集合)
     if (map != null) {
         ThreadLocalMap.Entry e = map.getEntry(this);   //this指的是ThreadLocal对象,通过它才能知道是哪一个工作纽带
         if (e != null) {
             @SuppressWarnings("unchecked")
             T result = (T)e.value;     //entry.value就可以获取到工具箱了
             return result;
         }
     }
     return setInitialValue();
 }
 

可以将ThreadLocal和ConnUtil一起封装成JDBCUtil工具类,统一完成获取连接,还能使用druid连接池。
然后TransactionManager里封装事务管理的基本步骤:
1.开启事务:setAutoCommit(false); 2.事务提交:Commit(); 线程释放; 3.事务回滚:rollback();线程释放;

Alt text

最后:在OpenSessionInViewFilter里重写doFilter方法,统一try….catch….并融入事务管理。事务提交要么一起成功,要么一起失败。
Alt text

Listener

Listener:监听器。

1) ServletContextListener - 监听ServletContext对象的创建和销毁的过程
2) HttpSessionListener - 监听HttpSession对象的创建和销毁的过程
3) ServletRequestListener - 监听ServletRequest对象的创建和销毁的过程

4) ServletContextAttributeListener - 监听ServletContext的保存作用域的改动(add,remove,replace)
5) HttpSessionAttributeListener - 监听HttpSession的保存作用域的改动(add,remove,replace)
6) ServletRequestAttributeListener - 监听ServletRequest的保存作用域的改动(add,remove,replace)

7) HttpSessionBindingListener - 监听某个对象在Session域中的创建与移除
8) HttpSessionActivationListener - 监听某个对象在Session域中的序列化和反序列化

不用调用,只需要注释或者XML里配置即可使用。

ServletContextListener的应用 - ContextLoaderListener

Alt text

Web项目开发流程

1.业务需求

根据业务需求进行需求分析,抽取出业务需求所需要的接口(或者功能)

如QQZone业务需求:

  1. 用户登录
  2. 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
  3. 查看日志详情:
    • 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
    • 回复列表(回复者的头像、昵称、回复内容、回复日期)
    • 主人回复信息
  4. 删除日志
  5. 删除特定回复
  6. 删除特定主人回复
  7. 添加日志、添加回复、添加主人回复
  8. 点击左侧好友链接,进入好友的空间

2.数据库设计

根据业务功能抽取出pojo实体类
封装pojo实体类的属性
分析pojo实体类之间的关系

如QQZone数据库设计:
1) 抽取实体 : 用户登录信息、用户详情信息 、 日志 、 回贴 、 主人回复
2) 分析其中的属性:
- 用户登录信息:账号、密码、头像、昵称
- 用户详情信息:真实姓名、星座、血型、邮箱、手机号…..
- 日志:标题、内容、日期、作者
- 回复:内容、日期、作者、日志
- 主人回复:内容、日期、作者、回复
3) 分析实体之间的关系
- 用户登录信息 : 用户详情信息 1:1
- 用户 : 日志 1:N
- 日志 : 回复 1:N
- 回复 : 主人回复 1:1 或者N:1或者N :M
- 用户 : 好友 M : N

3.数据库的范式

1) 第一范式:列不可再分
2) 第二范式:一张表只表达一层含义(只描述一件事情)
3) 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖

4.数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况作出选择

  • 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率
  • 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能

前面4步为需求分析和数据库设计,为开发前置工作。优秀的分析和设计会为后续实际开发带来清晰明确的开发思路和上线后维护的思路。

5.Web项目实际开发(基于SpringMVC和thymeleaf)

SpringMVC框架通用代码jar导入:包含BaseDAO类,IOC容器类,filter类,listener类,transaction类,
ViewBaseServlet类,controller类,dispatcherServlet类,配置文件和一些工具类。
开发编写顺序为pojo实体层——>DAO层——>Service层——>Controller层。
pojo实体类——>(DAO层接口——>DAO层实现类)——>(Service层接口——>Service层实现类)——>(Controller层——>控制转发方法进行然后进行视图处理)。

详细开发套路:

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
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.补充内容:

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这个公共的内置对象

1
${#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”,webContext,resp.getWriter());
  10. 此时ViewBaseServlet中的processTemplete方法会执行,效果是:
    在”login”这个字符串前面拼接 “/“ (其实就是配置文件中view-prefixe配置的值)
    在”login”这个字符串后面拼接 “.html” (其实就是配置文件中view-suffix配置的值)
    最后进行服务器转发

尚硅谷书城项目笔记

1. 需求分析

2. 数据库设计

1) 实体分析
- 图书 Book
- 用户 User
- 订单 OrderBean
- 订单详情 OrderItem
- 购物车项 CartItem
2) 实体属性分析
- 图书 : 书名、作者、价格、销量、库存、封面、状态
- 用户 : 用户名、密码、邮箱
- 订单 : 订单编号、订单日期、订单金额、订单数量、订单状态、用户
- 订单详情 : 图书、数量、所属订单
- 购物车项 : 图书、数量、所属用户

3. 显示主页面(index页面)

  • 新建BookDAO 、 BookDAOImpl : getBookList()
  • 新建BookService 、 BookServiceImpl : getBookList()
  • 新建BookController : index()
  • 编辑index.html

4. 首页上登录成功之后显示欢迎语和购物车数量

5. 点击具体图书的添加到购物车按钮

6. 购物车详情

7. 结账

  1. 订单表添加一条记录
  2. 订单详情表添加7条记录
  3. 购物车项表中需要删除对应的7条记录

8. 关于订单信息中的订单数量问题

9. 编辑购物车

10. 关于金额的精度问题

11. 过滤器判断是否是合法用户:

  • 解决方法:新建SessionFilter , 用来判断session中是否保存了currUser
  • 如果没有currUser,表明当前不是一个登录合法的用户,应该跳转到登录页面让其登录

12. Cookie(代码重工 (gitee.io)

  1. 创建Cookie对象
  2. 在客户端保存Cookie
  3. 设置Cookie的有效时长
    cookie.setMaxAge(60) , 设置cookie的有效时长是60秒
    cookie.setDomain(pattern);
    cookie.setPath(uri);
  4. Cookie的应用:
    4-1: 记住用户名和密码十天 setMaxAge(60 * 60 * 24 * 10)
    4-2: 十天免登录

13. Kaptcha(代码重工 (gitee.io)

  1. 为什么需要验证码
  2. kaptcha如何使用:
    • 添加jar包
    • 在web.xml文件中注册KaptchaServlet,并设置验证码图片的相关属性
    • 在html页面上编写一个img标签,然后设置src等于KaptchaServlet对应的url-pattern
  3. kaptcha验证码图片的各个属性在常量接口:Constants中
  4. KaptchaServlet在生成验证码图片时,会同时将验证码信息保存到session中
    因此,我们在注册请求时,首先将用户文本框中输入的验证码值和session中保存的值进行比较,相等,则进行注册

14. JavaScript-Regular Expression——正则表达式:(代码重工 (gitee.io)

正则表达式使用步骤:
   1. 定义正则表达式对象
      正则表达式定义有两个方式:
      1) 对象形式
         var reg = new RegExp("abc")
      2) 直接量形式
         var reg = /abc/;
      3) 匹配模式:
       - g 全局匹配
       - i 忽略大小写匹配
       - m 多行匹配
       - gim这三个可以组合使用,不区分先后顺序
         例如: var reg = /abc/gim , var reg = new RegExp("abc","gim");
   2. 定义待校验的字符串
   3. 校验

元字符
[ . , \w , \W , \s , \S , \d , \D , \b , ^ , $]
Alt text

[]表示集合
 [abc] 表示 a或者b或者c
 [^abc] 表示取反,只要不是a不是b不是c就匹配
 [a-c] 表示a到c这个范围匹配

出现的次数
 * 表示多次 (0 ~ n )
 + 至少一次 ( >=1 )
 ? 最多一次 (0 ~ 1)
 {n} 出现n次
 {n,} 出现n次或者多次
 {n,m} 出现n到m次

Alt text

15. 注册页面表单验证

1
2
3
4
5
6
7
8
1) <form>有一个事件 onsubmit ,
onsubmit="return false" , 那么表单点击提交按钮时不会提交
onsubmit="return true" , 那么表单点击提交按钮时会提交
2) 获取文档中某一个节点的方式:
//DOM:Document
//var unameTxt = document.getElementById("unameTxt");
//BOM:Browser
//document.forms[0].uname

16. 原生的Ajax(了解)(代码重工 (gitee.io))总的来说就是可以一边浏览当前网页一边实现当前网页局部的刷新,比如登录后网页也只是显示了网页右上角欢迎用户登录的部分信息,异步请求就是并发请求,不会影响其他请求;同步请求就是一次性请求整个网页数据,非常耗费带宽,而且有个环节出现问题就会进行等待。

第一步: 客户端发送异步请求;并绑定对结果处理的回调函数
1) [[<input type="text" name="uname" onblur="ckUname()"/>]]
2) 定义ckUname方法:
   - 创建XMLHttpRequest对象
   - XMLHttpRequest对象操作步骤:
     - open("GET",url,true)
     - onreadyStateChange 设置回调
     - send() 发送请求
   - 在回调函数中需要判断XMLHttpRequest对象的状态:
     readyState(0-4) , status(200)
第二步:服务器端做校验,然后将校验结果响应给客户端

17.Ajax : 异步的JavaScript and XML

目的: 用来发送异步的请求,然后当服务器给我响应的时候再进行回调操作
好处: 提高用户体验;<font color=red >实现局部刷新!!!</font>:降低服务器负担、减轻浏览器压力、减轻网络带宽压力
开发步骤:
  1) 创建XMLHttpRequest
  2) 调用open进行设置:"GET" , URL , true
  3) 绑定状态改变时执行的回调函数 - onreadystatechange
  4) 发送请求 - send()
  5) 编写回调函数,在回调函数中,我们只对XMLHttpRequest的readystate为4的时候感兴趣
                            我们只对XMLHttpRequest的status为200的时候感兴趣

readystate:
0: (Uninitialized) the send( ) method has not yet been invoked.
1: (Loading) the send( ) method has been invoked, request in progress.
2: (Loaded) the send( ) method has completed, entire response received.
3: (Interactive) the response is being parsed.
4: (Completed) the response has been parsed, is ready for harvesting.

0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
3 - (交互)正在解析响应内容
4 - (完成)响应内容解析完成,可以在客户端调用了

18. Vue(第四章 Vue.js | 代码重工 (gitee.io)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1) {{}} - 相当于innerText
2) v-bind:attr 绑定属性值。例如,v-bind:value - 绑定value值
简写: :value
3) v-model 双向绑定
v-model:value , 简写 v-model
4) v-if , v-else , v-show
v-if和v-else之间不能有其他的节点
v-show是通过样式表display来控制节点是否显示
5) v-for 迭代
v-for={fruit in fruitList}
6) v-on 绑定事件
7) 其他:
- trim:去除首尾空格 , split() , join()
- watch表示侦听属性
- 生命周期

19. Axios(代码重工 (gitee.io)

Axios是Ajax的一个框架,简化Ajax操作
Axios执行Ajax操作的步骤:

  1. 添加并引入axios的js文件

2-1. 客户端向服务器端异步发送普通参数值

1
2
3
4
5
6
7
8
9
10
11
12
13
- 基本格式: axios().then().catch()
- 示例:
axios({
method:"POST",
url:"....",
params:{
uname:"lina",
pwd:"ok"
}
})
.then(function(value){}) //成功响应时执行的回调 value.data可以获取到服务器响应内容
.catch(function(reason){}); //有异常时执行的回调 reason.response.data可以获取到响应的内容
reason.message / reason.stack 可以查看错误的信息

2-2. 客户端向服务器发送JSON格式的数据
- 什么是JSON
JSON是一种数据格式
XML也是一种数据格式
XML格式表示两个学员信息的代码如下:

1
2
3
4
5
6
7
8
9
10
<students>
<student sid="s001">
<sname>jim</sname>
<age>18</age>
</student>
<student sid="s002">
<sname>tom</sname>
<age>19</age>
</student>
</students>

JSON格式表示两个学员信息的代码如下:
[{sid:”s001”,age:18},{sid:”s002”,age:19}]
- JSON表达数据更简洁,更能够节约网络带宽
- 客户端发送JSON格式的数据给服务器端
1) 客户端中params需要修改成: data:
2) 服务器获取参数值不再是 request.getParameter()…
而是:
StringBuffer stringBuffer = new StringBuffer(“”);
BufferedReader bufferedReader = request.getReader();
String str = null ;
while((str=bufferedReader.readLine())!=null){
stringBuffer.append(str);
}
str = stringBuffer.toString() ;

3) 我们会发现 str的内容如下:
   {"uname":"lina","pwd":"ok"}

-  服务器端给客户端响应JSON格式的字符串,然后客户端需要将字符串转化成js Object

后期完善:

1.带数据库的登录注册:

# 完成带数据库的登录注册

#1、密码加密

#①加密方式介绍

  • 对称加密:在知道密文和加密算法的情况下,能够反推回明文
  • 非对称加密:
    • 加密:使用私钥加密
    • 解密:使用公钥解密

#②加密算法:HASH

  • 特点1:不可逆
  • 特点2:加密后,密文长度固定
  • 特点3:输入数据不变,输出数据也保证不变;输入数据变化,输出数据一定变化

常见的HASH算法举例:

  • MD5
  • SHA1
  • SHA512
  • CRC32

#③执行加密的工具方法

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
public class MD5Util {

/**
* 针对明文字符串执行MD5加密
* @param source
* @return
*/
public static String encode(String source) {

// 1.判断明文字符串是否有效
if (source == null || "".equals(source)) {
throw new RuntimeException("用于加密的明文不可为空");
}

// 2.声明算法名称
String algorithm = "md5";

// 3.获取MessageDigest对象
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}

// 4.获取明文字符串对应的字节数组
byte[] input = source.getBytes();

// 5.执行加密
byte[] output = messageDigest.digest(input);

// 6.创建BigInteger对象
int signum = 1;
BigInteger bigInteger = new BigInteger(signum, output);

// 7.按照16进制将bigInteger的值转换为字符串
int radix = 16;
String encoded = bigInteger.toString(radix).toUpperCase();

return encoded;
}

}

#2、注册功能

#①目标

检查用户名是否可用,如果用户名可用则保存User对象

#②思路

./images

#③代码

#[1]创建UserService

./images

1
2
3
4
5
6
7
public interface UserService {

void doRegister(User userForm);

User doLogin(User userForm);

}

开发中,接口设计和接口中方式定义的理念:

  • 方法的返回值应该对应这个方法本身的业务功能
    • 写操作:没有返回值
    • 读操作:有返回值,返回值就是查询的结果
  • 方法执行是否成功
    • 成功:不抛异常
    • 失败:抛异常

启发:

上层方法向下层方法布置任务:方法名、方法的参数

下层方法向上层方法反馈结果:返回值、是否抛异常

#[2]实现UserService接口

./images

./images

#(1)UserDao声明为成员变量

说明:将来在Servlet中使用Service的时候,也是同样声明为成员变量,那么从Servlet、Service到Dao就都是『单实例,多线程』方式运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserServiceImpl implements UserService {

private UserDao userDao = new UserDaoImpl();

@Override
public void doRegister(User userForm) {

}

@Override
public User doLogin(User userForm) {

return null;
}
}

理由:

  • 创建对象的操作只执行一次
  • 对象在内存中只保存一份,不会过多占用内存空间
#(2)实现注册功能
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
@Override
public void doRegister(User userForm) {

// 1.从userForm对象中获取用户通过表单提交的用户名
String userName = userForm.getUserName();

// 2.根据用户名调用UserDao方法查询对应的User对象
User userDB = userDao.selectUserByName(userName);

// 3.检查User对象是否为空
if (userDB != null) {
// 4.如果User对象不为空,则抛出异常,通知上层调用方法:用户名已经被占用
throw new RuntimeException("用户名已经被占用");
}

// 5.对表单提交的密码执行MD5加密
// ①取出表单的密码
String userPwd = userForm.getUserPwd();

// ②执行加密
String encode = MD5Util.encode(userPwd);

// ③将加密得到的密文字符串设置回userForm对象
userForm.setUserPwd(encode);

// 6.调用UserDao方法将userForm对象保存到数据库
userDao.insertUser(userForm);
}

#[3]修改RegisterServlet

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
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

// 1.从请求参数中获取数据封装为User对象
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");

User userForm = new User(null, username, password, email);

// 2.调用UserService的方法执行注册
try {
userService.doRegister(userForm);

// 3.如果没有抛出异常那么就跳转到注册成功的页面
// 选择重定向的原因:跳转到regist_success.html页面后,用户刷新浏览器不会重复提交注册表单
response.sendRedirect(request.getContextPath()+"/pages/user/regist_success.html");
} catch (Exception e) {
e.printStackTrace();

// 4.如果抛出了异常
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("注册失败:" + e.getMessage());
}

}

#3、登录功能

#①关键点提示

对用户密码进行验证时,无法将密文解密为明文,只能将明文再次加密为密文,『比较密文是否一致』。

#②思路

./images

#③代码

#[1]UserService接口的doLogin()

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
@Override
public User doLogin(User userForm) {

// 1.获取表单提交的用户名
String userName = userForm.getUserName();

// 2.根据用户名调用UserDao方法查询User对象
User userDB = userDao.selectUserByName(userName);

// 3.检查数据库查询的User对象是否为null
if (userDB == null) {
// 4.如果数据库查询的User对象为null,说明用户名不正确,抛出异常:登录失败
throw new RuntimeException("用户名或密码不正确");
}

// 5.密码验证
// ①获取表单提交的密码
String userPwdForm = userForm.getUserPwd();

// ②对表单提交的密码进行加密
String encode = MD5Util.encode(userPwdForm);

// ③获取数据库查询到的密码
String userPwdDB = userDB.getUserPwd();

// ④比较表单密码和数据库密码
if (Objects.equals(encode, userPwdDB)) {
// 6.如果密码验证成功,则将从数据库查询出来的User对象返回
return userDB;

}else{
// 7.如果密码验证失败,说明密码不正确,抛出异常:登录失败
throw new RuntimeException("用户名或密码不正确");
}

}

#[2]LoginServlet的doPost()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

// 1.从请求参数中获取用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");

// 2.封装为User对象
User userForm = new User(null, username, password, null);

// 3.调用UserService的方法执行登录验证
try {
User userDB = userService.doLogin(userForm);

// 4.登录成功后跳转到登录成功页面
response.sendRedirect(request.getContextPath() + "/pages/user/login_success.html");
} catch (Exception e) {
e.printStackTrace();

// 5.登录失败则显示提示消息
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("登录失败:" + e.getMessage());
}

}

结账过程中使用事务(重点):
代码重工 (gitee.io)

事件驱动补充:代码重工 (gitee.io)

动态Web工程内编写路径:代码重工 (gitee.io)