学习总结
2024-01-09 14:36:48 6 举报
AI智能生成
部分学习笔记,仅供参考,互相学习加油
作者其他创作
大纲/内容
Linux
Linux基础
概述
Linux是基于Unix的
Linux是一种自由和开放源码的操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、台式计算机
Linux是一种自由和开放源码的操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、台式计算机
主流版本
安装
VMware Linux系统
目录结构
Linux命令
切换目录命令cd
列出文件列表:ls ll
创建目录和移除目录:mkdir rmdir(-p级联创建和删除的符号)
浏览文件
cat
由第一行开始显示文件内
tac
从最后一行开始显示
nl
显示的时候,顺道输出行号
more
展示一个页面,如果过多,通过空格展示下一个页面
less
和more差不多,但是可以通过 上下键进行查看
tail
tail命令是在实际使用过程中使用非常多的一个命令,它的功能是:用于显示文件后几行的内容。
用法:
tail -10 /etc/passwd 查看后10行数据
tail -f catalina.log 动态查看日志(*****)
用法:
tail -10 /etc/passwd 查看后10行数据
tail -f catalina.log 动态查看日志(*****)
文件操作
rm 删除文件
用法:rm [选项]... 文件...
用法:rm [选项]... 文件...
rm -f a.txt 不询问,直接删除rm 删除目录
rm -r a 递归删除不询问递归删除(慎用)
rm -rf a 不询问递归删除
rm -rf * 删除所有文件
rm -rf /* 自杀
rm -r a 递归删除不询问递归删除(慎用)
rm -rf a 不询问递归删除
rm -rf * 删除所有文件
rm -rf /* 自杀
cp、mv
cp(copy)命令可以将文件从一处复制到另一处。一般在使用cp命令时将一个文件复制成另一个文件或复制到某目录时,需要指定源文件名与目标文件名或目录。
cp a.txt b.txt 将a.txt复制为b.txt文件
cp a.txt ../ 将a.txt文件复制到上一层目录中
mv 移动或者重命名
mv a.txt ../ 将a.txt文件移动到上一层目录中
mv a.txt b.txt 将a.txt文件重命名为b.txt
cp a.txt b.txt 将a.txt复制为b.txt文件
cp a.txt ../ 将a.txt文件复制到上一层目录中
mv 移动或者重命名
mv a.txt ../ 将a.txt文件移动到上一层目录中
mv a.txt b.txt 将a.txt文件重命名为b.txt
tar
-c:创建一个新tar文件
-v:显示运行过程的信息
-f:指定文件名
-z:调用gzip压缩命令进行压缩
-t:查看压缩文件的内容
-x:解开tar文件
打包:
tar –cvf xxx.tar ./*
打包并且压缩:
tar –zcvf xxx.tar.gz ./*
解压
tar –xvf xxx.tar
tar -zxvf xxx.tar.gz -C /usr/aaa
-v:显示运行过程的信息
-f:指定文件名
-z:调用gzip压缩命令进行压缩
-t:查看压缩文件的内容
-x:解开tar文件
打包:
tar –cvf xxx.tar ./*
打包并且压缩:
tar –zcvf xxx.tar.gz ./*
解压
tar –xvf xxx.tar
tar -zxvf xxx.tar.gz -C /usr/aaa
find
find指令用于查找符合条件的文件
示例:
find / -name “ins*” 查找文件名称是以ins开头的文件
find / -name “ins*” –ls
find / –user itcast –ls 查找用户itcast的文件
find / –user itcast –type d –ls 查找用户itcast的目录
find /-perm -777 –type d-ls 查找权限是777的文件
示例:
find / -name “ins*” 查找文件名称是以ins开头的文件
find / -name “ins*” –ls
find / –user itcast –ls 查找用户itcast的文件
find / –user itcast –type d –ls 查找用户itcast的目录
find /-perm -777 –type d-ls 查找权限是777的文件
grep
查找文件里符合条件的字符串。
用法: grep [选项]... PATTERN [FILE]...示例:
grep lang anaconda-ks.cfg 在文件中查找lang
grep lang anaconda-ks.cfg –color 高亮显示
用法: grep [选项]... PATTERN [FILE]...示例:
grep lang anaconda-ks.cfg 在文件中查找lang
grep lang anaconda-ks.cfg –color 高亮显示
其它常用命令
pwd
显示当前所在目录
touch
创建一个空文件
* touch a.txt
* touch a.txt
clear crtl+L
Vi和Vim编辑器
在Linux下一般使用vi编辑器来编辑文件。vi既可以查看文件也可以编辑文件。
三种模式:命令行、插入、底行模式。
切换到命令行模式:按Esc键;
切换到插入模式:按 i 、o、a键;
i 在当前位置前插入
I 在当前行首插入
a 在当前位置后插入
A 在当前行尾插入
o 在当前行之后插入一行
O 在当前行之前插入一行
三种模式:命令行、插入、底行模式。
切换到命令行模式:按Esc键;
切换到插入模式:按 i 、o、a键;
i 在当前位置前插入
I 在当前行首插入
a 在当前位置后插入
A 在当前行尾插入
o 在当前行之后插入一行
O 在当前行之前插入一行
快捷键:
dd – 快速删除一行
yy - 复制当前行
nyy - 从当前行向后复制几行
p - 粘贴
R – 替换
x 删除当前光标所在处的字符
dd – 快速删除一行
yy - 复制当前行
nyy - 从当前行向后复制几行
p - 粘贴
R – 替换
x 删除当前光标所在处的字符
重定向输出>和>>
cat /etc/passwd > a.txt 将输出定向到a.txt中
cat /etc/passwd >> a.txt 输出并且追加
ifconfig > ifconfig.txt
cat /etc/passwd >> a.txt 输出并且追加
ifconfig > ifconfig.txt
系统管理命令
ps 正在运行的某个进程的状态
ps –ef 查看所有进程
ps –ef | grep ssh 查找某一进程
kill 2868 杀掉2868编号的进程
kill -9 2868 强制杀死进程
ps –ef 查看所有进程
ps –ef | grep ssh 查找某一进程
kill 2868 杀掉2868编号的进程
kill -9 2868 强制杀死进程
管道
管道是Linux命令中重要的一个概念,其作用是将一个命令的输出用作另一个命令的输入。示例
ls --help | more 分页查询帮助信息
ps –ef | grep java 查询名称中包含java的进程
ifconfig | more
cat index.html | more
ps –ef | grep aio
ls --help | more 分页查询帮助信息
ps –ef | grep java 查询名称中包含java的进程
ifconfig | more
cat index.html | more
ps –ef | grep aio
权限命令
Linux的三种文件形式
普通文件: 包括文本文件、数据文件、可执行的二进制程序文件等。
目录文件: Linux系统把目录看成是一种特殊的文件,利用它构成文件系统的树型结构。
设备文件: Linux系统把每一个设备都看成是一个文件
目录文件: Linux系统把目录看成是一种特殊的文件,利用它构成文件系统的树型结构。
设备文件: Linux系统把每一个设备都看成是一个文件
文件类型标识
普通文件(-)目录(d)符号链接(l)
* 进入etc可以查看,相当于快捷方式字符设备文件(c)块设备文件(s)套接字(s)命名管道(p)
* 进入etc可以查看,相当于快捷方式字符设备文件(c)块设备文件(s)套接字(s)命名管道(p)
文件权限变更
chmod 变更文件或目录的权限。
chmod 755 a.txt
chmod u=rwx,g=rx,o=rx a.txt
chmod 755 a.txt
chmod u=rwx,g=rx,o=rx a.txt
账号操作
增加账户——useradd
-c comment 指定一段注释性描述。
-d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。
-g 用户组 指定用户所属的用户组。
-G 用户组,用户组 指定用户所属的附加组。
-m 使用者目录如不存在则自动建立。
-s Shell文件 指定用户的登录Shell。
-u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号
-d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。
-g 用户组 指定用户所属的用户组。
-G 用户组,用户组 指定用户所属的附加组。
-m 使用者目录如不存在则自动建立。
-s Shell文件 指定用户的登录Shell。
-u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号
删除账户
userdel 选项 用户名
常用的选项是 -r,它的作用是把用户的主目录一起删除。
修改帐号
usermod 选项 用户名
常用的选项包括 -c, -d, -m, -g, -G, -s, -u以及-o等 ,这些选项的意义与 useradd 命令中的选项
一样,可以为用户指定新的资源值。
一样,可以为用户指定新的资源值。
用户口令的管理
指定和修改用户口令的Shell命令是 passwd 。超级用户可以为自己和其他用户指定口令,普通用户只
能用它修改自己的口令。
能用它修改自己的口令。
-l 锁定口令,即禁用账号。
-u 口令解锁。
-d 使账号无口令。
-f 强迫用户下次登录时修改口令
-u 口令解锁。
-d 使账号无口令。
-f 强迫用户下次登录时修改口令
磁盘管理
Linux磁盘管理常用三个命令为 df、du 和 fdisk
df :列出文件系统的整体磁盘使用量
df命令参数功能:检查文件系统的磁盘空间占用情况。
可以利用该命令来获取硬盘被占用了多少空间,目前
还剩下多少空间等信息。
可以利用该命令来获取硬盘被占用了多少空间,目前
还剩下多少空间等信息。
-a :列出所有的文件系统,包括系统特有的 /proc 等文件系统;
-k :以 KBytes 的容量显示各文件系统;
-m :以 MBytes 的容量显示各文件系统;
-h :以人们较易阅读的 GBytes, MBytes, KBytes 等格式自行显示;
-H :以 M=1000K 取代 M=1024K 的进位方式;
-T :显示文件系统类型, 连同该 partition 的 filesystem 名称 (例如 ext3) 也列出;
-i :不用硬盘容量,而以 inode 的数量来显示
-k :以 KBytes 的容量显示各文件系统;
-m :以 MBytes 的容量显示各文件系统;
-h :以人们较易阅读的 GBytes, MBytes, KBytes 等格式自行显示;
-H :以 M=1000K 取代 M=1024K 的进位方式;
-T :显示文件系统类型, 连同该 partition 的 filesystem 名称 (例如 ext3) 也列出;
-i :不用硬盘容量,而以 inode 的数量来显示
测试
# 将系统内所有的文件系统列出来!
# 在 Linux 底下如果 df 没有加任何选项
# 那么默认会将系统内所有的 (不含特殊内存内的文件系统与 swap) 都以 1 Kbytes 的容量来列出
来!
[root@fzl /]# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 889100 0 889100 0% /dev
tmpfs 899460 704 898756 1% /dev/shm
tmpfs 899460 496 898964 1% /run
tmpfs 899460 0 899460 0% /sys/fs/cgroup
/dev/vda1 41152812 6586736 32662368 17% /
tmpfs 179896 0 179896 0% /run/user/0
# 在 Linux 底下如果 df 没有加任何选项
# 那么默认会将系统内所有的 (不含特殊内存内的文件系统与 swap) 都以 1 Kbytes 的容量来列出
来!
[root@fzl /]# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 889100 0 889100 0% /dev
tmpfs 899460 704 898756 1% /dev/shm
tmpfs 899460 496 898964 1% /run
tmpfs 899460 0 899460 0% /sys/fs/cgroup
/dev/vda1 41152812 6586736 32662368 17% /
tmpfs 179896 0 179896 0% /run/user/0
# 将容量结果以易读的容量格式显示出来
[root@fzl/]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 869M 0 869M 0% /dev
tmpfs 879M 708K 878M 1% /dev/shm
tmpfs 879M 496K 878M 1% /run
tmpfs 879M 0 879M 0% /sys/fs/cgroup
/dev/vda1 40G 6.3G 32G 17% /
tmpfs 176M 0 176M 0% /run/user/0
[root@fzl/]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 869M 0 869M 0% /dev
tmpfs 879M 708K 878M 1% /dev/shm
tmpfs 879M 496K 878M 1% /run
tmpfs 879M 0 879M 0% /sys/fs/cgroup
/dev/vda1 40G 6.3G 32G 17% /
tmpfs 176M 0 176M 0% /run/user/0
# 将系统内的所有特殊文件格式及名称都列出来
[root@fzl/]# df -aT
Filesystem Type 1K-blocks Used Available Use% Mounted on
sysfs sysfs 0 0 0 - /sys
proc proc 0 0 0 - /proc
devtmpfs devtmpfs 889100 0 889100 0% /dev
securityfs securityfs 0 0 0 -
/sys/kernel/security
tmpfs tmpfs 899460 708 898752 1% /dev/shm
devpts devpts 0 0 0 - /dev/pts
tmpfs tmpfs 899460 496 898964 1% /run
tmpfs tmpfs 899460 0 899460 0% /sys/fs/cgroup
[root@fzl/]# df -aT
Filesystem Type 1K-blocks Used Available Use% Mounted on
sysfs sysfs 0 0 0 - /sys
proc proc 0 0 0 - /proc
devtmpfs devtmpfs 889100 0 889100 0% /dev
securityfs securityfs 0 0 0 -
/sys/kernel/security
tmpfs tmpfs 899460 708 898752 1% /dev/shm
devpts devpts 0 0 0 - /dev/pts
tmpfs tmpfs 899460 496 898964 1% /run
tmpfs tmpfs 899460 0 899460 0% /sys/fs/cgroup
# 将 /etc 底下的可用的磁盘容量以易读的容量格式显示
[root@kuangshen /]# df -h /etc
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 6.3G 32G 17% /
[root@kuangshen /]# df -h /etc
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 6.3G 32G 17% /
du:检查磁盘空间使用量
Linux du命令也是查看使用空间的,但是与df命令不同的是
Linux du命令是对文件和目录磁盘使用的空
间的查看,还是和df命令有一些区别的,这里介绍Linux du命令
Linux du命令是对文件和目录磁盘使用的空
间的查看,还是和df命令有一些区别的,这里介绍Linux du命令
du [-ahskm] 文件或目录名称
-a :列出所有的文件与目录容量,因为默认仅统计目录底下的文件量而已。
-h :以人们较易读的容量格式 (G/M) 显示;
-s :列出总量而已,而不列出每个各别的目录占用容量;
-S :不包括子目录下的总计,与 -s 有点差别。
-k :以 KBytes 列出容量显示;
-m :以 MBytes 列出容量显示;
-h :以人们较易读的容量格式 (G/M) 显示;
-s :列出总量而已,而不列出每个各别的目录占用容量;
-S :不包括子目录下的总计,与 -s 有点差别。
-k :以 KBytes 列出容量显示;
-m :以 MBytes 列出容量显示;
测试
# 只列出当前目录下的所有文件夹容量(包括隐藏文件夹):
# 直接输入 du 没有加任何选项时,则 du 会分析当前所在目录的文件与目录所占用的硬盘空间。
[root@kuangshen home]# du
16 ./redis
8 ./www/.oracle_jre_usage # 包括隐藏文件的目录
24 ./www
48 . # 这个目录(.)所占用的总量
# 直接输入 du 没有加任何选项时,则 du 会分析当前所在目录的文件与目录所占用的硬盘空间。
[root@kuangshen home]# du
16 ./redis
8 ./www/.oracle_jre_usage # 包括隐藏文件的目录
24 ./www
48 . # 这个目录(.)所占用的总量
# 将文件的容量也列出来
[root@fzl home]# du -a
4 ./redis/.bash_profile
4 ./redis/.bash_logout
....中间省略....
4 ./kuangstudy.txt # 有文件的列表了
48 .
[root@fzl home]# du -a
4 ./redis/.bash_profile
4 ./redis/.bash_logout
....中间省略....
4 ./kuangstudy.txt # 有文件的列表了
48 .
# 检查根目录底下每个目录所占用的容量
[root@fzl home]# du -sm /*
0 /bin
146 /boot
.....中间省略....
0 /proc
.....中间省略....
1 /tmp
3026 /usr # 系统初期最大就是他了啦!
513 /var
2666 /www
[root@fzl home]# du -sm /*
0 /bin
146 /boot
.....中间省略....
0 /proc
.....中间省略....
1 /tmp
3026 /usr # 系统初期最大就是他了啦!
513 /var
2666 /www
磁盘挂载与删除
Linux 的磁盘挂载使用 mount 命令,卸载使用 umount 命令
磁盘挂载语法
mount [-t 文件系统] [-L Label名] [-o 额外选项] [-n] 装置文件名 挂载点
测试
# 将 /dev/hdc6 挂载到 /mnt/hdc6 上面!
[root@www ~]# mkdir /mnt/hdc6
[root@www ~]# mount /dev/hdc6 /mnt/hdc6
[root@www ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/hdc6 1976312 42072 1833836 3% /mnt/hdc6
[root@www ~]# mkdir /mnt/hdc6
[root@www ~]# mount /dev/hdc6 /mnt/hdc6
[root@www ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/hdc6 1976312 42072 1833836 3% /mnt/hdc6
Tomcat安装使用
1、安装好了Java环境后我们可以测试下Tomcat!准备好Tomcat的安装包!
2、将文件移动到/usr/tomcat/下,并解压
3、运行Tomcat,进入bin目录,和我们以前在Windows下看的都是一样的
4、确保Linux的防火墙端口是开启的,如果是阿里云,需要保证阿里云的安全组策略是开放的
2、将文件移动到/usr/tomcat/下,并解压
3、运行Tomcat,进入bin目录,和我们以前在Windows下看的都是一样的
4、确保Linux的防火墙端口是开启的,如果是阿里云,需要保证阿里云的安全组策略是开放的
# 查看防火墙规则
firewall-cmd --list-all # 查看全部信息
firewall-cmd --list-ports # 只看端口信息
firewall-cmd --list-all # 查看全部信息
firewall-cmd --list-ports # 只看端口信息
# 开启端口
开端口命令:firewall-cmd --zone=public --add-port=80/tcp --permanent
重启防火墙:systemctl restart firewalld.service
开端口命令:firewall-cmd --zone=public --add-port=80/tcp --permanent
重启防火墙:systemctl restart firewalld.service
命令含义:
--zone #作用域
--add-port=80/tcp #添加端口,格式为:端口/通讯协议
--permanent #永久生效,没有此参数重启后失效
--zone #作用域
--add-port=80/tcp #添加端口,格式为:端口/通讯协议
--permanent #永久生效,没有此参数重启后失效
上网操作
配置主机名
配置IP
重启网络
在Linux上安装JDK
* 上传JDK
* 卸载open-JDK
# 查看jdk版本
java –version
# 查看安装的jdk信息
rpm -qa | grep java
# 卸载jdk
rpm -e --nodeps java-1.6.0-openjdk-1.6.0.35-1.13.7.1.el6_6.i686
rpm -e --nodeps java-1.7.0-openjdk-1.7.0.79-2.5.5.4.el6.i686
* 卸载open-JDK
# 查看jdk版本
java –version
# 查看安装的jdk信息
rpm -qa | grep java
# 卸载jdk
rpm -e --nodeps java-1.6.0-openjdk-1.6.0.35-1.13.7.1.el6_6.i686
rpm -e --nodeps java-1.7.0-openjdk-1.7.0.79-2.5.5.4.el6.i686
* 通常将软件安装到/usr/local
* 直接解压就可以
tar –xvf jdk.tar.gz -C 目标路径
* 直接解压就可以
tar –xvf jdk.tar.gz -C 目标路径
① vi /etc/profile
② 在末尾行添加
#set java environment
JAVA_HOME=/usr/local/jdk/jdk1.7.0_71
CLASSPATH=.:$JAVA_HOME/lib.tools.jar
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME CLASSPATH PATH
保存退出
② 在末尾行添加
#set java environment
JAVA_HOME=/usr/local/jdk/jdk1.7.0_71
CLASSPATH=.:$JAVA_HOME/lib.tools.jar
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME CLASSPATH PATH
保存退出
curl
一款很强大的http命令行工具。它支持文件的上传和下载,是综合传输工具
学习地址:https://www.cnblogs.com/duhuo/p/5695256.html
学习地址:https://www.cnblogs.com/duhuo/p/5695256.html
版本控制工具
Git
使用详解
概述
版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历
史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术
史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术
本地版本控制
集中版本控制
分布式版本控制
Git配置
查看配置
git config -l
git config -l
#查看系统config
git config --system --list
#查看当前用户(global)配置
git config --global --list
git config --system --list
#查看当前用户(global)配置
git config --global --list
Git相关配置文件
Git相关的配置文件:
1)、Git\mingw64\etc\gitconfig : Git 安装目录下的 gitconfig --system 系统级
2)、C:\Users\Administrator\ .gitconfig 只适用于当前登录用户的配置 --global 全局
1)、Git\mingw64\etc\gitconfig : Git 安装目录下的 gitconfig --system 系统级
2)、C:\Users\Administrator\ .gitconfig 只适用于当前登录用户的配置 --global 全局
设置用户名和邮箱
git config --global user.name "kuangshen" #名称
git config --global user.email 24736743@qq.com #邮箱
git config --global user.email 24736743@qq.com #邮箱
Git的环境配置
Git启动
Git项目搭建
本地仓库搭建
# 在当前目录新建一个Git代码库
$ git init
$ git init
克隆远程仓库
# 克隆一个项目和它的整个代码历史(版本信息)
$ git clone [url]
$ git clone [url]
查看文件状态
#查看指定文件状态
git status [filename]
#查看所有文件状态
git status
git status [filename]
#查看所有文件状态
git status
git常用命令
# 列出所有本地分支
git branch
# 列出所有远程分支
git branch -r
# 新建一个分支,但依然停留在当前分支
git branch [branch-name]
# 新建一个分支,并切换到该分支
git checkout -b [branch]
# 合并指定分支到当前分支
$ git merge [branch]
# 删除分支
$ git branch -d [branch-name]
# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]
git branch
# 列出所有远程分支
git branch -r
# 新建一个分支,但依然停留在当前分支
git branch [branch-name]
# 新建一个分支,并切换到该分支
git checkout -b [branch]
# 合并指定分支到当前分支
$ git merge [branch]
# 删除分支
$ git branch -d [branch-name]
# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]
案例
上传文件
$ git init
$ touch README.md
$ git add README.md
$ git commit -m 'first_commit'
$ git remote add origin https://github.com/jerryhanjj/baike_spider.git
$ git push origin master
$ touch README.md
$ git add README.md
$ git commit -m 'first_commit'
$ git remote add origin https://github.com/jerryhanjj/baike_spider.git
$ git push origin master
上传项目
上传项目
跟踪项目文件夹中的所有文件和文件夹
$ git add .
输入本次的提交说明,准备提交暂存区中的更改的已跟踪文件,单引号内为说明内容
$ git commit -m 'first_commit'
关联远程仓库,添加后,远程库的名字就是 origin,这是 Git 默认的叫法,也可以改成别的,但是 origin 这个名字一看就知道是远程库。
$ git remote add origin https://github.com/jerryhanjj/baike_spider.git
如果关联出现错误 fatal: remote origin already exists,则执行下列语句再进行关联
git remote rm origin
把本地库的所有内容推送到远程库上
$ git push -u origin master
如果在推送时出现错误 error:failed to push som refs to.......,则执行下列语句
git pull origin master
将远程仓库 Github 上的文件拉下来合并之后重新推送上去,如果在推送时失败,提示fatal: refusing to merge unrelated histories,则执行下列语句
git pull origin master --allow-unrelated-histories
跟踪项目文件夹中的所有文件和文件夹
$ git add .
输入本次的提交说明,准备提交暂存区中的更改的已跟踪文件,单引号内为说明内容
$ git commit -m 'first_commit'
关联远程仓库,添加后,远程库的名字就是 origin,这是 Git 默认的叫法,也可以改成别的,但是 origin 这个名字一看就知道是远程库。
$ git remote add origin https://github.com/jerryhanjj/baike_spider.git
如果关联出现错误 fatal: remote origin already exists,则执行下列语句再进行关联
git remote rm origin
把本地库的所有内容推送到远程库上
$ git push -u origin master
如果在推送时出现错误 error:failed to push som refs to.......,则执行下列语句
git pull origin master
将远程仓库 Github 上的文件拉下来合并之后重新推送上去,如果在推送时失败,提示fatal: refusing to merge unrelated histories,则执行下列语句
git pull origin master --allow-unrelated-histories
Maven
使用详解
概述
项目对象模
型 (POM:Project Object Model)
型 (POM:Project Object Model)
下载
选择合适版本
https://maven.apache.org/download.cgi
安装
将下载好的安装包解压到无中文的路径下
目录结构
bin:存放了 maven 的命令,比如我们前面用到的 mvn tomcat:run
boot:存放了一些 maven 本身的引导程序,如类加载器等
conf:存放了 maven 的一些配置文件,如 setting.xml 文件
lib:存放了 maven 本身运行所需的一些 jar 包
boot:存放了一些 maven 本身的引导程序,如类加载器等
conf:存放了 maven 的一些配置文件,如 setting.xml 文件
lib:存放了 maven 本身运行所需的一些 jar 包
构建Maven的环境变量
mvn -v
环境变量测试
Maven的目录结构
常用命令
compile 是 maven 工程的编译命令,作用是将 src/main/java 下的文件编译为 class 文件输出到 target
目录下。
cmd 进入命令状态,执行 mvn compile
目录下。
cmd 进入命令状态,执行 mvn compile
test 是 maven 工程的测试命令 mvn test,会执行 src/test/java 下的单元测试类。
cmd 执行 mvn test 执行 src/test/java 下单元测试类
cmd 执行 mvn test 执行 src/test/java 下单元测试类
clean 是 maven 工程的清理命令,执行 clean 会删除 target 目录及内容
package 是 maven 工程的打包命令,对于 java 工程执行 package 打成 jar 包,对于 web 工程打成 war
包
包
install 是 maven 工程的安装命令,执行 install 将 maven 打成 jar 包或 war 包发布到本地仓库
概念模型
Maven 包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期(Project
Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段
(phase)中插件(plugin)目标(goal)的逻辑
Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段
(phase)中插件(plugin)目标(goal)的逻辑
分模块开发与设计
概念
聚合
用于快速构建maven项目,一次构建多个项目和模块
创建一个空模块,打包类型定义为pom
<packaging>pom</packaging>
定义当前模块进行构建操作时定义其它模块
<modules>
<module>../ssm_controller</module>
<module>../ssm_service</module>
<module>../ssm_dao</module>
<module>../ssm_pojo</module>
</modules>
<module>../ssm_controller</module>
<module>../ssm_service</module>
<module>../ssm_dao</module>
<module>../ssm_pojo</module>
</modules>
继承
=
<!-- 申明此处进行依赖管理-->
<dependencyManagement>
<!--具体的依赖 -->
<dependencies>
<!--spring环境 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependencies>
<dependencyManagement>
<dependencyManagement>
<!--具体的依赖 -->
<dependencies>
<!--spring环境 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependencies>
<dependencyManagement>
版本管理
资源管理
多环境开发配置
跳过测试
私服
JavaEE
基础加强
Junit单元测试
反射
注解
JDBC
概念
Java DataBase Connectivity Java 数据库连接, Java语言操作数据库
连接步骤
* 步骤:
1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
1.复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
2.右键-->Add As Library
2. 注册驱动
3. 获取数据库连接对象 Connection
4. 定义sql
5. 获取执行sql语句的对象 Statement
6. 执行sql,接受返回结果
7. 处理结果
8. 释放资源
1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
1.复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
2.右键-->Add As Library
2. 注册驱动
3. 获取数据库连接对象 Connection
4. 定义sql
5. 获取执行sql语句的对象 Statement
6. 执行sql,接受返回结果
7. 处理结果
8. 释放资源
代码实现
代码实现:
//1. 导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
//4.定义sql语句
String sql = "update account set balance = 500 where id = 1";
//5.获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();
//1. 导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
//4.定义sql语句
String sql = "update account set balance = 500 where id = 1";
//5.获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();
配置对象的详解
1. DriverManager:驱动管理对象
* 功能:
1. 注册驱动:告诉程序该使用哪一个数据库驱动jar
static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。
写代码使用: Class.forName("com.mysql.jdbc.Driver");
通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
2. 获取数据库连接:
* 方法:static Connection getConnection(String url, String user, String password)
* 参数:
* url:指定连接的路径
* 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
* 例子:jdbc:mysql://localhost:3306/db3
* 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
* user:用户名
* password:密码
* 功能:
1. 注册驱动:告诉程序该使用哪一个数据库驱动jar
static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。
写代码使用: Class.forName("com.mysql.jdbc.Driver");
通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
2. 获取数据库连接:
* 方法:static Connection getConnection(String url, String user, String password)
* 参数:
* url:指定连接的路径
* 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
* 例子:jdbc:mysql://localhost:3306/db3
* 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
* user:用户名
* password:密码
2. Connection:数据库连接对象
1. 功能:
1. 获取执行sql 的对象
* Statement createStatement()
* PreparedStatement prepareStatement(String sql)
2. 管理事务:
* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
* 提交事务:commit()
* 回滚事务:rollback()
1. 功能:
1. 获取执行sql 的对象
* Statement createStatement()
* PreparedStatement prepareStatement(String sql)
2. 管理事务:
* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
* 提交事务:commit()
* 回滚事务:rollback()
3. Statement:执行sql的对象
1. 执行sql
1. boolean execute(String sql) :可以执行任意的sql 了解
2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
* 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。
3. ResultSet executeQuery(String sql) :执行DQL(select)语句
2. 练习:
1. account表 添加一条记录
2. account表 修改记录
3. account表 删除一条记录
代码:
Statement stmt = null;
Connection conn = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 定义sql
String sql = "insert into account values(null,'王五',3000)";
//3.获取Connection对象
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
//4.获取执行sql的对象 Statement
stmt = conn.createStatement();
//5.执行sql
int count = stmt.executeUpdate(sql);//影响的行数
//6.处理结果
System.out.println(count);
if(count > 0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//stmt.close();
//7. 释放资源
//避免空指针异常
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1. 执行sql
1. boolean execute(String sql) :可以执行任意的sql 了解
2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
* 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。
3. ResultSet executeQuery(String sql) :执行DQL(select)语句
2. 练习:
1. account表 添加一条记录
2. account表 修改记录
3. account表 删除一条记录
代码:
Statement stmt = null;
Connection conn = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 定义sql
String sql = "insert into account values(null,'王五',3000)";
//3.获取Connection对象
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
//4.获取执行sql的对象 Statement
stmt = conn.createStatement();
//5.执行sql
int count = stmt.executeUpdate(sql);//影响的行数
//6.处理结果
System.out.println(count);
if(count > 0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//stmt.close();
//7. 释放资源
//避免空指针异常
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4. ResultSet:结果集对象,封装查询结果
* boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true
* getXxx(参数):获取数据
* Xxx:代表数据类型 如: int getInt() , String getString()
* 参数:
1. int:代表列的编号,从1开始 如: getString(1)
2. String:代表列名称。 如: getDouble("balance")
* 注意:
* 使用步骤:
1. 游标向下移动一行
2. 判断是否有数据
3. 获取数据
//循环判断游标是否是最后一行末尾。
while(rs.next()){
//获取数据
//6.2 获取数据
int id = rs.getInt(1);
String name = rs.getString("name");
double balance = rs.getDouble(3);
System.out.println(id + "---" + name + "---" + balance);
}
* boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true
* getXxx(参数):获取数据
* Xxx:代表数据类型 如: int getInt() , String getString()
* 参数:
1. int:代表列的编号,从1开始 如: getString(1)
2. String:代表列名称。 如: getDouble("balance")
* 注意:
* 使用步骤:
1. 游标向下移动一行
2. 判断是否有数据
3. 获取数据
//循环判断游标是否是最后一行末尾。
while(rs.next()){
//获取数据
//6.2 获取数据
int id = rs.getInt(1);
String name = rs.getString("name");
double balance = rs.getDouble(3);
System.out.println(id + "---" + name + "---" + balance);
}
练习
* 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。
1. 定义Emp类
2. 定义方法 public List<Emp> findAll(){}
3. 实现方法 select * from emp;
1. 定义Emp类
2. 定义方法 public List<Emp> findAll(){}
3. 实现方法 select * from emp;
5. PreparedStatement:执行sql的对象
1. SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
1. 输入用户随便,输入密码:a' or 'a' = 'a
2. sql:select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'
2. 解决sql注入问题:使用PreparedStatement对象来解决
3. 预编译的SQL:参数使用?作为占位符
4. 步骤:
1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
2. 注册驱动
3. 获取数据库连接对象 Connection
4. 定义sql
* 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?;
5. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql)
6. 给?赋值:
* 方法: setXxx(参数1,参数2)
* 参数1:?的位置编号 从1 开始
* 参数2:?的值
7. 执行sql,接受返回结果,不需要传递sql语句
8. 处理结果
9. 释放资源
5. 注意:后期都会使用PreparedStatement来完成增删改查的所有操作
1. 可以防止SQL注入
2. 效率更高
1. SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
1. 输入用户随便,输入密码:a' or 'a' = 'a
2. sql:select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'
2. 解决sql注入问题:使用PreparedStatement对象来解决
3. 预编译的SQL:参数使用?作为占位符
4. 步骤:
1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
2. 注册驱动
3. 获取数据库连接对象 Connection
4. 定义sql
* 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?;
5. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql)
6. 给?赋值:
* 方法: setXxx(参数1,参数2)
* 参数1:?的位置编号 从1 开始
* 参数2:?的值
7. 执行sql,接受返回结果,不需要传递sql语句
8. 处理结果
9. 释放资源
5. 注意:后期都会使用PreparedStatement来完成增删改查的所有操作
1. 可以防止SQL注入
2. 效率更高
JDBC UTILS
* 目的:简化书写
* 分析:
1. 注册驱动也抽取
2. 抽取一个方法获取连接对象
* 需求:不想传递参数(麻烦),还得保证工具类的通用性。
* 解决:配置文件
jdbc.properties
url=
user=
password=
3. 抽取一个方法释放资源
* 代码实现:
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/**
* 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
*/
static{
//读取资源文件,获取值。
try {
//1. 创建Properties集合类。
Properties pro = new Properties();
//获取src路径下的文件的方式--->ClassLoader 类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath();
System.out.println(path);///D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties
//2. 加载文件
// pro.load(new FileReader("D:\\IdeaProjects\\itcast\\day04_jdbc\\src\\jdbc.properties"));
pro.load(new FileReader(path));
//3. 获取数据,赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
//4. 注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/**
* 释放资源
* @param stmt
* @param conn
*/
public static void close(Statement stmt,Connection conn){
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 释放资源
* @param stmt
* @param conn
*/
public static void close(ResultSet rs,Statement stmt, Connection conn){
if( rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
* 分析:
1. 注册驱动也抽取
2. 抽取一个方法获取连接对象
* 需求:不想传递参数(麻烦),还得保证工具类的通用性。
* 解决:配置文件
jdbc.properties
url=
user=
password=
3. 抽取一个方法释放资源
* 代码实现:
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/**
* 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
*/
static{
//读取资源文件,获取值。
try {
//1. 创建Properties集合类。
Properties pro = new Properties();
//获取src路径下的文件的方式--->ClassLoader 类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath();
System.out.println(path);///D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties
//2. 加载文件
// pro.load(new FileReader("D:\\IdeaProjects\\itcast\\day04_jdbc\\src\\jdbc.properties"));
pro.load(new FileReader(path));
//3. 获取数据,赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
//4. 注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/**
* 释放资源
* @param stmt
* @param conn
*/
public static void close(Statement stmt,Connection conn){
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 释放资源
* @param stmt
* @param conn
*/
public static void close(ResultSet rs,Statement stmt, Connection conn){
if( rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
练习
* 练习:
* 需求:
1. 通过键盘录入用户名和密码
2. 判断用户是否登录成功
* select * from user where username = "" and password = "";
* 如果这个sql有查询结果,则成功,反之,则失败
* 步骤:
1. 创建数据库表 user
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
PASSWORD VARCHAR(32)
);
INSERT INTO USER VALUES(NULL,'zhangsan','123');
INSERT INTO USER VALUES(NULL,'lisi','234');
2. 代码实现:
public class JDBCDemo9 {
public static void main(String[] args) {
//1.键盘录入,接受用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
//2.调用方法
boolean flag = new JDBCDemo9().login(username, password);
//3.判断结果,输出不同语句
if(flag){
//登录成功
System.out.println("登录成功!");
}else{
System.out.println("用户名或密码错误!");
}
}
/**
* 登录方法
*/
public boolean login(String username ,String password){
if(username == null || password == null){
return false;
}
//连接数据库判断是否登录成功
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
//1.获取连接
try {
conn = JDBCUtils.getConnection();
//2.定义sql
String sql = "select * from user where username = '"+username+"' and password = '"+password+"' ";
//3.获取执行sql的对象
stmt = conn.createStatement();
//4.执行查询
rs = stmt.executeQuery(sql);
//5.判断
/* if(rs.next()){//如果有下一行,则返回true
return true;
}else{
return false;
}*/
return rs.next();//如果有下一行,则返回true
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,stmt,conn);
}
return false;
}
}
* 需求:
1. 通过键盘录入用户名和密码
2. 判断用户是否登录成功
* select * from user where username = "" and password = "";
* 如果这个sql有查询结果,则成功,反之,则失败
* 步骤:
1. 创建数据库表 user
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
PASSWORD VARCHAR(32)
);
INSERT INTO USER VALUES(NULL,'zhangsan','123');
INSERT INTO USER VALUES(NULL,'lisi','234');
2. 代码实现:
public class JDBCDemo9 {
public static void main(String[] args) {
//1.键盘录入,接受用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
//2.调用方法
boolean flag = new JDBCDemo9().login(username, password);
//3.判断结果,输出不同语句
if(flag){
//登录成功
System.out.println("登录成功!");
}else{
System.out.println("用户名或密码错误!");
}
}
/**
* 登录方法
*/
public boolean login(String username ,String password){
if(username == null || password == null){
return false;
}
//连接数据库判断是否登录成功
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
//1.获取连接
try {
conn = JDBCUtils.getConnection();
//2.定义sql
String sql = "select * from user where username = '"+username+"' and password = '"+password+"' ";
//3.获取执行sql的对象
stmt = conn.createStatement();
//4.执行查询
rs = stmt.executeQuery(sql);
//5.判断
/* if(rs.next()){//如果有下一行,则返回true
return true;
}else{
return false;
}*/
return rs.next();//如果有下一行,则返回true
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,stmt,conn);
}
return false;
}
}
JDBC控制事务
事务
事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
流程
操作:
1. 开启事务
2. 提交事务
3. 回滚事务
1. 开启事务
2. 提交事务
3. 回滚事务
使用Connection对象来管理事务
* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
* 在执行sql之前开启事务
* 提交事务:commit()
* 当所有sql都执行完提交事务
* 回滚事务:rollback()
* 在catch中回滚事务
* 在执行sql之前开启事务
* 提交事务:commit()
* 当所有sql都执行完提交事务
* 回滚事务:rollback()
* 在catch中回滚事务
代码
代码:
public class JDBCDemo10 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义sql
//2.1 张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. 设置参数
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,2);
//5.执行sql
pstmt1.executeUpdate();
// 手动制造异常
int i = 3/0;
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
//事务回滚
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}
}
public class JDBCDemo10 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义sql
//2.1 张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. 设置参数
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,2);
//5.执行sql
pstmt1.executeUpdate();
// 手动制造异常
int i = 3/0;
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
//事务回滚
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}
}
JDBC连接池和JDBCtemplate
JDBC连接池
概念:其实就是一个容器(集合),存放数据库连接的容器。
好处
1. 节约资源
2. 用户访问高效
2. 用户访问高效
实现
1. 标准接口:DataSource javax.sql包下的
1. 方法:
* 获取连接:getConnection()
* 归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,
那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接
2. 一般我们不去实现它,有数据库厂商来实现
1. C3P0:数据库连接池技术
2. Druid:数据库连接池实现技术,由阿里巴巴提供的
1. 方法:
* 获取连接:getConnection()
* 归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,
那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接
2. 一般我们不去实现它,有数据库厂商来实现
1. C3P0:数据库连接池技术
2. Druid:数据库连接池实现技术,由阿里巴巴提供的
C3P0
* 步骤:
1. 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar ,
* 不要忘记导入数据库驱动jar包
2. 定义配置文件:
* 名称: c3p0.properties 或者 c3p0-config.xml
* 路径:直接将文件放在src目录下即可。
3. 创建核心对象 数据库连接池对象 ComboPooledDataSource
4. 获取连接: getConnection
* 代码:
//1.创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
//2. 获取连接对象
Connection conn = ds.getConnection();
1. 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar ,
* 不要忘记导入数据库驱动jar包
2. 定义配置文件:
* 名称: c3p0.properties 或者 c3p0-config.xml
* 路径:直接将文件放在src目录下即可。
3. 创建核心对象 数据库连接池对象 ComboPooledDataSource
4. 获取连接: getConnection
* 代码:
//1.创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
//2. 获取连接对象
Connection conn = ds.getConnection();
Druid
1. 步骤:
1. 导入jar包 druid-1.0.9.jar
2. 定义配置文件:
* 是properties形式的
* 可以叫任意名称,可以放在任意目录下
3. 加载配置文件。Properties
4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory
5. 获取连接:getConnection
* 代码:
//3.加载配置文件
Properties pro = new Properties();
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//4.获取连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
//5.获取连接
Connection conn = ds.getConnection();
2. 定义工具类
1. 定义一个类 JDBCUtils
2. 提供静态代码块加载配置文件,初始化连接池对象
3. 提供方法
1. 获取连接方法:通过数据库连接池获取连接
2. 释放资源
3. 获取连接池的方法
* 代码:
public class JDBCUtils {
//1.定义成员变量 DataSource
private static DataSource ds ;
static{
try {
//1.加载配置文件
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//2.获取DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/**
* 释放资源
*/
public static void close(Statement stmt,Connection conn){
/* if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}*/
close(null,stmt,conn);
}
public static void close(ResultSet rs , Statement stmt, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 获取连接池方法
*/
public static DataSource getDataSource(){
return ds;
}
}
1. 导入jar包 druid-1.0.9.jar
2. 定义配置文件:
* 是properties形式的
* 可以叫任意名称,可以放在任意目录下
3. 加载配置文件。Properties
4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory
5. 获取连接:getConnection
* 代码:
//3.加载配置文件
Properties pro = new Properties();
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//4.获取连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
//5.获取连接
Connection conn = ds.getConnection();
2. 定义工具类
1. 定义一个类 JDBCUtils
2. 提供静态代码块加载配置文件,初始化连接池对象
3. 提供方法
1. 获取连接方法:通过数据库连接池获取连接
2. 释放资源
3. 获取连接池的方法
* 代码:
public class JDBCUtils {
//1.定义成员变量 DataSource
private static DataSource ds ;
static{
try {
//1.加载配置文件
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//2.获取DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/**
* 释放资源
*/
public static void close(Statement stmt,Connection conn){
/* if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}*/
close(null,stmt,conn);
}
public static void close(ResultSet rs , Statement stmt, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 获取连接池方法
*/
public static DataSource getDataSource(){
return ds;
}
}
Spring JDBC
* Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
* 步骤:
1. 导入jar包
2. 创建JdbcTemplate对象。依赖于数据源DataSource
* JdbcTemplate template = new JdbcTemplate(ds);
3. 调用JdbcTemplate的方法来完成CRUD的操作
* update():执行DML语句。增、删、改语句
* queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
* 注意:这个方法查询的结果集长度只能是1
* queryForList():查询结果将结果集封装为list集合
* 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
* query():查询结果,将结果封装为JavaBean对象
* query的参数:RowMapper
* 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
* new BeanPropertyRowMapper<类型>(类型.class)
* queryForObject:查询结果,将结果封装为对象
* 一般用于聚合函数的查询
* 步骤:
1. 导入jar包
2. 创建JdbcTemplate对象。依赖于数据源DataSource
* JdbcTemplate template = new JdbcTemplate(ds);
3. 调用JdbcTemplate的方法来完成CRUD的操作
* update():执行DML语句。增、删、改语句
* queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
* 注意:这个方法查询的结果集长度只能是1
* queryForList():查询结果将结果集封装为list集合
* 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
* query():查询结果,将结果封装为JavaBean对象
* query的参数:RowMapper
* 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
* new BeanPropertyRowMapper<类型>(类型.class)
* queryForObject:查询结果,将结果封装为对象
* 一般用于聚合函数的查询
练习
* 需求:
1. 修改1号数据的 salary 为 10000
2. 添加一条记录
3. 删除刚才添加的记录
4. 查询id为1的记录,将其封装为Map集合
5. 查询所有记录,将其封装为List
6. 查询所有记录,将其封装为Emp对象的List集合
7. 查询总记录数
* 代码:
import cn.itcast.domain.Emp;
import cn.itcast.utils.JDBCUtils;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
public class JdbcTemplateDemo2 {
//Junit单元测试,可以让方法独立执行
//1. 获取JDBCTemplate对象
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/**
* 1. 修改1号数据的 salary 为 10000
*/
@Test
public void test1(){
//2. 定义sql
String sql = "update emp set salary = 10000 where id = 1001";
//3. 执行sql
int count = template.update(sql);
System.out.println(count);
}
/**
* 2. 添加一条记录
*/
@Test
public void test2(){
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
int count = template.update(sql, 1015, "郭靖", 10);
System.out.println(count);
}
/**
* 3.删除刚才添加的记录
*/
@Test
public void test3(){
String sql = "delete from emp where id = ?";
int count = template.update(sql, 1015);
System.out.println(count);
}
/**
* 4.查询id为1001的记录,将其封装为Map集合
* 注意:这个方法查询的结果集长度只能是1
*/
@Test
public void test4(){
String sql = "select * from emp where id = ? or id = ?";
Map<String, Object> map = template.queryForMap(sql, 1001,1002);
System.out.println(map);
//{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}
}
/**
* 5. 查询所有记录,将其封装为List
*/
@Test
public void test5(){
String sql = "select * from emp";
List<Map<String, Object>> list = template.queryForList(sql);
for (Map<String, Object> stringObjectMap : list) {
System.out.println(stringObjectMap);
}
}
/**
* 6. 查询所有记录,将其封装为Emp对象的List集合
*/
@Test
public void test6(){
String sql = "select * from emp";
List<Emp> list = template.query(sql, new RowMapper<Emp>() {
@Override
public Emp mapRow(ResultSet rs, int i) throws SQLException {
Emp emp = new Emp();
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
return emp;
}
});
for (Emp emp : list) {
System.out.println(emp);
}
}
/**
* 6. 查询所有记录,将其封装为Emp对象的List集合
*/
@Test
public void test6_2(){
String sql = "select * from emp";
List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
for (Emp emp : list) {
System.out.println(emp);
}
}
/**
* 7. 查询总记录数
*/
@Test
public void test7(){
String sql = "select count(id) from emp";
Long total = template.queryForObject(sql, Long.class);
System.out.println(total);
}
}
1. 修改1号数据的 salary 为 10000
2. 添加一条记录
3. 删除刚才添加的记录
4. 查询id为1的记录,将其封装为Map集合
5. 查询所有记录,将其封装为List
6. 查询所有记录,将其封装为Emp对象的List集合
7. 查询总记录数
* 代码:
import cn.itcast.domain.Emp;
import cn.itcast.utils.JDBCUtils;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
public class JdbcTemplateDemo2 {
//Junit单元测试,可以让方法独立执行
//1. 获取JDBCTemplate对象
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/**
* 1. 修改1号数据的 salary 为 10000
*/
@Test
public void test1(){
//2. 定义sql
String sql = "update emp set salary = 10000 where id = 1001";
//3. 执行sql
int count = template.update(sql);
System.out.println(count);
}
/**
* 2. 添加一条记录
*/
@Test
public void test2(){
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
int count = template.update(sql, 1015, "郭靖", 10);
System.out.println(count);
}
/**
* 3.删除刚才添加的记录
*/
@Test
public void test3(){
String sql = "delete from emp where id = ?";
int count = template.update(sql, 1015);
System.out.println(count);
}
/**
* 4.查询id为1001的记录,将其封装为Map集合
* 注意:这个方法查询的结果集长度只能是1
*/
@Test
public void test4(){
String sql = "select * from emp where id = ? or id = ?";
Map<String, Object> map = template.queryForMap(sql, 1001,1002);
System.out.println(map);
//{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}
}
/**
* 5. 查询所有记录,将其封装为List
*/
@Test
public void test5(){
String sql = "select * from emp";
List<Map<String, Object>> list = template.queryForList(sql);
for (Map<String, Object> stringObjectMap : list) {
System.out.println(stringObjectMap);
}
}
/**
* 6. 查询所有记录,将其封装为Emp对象的List集合
*/
@Test
public void test6(){
String sql = "select * from emp";
List<Emp> list = template.query(sql, new RowMapper<Emp>() {
@Override
public Emp mapRow(ResultSet rs, int i) throws SQLException {
Emp emp = new Emp();
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
return emp;
}
});
for (Emp emp : list) {
System.out.println(emp);
}
}
/**
* 6. 查询所有记录,将其封装为Emp对象的List集合
*/
@Test
public void test6_2(){
String sql = "select * from emp";
List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
for (Emp emp : list) {
System.out.println(emp);
}
}
/**
* 7. 查询总记录数
*/
@Test
public void test7(){
String sql = "select count(id) from emp";
Long total = template.queryForObject(sql, Long.class);
System.out.println(total);
}
}
HTML
标签复习
. 文件标签:构成html最基本的标签
* html:html文档的根标签
* head:头标签。用于指定html文档的一些属性。引入外部的资源
* title:标题标签。
* body:体标签
* <!DOCTYPE html>:html5中定义该文档是html文档
* html:html文档的根标签
* head:头标签。用于指定html文档的一些属性。引入外部的资源
* title:标题标签。
* body:体标签
* <!DOCTYPE html>:html5中定义该文档是html文档
文本标签:和文本有关的标签
* 注释:<!-- 注释内容 -->
* <h1> to <h6>:标题标签
* h1~h6:字体大小逐渐递减
* <p>:段落标签
* <br>:换行标签
* <hr>:展示一条水平线
* 属性:
* color:颜色
* width:宽度
* size:高度
* align:对其方式
* center:居中
* left:左对齐
* right:右对齐
* <b>:字体加粗
* <i>:字体斜体
* <font>:字体标签
* <center>:文本居中
* 属性:
* color:颜色
* size:大小
* face:字体
* 属性定义:
* color:
1. 英文单词:red,green,blue
2. rgb(值1,值2,值3):值的范围:0~255 如 rgb(0,0,255)
3. #值1值2值3:值的范围:00~FF之间。如: #FF00FF
* width:
1. 数值:width='20' ,数值的单位,默认是 px(像素)
2. 数值%:占比相对于父元素的比例
* 注释:<!-- 注释内容 -->
* <h1> to <h6>:标题标签
* h1~h6:字体大小逐渐递减
* <p>:段落标签
* <br>:换行标签
* <hr>:展示一条水平线
* 属性:
* color:颜色
* width:宽度
* size:高度
* align:对其方式
* center:居中
* left:左对齐
* right:右对齐
* <b>:字体加粗
* <i>:字体斜体
* <font>:字体标签
* <center>:文本居中
* 属性:
* color:颜色
* size:大小
* face:字体
* 属性定义:
* color:
1. 英文单词:red,green,blue
2. rgb(值1,值2,值3):值的范围:0~255 如 rgb(0,0,255)
3. #值1值2值3:值的范围:00~FF之间。如: #FF00FF
* width:
1. 数值:width='20' ,数值的单位,默认是 px(像素)
2. 数值%:占比相对于父元素的比例
公司介绍
<!DOCTYPE html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<title>黑马程序员简介</title>
</head>
<body>
<h1>
公司简介
</h1>
<hr color="#ffd700">
<p>
<font color="#FF0000">"中关村黑马程序员训练营"</font>是由<b><i>传智播客</i></b>联合中关村软件园、CSDN, 并委托传智播客进行教学实施的软件开发高端培训机构,致力于服务各大软件企业,解决当前软件开发技术飞速发展, 而企业招不到优秀人才的困扰。
</p>
<p>
目前,“中关村黑马程序员训练营”已成长为行业“学员质量好、课程内容深、企业满意”的移动开发高端训练基地, 并被评为中关村软件园重点扶持人才企业。
</p>
<p>
黑马程序员的学员多为大学毕业后,有理想、有梦想,想从事IT行业,而没有环境和机遇改变自己命运的年轻人。 黑马程序员的学员筛选制度,远比现在90%以上的企业招聘流程更为严格。任何一名学员想成功入学“黑马程序员”, 必须经历长达2个月的面试流程,这些流程中不仅包括严格的技术测试、自学能力测试,还包括性格测试、压力测试、 品德测试等等测试。毫不夸张地说,黑马程序员训练营所有学员都是精挑细选出来的。百里挑一的残酷筛选制度确 保学员质量,并降低企业的用人风险。
中关村黑马程序员训练营不仅着重培养学员的基础理论知识,更注重培养项目实施管理能力,并密切关注技术革新, 不断引入先进的技术,研发更新技术课程,确保学员进入企业后不仅能独立从事开发工作,更能给企业带来新的技术体系和理念。
</p>
<p>
一直以来,黑马程序员以技术视角关注IT产业发展,以深度分享推进产业技术成长,致力于弘扬技术创新,倡导分享、 开放和协作,努力打造高质量的IT人才服务平台。
</p>
<hr color="#ffd700">
<font color="gray" size="2">
<center>
江苏传智播客教育科技股份有限公司<br>
版权所有Copyright 2006-2018©, All Rights Reserved 苏ICP备16007882
</center>
</font>
</body>
</html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<title>黑马程序员简介</title>
</head>
<body>
<h1>
公司简介
</h1>
<hr color="#ffd700">
<p>
<font color="#FF0000">"中关村黑马程序员训练营"</font>是由<b><i>传智播客</i></b>联合中关村软件园、CSDN, 并委托传智播客进行教学实施的软件开发高端培训机构,致力于服务各大软件企业,解决当前软件开发技术飞速发展, 而企业招不到优秀人才的困扰。
</p>
<p>
目前,“中关村黑马程序员训练营”已成长为行业“学员质量好、课程内容深、企业满意”的移动开发高端训练基地, 并被评为中关村软件园重点扶持人才企业。
</p>
<p>
黑马程序员的学员多为大学毕业后,有理想、有梦想,想从事IT行业,而没有环境和机遇改变自己命运的年轻人。 黑马程序员的学员筛选制度,远比现在90%以上的企业招聘流程更为严格。任何一名学员想成功入学“黑马程序员”, 必须经历长达2个月的面试流程,这些流程中不仅包括严格的技术测试、自学能力测试,还包括性格测试、压力测试、 品德测试等等测试。毫不夸张地说,黑马程序员训练营所有学员都是精挑细选出来的。百里挑一的残酷筛选制度确 保学员质量,并降低企业的用人风险。
中关村黑马程序员训练营不仅着重培养学员的基础理论知识,更注重培养项目实施管理能力,并密切关注技术革新, 不断引入先进的技术,研发更新技术课程,确保学员进入企业后不仅能独立从事开发工作,更能给企业带来新的技术体系和理念。
</p>
<p>
一直以来,黑马程序员以技术视角关注IT产业发展,以深度分享推进产业技术成长,致力于弘扬技术创新,倡导分享、 开放和协作,努力打造高质量的IT人才服务平台。
</p>
<hr color="#ffd700">
<font color="gray" size="2">
<center>
江苏传智播客教育科技股份有限公司<br>
版权所有Copyright 2006-2018©, All Rights Reserved 苏ICP备16007882
</center>
</font>
</body>
</html>
图片标签:
* img:展示图片
* 属性:
* src:指定图片的位置
* 代码:
<!--展示一张图片 img-->
<img src="image/jingxuan_2.jpg" align="right" alt="古镇" width="500" height="500"/>
<!--
相对路径
* 以.开头的路径
* ./:代表当前目录 ./image/1.jpg
* ../:代表上一级目录
-->
<img src="./image/jiangwai_1.jpg">
<img src="../image/jiangwai_1.jpg">
* img:展示图片
* 属性:
* src:指定图片的位置
* 代码:
<!--展示一张图片 img-->
<img src="image/jingxuan_2.jpg" align="right" alt="古镇" width="500" height="500"/>
<!--
相对路径
* 以.开头的路径
* ./:代表当前目录 ./image/1.jpg
* ../:代表上一级目录
-->
<img src="./image/jiangwai_1.jpg">
<img src="../image/jiangwai_1.jpg">
列表标签:
* 有序列表:
* ol:
* li:
* 无序列表:
* ul:
* li:
* 有序列表:
* ol:
* li:
* 无序列表:
* ul:
* li:
链接标签:
* a:定义一个超链接
* 属性:
* href:指定访问资源的URL(统一资源定位符)
* target:指定打开资源的方式
* _self:默认值,在当前页面打开
* _blank:在空白页面打开
* 代码:
<!--超链接 a-->
<a href="http://www.itcast.cn">点我</a>
<br>
<a href="http://www.itcast.cn" target="_self">点我</a>
<br>
<a href="http://www.itcast.cn" target="_blank">点我</a>
<br>
<a href="./5_列表标签.html">列表标签</a><br>
<a href="mailto:itcast@itcast.cn">联系我们</a>
<br>
<a href="http://www.itcast.cn"><img src="image/jiangwai_1.jpg"></a>
* a:定义一个超链接
* 属性:
* href:指定访问资源的URL(统一资源定位符)
* target:指定打开资源的方式
* _self:默认值,在当前页面打开
* _blank:在空白页面打开
* 代码:
<!--超链接 a-->
<a href="http://www.itcast.cn">点我</a>
<br>
<a href="http://www.itcast.cn" target="_self">点我</a>
<br>
<a href="http://www.itcast.cn" target="_blank">点我</a>
<br>
<a href="./5_列表标签.html">列表标签</a><br>
<a href="mailto:itcast@itcast.cn">联系我们</a>
<br>
<a href="http://www.itcast.cn"><img src="image/jiangwai_1.jpg"></a>
div和span:
* div:每一个div占满一整行。块级标签
* span:文本信息在一行展示,行内标签 内联标签
* div:每一个div占满一整行。块级标签
* span:文本信息在一行展示,行内标签 内联标签
语义化标签:html5中为了提高程序的可读性,提供了一些标签。
1. <header>:页眉
2. <footer>:页脚
1. <header>:页眉
2. <footer>:页脚
表格标签:
* table:定义表格
* width:宽度
* border:边框
* cellpadding:定义内容和单元格的距离
* cellspacing:定义单元格之间的距离。如果指定为0,则单元格的线会合为一条、
* bgcolor:背景色
* align:对齐方式
* tr:定义行
* bgcolor:背景色
* align:对齐方式
* td:定义单元格
* colspan:合并列
* rowspan:合并行
* th:定义表头单元格
* <caption>:表格标题
* <thead>:表示表格的头部分
* <tbody>:表示表格的体部分
* <tfoot>:表示表格的脚部分
* table:定义表格
* width:宽度
* border:边框
* cellpadding:定义内容和单元格的距离
* cellspacing:定义单元格之间的距离。如果指定为0,则单元格的线会合为一条、
* bgcolor:背景色
* align:对齐方式
* tr:定义行
* bgcolor:背景色
* align:对齐方式
* td:定义单元格
* colspan:合并列
* rowspan:合并行
* th:定义表头单元格
* <caption>:表格标题
* <thead>:表示表格的头部分
* <tbody>:表示表格的体部分
* <tfoot>:表示表格的脚部分
案例:旅游网页
## 案例:旅游网站首页
1. 确定使用table来完成布局
2. 如果某一行只有一个单元格,则使用<tr><td></td></tr>
3. 如果某一行有多个单元格,则使用
<tr>
<td>
<table></table>
</td>
</tr>
4. 代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>黑马旅游网</title>
</head>
<body>
<!--采用table来完成布局-->
<!--最外层的table,用于整个页面的布局-->
<table width="100%" align="center">
<!-- 第1行 -->
<tr>
<td>
<img src="image/top_banner.jpg" width="100%" alt="">
</td>
</tr>
<!-- 第2行 -->
<tr>
<td>
<table width="100%" align="center">
<tr>
<td>
<img src="image/logo.jpg" alt="">
</td>
<td>
<img src="image/search.png" alt="">
</td>
<td>
<img src="image/hotel_tel.png" alt="">
</td>
</tr>
</table>
</td>
</tr>
<!-- 第3行 -->
<tr>
<td>
<table width="100%" align="center">
<tr bgcolor="#ffd700" align="center" height="45" >
<td>
<a href="">首页</a>
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
</tr>
</table>
</td>
</tr>
<!-- 第4行 轮播图 -->
<tr>
<td>
<img src="image/banner_3.jpg" alt="" width="100%">
</td>
</tr>
<!-- 第5行 黑马精选-->
<tr>
<td>
<img src="image/icon_5.jpg" alt="">
黑马精选
<hr color="#ffd700" >
</td>
</tr>
<!-- 第6行 -->
<tr>
<td>
<table align="center" width="95%">
<tr>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
</tr>
</table>
</td>
</tr>
<!-- 第7行 国内游 -->
<tr>
<td>
<img src="image/icon_6.jpg" alt="">
国内游
<hr color="#ffd700" >
</td>
</tr>
<!-- 第8行 -->
<tr>
<td>
<table align="center" width="95%">
<tr>
<td rowspan="2">
<img src="image/guonei_1.jpg" alt="">
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="" height="100%">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
<tr>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
</table>
</td>
</tr>
<!-- 第9行 境外游 -->
<tr>
<td>
<img src="image/icon_7.jpg" alt="">
境外游
<hr color="#ffd700" >
</td>
</tr>
<!-- 第10行 -->
<tr>
<td>
<table align="center" width="95%">
<tr>
<td rowspan="2">
<img src="image/jiangwai_1.jpg" alt="">
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="" height="100%">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
<tr>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
</table>
</td>
</tr>
<!-- 第11行 -->
<tr>
<td>
<img src="image/footer_service.png" alt="" width="100%">
</td>
</tr>
<!-- 第12行 -->
<tr>
<td align="center" bgcolor="#ffd700" height="40">
<font color="gray" size="2">
江苏传智播客教育科技股份有限公司
版权所有Copyright 2006-2018©, All Rights Reserved 苏ICP备16007882
</font>
</td>
</tr>
</table>
</body>
</html>
1. 确定使用table来完成布局
2. 如果某一行只有一个单元格,则使用<tr><td></td></tr>
3. 如果某一行有多个单元格,则使用
<tr>
<td>
<table></table>
</td>
</tr>
4. 代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>黑马旅游网</title>
</head>
<body>
<!--采用table来完成布局-->
<!--最外层的table,用于整个页面的布局-->
<table width="100%" align="center">
<!-- 第1行 -->
<tr>
<td>
<img src="image/top_banner.jpg" width="100%" alt="">
</td>
</tr>
<!-- 第2行 -->
<tr>
<td>
<table width="100%" align="center">
<tr>
<td>
<img src="image/logo.jpg" alt="">
</td>
<td>
<img src="image/search.png" alt="">
</td>
<td>
<img src="image/hotel_tel.png" alt="">
</td>
</tr>
</table>
</td>
</tr>
<!-- 第3行 -->
<tr>
<td>
<table width="100%" align="center">
<tr bgcolor="#ffd700" align="center" height="45" >
<td>
<a href="">首页</a>
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
<td>
门票
</td>
</tr>
</table>
</td>
</tr>
<!-- 第4行 轮播图 -->
<tr>
<td>
<img src="image/banner_3.jpg" alt="" width="100%">
</td>
</tr>
<!-- 第5行 黑马精选-->
<tr>
<td>
<img src="image/icon_5.jpg" alt="">
黑马精选
<hr color="#ffd700" >
</td>
</tr>
<!-- 第6行 -->
<tr>
<td>
<table align="center" width="95%">
<tr>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
<td>
<img src="image/jiangxuan_1.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 899</font>
</td>
</tr>
</table>
</td>
</tr>
<!-- 第7行 国内游 -->
<tr>
<td>
<img src="image/icon_6.jpg" alt="">
国内游
<hr color="#ffd700" >
</td>
</tr>
<!-- 第8行 -->
<tr>
<td>
<table align="center" width="95%">
<tr>
<td rowspan="2">
<img src="image/guonei_1.jpg" alt="">
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="" height="100%">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
<tr>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_2.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
</table>
</td>
</tr>
<!-- 第9行 境外游 -->
<tr>
<td>
<img src="image/icon_7.jpg" alt="">
境外游
<hr color="#ffd700" >
</td>
</tr>
<!-- 第10行 -->
<tr>
<td>
<table align="center" width="95%">
<tr>
<td rowspan="2">
<img src="image/jiangwai_1.jpg" alt="">
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="" height="100%">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
<tr>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
<td>
<img src="image/jiangxuan_3.jpg" alt="">
<p>上海飞三亚五天4晚自由行(春节销售+亲子+蜜月+自由行)</p>
<font color="red">¥ 699</font>
</td>
</tr>
</table>
</td>
</tr>
<!-- 第11行 -->
<tr>
<td>
<img src="image/footer_service.png" alt="" width="100%">
</td>
</tr>
<!-- 第12行 -->
<tr>
<td align="center" bgcolor="#ffd700" height="40">
<font color="gray" size="2">
江苏传智播客教育科技股份有限公司
版权所有Copyright 2006-2018©, All Rights Reserved 苏ICP备16007882
</font>
</td>
</tr>
</table>
</body>
</html>
* 表单:
* 概念:用于采集用户输入的数据的。用于和服务器进行交互。
* form:用于定义表单的。可以定义一个范围,范围代表采集用户数据的范围
* 属性:
* action:指定提交数据的URL
* method:指定提交方式
* 分类:一共7种,2种比较常用
* get:
1. 请求参数会在地址栏中显示。会封装到请求行中(HTTP协议后讲解)。
2. 请求参数大小是有限制的。
3. 不太安全。
* post:
2. 请求参数不会再地址栏中显示。会封装在请求体中(HTTP协议后讲解)
2. 请求参数的大小没有限制。
3. 较为安全。
* 表单项中的数据要想被提交:必须指定其name属性
* 概念:用于采集用户输入的数据的。用于和服务器进行交互。
* form:用于定义表单的。可以定义一个范围,范围代表采集用户数据的范围
* 属性:
* action:指定提交数据的URL
* method:指定提交方式
* 分类:一共7种,2种比较常用
* get:
1. 请求参数会在地址栏中显示。会封装到请求行中(HTTP协议后讲解)。
2. 请求参数大小是有限制的。
3. 不太安全。
* post:
2. 请求参数不会再地址栏中显示。会封装在请求体中(HTTP协议后讲解)
2. 请求参数的大小没有限制。
3. 较为安全。
* 表单项中的数据要想被提交:必须指定其name属性
* 表单项标签:
* input:可以通过type属性值,改变元素展示的样式
* type属性:
* text:文本输入框,默认值
* placeholder:指定输入框的提示信息,当输入框的内容发生变化,会自动清空提示信息
* password:密码输入框
* radio:单选框
* 注意:
1. 要想让多个单选框实现单选的效果,则多个单选框的name属性值必须一样。
2. 一般会给每一个单选框提供value属性,指定其被选中后提交的值
3. checked属性,可以指定默认值
* checkbox:复选框
* 注意:
1. 一般会给每一个单选框提供value属性,指定其被选中后提交的值
2. checked属性,可以指定默认值
* file:文件选择框
* hidden:隐藏域,用于提交一些信息。
* 按钮:
* submit:提交按钮。可以提交表单
* button:普通按钮
* image:图片提交按钮
* src属性指定图片的路径
* label:指定输入项的文字描述信息
* 注意:
* label的for属性一般会和 input 的 id属性值 对应。如果对应了,则点击label区域,会让input输入框获取焦点。
* select: 下拉列表
* 子元素:option,指定列表项
* textarea:文本域
* cols:指定列数,每一行有多少个字符
* rows:默认多少行。
* input:可以通过type属性值,改变元素展示的样式
* type属性:
* text:文本输入框,默认值
* placeholder:指定输入框的提示信息,当输入框的内容发生变化,会自动清空提示信息
* password:密码输入框
* radio:单选框
* 注意:
1. 要想让多个单选框实现单选的效果,则多个单选框的name属性值必须一样。
2. 一般会给每一个单选框提供value属性,指定其被选中后提交的值
3. checked属性,可以指定默认值
* checkbox:复选框
* 注意:
1. 一般会给每一个单选框提供value属性,指定其被选中后提交的值
2. checked属性,可以指定默认值
* file:文件选择框
* hidden:隐藏域,用于提交一些信息。
* 按钮:
* submit:提交按钮。可以提交表单
* button:普通按钮
* image:图片提交按钮
* src属性指定图片的路径
* label:指定输入项的文字描述信息
* 注意:
* label的for属性一般会和 input 的 id属性值 对应。如果对应了,则点击label区域,会让input输入框获取焦点。
* select: 下拉列表
* 子元素:option,指定列表项
* textarea:文本域
* cols:指定列数,每一行有多少个字符
* rows:默认多少行。
CSS
概念
Cascading Style Sheets 层叠样式表
* 层叠:多个样式可以作用在同一个html的元素上,同时生效
* 层叠:多个样式可以作用在同一个html的元素上,同时生效
好处
1. 功能强大
2. 将内容展示和样式控制分离
* 降低耦合度。解耦
* 让分工协作更容易
* 提高开发效率
2. 将内容展示和样式控制分离
* 降低耦合度。解耦
* 让分工协作更容易
* 提高开发效率
使用
CSS的使用:CSS与html结合方式
1. 内联样式
* 在标签内使用style属性指定css代码
* 如:<div style="color:red;">hello css</div>
* 在标签内使用style属性指定css代码
* 如:<div style="color:red;">hello css</div>
2. 内部样式
* 在head标签内,定义style标签,style标签的标签体内容就是css代码
* 如:
<style>
div{
color:blue;
}
</style>
<div>hello css</div>
* 在head标签内,定义style标签,style标签的标签体内容就是css代码
* 如:
<style>
div{
color:blue;
}
</style>
<div>hello css</div>
3. 外部样式
1. 定义css资源文件。
2. 在head标签内,定义link标签,引入外部的资源文件
* 如:
* a.css文件:
div{
color:green;
}
<link rel="stylesheet" href="css/a.css">
<div>hello css</div>
<div>hello css</div>
1. 定义css资源文件。
2. 在head标签内,定义link标签,引入外部的资源文件
* 如:
* a.css文件:
div{
color:green;
}
<link rel="stylesheet" href="css/a.css">
<div>hello css</div>
<div>hello css</div>
* 注意:
* 1,2,3种方式 css作用范围越来越大
* 1方式不常用,后期常用2,3
* 3种格式可以写为:
<style>
@import "css/a.css";
</style>
* 1,2,3种方式 css作用范围越来越大
* 1方式不常用,后期常用2,3
* 3种格式可以写为:
<style>
@import "css/a.css";
</style>
语法
* 格式:
选择器 {
属性名1:属性值1;
属性名2:属性值2;
...
}
* 选择器:筛选具有相似特征的元素
* 注意:
* 每一对属性需要使用;隔开,最后一对属性可以不加;
选择器 {
属性名1:属性值1;
属性名2:属性值2;
...
}
* 选择器:筛选具有相似特征的元素
* 注意:
* 每一对属性需要使用;隔开,最后一对属性可以不加;
选择器
分类
基础选择器
1. 基础选择器
1. id选择器:选择具体的id属性值的元素.建议在一个html页面中id值唯一
* 语法:#id属性值{}
1. id选择器:选择具体的id属性值的元素.建议在一个html页面中id值唯一
* 语法:#id属性值{}
2. 元素选择器:选择具有相同标签名称的元素
* 语法: 标签名称{}
* 注意:id选择器优先级高于元素选择器
* 语法: 标签名称{}
* 注意:id选择器优先级高于元素选择器
3. 类选择器:选择具有相同的class属性值的元素。
* 语法:.class属性值{}
* 注意:类选择器选择器优先级高于元素选择器
* 语法:.class属性值{}
* 注意:类选择器选择器优先级高于元素选择器
扩展选择器
1. 选择所有元素:
* 语法: *{}
2. 并集选择器:
* 选择器1,选择器2{}
3. 子选择器:筛选选择器1元素下的选择器2元素
* 语法: 选择器1 选择器2{}
4. 父选择器:筛选选择器2的父元素选择器1
* 语法: 选择器1 > 选择器2{}
5. 属性选择器:选择元素名称,属性名=属性值的元素
* 语法: 元素名称[属性名="属性值"]{}
6. 伪类选择器:选择一些元素具有的状态
* 语法: 元素:状态{}
* 如: <a>
* 状态:
* link:初始化的状态
* visited:被访问过的状态
* active:正在访问状态
* hover:鼠标悬浮状态
* 语法: *{}
2. 并集选择器:
* 选择器1,选择器2{}
3. 子选择器:筛选选择器1元素下的选择器2元素
* 语法: 选择器1 选择器2{}
4. 父选择器:筛选选择器2的父元素选择器1
* 语法: 选择器1 > 选择器2{}
5. 属性选择器:选择元素名称,属性名=属性值的元素
* 语法: 元素名称[属性名="属性值"]{}
6. 伪类选择器:选择一些元素具有的状态
* 语法: 元素:状态{}
* 如: <a>
* 状态:
* link:初始化的状态
* visited:被访问过的状态
* active:正在访问状态
* hover:鼠标悬浮状态
属性
1. 字体、文本
* font-size:字体大小
* color:文本颜色
* text-align:对其方式
* line-height:行高
2. 背景
* background:
3. 边框
* border:设置边框,符合属性
4. 尺寸
* width:宽度
* height:高度
5. 盒子模型:控制布局
* margin:外边距
* padding:内边距
* 默认情况下内边距会影响整个盒子的大小
* box-sizing: border-box; 设置盒子的属性,让width和height就是最终盒子的大小
* float:浮动
* left
* right
* font-size:字体大小
* color:文本颜色
* text-align:对其方式
* line-height:行高
2. 背景
* background:
3. 边框
* border:设置边框,符合属性
4. 尺寸
* width:宽度
* height:高度
5. 盒子模型:控制布局
* margin:外边距
* padding:内边距
* 默认情况下内边距会影响整个盒子的大小
* box-sizing: border-box; 设置盒子的属性,让width和height就是最终盒子的大小
* float:浮动
* left
* right
- CSS案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<style>
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body{
background: url("img/register_bg.png") no-repeat center;
padding-top: 25px;
}
.rg_layout{
width: 900px;
height: 500px;
border: 8px solid #EEEEEE;
background-color: white;
/*让div水平居中*/
margin: auto;
}
.rg_left{
/*border: 1px solid red;*/
float: left;
margin: 15px;
}
.rg_left > p:first-child{
color:#FFD026;
font-size: 20px;
}
.rg_left > p:last-child{
color:#A6A6A6;
font-size: 20px;
}
.rg_center{
float: left;
/* border: 1px solid red;*/
}
.rg_right{
/*border: 1px solid red;*/
float: right;
margin: 15px;
}
.rg_right > p:first-child{
font-size: 15px;
}
.rg_right p a {
color:pink;
}
.td_left{
width: 100px;
text-align: right;
height: 45px;
}
.td_right{
padding-left: 50px ;
}
#username,#password,#email,#name,#tel,#birthday,#checkcode{
width: 251px;
height: 32px;
border: 1px solid #A6A6A6 ;
/*设置边框圆角*/
border-radius: 5px;
padding-left: 10px;
}
#checkcode{
width: 110px;
}
#img_check{
height: 32px;
vertical-align: middle;
}
#btn_sub{
width: 150px;
height: 40px;
background-color: #FFD026;
border: 1px solid #FFD026 ;
}
</style>
</head>
<body>
<div class="rg_layout">
<div class="rg_left">
<p>新用户注册</p>
<p>USER REGISTER</p>
</div>
<div class="rg_center">
<div class="rg_form">
<!--定义表单 form-->
<form action="#" method="post">
<table>
<tr>
<td class="td_left"><label for="username">用户名</label></td>
<td class="td_right"><input type="text" name="username" id="username" placeholder="请输入用户名"></td>
</tr>
<tr>
<td class="td_left"><label for="password">密码</label></td>
<td class="td_right"><input type="password" name="password" id="password" placeholder="请输入密码"></td>
</tr>
<tr>
<td class="td_left"><label for="email">Email</label></td>
<td class="td_right"><input type="email" name="email" id="email" placeholder="请输入邮箱"></td>
</tr>
<tr>
<td class="td_left"><label for="name">姓名</label></td>
<td class="td_right"><input type="text" name="name" id="name" placeholder="请输入姓名"></td>
</tr>
<tr>
<td class="td_left"><label for="tel">手机号</label></td>
<td class="td_right"><input type="text" name="tel" id="tel" placeholder="请输入手机号"></td>
</tr>
<tr>
<td class="td_left"><label>性别</label></td>
<td class="td_right">
<input type="radio" name="gender" value="male"> 男
<input type="radio" name="gender" value="female"> 女
</td>
</tr>
<tr>
<td class="td_left"><label for="birthday">出生日期</label></td>
<td class="td_right"><input type="date" name="birthday" id="birthday" placeholder="请输入出生日期"></td>
</tr>
<tr>
<td class="td_left"><label for="checkcode" >验证码</label></td>
<td class="td_right"><input type="text" name="checkcode" id="checkcode" placeholder="请输入验证码">
<img id="img_check" src="img/verify_code.jpg">
</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" id="btn_sub" value="注册"></td>
</tr>
</table>
</form>
</div>
</div>
<div class="rg_right">
<p>已有账号?<a href="#">立即登录</a></p>
</div>
</div>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<style>
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body{
background: url("img/register_bg.png") no-repeat center;
padding-top: 25px;
}
.rg_layout{
width: 900px;
height: 500px;
border: 8px solid #EEEEEE;
background-color: white;
/*让div水平居中*/
margin: auto;
}
.rg_left{
/*border: 1px solid red;*/
float: left;
margin: 15px;
}
.rg_left > p:first-child{
color:#FFD026;
font-size: 20px;
}
.rg_left > p:last-child{
color:#A6A6A6;
font-size: 20px;
}
.rg_center{
float: left;
/* border: 1px solid red;*/
}
.rg_right{
/*border: 1px solid red;*/
float: right;
margin: 15px;
}
.rg_right > p:first-child{
font-size: 15px;
}
.rg_right p a {
color:pink;
}
.td_left{
width: 100px;
text-align: right;
height: 45px;
}
.td_right{
padding-left: 50px ;
}
#username,#password,#email,#name,#tel,#birthday,#checkcode{
width: 251px;
height: 32px;
border: 1px solid #A6A6A6 ;
/*设置边框圆角*/
border-radius: 5px;
padding-left: 10px;
}
#checkcode{
width: 110px;
}
#img_check{
height: 32px;
vertical-align: middle;
}
#btn_sub{
width: 150px;
height: 40px;
background-color: #FFD026;
border: 1px solid #FFD026 ;
}
</style>
</head>
<body>
<div class="rg_layout">
<div class="rg_left">
<p>新用户注册</p>
<p>USER REGISTER</p>
</div>
<div class="rg_center">
<div class="rg_form">
<!--定义表单 form-->
<form action="#" method="post">
<table>
<tr>
<td class="td_left"><label for="username">用户名</label></td>
<td class="td_right"><input type="text" name="username" id="username" placeholder="请输入用户名"></td>
</tr>
<tr>
<td class="td_left"><label for="password">密码</label></td>
<td class="td_right"><input type="password" name="password" id="password" placeholder="请输入密码"></td>
</tr>
<tr>
<td class="td_left"><label for="email">Email</label></td>
<td class="td_right"><input type="email" name="email" id="email" placeholder="请输入邮箱"></td>
</tr>
<tr>
<td class="td_left"><label for="name">姓名</label></td>
<td class="td_right"><input type="text" name="name" id="name" placeholder="请输入姓名"></td>
</tr>
<tr>
<td class="td_left"><label for="tel">手机号</label></td>
<td class="td_right"><input type="text" name="tel" id="tel" placeholder="请输入手机号"></td>
</tr>
<tr>
<td class="td_left"><label>性别</label></td>
<td class="td_right">
<input type="radio" name="gender" value="male"> 男
<input type="radio" name="gender" value="female"> 女
</td>
</tr>
<tr>
<td class="td_left"><label for="birthday">出生日期</label></td>
<td class="td_right"><input type="date" name="birthday" id="birthday" placeholder="请输入出生日期"></td>
</tr>
<tr>
<td class="td_left"><label for="checkcode" >验证码</label></td>
<td class="td_right"><input type="text" name="checkcode" id="checkcode" placeholder="请输入验证码">
<img id="img_check" src="img/verify_code.jpg">
</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" id="btn_sub" value="注册"></td>
</tr>
</table>
</form>
</div>
</div>
<div class="rg_right">
<p>已有账号?<a href="#">立即登录</a></p>
</div>
</div>
</body>
</html>
JS
练习99乘法表
练习:99乘法表
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>99乘法表</title>
<style>
td{
border: 1px solid;
}
</style>
<script>
document.write("<table align='center'>");
//1.完成基本的for循环嵌套,展示乘法表
for (var i = 1; i <= 9 ; i++) {
document.write("<tr>");
for (var j = 1; j <=i ; j++) {
document.write("<td>");
//输出 1 * 1 = 1
document.write(i + " * " + j + " = " + ( i*j) +" ");
document.write("</td>");
}
/*//输出换行
document.write("<br>");*/
document.write("</tr>");
}
//2.完成表格嵌套
document.write("</table>");
</script>
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>99乘法表</title>
<style>
td{
border: 1px solid;
}
</style>
<script>
document.write("<table align='center'>");
//1.完成基本的for循环嵌套,展示乘法表
for (var i = 1; i <= 9 ; i++) {
document.write("<tr>");
for (var j = 1; j <=i ; j++) {
document.write("<td>");
//输出 1 * 1 = 1
document.write(i + " * " + j + " = " + ( i*j) +" ");
document.write("</td>");
}
/*//输出换行
document.write("<br>");*/
document.write("</tr>");
}
//2.完成表格嵌套
document.write("</table>");
</script>
</head>
<body>
</body>
</html>
灯泡控制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>电灯开关</title>
</head>
<body>
<img id="light" src="img/off.gif">
<script>
/*
分析:
1.获取图片对象
2.绑定单击事件
3.每次点击切换图片
* 规则:
* 如果灯是开的 on,切换图片为 off
* 如果灯是关的 off,切换图片为 on
* 使用标记flag来完成
*/
//1.获取图片对象
var light = document.getElementById("light");
var flag = false;//代表灯是灭的。 off图片
//2.绑定单击事件
light.onclick = function(){
if(flag){//判断如果灯是开的,则灭掉
light.src = "img/off.gif";
flag = false;
}else{
//如果灯是灭的,则打开
light.src = "img/on.gif";
flag = true;
}
}
</script>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>电灯开关</title>
</head>
<body>
<img id="light" src="img/off.gif">
<script>
/*
分析:
1.获取图片对象
2.绑定单击事件
3.每次点击切换图片
* 规则:
* 如果灯是开的 on,切换图片为 off
* 如果灯是关的 off,切换图片为 on
* 使用标记flag来完成
*/
//1.获取图片对象
var light = document.getElementById("light");
var flag = false;//代表灯是灭的。 off图片
//2.绑定单击事件
light.onclick = function(){
if(flag){//判断如果灯是开的,则灭掉
light.src = "img/off.gif";
flag = false;
}else{
//如果灯是灭的,则打开
light.src = "img/on.gif";
flag = true;
}
}
</script>
</body>
</html>
轮播图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>轮播图</title>
</head>
<body>
<img id="img" src="img/banner_1.jpg" width="100%">
<script>
/*
分析:
1.在页面上使用img标签展示图片
2.定义一个方法,修改图片对象的src属性
3.定义一个定时器,每隔3秒调用方法一次。
*/
//修改图片src属性
var number = 1;
function fun(){
number ++ ;
//判断number是否大于3
if(number > 3){
number = 1;
}
//获取img对象
var img = document.getElementById("img");
img.src = "img/banner_"+number+".jpg";
}
//2.定义定时器
setInterval(fun,3000);
</script>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>轮播图</title>
</head>
<body>
<img id="img" src="img/banner_1.jpg" width="100%">
<script>
/*
分析:
1.在页面上使用img标签展示图片
2.定义一个方法,修改图片对象的src属性
3.定义一个定时器,每隔3秒调用方法一次。
*/
//修改图片src属性
var number = 1;
function fun(){
number ++ ;
//判断number是否大于3
if(number > 3){
number = 1;
}
//获取img对象
var img = document.getElementById("img");
img.src = "img/banner_"+number+".jpg";
}
//2.定义定时器
setInterval(fun,3000);
</script>
</body>
</html>
自动跳转首页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自动跳转</title>
<style>
p{
text-align: center;
}
span{
color:red;
}
</style>
</head>
<body>
<p>
<span id="time">5</span>秒之后,自动跳转到首页...
</p>
<script>
/*
分析:
1.显示页面效果 <p>
2.倒计时读秒效果实现
2.1 定义一个方法,获取span标签,修改span标签体内容,时间--
2.2 定义一个定时器,1秒执行一次该方法
3.在方法中判断时间如果<= 0 ,则跳转到首页
*/
// 2.倒计时读秒效果实现
var second = 5;
var time = document.getElementById("time");
//定义一个方法,获取span标签,修改span标签体内容,时间--
function showTime(){
second -- ;
//判断时间如果<= 0 ,则跳转到首页
if(second <= 0){
//跳转到首页
location.href = "https://www.baidu.com";
}
time.innerHTML = second +"";
}
//设置定时器,1秒执行一次该方法
setInterval(showTime,1000);
</script>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自动跳转</title>
<style>
p{
text-align: center;
}
span{
color:red;
}
</style>
</head>
<body>
<p>
<span id="time">5</span>秒之后,自动跳转到首页...
</p>
<script>
/*
分析:
1.显示页面效果 <p>
2.倒计时读秒效果实现
2.1 定义一个方法,获取span标签,修改span标签体内容,时间--
2.2 定义一个定时器,1秒执行一次该方法
3.在方法中判断时间如果<= 0 ,则跳转到首页
*/
// 2.倒计时读秒效果实现
var second = 5;
var time = document.getElementById("time");
//定义一个方法,获取span标签,修改span标签体内容,时间--
function showTime(){
second -- ;
//判断时间如果<= 0 ,则跳转到首页
if(second <= 0){
//跳转到首页
location.href = "https://www.baidu.com";
}
time.innerHTML = second +"";
}
//设置定时器,1秒执行一次该方法
setInterval(showTime,1000);
</script>
</body>
</html>
动态表格
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>动态表格</title>
<style>
table{
border: 1px solid;
margin: auto;
width: 500px;
}
td,th{
text-align: center;
border: 1px solid;
}
div{
text-align: center;
margin: 50px;
}
</style>
</head>
<body>
<div>
<input type="text" id="id" placeholder="请输入编号">
<input type="text" id="name" placeholder="请输入姓名">
<input type="text" id="gender" placeholder="请输入性别">
<input type="button" value="添加" id="btn_add">
</div>
<table>
<caption>学生信息表</caption>
<tr>
<th>编号</th>
<th>姓名</th>
<th>性别</th>
<th>操作</th>
</tr>
<tr>
<td>1</td>
<td>令狐冲</td>
<td>男</td>
<td><a href="javascript:void(0);" onclick="delTr(this);">删除</a></td>
</tr>
<tr>
<td>2</td>
<td>任我行</td>
<td>男</td>
<td><a href="javascript:void(0);" onclick="delTr(this);">删除</a></td>
</tr>
<tr>
<td>3</td>
<td>岳不群</td>
<td>?</td>
<td><a href="javascript:void(0);" onclick="delTr(this);" >删除</a></td>
</tr>
</table>
<script>
/*
分析:
1.添加:
1. 给添加按钮绑定单击事件
2. 获取文本框的内容
3. 创建td,设置td的文本为文本框的内容。
4. 创建tr
5. 将td添加到tr中
6. 获取table,将tr添加到table中
2.删除:
1.确定点击的是哪一个超链接
<a href="javascript:void(0);" onclick="delTr(this);" >删除</a>
2.怎么删除?
removeChild():通过父节点删除子节点
*/
//1.获取按钮
/* document.getElementById("btn_add").onclick = function(){
//2.获取文本框的内容
var id = document.getElementById("id").value;
var name = document.getElementById("name").value;
var gender = document.getElementById("gender").value;
//3.创建td,赋值td的标签体
//id 的 td
var td_id = document.createElement("td");
var text_id = document.createTextNode(id);
td_id.appendChild(text_id);
//name 的 td
var td_name = document.createElement("td");
var text_name = document.createTextNode(name);
td_name.appendChild(text_name);
//gender 的 td
var td_gender = document.createElement("td");
var text_gender = document.createTextNode(gender);
td_gender.appendChild(text_gender);
//a标签的td
var td_a = document.createElement("td");
var ele_a = document.createElement("a");
ele_a.setAttribute("href","javascript:void(0);");
ele_a.setAttribute("onclick","delTr(this);");
var text_a = document.createTextNode("删除");
ele_a.appendChild(text_a);
td_a.appendChild(ele_a);
//4.创建tr
var tr = document.createElement("tr");
//5.添加td到tr中
tr.appendChild(td_id);
tr.appendChild(td_name);
tr.appendChild(td_gender);
tr.appendChild(td_a);
//6.获取table
var table = document.getElementsByTagName("table")[0];
table.appendChild(tr);
}*/
//使用innerHTML添加
document.getElementById("btn_add").onclick = function() {
//2.获取文本框的内容
var id = document.getElementById("id").value;
var name = document.getElementById("name").value;
var gender = document.getElementById("gender").value;
//获取table
var table = document.getElementsByTagName("table")[0];
//追加一行
table.innerHTML += "<tr>\n" +
" <td>"+id+"</td>\n" +
" <td>"+name+"</td>\n" +
" <td>"+gender+"</td>\n" +
" <td><a href=\"javascript:void(0);\" onclick=\"delTr(this);\" >删除</a></td>\n" +
" </tr>";
}
//删除方法
function delTr(obj){
var table = obj.parentNode.parentNode.parentNode;
var tr = obj.parentNode.parentNode;
table.removeChild(tr);
}
</script>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>动态表格</title>
<style>
table{
border: 1px solid;
margin: auto;
width: 500px;
}
td,th{
text-align: center;
border: 1px solid;
}
div{
text-align: center;
margin: 50px;
}
</style>
</head>
<body>
<div>
<input type="text" id="id" placeholder="请输入编号">
<input type="text" id="name" placeholder="请输入姓名">
<input type="text" id="gender" placeholder="请输入性别">
<input type="button" value="添加" id="btn_add">
</div>
<table>
<caption>学生信息表</caption>
<tr>
<th>编号</th>
<th>姓名</th>
<th>性别</th>
<th>操作</th>
</tr>
<tr>
<td>1</td>
<td>令狐冲</td>
<td>男</td>
<td><a href="javascript:void(0);" onclick="delTr(this);">删除</a></td>
</tr>
<tr>
<td>2</td>
<td>任我行</td>
<td>男</td>
<td><a href="javascript:void(0);" onclick="delTr(this);">删除</a></td>
</tr>
<tr>
<td>3</td>
<td>岳不群</td>
<td>?</td>
<td><a href="javascript:void(0);" onclick="delTr(this);" >删除</a></td>
</tr>
</table>
<script>
/*
分析:
1.添加:
1. 给添加按钮绑定单击事件
2. 获取文本框的内容
3. 创建td,设置td的文本为文本框的内容。
4. 创建tr
5. 将td添加到tr中
6. 获取table,将tr添加到table中
2.删除:
1.确定点击的是哪一个超链接
<a href="javascript:void(0);" onclick="delTr(this);" >删除</a>
2.怎么删除?
removeChild():通过父节点删除子节点
*/
//1.获取按钮
/* document.getElementById("btn_add").onclick = function(){
//2.获取文本框的内容
var id = document.getElementById("id").value;
var name = document.getElementById("name").value;
var gender = document.getElementById("gender").value;
//3.创建td,赋值td的标签体
//id 的 td
var td_id = document.createElement("td");
var text_id = document.createTextNode(id);
td_id.appendChild(text_id);
//name 的 td
var td_name = document.createElement("td");
var text_name = document.createTextNode(name);
td_name.appendChild(text_name);
//gender 的 td
var td_gender = document.createElement("td");
var text_gender = document.createTextNode(gender);
td_gender.appendChild(text_gender);
//a标签的td
var td_a = document.createElement("td");
var ele_a = document.createElement("a");
ele_a.setAttribute("href","javascript:void(0);");
ele_a.setAttribute("onclick","delTr(this);");
var text_a = document.createTextNode("删除");
ele_a.appendChild(text_a);
td_a.appendChild(ele_a);
//4.创建tr
var tr = document.createElement("tr");
//5.添加td到tr中
tr.appendChild(td_id);
tr.appendChild(td_name);
tr.appendChild(td_gender);
tr.appendChild(td_a);
//6.获取table
var table = document.getElementsByTagName("table")[0];
table.appendChild(tr);
}*/
//使用innerHTML添加
document.getElementById("btn_add").onclick = function() {
//2.获取文本框的内容
var id = document.getElementById("id").value;
var name = document.getElementById("name").value;
var gender = document.getElementById("gender").value;
//获取table
var table = document.getElementsByTagName("table")[0];
//追加一行
table.innerHTML += "<tr>\n" +
" <td>"+id+"</td>\n" +
" <td>"+name+"</td>\n" +
" <td>"+gender+"</td>\n" +
" <td><a href=\"javascript:void(0);\" onclick=\"delTr(this);\" >删除</a></td>\n" +
" </tr>";
}
//删除方法
function delTr(obj){
var table = obj.parentNode.parentNode.parentNode;
var tr = obj.parentNode.parentNode;
table.removeChild(tr);
}
</script>
</body>
</html>
表格全选
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表格全选</title>
<style>
table{
border: 1px solid;
width: 500px;
margin-left: 30%;
}
td,th{
text-align: center;
border: 1px solid;
}
div{
margin-top: 10px;
margin-left: 30%;
}
.out{
background-color: white;
}
.over{
background-color: pink;
}
</style>
<script>
/*
分析:
1.全选:
* 获取所有的checkbox
* 遍历cb,设置每一个cb的状态为选中 checked
*/
//1.在页面加载完后绑定事件
window.onload = function(){
//2.给全选按钮绑定单击事件
document.getElementById("selectAll").onclick = function(){
//全选
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态为选中 checked
cbs[i].checked = true;
}
}
document.getElementById("unSelectAll").onclick = function(){
//全不选
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态为未选中 checked
cbs[i].checked = false;
}
}
document.getElementById("selectRev").onclick = function(){
//反选
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态为相反
cbs[i].checked = !cbs[i].checked;
}
}
document.getElementById("firstCb").onclick = function(){
//第一个cb点击
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//获取第一个cb
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态和第一个cb的状态一样
cbs[i].checked = this.checked;
}
}
//给所有tr绑定鼠标移到元素之上和移出元素事件
var trs = document.getElementsByTagName("tr");
//2.遍历
for (var i = 0; i < trs.length; i++) {
//移到元素之上
trs[i].onmouseover = function(){
this.className = "over";
}
//移出元素
trs[i].onmouseout = function(){
this.className = "out";
}
}
}
</script>
</head>
<body>
<table>
<caption>学生信息表</caption>
<tr>
<th><input type="checkbox" name="cb" id="firstCb"></th>
<th>编号</th>
<th>姓名</th>
<th>性别</th>
<th>操作</th>
</tr>
<tr>
<td><input type="checkbox" name="cb" ></td>
<td>1</td>
<td>令狐冲</td>
<td>男</td>
<td><a href="javascript:void(0);">删除</a></td>
</tr>
<tr>
<td><input type="checkbox" name="cb" ></td>
<td>2</td>
<td>任我行</td>
<td>男</td>
<td><a href="javascript:void(0);">删除</a></td>
</tr>
<tr>
<td><input type="checkbox" name="cb" ></td>
<td>3</td>
<td>岳不群</td>
<td>?</td>
<td><a href="javascript:void(0);">删除</a></td>
</tr>
</table>
<div>
<input type="button" id="selectAll" value="全选">
<input type="button" id="unSelectAll" value="全不选">
<input type="button" id="selectRev" value="反选">
</div>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表格全选</title>
<style>
table{
border: 1px solid;
width: 500px;
margin-left: 30%;
}
td,th{
text-align: center;
border: 1px solid;
}
div{
margin-top: 10px;
margin-left: 30%;
}
.out{
background-color: white;
}
.over{
background-color: pink;
}
</style>
<script>
/*
分析:
1.全选:
* 获取所有的checkbox
* 遍历cb,设置每一个cb的状态为选中 checked
*/
//1.在页面加载完后绑定事件
window.onload = function(){
//2.给全选按钮绑定单击事件
document.getElementById("selectAll").onclick = function(){
//全选
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态为选中 checked
cbs[i].checked = true;
}
}
document.getElementById("unSelectAll").onclick = function(){
//全不选
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态为未选中 checked
cbs[i].checked = false;
}
}
document.getElementById("selectRev").onclick = function(){
//反选
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态为相反
cbs[i].checked = !cbs[i].checked;
}
}
document.getElementById("firstCb").onclick = function(){
//第一个cb点击
//1.获取所有的checkbox
var cbs = document.getElementsByName("cb");
//获取第一个cb
//2.遍历
for (var i = 0; i < cbs.length; i++) {
//3.设置每一个cb的状态和第一个cb的状态一样
cbs[i].checked = this.checked;
}
}
//给所有tr绑定鼠标移到元素之上和移出元素事件
var trs = document.getElementsByTagName("tr");
//2.遍历
for (var i = 0; i < trs.length; i++) {
//移到元素之上
trs[i].onmouseover = function(){
this.className = "over";
}
//移出元素
trs[i].onmouseout = function(){
this.className = "out";
}
}
}
</script>
</head>
<body>
<table>
<caption>学生信息表</caption>
<tr>
<th><input type="checkbox" name="cb" id="firstCb"></th>
<th>编号</th>
<th>姓名</th>
<th>性别</th>
<th>操作</th>
</tr>
<tr>
<td><input type="checkbox" name="cb" ></td>
<td>1</td>
<td>令狐冲</td>
<td>男</td>
<td><a href="javascript:void(0);">删除</a></td>
</tr>
<tr>
<td><input type="checkbox" name="cb" ></td>
<td>2</td>
<td>任我行</td>
<td>男</td>
<td><a href="javascript:void(0);">删除</a></td>
</tr>
<tr>
<td><input type="checkbox" name="cb" ></td>
<td>3</td>
<td>岳不群</td>
<td>?</td>
<td><a href="javascript:void(0);">删除</a></td>
</tr>
</table>
<div>
<input type="button" id="selectAll" value="全选">
<input type="button" id="unSelectAll" value="全不选">
<input type="button" id="selectRev" value="反选">
</div>
</body>
</html>
表格验证
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<style>
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body{
background: url("img/register_bg.png") no-repeat center;
padding-top: 25px;
}
.rg_layout{
width: 900px;
height: 500px;
border: 8px solid #EEEEEE;
background-color: white;
/*让div水平居中*/
margin: auto;
}
.rg_left{
/*border: 1px solid red;*/
float: left;
margin: 15px;
}
.rg_left > p:first-child{
color:#FFD026;
font-size: 20px;
}
.rg_left > p:last-child{
color:#A6A6A6;
font-size: 20px;
}
.rg_center{
float: left;
/* border: 1px solid red;*/
}
.rg_right{
/*border: 1px solid red;*/
float: right;
margin: 15px;
}
.rg_right > p:first-child{
font-size: 15px;
}
.rg_right p a {
color:pink;
}
.td_left{
width: 100px;
text-align: right;
height: 45px;
}
.td_right{
padding-left: 50px ;
}
#username,#password,#email,#name,#tel,#birthday,#checkcode{
width: 251px;
height: 32px;
border: 1px solid #A6A6A6 ;
/*设置边框圆角*/
border-radius: 5px;
padding-left: 10px;
}
#checkcode{
width: 110px;
}
#img_check{
height: 32px;
vertical-align: middle;
}
#btn_sub{
width: 150px;
height: 40px;
background-color: #FFD026;
border: 1px solid #FFD026 ;
}
.error{
color:red;
}
#td_sub{
padding-left: 150px;
}
</style>
<script>
/*
分析:
1.给表单绑定onsubmit事件。监听器中判断每一个方法校验的结果。
如果都为true,则监听器方法返回true
如果有一个为false,则监听器方法返回false
2.定义一些方法分别校验各个表单项。
3.给各个表单项绑定onblur事件。
*/
window.onload = function(){
//1.给表单绑定onsubmit事件
document.getElementById("form").onsubmit = function(){
//调用用户校验方法 chekUsername();
//调用密码校验方法 chekPassword();
//return checkUsername() && checkPassword();
return checkUsername() && checkPassword();
}
//给用户名和密码框分别绑定离焦事件
document.getElementById("username").onblur = checkUsername;
document.getElementById("password").onblur = checkPassword;
}
//校验用户名
function checkUsername(){
//1.获取用户名的值
var username = document.getElementById("username").value;
//2.定义正则表达式
var reg_username = /^\w{6,12}$/;
//3.判断值是否符合正则的规则
var flag = reg_username.test(username);
//4.提示信息
var s_username = document.getElementById("s_username");
if(flag){
//提示绿色对勾
s_username.innerHTML = "<img width='35' height='25' src='img/gou.png'/>";
}else{
//提示红色用户名有误
s_username.innerHTML = "用户名格式有误";
}
return flag;
}
//校验密码
function checkPassword(){
//1.获取用户名的值
var password = document.getElementById("password").value;
//2.定义正则表达式
var reg_password = /^\w{6,12}$/;
//3.判断值是否符合正则的规则
var flag = reg_password.test(password);
//4.提示信息
var s_password = document.getElementById("s_password");
if(flag){
//提示绿色对勾
s_password.innerHTML = "<img width='35' height='25' src='img/gou.png'/>";
}else{
//提示红色用户名有误
s_password.innerHTML = "密码格式有误";
}
return flag;
}
</script>
</head>
<body>
<div class="rg_layout">
<div class="rg_left">
<p>新用户注册</p>
<p>USER REGISTER</p>
</div>
<div class="rg_center">
<div class="rg_form">
<!--定义表单 form-->
<form action="#" id="form" method="get">
<table>
<tr>
<td class="td_left"><label for="username">用户名</label></td>
<td class="td_right">
<input type="text" name="username" id="username" placeholder="请输入用户名">
<span id="s_username" class="error"></span>
</td>
</tr>
<tr>
<td class="td_left"><label for="password">密码</label></td>
<td class="td_right">
<input type="password" name="password" id="password" placeholder="请输入密码">
<span id="s_password" class="error"></span>
</td>
</tr>
<tr>
<td class="td_left"><label for="email">Email</label></td>
<td class="td_right"><input type="email" name="email" id="email" placeholder="请输入邮箱"></td>
</tr>
<tr>
<td class="td_left"><label for="name">姓名</label></td>
<td class="td_right"><input type="text" name="name" id="name" placeholder="请输入姓名"></td>
</tr>
<tr>
<td class="td_left"><label for="tel">手机号</label></td>
<td class="td_right"><input type="text" name="tel" id="tel" placeholder="请输入手机号"></td>
</tr>
<tr>
<td class="td_left"><label>性别</label></td>
<td class="td_right">
<input type="radio" name="gender" value="male" checked> 男
<input type="radio" name="gender" value="female"> 女
</td>
</tr>
<tr>
<td class="td_left"><label for="birthday">出生日期</label></td>
<td class="td_right"><input type="date" name="birthday" id="birthday" placeholder="请输入出生日期"></td>
</tr>
<tr>
<td class="td_left"><label for="checkcode" >验证码</label></td>
<td class="td_right"><input type="text" name="checkcode" id="checkcode" placeholder="请输入验证码">
<img id="img_check" src="img/verify_code.jpg">
</td>
</tr>
<tr>
<td colspan="2" id="td_sub"><input type="submit" id="btn_sub" value="注册"></td>
</tr>
</table>
</form>
</div>
</div>
<div class="rg_right">
<p>已有账号?<a href="#">立即登录</a></p>
</div>
</div>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<style>
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body{
background: url("img/register_bg.png") no-repeat center;
padding-top: 25px;
}
.rg_layout{
width: 900px;
height: 500px;
border: 8px solid #EEEEEE;
background-color: white;
/*让div水平居中*/
margin: auto;
}
.rg_left{
/*border: 1px solid red;*/
float: left;
margin: 15px;
}
.rg_left > p:first-child{
color:#FFD026;
font-size: 20px;
}
.rg_left > p:last-child{
color:#A6A6A6;
font-size: 20px;
}
.rg_center{
float: left;
/* border: 1px solid red;*/
}
.rg_right{
/*border: 1px solid red;*/
float: right;
margin: 15px;
}
.rg_right > p:first-child{
font-size: 15px;
}
.rg_right p a {
color:pink;
}
.td_left{
width: 100px;
text-align: right;
height: 45px;
}
.td_right{
padding-left: 50px ;
}
#username,#password,#email,#name,#tel,#birthday,#checkcode{
width: 251px;
height: 32px;
border: 1px solid #A6A6A6 ;
/*设置边框圆角*/
border-radius: 5px;
padding-left: 10px;
}
#checkcode{
width: 110px;
}
#img_check{
height: 32px;
vertical-align: middle;
}
#btn_sub{
width: 150px;
height: 40px;
background-color: #FFD026;
border: 1px solid #FFD026 ;
}
.error{
color:red;
}
#td_sub{
padding-left: 150px;
}
</style>
<script>
/*
分析:
1.给表单绑定onsubmit事件。监听器中判断每一个方法校验的结果。
如果都为true,则监听器方法返回true
如果有一个为false,则监听器方法返回false
2.定义一些方法分别校验各个表单项。
3.给各个表单项绑定onblur事件。
*/
window.onload = function(){
//1.给表单绑定onsubmit事件
document.getElementById("form").onsubmit = function(){
//调用用户校验方法 chekUsername();
//调用密码校验方法 chekPassword();
//return checkUsername() && checkPassword();
return checkUsername() && checkPassword();
}
//给用户名和密码框分别绑定离焦事件
document.getElementById("username").onblur = checkUsername;
document.getElementById("password").onblur = checkPassword;
}
//校验用户名
function checkUsername(){
//1.获取用户名的值
var username = document.getElementById("username").value;
//2.定义正则表达式
var reg_username = /^\w{6,12}$/;
//3.判断值是否符合正则的规则
var flag = reg_username.test(username);
//4.提示信息
var s_username = document.getElementById("s_username");
if(flag){
//提示绿色对勾
s_username.innerHTML = "<img width='35' height='25' src='img/gou.png'/>";
}else{
//提示红色用户名有误
s_username.innerHTML = "用户名格式有误";
}
return flag;
}
//校验密码
function checkPassword(){
//1.获取用户名的值
var password = document.getElementById("password").value;
//2.定义正则表达式
var reg_password = /^\w{6,12}$/;
//3.判断值是否符合正则的规则
var flag = reg_password.test(password);
//4.提示信息
var s_password = document.getElementById("s_password");
if(flag){
//提示绿色对勾
s_password.innerHTML = "<img width='35' height='25' src='img/gou.png'/>";
}else{
//提示红色用户名有误
s_password.innerHTML = "密码格式有误";
}
return flag;
}
</script>
</head>
<body>
<div class="rg_layout">
<div class="rg_left">
<p>新用户注册</p>
<p>USER REGISTER</p>
</div>
<div class="rg_center">
<div class="rg_form">
<!--定义表单 form-->
<form action="#" id="form" method="get">
<table>
<tr>
<td class="td_left"><label for="username">用户名</label></td>
<td class="td_right">
<input type="text" name="username" id="username" placeholder="请输入用户名">
<span id="s_username" class="error"></span>
</td>
</tr>
<tr>
<td class="td_left"><label for="password">密码</label></td>
<td class="td_right">
<input type="password" name="password" id="password" placeholder="请输入密码">
<span id="s_password" class="error"></span>
</td>
</tr>
<tr>
<td class="td_left"><label for="email">Email</label></td>
<td class="td_right"><input type="email" name="email" id="email" placeholder="请输入邮箱"></td>
</tr>
<tr>
<td class="td_left"><label for="name">姓名</label></td>
<td class="td_right"><input type="text" name="name" id="name" placeholder="请输入姓名"></td>
</tr>
<tr>
<td class="td_left"><label for="tel">手机号</label></td>
<td class="td_right"><input type="text" name="tel" id="tel" placeholder="请输入手机号"></td>
</tr>
<tr>
<td class="td_left"><label>性别</label></td>
<td class="td_right">
<input type="radio" name="gender" value="male" checked> 男
<input type="radio" name="gender" value="female"> 女
</td>
</tr>
<tr>
<td class="td_left"><label for="birthday">出生日期</label></td>
<td class="td_right"><input type="date" name="birthday" id="birthday" placeholder="请输入出生日期"></td>
</tr>
<tr>
<td class="td_left"><label for="checkcode" >验证码</label></td>
<td class="td_right"><input type="text" name="checkcode" id="checkcode" placeholder="请输入验证码">
<img id="img_check" src="img/verify_code.jpg">
</td>
</tr>
<tr>
<td colspan="2" id="td_sub"><input type="submit" id="btn_sub" value="注册"></td>
</tr>
</table>
</form>
</div>
</div>
<div class="rg_right">
<p>已有账号?<a href="#">立即登录</a></p>
</div>
</div>
</body>
</html>
常见的事件
常见的事件:
1. 点击事件:
1. onclick:单击事件
2. ondblclick:双击事件
2. 焦点事件
1. onblur:失去焦点
2. onfocus:元素获得焦点。
3. 加载事件:
1. onload:一张页面或一幅图像完成加载。
4. 鼠标事件:
1. onmousedown 鼠标按钮被按下。
2. onmouseup 鼠标按键被松开。
3. onmousemove 鼠标被移动。
4. onmouseover 鼠标移到某元素之上。
5. onmouseout 鼠标从某元素移开。
5. 键盘事件:
1. onkeydown 某个键盘按键被按下。
2. onkeyup 某个键盘按键被松开。
3. onkeypress 某个键盘按键被按下并松开。
6. 选择和改变
1. onchange 域的内容被改变。
2. onselect 文本被选中。
7. 表单事件:
1. onsubmit 确认按钮被点击。
2. onreset 重置按钮被点击。
1. 点击事件:
1. onclick:单击事件
2. ondblclick:双击事件
2. 焦点事件
1. onblur:失去焦点
2. onfocus:元素获得焦点。
3. 加载事件:
1. onload:一张页面或一幅图像完成加载。
4. 鼠标事件:
1. onmousedown 鼠标按钮被按下。
2. onmouseup 鼠标按键被松开。
3. onmousemove 鼠标被移动。
4. onmouseover 鼠标移到某元素之上。
5. onmouseout 鼠标从某元素移开。
5. 键盘事件:
1. onkeydown 某个键盘按键被按下。
2. onkeyup 某个键盘按键被松开。
3. onkeypress 某个键盘按键被按下并松开。
6. 选择和改变
1. onchange 域的内容被改变。
2. onselect 文本被选中。
7. 表单事件:
1. onsubmit 确认按钮被点击。
2. onreset 重置按钮被点击。
BootStrap
概念
一个前端开发的框架,Bootstrap,来自 Twitter,是目前很受欢迎的前端框架。
Bootstrap 是基于 HTML、CSS、JavaScript 的,它简洁灵活,使得 Web 开发更加快捷。
Bootstrap 是基于 HTML、CSS、JavaScript 的,它简洁灵活,使得 Web 开发更加快捷。
* 框架:一个半成品软件,开发人员可以在框架基础上,在进行开发,简化编码。
* 好处:
1. 定义了很多的css样式和js插件。我们开发人员直接可以使用这些样式和插件得到丰富的页面效果。
2. 响应式布局。
* 同一套页面可以兼容不同分辨率的设备。
* 好处:
1. 定义了很多的css样式和js插件。我们开发人员直接可以使用这些样式和插件得到丰富的页面效果。
2. 响应式布局。
* 同一套页面可以兼容不同分辨率的设备。
快速入门
步骤
1. 下载Bootstrap
2. 在项目中将这三个文件夹复制
3. 创建html页面,引入必要的资源文件
示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap HelloWorld</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="js/jquery-3.2.1.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="js/bootstrap.min.js"></script>
</head>
<body>
<h1>你好,世界!</h1>
</body>
</html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap HelloWorld</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="js/jquery-3.2.1.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="js/bootstrap.min.js"></script>
</head>
<body>
<h1>你好,世界!</h1>
</body>
</html>
响应式布局
实现
实现:依赖于栅格系统:将一行平均分成12个格子,可以指定元素占几个格子
步骤
1. 定义容器。相当于之前的table、
* 容器分类:
1. container:两边留白
2. container-fluid:每一种设备都是100%宽度
* 容器分类:
1. container:两边留白
2. container-fluid:每一种设备都是100%宽度
2. 定义行。相当于之前的tr 样式:row
3. 定义元素。指定该元素在不同的设备上,所占的格子数目。样式:col-设备代号-格子数目
* 设备代号:
1. xs:超小屏幕 手机 (<768px):col-xs-12
2. sm:小屏幕 平板 (≥768px)
3. md:中等屏幕 桌面显示器 (≥992px)
4. lg:大屏幕 大桌面显示器 (≥1200px)
* 设备代号:
1. xs:超小屏幕 手机 (<768px):col-xs-12
2. sm:小屏幕 平板 (≥768px)
3. md:中等屏幕 桌面显示器 (≥992px)
4. lg:大屏幕 大桌面显示器 (≥1200px)
注意:
1. 一行中如果格子数目超过12,则超出部分自动换行。
2. 栅格类属性可以向上兼容。栅格类适用于与屏幕宽度大于或等于分界点大小的设备。
3. 如果真实设备宽度小于了设置栅格类属性的设备代码的最小值,会一个元素沾满一整行。
1. 一行中如果格子数目超过12,则超出部分自动换行。
2. 栅格类属性可以向上兼容。栅格类适用于与屏幕宽度大于或等于分界点大小的设备。
3. 如果真实设备宽度小于了设置栅格类属性的设备代码的最小值,会一个元素沾满一整行。
CSS样式和JS插件
全局CSS样式
* 按钮:class="btn btn-default"
* 图片:
* class="img-responsive":图片在任意尺寸都占100%
* 图片形状
* <img src="..." alt="..." class="img-rounded">:方形
* <img src="..." alt="..." class="img-circle"> : 圆形
* <img src="..." alt="..." class="img-thumbnail"> :相框
* 表格
* table
* table-bordered
* table-hover
* 表单
* 给表单项添加:class="form-control"
* 图片:
* class="img-responsive":图片在任意尺寸都占100%
* 图片形状
* <img src="..." alt="..." class="img-rounded">:方形
* <img src="..." alt="..." class="img-circle"> : 圆形
* <img src="..." alt="..." class="img-thumbnail"> :相框
* 表格
* table
* table-bordered
* table-hover
* 表单
* 给表单项添加:class="form-control"
组件
* 导航条
* 分页条
* 分页条
插件
* 轮播图
案例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap HelloWorld</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="js/jquery-3.2.1.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="js/bootstrap.min.js"></script>
<style>
.paddtop{
padding-top: 10px;
}
.search-btn{
float: left;
border:1px solid #ffc900;
width: 90px;
height: 35px;
background-color:#ffc900 ;
text-align: center;
line-height: 35px;
margin-top: 15px;
}
.search-input{
float: left;
border:2px solid #ffc900;
width: 400px;
height: 35px;
padding-left: 5px;
margin-top: 15px;
}
.jx{
border-bottom: 2px solid #ffc900;
padding: 5px;
}
.company{
height: 40px;
background-color: #ffc900;
text-align: center;
line-height:40px ;
font-size: 8px;
}
</style>
</head>
<body>
<!-- 1.页眉部分-->
<header class="container-fluid">
<div class="row">
<img src="img/top_banner.jpg" class="img-responsive">
</div>
<div class="row paddtop">
<div class="col-md-3">
<img src="img/logo.jpg" class="img-responsive">
</div>
<div class="col-md-5">
<input class="search-input" placeholder="请输入线路名称">
<a class="search-btn" href="#">搜索</a>
</div>
<div class="col-md-4">
<img src="img/hotel_tel.png" class="img-responsive">
</div>
</div>
<!--导航栏-->
<div class="row">
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<!-- 定义汉堡按钮 -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
<!--轮播图-->
<div class="row">
<div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
<!-- Indicators -->
<ol class="carousel-indicators">
<li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
<li data-target="#carousel-example-generic" data-slide-to="1"></li>
<li data-target="#carousel-example-generic" data-slide-to="2"></li>
</ol>
<!-- Wrapper for slides -->
<div class="carousel-inner" role="listbox">
<div class="item active">
<img src="img/banner_1.jpg" alt="...">
</div>
<div class="item">
<img src="img/banner_2.jpg" alt="...">
</div>
<div class="item">
<img src="img/banner_3.jpg" alt="...">
</div>
</div>
<!-- Controls -->
<a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
</header>
<!-- 2.主体部分-->
<div class="container">
<div class="row jx">
<img src="img/icon_5.jpg">
<span>黑马精选</span>
</div>
<div class="row paddtop">
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
</div>
<div class="row jx">
<img src="img/icon_6.jpg">
<span>国内游</span>
</div>
<div class="row paddtop">
<div class="col-md-4">
<img src="img/guonei_1.jpg">
</div>
<div class="col-md-8">
<div class="row">
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 3.页脚部分-->
<footer class="container-fluid">
<div class="row">
<img src="img/footer_service.png" class="img-responsive">
</div>
<div class="row company">
江苏传智播客教育科技股份有限公司 版权所有Copyright 2006-2018, All Rights Reserved 苏ICP备16007882
</div>
</footer>
</body>
</html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap HelloWorld</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="js/jquery-3.2.1.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="js/bootstrap.min.js"></script>
<style>
.paddtop{
padding-top: 10px;
}
.search-btn{
float: left;
border:1px solid #ffc900;
width: 90px;
height: 35px;
background-color:#ffc900 ;
text-align: center;
line-height: 35px;
margin-top: 15px;
}
.search-input{
float: left;
border:2px solid #ffc900;
width: 400px;
height: 35px;
padding-left: 5px;
margin-top: 15px;
}
.jx{
border-bottom: 2px solid #ffc900;
padding: 5px;
}
.company{
height: 40px;
background-color: #ffc900;
text-align: center;
line-height:40px ;
font-size: 8px;
}
</style>
</head>
<body>
<!-- 1.页眉部分-->
<header class="container-fluid">
<div class="row">
<img src="img/top_banner.jpg" class="img-responsive">
</div>
<div class="row paddtop">
<div class="col-md-3">
<img src="img/logo.jpg" class="img-responsive">
</div>
<div class="col-md-5">
<input class="search-input" placeholder="请输入线路名称">
<a class="search-btn" href="#">搜索</a>
</div>
<div class="col-md-4">
<img src="img/hotel_tel.png" class="img-responsive">
</div>
</div>
<!--导航栏-->
<div class="row">
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<!-- 定义汉堡按钮 -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
<!--轮播图-->
<div class="row">
<div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
<!-- Indicators -->
<ol class="carousel-indicators">
<li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
<li data-target="#carousel-example-generic" data-slide-to="1"></li>
<li data-target="#carousel-example-generic" data-slide-to="2"></li>
</ol>
<!-- Wrapper for slides -->
<div class="carousel-inner" role="listbox">
<div class="item active">
<img src="img/banner_1.jpg" alt="...">
</div>
<div class="item">
<img src="img/banner_2.jpg" alt="...">
</div>
<div class="item">
<img src="img/banner_3.jpg" alt="...">
</div>
</div>
<!-- Controls -->
<a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
</header>
<!-- 2.主体部分-->
<div class="container">
<div class="row jx">
<img src="img/icon_5.jpg">
<span>黑马精选</span>
</div>
<div class="row paddtop">
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-3">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
</div>
<div class="row jx">
<img src="img/icon_6.jpg">
<span>国内游</span>
</div>
<div class="row paddtop">
<div class="col-md-4">
<img src="img/guonei_1.jpg">
</div>
<div class="col-md-8">
<div class="row">
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<img src="img/jiangxuan_3.jpg" alt="">
<p>上海直飞三亚5天4晚自由行(春节预售+亲子/蜜月/休闲游首选+豪华酒店任选+接送机)</p>
<font color="red">¥ 699</font>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 3.页脚部分-->
<footer class="container-fluid">
<div class="row">
<img src="img/footer_service.png" class="img-responsive">
</div>
<div class="row company">
江苏传智播客教育科技股份有限公司 版权所有Copyright 2006-2018, All Rights Reserved 苏ICP备16007882
</div>
</footer>
</body>
</html>
黑马旅游网
XML
概念
Extensible Markup Language 可扩展标记语言
* 可扩展:标签都是自定义的。 <user> <student>
功能
* 功能
* 存储数据
1. 配置文件
2. 在网络中传输
* 存储数据
1. 配置文件
2. 在网络中传输
区别
* xml与html的区别
1. xml标签都是自定义的,html标签是预定义。
2. xml的语法严格,html语法松散
3. xml是存储数据的,html是展示数据
1. xml标签都是自定义的,html标签是预定义。
2. xml的语法严格,html语法松散
3. xml是存储数据的,html是展示数据
* w3c:万维网联盟
语法
基本语法
1. xml文档的后缀名 .xml
2. xml第一行必须定义为文档声明
3. xml文档中有且仅有一个根标签
4. 属性值必须使用引号(单双都可)引起来
5. 标签必须正确关闭
6. xml标签名称区分大小写
2. xml第一行必须定义为文档声明
3. xml文档中有且仅有一个根标签
4. 属性值必须使用引号(单双都可)引起来
5. 标签必须正确关闭
6. xml标签名称区分大小写
快速入门
<?xml version='1.0' ?>
<users>
<user id='1'>
<name>zhangsan</name>
<age>23</age>
<gender>male</gender>
<br/>
</user>
<user id='2'>
<name>lisi</name>
<age>24</age>
<gender>female</gender>
</user>
</users>
<users>
<user id='1'>
<name>zhangsan</name>
<age>23</age>
<gender>male</gender>
<br/>
</user>
<user id='2'>
<name>lisi</name>
<age>24</age>
<gender>female</gender>
</user>
</users>
组成部分
1. 文档声明
1. 格式:<?xml 属性列表 ?>
1. 格式:<?xml 属性列表 ?>
2. 属性列表:
* version:版本号,必须的属性
* encoding:编码方式。告知解析引擎当前文档使用的字符集,默认值:ISO-8859-1
* standalone:是否独立
* 取值:
* yes:不依赖其他文件
* no:依赖其他文件
* version:版本号,必须的属性
* encoding:编码方式。告知解析引擎当前文档使用的字符集,默认值:ISO-8859-1
* standalone:是否独立
* 取值:
* yes:不依赖其他文件
* no:依赖其他文件
2. 指令(了解):结合css的
* <?xml-stylesheet type="text/css" href="a.css" ?>
* <?xml-stylesheet type="text/css" href="a.css" ?>
3. 标签:标签名称自定义的
* 规则:
* 名称可以包含字母、数字以及其他的字符
* 名称不能以数字或者标点符号开始
* 名称不能以字母 xml(或者 XML、Xml 等等)开始
* 名称不能包含空格
* 规则:
* 名称可以包含字母、数字以及其他的字符
* 名称不能以数字或者标点符号开始
* 名称不能以字母 xml(或者 XML、Xml 等等)开始
* 名称不能包含空格
4. 属性:
id属性值唯一
id属性值唯一
5. 文本:
* CDATA区:在该区域中的数据会被原样展示
* 格式: <![CDATA[ 数据 ]]>
* CDATA区:在该区域中的数据会被原样展示
* 格式: <![CDATA[ 数据 ]]>
约束
* 作为框架的使用者(程序员):
1. 能够在xml中引入约束文档
2. 能够简单的读懂约束文档
1. 能够在xml中引入约束文档
2. 能够简单的读懂约束文档
* 分类:
1. DTD:一种简单的约束技术
2. Schema:一种复杂的约束技术
1. DTD:一种简单的约束技术
2. Schema:一种复杂的约束技术
dtd
原理
* DTD:
* 引入dtd文档到xml文档中
* 内部dtd:将约束规则定义在xml文档中
* 外部dtd:将约束的规则定义在外部的dtd文件中
* 本地:<!DOCTYPE 根标签名 SYSTEM "dtd文件的位置">
* 网络:<!DOCTYPE 根标签名 PUBLIC "dtd文件名字" "dtd文件的位置URL">
* 引入dtd文档到xml文档中
* 内部dtd:将约束规则定义在xml文档中
* 外部dtd:将约束的规则定义在外部的dtd文件中
* 本地:<!DOCTYPE 根标签名 SYSTEM "dtd文件的位置">
* 网络:<!DOCTYPE 根标签名 PUBLIC "dtd文件名字" "dtd文件的位置URL">
Schema
* Schema:
* 引入:
1.填写xml文档的根元素
2.引入xsi前缀. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3.引入xsd文件命名空间. xsi:schemaLocation="http://www.itcast.cn/xml student.xsd"
4.为每一个xsd约束声明一个前缀,作为标识 xmlns="http://www.itcast.cn/xml"
<students xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.itcast.cn/xml"
xsi:schemaLocation="http://www.itcast.cn/xml student.xsd">
* 引入:
1.填写xml文档的根元素
2.引入xsi前缀. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3.引入xsd文件命名空间. xsi:schemaLocation="http://www.itcast.cn/xml student.xsd"
4.为每一个xsd约束声明一个前缀,作为标识 xmlns="http://www.itcast.cn/xml"
<students xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.itcast.cn/xml"
xsi:schemaLocation="http://www.itcast.cn/xml student.xsd">
解析
操作xml文档
1. 解析(读取):将文档中的数据读取到内存中
2. 写入:将内存中的数据保存到xml文档中。持久化的存储
解析xml方式
1. DOM:将标记语言文档一次性加载进内存,在内存中形成一颗dom树
* 优点:操作方便,可以对文档进行CRUD的所有操作
* 缺点:占内存
* 优点:操作方便,可以对文档进行CRUD的所有操作
* 缺点:占内存
2. SAX:逐行读取,基于事件驱动的。
* 优点:不占内存。
* 缺点:只能读取,不能增删改
* 优点:不占内存。
* 缺点:只能读取,不能增删改
xml的常见解析器
1. JAXP:sun公司提供的解析器,支持dom和sax两种思想
2. DOM4J:一款非常优秀的解析器
3. Jsoup:jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。
它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
4. PULL:Android操作系统内置的解析器,sax方式的。
Jsoup
jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
* 快速入门:
* 步骤:
1. 导入jar包
2. 获取Document对象
3. 获取对应的标签Element对象
4. 获取数据
* 快速入门:
* 步骤:
1. 导入jar包
2. 获取Document对象
3. 获取对应的标签Element对象
4. 获取数据
* 代码:
//2.1获取student.xml的path
String path = JsoupDemo1.class.getClassLoader().getResource("student.xml").getPath();
//2.2解析xml文档,加载文档进内存,获取dom树--->Document
Document document = Jsoup.parse(new File(path), "utf-8");
//3.获取元素对象 Element
Elements elements = document.getElementsByTag("name");
System.out.println(elements.size());
//3.1获取第一个name的Element对象
Element element = elements.get(0);
//3.2获取数据
String name = element.text();
System.out.println(name);
//2.1获取student.xml的path
String path = JsoupDemo1.class.getClassLoader().getResource("student.xml").getPath();
//2.2解析xml文档,加载文档进内存,获取dom树--->Document
Document document = Jsoup.parse(new File(path), "utf-8");
//3.获取元素对象 Element
Elements elements = document.getElementsByTag("name");
System.out.println(elements.size());
//3.1获取第一个name的Element对象
Element element = elements.get(0);
//3.2获取数据
String name = element.text();
System.out.println(name);
对象的使用
1. Jsoup:工具类,可以解析html或xml文档,返回Document
* parse:解析html或xml文档,返回Document
* parse(File in, String charsetName):解析xml或html文件的。
* parse(String html):解析xml或html字符串
* parse(URL url, int timeoutMillis):通过网络路径获取指定的html或xml的文档对象
* parse:解析html或xml文档,返回Document
* parse(File in, String charsetName):解析xml或html文件的。
* parse(String html):解析xml或html字符串
* parse(URL url, int timeoutMillis):通过网络路径获取指定的html或xml的文档对象
2. Document:文档对象。代表内存中的dom树
* 获取Element对象
* getElementById(String id):根据id属性值获取唯一的element对象
* getElementsByTag(String tagName):根据标签名称获取元素对象集合
* getElementsByAttribute(String key):根据属性名称获取元素对象集合
* getElementsByAttributeValue(String key, String value):根据对应的属性名和属性值获取元素对象集合
* 获取Element对象
* getElementById(String id):根据id属性值获取唯一的element对象
* getElementsByTag(String tagName):根据标签名称获取元素对象集合
* getElementsByAttribute(String key):根据属性名称获取元素对象集合
* getElementsByAttributeValue(String key, String value):根据对应的属性名和属性值获取元素对象集合
3. Elements:元素Element对象的集合。可以当做 ArrayList<Element>来使用
4. Element:元素对象
1. 获取子元素对象
* getElementById(String id):根据id属性值获取唯一的element对象
* getElementsByTag(String tagName):根据标签名称获取元素对象集合
* getElementsByAttribute(String key):根据属性名称获取元素对象集合
* getElementsByAttributeValue(String key, String value):根据对应的属性名和属性值获取元素对象集
* getElementById(String id):根据id属性值获取唯一的element对象
* getElementsByTag(String tagName):根据标签名称获取元素对象集合
* getElementsByAttribute(String key):根据属性名称获取元素对象集合
* getElementsByAttributeValue(String key, String value):根据对应的属性名和属性值获取元素对象集
2. 获取属性值
* String attr(String key):根据属性名称获取属性值
* String attr(String key):根据属性名称获取属性值
3. 获取文本内容
* String text():获取文本内容
* String html():获取标签体的所有内容(包括字标签的字符串内容)
* String text():获取文本内容
* String html():获取标签体的所有内容(包括字标签的字符串内容)
5. Node:节点对象
* 是Document和Element的父类
* 是Document和Element的父类
快捷查询方式
1. selector:选择器
* 使用的方法:Elements select(String cssQuery)
* 语法:参考Selector类中定义的语法
* 使用的方法:Elements select(String cssQuery)
* 语法:参考Selector类中定义的语法
2. XPath:XPath即为XML路径语言,它是一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言
* 使用Jsoup的Xpath需要额外导入jar包。
* 查询w3cshool参考手册,使用xpath的语法完成查询
* 代码:
//1.获取student.xml的path
String path = JsoupDemo6.class.getClassLoader().getResource("student.xml").getPath();
//2.获取Document对象
Document document = Jsoup.parse(new File(path), "utf-8");
//3.根据document对象,创建JXDocument对象
JXDocument jxDocument = new JXDocument(document);
//4.结合xpath语法查询
//4.1查询所有student标签
List<JXNode> jxNodes = jxDocument.selN("//student");
for (JXNode jxNode : jxNodes) {
System.out.println(jxNode);
}
System.out.println("--------------------");
//4.2查询所有student标签下的name标签
List<JXNode> jxNodes2 = jxDocument.selN("//student/name");
for (JXNode jxNode : jxNodes2) {
System.out.println(jxNode);
}
System.out.println("--------------------");
//4.3查询student标签下带有id属性的name标签
List<JXNode> jxNodes3 = jxDocument.selN("//student/name[@id]");
for (JXNode jxNode : jxNodes3) {
System.out.println(jxNode);
}
System.out.println("--------------------");
//4.4查询student标签下带有id属性的name标签 并且id属性值为itcast
List<JXNode> jxNodes4 = jxDocument.selN("//student/name[@id='itcast']");
for (JXNode jxNode : jxNodes4) {
System.out.println(jxNode);
}
* 使用Jsoup的Xpath需要额外导入jar包。
* 查询w3cshool参考手册,使用xpath的语法完成查询
* 代码:
//1.获取student.xml的path
String path = JsoupDemo6.class.getClassLoader().getResource("student.xml").getPath();
//2.获取Document对象
Document document = Jsoup.parse(new File(path), "utf-8");
//3.根据document对象,创建JXDocument对象
JXDocument jxDocument = new JXDocument(document);
//4.结合xpath语法查询
//4.1查询所有student标签
List<JXNode> jxNodes = jxDocument.selN("//student");
for (JXNode jxNode : jxNodes) {
System.out.println(jxNode);
}
System.out.println("--------------------");
//4.2查询所有student标签下的name标签
List<JXNode> jxNodes2 = jxDocument.selN("//student/name");
for (JXNode jxNode : jxNodes2) {
System.out.println(jxNode);
}
System.out.println("--------------------");
//4.3查询student标签下带有id属性的name标签
List<JXNode> jxNodes3 = jxDocument.selN("//student/name[@id]");
for (JXNode jxNode : jxNodes3) {
System.out.println(jxNode);
}
System.out.println("--------------------");
//4.4查询student标签下带有id属性的name标签 并且id属性值为itcast
List<JXNode> jxNodes4 = jxDocument.selN("//student/name[@id='itcast']");
for (JXNode jxNode : jxNodes4) {
System.out.println(jxNode);
}
Tomcat&servlet
web相关概念
软件架构
1. C/S:客户端/服务器端
2. B/S:浏览器/服务器端
资源分类
1. 静态资源:所有用户访问后,得到的结果都是一样的,称为静态资源.静态资源可以直接被浏览器解析
* 如: html,css,JavaScript
* 如: html,css,JavaScript
2. 动态资源:每个用户访问相同资源后,得到的结果可能不一样。称为动态资源。动态资源被访问后,需要先转换为静态资源,在返回给浏览器
* 如:servlet/jsp,php,asp....
* 如:servlet/jsp,php,asp....
网络传输的三要素
1. IP:电子设备(计算机)在网络中的唯一标识。
2. 端口:应用程序在计算机中的唯一标识。 0~65536
3. 传输协议:规定了数据传输的规则
1. 基础协议:
1. tcp:安全协议,三次握手。 速度稍慢
2. udp:不安全协议。 速度快
1. 基础协议:
1. tcp:安全协议,三次握手。 速度稍慢
2. udp:不安全协议。 速度快
web服务器软件
* 服务器:安装了服务器软件的计算机
* 服务器软件:接收用户的请求,处理请求,做出响应
* web服务器软件:接收用户的请求,处理请求,做出响应。
* 在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目
* web容器
* 服务器软件:接收用户的请求,处理请求,做出响应
* web服务器软件:接收用户的请求,处理请求,做出响应。
* 在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目
* web容器
常见Java服务器相关的web服务器软件
* webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
* webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
* JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
* Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范servlet/jsp。开源的,免费的。
* webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
* JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
* Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范servlet/jsp。开源的,免费的。
Tomcat
概述
web服务器软件
下载
http://tomcat.apache.org/
安装
安装:解压压缩包即可。
* 注意:安装目录建议不要有中文和空格
* 注意:安装目录建议不要有中文和空格
卸载
删除目录就行了
启动
* bin/startup.bat ,双击运行该文件即可
* 访问:浏览器输入:http://localhost:8080 回车访问自己
http://别人的ip:8080 访问别人
* 可能遇到的问题:
1. 黑窗口一闪而过:
* 原因: 没有正确配置JAVA_HOME环境变量
* 解决方案:正确配置JAVA_HOME环境变量
2. 启动报错:
1. 暴力:找到占用的端口号,并且找到对应的进程,杀死该进程
* netstat -ano
2. 温柔:修改自身的端口号
* conf/server.xml
* <Connector port="8888" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8445" />
* 一般会将tomcat的默认端口号修改为80。80端口号是http协议的默认端口号。
* 好处:在访问时,就不用输入端口号
* 访问:浏览器输入:http://localhost:8080 回车访问自己
http://别人的ip:8080 访问别人
* 可能遇到的问题:
1. 黑窗口一闪而过:
* 原因: 没有正确配置JAVA_HOME环境变量
* 解决方案:正确配置JAVA_HOME环境变量
2. 启动报错:
1. 暴力:找到占用的端口号,并且找到对应的进程,杀死该进程
* netstat -ano
2. 温柔:修改自身的端口号
* conf/server.xml
* <Connector port="8888" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8445" />
* 一般会将tomcat的默认端口号修改为80。80端口号是http协议的默认端口号。
* 好处:在访问时,就不用输入端口号
关闭
1. 正常关闭:
* bin/shutdown.bat
* ctrl+c
2. 强制关闭:
* 点击启动窗口的×
* bin/shutdown.bat
* ctrl+c
2. 强制关闭:
* 点击启动窗口的×
配置
* 部署项目的方式:
1. 直接将项目放到webapps目录下即可。
* /hello:项目的访问路径-->虚拟目录
* 简化部署:将项目打成一个war包,再将war包放置到webapps目录下。
* war包会自动解压缩
2. 配置conf/server.xml文件
在<Host>标签体中配置
<Context docBase="D:\hello" path="/hehe" />
* docBase:项目存放的路径
* path:虚拟目录
3. 在conf\Catalina\localhost创建任意名称的xml文件。在文件中编写
<Context docBase="D:\hello" />
* 虚拟目录:xml文件的名称
* 静态项目和动态项目:
* 目录结构
* java动态项目的目录结构:
-- 项目的根目录
-- WEB-INF目录:
-- web.xml:web项目的核心配置文件
-- classes目录:放置字节码文件的目录
-- lib目录:放置依赖的jar包
* 将Tomcat集成到IDEA中,并且创建JavaEE的项目,部署项目。
1. 直接将项目放到webapps目录下即可。
* /hello:项目的访问路径-->虚拟目录
* 简化部署:将项目打成一个war包,再将war包放置到webapps目录下。
* war包会自动解压缩
2. 配置conf/server.xml文件
在<Host>标签体中配置
<Context docBase="D:\hello" path="/hehe" />
* docBase:项目存放的路径
* path:虚拟目录
3. 在conf\Catalina\localhost创建任意名称的xml文件。在文件中编写
<Context docBase="D:\hello" />
* 虚拟目录:xml文件的名称
* 静态项目和动态项目:
* 目录结构
* java动态项目的目录结构:
-- 项目的根目录
-- WEB-INF目录:
-- web.xml:web项目的核心配置文件
-- classes目录:放置字节码文件的目录
-- lib目录:放置依赖的jar包
* 将Tomcat集成到IDEA中,并且创建JavaEE的项目,部署项目。
servlet
概念:运行在服务器端的小程序
* Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。
* 将来我们自定义一个类,实现Servlet接口,复写方法。
* 将来我们自定义一个类,实现Servlet接口,复写方法。
快速入门
1. 创建JavaEE项目
2. 定义一个类,实现Servlet接口
* public class ServletDemo1 implements Servlet
* public class ServletDemo1 implements Servlet
3. 实现接口中的抽象方法
4. 配置Servlet
在web.xml中配置:
<!--配置Servlet -->
<servlet>
<servlet-name>demo1</servlet-name>
<servlet-class>cn.itcast.web.servlet.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
在web.xml中配置:
<!--配置Servlet -->
<servlet>
<servlet-name>demo1</servlet-name>
<servlet-class>cn.itcast.web.servlet.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
执行原理
* 执行原理:
1. 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容。
3. 如果有,则在找到对应的<servlet-class>全类名
4. tomcat会将字节码文件加载进内存,并且创建其对象
5. 调用其方法
1. 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容。
3. 如果有,则在找到对应的<servlet-class>全类名
4. tomcat会将字节码文件加载进内存,并且创建其对象
5. 调用其方法
servlet中的生命周期方法
1. 被创建:执行init方法,只执行一次
* Servlet什么时候被创建?
* 默认情况下,第一次被访问时,Servlet被创建
* 可以配置执行Servlet的创建时机。
* 在<servlet>标签下配置
1. 第一次被访问时,创建
* <load-on-startup>的值为负数
2. 在服务器启动时,创建
* <load-on-startup>的值为0或正整数
* Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的
* 多个用户同时访问时,可能存在线程安全问题。
* 解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对修改值
* Servlet什么时候被创建?
* 默认情况下,第一次被访问时,Servlet被创建
* 可以配置执行Servlet的创建时机。
* 在<servlet>标签下配置
1. 第一次被访问时,创建
* <load-on-startup>的值为负数
2. 在服务器启动时,创建
* <load-on-startup>的值为0或正整数
* Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的
* 多个用户同时访问时,可能存在线程安全问题。
* 解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对修改值
2. 提供服务:执行service方法,执行多次
* 每次访问Servlet时,Service方法都会被调用一次。
* 每次访问Servlet时,Service方法都会被调用一次。
3. 被销毁:执行destroy方法,只执行一次
* Servlet被销毁时执行。服务器关闭时,Servlet被销毁
* 只有服务器正常关闭时,才会执行destroy方法。
* destroy方法在Servlet被销毁之前执行,一般用于释放资源
* Servlet被销毁时执行。服务器关闭时,Servlet被销毁
* 只有服务器正常关闭时,才会执行destroy方法。
* destroy方法在Servlet被销毁之前执行,一般用于释放资源
Servlet3.0
好处
支持注解配置。可以不需要web.xml了
步骤
1. 创建JavaEE项目,选择Servlet的版本3.0以上,可以不创建web.xml
2. 定义一个类,实现Servlet接口
3. 复写方法
4. 在类上使用@WebServlet注解,进行配置
* @WebServlet("资源路径")
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";//相当于<Servlet-name>
String[] value() default {};//代表urlPatterns()属性配置
String[] urlPatterns() default {};//相当于<url-pattern>
int loadOnStartup() default -1;//相当于<load-on-startup>
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
2. 定义一个类,实现Servlet接口
3. 复写方法
4. 在类上使用@WebServlet注解,进行配置
* @WebServlet("资源路径")
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";//相当于<Servlet-name>
String[] value() default {};//代表urlPatterns()属性配置
String[] urlPatterns() default {};//相当于<url-pattern>
int loadOnStartup() default -1;//相当于<load-on-startup>
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
idea与Tomcat的相关配置
1. IDEA会为每一个tomcat部署的项目单独建立一份配置文件
* 查看控制台的log:Using CATALINA_BASE: "C:\Users\fqy\.IntelliJIdea2018.1\system\tomcat\_itcast"
* 查看控制台的log:Using CATALINA_BASE: "C:\Users\fqy\.IntelliJIdea2018.1\system\tomcat\_itcast"
2. 工作空间项目 和 tomcat部署的web项目
* tomcat真正访问的是“tomcat部署的web项目”,"tomcat部署的web项目"对应着"工作空间项目" 的web目录下的所有资源
* WEB-INF目录下的资源不能被浏览器直接访问。
* tomcat真正访问的是“tomcat部署的web项目”,"tomcat部署的web项目"对应着"工作空间项目" 的web目录下的所有资源
* WEB-INF目录下的资源不能被浏览器直接访问。
3. 断点调试:使用"小虫子"启动 dubug 启动
servlet相关配置
1. urlpartten:Servlet访问路径
1. 一个Servlet可以定义多个访问路径 : @WebServlet({"/d4","/dd4","/ddd4"})
2. 路径定义规则:
1. /xxx:路径匹配
2. /xxx/xxx:多层路径,目录结构
3. *.do:扩展名匹配
1. 一个Servlet可以定义多个访问路径 : @WebServlet({"/d4","/dd4","/ddd4"})
2. 路径定义规则:
1. /xxx:路径匹配
2. /xxx/xxx:多层路径,目录结构
3. *.do:扩展名匹配
HTTP&request
HTTP
概念
Hyper Text Transfer Protocol 超文本传输协议
传输协议
* 传输协议:定义了,客户端和服务器端通信时,发送数据的格式
* 特点:
1. 基于TCP/IP的高级协议
2. 默认端口号:80
3. 基于请求/响应模型的:一次请求对应一次响应
4. 无状态的:每次请求之间相互独立,不能交互数
* 特点:
1. 基于TCP/IP的高级协议
2. 默认端口号:80
3. 基于请求/响应模型的:一次请求对应一次响应
4. 无状态的:每次请求之间相互独立,不能交互数
历史版本
* 历史版本:
* 1.0:每一次请求响应都会建立新的连接
* 1.1:复用连接
* 1.0:每一次请求响应都会建立新的连接
* 1.1:复用连接
请求消息数据格式
1. 请求行
请求方式 请求url 请求协议/版本
GET /login.html HTTP/1.1
* 请求方式:
* HTTP协议有7中请求方式,常用的有2种
* GET:
1. 请求参数在请求行中,在url后。
2. 请求的url长度有限制的
3. 不太安全
* POST:
1. 请求参数在请求体中
2. 请求的url长度没有限制的
3. 相对安全
1. 请求行
请求方式 请求url 请求协议/版本
GET /login.html HTTP/1.1
* 请求方式:
* HTTP协议有7中请求方式,常用的有2种
* GET:
1. 请求参数在请求行中,在url后。
2. 请求的url长度有限制的
3. 不太安全
* POST:
1. 请求参数在请求体中
2. 请求的url长度没有限制的
3. 相对安全
2. 请求头:客户端浏览器告诉服务器一些信息
请求头名称: 请求头值
* 常见的请求头:
1. User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
* 可以在服务器端获取该头的信息,解决浏览器的兼容性问题
2. Referer:http://localhost/login.html
* 告诉服务器,我(当前请求)从哪里来?
* 作用:
1. 防盗链:
2. 统计工作:
请求头名称: 请求头值
* 常见的请求头:
1. User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
* 可以在服务器端获取该头的信息,解决浏览器的兼容性问题
2. Referer:http://localhost/login.html
* 告诉服务器,我(当前请求)从哪里来?
* 作用:
1. 防盗链:
2. 统计工作:
3. 请求空行
空行,就是用于分割POST请求的请求头,和请求体的。
空行,就是用于分割POST请求的请求头,和请求体的。
4. 请求体(正文):
* 封装POST请求消息的请求参数的
* 字符串格式:
POST /login.html HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
username=zhangsan
* 响应消息数据格式
* 封装POST请求消息的请求参数的
* 字符串格式:
POST /login.html HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
username=zhangsan
* 响应消息数据格式
Request
request对象和response对象的原理
1. request和response对象是由服务器创建的。我们来使用它们
2. request对象是来获取请求消息,response对象是来设置响应消息
2. request对象是来获取请求消息,response对象是来设置响应消息
request对象的继承体系架构
ServletRequest -- 接口
| 继承
HttpServletRequest -- 接口
| 实现
org.apache.catalina.connector.RequestFacade 类(tomcat)
| 继承
HttpServletRequest -- 接口
| 实现
org.apache.catalina.connector.RequestFacade 类(tomcat)
request功能
1. 获取请求消息数据
1. 获取请求行数据
* GET /day14/demo1?name=zhangsan HTTP/1.1
* 方法:
1. 获取请求方式 :GET
* String getMethod()
2. (*)获取虚拟目录:/day14
* String getContextPath()
3. 获取Servlet路径: /demo1
* String getServletPath()
4. 获取get方式请求参数:name=zhangsan
* String getQueryString()
5. (*)获取请求URI:/day14/demo1
* String getRequestURI(): /day14/demo1
* StringBuffer getRequestURL() :http://localhost/day14/demo1
* URL:统一资源定位符 : http://localhost/day14/demo1 中华人民共和国
* URI:统一资源标识符 : /day14/demo1 共和国
6. 获取协议及版本:HTTP/1.1
* String getProtocol()
7. 获取客户机的IP地址:
* String getRemoteAddr()
1. 获取请求行数据
* GET /day14/demo1?name=zhangsan HTTP/1.1
* 方法:
1. 获取请求方式 :GET
* String getMethod()
2. (*)获取虚拟目录:/day14
* String getContextPath()
3. 获取Servlet路径: /demo1
* String getServletPath()
4. 获取get方式请求参数:name=zhangsan
* String getQueryString()
5. (*)获取请求URI:/day14/demo1
* String getRequestURI(): /day14/demo1
* StringBuffer getRequestURL() :http://localhost/day14/demo1
* URL:统一资源定位符 : http://localhost/day14/demo1 中华人民共和国
* URI:统一资源标识符 : /day14/demo1 共和国
6. 获取协议及版本:HTTP/1.1
* String getProtocol()
7. 获取客户机的IP地址:
* String getRemoteAddr()
2. 获取请求头数据
* 方法:
* (*)String getHeader(String name):通过请求头的名称获取请求头的值
* Enumeration<String> getHeaderNames():获取所有的请求头名称
* 方法:
* (*)String getHeader(String name):通过请求头的名称获取请求头的值
* Enumeration<String> getHeaderNames():获取所有的请求头名称
3. 获取请求体数据:
* 请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
* 步骤:
1. 获取流对象
* BufferedReader getReader():获取字符输入流,只能操作字符数据
* ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据
* 在文件上传知识点后讲解
2. 再从流对象中拿数据
* 请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
* 步骤:
1. 获取流对象
* BufferedReader getReader():获取字符输入流,只能操作字符数据
* ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据
* 在文件上传知识点后讲解
2. 再从流对象中拿数据
其它功能
1. 获取请求参数通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数
1. String getParameter(String name):根据参数名称获取参数值 username=zs&password=123
2. String[] getParameterValues(String name):根据参数名称获取参数值的数组 hobby=xx&hobby=game
3. Enumeration<String> getParameterNames():获取所有请求的参数名称
4. Map<String,String[]> getParameterMap():获取所有参数的map集合
* 中文乱码问题:
* get方式:tomcat 8 已经将get方式乱码问题解决了
* post方式:会乱码
* 解决:在获取参数前,设置request的编码request.setCharacterEncoding("utf-8");
1. String getParameter(String name):根据参数名称获取参数值 username=zs&password=123
2. String[] getParameterValues(String name):根据参数名称获取参数值的数组 hobby=xx&hobby=game
3. Enumeration<String> getParameterNames():获取所有请求的参数名称
4. Map<String,String[]> getParameterMap():获取所有参数的map集合
* 中文乱码问题:
* get方式:tomcat 8 已经将get方式乱码问题解决了
* post方式:会乱码
* 解决:在获取参数前,设置request的编码request.setCharacterEncoding("utf-8");
2. 请求转发:一种在服务器内部的资源跳转方式
1. 步骤:
1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
2. 使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response)
2. 特点:
1. 浏览器地址栏路径不发生变化
2. 只能转发到当前服务器内部资源中。
3. 转发是一次请求
1. 步骤:
1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
2. 使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response)
2. 特点:
1. 浏览器地址栏路径不发生变化
2. 只能转发到当前服务器内部资源中。
3. 转发是一次请求
3. 共享数据:
* 域对象:一个有作用范围的对象,可以在范围内共享数据
* request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
* 方法:
1. void setAttribute(String name,Object obj):存储数据
2. Object getAttitude(String name):通过键获取值
3. void removeAttribute(String name):通过键移除键值对
4. 获取ServletContext:
* ServletContext getServletContext()
* 域对象:一个有作用范围的对象,可以在范围内共享数据
* request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
* 方法:
1. void setAttribute(String name,Object obj):存储数据
2. Object getAttitude(String name):通过键获取值
3. void removeAttribute(String name):通过键移除键值对
4. 获取ServletContext:
* ServletContext getServletContext()
案例
* 用户登录案例需求:
1.编写login.html登录页面
username & password 两个输入框
2.使用Druid数据库连接池技术,操作mysql,day14数据库中user表
3.使用JdbcTemplate技术封装JDBC
4.登录成功跳转到SuccessServlet展示:登录成功!用户名,欢迎您
5.登录失败跳转到FailServlet展示:登录失败,用户名或密码错误
* 分析
* 开发步骤
1. 创建项目,导入html页面,配置文件,jar包
2. 创建数据库环境
CREATE DATABASE day14;
USE day14;
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) UNIQUE NOT NULL,
PASSWORD VARCHAR(32) NOT NULL
);
3. 创建包cn.itcast.domain,创建类User
package cn.itcast.domain;
/**
* 用户的实体类
*/
public class User {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
4. 创建包cn.itcast.util,编写工具类JDBCUtils
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* JDBC工具类 使用Durid连接池
*/
public class JDBCUtils {
private static DataSource ds ;
static {
try {
//1.加载配置文件
Properties pro = new Properties();
//使用ClassLoader加载配置文件,获取字节输入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//2.初始化连接池对象
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接池对象
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 获取连接Connection对象
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
5. 创建包cn.itcast.dao,创建类UserDao,提供login方法
import cn.itcast.domain.User;
import cn.itcast.util.JDBCUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 操作数据库中User表的类
*/
public class UserDao {
//声明JDBCTemplate对象共用
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/**
* 登录方法
* @param loginUser 只有用户名和密码
* @return user包含用户全部数据,没有查询到,返回null
*/
public User login(User loginUser){
try {
//1.编写sql
String sql = "select * from user where username = ? and password = ?";
//2.调用query方法
User user = template.queryForObject(sql,
new BeanPropertyRowMapper<User>(User.class),
loginUser.getUsername(), loginUser.getPassword());
return user;
} catch (DataAccessException e) {
e.printStackTrace();//记录日志
return null;
}
}
}
6. 编写cn.itcast.web.servlet.LoginServlet类
package cn.itcast.web.servlet;
import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;
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.IOException;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置编码
req.setCharacterEncoding("utf-8");
//2.获取请求参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//3.封装user对象
User loginUser = new User();
loginUser.setUsername(username);
loginUser.setPassword(password);
//4.调用UserDao的login方法
UserDao dao = new UserDao();
User user = dao.login(loginUser);
//5.判断user
if(user == null){
//登录失败
req.getRequestDispatcher("/failServlet").forward(req,resp);
}else{
//登录成功
//存储数据
req.setAttribute("user",user);
//转发
req.getRequestDispatcher("/successServlet").forward(req,resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
7. 编写FailServlet和SuccessServlet类
@WebServlet("/successServlet")
public class SuccessServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取request域中共享的user对象
User user = (User) request.getAttribute("user");
if(user != null){
//给页面写一句话
//设置编码
response.setContentType("text/html;charset=utf-8");
//输出
response.getWriter().write("登录成功!"+user.getUsername()+",欢迎您");
}
}
@WebServlet("/failServlet")
public class FailServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//给页面写一句话
//设置编码
response.setContentType("text/html;charset=utf-8");
//输出
response.getWriter().write("登录失败,用户名或密码错误");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
8. login.html中form表单的action路径的写法
虚拟目录+Servlet的资源路径
1.编写login.html登录页面
username & password 两个输入框
2.使用Druid数据库连接池技术,操作mysql,day14数据库中user表
3.使用JdbcTemplate技术封装JDBC
4.登录成功跳转到SuccessServlet展示:登录成功!用户名,欢迎您
5.登录失败跳转到FailServlet展示:登录失败,用户名或密码错误
* 分析
* 开发步骤
1. 创建项目,导入html页面,配置文件,jar包
2. 创建数据库环境
CREATE DATABASE day14;
USE day14;
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) UNIQUE NOT NULL,
PASSWORD VARCHAR(32) NOT NULL
);
3. 创建包cn.itcast.domain,创建类User
package cn.itcast.domain;
/**
* 用户的实体类
*/
public class User {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
4. 创建包cn.itcast.util,编写工具类JDBCUtils
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* JDBC工具类 使用Durid连接池
*/
public class JDBCUtils {
private static DataSource ds ;
static {
try {
//1.加载配置文件
Properties pro = new Properties();
//使用ClassLoader加载配置文件,获取字节输入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//2.初始化连接池对象
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接池对象
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 获取连接Connection对象
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
5. 创建包cn.itcast.dao,创建类UserDao,提供login方法
import cn.itcast.domain.User;
import cn.itcast.util.JDBCUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 操作数据库中User表的类
*/
public class UserDao {
//声明JDBCTemplate对象共用
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/**
* 登录方法
* @param loginUser 只有用户名和密码
* @return user包含用户全部数据,没有查询到,返回null
*/
public User login(User loginUser){
try {
//1.编写sql
String sql = "select * from user where username = ? and password = ?";
//2.调用query方法
User user = template.queryForObject(sql,
new BeanPropertyRowMapper<User>(User.class),
loginUser.getUsername(), loginUser.getPassword());
return user;
} catch (DataAccessException e) {
e.printStackTrace();//记录日志
return null;
}
}
}
6. 编写cn.itcast.web.servlet.LoginServlet类
package cn.itcast.web.servlet;
import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;
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.IOException;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置编码
req.setCharacterEncoding("utf-8");
//2.获取请求参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//3.封装user对象
User loginUser = new User();
loginUser.setUsername(username);
loginUser.setPassword(password);
//4.调用UserDao的login方法
UserDao dao = new UserDao();
User user = dao.login(loginUser);
//5.判断user
if(user == null){
//登录失败
req.getRequestDispatcher("/failServlet").forward(req,resp);
}else{
//登录成功
//存储数据
req.setAttribute("user",user);
//转发
req.getRequestDispatcher("/successServlet").forward(req,resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
7. 编写FailServlet和SuccessServlet类
@WebServlet("/successServlet")
public class SuccessServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取request域中共享的user对象
User user = (User) request.getAttribute("user");
if(user != null){
//给页面写一句话
//设置编码
response.setContentType("text/html;charset=utf-8");
//输出
response.getWriter().write("登录成功!"+user.getUsername()+",欢迎您");
}
}
@WebServlet("/failServlet")
public class FailServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//给页面写一句话
//设置编码
response.setContentType("text/html;charset=utf-8");
//输出
response.getWriter().write("登录失败,用户名或密码错误");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
8. login.html中form表单的action路径的写法
虚拟目录+Servlet的资源路径
BeanUtils
9. BeanUtils工具类,简化数据封装
* 用于封装JavaBean的
1. JavaBean:标准的Java类
1. 要求:
1. 类必须被public修饰
2. 必须提供空参的构造器
3. 成员变量必须使用private修饰
4. 提供公共setter和getter方法
2. 功能:封装数据
2. 概念:
成员变量:
属性:setter和getter方法截取后的产物
例如:getUsername() --> Username--> username
3. 方法:
1. setProperty()
2. getProperty()
3. populate(Object obj , Map map):将map集合的键值对信息,封装到对应的JavaBean对象中
* 用于封装JavaBean的
1. JavaBean:标准的Java类
1. 要求:
1. 类必须被public修饰
2. 必须提供空参的构造器
3. 成员变量必须使用private修饰
4. 提供公共setter和getter方法
2. 功能:封装数据
2. 概念:
成员变量:
属性:setter和getter方法截取后的产物
例如:getUsername() --> Username--> username
3. 方法:
1. setProperty()
2. getProperty()
3. populate(Object obj , Map map):将map集合的键值对信息,封装到对应的JavaBean对象中
response
响应消息
服务器端发送给客户端的数据
数据格式
1. 响应行
1. 组成:协议/版本 响应状态码 状态码描述
2. 响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态。
1. 状态码都是3位数字
2. 分类:
1. 1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx多状态码
2. 2xx:成功。代表:200
3. 3xx:重定向。代表:302(重定向),304(访问缓存)
4. 4xx:客户端错误。
* 代表:
* 404(请求路径没有对应的资源)
* 405:请求方式没有对应的doXxx方法
5. 5xx:服务器端错误。代表:500(服务器内部出现异常)
1. 组成:协议/版本 响应状态码 状态码描述
2. 响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态。
1. 状态码都是3位数字
2. 分类:
1. 1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx多状态码
2. 2xx:成功。代表:200
3. 3xx:重定向。代表:302(重定向),304(访问缓存)
4. 4xx:客户端错误。
* 代表:
* 404(请求路径没有对应的资源)
* 405:请求方式没有对应的doXxx方法
5. 5xx:服务器端错误。代表:500(服务器内部出现异常)
2. 响应头:
1. 格式:头名称: 值
2. 常见的响应头:
1. Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据
* 值:
* in-line:默认值,在当前页面内打开
* attachment;filename=xxx:以附件形式打开响应体。文件下载
3. 响应空行
4. 响应体:传输的数据
1. 格式:头名称: 值
2. 常见的响应头:
1. Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据
* 值:
* in-line:默认值,在当前页面内打开
* attachment;filename=xxx:以附件形式打开响应体。文件下载
3. 响应空行
4. 响应体:传输的数据
响应字符串格式
* 响应字符串格式
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 101
Date: Wed, 06 Jun 2018 07:08:42 GMT
<html>
<head>
<title>$Title$</title>
</head>
<body>
hello , response
</body>
</html>
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 101
Date: Wed, 06 Jun 2018 07:08:42 GMT
<html>
<head>
<title>$Title$</title>
</head>
<body>
hello , response
</body>
</html>
Response对象
* 功能:设置响应消息
1. 设置响应行
1. 格式:HTTP/1.1 200 ok
2. 设置状态码:setStatus(int sc)
2. 设置响应头:setHeader(String name, String value)
3. 设置响应体:
* 使用步骤:
1. 获取输出流
* 字符输出流:PrintWriter getWriter()
* 字节输出流:ServletOutputStream getOutputStream()
2. 使用输出流,将数据输出到客户端浏览器
1. 设置响应行
1. 格式:HTTP/1.1 200 ok
2. 设置状态码:setStatus(int sc)
2. 设置响应头:setHeader(String name, String value)
3. 设置响应体:
* 使用步骤:
1. 获取输出流
* 字符输出流:PrintWriter getWriter()
* 字节输出流:ServletOutputStream getOutputStream()
2. 使用输出流,将数据输出到客户端浏览器
案例
* 案例:
1. 完成重定向
* 重定向:资源跳转的方式
* 代码实现:
//1. 设置状态码为302
response.setStatus(302);
//2.设置响应头location
response.setHeader("location","/day15/responseDemo2");
//简单的重定向方法
response.sendRedirect("/day15/responseDemo2");
* 重定向的特点:redirect
1. 地址栏发生变化
2. 重定向可以访问其他站点(服务器)的资源
3. 重定向是两次请求。不能使用request对象来共享数据
* 转发的特点:forward
1. 转发地址栏路径不变
2. 转发只能访问当前服务器下的资源
3. 转发是一次请求,可以使用request对象来共享数据
* forward 和 redirect 区别
* 路径写法:
1. 路径分类
1. 相对路径:通过相对路径不可以确定唯一资源
* 如:./index.html
* 不以/开头,以.开头路径
* 规则:找到当前资源和目标资源之间的相对位置关系
* ./:当前目录
* ../:后退一级目录
2. 绝对路径:通过绝对路径可以确定唯一资源
* 如:http://localhost/day15/responseDemo2 /day15/responseDemo2
* 以/开头的路径
* 规则:判断定义的路径是给谁用的?判断请求将来从哪儿发出
* 给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
* 建议虚拟目录动态获取:request.getContextPath()
* <a> , <form> 重定向...
* 给服务器使用:不需要加虚拟目录
* 转发路径
1. 完成重定向
* 重定向:资源跳转的方式
* 代码实现:
//1. 设置状态码为302
response.setStatus(302);
//2.设置响应头location
response.setHeader("location","/day15/responseDemo2");
//简单的重定向方法
response.sendRedirect("/day15/responseDemo2");
* 重定向的特点:redirect
1. 地址栏发生变化
2. 重定向可以访问其他站点(服务器)的资源
3. 重定向是两次请求。不能使用request对象来共享数据
* 转发的特点:forward
1. 转发地址栏路径不变
2. 转发只能访问当前服务器下的资源
3. 转发是一次请求,可以使用request对象来共享数据
* forward 和 redirect 区别
* 路径写法:
1. 路径分类
1. 相对路径:通过相对路径不可以确定唯一资源
* 如:./index.html
* 不以/开头,以.开头路径
* 规则:找到当前资源和目标资源之间的相对位置关系
* ./:当前目录
* ../:后退一级目录
2. 绝对路径:通过绝对路径可以确定唯一资源
* 如:http://localhost/day15/responseDemo2 /day15/responseDemo2
* 以/开头的路径
* 规则:判断定义的路径是给谁用的?判断请求将来从哪儿发出
* 给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
* 建议虚拟目录动态获取:request.getContextPath()
* <a> , <form> 重定向...
* 给服务器使用:不需要加虚拟目录
* 转发路径
2. 服务器输出字符数据到浏览器
* 步骤:
1. 获取字符输出流
2. 输出数据
* 注意:
* 乱码问题:
1. PrintWriter pw = response.getWriter();获取的流的默认编码是ISO-8859-1
2. 设置该流的默认编码
3. 告诉浏览器响应体使用的编码
//简单的形式,设置编码,是在获取流之前设置
response.setContentType("text/html;charset=utf-8");
3. 服务器输出字节数据到浏览器
* 步骤:
1. 获取字节输出流
2. 输出数据
4. 验证码
1. 本质:图片
2. 目的:防止恶意表单注册
* 步骤:
1. 获取字符输出流
2. 输出数据
* 注意:
* 乱码问题:
1. PrintWriter pw = response.getWriter();获取的流的默认编码是ISO-8859-1
2. 设置该流的默认编码
3. 告诉浏览器响应体使用的编码
//简单的形式,设置编码,是在获取流之前设置
response.setContentType("text/html;charset=utf-8");
3. 服务器输出字节数据到浏览器
* 步骤:
1. 获取字节输出流
2. 输出数据
4. 验证码
1. 本质:图片
2. 目的:防止恶意表单注册
## ServletContext对象:
1. 概念:代表整个web应用,可以和程序的容器(服务器)来通信
2. 获取:
1. 通过request对象获取
request.getServletContext();
2. 通过HttpServlet获取
this.getServletContext();
3. 功能:
1. 获取MIME类型:
* MIME类型:在互联网通信过程中定义的一种文件数据类型
* 格式: 大类型/小类型 text/html image/jpeg
* 获取:String getMimeType(String file)
2. 域对象:共享数据
1. setAttribute(String name,Object value)
2. getAttribute(String name)
3. removeAttribute(String name)
* ServletContext对象范围:所有用户所有请求的数据
3. 获取文件的真实(服务器)路径
1. 方法:String getRealPath(String path)
String b = context.getRealPath("/b.txt");//web目录下资源访问
System.out.println(b);
String c = context.getRealPath("/WEB-INF/c.txt");//WEB-INF目录下的资源访问
System.out.println(c);
String a = context.getRealPath("/WEB-INF/classes/a.txt");//src目录下的资源访问
System.out.println(a);
1. 概念:代表整个web应用,可以和程序的容器(服务器)来通信
2. 获取:
1. 通过request对象获取
request.getServletContext();
2. 通过HttpServlet获取
this.getServletContext();
3. 功能:
1. 获取MIME类型:
* MIME类型:在互联网通信过程中定义的一种文件数据类型
* 格式: 大类型/小类型 text/html image/jpeg
* 获取:String getMimeType(String file)
2. 域对象:共享数据
1. setAttribute(String name,Object value)
2. getAttribute(String name)
3. removeAttribute(String name)
* ServletContext对象范围:所有用户所有请求的数据
3. 获取文件的真实(服务器)路径
1. 方法:String getRealPath(String path)
String b = context.getRealPath("/b.txt");//web目录下资源访问
System.out.println(b);
String c = context.getRealPath("/WEB-INF/c.txt");//WEB-INF目录下的资源访问
System.out.println(c);
String a = context.getRealPath("/WEB-INF/classes/a.txt");//src目录下的资源访问
System.out.println(a);
## 案例:
* 文件下载需求:
1. 页面显示超链接
2. 点击超链接后弹出下载提示框
3. 完成图片文件下载
* 分析:
1. 超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框。不满足需求
2. 任何资源都必须弹出下载提示框
3. 使用响应头设置资源的打开方式:
* content-disposition:attachment;filename=xxx
* 步骤:
1. 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename
2. 定义Servlet
1. 获取文件名称
2. 使用字节输入流加载文件进内存
3. 指定response的响应头: content-disposition:attachment;filename=xxx
4. 将数据写出到response输出流
* 问题:
* 中文文件问题
* 解决思路:
1. 获取客户端使用的浏览器版本信息
2. 根据不同的版本信息,设置filename的编码方式不同
* 文件下载需求:
1. 页面显示超链接
2. 点击超链接后弹出下载提示框
3. 完成图片文件下载
* 分析:
1. 超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框。不满足需求
2. 任何资源都必须弹出下载提示框
3. 使用响应头设置资源的打开方式:
* content-disposition:attachment;filename=xxx
* 步骤:
1. 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename
2. 定义Servlet
1. 获取文件名称
2. 使用字节输入流加载文件进内存
3. 指定response的响应头: content-disposition:attachment;filename=xxx
4. 将数据写出到response输出流
* 问题:
* 中文文件问题
* 解决思路:
1. 获取客户端使用的浏览器版本信息
2. 根据不同的版本信息,设置filename的编码方式不同
会话技术
概述
1. 会话:一次会话中包含多次请求和响应。
* 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止
2. 功能:在一次会话的范围内的多次请求间,共享数据
3. 方式:
1. 客户端会话技术:Cookie
2. 服务器端会话技术:Session
* 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止
2. 功能:在一次会话的范围内的多次请求间,共享数据
3. 方式:
1. 客户端会话技术:Cookie
2. 服务器端会话技术:Session
cookie
概念
客户端会话技术,将数据保存到客户端
快速入门
1. 创建Cookie对象,绑定数据
* new Cookie(String name, String value)
2. 发送Cookie对象
* response.addCookie(Cookie cookie)
3. 获取Cookie,拿到数据
* Cookie[] request.getCookies()
* new Cookie(String name, String value)
2. 发送Cookie对象
* response.addCookie(Cookie cookie)
3. 获取Cookie,拿到数据
* Cookie[] request.getCookies()
实现原理
基于响应头set-cookie和请求头cookie实现
cookie细节
1. 一次可不可以发送多个cookie?
* 可以
* 可以创建多个Cookie对象,使用response调用多次addCookie方法发送cookie即可。
* 可以
* 可以创建多个Cookie对象,使用response调用多次addCookie方法发送cookie即可。
2. cookie在浏览器中保存多长时间?
1. 默认情况下,当浏览器关闭后,Cookie数据被销毁
2. 持久化存储:
* setMaxAge(int seconds)
1. 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,时间到后,cookie文件自动失效
2. 负数:默认值
3. 零:删除cookie信息
1. 默认情况下,当浏览器关闭后,Cookie数据被销毁
2. 持久化存储:
* setMaxAge(int seconds)
1. 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,时间到后,cookie文件自动失效
2. 负数:默认值
3. 零:删除cookie信息
3. cookie能不能存中文?
* 在tomcat 8 之前 cookie中不能直接存储中文数据。
* 需要将中文数据转码---一般采用URL编码(%E3)
* 在tomcat 8 之后,cookie支持中文数据。特殊字符还是不支持,建议使用URL编码存储,URL解码解析
* 在tomcat 8 之前 cookie中不能直接存储中文数据。
* 需要将中文数据转码---一般采用URL编码(%E3)
* 在tomcat 8 之后,cookie支持中文数据。特殊字符还是不支持,建议使用URL编码存储,URL解码解析
4. cookie共享问题?
1. 假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?
* 默认情况下cookie不能共享
* setPath(String path):设置cookie的获取范围。默认情况下,设置当前的虚拟目录
* 如果要共享,则可以将path设置为"/"
2. 不同的tomcat服务器间cookie共享问题?
* setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享
* setDomain(".baidu.com"),那么tieba.baidu.com和news.baidu.com中cookie可以共享
1. 假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?
* 默认情况下cookie不能共享
* setPath(String path):设置cookie的获取范围。默认情况下,设置当前的虚拟目录
* 如果要共享,则可以将path设置为"/"
2. 不同的tomcat服务器间cookie共享问题?
* setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享
* setDomain(".baidu.com"),那么tieba.baidu.com和news.baidu.com中cookie可以共享
Cookie的特点和作用
1. cookie存储数据在客户端浏览器
2. 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)
* 作用:
1. cookie一般用于存出少量的不太敏感的数据
2. 在不登录的情况下,完成服务器对客户端的身份识别
2. 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)
* 作用:
1. cookie一般用于存出少量的不太敏感的数据
2. 在不登录的情况下,完成服务器对客户端的身份识别
案例
记住上一次访问时间
1. 需求:
1. 访问一个Servlet,如果是第一次访问,则提示:您好,欢迎您首次访问。
2. 如果不是第一次访问,则提示:欢迎回来,您上次访问时间为:显示时间字符串
2. 分析:
1. 可以采用Cookie来完成
2. 在服务器中的Servlet判断是否有一个名为lastTime的cookie
1. 有:不是第一次访问
1. 响应数据:欢迎回来,您上次访问时间为:2018年6月10日11:50:20
2. 写回Cookie:lastTime=2018年6月10日11:50:01
2. 没有:是第一次访问
1. 响应数据:您好,欢迎您首次访问
2. 写回Cookie:lastTime=2018年6月10日11:50:01
3. 代码实现:
package cn.itcast.cookie;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
@WebServlet("/cookieTest")
public class CookieTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应的消息体的数据格式以及编码
response.setContentType("text/html;charset=utf-8");
//1.获取所有Cookie
Cookie[] cookies = request.getCookies();
boolean flag = false;//没有cookie为lastTime
//2.遍历cookie数组
if(cookies != null && cookies.length > 0){
for (Cookie cookie : cookies) {
//3.获取cookie的名称
String name = cookie.getName();
//4.判断名称是否是:lastTime
if("lastTime".equals(name)){
//有该Cookie,不是第一次访问
flag = true;//有lastTime的cookie
//设置Cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date = sdf.format(date);
System.out.println("编码前:"+str_date);
//URL编码
str_date = URLEncoder.encode(str_date,"utf-8");
System.out.println("编码后:"+str_date);
cookie.setValue(str_date);
//设置cookie的存活时间
cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
response.addCookie(cookie);
//响应数据
//获取Cookie的value,时间
String value = cookie.getValue();
System.out.println("解码前:"+value);
//URL解码:
value = URLDecoder.decode(value,"utf-8");
System.out.println("解码后:"+value);
response.getWriter().write("<h1>欢迎回来,您上次访问时间为:"+value+"</h1>");
break;
}
}
}
if(cookies == null || cookies.length == 0 || flag == false){
//没有,第一次访问
//设置Cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date = sdf.format(date);
System.out.println("编码前:"+str_date);
//URL编码
str_date = URLEncoder.encode(str_date,"utf-8");
System.out.println("编码后:"+str_date);
Cookie cookie = new Cookie("lastTime",str_date);
//设置cookie的存活时间
cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
response.addCookie(cookie);
response.getWriter().write("<h1>您好,欢迎您首次访问</h1>");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
1. 需求:
1. 访问一个Servlet,如果是第一次访问,则提示:您好,欢迎您首次访问。
2. 如果不是第一次访问,则提示:欢迎回来,您上次访问时间为:显示时间字符串
2. 分析:
1. 可以采用Cookie来完成
2. 在服务器中的Servlet判断是否有一个名为lastTime的cookie
1. 有:不是第一次访问
1. 响应数据:欢迎回来,您上次访问时间为:2018年6月10日11:50:20
2. 写回Cookie:lastTime=2018年6月10日11:50:01
2. 没有:是第一次访问
1. 响应数据:您好,欢迎您首次访问
2. 写回Cookie:lastTime=2018年6月10日11:50:01
3. 代码实现:
package cn.itcast.cookie;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
@WebServlet("/cookieTest")
public class CookieTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应的消息体的数据格式以及编码
response.setContentType("text/html;charset=utf-8");
//1.获取所有Cookie
Cookie[] cookies = request.getCookies();
boolean flag = false;//没有cookie为lastTime
//2.遍历cookie数组
if(cookies != null && cookies.length > 0){
for (Cookie cookie : cookies) {
//3.获取cookie的名称
String name = cookie.getName();
//4.判断名称是否是:lastTime
if("lastTime".equals(name)){
//有该Cookie,不是第一次访问
flag = true;//有lastTime的cookie
//设置Cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date = sdf.format(date);
System.out.println("编码前:"+str_date);
//URL编码
str_date = URLEncoder.encode(str_date,"utf-8");
System.out.println("编码后:"+str_date);
cookie.setValue(str_date);
//设置cookie的存活时间
cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
response.addCookie(cookie);
//响应数据
//获取Cookie的value,时间
String value = cookie.getValue();
System.out.println("解码前:"+value);
//URL解码:
value = URLDecoder.decode(value,"utf-8");
System.out.println("解码后:"+value);
response.getWriter().write("<h1>欢迎回来,您上次访问时间为:"+value+"</h1>");
break;
}
}
}
if(cookies == null || cookies.length == 0 || flag == false){
//没有,第一次访问
//设置Cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date = sdf.format(date);
System.out.println("编码前:"+str_date);
//URL编码
str_date = URLEncoder.encode(str_date,"utf-8");
System.out.println("编码后:"+str_date);
Cookie cookie = new Cookie("lastTime",str_date);
//设置cookie的存活时间
cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
response.addCookie(cookie);
response.getWriter().write("<h1>您好,欢迎您首次访问</h1>");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
session
概念
服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。HttpSession
快速入门
1. 获取HttpSession对象:
HttpSession session = request.getSession();
2. 使用HttpSession对象:
Object getAttribute(String name)
void setAttribute(String name, Object value)
void removeAttribute(String name)
HttpSession session = request.getSession();
2. 使用HttpSession对象:
Object getAttribute(String name)
void setAttribute(String name, Object value)
void removeAttribute(String name)
原理
Session的实现是依赖于Cookie的
细节
1. 当客户端关闭后,服务器不关闭,两次获取session是否为同一个?
* 默认情况下。不是。
* 如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。
Cookie c = new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60);
response.addCookie(c);
2. 客户端不关闭,服务器关闭后,两次获取的session是同一个吗?
* 不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
* session的钝化:
* 在服务器正常关闭之前,将session对象系列化到硬盘上
* session的活化:
* 在服务器启动后,将session文件转化为内存中的session对象即可。
3. session什么时候被销毁?
1. 服务器关闭
2. session对象调用invalidate() 。
3. session默认失效时间 30分钟
选择性配置修改
<session-config>
<session-timeout>30</session-timeout>
</session-config>
* 默认情况下。不是。
* 如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。
Cookie c = new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60);
response.addCookie(c);
2. 客户端不关闭,服务器关闭后,两次获取的session是同一个吗?
* 不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
* session的钝化:
* 在服务器正常关闭之前,将session对象系列化到硬盘上
* session的活化:
* 在服务器启动后,将session文件转化为内存中的session对象即可。
3. session什么时候被销毁?
1. 服务器关闭
2. session对象调用invalidate() 。
3. session默认失效时间 30分钟
选择性配置修改
<session-config>
<session-timeout>30</session-timeout>
</session-config>
特点
1. session用于存储一次会话的多次请求的数据,存在服务器端
2. session可以存储任意类型,任意大小的数据
* session与Cookie的区别:
1. session存储数据在服务器端,Cookie在客户端
2. session没有数据大小限制,Cookie有
3. session数据安全,Cookie相对于不安全
2. session可以存储任意类型,任意大小的数据
* session与Cookie的区别:
1. session存储数据在服务器端,Cookie在客户端
2. session没有数据大小限制,Cookie有
3. session数据安全,Cookie相对于不安全
案例
1. 案例需求:
1. 访问带有验证码的登录页面login.jsp
2. 用户输入用户名,密码以及验证码。
* 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
* 如果验证码输入有误,跳转登录页面,提示:验证码错误
* 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
1. 访问带有验证码的登录页面login.jsp
2. 用户输入用户名,密码以及验证码。
* 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
* 如果验证码输入有误,跳转登录页面,提示:验证码错误
* 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
JSP/EL/JSTL
JSP
指令
作用
用于配置JSP页面,导入资源文件
格式
<%@ 指令名称 属性名1=属性值1 属性名2=属性值2 ... %>
分类
1. page : 配置JSP页面的
* contentType:等同于response.setContentType()
1. 设置响应体的mime类型以及字符集
2. 设置当前jsp页面的编码(只能是高级的IDE才能生效,如果使用低级工具,则需要设置pageEncoding属性设置当前页面的字符集)
* import:导包
* errorPage:当前页面发生异常后,会自动跳转到指定的错误页面
* isErrorPage:标识当前也是是否是错误页面。
* true:是,可以使用内置对象exception
* false:否。默认值。不可以使用内置对象exception
2. include : 页面包含的。导入页面的资源文件
* <%@include file="top.jsp"%>
3. taglib : 导入资源
* <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
* prefix:前缀,自定义的
* contentType:等同于response.setContentType()
1. 设置响应体的mime类型以及字符集
2. 设置当前jsp页面的编码(只能是高级的IDE才能生效,如果使用低级工具,则需要设置pageEncoding属性设置当前页面的字符集)
* import:导包
* errorPage:当前页面发生异常后,会自动跳转到指定的错误页面
* isErrorPage:标识当前也是是否是错误页面。
* true:是,可以使用内置对象exception
* false:否。默认值。不可以使用内置对象exception
2. include : 页面包含的。导入页面的资源文件
* <%@include file="top.jsp"%>
3. taglib : 导入资源
* <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
* prefix:前缀,自定义的
注释
1. html注释:
<!-- -->:只能注释html代码片段
2. jsp注释:推荐使用
<%-- --%>:可以注释所有
<!-- -->:只能注释html代码片段
2. jsp注释:推荐使用
<%-- --%>:可以注释所有
内置对象
* 在jsp页面中不需要创建,直接使用的对象
* 一共有9个:
变量名 真实类型 作用
* pageContext PageContext 当前页面共享数据,还可以获取其他八个内置对象
* request HttpServletRequest 一次请求访问的多个资源(转发)
* session HttpSession 一次会话的多个请求间
* application ServletContext 所有用户间共享数据
* response HttpServletResponse 响应对象
* page Object 当前页面(Servlet)的对象 this
* out JspWriter 输出对象,数据输出到页面上
* config ServletConfig Servlet的配置对象
* exception Throwable 异常对象
* 一共有9个:
变量名 真实类型 作用
* pageContext PageContext 当前页面共享数据,还可以获取其他八个内置对象
* request HttpServletRequest 一次请求访问的多个资源(转发)
* session HttpSession 一次会话的多个请求间
* application ServletContext 所有用户间共享数据
* response HttpServletResponse 响应对象
* page Object 当前页面(Servlet)的对象 this
* out JspWriter 输出对象,数据输出到页面上
* config ServletConfig Servlet的配置对象
* exception Throwable 异常对象
EL
概念
Expression Language 表达式语言
作用
替换和简化jsp页面中java代码的编写
注意
* jsp默认支持el表达式的。如果要忽略el表达式
1. 设置jsp中page指令中:isELIgnored="true" 忽略当前jsp页面中所有的el表达式
2. \${表达式} :忽略当前这个el表达式
1. 设置jsp中page指令中:isELIgnored="true" 忽略当前jsp页面中所有的el表达式
2. \${表达式} :忽略当前这个el表达式
语法
${表达式}
使用
1. 运算:
* 运算符:
1. 算数运算符: + - * /(div) %(mod)
2. 比较运算符: > < >= <= == !=
3. 逻辑运算符: &&(and) ||(or) !(not)
4. 空运算符: empty
* 功能:用于判断字符串、集合、数组对象是否为null或者长度是否为0
* ${empty list}:判断字符串、集合、数组对象是否为null或者长度为0
* ${not empty str}:表示判断字符串、集合、数组对象是否不为null 并且 长度>0
2. 获取值
1. el表达式只能从域对象中获取值
2. 语法:
1. ${域名称.键名}:从指定域中获取指定键的值
* 域名称:
1. pageScope --> pageContext
2. requestScope --> request
3. sessionScope --> session
4. applicationScope --> application(ServletContext)
* 举例:在request域中存储了name=张三
* 获取:${requestScope.name}
2. ${键名}:表示依次从最小的域中查找是否有该键对应的值,直到找到为止。
3. 获取对象、List集合、Map集合的值
1. 对象:${域名称.键名.属性名}
* 本质上会去调用对象的getter方法
2. List集合:${域名称.键名[索引]}
3. Map集合:
* ${域名称.键名.key名称}
* ${域名称.键名["key名称"]}
3. 隐式对象:
* el表达式中有11个隐式对象
* pageContext:
* 获取jsp其他八个内置对象
* ${pageContext.request.contextPath}:动态获取虚拟目录
* 运算符:
1. 算数运算符: + - * /(div) %(mod)
2. 比较运算符: > < >= <= == !=
3. 逻辑运算符: &&(and) ||(or) !(not)
4. 空运算符: empty
* 功能:用于判断字符串、集合、数组对象是否为null或者长度是否为0
* ${empty list}:判断字符串、集合、数组对象是否为null或者长度为0
* ${not empty str}:表示判断字符串、集合、数组对象是否不为null 并且 长度>0
2. 获取值
1. el表达式只能从域对象中获取值
2. 语法:
1. ${域名称.键名}:从指定域中获取指定键的值
* 域名称:
1. pageScope --> pageContext
2. requestScope --> request
3. sessionScope --> session
4. applicationScope --> application(ServletContext)
* 举例:在request域中存储了name=张三
* 获取:${requestScope.name}
2. ${键名}:表示依次从最小的域中查找是否有该键对应的值,直到找到为止。
3. 获取对象、List集合、Map集合的值
1. 对象:${域名称.键名.属性名}
* 本质上会去调用对象的getter方法
2. List集合:${域名称.键名[索引]}
3. Map集合:
* ${域名称.键名.key名称}
* ${域名称.键名["key名称"]}
3. 隐式对象:
* el表达式中有11个隐式对象
* pageContext:
* 获取jsp其他八个内置对象
* ${pageContext.request.contextPath}:动态获取虚拟目录
JSTL
概念
Java Server Pages Tag Library JSP标准标签库
* 是由Apache组织提供的开源的免费的jsp标签 <标签>
* 是由Apache组织提供的开源的免费的jsp标签 <标签>
作用
作用:用于简化和替换jsp页面上的java代码
使用步骤
1. 导入jstl相关jar包
2. 引入标签库:taglib指令: <%@ taglib %>
3. 使用标签
2. 引入标签库:taglib指令: <%@ taglib %>
3. 使用标签
常用JSTL标签
1. if:相当于java代码的if语句
1. 属性:
* test 必须属性,接受boolean表达式
* 如果表达式为true,则显示if标签体内容,如果为false,则不显示标签体内容
* 一般情况下,test属性值会结合el表达式一起使用
2. 注意:
* c:if标签没有else情况,想要else情况,则可以在定义一个c:if标签
2. choose:相当于java代码的switch语句
1. 使用choose标签声明 相当于switch声明
2. 使用when标签做判断 相当于case
3. 使用otherwise标签做其他情况的声明 相当于default
3. foreach:相当于java代码的for语句
1. 属性:
* test 必须属性,接受boolean表达式
* 如果表达式为true,则显示if标签体内容,如果为false,则不显示标签体内容
* 一般情况下,test属性值会结合el表达式一起使用
2. 注意:
* c:if标签没有else情况,想要else情况,则可以在定义一个c:if标签
2. choose:相当于java代码的switch语句
1. 使用choose标签声明 相当于switch声明
2. 使用when标签做判断 相当于case
3. 使用otherwise标签做其他情况的声明 相当于default
3. foreach:相当于java代码的for语句
练习
* 需求:在request域中有一个存有User对象的List集合。
需要使用jstl+el将list集合数据展示到jsp页面的表格table中
需要使用jstl+el将list集合数据展示到jsp页面的表格table中
MVC:开发模式
1. M:Model,模型。JavaBean
* 完成具体的业务操作,如:查询数据库,封装对象
2. V:View,视图。JSP
* 展示数据
3. C:Controller,控制器。Servlet
* 获取用户的输入
* 调用模型
* 将数据交给视图进行展示
* 优缺点:
1. 优点:
1. 耦合性低,方便维护,可以利于分工协作
2. 重用性高
2. 缺点:
1. 使得项目架构变得复杂,对开发人员要求高
* 完成具体的业务操作,如:查询数据库,封装对象
2. V:View,视图。JSP
* 展示数据
3. C:Controller,控制器。Servlet
* 获取用户的输入
* 调用模型
* 将数据交给视图进行展示
* 优缺点:
1. 优点:
1. 耦合性低,方便维护,可以利于分工协作
2. 重用性高
2. 缺点:
1. 使得项目架构变得复杂,对开发人员要求高
Filter&Listener
Filter
概念
* 生活中的过滤器:净水器,空气净化器,土匪、
* web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。
* 过滤器的作用:
* 一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤...
* web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。
* 过滤器的作用:
* 一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤...
快速入门
1. 步骤:
1. 定义一个类,实现接口Filter
2. 复写方法
3. 配置拦截路径
1. web.xml
2. 注解
1. 定义一个类,实现接口Filter
2. 复写方法
3. 配置拦截路径
1. web.xml
2. 注解
2. 代码:
@WebFilter("/*")//访问所有资源之前,都会执行该过滤器
public class FilterDemo1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filterDemo1被执行了....");
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
@WebFilter("/*")//访问所有资源之前,都会执行该过滤器
public class FilterDemo1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filterDemo1被执行了....");
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
过滤器细节
1. web.xml配置
<filter>
<filter-name>demo1</filter-name>
<filter-class>cn.itcast.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
<filter-name>demo1</filter-name>
<!-- 拦截路径 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
2. 过滤器执行流程
1. 执行过滤器
2. 执行放行后的资源
3. 回来执行过滤器放行代码下边的代码
3. 过滤器生命周期方法
1. init:在服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源
2. doFilter:每一次请求被拦截资源时,会执行。执行多次
3. destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源
4. 过滤器配置详解
* 拦截路径配置:
1. 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
2. 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
3. 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行
4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
* 拦截方式配置:资源被访问的方式
* 注解配置:
* 设置dispatcherTypes属性
1. REQUEST:默认值。浏览器直接请求资源
2. FORWARD:转发访问资源
3. INCLUDE:包含访问资源
4. ERROR:错误跳转资源
5. ASYNC:异步访问资源
* web.xml配置
* 设置<dispatcher></dispatcher>标签即可
5. 过滤器链(配置多个过滤器)
* 执行顺序:如果有两个过滤器:过滤器1和过滤器2
1. 过滤器1
2. 过滤器2
3. 资源执行
4. 过滤器2
5. 过滤器1
* 过滤器先后顺序问题:
1. 注解配置:按照类名的字符串比较规则比较,值小的先执行
* 如: AFilter 和 BFilter,AFilter就先执行了。
2. web.xml配置: <filter-mapping>谁定义在上边,谁先执行
<filter>
<filter-name>demo1</filter-name>
<filter-class>cn.itcast.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
<filter-name>demo1</filter-name>
<!-- 拦截路径 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
2. 过滤器执行流程
1. 执行过滤器
2. 执行放行后的资源
3. 回来执行过滤器放行代码下边的代码
3. 过滤器生命周期方法
1. init:在服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源
2. doFilter:每一次请求被拦截资源时,会执行。执行多次
3. destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源
4. 过滤器配置详解
* 拦截路径配置:
1. 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
2. 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
3. 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行
4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
* 拦截方式配置:资源被访问的方式
* 注解配置:
* 设置dispatcherTypes属性
1. REQUEST:默认值。浏览器直接请求资源
2. FORWARD:转发访问资源
3. INCLUDE:包含访问资源
4. ERROR:错误跳转资源
5. ASYNC:异步访问资源
* web.xml配置
* 设置<dispatcher></dispatcher>标签即可
5. 过滤器链(配置多个过滤器)
* 执行顺序:如果有两个过滤器:过滤器1和过滤器2
1. 过滤器1
2. 过滤器2
3. 资源执行
4. 过滤器2
5. 过滤器1
* 过滤器先后顺序问题:
1. 注解配置:按照类名的字符串比较规则比较,值小的先执行
* 如: AFilter 和 BFilter,AFilter就先执行了。
2. web.xml配置: <filter-mapping>谁定义在上边,谁先执行
案例
1. 案例1_登录验证
* 需求:
1. 访问day17_case案例的资源。验证其是否登录
2. 如果登录了,则直接放行。
3. 如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"。
2. 案例2_敏感词汇过滤
* 需求:
1. 对day17_case案例录入的数据进行敏感词汇过滤
2. 敏感词汇参考《敏感词汇.txt》
3. 如果是敏感词汇,替换为 ***
* 分析:
1. 对request对象进行增强。增强获取参数相关方法
2. 放行。传递代理对象
* 增强对象的功能:
* 设计模式:一些通用的解决固定问题的方式
1. 装饰模式
2. 代理模式
* 概念:
1. 真实对象:被代理的对象
2. 代理对象:
3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
* 实现方式:
1. 静态代理:有一个类文件描述代理模式
2. 动态代理:在内存中形成代理类
* 实现步骤:
1. 代理对象和真实对象实现相同的接口
2. 代理对象 = Proxy.newProxyInstance();
3. 使用代理对象调用方法。
4. 增强方法
* 增强方式:
1. 增强参数列表
2. 增强返回值类型
3. 增强方法体执行逻辑
* 需求:
1. 访问day17_case案例的资源。验证其是否登录
2. 如果登录了,则直接放行。
3. 如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"。
2. 案例2_敏感词汇过滤
* 需求:
1. 对day17_case案例录入的数据进行敏感词汇过滤
2. 敏感词汇参考《敏感词汇.txt》
3. 如果是敏感词汇,替换为 ***
* 分析:
1. 对request对象进行增强。增强获取参数相关方法
2. 放行。传递代理对象
* 增强对象的功能:
* 设计模式:一些通用的解决固定问题的方式
1. 装饰模式
2. 代理模式
* 概念:
1. 真实对象:被代理的对象
2. 代理对象:
3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
* 实现方式:
1. 静态代理:有一个类文件描述代理模式
2. 动态代理:在内存中形成代理类
* 实现步骤:
1. 代理对象和真实对象实现相同的接口
2. 代理对象 = Proxy.newProxyInstance();
3. 使用代理对象调用方法。
4. 增强方法
* 增强方式:
1. 增强参数列表
2. 增强返回值类型
3. 增强方法体执行逻辑
Listener
* 概念:web的三大组件之一。
* 事件监听机制
* 事件 :一件事情
* 事件源 :事件发生的地方
* 监听器 :一个对象
* 注册监听:将事件、事件源、监听器绑定在一起。 当事件源上发生某个事件后,执行监听器代码
* ServletContextListener:监听ServletContext对象的创建和销毁
* 方法:
* void contextDestroyed(ServletContextEvent sce) :ServletContext对象被销毁之前会调用该方法
* void contextInitialized(ServletContextEvent sce) :ServletContext对象创建后会调用该方法
* 步骤:
1. 定义一个类,实现ServletContextListener接口
2. 复写方法
3. 配置
1. web.xml
<listener>
<listener-class>cn.itcast.web.listener.ContextLoaderListener</listener-class>
</listener>
* 指定初始化参数<context-param>
2. 注解:
* @WebListener
* 事件监听机制
* 事件 :一件事情
* 事件源 :事件发生的地方
* 监听器 :一个对象
* 注册监听:将事件、事件源、监听器绑定在一起。 当事件源上发生某个事件后,执行监听器代码
* ServletContextListener:监听ServletContext对象的创建和销毁
* 方法:
* void contextDestroyed(ServletContextEvent sce) :ServletContext对象被销毁之前会调用该方法
* void contextInitialized(ServletContextEvent sce) :ServletContext对象创建后会调用该方法
* 步骤:
1. 定义一个类,实现ServletContextListener接口
2. 复写方法
3. 配置
1. web.xml
<listener>
<listener-class>cn.itcast.web.listener.ContextLoaderListener</listener-class>
</listener>
* 指定初始化参数<context-param>
2. 注解:
* @WebListener
Jquery
基础
概念
一个JavaScript框架。简化JS开发
* jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架)。jQuery设计的宗旨 是“write Less,Do More”,即倡导写更少的代码,做更多的事情。它封装JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优 化HTML文档操作、事件处理、动画设计和Ajax交互。
* jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架)。jQuery设计的宗旨 是“write Less,Do More”,即倡导写更少的代码,做更多的事情。它封装JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优 化HTML文档操作、事件处理、动画设计和Ajax交互。
快速入门
1. 步骤:
1. 下载JQuery
* 目前jQuery有三个大版本:
1.x:兼容ie678,使用最为广泛的,官方只做BUG维护,
功能不再新增。因此一般项目来说,使用1.x版本就可以了,
最终版本:1.12.4 (2016年5月20日)
2.x:不兼容ie678,很少有人使用,官方只做BUG维护,
功能不再新增。如果不考虑兼容低版本的浏览器可以使用2.x,
最终版本:2.2.4 (2016年5月20日)
3.x:不兼容ie678,只支持最新的浏览器。除非特殊要求,
一般不会使用3.x版本的,很多老的jQuery插件不支持这个版本。
目前该版本是官方主要更新维护的版本。最新版本:3.2.1(2017年3月20日)
* jquery-xxx.js 与 jquery-xxx.min.js区别:
1. jquery-xxx.js:开发版本。给程序员看的,有良好的缩进和注释。体积大一些
2. jquery-xxx.min.js:生产版本。程序中使用,没有缩进。体积小一些。程序加载更快
2. 导入JQuery的js文件:导入min.js文件
3. 使用
var div1 = $("#div1");
alert(div1.html());
1. 下载JQuery
* 目前jQuery有三个大版本:
1.x:兼容ie678,使用最为广泛的,官方只做BUG维护,
功能不再新增。因此一般项目来说,使用1.x版本就可以了,
最终版本:1.12.4 (2016年5月20日)
2.x:不兼容ie678,很少有人使用,官方只做BUG维护,
功能不再新增。如果不考虑兼容低版本的浏览器可以使用2.x,
最终版本:2.2.4 (2016年5月20日)
3.x:不兼容ie678,只支持最新的浏览器。除非特殊要求,
一般不会使用3.x版本的,很多老的jQuery插件不支持这个版本。
目前该版本是官方主要更新维护的版本。最新版本:3.2.1(2017年3月20日)
* jquery-xxx.js 与 jquery-xxx.min.js区别:
1. jquery-xxx.js:开发版本。给程序员看的,有良好的缩进和注释。体积大一些
2. jquery-xxx.min.js:生产版本。程序中使用,没有缩进。体积小一些。程序加载更快
2. 导入JQuery的js文件:导入min.js文件
3. 使用
var div1 = $("#div1");
alert(div1.html());
JQuery和js对象区别与转换
1. JQuery对象在操作时,更加方便。
2. JQuery对象和js对象方法不通用的.
3. 两者相互转换
* jq -- > js : jq对象[索引] 或者 jq对象.get(索引)
* js -- > jq : $(js对象)
2. JQuery对象和js对象方法不通用的.
3. 两者相互转换
* jq -- > js : jq对象[索引] 或者 jq对象.get(索引)
* js -- > jq : $(js对象)
选择器
1. 基本操作学习:
1. 事件绑定
//1.获取b1按钮
$("#b1").click(function(){
alert("abc");
});
2. 入口函数
$(function () {
});
window.onload 和 $(function) 区别
* window.onload 只能定义一次,如果定义多次,后边的会将前边的覆盖掉
* $(function)可以定义多次的。
3. 样式控制:css方法
// $("#div1").css("background-color","red");
$("#div1").css("backgroundColor","pink");
1. 事件绑定
//1.获取b1按钮
$("#b1").click(function(){
alert("abc");
});
2. 入口函数
$(function () {
});
window.onload 和 $(function) 区别
* window.onload 只能定义一次,如果定义多次,后边的会将前边的覆盖掉
* $(function)可以定义多次的。
3. 样式控制:css方法
// $("#div1").css("background-color","red");
$("#div1").css("backgroundColor","pink");
2. 分类
1. 基本选择器
1. 标签选择器(元素选择器)
* 语法: $("html标签名") 获得所有匹配标签名称的元素
2. id选择器
* 语法: $("#id的属性值") 获得与指定id属性值匹配的元素
3. 类选择器
* 语法: $(".class的属性值") 获得与指定的class属性值匹配的元素
4. 并集选择器:
* 语法: $("选择器1,选择器2....") 获取多个选择器选中的所有元素
2. 层级选择器
1. 后代选择器
* 语法: $("A B ") 选择A元素内部的所有B元素
2. 子选择器
* 语法: $("A > B") 选择A元素内部的所有B子元素
3. 属性选择器
1. 属性名称选择器
* 语法: $("A[属性名]") 包含指定属性的选择器
2. 属性选择器
* 语法: $("A[属性名='值']") 包含指定属性等于指定值的选择器
3. 复合属性选择器
* 语法: $("A[属性名='值'][]...") 包含多个属性条件的选择器
4. 过滤选择器
1. 首元素选择器
* 语法: :first 获得选择的元素中的第一个元素
2. 尾元素选择器
* 语法: :last 获得选择的元素中的最后一个元素
3. 非元素选择器
* 语法: :not(selector) 不包括指定内容的元素
4. 偶数选择器
* 语法: :even 偶数,从 0 开始计数
5. 奇数选择器
* 语法: :odd 奇数,从 0 开始计数
6. 等于索引选择器
* 语法: :eq(index) 指定索引元素
7. 大于索引选择器
* 语法: :gt(index) 大于指定索引元素
8. 小于索引选择器
* 语法: :lt(index) 小于指定索引元素
9. 标题选择器
* 语法: :header 获得标题(h1~h6)元素,固定写法
5. 表单过滤选择器
1. 可用元素选择器
* 语法: :enabled 获得可用元素
2. 不可用元素选择器
* 语法: :disabled 获得不可用元素
3. 选中选择器
* 语法: :checked 获得单选/复选框选中的元素
4. 选中选择器
* 语法: :selected 获得下拉框选中的元素
1. 基本选择器
1. 标签选择器(元素选择器)
* 语法: $("html标签名") 获得所有匹配标签名称的元素
2. id选择器
* 语法: $("#id的属性值") 获得与指定id属性值匹配的元素
3. 类选择器
* 语法: $(".class的属性值") 获得与指定的class属性值匹配的元素
4. 并集选择器:
* 语法: $("选择器1,选择器2....") 获取多个选择器选中的所有元素
2. 层级选择器
1. 后代选择器
* 语法: $("A B ") 选择A元素内部的所有B元素
2. 子选择器
* 语法: $("A > B") 选择A元素内部的所有B子元素
3. 属性选择器
1. 属性名称选择器
* 语法: $("A[属性名]") 包含指定属性的选择器
2. 属性选择器
* 语法: $("A[属性名='值']") 包含指定属性等于指定值的选择器
3. 复合属性选择器
* 语法: $("A[属性名='值'][]...") 包含多个属性条件的选择器
4. 过滤选择器
1. 首元素选择器
* 语法: :first 获得选择的元素中的第一个元素
2. 尾元素选择器
* 语法: :last 获得选择的元素中的最后一个元素
3. 非元素选择器
* 语法: :not(selector) 不包括指定内容的元素
4. 偶数选择器
* 语法: :even 偶数,从 0 开始计数
5. 奇数选择器
* 语法: :odd 奇数,从 0 开始计数
6. 等于索引选择器
* 语法: :eq(index) 指定索引元素
7. 大于索引选择器
* 语法: :gt(index) 大于指定索引元素
8. 小于索引选择器
* 语法: :lt(index) 小于指定索引元素
9. 标题选择器
* 语法: :header 获得标题(h1~h6)元素,固定写法
5. 表单过滤选择器
1. 可用元素选择器
* 语法: :enabled 获得可用元素
2. 不可用元素选择器
* 语法: :disabled 获得不可用元素
3. 选中选择器
* 语法: :checked 获得单选/复选框选中的元素
4. 选中选择器
* 语法: :selected 获得下拉框选中的元素
Dom操作
1. 内容操作
1. html(): 获取/设置元素的标签体内容 <a><font>内容</font></a> --> <font>内容</font>
2. text(): 获取/设置元素的标签体纯文本内容 <a><font>内容</font></a> --> 内容
3. val(): 获取/设置元素的value属性值
2. 属性操作
1. 通用属性操作
1. attr(): 获取/设置元素的属性
2. removeAttr():删除属性
3. prop():获取/设置元素的属性
4. removeProp():删除属性
* attr和prop区别?
1. 如果操作的是元素的固有属性,则建议使用prop
2. 如果操作的是元素自定义的属性,则建议使用attr
2. 对class属性操作
1. addClass():添加class属性值
2. removeClass():删除class属性值
3. toggleClass():切换class属性
* toggleClass("one"):
* 判断如果元素对象上存在class="one",则将属性值one删除掉。 如果元素对象上不存在class="one",则添加
4. css():
3. CRUD操作:
1. append():父元素将子元素追加到末尾
* 对象1.append(对象2): 将对象2添加到对象1元素内部,并且在末尾
2. prepend():父元素将子元素追加到开头
* 对象1.prepend(对象2):将对象2添加到对象1元素内部,并且在开头
3. appendTo():
* 对象1.appendTo(对象2):将对象1添加到对象2内部,并且在末尾
4. prependTo():
* 对象1.prependTo(对象2):将对象1添加到对象2内部,并且在开头
5. after():添加元素到元素后边
* 对象1.after(对象2): 将对象2添加到对象1后边。对象1和对象2是兄弟关系
6. before():添加元素到元素前边
* 对象1.before(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系
7. insertAfter()
* 对象1.insertAfter(对象2):将对象2添加到对象1后边。对象1和对象2是兄弟关系
8. insertBefore()
* 对象1.insertBefore(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系
9. remove():移除元素
* 对象.remove():将对象删除掉
10. empty():清空元素的所有后代元素。
* 对象.empty():将对象的后代元素全部清空,但是保留当前对象以及其属性节点
1. html(): 获取/设置元素的标签体内容 <a><font>内容</font></a> --> <font>内容</font>
2. text(): 获取/设置元素的标签体纯文本内容 <a><font>内容</font></a> --> 内容
3. val(): 获取/设置元素的value属性值
2. 属性操作
1. 通用属性操作
1. attr(): 获取/设置元素的属性
2. removeAttr():删除属性
3. prop():获取/设置元素的属性
4. removeProp():删除属性
* attr和prop区别?
1. 如果操作的是元素的固有属性,则建议使用prop
2. 如果操作的是元素自定义的属性,则建议使用attr
2. 对class属性操作
1. addClass():添加class属性值
2. removeClass():删除class属性值
3. toggleClass():切换class属性
* toggleClass("one"):
* 判断如果元素对象上存在class="one",则将属性值one删除掉。 如果元素对象上不存在class="one",则添加
4. css():
3. CRUD操作:
1. append():父元素将子元素追加到末尾
* 对象1.append(对象2): 将对象2添加到对象1元素内部,并且在末尾
2. prepend():父元素将子元素追加到开头
* 对象1.prepend(对象2):将对象2添加到对象1元素内部,并且在开头
3. appendTo():
* 对象1.appendTo(对象2):将对象1添加到对象2内部,并且在末尾
4. prependTo():
* 对象1.prependTo(对象2):将对象1添加到对象2内部,并且在开头
5. after():添加元素到元素后边
* 对象1.after(对象2): 将对象2添加到对象1后边。对象1和对象2是兄弟关系
6. before():添加元素到元素前边
* 对象1.before(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系
7. insertAfter()
* 对象1.insertAfter(对象2):将对象2添加到对象1后边。对象1和对象2是兄弟关系
8. insertBefore()
* 对象1.insertBefore(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系
9. remove():移除元素
* 对象.remove():将对象删除掉
10. empty():清空元素的所有后代元素。
* 对象.empty():将对象的后代元素全部清空,但是保留当前对象以及其属性节点
案例
高级
动画
1. 动画
1. 三种方式显示和隐藏元素
1. 默认显示和隐藏方式
1. show([speed,[easing],[fn]])
1. 参数:
1. speed:动画的速度。三个预定义的值("slow","normal", "fast")或表示动画时长的毫秒数值(如:1000)
2. easing:用来指定切换效果,默认是"swing",可用参数"linear"
* swing:动画执行时效果是 先慢,中间快,最后又慢
* linear:动画执行时速度是匀速的
3. fn:在动画完成时执行的函数,每个元素执行一次。
2. hide([speed,[easing],[fn]])
3. toggle([speed],[easing],[fn])
2. 滑动显示和隐藏方式
1. slideDown([speed],[easing],[fn])
2. slideUp([speed,[easing],[fn]])
3. slideToggle([speed],[easing],[fn])
3. 淡入淡出显示和隐藏方式
1. fadeIn([speed],[easing],[fn])
2. fadeOut([speed],[easing],[fn])
3. fadeToggle([speed,[easing],[fn]])
1. 三种方式显示和隐藏元素
1. 默认显示和隐藏方式
1. show([speed,[easing],[fn]])
1. 参数:
1. speed:动画的速度。三个预定义的值("slow","normal", "fast")或表示动画时长的毫秒数值(如:1000)
2. easing:用来指定切换效果,默认是"swing",可用参数"linear"
* swing:动画执行时效果是 先慢,中间快,最后又慢
* linear:动画执行时速度是匀速的
3. fn:在动画完成时执行的函数,每个元素执行一次。
2. hide([speed,[easing],[fn]])
3. toggle([speed],[easing],[fn])
2. 滑动显示和隐藏方式
1. slideDown([speed],[easing],[fn])
2. slideUp([speed,[easing],[fn]])
3. slideToggle([speed],[easing],[fn])
3. 淡入淡出显示和隐藏方式
1. fadeIn([speed],[easing],[fn])
2. fadeOut([speed],[easing],[fn])
3. fadeToggle([speed,[easing],[fn]])
遍历
1. js的遍历方式
* for(初始化值;循环结束条件;步长)
2. jq的遍历方式
1. jq对象.each(callback)
1. 语法:
jquery对象.each(function(index,element){});
* index:就是元素在集合中的索引
* element:就是集合中的每一个元素对象
* this:集合中的每一个元素对象
2. 回调函数返回值:
* true:如果当前function返回为false,则结束循环(break)。
* false:如果当前function返回为true,则结束本次循环,继续下次循环(continue)
2. $.each(object, [callback])
3. for..of: jquery 3.0 版本之后提供的方式
for(元素对象 of 容器对象)
* for(初始化值;循环结束条件;步长)
2. jq的遍历方式
1. jq对象.each(callback)
1. 语法:
jquery对象.each(function(index,element){});
* index:就是元素在集合中的索引
* element:就是集合中的每一个元素对象
* this:集合中的每一个元素对象
2. 回调函数返回值:
* true:如果当前function返回为false,则结束循环(break)。
* false:如果当前function返回为true,则结束本次循环,继续下次循环(continue)
2. $.each(object, [callback])
3. for..of: jquery 3.0 版本之后提供的方式
for(元素对象 of 容器对象)
数据绑定
1. jquery标准的绑定方式
* jq对象.事件方法(回调函数);
* 注:如果调用事件方法,不传递回调函数,则会触发浏览器默认行为。
* 表单对象.submit();//让表单提交
2. on绑定事件/off解除绑定
* jq对象.on("事件名称",回调函数)
* jq对象.off("事件名称")
* 如果off方法不传递任何参数,则将组件上的所有事件全部解绑
3. 事件切换:toggle
* jq对象.toggle(fn1,fn2...)
* 当单击jq对象对应的组件后,会执行fn1.第二次点击会执行fn2.....
* 注意:1.9版本 .toggle() 方法删除,jQuery Migrate(迁移)插件可以恢复此功能。
<script src="../js/jquery-migrate-1.0.0.js" type="text/javascript" charset="utf-8"></script>
* jq对象.事件方法(回调函数);
* 注:如果调用事件方法,不传递回调函数,则会触发浏览器默认行为。
* 表单对象.submit();//让表单提交
2. on绑定事件/off解除绑定
* jq对象.on("事件名称",回调函数)
* jq对象.off("事件名称")
* 如果off方法不传递任何参数,则将组件上的所有事件全部解绑
3. 事件切换:toggle
* jq对象.toggle(fn1,fn2...)
* 当单击jq对象对应的组件后,会执行fn1.第二次点击会执行fn2.....
* 注意:1.9版本 .toggle() 方法删除,jQuery Migrate(迁移)插件可以恢复此功能。
<script src="../js/jquery-migrate-1.0.0.js" type="text/javascript" charset="utf-8"></script>
案例
1. 广告显示和隐藏
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>广告的自动显示与隐藏</title>
<style>
#content{width:100%;height:500px;background:#999}
</style>
<!--引入jquery-->
<script type="text/javascript" src="../js/jquery-3.3.1.min.js"></script>
<script>
/*
需求:
1. 当页面加载完,3秒后。自动显示广告
2. 广告显示5秒后,自动消失。
分析:
1. 使用定时器来完成。setTimeout (执行一次定时器)
2. 分析发现JQuery的显示和隐藏动画效果其实就是控制display
3. 使用 show/hide方法来完成广告的显示
*/
//入口函数,在页面加载完成之后,定义定时器,调用这两个方法
$(function () {
//定义定时器,调用adShow方法 3秒后执行一次
setTimeout(adShow,3000);
//定义定时器,调用adHide方法,8秒后执行一次
setTimeout(adHide,8000);
});
//显示广告
function adShow() {
//获取广告div,调用显示方法
$("#ad").show("slow");
}
//隐藏广告
function adHide() {
//获取广告div,调用隐藏方法
$("#ad").hide("slow");
}
</script>
</head>
<body>
<!-- 整体的DIV -->
<div>
<!-- 广告DIV -->
<div id="ad" style="display: none;">
<img style="width:100%" src="../img/adv.jpg" />
</div>
<!-- 下方正文部分 -->
<div id="content">
正文部分
</div>
</div>
</body>
</html>
2. 抽奖
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jquery案例之抽奖</title>
<script type="text/javascript" src="../js/jquery-3.3.1.min.js"></script>
<script language='javascript' type='text/javascript'>
/*
分析:
1. 给开始按钮绑定单击事件
1.1 定义循环定时器
1.2 切换小相框的src属性
* 定义数组,存放图片资源路径
* 生成随机数。数组索引
2. 给结束按钮绑定单击事件
1.1 停止定时器
1.2 给大相框设置src属性
*/
var imgs = ["../img/man00.jpg",
"../img/man01.jpg",
"../img/man02.jpg",
"../img/man03.jpg",
"../img/man04.jpg",
"../img/man05.jpg",
"../img/man06.jpg",
];
var startId;//开始定时器的id
var index;//随机角标
$(function () {
//处理按钮是否可以使用的效果
$("#startID").prop("disabled",false);
$("#stopID").prop("disabled",true);
//1. 给开始按钮绑定单击事件
$("#startID").click(function () {
// 1.1 定义循环定时器 20毫秒执行一次
startId = setInterval(function () {
//处理按钮是否可以使用的效果
$("#startID").prop("disabled",true);
$("#stopID").prop("disabled",false);
//1.2生成随机角标 0-6
index = Math.floor(Math.random() * 7);//0.000--0.999 --> * 7 --> 0.0-----6.9999
//1.3设置小相框的src属性
$("#img1ID").prop("src",imgs[index]);
},20);
});
//2. 给结束按钮绑定单击事件
$("#stopID").click(function () {
//处理按钮是否可以使用的效果
$("#startID").prop("disabled",false);
$("#stopID").prop("disabled",true);
// 1.1 停止定时器
clearInterval(startId);
// 1.2 给大相框设置src属性
$("#img2ID").prop("src",imgs[index]).hide();
//显示1秒之后
$("#img2ID").show(1000);
});
});
</script>
</head>
<body>
<!-- 小像框 -->
<div style="border-style:dotted;width:160px;height:100px">
<img id="img1ID" src="../img/man00.jpg" style="width:160px;height:100px"/>
</div>
<!-- 大像框 -->
<div
style="border-style:double;width:800px;height:500px;position:absolute;left:500px;top:10px">
<img id="img2ID" src="../img/man00.jpg" width="800px" height="500px"/>
</div>
<!-- 开始按钮 -->
<input
id="startID"
type="button"
value="点击开始"
style="width:150px;height:150px;font-size:22px">
<!-- 停止按钮 -->
<input
id="stopID"
type="button"
value="点击停止"
style="width:150px;height:150px;font-size:22px">
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>广告的自动显示与隐藏</title>
<style>
#content{width:100%;height:500px;background:#999}
</style>
<!--引入jquery-->
<script type="text/javascript" src="../js/jquery-3.3.1.min.js"></script>
<script>
/*
需求:
1. 当页面加载完,3秒后。自动显示广告
2. 广告显示5秒后,自动消失。
分析:
1. 使用定时器来完成。setTimeout (执行一次定时器)
2. 分析发现JQuery的显示和隐藏动画效果其实就是控制display
3. 使用 show/hide方法来完成广告的显示
*/
//入口函数,在页面加载完成之后,定义定时器,调用这两个方法
$(function () {
//定义定时器,调用adShow方法 3秒后执行一次
setTimeout(adShow,3000);
//定义定时器,调用adHide方法,8秒后执行一次
setTimeout(adHide,8000);
});
//显示广告
function adShow() {
//获取广告div,调用显示方法
$("#ad").show("slow");
}
//隐藏广告
function adHide() {
//获取广告div,调用隐藏方法
$("#ad").hide("slow");
}
</script>
</head>
<body>
<!-- 整体的DIV -->
<div>
<!-- 广告DIV -->
<div id="ad" style="display: none;">
<img style="width:100%" src="../img/adv.jpg" />
</div>
<!-- 下方正文部分 -->
<div id="content">
正文部分
</div>
</div>
</body>
</html>
2. 抽奖
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jquery案例之抽奖</title>
<script type="text/javascript" src="../js/jquery-3.3.1.min.js"></script>
<script language='javascript' type='text/javascript'>
/*
分析:
1. 给开始按钮绑定单击事件
1.1 定义循环定时器
1.2 切换小相框的src属性
* 定义数组,存放图片资源路径
* 生成随机数。数组索引
2. 给结束按钮绑定单击事件
1.1 停止定时器
1.2 给大相框设置src属性
*/
var imgs = ["../img/man00.jpg",
"../img/man01.jpg",
"../img/man02.jpg",
"../img/man03.jpg",
"../img/man04.jpg",
"../img/man05.jpg",
"../img/man06.jpg",
];
var startId;//开始定时器的id
var index;//随机角标
$(function () {
//处理按钮是否可以使用的效果
$("#startID").prop("disabled",false);
$("#stopID").prop("disabled",true);
//1. 给开始按钮绑定单击事件
$("#startID").click(function () {
// 1.1 定义循环定时器 20毫秒执行一次
startId = setInterval(function () {
//处理按钮是否可以使用的效果
$("#startID").prop("disabled",true);
$("#stopID").prop("disabled",false);
//1.2生成随机角标 0-6
index = Math.floor(Math.random() * 7);//0.000--0.999 --> * 7 --> 0.0-----6.9999
//1.3设置小相框的src属性
$("#img1ID").prop("src",imgs[index]);
},20);
});
//2. 给结束按钮绑定单击事件
$("#stopID").click(function () {
//处理按钮是否可以使用的效果
$("#startID").prop("disabled",false);
$("#stopID").prop("disabled",true);
// 1.1 停止定时器
clearInterval(startId);
// 1.2 给大相框设置src属性
$("#img2ID").prop("src",imgs[index]).hide();
//显示1秒之后
$("#img2ID").show(1000);
});
});
</script>
</head>
<body>
<!-- 小像框 -->
<div style="border-style:dotted;width:160px;height:100px">
<img id="img1ID" src="../img/man00.jpg" style="width:160px;height:100px"/>
</div>
<!-- 大像框 -->
<div
style="border-style:double;width:800px;height:500px;position:absolute;left:500px;top:10px">
<img id="img2ID" src="../img/man00.jpg" width="800px" height="500px"/>
</div>
<!-- 开始按钮 -->
<input
id="startID"
type="button"
value="点击开始"
style="width:150px;height:150px;font-size:22px">
<!-- 停止按钮 -->
<input
id="stopID"
type="button"
value="点击停止"
style="width:150px;height:150px;font-size:22px">
</body>
</html>
插件
5. 插件:增强JQuery的功能
1. 实现方式:
1. $.fn.extend(object)
* 增强通过Jquery获取的对象的功能 $("#id")
2. $.extend(object)
* 增强JQeury对象自身的功能 $/jQuery
1. 实现方式:
1. $.fn.extend(object)
* 增强通过Jquery获取的对象的功能 $("#id")
2. $.extend(object)
* 增强JQeury对象自身的功能 $/jQuery
Ajax&JSON
Ajax
概念
ASynchronous JavaScript And XML 异步的JavaScript 和 XML
1. 异步和同步:客户端和服务器端相互通信的基础上
* 客户端必须等待服务器端的响应。在等待的期间客户端不能做其他操作。
* 客户端不需要等待服务器端的响应。在服务器处理请求的过程中,客户端可以进行其他的操作。
Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。 [1]
通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。
提升用户的体验
* 客户端必须等待服务器端的响应。在等待的期间客户端不能做其他操作。
* 客户端不需要等待服务器端的响应。在服务器处理请求的过程中,客户端可以进行其他的操作。
Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。 [1]
通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。
提升用户的体验
实现方式
1. 原生的JS实现方式(了解)
//1.创建核心对象
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
//2. 建立连接
/*
参数:
1. 请求方式:GET、POST
* get方式,请求参数在URL后边拼接。send方法为空参
* post方式,请求参数在send方法中定义
2. 请求的URL:
3. 同步或异步请求:true(异步)或 false(同步)
*/
xmlhttp.open("GET","ajaxServlet?username=tom",true);
//3.发送请求
xmlhttp.send();
//4.接受并处理来自服务器的响应结果
//获取方式 :xmlhttp.responseText
//什么时候获取?当服务器响应成功后再获取
//当xmlhttp对象的就绪状态改变时,触发事件onreadystatechange。
xmlhttp.onreadystatechange=function()
{
//判断readyState就绪状态是否为4,判断status响应状态码是否为200
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
//获取服务器的响应结果
var responseText = xmlhttp.responseText;
alert(responseText);
}
}
//1.创建核心对象
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
//2. 建立连接
/*
参数:
1. 请求方式:GET、POST
* get方式,请求参数在URL后边拼接。send方法为空参
* post方式,请求参数在send方法中定义
2. 请求的URL:
3. 同步或异步请求:true(异步)或 false(同步)
*/
xmlhttp.open("GET","ajaxServlet?username=tom",true);
//3.发送请求
xmlhttp.send();
//4.接受并处理来自服务器的响应结果
//获取方式 :xmlhttp.responseText
//什么时候获取?当服务器响应成功后再获取
//当xmlhttp对象的就绪状态改变时,触发事件onreadystatechange。
xmlhttp.onreadystatechange=function()
{
//判断readyState就绪状态是否为4,判断status响应状态码是否为200
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
//获取服务器的响应结果
var responseText = xmlhttp.responseText;
alert(responseText);
}
}
2. JQeury实现方式
1. $.ajax()
* 语法:$.ajax({键值对});
//使用$.ajax()发送异步请求
$.ajax({
url:"ajaxServlet1111" , // 请求路径
type:"POST" , //请求方式
//data: "username=jack&age=23",//请求参数
data:{"username":"jack","age":23},
success:function (data) {
alert(data);
},//响应成功后的回调函数
error:function () {
alert("出错啦...")
},//表示如果请求响应出现错误,会执行的回调函数
dataType:"text"//设置接受到的响应数据的格式
});
2. $.get():发送get请求
* 语法:$.get(url, [data], [callback], [type])
* 参数:
* url:请求路径
* data:请求参数
* callback:回调函数
* type:响应结果的类型
3. $.post():发送post请求
* 语法:$.post(url, [data], [callback], [type])
* 参数:
* url:请求路径
* data:请求参数
* callback:回调函数
* type:响应结果的类型
1. $.ajax()
* 语法:$.ajax({键值对});
//使用$.ajax()发送异步请求
$.ajax({
url:"ajaxServlet1111" , // 请求路径
type:"POST" , //请求方式
//data: "username=jack&age=23",//请求参数
data:{"username":"jack","age":23},
success:function (data) {
alert(data);
},//响应成功后的回调函数
error:function () {
alert("出错啦...")
},//表示如果请求响应出现错误,会执行的回调函数
dataType:"text"//设置接受到的响应数据的格式
});
2. $.get():发送get请求
* 语法:$.get(url, [data], [callback], [type])
* 参数:
* url:请求路径
* data:请求参数
* callback:回调函数
* type:响应结果的类型
3. $.post():发送post请求
* 语法:$.post(url, [data], [callback], [type])
* 参数:
* url:请求路径
* data:请求参数
* callback:回调函数
* type:响应结果的类型
JSON
概念
概念: JavaScript Object Notation JavaScript对象表示法
Person p = new Person();
p.setName("张三");
p.setAge(23);
p.setGender("男");
var p = {"name":"张三","age":23,"gender":"男"};
* json现在多用于存储和交换文本信息的语法
* 进行数据的传输
* JSON 比 XML 更小、更快,更易解析。
Person p = new Person();
p.setName("张三");
p.setAge(23);
p.setGender("男");
var p = {"name":"张三","age":23,"gender":"男"};
* json现在多用于存储和交换文本信息的语法
* 进行数据的传输
* JSON 比 XML 更小、更快,更易解析。
语法
1. 基本规则
* 数据在名称/值对中:json数据是由键值对构成的
* 键用引号(单双都行)引起来,也可以不使用引号
* 值得取值类型:
1. 数字(整数或浮点数)
2. 字符串(在双引号中)
3. 逻辑值(true 或 false)
4. 数组(在方括号中) {"persons":[{},{}]}
5. 对象(在花括号中) {"address":{"province":"陕西"....}}
6. null
* 数据由逗号分隔:多个键值对由逗号分隔
* 花括号保存对象:使用{}定义json 格式
* 方括号保存数组:[]
2. 获取数据:
1. json对象.键名
2. json对象["键名"]
3. 数组对象[索引]
4. 遍历
//1.定义基本格式
var person = {"name": "张三", age: 23, 'gender': true};
var ps = [{"name": "张三", "age": 23, "gender": true},
{"name": "李四", "age": 24, "gender": true},
{"name": "王五", "age": 25, "gender": false}];
//获取person对象中所有的键和值
//for in 循环
/* for(var key in person){
//这样的方式获取不行。因为相当于 person."name"
//alert(key + ":" + person.key);
alert(key+":"+person[key]);
}*/
//获取ps中的所有值
for (var i = 0; i < ps.length; i++) {
var p = ps[i];
for(var key in p){
alert(key+":"+p[key]);
}
}
* 数据在名称/值对中:json数据是由键值对构成的
* 键用引号(单双都行)引起来,也可以不使用引号
* 值得取值类型:
1. 数字(整数或浮点数)
2. 字符串(在双引号中)
3. 逻辑值(true 或 false)
4. 数组(在方括号中) {"persons":[{},{}]}
5. 对象(在花括号中) {"address":{"province":"陕西"....}}
6. null
* 数据由逗号分隔:多个键值对由逗号分隔
* 花括号保存对象:使用{}定义json 格式
* 方括号保存数组:[]
2. 获取数据:
1. json对象.键名
2. json对象["键名"]
3. 数组对象[索引]
4. 遍历
//1.定义基本格式
var person = {"name": "张三", age: 23, 'gender': true};
var ps = [{"name": "张三", "age": 23, "gender": true},
{"name": "李四", "age": 24, "gender": true},
{"name": "王五", "age": 25, "gender": false}];
//获取person对象中所有的键和值
//for in 循环
/* for(var key in person){
//这样的方式获取不行。因为相当于 person."name"
//alert(key + ":" + person.key);
alert(key+":"+person[key]);
}*/
//获取ps中的所有值
for (var i = 0; i < ps.length; i++) {
var p = ps[i];
for(var key in p){
alert(key+":"+p[key]);
}
}
转换
* JSON解析器:
* 常见的解析器:Jsonlib,Gson,fastjson,jackson
1. JSON转为Java对象
1. 导入jackson的相关jar包
2. 创建Jackson核心对象 ObjectMapper
3. 调用ObjectMapper的相关方法进行转换
1. readValue(json字符串数据,Class)
2. Java对象转换JSON
1. 使用步骤:
1. 导入jackson的相关jar包
2. 创建Jackson核心对象 ObjectMapper
3. 调用ObjectMapper的相关方法进行转换
1. 转换方法:
* writeValue(参数1,obj):
参数1:
File:将obj对象转换为JSON字符串,并保存到指定的文件中
Writer:将obj对象转换为JSON字符串,并将json数据填充到字符输出流中
OutputStream:将obj对象转换为JSON字符串,并将json数据填充到字节输出流中
* writeValueAsString(obj):将对象转为json字符串
2. 注解:
1. @JsonIgnore:排除属性。
2. @JsonFormat:属性值得格式化
* @JsonFormat(pattern = "yyyy-MM-dd")
3. 复杂java对象转换
1. List:数组
2. Map:对象格式一致
案例
* 校验用户名是否存在
1. 服务器响应的数据,在客户端使用时,要想当做json数据格式使用。有两种解决方案:
1. $.get(type):将最后一个参数type指定为"json"
2. 在服务器端设置MIME类型
response.setContentType("application/json;charset=utf-8");
1. 服务器响应的数据,在客户端使用时,要想当做json数据格式使用。有两种解决方案:
1. $.get(type):将最后一个参数type指定为"json"
2. 在服务器端设置MIME类型
response.setContentType("application/json;charset=utf-8");
综合案例
Nginx
概念
Nginx 是一款高性能的 http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。由俄罗斯的程序设计师伊戈尔·西索夫(Igor Sysoev)所开发,官方测试 nginx 能够支支撑 5 万并发链接,并且 cpu、内存等资源消耗却非常低,运行非常稳定。
应用场景
1、http 服务器。Nginx 是一个 http 服务可以独立提供 http 服务。可以做网页静态服务器。
2、虚拟主机。可以实现在一台服务器虚拟出多个网站。例如个人网站使用的虚拟主机。
3、反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用 nginx 做反向代理。并且多台服务器可以平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置的情况。
2、虚拟主机。可以实现在一台服务器虚拟出多个网站。例如个人网站使用的虚拟主机。
3、反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用 nginx 做反向代理。并且多台服务器可以平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置的情况。
安装
环境准备
(1)需要安装 gcc 的环境【此步省略】
yum install gcc-c++
(2)第三方的开发包。
PCRE
PCRE(Perl Compatible Regular Expressions)是一个 Perl 库,包括 perl 兼容的正则表达式库。nginx 的 http 模块使用 pcre 来解析 正则表达式,所以需要在 linux 上安装 pcre 库。
yum install -y pcre pcre-devel
注:pcre-devel 是使用 pcre 开发的一个二次开发库。nginx 也需要此库。
zlib
zlib 库提供了很多种压缩和解压缩的方式,nginx 使用 zlib 对 http 包的内容进行 gzip,所以需要在 linux 上安装 zlib 库。
yum install -y zlib zlib-devel
、OpenSSL
OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及 SSL 协议,并提供丰富的应用程序供测试或其它目的使用。nginx 不仅支持 http 协议,还支持 https(即在 ssl 协议上传输 http),所以需要在 linux安装 openssl 库。
yum install -y openssl openssl-devel
yum install gcc-c++
(2)第三方的开发包。
PCRE
PCRE(Perl Compatible Regular Expressions)是一个 Perl 库,包括 perl 兼容的正则表达式库。nginx 的 http 模块使用 pcre 来解析 正则表达式,所以需要在 linux 上安装 pcre 库。
yum install -y pcre pcre-devel
注:pcre-devel 是使用 pcre 开发的一个二次开发库。nginx 也需要此库。
zlib
zlib 库提供了很多种压缩和解压缩的方式,nginx 使用 zlib 对 http 包的内容进行 gzip,所以需要在 linux 上安装 zlib 库。
yum install -y zlib zlib-devel
、OpenSSL
OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及 SSL 协议,并提供丰富的应用程序供测试或其它目的使用。nginx 不仅支持 http 协议,还支持 https(即在 ssl 协议上传输 http),所以需要在 linux安装 openssl 库。
yum install -y openssl openssl-devel
下载
官方网站下载 nginx:http://nginx.org/
安装步骤
第一步:把 nginx 的源码包nginx-1.8.0.tar.gz上传到 linux 系统
第二步:解压缩
tar zxvf nginx-1.8.0.tar.gz
第三步:进入nginx-1.8.0目录 使用 configure 命令创建一 makeFile 文件。
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi
执行后可以看到Makefile文件
第四步:编译
make
第五步:安装
make install
第二步:解压缩
tar zxvf nginx-1.8.0.tar.gz
第三步:进入nginx-1.8.0目录 使用 configure 命令创建一 makeFile 文件。
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi
执行后可以看到Makefile文件
第四步:编译
make
第五步:安装
make install
configure参数
./configure \
--prefix=/usr \ 指向安装目录
--sbin-path=/usr/sbin/nginx \ 指向(执行)程序文件(nginx)
--conf-path=/etc/nginx/nginx.conf \ 指向配置文件
--error-log-path=/var/log/nginx/error.log \ 指向log
--http-log-path=/var/log/nginx/access.log \ 指向http-log
--pid-path=/var/run/nginx/nginx.pid \ 指向pid
--lock-path=/var/lock/nginx.lock \ (安装文件锁定,防止安装文件被别人利用,或自己误操作。)
--user=nginx \
--group=nginx \
--with-http_ssl_module \ 启用ngx_http_ssl_module支持(使支持https请求,需已安装openssl)
--with-http_flv_module \ 启用ngx_http_flv_module支持(提供寻求内存使用基于时间的偏移量文件)
--with-http_stub_status_module \ 启用ngx_http_stub_status_module支持(获取nginx自上次启动以来的工作状态)
--with-http_gzip_static_module \ 启用ngx_http_gzip_static_module支持(在线实时压缩输出数据流)
--http-client-body-temp-path=/var/tmp/nginx/client/ \ 设定http客户端请求临时文件路径
--http-proxy-temp-path=/var/tmp/nginx/proxy/ \ 设定http代理临时文件路径
--http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ \ 设定http fastcgi临时文件路径
--http-uwsgi-temp-path=/var/tmp/nginx/uwsgi \ 设定http uwsgi临时文件路径
--http-scgi-temp-path=/var/tmp/nginx/scgi \ 设定http scgi临时文件路径
--with-pcre 启用pcre库
./configure \
--prefix=/usr \ 指向安装目录
--sbin-path=/usr/sbin/nginx \ 指向(执行)程序文件(nginx)
--conf-path=/etc/nginx/nginx.conf \ 指向配置文件
--error-log-path=/var/log/nginx/error.log \ 指向log
--http-log-path=/var/log/nginx/access.log \ 指向http-log
--pid-path=/var/run/nginx/nginx.pid \ 指向pid
--lock-path=/var/lock/nginx.lock \ (安装文件锁定,防止安装文件被别人利用,或自己误操作。)
--user=nginx \
--group=nginx \
--with-http_ssl_module \ 启用ngx_http_ssl_module支持(使支持https请求,需已安装openssl)
--with-http_flv_module \ 启用ngx_http_flv_module支持(提供寻求内存使用基于时间的偏移量文件)
--with-http_stub_status_module \ 启用ngx_http_stub_status_module支持(获取nginx自上次启动以来的工作状态)
--with-http_gzip_static_module \ 启用ngx_http_gzip_static_module支持(在线实时压缩输出数据流)
--http-client-body-temp-path=/var/tmp/nginx/client/ \ 设定http客户端请求临时文件路径
--http-proxy-temp-path=/var/tmp/nginx/proxy/ \ 设定http代理临时文件路径
--http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ \ 设定http fastcgi临时文件路径
--http-uwsgi-temp-path=/var/tmp/nginx/uwsgi \ 设定http uwsgi临时文件路径
--http-scgi-temp-path=/var/tmp/nginx/scgi \ 设定http scgi临时文件路径
--with-pcre 启用pcre库
启动与访问
进入到Nginx目录下的sbin目录
cd /usr/local/ngiux/sbin
输入命令启动Nginx
./nginx
启动后查看进程
ps aux|grep nginx
cd /usr/local/ngiux/sbin
输入命令启动Nginx
./nginx
启动后查看进程
ps aux|grep nginx
关闭
关闭 nginx:
./nginx -s stop
或者
./nginx -s quit
重启 nginx:
1、先关闭后启动。
2、刷新配置文件:
./nginx -s reload
./nginx -s stop
或者
./nginx -s quit
重启 nginx:
1、先关闭后启动。
2、刷新配置文件:
./nginx -s reload
静态网站部署
将/资料/静态页面/index目录下的所有内容 上传到服务器的/usr/local/nginx/html下即可访问
配置虚拟主机
端口绑定
(1)上传静态网站:
将/资料/静态页面/index目录上传至 /usr/local/nginx/index下
将/资料/静态页面/regist目录上传至 /usr/local/nginx/regist下
(2)修改Nginx 的配置文件:/usr/local/nginx/conf/nginx.conf
server {
listen 81; # 监听的端口
server_name localhost; # 域名或ip
location / { # 访问路径配置
root index;# 根目录
index index.html index.htm; # 默认首页
}
error_page 500 502 503 504 /50x.html; # 错误页面
location = /50x.html {
root html;
}
}
server {
listen 82; # 监听的端口
server_name localhost; # 域名或ip
location / { # 访问路径配置
root regist;# 根目录
index regist.html; # 默认首页
}
error_page 500 502 503 504 /50x.html; # 错误页面
location = /50x.html {
root html;
}
}
(3)访问测试:
地址栏输入http://192.168.177.129/:81 可以看到首页面
地址栏输入http://192.168.177.129/:82 可以看到注册页面
将/资料/静态页面/index目录上传至 /usr/local/nginx/index下
将/资料/静态页面/regist目录上传至 /usr/local/nginx/regist下
(2)修改Nginx 的配置文件:/usr/local/nginx/conf/nginx.conf
server {
listen 81; # 监听的端口
server_name localhost; # 域名或ip
location / { # 访问路径配置
root index;# 根目录
index index.html index.htm; # 默认首页
}
error_page 500 502 503 504 /50x.html; # 错误页面
location = /50x.html {
root html;
}
}
server {
listen 82; # 监听的端口
server_name localhost; # 域名或ip
location / { # 访问路径配置
root regist;# 根目录
index regist.html; # 默认首页
}
error_page 500 502 503 504 /50x.html; # 错误页面
location = /50x.html {
root html;
}
}
(3)访问测试:
地址栏输入http://192.168.177.129/:81 可以看到首页面
地址栏输入http://192.168.177.129/:82 可以看到注册页面
域名绑定
什么是域名
域名(Domain Name),是由一串用“点”分隔的字符组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置,地理上的域名,指代有行政自主权的一个地方区域)。域名是一个IP地址上有“面具” 。域名的目的是便于记忆和沟通的一组服务器的地址(网站,电子邮件,FTP等)。域名作为力所能及难忘的互联网参与者的名称。域名按域名系统(DNS)的规则流程组成。在DNS中注册的任何名称都是域名。域名用于各种网络环境和应用程序特定的命名和寻址目的。通常,域名表示互联网协议(IP)资源,例如用于访问因特网的个人计算机,托管网站的服务器计算机,或网站本身或通过因特网传送的任何其他服务。世界上第一个注册的域名是在1985年1月注册的。
域名和IP绑定
一个域名对应一个 ip 地址,一个 ip 地址可以被多个域名绑定。
本地测试可以修改 hosts 文件(C:\Windows\System32\drivers\etc)
可以配置域名和 ip 的映射关系,如果 hosts 文件中配置了域名和 ip 的对应关系,不需要走dns 服务器。
192.168.177.129 www.hmtravel.com
192.168.177.129 regist.hmtravel.com
做好域名指向后,修改nginx配置文件
server {
listen 80;
server_name www.hmtravel.com;
location / {
root cart;
index cart.html;
}
}
server {
listen 80;
server_name regist.hmtravel.com;
location / {
root search;
index search.html;
}
}
执行以下命令,刷新配置
[root@localhost sbin]# ./nginx -s reload
本地测试可以修改 hosts 文件(C:\Windows\System32\drivers\etc)
可以配置域名和 ip 的映射关系,如果 hosts 文件中配置了域名和 ip 的对应关系,不需要走dns 服务器。
192.168.177.129 www.hmtravel.com
192.168.177.129 regist.hmtravel.com
做好域名指向后,修改nginx配置文件
server {
listen 80;
server_name www.hmtravel.com;
location / {
root cart;
index cart.html;
}
}
server {
listen 80;
server_name regist.hmtravel.com;
location / {
root search;
index search.html;
}
}
执行以下命令,刷新配置
[root@localhost sbin]# ./nginx -s reload
Nginx反向代理与负载均衡
反向代理
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,
然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet
上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet
上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
正向代理图
反向代理图
配置反向代理-准备工作
(1) 将travel案例部署到tomcat中(ROOT目录),上传到服务器。
(2)启动TOMCAT,输入网址http://192.168.177.129:8080 可以看到网站首页
(2)启动TOMCAT,输入网址http://192.168.177.129:8080 可以看到网站首页
(1)在Nginx主机修改 Nginx配置文件
upstream tomcat-travel{
server 192.168.177.129:8080;
}
server {
listen 80; # 监听的端口
server_name www.hmtravel.com; # 域名或ip
location / { # 访问路径配置
# root index;# 根目录
proxy_pass http://tomcat-travel;
index index.html index.htm; # 默认首页
}
}
(2)重新启动Nginx 然后用浏览器测试:http://www.hmtravel.com (此域名须配置域名指向)
upstream tomcat-travel{
server 192.168.177.129:8080;
}
server {
listen 80; # 监听的端口
server_name www.hmtravel.com; # 域名或ip
location / { # 访问路径配置
# root index;# 根目录
proxy_pass http://tomcat-travel;
index index.html index.htm; # 默认首页
}
}
(2)重新启动Nginx 然后用浏览器测试:http://www.hmtravel.com (此域名须配置域名指向)
负载均衡
负载均衡 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、
增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡,英文名称为Load Balance,其意思就是分摊到多个操作单元上进行执行,
例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。
增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡,英文名称为Load Balance,其意思就是分摊到多个操作单元上进行执行,
例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。
准备工作
(1)将刚才的存放工程的tomcat复制三份,修改端口分别为8080 ,8081,8082 。
(2)分别启动这三个tomcat服务。
(3)为了能够区分是访问哪个服务器的网站,可以在首页标题加上标记以便区分。
(2)分别启动这三个tomcat服务。
(3)为了能够区分是访问哪个服务器的网站,可以在首页标题加上标记以便区分。
配置负载均衡
修改 Nginx配置文件:
upstream tomcat-travel {
server 192.168.177.129:8080;
server 192.168.177.129:8081;
server 192.168.177.129:8082;
}
server {
listen 80; # 监听的端口
server_name www.hmtravel.com; # 域名或ip
location / { # 访问路径配置
# root index;# 根目录
proxy_pass http://tomcat-travel;
index index.html index.htm; # 默认首页
}
error_page 500 502 503 504 /50x.html; # 错误页面
location = /50x.html {
root html;
}
}
地址栏输入http:// www.hmtravel.com / 刷新观察每个网页的标题,看是否不同。
经过测试,三台服务器出现的概率各为33.3333333%,交替显示。
如果其中一台服务器性能比较好,想让其承担更多的压力,可以设置权重。
比如想让NO.1出现次数是其它服务器的2倍,则修改配置如下:
upstream tomcat-travel {
server 192.168.177.129:8080;
server 192.168.177.129:8081 weight=2;
server 192.168.177.129:8082;
}
经过测试,每刷新四次,有两次是8081
upstream tomcat-travel {
server 192.168.177.129:8080;
server 192.168.177.129:8081;
server 192.168.177.129:8082;
}
server {
listen 80; # 监听的端口
server_name www.hmtravel.com; # 域名或ip
location / { # 访问路径配置
# root index;# 根目录
proxy_pass http://tomcat-travel;
index index.html index.htm; # 默认首页
}
error_page 500 502 503 504 /50x.html; # 错误页面
location = /50x.html {
root html;
}
}
地址栏输入http:// www.hmtravel.com / 刷新观察每个网页的标题,看是否不同。
经过测试,三台服务器出现的概率各为33.3333333%,交替显示。
如果其中一台服务器性能比较好,想让其承担更多的压力,可以设置权重。
比如想让NO.1出现次数是其它服务器的2倍,则修改配置如下:
upstream tomcat-travel {
server 192.168.177.129:8080;
server 192.168.177.129:8081 weight=2;
server 192.168.177.129:8082;
}
经过测试,每刷新四次,有两次是8081
Other technology
正则
概念
什么是正则表达式?
正则表达式是⼀组由字⺟和符号组成的特殊⽂本,
它可以⽤来从⽂本中找出满⾜你想要的格式的句⼦。
它可以⽤来从⽂本中找出满⾜你想要的格式的句⼦。
基本匹配
字符之间的匹配
元字符
概念
. 句号匹配任意单个字符除了换⾏符。
[ ] 字符种类。匹配⽅括号内的任意字符。
[^ ] 否定的字符种类。匹配除了⽅括号⾥的任意字符
* 匹配>=0个重复的在*号之前的字符。
[ ] 字符种类。匹配⽅括号内的任意字符。
[^ ] 否定的字符种类。匹配除了⽅括号⾥的任意字符
* 匹配>=0个重复的在*号之前的字符。
|+| 匹配>=1个重复的+号前的字符。
|?| 标记?之前的字符为可选.|
|{n,m}| 匹配num个⼤括号之前的字符或字符集 (n <= num <= m).|
|(xyz)| 字符集,匹配与 xyz 完全相等的字符串.|
||| 或运算符,匹配符号前或后的字符.|
|\| 转义字符,⽤于匹配⼀些保留的字符 [ ] ( ) { } . * + ? ^ $ \ | |
|^| 从开始⾏开始匹配.|
|$| 从末端开始匹配.|
|?| 标记?之前的字符为可选.|
|{n,m}| 匹配num个⼤括号之前的字符或字符集 (n <= num <= m).|
|(xyz)| 字符集,匹配与 xyz 完全相等的字符串.|
||| 或运算符,匹配符号前或后的字符.|
|\| 转义字符,⽤于匹配⼀些保留的字符 [ ] ( ) { } . * + ? ^ $ \ | |
|^| 从开始⾏开始匹配.|
|$| 从末端开始匹配.|
点运算
. 是元字符中最简单的例⼦。
. 匹配任意单个字符,但不匹配换⾏符。
例如,表达式.ar 匹配⼀个任意字符后⾯跟着是a 和r 的字符串。
".ar" => The car parked in the garage.
. 匹配任意单个字符,但不匹配换⾏符。
例如,表达式.ar 匹配⼀个任意字符后⾯跟着是a 和r 的字符串。
".ar" => The car parked in the garage.
字符类
字符集也叫做字符类。
⽅括号⽤来指定⼀个字符集。
在⽅括号中使⽤连字符来指定字符集的范围。
⽅括号⽤来指定⼀个字符集。
在⽅括号中使⽤连字符来指定字符集的范围。
表达式 匹配 ar. 字符串
"ar[.]" => A garage is a good place to park a car.
"ar[.]" => A garage is a good place to park a car.
否定字符集
表达式[^c]ar 匹配⼀个后⾯跟着ar 的除了c 的任意字符。
"[^c]ar" => The car parked in the garage.
"[^c]ar" => The car parked in the garage.
重复次数
后⾯跟着元字符 + , * or ? 的,⽤来指定匹配⼦模式的次数。
这些元字符在不同的情况下有着不同的意思。
这些元字符在不同的情况下有着不同的意思。
* 号
* 号匹配 在* 之前的字符出现⼤于等于0 次。
例如,表达式字符串。
匹配0或更多个以a开头的字符。表达式[a-z]* 匹配⼀个⾏中所有以⼩写字⺟开头的
"[a-z]*" => The car parked in the garage #21.
例如,表达式字符串。
匹配0或更多个以a开头的字符。表达式[a-z]* 匹配⼀个⾏中所有以⼩写字⺟开头的
"[a-z]*" => The car parked in the garage #21.
+ 号
+号匹配+ 号之前的字符出现 >=1 次。
例如表达式c.+t 匹配以⾸字⺟c 开头以t 结尾,中间跟着⾄少⼀个字符的字符串。
例如表达式c.+t 匹配以⾸字⺟c 开头以t 结尾,中间跟着⾄少⼀个字符的字符串。
? 号
在正则表达式中元字符例如,表达式
标记在?符号前⾯的字符为可选,即出现 0 或 1 次。
标记在?符号前⾯的字符为可选,即出现 0 或 1 次。
{}
在正则表达式中 {} 是⼀个量词,常⽤来限定⼀个或⼀组字符可以重复出现的次数。
例如, 表达式
匹配最少 2 位最多 3 位 0~9 的数字。
例如, 表达式
匹配最少 2 位最多 3 位 0~9 的数字。
(......)特征标群
特征标群是⼀组写在 中的⼦模式。(...) 中包含的内容将会被看成⼀个整体,
和数学中⼩括号( )的作⽤相同。例如, 表达式
匹配连续出现 0 或更多个 ab 。如果没有使,那么表
达式 将匹配连续出现 0 或更多个 b 。再⽐如之前说的
是⽤来表示前⾯⼀个字符出现指定次数。但如果在
前加上特征标群则表示整个标群内的字符重复 N 次。
和数学中⼩括号( )的作⽤相同。例如, 表达式
匹配连续出现 0 或更多个 ab 。如果没有使,那么表
达式 将匹配连续出现 0 或更多个 b 。再⽐如之前说的
是⽤来表示前⾯⼀个字符出现指定次数。但如果在
前加上特征标群则表示整个标群内的字符重复 N 次。
| 运算符
或运算符就表示或,⽤作判断条件。
例如 匹配 或 car 。
例如 匹配 或 car 。
转码特殊字符
反斜线 在表达式中⽤于转码紧跟其后的字符。⽤于指定 { } [ ] / \ + * . $ ^ | ? 这些特殊字
符。如果想要匹配这些特殊字符则要在其前⾯加上反斜线 \ 。
符。如果想要匹配这些特殊字符则要在其前⾯加上反斜线 \ 。
锚点
^ ⽤来检查匹配的字符串是否在所匹配字符串的开头。
例如,在中使⽤表达式
会得到结果 a 。但如果使⽤
将匹配不到任何结果。因为在字符串
中并不是以 开头。
例如,在中使⽤表达式
会得到结果 a 。但如果使⽤
将匹配不到任何结果。因为在字符串
中并不是以 开头。
使用
整数或者小数
^[0-9]+\.{0,1}[0-9]{0,2}$
只能输入数字
^[0-9]*$
只能输入n位的数字
^\d{n}$
只能输入至少n位的数字
^\d{n,}$
只能输入m~n位的数字
^\d{m,n}$
只能输入零和非零开头的数字
^(0|[1-9][0-9]*)$
只能输入有两位小数的正实数
^[0-9]+(.[0-9]{2})?$
只能输入有1~3位小数的正实数
^[0-9]+(.[0-9]{1,3})?$
只能输入非零的正整数
^\+?[1-9][0-9]*$
只能输入非零的负整数
^\-[1-9][]0-9*$
只能输入长度为3的字符
^.{3}$
只能输入由26个英文字母组成的字符串
^[A-Za-z]+$
只能输入由26个大写英文字母组成的字符串
^[A-Z]+$
只能输入由26个小写英文字母组成的字符串
^[a-z]+$
只能输入由数字和26个英文字母组成的字符串
^[A-Za-z0-9]+$
只能输入由数字、26个英文字母或者下划线组成的字符串
^\w+$
验证用户密码:
^[a-zA-Z]\w{5,17}$
注:正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。
验证是否含有^%&',;=?$\等字符
验证是否含有^%&',;=?$\等字符
[^%&',;=?$\x22]+
只能输入汉字
^[\u4e00-\u9fa5]{0,}$
验证Email地址
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
验证Internet URL
^[http|https]://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
验证电话号码
^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
验证身份证号(15位或18位数字)
^\d{15}|\d{18}$
验证一年的12个月
^(0?[1-9]|1[0-2])$
验证一个月的31天
^((0?[1-9])|((1|2)[0-9])|30|31)$
匹配中文字符的正则表达式
[\u4e00-\u9fa5]
匹配双字节字符(包括汉字在内)
[^\x00-\xff]
匹配空行的正则表达式
\n[\s| ]*\r
匹配html标签的正则表达式
<(.*)>(.*)<\/(.*)>|<(.*)\/>
匹配首尾空格的正则表达式
(^\s*)|(\s*$)
匹配Email地址的正则表达式
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
匹配HTML标记的正则表达式
<(\S*?)[^>]*>.*?|<.*? />
匹配首尾空白字符的正则表达式
^\s*|\s*$
匹配Email地址的正则表达式
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
匹配网址URL的正则表达式
[a-zA-z]+://[^\s]*
匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线)
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
匹配国内电话号码
\d{3}-\d{8}|\d{4}-\d{7}
匹配腾讯QQ号
[1-9][0-9]{4,}
匹配中国邮政编码
[1-9]\d{5}(?!\d)
匹配身份证
\d{15}|\d{18}
匹配ip地址
\d+\.\d+\.\d+\.\d+
匹配特定数字
^[1-9]\d*$ //匹配正整数
^-[1-9]\d*$ //匹配负整数
^-?[1-9]\d*$ //匹配整数
^[1-9]\d*|0$ //匹配非负整数(正整数 + 0)
^-[1-9]\d*|0$ //匹配非正整数(负整数 + 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ //匹配正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ //匹配负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$ //匹配浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$ //匹配非负浮点数(正浮点数 + 0)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$//匹配非正浮点数(负浮点数 + 0)s
^-[1-9]\d*$ //匹配负整数
^-?[1-9]\d*$ //匹配整数
^[1-9]\d*|0$ //匹配非负整数(正整数 + 0)
^-[1-9]\d*|0$ //匹配非正整数(负整数 + 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ //匹配正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ //匹配负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$ //匹配浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$ //匹配非负浮点数(正浮点数 + 0)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$//匹配非正浮点数(负浮点数 + 0)s
匹配特定字符串
^[A-Za-z]+$//匹配由26个英文字母组成的字符串
^[A-Z]+$//匹配由26个英文字母的大写组成的字符串
^[a-z]+$//匹配由26个英文字母的小写组成的字符串
^[A-Za-z0-9]+$//匹配由数字和26个英文字母组成的字符串
^\w+$//匹配由数字、26个英文字母或者下划线组成的字符串
^[A-Z]+$//匹配由26个英文字母的大写组成的字符串
^[a-z]+$//匹配由26个英文字母的小写组成的字符串
^[A-Za-z0-9]+$//匹配由数字和26个英文字母组成的字符串
^\w+$//匹配由数字、26个英文字母或者下划线组成的字符串
校验密码强度
包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间。
包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间。
^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
校验字符串
中文
^[\\u4e00-\\u9fa5]{0,}$
由数字、26个英文字母或下划线组成的字符串
^\\w+$
校验E-Mail 地址
[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?
校验身份证号码
15位: ^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$
18位: ^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$
校验日期
“yyyy-mm-dd“ 格式的日期校验,已考虑平闰年。
“yyyy-mm-dd“ 格式的日期校验,已考虑平闰年。
^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$
校验金额
精确到2位小数。
精确到2位小数。
^[0-9]+(.[0-9]{2})?$
校验手机号
下面是国内 13、15、18开头的手机号正则表达式。(可根据目前国内收集号扩展前两位开头号码)
下面是国内 13、15、18开头的手机号正则表达式。(可根据目前国内收集号扩展前两位开头号码)
^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$
判断IE的版本
^.*MSIE [5-8](?:\\.[0-9]+)?(?!.*Trident\\/[5-9]\\.0).*$
校验IP-v4地址
\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b
校验IP-v6地址
(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))
检查URL的前缀
if (!s.match(/^[a-zA-Z]+:\\/\\//))
{
s = 'http://' + s;
}
{
s = 'http://' + s;
}
提取URL链接
^(f|ht){1}(tp|tps):\\/\\/([\\w-]+\\.)+[\\w-]+(\\/[\\w- ./?%&=]*)?
文件路径及扩展名校验
验证windows下文件路径和扩展名(下面的例子中为.txt文件)
验证windows下文件路径和扩展名(下面的例子中为.txt文件)
^([a-zA-Z]\\:|\\\\)\\\\([^\\\\]+\\\\)*[^\\/:*?"<>|]+\\.txt(l)?$
提取网页颜色代码
有时需要抽取网页中的颜色代码,可以使用下面的表达式。
有时需要抽取网页中的颜色代码,可以使用下面的表达式。
^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$
提取网页图片
\\< *[img][^\\>]*[src] *= *[\\"\\']{0,1}([^\\"\\'\\ >]*)
提取页面超链接
(<a\\s*(?!.*\\brel=)[^>]*)(href="https?:\\/\\/)((?!(?:(?:www\\.)?'.implode('|(?:www\\.)?', $follow_list).'))[^"]+)"((?!.*\\brel=)[^>]*)(?:[^>]*)>
查找CSS属性
^\\s*[a-zA-Z\\-]+\\s*[:]{1}\\s[a-zA-Z0-9\\s.#]+[;]{1}
抽取注释
<!--(.*?)-->
匹配HTML标签
<\\/?\\w+((\\s+\\w+(\\s*=\\s*(?:".*?"|'.*?'|[\\^'">\\s]+))?)+\\s*|\\s*)\\/?>
时间正则案例
简单的日期判断(YYYY/MM/DD)
^\d{4}(\-|\/|\.)\d{1,2}\1\d{1,2}$
演化的日期判断(YYYY/MM/DD| YY/MM/DD)
^(^(\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2}$)|(^\d{4}年\d{1,2}月\d{1,2}日$)$
加入闰年的判断的
^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$
Quartz
在线表达式生成地址
https://www.pppet.net/
表达式详解
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year或
Seconds Minutes Hours DayofMonth Month DayofWeek
每一个域可出现的字符如下:
Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
Hours:可出现", - * /"四个字符,有效范围为0-23的整数
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
Year:可出现", - * /"四个字符,有效范围为1970-2099年
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1)*:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。
(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W: 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
举几个例子:
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。
按顺序依次为
秒(0~59)
分钟(0~59)
小时(0~23)
天(月)(0~31,但是你需要考虑你月的天数)
月(0~11)
天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
年份(1970-2099)
其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
有些子表达式能包含一些范围或列表
例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”
“*”字符代表所有可能的值
因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天
“/”字符用来指定数值的增量
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样
“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”
“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,“L”表示一个月的最后一天
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT
如果在“L”前有具体的内容,它就具有其他的含义了
例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /
Seconds Minutes Hours DayofMonth Month DayofWeek Year或
Seconds Minutes Hours DayofMonth Month DayofWeek
每一个域可出现的字符如下:
Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
Hours:可出现", - * /"四个字符,有效范围为0-23的整数
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
Year:可出现", - * /"四个字符,有效范围为1970-2099年
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1)*:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。
(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W: 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
举几个例子:
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。
按顺序依次为
秒(0~59)
分钟(0~59)
小时(0~23)
天(月)(0~31,但是你需要考虑你月的天数)
月(0~11)
天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
年份(1970-2099)
其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
有些子表达式能包含一些范围或列表
例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”
“*”字符代表所有可能的值
因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天
“/”字符用来指定数值的增量
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样
“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”
“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,“L”表示一个月的最后一天
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT
如果在“L”前有具体的内容,它就具有其他的含义了
例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /
表达式范例
Cron表达式范例:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
zookeeper
概念
安装与配置
单机模式
伪分布模式
全分布式
命令操作
数据模型
树形文件系统
每一个节点都被称为: ZNode,每个节点上都会保存自己的数据和节点信息。
节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下
节点可以分为四大类
PERSISTENT 持久化节点
EPHEMERAL 临时节点 :-e
PERSISTENT_SEQUENTIAL 持久化顺序节点 :-s
EPHEMERAL_SEQUENTIAL 临时顺序节点 :-es
服务端常用命令
启动 ZooKeeper 服务: ./zkServer.sh start
查看 ZooKeeper 服务状态: ./zkServer.sh status
停止 ZooKeeper 服务: ./zkServer.sh stop
重启 ZooKeeper 服务: ./zkServer.sh restart
查看 ZooKeeper 服务状态: ./zkServer.sh status
停止 ZooKeeper 服务: ./zkServer.sh stop
重启 ZooKeeper 服务: ./zkServer.sh restart
客户端常用命令
连接ZooKeeper服务端
./zkCli.sh –server ip:port
断开连接 quit
查看命令帮助 help
显示指定目录下节点 ls
创建节点
create /节点path value
获取节点值
get /节点path
设置节点值
set /节点path value
删除单个节点
delete /节点path
删除带有子节点的节点
deleteall /节点path
czxid:节点被创建的事务ID
ctime: 创建时间
mzxid: 最后一次被更新的事务ID
mtime: 修改时间
pzxid:子节点列表最后一次被更新的事务ID
cversion:子节点的版本号
ctime: 创建时间
mzxid: 最后一次被更新的事务ID
mtime: 修改时间
pzxid:子节点列表最后一次被更新的事务ID
cversion:子节点的版本号
dataversion:数据版本号
aclversion:权限版本号
ephemeralOwner:用于临时节点,代表临时节点的事务ID,如果为持久节点则为0
dataLength:节点存储的数据的长度
numChildren:当前节点的子节点个数
aclversion:权限版本号
ephemeralOwner:用于临时节点,代表临时节点的事务ID,如果为持久节点则为0
dataLength:节点存储的数据的长度
numChildren:当前节点的子节点个数
JavaAPI操作
Curator
Curator 是 Apache ZooKeeper 的Java客户端库。
常见的ZooKeeper Java API :原生Java APIZkClientCuratorCurator 项目的目标是简化 ZooKeeper 客户端的使用。
Curator 最初是 Netfix 研发的,后来捐献了 Apache 基金会,目前是 Apache 的顶级项目。
官网:http://curator.apache.org/
常见的ZooKeeper Java API :原生Java APIZkClientCuratorCurator 项目的目标是简化 ZooKeeper 客户端的使用。
Curator 最初是 Netfix 研发的,后来捐献了 Apache 基金会,目前是 Apache 的顶级项目。
官网:http://curator.apache.org/
API操作
建立连接
/**
* 建立连接
*/
@Before
public void testConnect() {
/*
*
* @param connectString 连接字符串。zk server 地址和端口 "192.168.149.135:2181,192.168.149.136:2181"
* @param sessionTimeoutMs 会话超时时间 单位ms
* @param connectionTimeoutMs 连接超时时间 单位ms
* @param retryPolicy 重试策略
*/
/* //重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
//1.第一种方式
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.149.135:2181",
60 * 1000, 15 * 1000, retryPolicy);*/
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//2.第二种方式
//CuratorFrameworkFactory.builder();
client = CuratorFrameworkFactory.builder()
.connectString("192.168.149.135:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.namespace("itheima")
.build();
//开启连接
client.start();
}
* 建立连接
*/
@Before
public void testConnect() {
/*
*
* @param connectString 连接字符串。zk server 地址和端口 "192.168.149.135:2181,192.168.149.136:2181"
* @param sessionTimeoutMs 会话超时时间 单位ms
* @param connectionTimeoutMs 连接超时时间 单位ms
* @param retryPolicy 重试策略
*/
/* //重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
//1.第一种方式
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.149.135:2181",
60 * 1000, 15 * 1000, retryPolicy);*/
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//2.第二种方式
//CuratorFrameworkFactory.builder();
client = CuratorFrameworkFactory.builder()
.connectString("192.168.149.135:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.namespace("itheima")
.build();
//开启连接
client.start();
}
添加节点
@Test
public void testCreate() throws Exception {
//2. 创建节点 带有数据
//如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
String path = client.create().forPath("/app2", "hehe".getBytes());
System.out.println(path);
}
@Test
public void testCreate2() throws Exception {
//1. 基本创建
//如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
String path = client.create().forPath("/app1");
System.out.println(path);
}
@Test
public void testCreate3() throws Exception {
//3. 设置节点的类型
//默认类型:持久化
String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
System.out.println(path);
}
@Test
public void testCreate4() throws Exception {
//4. 创建多级节点 /app1/p1
//creatingParentsIfNeeded():如果父节点不存在,则创建父节点
String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1");
System.out.println(path);
}
public void testCreate() throws Exception {
//2. 创建节点 带有数据
//如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
String path = client.create().forPath("/app2", "hehe".getBytes());
System.out.println(path);
}
@Test
public void testCreate2() throws Exception {
//1. 基本创建
//如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
String path = client.create().forPath("/app1");
System.out.println(path);
}
@Test
public void testCreate3() throws Exception {
//3. 设置节点的类型
//默认类型:持久化
String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
System.out.println(path);
}
@Test
public void testCreate4() throws Exception {
//4. 创建多级节点 /app1/p1
//creatingParentsIfNeeded():如果父节点不存在,则创建父节点
String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1");
System.out.println(path);
}
删除节点
/**
* 删除节点: delete deleteall
* 1. 删除单个节点:delete().forPath("/app1");
* 2. 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1");
* 3. 必须成功的删除:为了防止网络抖动。本质就是重试。 client.delete().guaranteed().forPath("/app2");
* 4. 回调:inBackground
* @throws Exception
*/
@Test
public void testDelete() throws Exception {
// 1. 删除单个节点
client.delete().forPath("/app1");
}
@Test
public void testDelete2() throws Exception {
//2. 删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app4");
}
@Test
public void testDelete3() throws Exception {
//3. 必须成功的删除
client.delete().guaranteed().forPath("/app2");
}
@Test
public void testDelete4() throws Exception {
//4. 回调
client.delete().guaranteed().inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("我被删除了~");
System.out.println(event);
}
}).forPath("/app1");
}
* 删除节点: delete deleteall
* 1. 删除单个节点:delete().forPath("/app1");
* 2. 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1");
* 3. 必须成功的删除:为了防止网络抖动。本质就是重试。 client.delete().guaranteed().forPath("/app2");
* 4. 回调:inBackground
* @throws Exception
*/
@Test
public void testDelete() throws Exception {
// 1. 删除单个节点
client.delete().forPath("/app1");
}
@Test
public void testDelete2() throws Exception {
//2. 删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app4");
}
@Test
public void testDelete3() throws Exception {
//3. 必须成功的删除
client.delete().guaranteed().forPath("/app2");
}
@Test
public void testDelete4() throws Exception {
//4. 回调
client.delete().guaranteed().inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("我被删除了~");
System.out.println(event);
}
}).forPath("/app1");
}
修改节点
/**
* 修改数据
* 1. 基本修改数据:setData().forPath()
* 2. 根据版本修改: setData().withVersion().forPath()
* * version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。
*
* @throws Exception
*/
@Test
public void testSet() throws Exception {
client.setData().forPath("/app1", "itcast".getBytes());
}
@Test
public void testSetForVersion() throws Exception {
Stat status = new Stat();
//3. 查询节点状态信息:ls -s
client.getData().storingStatIn(status).forPath("/app1");
int version = status.getVersion();//查询出来的 3
System.out.println(version);
client.setData().withVersion(version).forPath("/app1", "hehe".getBytes());
}
* 修改数据
* 1. 基本修改数据:setData().forPath()
* 2. 根据版本修改: setData().withVersion().forPath()
* * version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。
*
* @throws Exception
*/
@Test
public void testSet() throws Exception {
client.setData().forPath("/app1", "itcast".getBytes());
}
@Test
public void testSetForVersion() throws Exception {
Stat status = new Stat();
//3. 查询节点状态信息:ls -s
client.getData().storingStatIn(status).forPath("/app1");
int version = status.getVersion();//查询出来的 3
System.out.println(version);
client.setData().withVersion(version).forPath("/app1", "hehe".getBytes());
}
查询节点
/**
* 查询节点:
* 1. 查询数据:get: getData().forPath()
* 2. 查询子节点: ls: getChildren().forPath()
* 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()
*/
@Test
public void testGet1() throws Exception {
//1. 查询数据:get
byte[] data = client.getData().forPath("/app1");
System.out.println(new String(data));
}
@Test
public void testGet2() throws Exception {
* 查询节点:
* 1. 查询数据:get: getData().forPath()
* 2. 查询子节点: ls: getChildren().forPath()
* 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()
*/
@Test
public void testGet1() throws Exception {
//1. 查询数据:get
byte[] data = client.getData().forPath("/app1");
System.out.println(new String(data));
}
@Test
public void testGet2() throws Exception {
// 2. 查询子节点: ls
List<String> path = client.getChildren().forPath("/");
System.out.println(path);
}
@Test
public void testGet3() throws Exception {
Stat status = new Stat();
System.out.println(status);
//3. 查询节点状态信息:ls -s
client.getData().storingStatIn(status).forPath("/app1");
System.out.println(status);
}
List<String> path = client.getChildren().forPath("/");
System.out.println(path);
}
@Test
public void testGet3() throws Exception {
Stat status = new Stat();
System.out.println(status);
//3. 查询节点状态信息:ls -s
client.getData().storingStatIn(status).forPath("/app1");
System.out.println(status);
}
Watch事件监听
/**
* 演示 NodeCache:给指定一个节点注册监听器
*/
@Test
public void testNodeCache() throws Exception {
//1. 创建NodeCache对象
final NodeCache nodeCache = new NodeCache(client,"/app1");
//2. 注册监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变化了~");
//获取修改节点后的数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
//3. 开启监听.如果设置为true,则开启监听是,加载缓冲数据
nodeCache.start(true);
while (true){
}
}
* 演示 NodeCache:给指定一个节点注册监听器
*/
@Test
public void testNodeCache() throws Exception {
//1. 创建NodeCache对象
final NodeCache nodeCache = new NodeCache(client,"/app1");
//2. 注册监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变化了~");
//获取修改节点后的数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
//3. 开启监听.如果设置为true,则开启监听是,加载缓冲数据
nodeCache.start(true);
while (true){
}
}
/**
* 演示 PathChildrenCache:监听某个节点的所有子节点们
*/
@Test
public void testPathChildrenCache() throws Exception {
//1.创建监听对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true);
//2. 绑定监听器
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
System.out.println("子节点变化了~");
System.out.println(event);
//监听子节点的数据变更,并且拿到变更后的数据
//1.获取类型
PathChildrenCacheEvent.Type type = event.getType();
//2.判断类型是否是update
if(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
System.out.println("数据变了!!!");
byte[] data = event.getData().getData();
System.out.println(new String(data));
}
}
});
//3. 开启
pathChildrenCache.start();
while (true){
}
}
* 演示 PathChildrenCache:监听某个节点的所有子节点们
*/
@Test
public void testPathChildrenCache() throws Exception {
//1.创建监听对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true);
//2. 绑定监听器
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
System.out.println("子节点变化了~");
System.out.println(event);
//监听子节点的数据变更,并且拿到变更后的数据
//1.获取类型
PathChildrenCacheEvent.Type type = event.getType();
//2.判断类型是否是update
if(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
System.out.println("数据变了!!!");
byte[] data = event.getData().getData();
System.out.println(new String(data));
}
}
});
//3. 开启
pathChildrenCache.start();
while (true){
}
}
/**
* 演示 TreeCache:监听某个节点自己和所有子节点们
*/
@Test
public void testTreeCache() throws Exception {
//1. 创建监听器
TreeCache treeCache = new TreeCache(client,"/app2");
//2. 注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
System.out.println("节点变化了");
System.out.println(event);
}
});
//3. 开启
treeCache.start();
while (true){
}
}
* 演示 TreeCache:监听某个节点自己和所有子节点们
*/
@Test
public void testTreeCache() throws Exception {
//1. 创建监听器
TreeCache treeCache = new TreeCache(client,"/app2");
//2. 注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
System.out.println("节点变化了");
System.out.println(event);
}
});
//3. 开启
treeCache.start();
while (true){
}
}
分布式锁实现
核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点。
1.客户端获取锁时,在lock节点下创建临时顺序节点。
2.然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的
子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
3.如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,
此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
4.如果发现比自己小的那个节点被删除,则客户端的 Watcher会收到相应通知,
此时再次判断自己创建的节点 是否是lock子节点中序号最小的,如果是则获取到
了锁, 如果不是则重复以上步骤继续获取到比自己小的一个节点 并注册监听。
2.然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的
子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
3.如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,
此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
4.如果发现比自己小的那个节点被删除,则客户端的 Watcher会收到相应通知,
此时再次判断自己创建的节点 是否是lock子节点中序号最小的,如果是则获取到
了锁, 如果不是则重复以上步骤继续获取到比自己小的一个节点 并注册监听。
在Curator中有五种锁方案
InterProcessSemaphoreMutex:分布式排它锁(非可重入锁)
InterProcessMutex:分布式可重入排它锁
InterProcessReadWriteLock:分布式读写锁
InterProcessMultiLock:将多个锁作为单个实体管理的容器
InterProcessSemaphoreV2:共享信号量
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class Ticket12306 implements Runnable{
private int tickets = 10;//数据库的票数
private InterProcessMutex lock ;
public Ticket12306(){
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//2.第二种方式
//CuratorFrameworkFactory.builder();
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.149.135:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.build();
//开启连接
client.start();
lock = new InterProcessMutex(client,"/lock");
}
@Override
public void run() {
while(true){
//获取锁
try {
lock.acquire(3, TimeUnit.SECONDS);
if(tickets > 0){
System.out.println(Thread.currentThread()+":"+tickets);
Thread.sleep(100);
tickets--;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Ticket12306 ticket12306 = new Ticket12306();
//创建客户端
Thread t1 = new Thread(ticket12306,"携程");
Thread t2 = new Thread(ticket12306,"飞猪");
t1.start();
t2.start();
}
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class Ticket12306 implements Runnable{
private int tickets = 10;//数据库的票数
private InterProcessMutex lock ;
public Ticket12306(){
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//2.第二种方式
//CuratorFrameworkFactory.builder();
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.149.135:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.build();
//开启连接
client.start();
lock = new InterProcessMutex(client,"/lock");
}
@Override
public void run() {
while(true){
//获取锁
try {
lock.acquire(3, TimeUnit.SECONDS);
if(tickets > 0){
System.out.println(Thread.currentThread()+":"+tickets);
Thread.sleep(100);
tickets--;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Ticket12306 ticket12306 = new Ticket12306();
//创建客户端
Thread t1 = new Thread(ticket12306,"携程");
Thread t2 = new Thread(ticket12306,"飞猪");
t1.start();
t2.start();
}
集群搭建
核心理论
Apache POI
概念
Apache POI是用Java编写的免费开源的跨平台的Java API,
Apache POI提供API给Java程序对Microsoft Office格式档
案读和写的功能,其中使用最多的就是使用POI操作Excel文件。
Apache POI提供API给Java程序对Microsoft Office格式档
案读和写的功能,其中使用最多的就是使用POI操作Excel文件。
官方主页: http://poi.apache.org/index.html
API文档: http://poi.apache.org/apidocs/index.html
API文档: http://poi.apache.org/apidocs/index.html
使用
jxl:专门操作Excel
Maven座标
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
POI结构
HSSF - 提供读写Microsoft Excel XLS格式档案的功能
HSSFWorkbook
工作簿,代表一个excel的整个文档
HSSFWorkbook(); // 创建一个新的工作簿
HSSFWorkbook(InputStream inputStream); // 创建一个关联输入流的工作簿,可以将一个excel文件封装成工作簿
HSSFSheet createSheet(String sheetname); 创建一个新的Sheet
HSSFSheet getSheet(String sheetName); 通过名称获取Sheet
HSSFSheet getSheetAt(int index); // 通过索引获取Sheet,索引从0开始
HSSFCellStyle createCellStyle(); 创建单元格样式
int getNumberOfSheets(); 获取sheet的个数
setActiveSheet(int index); 设置默认选中的工作表
write();
write(File newFile);
write(OutputStream stream);
HSSFWorkbook(InputStream inputStream); // 创建一个关联输入流的工作簿,可以将一个excel文件封装成工作簿
HSSFSheet createSheet(String sheetname); 创建一个新的Sheet
HSSFSheet getSheet(String sheetName); 通过名称获取Sheet
HSSFSheet getSheetAt(int index); // 通过索引获取Sheet,索引从0开始
HSSFCellStyle createCellStyle(); 创建单元格样式
int getNumberOfSheets(); 获取sheet的个数
setActiveSheet(int index); 设置默认选中的工作表
write();
write(File newFile);
write(OutputStream stream);
HSSFSheet
工作表
HSSFRow createRow(int rownum); 创建新行,需要指定行号,行号从0开始
HSSFRow getRow(int index); 根据索引获取指定的行
int addMergedRegion(CellRangeAddress region); 合并单元格
CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol); 单元格范围,
用于合并单元格,需要指定要合并的首行、最后一行、首列、最后一列。
autoSizeColumn(int column); 自动调整列的宽度来适应内容
getLastRowNum(); 获取最后的行的索引,没有行或者只有一行的时候返回0
setColumnWidth(int columnIndex, int width); 设置某一列的宽度,width=字符个数 * 256,例如20个字符的宽度就是20 * 256
HSSFRow getRow(int index); 根据索引获取指定的行
int addMergedRegion(CellRangeAddress region); 合并单元格
CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol); 单元格范围,
用于合并单元格,需要指定要合并的首行、最后一行、首列、最后一列。
autoSizeColumn(int column); 自动调整列的宽度来适应内容
getLastRowNum(); 获取最后的行的索引,没有行或者只有一行的时候返回0
setColumnWidth(int columnIndex, int width); 设置某一列的宽度,width=字符个数 * 256,例如20个字符的宽度就是20 * 256
HSSFRow
行
HSSFCell createCell(int column); 创建新的单元格
HSSFCell setCell(shot index);
HSSFCell getCell(shot index);
HSSFCell getCell(CellReference.convertColStringToIndex(“A”)); 根据列名英文字母获取。
setRowStyle(HSSFCellStyle style); 设置行样式
short getLastCellNum(); 获取最后的单元格号,如果单元格有第一个开始算,lastCellNum就是列的个数
setHeightInPoints(float height); 设置行的高度
HSSFCell setCell(shot index);
HSSFCell getCell(shot index);
HSSFCell getCell(CellReference.convertColStringToIndex(“A”)); 根据列名英文字母获取。
setRowStyle(HSSFCellStyle style); 设置行样式
short getLastCellNum(); 获取最后的单元格号,如果单元格有第一个开始算,lastCellNum就是列的个数
setHeightInPoints(float height); 设置行的高度
HSSFCell
单元格
setCellValue(String value); 设置单元格的值
setCellType(); 设置单元格类型,如 字符串、数字、布尔等
setCellStyle(); 设置单元格样式
String getStringCellValue(); 获取单元格中的字符串值
setCellStyle(HSSFCellStyle style); 设置单元格样式,例如字体、加粗、格式化
setCellFormula(String formula); 设置计算公式,计算的结果作为单元格的值,也提供了异常常用的函数,
如求和"sum(A1,C1)"、日期函数、字符串相关函数、CountIf和SumIf函数、随机数函数等
setCellType(); 设置单元格类型,如 字符串、数字、布尔等
setCellStyle(); 设置单元格样式
String getStringCellValue(); 获取单元格中的字符串值
setCellStyle(HSSFCellStyle style); 设置单元格样式,例如字体、加粗、格式化
setCellFormula(String formula); 设置计算公式,计算的结果作为单元格的值,也提供了异常常用的函数,
如求和"sum(A1,C1)"、日期函数、字符串相关函数、CountIf和SumIf函数、随机数函数等
HSSFCellStyle
单元格样式
setFont(Font font); 为单元格设置字体样式
setAlignment(HorizontalAlignment align); // 设置水平对齐方式
setVerticalAlignment(VerticalAlignment align); // 设置垂直对齐方式
setFillPattern(FillPatternType fp);
setFillForegroundColor(short bg); 设置前景色
setFillBackgroundColor(short bg); 设置背景颜色
setAlignment(HorizontalAlignment align); // 设置水平对齐方式
setVerticalAlignment(VerticalAlignment align); // 设置垂直对齐方式
setFillPattern(FillPatternType fp);
setFillForegroundColor(short bg); 设置前景色
setFillBackgroundColor(short bg); 设置背景颜色
HSSFFont
字体
setColor(short color); // 设置字体颜色
setBold(boolean bold); // 设置是否粗体
setItalic(boolean italic); 设置倾斜
setUnderline(byte underline); 设置下划线
setBold(boolean bold); // 设置是否粗体
setItalic(boolean italic); 设置倾斜
setUnderline(byte underline); 设置下划线
HSSFName:名称
HSSFDataFormat :日期格式化
HSSFHeader : Sheet的头部
HSSFFooter :Sheet的尾部
HSSFDateUtil :日期工具
HSSFPrintSetup :打印设置
HSSFErrorConstants:错误信息表
HSSFDataFormat :日期格式化
HSSFHeader : Sheet的头部
HSSFFooter :Sheet的尾部
HSSFDateUtil :日期工具
HSSFPrintSetup :打印设置
HSSFErrorConstants:错误信息表
使用
引入依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.8</version>
</dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.8</version>
</dependency>
在桌面上生成一个Excel文件
public static void createExcel() throws IOException{
// 获取桌面路径
FileSystemView fsv = FileSystemView.getFileSystemView();
String desktop = fsv.getHomeDirectory().getPath();
String filePath = desktop + "/template.xls";
File file = new File(filePath);
OutputStream outputStream = new FileOutputStream(file);
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Sheet1");
HSSFRow row = sheet.createRow(0);
row.createCell(0).setCellValue("id");
row.createCell(1).setCellValue("订单号");
row.createCell(2).setCellValue("下单时间");
row.createCell(3).setCellValue("个数");
row.createCell(4).setCellValue("单价");
row.createCell(5).setCellValue("订单金额");
row.setHeightInPoints(30); // 设置行的高度
HSSFRow row1 = sheet.createRow(1);
row1.createCell(0).setCellValue("1");
row1.createCell(1).setCellValue("NO00001");
// 日期格式化
HSSFCellStyle cellStyle2 = workbook.createCellStyle();
HSSFCreationHelper creationHelper = workbook.getCreationHelper();
cellStyle2.setDataFormat(creationHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));
sheet.setColumnWidth(2, 20 * 256); // 设置列的宽度
HSSFCell cell2 = row1.createCell(2);
cell2.setCellStyle(cellStyle2);
cell2.setCellValue(new Date());
row1.createCell(3).setCellValue(2);
// 保留两位小数
HSSFCellStyle cellStyle3 = workbook.createCellStyle();
cellStyle3.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00"));
HSSFCell cell4 = row1.createCell(4);
cell4.setCellStyle(cellStyle3);
cell4.setCellValue(29.5);
// 货币格式化
HSSFCellStyle cellStyle4 = workbook.createCellStyle();
HSSFFont font = workbook.createFont();
font.setFontName("华文行楷");
font.setFontHeightInPoints((short)15);
font.setColor(HSSFColor.RED.index);
cellStyle4.setFont(font);
HSSFCell cell5 = row1.createCell(5);
cell5.setCellFormula("D2*E2"); // 设置计算公式
// 获取计算公式的值
HSSFFormulaEvaluator e = new HSSFFormulaEvaluator(workbook);
cell5 = e.evaluateInCell(cell5);
System.out.println(cell5.getNumericCellValue());
workbook.setActiveSheet(0);
workbook.write(outputStream);
outputStream.close();
}
————————————————
版权声明:本文为CSDN博主「vbirdbest」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/vbirdbest/article/details/72870714
// 获取桌面路径
FileSystemView fsv = FileSystemView.getFileSystemView();
String desktop = fsv.getHomeDirectory().getPath();
String filePath = desktop + "/template.xls";
File file = new File(filePath);
OutputStream outputStream = new FileOutputStream(file);
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Sheet1");
HSSFRow row = sheet.createRow(0);
row.createCell(0).setCellValue("id");
row.createCell(1).setCellValue("订单号");
row.createCell(2).setCellValue("下单时间");
row.createCell(3).setCellValue("个数");
row.createCell(4).setCellValue("单价");
row.createCell(5).setCellValue("订单金额");
row.setHeightInPoints(30); // 设置行的高度
HSSFRow row1 = sheet.createRow(1);
row1.createCell(0).setCellValue("1");
row1.createCell(1).setCellValue("NO00001");
// 日期格式化
HSSFCellStyle cellStyle2 = workbook.createCellStyle();
HSSFCreationHelper creationHelper = workbook.getCreationHelper();
cellStyle2.setDataFormat(creationHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));
sheet.setColumnWidth(2, 20 * 256); // 设置列的宽度
HSSFCell cell2 = row1.createCell(2);
cell2.setCellStyle(cellStyle2);
cell2.setCellValue(new Date());
row1.createCell(3).setCellValue(2);
// 保留两位小数
HSSFCellStyle cellStyle3 = workbook.createCellStyle();
cellStyle3.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00"));
HSSFCell cell4 = row1.createCell(4);
cell4.setCellStyle(cellStyle3);
cell4.setCellValue(29.5);
// 货币格式化
HSSFCellStyle cellStyle4 = workbook.createCellStyle();
HSSFFont font = workbook.createFont();
font.setFontName("华文行楷");
font.setFontHeightInPoints((short)15);
font.setColor(HSSFColor.RED.index);
cellStyle4.setFont(font);
HSSFCell cell5 = row1.createCell(5);
cell5.setCellFormula("D2*E2"); // 设置计算公式
// 获取计算公式的值
HSSFFormulaEvaluator e = new HSSFFormulaEvaluator(workbook);
cell5 = e.evaluateInCell(cell5);
System.out.println(cell5.getNumericCellValue());
workbook.setActiveSheet(0);
workbook.write(outputStream);
outputStream.close();
}
————————————————
版权声明:本文为CSDN博主「vbirdbest」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/vbirdbest/article/details/72870714
读取Excel,解析数据
public static void readExcel() throws IOException{
FileSystemView fsv = FileSystemView.getFileSystemView();
String desktop = fsv.getHomeDirectory().getPath();
String filePath = desktop + "/template.xls";
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(bufferedInputStream);
HSSFWorkbook workbook = new HSSFWorkbook(fileSystem);
HSSFSheet sheet = workbook.getSheet("Sheet1");
int lastRowIndex = sheet.getLastRowNum();
System.out.println(lastRowIndex);
for (int i = 0; i <= lastRowIndex; i++) {
HSSFRow row = sheet.getRow(i);
if (row == null) { break; }
short lastCellNum = row.getLastCellNum();
for (int j = 0; j < lastCellNum; j++) {
String cellValue = row.getCell(j).getStringCellValue();
System.out.println(cellValue);
}
}
bufferedInputStream.close();
}
FileSystemView fsv = FileSystemView.getFileSystemView();
String desktop = fsv.getHomeDirectory().getPath();
String filePath = desktop + "/template.xls";
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(bufferedInputStream);
HSSFWorkbook workbook = new HSSFWorkbook(fileSystem);
HSSFSheet sheet = workbook.getSheet("Sheet1");
int lastRowIndex = sheet.getLastRowNum();
System.out.println(lastRowIndex);
for (int i = 0; i <= lastRowIndex; i++) {
HSSFRow row = sheet.getRow(i);
if (row == null) { break; }
short lastCellNum = row.getLastCellNum();
for (int j = 0; j < lastCellNum; j++) {
String cellValue = row.getCell(j).getStringCellValue();
System.out.println(cellValue);
}
}
bufferedInputStream.close();
}
Java Web 中导出和导入Excel
@SuppressWarnings("resource")
@RequestMapping("/export")
public void exportExcel(HttpServletResponse response, HttpSession session, String name) throws Exception {
String[] tableHeaders = {"id", "姓名", "年龄"};
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Sheet1");
HSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
font.setColor(HSSFColor.RED.index);
font.setBold(true);
cellStyle.setFont(font);
// 将第一行的三个单元格给合并
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));
HSSFRow row = sheet.createRow(0);
HSSFCell beginCell = row.createCell(0);
beginCell.setCellValue("通讯录");
beginCell.setCellStyle(cellStyle);
row = sheet.createRow(1);
// 创建表头
for (int i = 0; i < tableHeaders.length; i++) {
HSSFCell cell = row.createCell(i);
cell.setCellValue(tableHeaders[i]);
cell.setCellStyle(cellStyle);
}
List<User> users = new ArrayList<>();
users.add(new User(1L, "张三", 20));
users.add(new User(2L, "李四", 21));
users.add(new User(3L, "王五", 22));
for (int i = 0; i < users.size(); i++) {
row = sheet.createRow(i + 2);
User user = users.get(i);
row.createCell(0).setCellValue(user.getId());
row.createCell(1).setCellValue(user.getName());
row.createCell(2).setCellValue(user.getAge());
}
OutputStream outputStream = response.getOutputStream();
response.reset();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment;filename=template.xls");
workbook.write(outputStream);
outputStream.flush();
outputStream.close();
}
@RequestMapping("/export")
public void exportExcel(HttpServletResponse response, HttpSession session, String name) throws Exception {
String[] tableHeaders = {"id", "姓名", "年龄"};
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Sheet1");
HSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
font.setColor(HSSFColor.RED.index);
font.setBold(true);
cellStyle.setFont(font);
// 将第一行的三个单元格给合并
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));
HSSFRow row = sheet.createRow(0);
HSSFCell beginCell = row.createCell(0);
beginCell.setCellValue("通讯录");
beginCell.setCellStyle(cellStyle);
row = sheet.createRow(1);
// 创建表头
for (int i = 0; i < tableHeaders.length; i++) {
HSSFCell cell = row.createCell(i);
cell.setCellValue(tableHeaders[i]);
cell.setCellStyle(cellStyle);
}
List<User> users = new ArrayList<>();
users.add(new User(1L, "张三", 20));
users.add(new User(2L, "李四", 21));
users.add(new User(3L, "王五", 22));
for (int i = 0; i < users.size(); i++) {
row = sheet.createRow(i + 2);
User user = users.get(i);
row.createCell(0).setCellValue(user.getId());
row.createCell(1).setCellValue(user.getName());
row.createCell(2).setCellValue(user.getAge());
}
OutputStream outputStream = response.getOutputStream();
response.reset();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment;filename=template.xls");
workbook.write(outputStream);
outputStream.flush();
outputStream.close();
}
导入示例
使用SpringMVC上传文件,需要用到commons-fileupload
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3</version>
</dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3</version>
</dependency>
需要在spring的配置文件中配置一下multipartResolver
<bean name="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
</bean>
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
</bean>
jsp文件
<a href="/Spring-Mybatis-Druid/user/export">导出</a> <br/>
<form action="/Spring-Mybatis-Druid/user/import" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="导入Excel">
</form>
<form action="/Spring-Mybatis-Druid/user/import" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="导入Excel">
</form>
解析上传的.xls文件
@SuppressWarnings("resource")
@RequestMapping("/import")
public void importExcel(@RequestParam("file") MultipartFile file) throws Exception{
InputStream inputStream = file.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(bufferedInputStream);
HSSFWorkbook workbook = new HSSFWorkbook(fileSystem);
//HSSFWorkbook workbook = new HSSFWorkbook(file.getInputStream());
HSSFSheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int i = 2; i <= lastRowNum; i++) {
HSSFRow row = sheet.getRow(i);
int id = (int) row.getCell(0).getNumericCellValue();
String name = row.getCell(1).getStringCellValue();
int age = (int) row.getCell(2).getNumericCellValue();
System.out.println(id + "-" + name + "-" + age);
}
}
@RequestMapping("/import")
public void importExcel(@RequestParam("file") MultipartFile file) throws Exception{
InputStream inputStream = file.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(bufferedInputStream);
HSSFWorkbook workbook = new HSSFWorkbook(fileSystem);
//HSSFWorkbook workbook = new HSSFWorkbook(file.getInputStream());
HSSFSheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int i = 2; i <= lastRowNum; i++) {
HSSFRow row = sheet.getRow(i);
int id = (int) row.getCell(0).getNumericCellValue();
String name = row.getCell(1).getStringCellValue();
int age = (int) row.getCell(2).getNumericCellValue();
System.out.println(id + "-" + name + "-" + age);
}
}
XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能
HWPF - 提供读写Microsoft Word DOC格式档案的功能
HSLF - 提供读写Microsoft PowerPoint格式档案的功能
HDGF - 提供读Microsoft Visio格式档案的功能
HPBF - 提供读Microsoft Publisher格式档案的功能
HSMF - 提供读Microsoft Outlook格式档案的功能
Excel中的工作簿、工作表、行、单元格中的关系
一个Excel文件对应于一个workbook(HSSFWorkbook),
一个workbook可以有多个sheet(HSSFSheet)组成,
一个sheet是由多个row(HSSFRow)组成,
一个row是由多个cell(HSSFCell)组成
一个workbook可以有多个sheet(HSSFSheet)组成,
一个sheet是由多个row(HSSFRow)组成,
一个row是由多个cell(HSSFCell)组成
PDF生成工具
Itext
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class ItextDemo {
public static void main(String[] args) {
try {
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream("D:\\test.pdf"));
document.open();
document.add(new Paragraph("hello itext"));
document.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
import com.lowagie.text.DocumentException;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class ItextDemo {
public static void main(String[] args) {
try {
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream("D:\\test.pdf"));
document.open();
document.add(new Paragraph("hello itext"));
document.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。 iText的安装非常方便,下载iText.jar文件后,只需要在系统的CLASSPATH中加入iText.jar的路径,在程序中就可以使用iText类库了。
JasperReports
JasperReports是一个强大、灵活的报表生成工具,能够展示丰富的页面内容,并将之转换成PDF,HTML,或者XML格式。该库完全由Java写成,可以用于在各种Java应用程序,包括J2EE,Web应用程序中生成动态内容。一般情况下,JasperReports会结合Jaspersoft Studio(模板设计器)使用导出PDF报表。
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.8.0</version>
</dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.8.0</version>
</dependency>
@Test
public void testJasperReports()throws Exception{
String jrxmlPath =
"D:\\ideaProjects\\projects111\\jasperdemo\\src\\main\\resources\\demo.jrxml";
String jasperPath =
"D:\\ideaProjects\\projects111\\jasperdemo\\src\\main\\resources\\demo.jasper";
//编译模板
JasperCompileManager.compileReportToFile(jrxmlPath,jasperPath);
//构造数据
Map paramters = new HashMap();
paramters.put("reportDate","2019-10-10");
paramters.put("company","itcast");
List<Map> list = new ArrayList();
Map map1 = new HashMap();
map1.put("name","xiaoming");
map1.put("address","beijing");
map1.put("email","xiaoming@itcast.cn");
Map map2 = new HashMap();
map2.put("name","xiaoli");
map2.put("address","nanjing");
map2.put("email","xiaoli@itcast.cn");
list.add(map1);
list.add(map2);
//填充数据
JasperPrint jasperPrint =
JasperFillManager.fillReport(jasperPath,
paramters,
new JRBeanCollectionDataSource(list));
//输出文件
String pdfPath = "D:\\test.pdf";
JasperExportManager.exportReportToPdfFile(jasperPrint,pdfPath);
}
public void testJasperReports()throws Exception{
String jrxmlPath =
"D:\\ideaProjects\\projects111\\jasperdemo\\src\\main\\resources\\demo.jrxml";
String jasperPath =
"D:\\ideaProjects\\projects111\\jasperdemo\\src\\main\\resources\\demo.jasper";
//编译模板
JasperCompileManager.compileReportToFile(jrxmlPath,jasperPath);
//构造数据
Map paramters = new HashMap();
paramters.put("reportDate","2019-10-10");
paramters.put("company","itcast");
List<Map> list = new ArrayList();
Map map1 = new HashMap();
map1.put("name","xiaoming");
map1.put("address","beijing");
map1.put("email","xiaoming@itcast.cn");
Map map2 = new HashMap();
map2.put("name","xiaoli");
map2.put("address","nanjing");
map2.put("email","xiaoli@itcast.cn");
list.add(map1);
list.add(map2);
//填充数据
JasperPrint jasperPrint =
JasperFillManager.fillReport(jasperPath,
paramters,
new JRBeanCollectionDataSource(list));
//输出文件
String pdfPath = "D:\\test.pdf";
JasperExportManager.exportReportToPdfFile(jasperPrint,pdfPath);
}
原理
- JRXML:报表填充模板,本质是一个xml文件
- Jasper:由JRXML模板编译成的二进制文件,用于代码填充数据
- Jrprint:当用数据填充完Jasper后生成的对象,用于输出报表
- Exporter:报表输出的管理类,可以指定要输出的报表为何种格式
- PDF/HTML/XML:报表形式
- Jasper:由JRXML模板编译成的二进制文件,用于代码填充数据
- Jrprint:当用数据填充完Jasper后生成的对象,用于输出报表
- Exporter:报表输出的管理类,可以指定要输出的报表为何种格式
- PDF/HTML/XML:报表形式
七牛云存储
若依框架
LayUI
消息队列
RabbitMQ
MQ 的基本概念
概念
MQ全称 Message Queue(消息队列),是在消息的传
输过程中保存消息的容器。多用于分布式系统之间进行通信
输过程中保存消息的容器。多用于分布式系统之间进行通信
总结
⚫ MQ,消息队列,存储消息的中间件
⚫ 分布式系统通信两种方式:直接远程调用 和 借助第三方 完成间接通信
⚫ 发送方称为生产者,接收方称为消费者
⚫ 分布式系统通信两种方式:直接远程调用 和 借助第三方 完成间接通信
⚫ 发送方称为生产者,接收方称为消费者
优劣势分析
优势
⚫ 应用解耦
⚫ 异步提速
⚫ 削峰填谷
劣势
⚫ 系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用?
⚫ 系统复杂度提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何
保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
⚫ 一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理
失败。如何保证消息数据处理的一致性?
失败。如何保证消息数据处理的一致性?
小结
既然 MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?
① 生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明
明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
② 容许短暂的不一致性。
③ 确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本
既然 MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?
① 生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明
明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
② 容许短暂的不一致性。
③ 确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本
常见的MQ产品
RabbitMQ 的安装和配置
简介
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),
是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产
品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。
是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产
品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。
RabbitMQ 基础架构如下图
RabbitMQ 中的相关概念
⚫ Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
⚫ Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网
络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多
个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
⚫ Connection:publisher/consumer 和 broker 之间的 TCP 连接
⚫ Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection
的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线
程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和
message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection
极大减少了操作系统建立 TCP connection 的开销
⚫ Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到
queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
⚫ Queue:消息最终被送到这里等待 consumer 取走
⚫ Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存
到 exchange 中的查询表中,用于 message 的分发依据
⚫ Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网
络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多
个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
⚫ Connection:publisher/consumer 和 broker 之间的 TCP 连接
⚫ Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection
的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线
程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和
message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection
极大减少了操作系统建立 TCP connection 的开销
⚫ Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到
queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
⚫ Queue:消息最终被送到这里等待 consumer 取走
⚫ Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存
到 exchange 中的查询表中,用于 message 的分发依据
六种工作模式
简单模式、
work queues、
Publish/Subscribe 发布与订阅模式、
Routing路由模式、
Topics 主题模式、
RPC 远程调用模式
work queues、
Publish/Subscribe 发布与订阅模式、
Routing路由模式、
Topics 主题模式、
RPC 远程调用模式
JMS
Java 消息服务(JavaMessage Service)应用程序接口,是一个 Java 平台中关于面向消息中间件
的API
的API
RabbitMQ 快速入门
code
producer
package com.itheima.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
1. exchange:交换机名称。简单模式下交换机会使用默认的 ""
2. routingKey:路由名称
3. props:配置信息
4. body:发送消息数据
*/
String body = "hello rabbitmq~~~";
//6. 发送消息
channel.basicPublish("","hello_world",null,body.getBytes());
//7.释放资源
channel.close();
connection.close();
}
}
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
1. exchange:交换机名称。简单模式下交换机会使用默认的 ""
2. routingKey:路由名称
3. props:配置信息
4. body:发送消息数据
*/
String body = "hello rabbitmq~~~";
//6. 发送消息
channel.basicPublish("","hello_world",null,body.getBytes());
//7.释放资源
channel.close();
connection.close();
}
}
consumer
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/itcast");//虚拟机 默认值/
factory.setUsername("heima");//用户名 默认 guest
factory.setPassword("heima");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("hello_world",true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/itcast");//虚拟机 默认值/
factory.setUsername("heima");//用户名 默认 guest
factory.setPassword("heima");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("hello_world",true,consumer);
//关闭资源?不要
}
}
RabbitMQ 的工作模式
工作队列模式
原型图
Work Queues
与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息
应用场景
对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度
代码编写
在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系
Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
例如:短信服务部署多个,只需要有一个节点成功发送即可
例如:短信服务部署多个,只需要有一个节点成功发送即可
producer
package com.itheima.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_WorkQueues {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
1. exchange:交换机名称。简单模式下交换机会使用默认的 ""
2. routingKey:路由名称
3. props:配置信息
4. body:发送消息数据
*/
for (int i = 1; i <= 10; i++) {
String body = i+"hello rabbitmq~~~";
//6. 发送消息
channel.basicPublish("","work_queues",null,body.getBytes());
}
//7.释放资源
channel.close();
connection.close();
}
}
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_WorkQueues {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
1. exchange:交换机名称。简单模式下交换机会使用默认的 ""
2. routingKey:路由名称
3. props:配置信息
4. body:发送消息数据
*/
for (int i = 1; i <= 10; i++) {
String body = i+"hello rabbitmq~~~";
//6. 发送消息
channel.basicPublish("","work_queues",null,body.getBytes());
}
//7.释放资源
channel.close();
connection.close();
}
}
consumer
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueues1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueues1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源?不要
}
}
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueues2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueues2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1. queue:队列名称
2. durable:是否持久化,当mq重启之后,还在
3. exclusive:
* 是否独占。只能有一个消费者监听这队列
* 当Connection关闭时,是否删除队列
*
4. autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5. arguments:参数。
*/
//如果没有一个名字叫hello_world的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源?不要
}
}
Pub/Sub 订阅模式
模式说明
在订阅模型中,多了一个 Exchange 角色,而且过程略有变化
⚫ P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
⚫ C:消费者,消息的接收者,会一直等待消息到来
⚫ Queue:消息队列,接收消息、缓存消息
⚫ Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、
递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
➢ Fanout:广播,将消息交给所有绑定到交换机的队列
➢ Direct:定向,把消息交给符合指定routing key 的队列
➢ Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合
路由规则的队列,那么消息会丢失!
⚫ C:消费者,消息的接收者,会一直等待消息到来
⚫ Queue:消息队列,接收消息、缓存消息
⚫ Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、
递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
➢ Fanout:广播,将消息交给所有绑定到交换机的队列
➢ Direct:定向,把消息交给符合指定routing key 的队列
➢ Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合
路由规则的队列,那么消息会丢失!
代码编写
producer
package com.itheima.producer;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_PubSub {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
1. exchange:交换机名称
2. type:交换机类型
DIRECT("direct"),:定向
FANOUT("fanout"),:扇形(广播),发送消息到每一个与之绑定队列。
TOPIC("topic"),通配符的方式
HEADERS("headers");参数匹配
3. durable:是否持久化
4. autoDelete:自动删除
5. internal:内部使用。 一般false
6. arguments:参数
*/
String exchangeName = "test_fanout";
//5. 创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
//6. 创建队列
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则
如果交换机的类型为fanout ,routingKey设置为""
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
//8. 发送消息
channel.basicPublish(exchangeName,"",null,body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_PubSub {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
1. exchange:交换机名称
2. type:交换机类型
DIRECT("direct"),:定向
FANOUT("fanout"),:扇形(广播),发送消息到每一个与之绑定队列。
TOPIC("topic"),通配符的方式
HEADERS("headers");参数匹配
3. durable:是否持久化
4. autoDelete:自动删除
5. internal:内部使用。 一般false
6. arguments:参数
*/
String exchangeName = "test_fanout";
//5. 创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
//6. 创建队列
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则
如果交换机的类型为fanout ,routingKey设置为""
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
//8. 发送消息
channel.basicPublish(exchangeName,"",null,body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
consumer
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息保存数据库.....");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息保存数据库.....");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
小结
1. 交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
2. 发布订阅模式与工作队列模式的区别:
⚫ 工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机
⚫ 发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)
⚫ 发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑定到默认的交换机
2. 发布订阅模式与工作队列模式的区别:
⚫ 工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机
⚫ 发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)
⚫ 发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑定到默认的交换机
Routing 路由模式
模式说明
⚫ 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key) ⚫ 消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey
⚫ Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的
Routingkey 与消息的 Routing key 完全一致,才会接收到消息
⚫ Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的
Routingkey 与消息的 Routing key 完全一致,才会接收到消息
图
代码编写
producer
package com.itheima.producer;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_Routing {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
1. exchange:交换机名称
2. type:交换机类型
DIRECT("direct"),:定向
FANOUT("fanout"),:扇形(广播),发送消息到每一个与之绑定队列。
TOPIC("topic"),通配符的方式
HEADERS("headers");参数匹配
3. durable:是否持久化
4. autoDelete:自动删除
5. internal:内部使用。 一般false
6. arguments:参数
*/
String exchangeName = "test_direct";
//5. 创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
//6. 创建队列
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则
如果交换机的类型为fanout ,routingKey设置为""
*/
//队列1绑定 error
channel.queueBind(queue1Name,exchangeName,"error");
//队列2绑定 info error warning
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
String body = "日志信息:张三调用了delete方法...出错误了。。。日志级别:error...";
//8. 发送消息
channel.basicPublish(exchangeName,"warning",null,body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_Routing {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
1. exchange:交换机名称
2. type:交换机类型
DIRECT("direct"),:定向
FANOUT("fanout"),:扇形(广播),发送消息到每一个与之绑定队列。
TOPIC("topic"),通配符的方式
HEADERS("headers");参数匹配
3. durable:是否持久化
4. autoDelete:自动删除
5. internal:内部使用。 一般false
6. arguments:参数
*/
String exchangeName = "test_direct";
//5. 创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
//6. 创建队列
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则
如果交换机的类型为fanout ,routingKey设置为""
*/
//队列1绑定 error
channel.queueBind(queue1Name,exchangeName,"error");
//队列2绑定 info error warning
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
String body = "日志信息:张三调用了delete方法...出错误了。。。日志级别:error...";
//8. 发送消息
channel.basicPublish(exchangeName,"warning",null,body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
consumer
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Routing1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Routing1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Routing2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息存储到数据库.....");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Routing2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息存储到数据库.....");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
Topics 通配符模式
模式说明
⚫ Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型
Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
⚫ Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
⚫ 通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc
或者 item.insert,item.* 只能匹配 item.insert
Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
⚫ Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
⚫ 通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc
或者 item.insert,item.* 只能匹配 item.insert
图解
代码编写
producer
package com.itheima.producer;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_Topics {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
1. exchange:交换机名称
2. type:交换机类型
DIRECT("direct"),:定向
FANOUT("fanout"),:扇形(广播),发送消息到每一个与之绑定队列。
TOPIC("topic"),通配符的方式
HEADERS("headers");参数匹配
3. durable:是否持久化
4. autoDelete:自动删除
5. internal:内部使用。 一般false
6. arguments:参数
*/
String exchangeName = "test_topic";
//5. 创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//6. 创建队列
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则
如果交换机的类型为fanout ,routingKey设置为""
*/
// routing key 系统的名称.日志的级别。
//=需求: 所有error级别的日志存入数据库,所有order系统的日志存入数据库
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.*");
channel.queueBind(queue2Name,exchangeName,"*.*");
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
//8. 发送消息
channel.basicPublish(exchangeName,"goods.error",null,body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 发送消息
*/
public class Producer_Topics {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
1. exchange:交换机名称
2. type:交换机类型
DIRECT("direct"),:定向
FANOUT("fanout"),:扇形(广播),发送消息到每一个与之绑定队列。
TOPIC("topic"),通配符的方式
HEADERS("headers");参数匹配
3. durable:是否持久化
4. autoDelete:自动删除
5. internal:内部使用。 一般false
6. arguments:参数
*/
String exchangeName = "test_topic";
//5. 创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//6. 创建队列
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7. 绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
1. queue:队列名称
2. exchange:交换机名称
3. routingKey:路由键,绑定规则
如果交换机的类型为fanout ,routingKey设置为""
*/
// routing key 系统的名称.日志的级别。
//=需求: 所有error级别的日志存入数据库,所有order系统的日志存入数据库
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.*");
channel.queueBind(queue2Name,exchangeName,"*.*");
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
//8. 发送消息
channel.basicPublish(exchangeName,"goods.error",null,body.getBytes());
//9. 释放资源
channel.close();
connection.close();
}
}
consumer
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息存入数据库.......");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息存入数据库.......");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源?不要
}
}
package com.itheima.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印控制台.......");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("192.168.89.66");//ip 默认值 localhost
factory.setPort(5672); //端口 默认值 5672
factory.setVirtualHost("/fzl");//虚拟机 默认值/
factory.setUsername("fzl");//用户名 默认 guest
factory.setPassword("fzl");//密码 默认值 guest
//3. 创建连接 Connection
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue:队列名称
2. autoAck:是否自动确认
3. callback:回调对象
*/
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
/*
回调方法,当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息,交换机,路由key...
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
/* System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("properties:"+properties);*/
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印控制台.......");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源?不要
}
}
工作模式总结
1. 简单模式 HelloWorld
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。
2. 工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。
3. 发布订阅模式 Publish/subscribe
需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消
息发送到绑定的队列。
4. 路由模式 Routing
需要设置类型为 direct 的交换机,交换机和队列进行绑定,并且指定 routing key,当发送消息到交换机
后,交换机会根据 routing key 将消息发送到对应的队列。
5. 通配符模式 Topic
需要设置类型为 topic 的交换机,交换机和队列进行绑定,并且指定通配符方式的 routing key,当发送
消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列。
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。
2. 工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。
3. 发布订阅模式 Publish/subscribe
需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消
息发送到绑定的队列。
4. 路由模式 Routing
需要设置类型为 direct 的交换机,交换机和队列进行绑定,并且指定 routing key,当发送消息到交换机
后,交换机会根据 routing key 将消息发送到对应的队列。
5. 通配符模式 Topic
需要设置类型为 topic 的交换机,交换机和队列进行绑定,并且指定通配符方式的 routing key,当发送
消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列。
Spring 整合 RabbitMQ
步骤图片
消费者具体代码
1.pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring-rabbitmq-consumers</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring-rabbitmq-consumers</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
rabbitmq配置
rabbitmq.host=192.168.89.66
rabbitmq.port=5672
rabbitmq.username=fzl
rabbitmq.password=fzl
rabbitmq.virtual-host=/fzl
rabbitmq.port=5672
rabbitmq.username=fzl
rabbitmq.password=fzl
rabbitmq.virtual-host=/fzl
spring对于rabbitmq的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id="springQueueListener" class="com.itheima.rabbitmq.listener.SpringQueueListener"/>
<!--<bean id="fanoutListener1" class="com.itheima.rabbitmq.listener.FanoutListener1"/>
<bean id="fanoutListener2" class="com.itheima.rabbitmq.listener.FanoutListener2"/>
<bean id="topicListenerStar" class="com.itheima.rabbitmq.listener.TopicListenerStar"/>
<bean id="topicListenerWell" class="com.itheima.rabbitmq.listener.TopicListenerWell"/>
<bean id="topicListenerWell2" class="com.itheima.rabbitmq.listener.TopicListenerWell2"/>
-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
<rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>
<rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
<rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
<rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id="springQueueListener" class="com.itheima.rabbitmq.listener.SpringQueueListener"/>
<!--<bean id="fanoutListener1" class="com.itheima.rabbitmq.listener.FanoutListener1"/>
<bean id="fanoutListener2" class="com.itheima.rabbitmq.listener.FanoutListener2"/>
<bean id="topicListenerStar" class="com.itheima.rabbitmq.listener.TopicListenerStar"/>
<bean id="topicListenerWell" class="com.itheima.rabbitmq.listener.TopicListenerWell"/>
<bean id="topicListenerWell2" class="com.itheima.rabbitmq.listener.TopicListenerWell2"/>
-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
<rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>
<rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
<rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
<rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
接收类具体代码
package com.itheima.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
//打印消息
System.out.println(new String(message.getBody()));
}
}
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
//打印消息
System.out.println(new String(message.getBody()));
}
}
生产者具体代码
1.pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring-rabbitmq-producers</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring-rabbitmq-producers</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
rabbitmq配置
rabbitmq.host=192.168.89.66
rabbitmq.port=5672
rabbitmq.username=fzl
rabbitmq.password=fzl
rabbitmq.virtual-host=/fzl
rabbitmq.port=5672
rabbitmq.username=fzl
rabbitmq.password=fzl
rabbitmq.virtual-host=/fzl
spring对于rabbitmq的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--
id:bean的名称
name:queue的名称
auto-declare:自动创建
auto-delete:自动删除。 最后一个消费者和该队列断开连接后,自动删除队列
exclusive:是否独占
durable:是否持久化
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1" />
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!--<rabbit:direct-exchange name="aa" >
<rabbit:bindings>
<!–direct 类型的交换机绑定队列 key :路由key queue:队列名称–>
<rabbit:binding queue="spring_queue" key="xxx"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>-->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="heima.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="heima.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="itcast.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--
id:bean的名称
name:queue的名称
auto-declare:自动创建
auto-delete:自动删除。 最后一个消费者和该队列断开连接后,自动删除队列
exclusive:是否独占
durable:是否持久化
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1" />
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!--<rabbit:direct-exchange name="aa" >
<rabbit:bindings>
<!–direct 类型的交换机绑定队列 key :路由key queue:队列名称–>
<rabbit:binding queue="spring_queue" key="xxx"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>-->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="heima.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="heima.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="itcast.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
发送消息具体代码
package com.itheima;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
//1.注入 RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWorld(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_queue","hello world spring....");
}
/**
* 发送fanout消息
*/
@Test
public void testFanout(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_fanout_exchange","","spring fanout....");
}
/**
* 发送topic消息
*/
@Test
public void testTopics(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_topic_exchange","heima.hehe.haha","spring topic....");
}
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
//1.注入 RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWorld(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_queue","hello world spring....");
}
/**
* 发送fanout消息
*/
@Test
public void testFanout(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_fanout_exchange","","spring fanout....");
}
/**
* 发送topic消息
*/
@Test
public void testTopics(){
//2.发送消息
rabbitTemplate.convertAndSend("spring_topic_exchange","heima.hehe.haha","spring topic....");
}
}
RabbitMQ 高级特性
消息可靠性投递
概念
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提
供了两种方式用来控制消息的投递可靠性模式。
供了两种方式用来控制消息的投递可靠性模式。
⚫ confirm 确认模式
⚫ return 退回模式
⚫ return 退回模式
rabbitmq 整个消息投递的路径为:
producer--->rabbitmq broker--->exchange--->queue--->consumer
⚫ 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
⚫ 消息从 exchange-->queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递
producer--->rabbitmq broker--->exchange--->queue--->consumer
⚫ 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
⚫ 消息从 exchange-->queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递
代码
确认模式
生产者
/**
* 确认模式:
* 步骤:
* 1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
* 2. 在rabbitTemplate定义ConfirmCallBack回调函数
*/
@Test
public void testConfirm() {
//2. 定义回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 相关配置信息
* @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm方法被执行了....");
if (ack) {
//接收成功
System.out.println("接收成功消息" + cause);
} else {
//接收失败
System.out.println("接收失败消息" + cause);
//做一些处理,让消息再次发送。
}
}
});
//3. 发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm111", "confirm", "message confirm....");
}
* 确认模式:
* 步骤:
* 1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
* 2. 在rabbitTemplate定义ConfirmCallBack回调函数
*/
@Test
public void testConfirm() {
//2. 定义回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 相关配置信息
* @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm方法被执行了....");
if (ack) {
//接收成功
System.out.println("接收成功消息" + cause);
} else {
//接收失败
System.out.println("接收失败消息" + cause);
//做一些处理,让消息再次发送。
}
}
});
//3. 发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm111", "confirm", "message confirm....");
}
回退模式
生产者
/**
* 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
* 步骤:
* 1. 开启回退模式:publisher-returns="true"
* 2. 设置ReturnCallBack
* 3. 设置Exchange处理消息的模式:
* 1. 如果消息没有路由到Queue,则丢弃消息(默认)
* 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
*/
@Test
public void testReturn() {
//设置交换机处理失败消息的模式
rabbitTemplate.setMandatory(true);
//2.设置ReturnCallBack
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 消息对象
* @param replyCode 错误码
* @param replyText 错误信息
* @param exchange 交换机
* @param routingKey 路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("return 执行了....");
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
//处理
}
});
//3. 发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
}
消息的可靠投递小结
➢ 设置ConnectionFactory的publisher-confirms="true" 开启 确认模式。
➢ 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回
调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发
送失败,需要处理。
➢ 设置ConnectionFactory的publisher-returns="true" 开启 退回模式。
➢ 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到
queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退
回给producer。并执行回调函数returnedMessage。 ➢ 在RabbitMQ中也提供了事务机制,但是性能较差,此处不做讲解。
使用channel下列方法,完成事务控制:
txSelect(), 用于将当前channel设置成transaction模式
txCommit(),用于提交事务
txRollback(),用于回滚事务
➢ 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回
调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发
送失败,需要处理。
➢ 设置ConnectionFactory的publisher-returns="true" 开启 退回模式。
➢ 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到
queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退
回给producer。并执行回调函数returnedMessage。 ➢ 在RabbitMQ中也提供了事务机制,但是性能较差,此处不做讲解。
使用channel下列方法,完成事务控制:
txSelect(), 用于将当前channel设置成transaction模式
txCommit(),用于提交事务
txRollback(),用于回滚事务
Consumer ACK
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
• 自动确认:acknowledge="none"
• 手动确认:acknowledge="manual"
• 根据异常情况确认:acknowledge="auto"
• 手动确认:acknowledge="manual"
• 根据异常情况确认:acknowledge="auto"
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的
消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如
果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则
调用channel.basicNack()方法,让其自动重新发送消息。
消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如
果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则
调用channel.basicNack()方法,让其自动重新发送消息。
代码
producer
consumer
package com.itheima.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* Consumer ACK机制:
* 1. 设置手动签收。acknowledge="manual"
* 2. 让监听器类实现ChannelAwareMessageListener接口
* 3. 如果消息成功处理,则调用channel的 basicAck()签收
* 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
*
*
*/
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
int i = 3/0;//出现错误
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
//4.拒绝签收
/*
第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
*/
channel.basicNack(deliveryTag,true,true);
//channel.basicReject(deliveryTag,true);
}
}
}
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* Consumer ACK机制:
* 1. 设置手动签收。acknowledge="manual"
* 2. 让监听器类实现ChannelAwareMessageListener接口
* 3. 如果消息成功处理,则调用channel的 basicAck()签收
* 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
*
*
*/
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
int i = 3/0;//出现错误
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
//4.拒绝签收
/*
第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
*/
channel.basicNack(deliveryTag,true,true);
//channel.basicReject(deliveryTag,true);
}
}
}
spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<context:component-scan base-package="com.itheima.listener" />
<!--定义监听器容器-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1" >
<!-- <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"></rabbit:listener>-->
<!-- <rabbit:listener ref="qosListener" queue-names="test_queue_confirm"></rabbit:listener>-->
<!--定义监听器,监听正常队列-->
<!--<rabbit:listener ref="dlxListener" queue-names="test_queue_dlx"></rabbit:listener>-->
<!--延迟队列效果实现: 一定要监听的是 死信队列!!!-->
<rabbit:listener ref="orderListener" queue-names="order_queue_dlx"></rabbit:listener>
</rabbit:listener-container>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<context:component-scan base-package="com.itheima.listener" />
<!--定义监听器容器-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1" >
<!-- <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"></rabbit:listener>-->
<!-- <rabbit:listener ref="qosListener" queue-names="test_queue_confirm"></rabbit:listener>-->
<!--定义监听器,监听正常队列-->
<!--<rabbit:listener ref="dlxListener" queue-names="test_queue_dlx"></rabbit:listener>-->
<!--延迟队列效果实现: 一定要监听的是 死信队列!!!-->
<rabbit:listener ref="orderListener" queue-names="order_queue_dlx"></rabbit:listener>
</rabbit:listener-container>
</beans>
rabbitmq配置文件
rabbitmq.host=192.168.89.66
rabbitmq.port=5672
rabbitmq.username=fzl
rabbitmq.password=fzl
rabbitmq.virtual-host=/
rabbitmq.port=5672
rabbitmq.username=fzl
rabbitmq.password=fzl
rabbitmq.virtual-host=/
Consumer Ack 小结
➢ 在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手
动确认
➢ 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
➢ 如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息。
动确认
➢ 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
➢ 如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息。
消费端限流
代码演示
配置
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1" >
消费端代码
package com.itheima.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* Consumer 限流机制
* 1. 确保ack机制为手动确认。
* 2. listener-container配置属性
* perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
//1.获取消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
//3. 签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* Consumer 限流机制
* 1. 确保ack机制为手动确认。
* 2. listener-container配置属性
* perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
//1.获取消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
//3. 签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
小结
➢ 在<rabbit:listener-container> 中配置 prefetch属性设置消费端一次拉取多少消息
➢ 消费端的确认模式一定为手动确认。acknowledge="manual"
➢ 消费端的确认模式一定为手动确认。acknowledge="manual"
TTL
概念
TTL 全称 Time To Live(存活时间/过期时间)
当消息到达存活时间后,还没有被消费,会被自动清除
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间
概念图
代码
producer
/**
* TTL:过期时间
* 1. 队列统一过期
*
* 2. 消息单独过期
*
*
* 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
* 队列过期后,会将队列所有消息全部移除。
* 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
*
*/
@Test
public void testTtl() {
/* for (int i = 0; i < 10; i++) {
// 发送消息
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
}*/
// 消息后处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息
message.getMessageProperties().setExpiration("5000");//消息的过期时间
//2.返回该消息
return message;
}
};
//消息单独过期
//rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
for (int i = 0; i < 10; i++) {
if(i == 5){
//消息单独过期
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
}else{
//不过期的消息
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
}
}
}
* TTL:过期时间
* 1. 队列统一过期
*
* 2. 消息单独过期
*
*
* 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
* 队列过期后,会将队列所有消息全部移除。
* 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
*
*/
@Test
public void testTtl() {
/* for (int i = 0; i < 10; i++) {
// 发送消息
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
}*/
// 消息后处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息
message.getMessageProperties().setExpiration("5000");//消息的过期时间
//2.返回该消息
return message;
}
};
//消息单独过期
//rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
for (int i = 0; i < 10; i++) {
if(i == 5){
//消息单独过期
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
}else{
//不过期的消息
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
}
}
}
配置
<!--ttl-->
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
<!--设置queue的参数-->
<rabbit:queue-arguments>
<!--x-message-ttl指队列的过期时间-->
<entry key="x-message-ttl" value="100000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
<!--设置queue的参数-->
<rabbit:queue-arguments>
<!--x-message-ttl指队列的过期时间-->
<entry key="x-message-ttl" value="100000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_ttl" >
<rabbit:bindings>
<rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<rabbit:bindings>
<rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
TTL 小结
➢ 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。
➢ 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断
这一消息是否过期。
➢ 如果两者都进行了设置,以时间短的为准。
➢ 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断
这一消息是否过期。
➢ 如果两者都进行了设置,以时间短的为准。
死信队列
概念
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以
被重新发送到另一个交换机,这个交换机就是DLX。
被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况
1. 队列消息长度到达限制;
2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3. 原队列存在消息过期设置,消息到达超时时间未被消费;
2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3. 原队列存在消息过期设置,消息到达超时时间未被消费;
队列绑定死信交换机
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
代码实现
spring配置
<!--
死信队列:
1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
3. 正常队列绑定死信交换机
设置两个参数:
* x-dead-letter-exchange:死信交换机名称
* x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--
1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
-->
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
<!--3. 正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--3.1 x-dead-letter-exchange:死信交换机名称-->
<entry key="x-dead-letter-exchange" value="exchange_dlx" />
<!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
<entry key="x-dead-letter-routing-key" value="dlx.hehe" />
<!--4.1 设置队列的过期时间 ttl-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
<!--4.2 设置队列的长度限制 max-length -->
<entry key="x-max-length" value="10" value-type="java.lang.Integer" />
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--
2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
-->
<rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
死信队列:
1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
3. 正常队列绑定死信交换机
设置两个参数:
* x-dead-letter-exchange:死信交换机名称
* x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--
1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
-->
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
<!--3. 正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--3.1 x-dead-letter-exchange:死信交换机名称-->
<entry key="x-dead-letter-exchange" value="exchange_dlx" />
<!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
<entry key="x-dead-letter-routing-key" value="dlx.hehe" />
<!--4.1 设置队列的过期时间 ttl-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
<!--4.2 设置队列的长度限制 max-length -->
<entry key="x-max-length" value="10" value-type="java.lang.Integer" />
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--
2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
-->
<rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
生产者
/**
* 发送测试死信消息:
* 1. 过期时间
* 2. 长度限制
* 3. 消息拒收
*/
@Test
public void testDlx(){
//1. 测试过期时间,死信消息
//rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
//2. 测试长度限制后,消息死信
/* for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
}*/
//3. 测试消息拒收
rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
}
* 发送测试死信消息:
* 1. 过期时间
* 2. 长度限制
* 3. 消息拒收
*/
@Test
public void testDlx(){
//1. 测试过期时间,死信消息
//rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
//2. 测试长度限制后,消息死信
/* for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
}*/
//3. 测试消息拒收
rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
}
监听
package com.itheima.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class DlxListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
int i = 3/0;//出现错误
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现异常,拒绝接受");
//4.拒绝签收,不重回队列 requeue=false
channel.basicNack(deliveryTag,true,false);
}
}
}
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class DlxListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
int i = 3/0;//出现错误
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现异常,拒绝接受");
//4.拒绝签收,不重回队列 requeue=false
channel.basicNack(deliveryTag,true,false);
}
}
}
延迟队列
概念
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
需求:
1. 下单后,30分钟未支付,取消订单,回滚库存。
2. 新用户注册成功7天后,发送短信问候。
1. 下单后,30分钟未支付,取消订单,回滚库存。
2. 新用户注册成功7天后,发送短信问候。
实现方式:
1. 定时器
2. 延迟队列
2. 延迟队列
图像展示
RabbitMQ中并未提供延迟队列功能但可以使用
TTL+死信队列 组合实现延迟队列的效果
TTL+死信队列 组合实现延迟队列的效果
代码
消费者
package com.itheima.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class OrderListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
System.out.println("根据订单id查询其状态...");
System.out.println("判断状态是否为支付成功");
System.out.println("取消订单,回滚库存....");
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现异常,拒绝接受");
//4.拒绝签收,不重回队列 requeue=false
channel.basicNack(deliveryTag,true,false);
}
}
}
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class OrderListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
System.out.println("根据订单id查询其状态...");
System.out.println("判断状态是否为支付成功");
System.out.println("取消订单,回滚库存....");
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现异常,拒绝接受");
//4.拒绝签收,不重回队列 requeue=false
channel.basicNack(deliveryTag,true,false);
}
}
}
生产者
@Test
public void testDelay() throws InterruptedException {
//1.发送订单消息。 将来是在订单系统中,下单成功后,发送消息
rabbitTemplate.convertAndSend("order_exchange","order.msg","订单信息:id=1,time=2019年8月17日16:41:47");
/*//2.打印倒计时10秒
for (int i = 10; i > 0 ; i--) {
System.out.println(i+"...");
Thread.sleep(1000);
}*/
}
public void testDelay() throws InterruptedException {
//1.发送订单消息。 将来是在订单系统中,下单成功后,发送消息
rabbitTemplate.convertAndSend("order_exchange","order.msg","订单信息:id=1,time=2019年8月17日16:41:47");
/*//2.打印倒计时10秒
for (int i = 10; i > 0 ; i--) {
System.out.println(i+"...");
Thread.sleep(1000);
}*/
}
spring配置文件
<!--
延迟队列:
1. 定义正常交换机(order_exchange)和队列(order_queue)
2. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)
3. 绑定,设置正常队列过期时间为30分钟
-->
<!-- 1. 定义正常交换机(order_exchange)和队列(order_queue)-->
<rabbit:queue id="order_queue" name="order_queue">
<!-- 3. 绑定,设置正常队列过期时间为30分钟-->
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="order_exchange_dlx" />
<entry key="x-dead-letter-routing-key" value="dlx.order.cancel" />
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="order_exchange">
<rabbit:bindings>
<rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- 2. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)-->
<rabbit:queue id="order_queue_dlx" name="order_queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="order_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
延迟队列:
1. 定义正常交换机(order_exchange)和队列(order_queue)
2. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)
3. 绑定,设置正常队列过期时间为30分钟
-->
<!-- 1. 定义正常交换机(order_exchange)和队列(order_queue)-->
<rabbit:queue id="order_queue" name="order_queue">
<!-- 3. 绑定,设置正常队列过期时间为30分钟-->
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="order_exchange_dlx" />
<entry key="x-dead-letter-routing-key" value="dlx.order.cancel" />
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="order_exchange">
<rabbit:bindings>
<rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- 2. 定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)-->
<rabbit:queue id="order_queue_dlx" name="order_queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="order_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
小结
1. 延迟队列 指消息进入队列后,可以被延迟一定时间,再进行消费。
2. RabbitMQ没有提供延迟队列功能,但是可以使用 : TTL + DLX 来实现延迟队列效果。
2. RabbitMQ没有提供延迟队列功能,但是可以使用 : TTL + DLX 来实现延迟队列效果。
日志与监控
RabbitMQ日志
RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@xxx.log
日志包含了RabbitMQ的版本号、Erlang的版本号、
RabbitMQ服务节点名称、cookie的hash值、
RabbitMQ配置文件地址、内存限制、磁盘限制、
默认账户guest的创建以及权限配置等等。
RabbitMQ服务节点名称、cookie的hash值、
RabbitMQ配置文件地址、内存限制、磁盘限制、
默认账户guest的创建以及权限配置等等。
web管控台监控
rabbitmqctl管理和监控
查看队列
# rabbitmqctl list_queues
查看exchanges
# rabbitmqctl list_exchanges
查看用户
# rabbitmqctl list_users
查看连接
# rabbitmqctl list_connections
查看消费者信息
# rabbitmqctl list_consumers
查看环境变量
# rabbitmqctl environment
查看未被确认的队列
# rabbitmqctl list_queues name messages_unacknowledged
查看单个队列的内存使用
# rabbitmqctl list_queues name memory
查看准备就绪的队列
# rabbitmqctl list_queues name messages_ready
# rabbitmqctl list_queues
查看exchanges
# rabbitmqctl list_exchanges
查看用户
# rabbitmqctl list_users
查看连接
# rabbitmqctl list_connections
查看消费者信息
# rabbitmqctl list_consumers
查看环境变量
# rabbitmqctl environment
查看未被确认的队列
# rabbitmqctl list_queues name messages_unacknowledged
查看单个队列的内存使用
# rabbitmqctl list_queues name memory
查看准备就绪的队列
# rabbitmqctl list_queues name messages_ready
消息可靠性分析与追踪
在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况
1可能是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;
2也有可能是因为交换器与队列之间不同的转发策略;甚至是交换器并没有与任何队列进行绑定,
生产者又不感知或者没有采取相应的措施;
生产者又不感知或者没有采取相应的措施;
另外RabbitMQ本身的集群策略也可能导致消息的丢失。
在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。
消息追踪-Firehose
firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式
发送到默认的exchange上。这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类
型的exchange。发送到这个exchange上的消息的routing key为 publish.exchangename 和
deliver.queuename。其中exchangename和queuename为实际exchange和queue的名称,分别
对应生产者投递到exchange的消息,和消费者从queue上获取的消息。
发送到默认的exchange上。这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类
型的exchange。发送到这个exchange上的消息的routing key为 publish.exchangename 和
deliver.queuename。其中exchangename和queuename为实际exchange和queue的名称,分别
对应生产者投递到exchange的消息,和消费者从queue上获取的消息。
注意:打开 trace 会影响消息写入功能,适当打开后请关闭。
rabbitmqctl trace_on:开启Firehose命令
rabbitmqctl trace_off:关闭Firehose命令
消息追踪-rabbitmq_tracing
rabbitmq_tracing和Firehose在实现上如出一辙,只不过rabbitmq_tracing的方式比Firehose多了一
层GUI的包装,更容易使用和管理。
层GUI的包装,更容易使用和管理。
启用插件:rabbitmq-plugins enable rabbitmq_tracing
RabbitMQ 应用问题
消息可靠性保障
• 消息补偿机制
消息幂等性处理
• 乐观锁解决方案
幂等性指一次和多次请求某一个资源,对于资源本身应该具有同样的结果。也就是说,其任
意多次执行对资源本身所产生的影响均与一次执行的影响相同。
在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果
意多次执行对资源本身所产生的影响均与一次执行的影响相同。
在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果
RabbitMQ 集群搭建
Conte
Conte
RabbitMQ高可用集群
Docker技术
kibana常用命令
使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
微服务(SpringCloud)
微服务治理
认识微服务
服务架构演变
单体架构
将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:
架构简单
部署成本低
架构简单
部署成本低
缺点:耦合度高
分布式架构
根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。
优点:
降低服务耦合
有利于服务升级拓展
降低服务耦合
有利于服务升级拓展
服务治理
微服务是一种经过良好架构设计的分布式架构方案,
微服务架构特征:
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
面向服务:微服务对外暴露业务接口
自治:团队独立、技术独立、数据独立、部署独立
隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务架构特征:
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
面向服务:微服务对外暴露业务接口
自治:团队独立、技术独立、数据独立、部署独立
隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务技术对比
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。
在国内最知名的就是SpringCloud和阿里巴巴的Dubbo。
在国内最知名的就是SpringCloud和阿里巴巴的Dubbo。
微服务
SpringCloud + Feign
使用SpringCloud技术栈服务
接口采用Restful风格
服务调用采用Feign方式
接口采用Restful风格
服务调用采用Feign方式
SpringCloudAlibaba + Dubbo
使用SpringCloudAlibaba技术栈服务
接口采用Dubbo协议标准
服务调用采用Dubbo方式
接口采用Dubbo协议标准
服务调用采用Dubbo方式
SpringCloudAlibaba + Feign
使用SpringCloudAlibaba技术栈服务
接口采用Restful风格
服务调用采用Feign方式
接口采用Restful风格
服务调用采用Feign方式
Dubbo原始模式
基于Dubbo老旧技术体系服务
接口采用Dubbo协议标准
服务调用采用Dubbo方式
接口采用Dubbo协议标准
服务调用采用Dubbo方式
SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
版本兼容如下
微服务拆分案例
服务拆分及远程调用
单一职责:不同微服务,不要重复开发相同业务
数据独立:不要访问其它微服务的数据库
面向服务:将自己的业务暴露为接口,供其它微服务调用
数据独立:不要访问其它微服务的数据库
面向服务:将自己的业务暴露为接口,供其它微服务调用
远程调用
根据订单id查询订单功能
步骤
1)注册RestTemplate
在order-service的OrderApplication中注册RestTemplate
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2)服务远程调用RestTemplate
修改order-service中的OrderService的queryOrderById方法:
@Service public class OrderService {
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// TODO 2.查询用户
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
// 3.封装user信息
order.setUser(user);
// 4.返回
return order;
}
}
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// TODO 2.查询用户
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
// 3.封装user信息
order.setUser(user);
// 4.返回
return order;
}
}
提供者与消费者
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
服务调用关系
服务提供者:暴露接口给其它微服务调用
服务消费者:调用其它微服务提供的接口提供者与消费者角色其实是
相对的一个服务可以同时是服务提供者和服务消费者
服务提供者:暴露接口给其它微服务调用
服务消费者:调用其它微服务提供的接口提供者与消费者角色其实是
相对的一个服务可以同时是服务提供者和服务消费者
eureka注册中心
远程调用的问题
eureka原理
消费者该如何获取服务提供者具体信息?
服务提供者启动时向eureka注册自己的信息eureka
保存这些信息消费者根据服务名称向eureka拉取提
供者信息如果有多个服务提供者,
消费者该如何选择?
服务消费者利用负载均衡算法,从服务列表中挑选一个消费者
如何感知服务提供者健康状态?
服务提供者会每隔30秒向EurekaServer发送心跳请求,报告
健康状态eureka会更新记录服务列表信息,心跳不正常会被
剔除消费者就可以拉取到最新的信息
服务提供者启动时向eureka注册自己的信息eureka
保存这些信息消费者根据服务名称向eureka拉取提
供者信息如果有多个服务提供者,
消费者该如何选择?
服务消费者利用负载均衡算法,从服务列表中挑选一个消费者
如何感知服务提供者健康状态?
服务提供者会每隔30秒向EurekaServer发送心跳请求,报告
健康状态eureka会更新记录服务列表信息,心跳不正常会被
剔除消费者就可以拉取到最新的信息
搭建EurekaServer
搭建EurekaServer服务步骤如下:
1.创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
1.创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2.编写启动类,添加@EnableEurekaServer注解
3.添加application.yml文件
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
服务注册
在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在application.yml文件
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
服务发现
order-service完成服务注册
order-service虽然是消费者,但与user-service一样都是eureka的client端,同样可以实现服务注册
在order-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
在application.yml文件,编写配置
在order-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
在application.yml文件,编写配置
<dependency>
<groupId>org.springframework.cloud</groupId>
| <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<groupId>org.springframework.cloud</groupId>
| <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
spring:
application:
name: orderservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
application:
name: orderservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
在order-service完成服务拉取
服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡
1.修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
String url = "http://userservice/user/" + order.getUserId();
2.在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
Ribbon负载均衡原理
负载均衡原理
负载均衡流程
负载均衡策略
通过定义IRule实现可以修改负载均衡规则,有两种方式:
代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
public IRule randomRule(){
return new RandomRule();
}
配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule# 负载均衡规则
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule# 负载均衡规则
懒加载
饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,
请求时间会很长。而饥饿加载则会在项目启动时创建,
降低第一次访问的耗时,通过下面配置开启饥饿加载:
请求时间会很长。而饥饿加载则会在项目启动时创建,
降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务饥饿加载
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务饥饿加载
nacos注册中心
认识和安装Nacos
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富
Nacos安装
Linux
# 2.Linux安装
Linux或者Mac安装方式与Windows类似。
## 21.安装JDK
Nacos依赖于JDK运行,索引Linux上也需要安装JDK才行。
上传jdk安装包:
![image-20210402172334810](assets/image-20210402172334810.png)
上传到某个目录,例如:`/usr/local/`
然后解压缩:
```sh
tar -xvf jdk-8u144-linux-x64.tar.gz
```
然后重命名为java
配置环境变量:
```sh
export JAVA_HOME=/usr/local/java
export PATH=$PATH:$JAVA_HOME/bin
```
设置环境变量:
```sh
source /etc/profile
``
## 2.2.上传安装包
如图:
![image-20210402161102887](assets/image-20210402161102887.png)
也可以直接使用课前资料中的tar.gz:
![image-20210402161130261](assets/image-20210402161130261.png)
上传到Linux服务器的某个目录,例如`/usr/local/src`目录下:
![image-20210402163715580](assets/image-20210402163715580.png)
## 2.3.解压
令解压缩安装包:
```sh
tar -xvf nacos-server-1.4.1.tar.gz
```
然后删除安装包:
```sh
rm -rf nacos-server-1.4.1.tar.gz
```
目录中最终样式:
![image-20210402163858429](assets/image-20210402163858429.png)
目录内部:
![image-20210402164414827](assets/image-20210402164414827.png)
## 2.4.端口配置
与windows中类似
## 2.5.启动
在nacos/bin目录中,输入命令启动Nacos:
```sh
sh startup.sh -m standalone
```
# 3.Nacos的依赖
父工程:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
```
客户端:
```xml
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
```
Linux或者Mac安装方式与Windows类似。
## 21.安装JDK
Nacos依赖于JDK运行,索引Linux上也需要安装JDK才行。
上传jdk安装包:
![image-20210402172334810](assets/image-20210402172334810.png)
上传到某个目录,例如:`/usr/local/`
然后解压缩:
```sh
tar -xvf jdk-8u144-linux-x64.tar.gz
```
然后重命名为java
配置环境变量:
```sh
export JAVA_HOME=/usr/local/java
export PATH=$PATH:$JAVA_HOME/bin
```
设置环境变量:
```sh
source /etc/profile
``
## 2.2.上传安装包
如图:
![image-20210402161102887](assets/image-20210402161102887.png)
也可以直接使用课前资料中的tar.gz:
![image-20210402161130261](assets/image-20210402161130261.png)
上传到Linux服务器的某个目录,例如`/usr/local/src`目录下:
![image-20210402163715580](assets/image-20210402163715580.png)
## 2.3.解压
令解压缩安装包:
```sh
tar -xvf nacos-server-1.4.1.tar.gz
```
然后删除安装包:
```sh
rm -rf nacos-server-1.4.1.tar.gz
```
目录中最终样式:
![image-20210402163858429](assets/image-20210402163858429.png)
目录内部:
![image-20210402164414827](assets/image-20210402164414827.png)
## 2.4.端口配置
与windows中类似
## 2.5.启动
在nacos/bin目录中,输入命令启动Nacos:
```sh
sh startup.sh -m standalone
```
# 3.Nacos的依赖
父工程:
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
```
客户端:
```xml
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
```
windows
# Nacos安装指南
# 1.Windows安装
开发阶段采用单机安装即可。
## 1.1.下载安装包
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
如图:
![image-20210402161102887](assets/image-20210402161102887.png)
本课程采用1.4.1.版本的Nacos,课前资料已经准备了安装包:
![image-20210402161130261](assets/image-20210402161130261.png)
windows版本使用`nacos-server-1.4.1.zip`包即可。
## 1.2.解压
将这个包解压到任意非中文目录下,如图:
![image-20210402161843337](assets/image-20210402161843337.png)
目录说明:
- bin:启动脚本
- conf:配置文件
## 1.3.端口配置
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
**无法关闭占用8848端口的进程**,也可以进入nacos的conf目录,修改配置文件中的端口:
![mage-20210402162008280](assets/image-20210402162008280.png)
修改其中的内容:
![image-20210402162251093](assets/image-20210402162251093.png)
## 1.4.启动
启动非常简单,进入bin目录,结构如下:
![image-20210402162350977](assets/image-20210402162350977.png)
然后执行命令即可:
- windows命令:
```
startup.cmd -m standalone
```
执行后的效果如图:
![iage-20210402162526774](assets/image-20210402162526774.png)
## .5.访问
在浏览器输入地址:http://127.0.0.1:8848/nacos即可:
![image-20210402162630427](assets/image-20210402162630427.png)
默认的账号和密码都是nacos,进入后:
![image-20210402162709515](assets/image-20210402162709515.png)
# 1.Windows安装
开发阶段采用单机安装即可。
## 1.1.下载安装包
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
如图:
![image-20210402161102887](assets/image-20210402161102887.png)
本课程采用1.4.1.版本的Nacos,课前资料已经准备了安装包:
![image-20210402161130261](assets/image-20210402161130261.png)
windows版本使用`nacos-server-1.4.1.zip`包即可。
## 1.2.解压
将这个包解压到任意非中文目录下,如图:
![image-20210402161843337](assets/image-20210402161843337.png)
目录说明:
- bin:启动脚本
- conf:配置文件
## 1.3.端口配置
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
**无法关闭占用8848端口的进程**,也可以进入nacos的conf目录,修改配置文件中的端口:
![mage-20210402162008280](assets/image-20210402162008280.png)
修改其中的内容:
![image-20210402162251093](assets/image-20210402162251093.png)
## 1.4.启动
启动非常简单,进入bin目录,结构如下:
![image-20210402162350977](assets/image-20210402162350977.png)
然后执行命令即可:
- windows命令:
```
startup.cmd -m standalone
```
执行后的效果如图:
![iage-20210402162526774](assets/image-20210402162526774.png)
## .5.访问
在浏览器输入地址:http://127.0.0.1:8848/nacos即可:
![image-20210402162630427](assets/image-20210402162630427.png)
默认的账号和密码都是nacos,进入后:
![image-20210402162709515](assets/image-20210402162709515.png)
Nacos快速入门
使用步骤
服务注册到Nacos
1.在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2.注释掉order-service和user-service中原有的eureka依赖
添加nacos的客户端依赖:
<!-- nacos客户端依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
4.修改user-service&order-service中的application.yml文件,注释eureka地址,添加nacos地址:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
启动并测试
Nacos服务分级存储模型
模型案例
服务跨集群调用问题
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其它集群
本地集群不可访问时,再去访问其它集群
服务集群属性
1.修改application.yml,添加如下内容
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ,杭州
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ,杭州
2.在Nacos控制台可以看到集群变化
根据集群负载均衡
1、修改order-service中的application.yml,设置集群为HZ
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置
2、然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
、根据权重负载均衡
实际部署中会出现这样的场景:服务器设备性能有差异,部分实例所在机器性能较好
,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
Nacos环境隔离
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
namespace
在Nacos控制台可以创建namespace,用来隔离不同环境
然后填写一个新的命名空间信息:
保存后会看到这个namespace的ID
修改order-service的application.yml,添加namespace
spring:
datasource:
url: jdbc:mysql://localhost:3306/heima?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH # 上海
namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
datasource:
url: jdbc:mysql://localhost:3306/heima?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH # 上海
namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
重启order-service后,再来查看控制台
此时访问order-service,因为namespace不同,会导致找不到userservice
Nacos注册中心原理
临时实例和非临时实例
服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例
临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会
总结
Nacos与eureka的共同点
都支持服务提供者心跳方式做健康检测
都支持服务注册和服务拉取
Nacos与Eureka的区别
Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
临时实例心跳不正常会被剔除,非临时实例则不会被剔除
Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
Nacos配置管理
统一配置管理
配置更改热更新
向nacos中添加配置文件
在弹出表单中填写配置信息
配置获取的步骤如下:
具体代码
1.引入Nacos的配置管理客户端依赖
<!--nacos配置管理依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
2.在userservice中的resource目录添加一个bootstrap.yml文件
,这个文件是引导文件,优先级高于application.yml
,这个文件是引导文件,优先级高于application.yml
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config: file-extension: yaml # 文件后缀名
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config: file-extension: yaml # 文件后缀名
3.在user-service中将pattern.dateformat这个属性注入到UserController中做测试
@RestController
@RequestMapping("/user") public class UserController {
// 注入nacos中的配置属性
@Value("${pattern.dateformat}")
private String dateformat;
// 编写controller,通过日期格式化器来格式化现在时间并返回
@GetMapping("now")
public String now(){
return LocalDate.now().format( DateTimeFormatter.ofPattern(dateformat, Locale.CHINA) ;
} // ... 略 }
@RequestMapping("/user") public class UserController {
// 注入nacos中的配置属性
@Value("${pattern.dateformat}")
private String dateformat;
// 编写controller,通过日期格式化器来格式化现在时间并返回
@GetMapping("now")
public String now(){
return LocalDate.now().format( DateTimeFormatter.ofPattern(dateformat, Locale.CHINA) ;
} // ... 略 }
总结
将配置交给Nacos管理的步骤
在Nacos中添加配置文件
在微服务中引入nacos的config依赖
在微服务中添加bootstrap.yml,配置nacos地址、
当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件
当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件
配置热更新
Nacos中的配置文件变更后,
微服务无需重启就可以感知。
不过需要通过下面两种配置实现
微服务无需重启就可以感知。
不过需要通过下面两种配置实现
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
方式二:使用@ConfigurationProperties注解
Nacos配置更改后,微服务可以实现热更新
注意事项
不是所有的配置都适合放到配置中心,维护起来比较麻烦
建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
配置共享
多种配置
[spring.application.name]-[spring.profiles.active].yaml,
例如:userservice-dev.yaml[spring.application.name].yaml,例如:userservice.yaml
例如:userservice-dev.yaml[spring.application.name].yaml,例如:userservice.yaml
无论profile如何变化,[spring.application.name].yaml这个文
件一定会加载,因此多环境共享配置可以写入这个文件
件一定会加载,因此多环境共享配置可以写入这个文件
多种配置的优先级
总结
微服务会从nacos读取的配置文件
[服务名]-[spring.profile.active].yaml,环境配置
[服务名].yaml,默认配置,多环境共享
优先级:[服务名]-[环境].yaml >[服务名].yaml > 本地配置
多服务共享配置
不同微服务之间可以共享配置文件,通过下面的两种方式来指定
方式一
方式二
多种配置的优先级
搭建Nacos集群
集群搭建步骤
搭建MySQL集群并初始化数据库表
下载解压nacos
修改集群配置(节点信息)、数据库配置
分别启动多个nacos节点
nginx反向代理
Feign远程调用
Feign替代RestTemplete
RestTemplate方式调用存在的问题
存在下面的问题
代码可读性差,编程体验不统一
参数复杂URL难以维护
Feign的介绍
Feign是一个声明式的http客户端
定义和使用Feign客户端
使用Feign的步骤如下
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在order-service的启动类添加注解开启Feign的功能
编写Feign客户端
用Feign客户端代替RestTemplate
总结
自定义配置
自定义Feign的配置
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下 一般需要的是日志级别
配置Feign日志有两种方式
方式一:配置文件方式
全局生效
局部生效
方式二:java代码方式
而后如果是全局配置,则把它放到@EnableFeignClients这个注解中
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
如果是局部配置,则把它放到@FeignClient这个注解中
@FeignClient(value = "userservice", configuration = FeignClientConfiguration.class)
Feign使用优化
Feign的性能优化
Feign底层的客户端实现
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
因此优化Feign的性能主要包括
使用连接池代替默认的URLConnection
日志级别,最好用basic或none
Feign的性能优化-连接池配置
Feign添加HttpClient的支持
引入依赖
配置连接池
最佳实践
方式一(继承)
给消费者的FeignClient和提供者的controller定义统一的父接口作为标准
服务紧耦合
父接口参数列表中的映射不会被继承
方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、
默认的Feign配置都放到这个模块中,提供给所有消费者使用
默认的Feign配置都放到这个模块中,提供给所有消费者使用
抽取FeignClient
首先创建一个module,命名为feign-api,然后引入feign的starter依赖
将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
在order-service中引入feign-api的依赖
修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
重启测试
当定义的FeignClient不在SpringBootApplication的扫描包
范围时,这些FeignClient无法使用。有两种方式解决
范围时,这些FeignClient无法使用。有两种方式解决
GateWay服务网关
为什么需要网关
网关功能
身份认证和权限校验
服务路由、负载均衡
请求限流
网关的技术实现
在SpringCloud中网关的实现包括两种
gateway
而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
zuul
Zuul是基于Servlet的实现,属于阻塞式编程。
gateway快速入门
搭建网关服务的步骤
创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖
编写路由配置及nacos地址
路由配置包括
路由id:路由的唯一标示
路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
路由断言(predicates):判断路由的规则
路由过滤器(filters):对请求或响应做处理
断言工厂
路由断言工厂Route Predicate Factory
网关路由可以配置的内容包括
路由id:路由唯一标示
uri:路由目的地,支持lb和http两种
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
filters:路由过滤器,处理请求或响应
过滤器工厂
路由过滤器 GatewayFilter
GatewayFilter是网关中提供的一种过滤器
可以对进入网关的请求和微服务返回的响应做处理
可以对进入网关的请求和微服务返回的响应做处理
案例
给所有进入userservice的请求添加一个请求头
默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下
全局过滤器
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,
与GatewayFilter的作用一样。区别在于GatewayFilter通过配
置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。
与GatewayFilter的作用一样。区别在于GatewayFilter通过配
置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口
定义全局过滤器,拦截并判断用户身份
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
参数中是否有authorization,authorization
参数值是否为admin如果同时满足则放行,否则拦截
参数中是否有authorization,authorization
参数值是否为admin如果同时满足则放行,否则拦截
步骤1:自定义过滤器
过滤器执行顺序
当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、
GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,
由我们自己指定路由过滤器和defaultFilter的order由Spring指定,默认是
按照声明顺序从1递增。当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,
由我们自己指定路由过滤器和defaultFilter的order由Spring指定,默认是
按照声明顺序从1递增。当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
跨域问题
域名不一致就是跨域,主要包括
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现
统一网关Gateway
限流过滤器-漏桶算法
令牌桶算法
计数器算法
异步通信
消息可靠性
消息从生产者发送到exchange,再到queue,再到消费者,有哪些导致消息丢失的可能性?
发送时丢失
生产者发送的消息未送达exchange
消息到达exchange后未到达queue
MQ宕机,queue将消息丢失
consumer接收到消息后未消费就宕机
生产者消息确认
RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。
消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
publisher-confirm,发送者确认
消息成功投递到交换机,返回ack
消息未投递到交换机,返回nack
publisher-return,发送者回执
消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因
SpringAMQP实现生产者确认
在publisher这个微服务的application.yml中添加配置
配置说明
publish-confirm-type:开启publisher-confirm,这里支持两种类型
simple:同步等待confirm结果,直到超时
correlated:异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback
publish-returns:开启publish-return功能,同样是基于callback机制,不过是定义ReturnCallback
template.mandatory:定义消息路由失败时的策略。true,则调用ReturnCallback;false:则直接丢弃消息
每个RabbitTemplate只能配置一个ReturnCallback
发送消息,指定消息ID、消息ConfirmCallback
消息持久化
MQ默认是内存存储消息,开启持久化功能可以确保缓存在MQ中的消息不丢失。
交换机持久化
队列持久化
消息持久化,SpringAMQP中的的消息默认是持久的,可以通过MessageProperties中的DeliveryMode来指定的
消费者消息确认
RabbitMQ支持消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息
SpringAMQP则允许配置三种确认模式
manual:手动ack,需要在业务代码结束后,调用api发送ack。
auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack
none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除
配置方式
消费失败重试机制
当消费者出现异常后,消息会不断requeue(重新入队)到队列,再重新发送给消费者,
然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力
然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力
消费者失败消息处理策略
在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecoverer接口来处理
RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
死信交换机
初始死信队列
当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter)
消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false
消息是一个过期消息,超时无人消费
要投递的队列消息堆积满了,最早的消息可能成为死信
如果该队列配置了dead-letter-exchange属性,指定了一个交换机,
那么队列中的死信就会投递到这个交换机中,而这个交换机称为
死信交换机(Dead Letter Exchange,简称DLX)
那么队列中的死信就会投递到这个交换机中,而这个交换机称为
死信交换机(Dead Letter Exchange,简称DLX)
TTL
TTL,也就是Time-To-Live。如果一个队列中的消息TTL结束仍未消费,则会变为死信,ttl超时分为两种情况
消息所在的队列设置了存活时间
消息本身设置了存活时间
基于注解方式生成
延迟队列
利用TTL结合死信交换机,我们实现了消息发出后,消费者延迟收到消息的效果。这种消息模式就称为延迟队列(Delay Queue)模式。
延迟队列的使用场景包括
延迟发送短信
用户下单,如果用户在15 分钟内未支付,则自动取消
预约工作会议,20分钟后自动通知所有参会人员
延迟队列插件
DelayExchange的本质还是官方的三种交换机,只是添加了延迟功能。因此使用时只
需要声明一个交换机,交换机的类型可以是任意类型,然后设定delayed属性为true即可。
需要声明一个交换机,交换机的类型可以是任意类型,然后设定delayed属性为true即可。
然后我们向这个delay为true的交换机中发送消息,一定要给消息添加一个header:x-delay,值为延迟的时间,单位为毫秒
惰性队列
消息堆积问题
当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中
的消息堆积,直到队列存储消息达到上限。最早接收到的消息,可能就会
成为死信,会被丢弃,这就是消息堆积问题。
的消息堆积,直到队列存储消息达到上限。最早接收到的消息,可能就会
成为死信,会被丢弃,这就是消息堆积问题。
解决消息堆积有三种种思路
增加更多消费者,提高消费速度
在消费者内开启线程池加快消息处理速度
扩大队列容积,提高堆积上限
惰性队列
从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念,也就是惰性队列
惰性队列的特征如下
接收到消息后直接存入磁盘而非内存
消费者要消费消息时才会从磁盘中读取并加载到内存
支持数百万条的消息存储
要设置一个队列为惰性队列,只需要在声明队列时,指定x-queue-mode属性为lazy
即可。可以通过命令行将一个运行中的队列修改为惰性队列
即可。可以通过命令行将一个运行中的队列修改为惰性队列
rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues
总结
MQ集群
集群分类
RabbitMQ的是基于Erlang语言编写,而Erlang又是一个面向并发的语言,天然支持集群模式。RabbitMQ的集群有两种模式
普通集群
普通集群:是一种分布式集群,将队列分散到集群的各个节点,从而提高整个集群的并发能力。
具备下列特征
会在集群的各个节点间共享部分数据,包括:交换机、队列元信息。不包含队列中的消息。
当访问集群某节点时,如果队列不在该节点,会从数据所在节点传递到当前节点并返回
队列所在节点宕机,队列中的消息就会丢失
镜像集群
镜像集群:是一种主从集群,普通集群的基础上,添加了主从备份功能,提高集群的数据可用性。
镜像集群:本质是主从模式,具备下面的特征
交换机、队列、队列中的消息会在各个mq的镜像节点之间同步备份。
创建队列的节点被称为该队列的主节点,备份到的其它节点叫做该队列的镜像节点
一个队列的主节点可能是另一个队列的镜像节点
所有操作都是主节点完成,然后同步给镜像节点
主宕机后,镜像节点会替代成新的主
仲裁队列
底层采用Raft协议确保主从的数据一致性。
仲裁队列:仲裁队列是3.8版本以后才有的新功能,用来替代镜像队列,具备下列特征
与镜像队列一样,都是主从模式,支持主从数据同步
使用非常简单,没有复杂的配置
主从同步基于Raft协议,强一致
分布式缓存
Redis持久化
RDB
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。
简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
快照文件称为RDB文件,默认是保存在当前运行目录。
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
fork采用的是copy-on-write技术
当主进程执行读操作时,访问共享内存
当主进程执行写操作时,则会拷贝一份数据,执行写操作
RDB方式bgsave的基本流程
fork主进程得到一个子进程,共享内存空间
子进程读取内存数据并写入新的RDB文件
用新RDB文件替换旧的RDB文件
RDB会在什么时候执行?save 60 1000代表什么含义
默认是服务停止时
代表60秒内至少执行1000次修改则触发RDB
RDB的缺点
RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
fork子进程、压缩、写出RDB文件都比较耗时
AOF
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作
但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
Redis主从
搭建主从架构
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离
原理图
主从数据同步原理
主从第一次同步是全量同步
master如何判断slave是不是第一次来同步数据?
Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。
每一个master都有唯一的replid,slave则会继承master节点的replid
每一个master都有唯一的replid,slave则会继承master节点的replid
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。
slave完成同步时也会记录当前同步的offset。如果slave的offset小于
master的offset,说明slave数据落后于master,需要更新。
slave完成同步时也会记录当前同步的offset。如果slave的offset小于
master的offset,说明slave数据落后于master,需要更新。
简述全量同步的流程?
slave节点请求增量同步
master节点判断replid,发现不一致,拒绝增量同步
master将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步
如果slave重启后同步,则执行增量同步
可以从以下几个方面来优化Redis主从就集群
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
简述全量同步和增量同步区别
全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
slave节点第一次连接master节点时
slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
slave节点断开又恢复,并且在repl_baklog中能找到offset时
Redis哨兵
哨兵的作用和原理
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
哨兵的结构和作用如下:
哨兵的结构和作用如下:
监控:Sentinel 会不断检查您的master和slave是否按预期工作
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
选举新的master
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的
首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
最后是判断slave节点的运行id大小,越小优先级越高。
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
如何实现故障转移
当选中了其中一个slave为新的master后(例如slave1),故障的转移的步骤如下:
sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
Sentinel的三个作用是什么?
监控
故障转移
通知
Sentinel如何判断一个redis实例是否健康?
每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
如果大多数sentinel都认为实例主观下线,则判定服务下线
如果大多数sentinel都认为实例主观下线,则判定服务下线
故障转移步骤有哪些
首先选定一个slave作为新的master,执行slaveof no one
然后让所有节点都执行slaveof 新master
修改故障节点配置,添加slaveof 新master
搭建哨兵集群
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,
Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate
底层利用lettuce实现了节点的感知和自动切换
Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate
底层利用lettuce实现了节点的感知和自动切换
RedisTemplate的哨兵模式
在pom文件中引入redis的starter依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
然后在配置文件application.yml中指定sentinel相关信息
spring:
redis:
sentinel:
master: mymaster # 指定master名称
nodes: # 指定redis-sentinel集群信息
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003
redis:
sentinel:
master: mymaster # 指定master名称
nodes: # 指定redis-sentinel集群信息
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003
配置主从读写分离
Redis分片集群
搭建分片集群
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
海量数据存储问题
高并发写的问题
使用分片集群可以解决上述问题,分片集群特征
集群中有多个master,每个master保存不同数据
每个master都可以有多个slave节点
master之间通过ping监测彼此健康状态
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
散列插槽
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到
Redis如何判断某个key应该在哪个实例?
将16384个插槽分配到不同的实例
根据key的有效部分计算哈希值,对16384取余
余数作为插槽,寻找插槽所在实例即可
根据key的有效部分计算哈希值,对16384取余
余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个Redis实例?
这一类数据使用相同的有效部分,例如key都以{typeId}为前缀
集群伸缩
添加一个节点到集群
故障转移
当集群中有一个master宕机会发生什么呢?
数据迁移
利用cluster failover命令可以手动让集群中的某个master宕机,
切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下
切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下
手动的Failover支持三种不同模式
缺省:默认的流程,如图1~6歩
force:省略了对offset的一致性校验
takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
使用命令:执行cluster failover命令 重新成为master
RedisTemplate访问分片集群
RedisTemplate底层同样基于lettuce实现了分片
集群的支持,而使用的步骤与哨兵模式基本一致:
集群的支持,而使用的步骤与哨兵模式基本一致:
引入redis的starter依赖
配置分片集群地址
配置读写分离
配置分片集群地址
配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异
分布式搜索
ES
微服务保护
初识Sentinel
雪崩问题及解决方案
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。
图示
解决方案
方案一
超时处理
设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
方案二
舱壁模式
限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
方案三
熔断降级
由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
方案四
流量控制
限制业务访问的QPS,避免服务因流量的突增而故障。
服务保护技术对比
Sentinel介绍和安装
Sentinel 具有以下特征
丰富的应用场景
Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀
(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控
Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态
Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点
Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
微服务整合Sentinel
引入sentinel依赖
配置控制台地址
访问任何地址,都可验证
流量控制
快速入门
簇点链路
就是项目内的调用链路,链路中被监控的每个接口就是一个资源。
默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint),
因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。
默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint),
因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。
流控、熔断等都是针对簇点链路中的资源来设置的
通过添加流控规则实现
流控模式
直接
统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
关联
统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
使用场景
比如用户支付时需要修改订单状态,同时用户要查询订单。
查询和修改操作会争抢数据库锁,产生竞争。业务需求是
有限支付和更新订单的业务,因此当修改订单业务触发阈
值时,需要对查询订单业务限流。
查询和修改操作会争抢数据库锁,产生竞争。业务需求是
有限支付和更新订单的业务,因此当修改订单业务触发阈
值时,需要对查询订单业务限流。
当/write资源访问量触发阈值时,就会对/read资源限流,避免影响/write资源
需求:
在OrderController新建两个端点:/order/query和/order/update,无需实现业务配置流控规则,
当/order/ update资源被访问的QPS超过5时,对/order/query请求限流
在OrderController新建两个端点:/order/query和/order/update,无需实现业务配置流控规则,
当/order/ update资源被访问的QPS超过5时,对/order/query请求限流
小结
满足下面条件可以使用关联模式:
两个有竞争关系的资源一个优先级较高,一个优先级较低
两个有竞争关系的资源一个优先级较高,一个优先级较低
链路
统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值
需求
需求:有查询订单和创建订单业务,两者都需要查询商品。
针对从查询订单进入到查询商品的请求统计,并设置限流。
针对从查询订单进入到查询商品的请求统计,并设置限流。
步骤
步骤:
1.在OrderService中添加一个queryGoods方法,不用实现业务在OrderController中,
2.改造/order/query端点,调用OrderService中的queryGoods方法
3.在OrderController中添加一个/order/save的端点,调用OrderService的queryGoods方法给queryGoods
4.设置限流规则,从/order/query进入queryGoods的方法限制QPS必须小于2
1.在OrderService中添加一个queryGoods方法,不用实现业务在OrderController中,
2.改造/order/query端点,调用OrderService中的queryGoods方法
3.在OrderController中添加一个/order/save的端点,调用OrderService的queryGoods方法给queryGoods
4.设置限流规则,从/order/query进入queryGoods的方法限制QPS必须小于2
流控效果
快速失败
达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式
warm up
预热模式,对超出阈值的请求同样是拒绝并抛出异常。
但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 threshold / coldFactor,
持续指定时长后,逐渐提高到threshold值。而coldFactor的默认值是3.例如,
我设置QPS的threshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10.
持续指定时长后,逐渐提高到threshold值。而coldFactor的默认值是3.例如,
我设置QPS的threshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10.
排队等待
让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长
当请求超过QPS阈值时,快速失败和warm up 会拒绝新的请求并抛出异常。
而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔
依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超
出最大时长,则会被拒绝。
而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔
依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超
出最大时长,则会被拒绝。
热点参数限流
热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。
在热点参数限流的高级选项中,可以对部分参数设置例外配置
隔离和降级
FeignClient整合Sentinel
虽然限流可以尽量避免因高并发而引起的服务故障,但服务还会因为
其它原因而故障。而要将这些故障控制在一定范围,避免雪崩,就要
靠线程隔离(舱壁模式)和熔断降级手段了。
其它原因而故障。而要将这些故障控制在一定范围,避免雪崩,就要
靠线程隔离(舱壁模式)和熔断降级手段了。
不管是线程隔离还是熔断降级,都是对客户端(调用方)的保护
修改OrderService的application.yml文件,开启Feign的Sentinel功能
给FeignClient编写失败后的降级逻辑
方式一:FallbackClass,无法对远程调用的异常做处理
FallbackFactory,可以对远程调用的异常做处理
在feing-api项目中定义类,实现FallbackFactory
步骤二:在feing-api项目中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean
步骤三:在feing-api项目中的UserClient接口中使用UserClientFallbackFactory
线程隔离(舱壁模式)
线程隔离有两种方式实现
线程池隔离
优点
支持主动超时
支持异步调用
支持异步调用
缺点
线程的额外开销比较大
场景
低扇出
信号量隔离(Sentinel默认采用)
优点
轻量级,无额外开销
缺点
不支持主动超时
不支持异步调用
不支持异步调用
场景
高频调用
高扇出
高扇出
线程隔离(舱壁模式)
在添加限流规则时,可以选择两种阈值类型
QPS:就是每秒的请求数
线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现舱壁模式。
线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现舱壁模式。
熔断降级
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、
慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;
而当服务恢复时,断路器会放行访问该服务的请求。
慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;
而当服务恢复时,断路器会放行访问该服务的请求。
熔断策略-慢调用
慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,
如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
RT超过500ms的调用是慢调用,统计最近10000ms内的请求,如果请求量超过10次,并且慢调用
比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
需求
需求:给 UserClient的查询用户接口设置降级规则,慢调用的RT
阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例
为0.4,熔断时长为5
阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例
为0.4,熔断时长为5
熔断策略-异常比例、异常数
异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,
并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。
并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。
需求:给 UserClient的查询用户接口设置降级规则,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5s
授权规则
授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式
白名单:来源(origin)在白名单内的调用者允许访问
黑名单:来源(origin)在黑名单内的调用者不允许访问
Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的
public interface RequestOriginParser {
/**
* 从请求request对象中获取origin,获取方式自定义
*/
String parseOrigin(HttpServletRequest request);
}
/**
* 从请求request对象中获取origin,获取方式自定义
*/
String parseOrigin(HttpServletRequest request);
}
在gateway服务中,利用网关的过滤器添加名为gateway的origin头
给/order/{orderId} 配置授权规则
自定义异常结果
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口
BlockException包含多个子类
实现
规则持久化
管理的三种模式
规则管理模式-原始模式
控制台配置的规则直接推送到Sentinel客户端,也就是我们的应用。然后保存在内存中,服务重启则丢失
规则管理模式-pull模式
pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
规则管理模式-push模式
push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
实现push模式
分布式事务
事务的四个属性(ACID)
A——原子性
事务中的所有操作,要么全部成功,要么全部失败
C——一致性
要保证数据库内部完整性约束、声明性约束
I——隔离性
对同一资源操作的事务不能同时发生
D——持久性
对数据库做的一切修改将永久保存,不管是否出现故障
分布式事务问题
在分布式系统下,一个业务跨越多个服务或数据源,每个服务都是一个分支事务,
要保证所有分支事务最终状态一致,这样的事务就是分布式事务。
要保证所有分支事务最终状态一致,这样的事务就是分布式事务。
理论基础
CAP定理
Consistency(一致性)
Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致
Availability(可用性)
Availability (可用性):用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝
Partition tolerance (分区容错性)
Partition(分区):因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。
Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务
Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务
原型图
分布式系统节点通过网络连接,一定会出现分区问题(P)
当分区出现时,系统的一致性(C)和可用性(A)就无法同时满足
当分区出现时,系统的一致性(C)和可用性(A)就无法同时满足
BASE理论
BASE理论是对CAP的一种解决思路,包含三个思想
Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
AP模式
各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
CP模式
各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
分布式事务模型
解决分布式事务,各个子系统之间必须能感知到彼此的事务状态,才能保证状态一致,
因此需要一个事务协调者来协调每一个事务的参与者(子系统事务)。这里的子系统
事务,称为分支事务;有关联的各个分支事务在一起称为全局事务
因此需要一个事务协调者来协调每一个事务的参与者(子系统事务)。这里的子系统
事务,称为分支事务;有关联的各个分支事务在一起称为全局事务
初识Seata
Seata的架构
概念
致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。
Seata事务管理中有三个重要的角色
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支
事务的状态,并驱动分支事务提交或回滚。
事务的状态,并驱动分支事务提交或回滚。
Seata提供了四种不同的分布式事务解决方案
XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
TCC模式:最终一致的分阶段事务模式,有业务侵入
AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
SAGA模式:长事务模式,有业务侵入
部署TC服务
微服务集成Seata
首先,引入seata相关依赖
然后,配置application.yml,让微服务通过注册中心找到seata-tc-server
动手实践
XA模式
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,
XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。
XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。
seata的XA模式做了一些调整,但大体相似
RM一阶段的工作
注册分支事务到TC
执行分支业务sql但不提交
报告执行状态到TC
TC二阶段的工作
TC检测各分支事务执行状态
如果都成功,通知所有RM提交事务
如果有失败,通知所有RM回滚事务
RM二阶段的工作
接收TC指令,提交或回滚事务
流程图
XA模式的优点是
事务的强一致性,满足ACID原则
常用数据库都支持,实现简单,并且没有代码侵入
XA模式的缺点
因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
依赖关系型数据库实现事务
实现XA模式
Seata的starter已经完成了XA模式的自动装配
修改application.yml文件(每个参与事务的微服务),开启XA模式:
给发起全局事务的入口方法添加@GlobalTransactional注解,本例中是OrderServiceImpl中的create方法
重启服务并测试
AT模式
AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。
原理图
阶段一RM的工作
注册分支事务
记录undo-log(数据快照)
执行业务sql并提交
报告事务状态
阶段二提交时RM的工作
删除undo-log即可
阶段三提交时RM的工作
根据undo-log恢复数据到更新前
原理
一个分支业务的SQL是这样的:update tb_account set money = money - 10 where id = 1
AT模式与XA模式最大的区别是什么?
XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
XA模式强一致;AT模式最终一致
XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
XA模式强一致;AT模式最终一致
AT模式的脏写问题
AT模式的优点
一阶段完成直接提交事务,释放数据库资源,性能比较好利用
全局锁实现读写隔离
没有代码侵入,框架自动完成回滚和提交
全局锁实现读写隔离
没有代码侵入,框架自动完成回滚和提交
AT模式的缺点
两阶段之间属于软状态,属于最终一致
框架的快照功能会影响性能,但比XA模式要好很多
框架的快照功能会影响性能,但比XA模式要好很多
实现AT
将事务模式改为AT
TCC模式
TCC模式原理
TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法
Try:资源的检测和预留
Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功
Cancel:预留资源释放,可以理解为try的反向操作
TCC模式工作图
TCC模式的每个阶段是做什么的?
Try:资源检查和预留
Confirm:业务执行和提交
Cancel:预留资源的释放
TCC的优点是什么?
一阶段完成直接提交事务,释放数据库资源,性能好
相比AT模型,无需生成快照,无需使用全局锁,性能最强
不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点是什么?
有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
软状态,事务是最终一致
需要考虑Confirm和Cancel的失败情况,做好幂等处理
SAGA模式
Saga模式是SEATA提供的长事务解决方案。也分为两个阶段
一阶段:直接提交本地事务
二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚
Saga模式优点
事务参与者可以基于事件驱动实现异步调用,吞吐高
一阶段直接提交事务,无锁,性能好
不用编写TCC中的三个阶段,实现简单
一阶段直接提交事务,无锁,性能好
不用编写TCC中的三个阶段,实现简单
缺点
软状态持续时间不确定,时效性差
没有锁,没有事务隔离,会有脏写
没有锁,没有事务隔离,会有脏写
高可用
高可用集群结构
TC的异地多机房容灾架构
TC服务作为Seata的核心服务,一定要保证高可用和异地容灾
实现高可用集群
多级缓存
传统缓存的问题
统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,存在下面的问题
请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈
Redis缓存失效时,会对数据库产生冲击
缓存图描述
JVM进程缓存
本地进程缓存
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的
读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类
读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类
分布式缓存,例如Redis
优点:存储容量更大、可靠性更好、可以在集群间共享
缺点:访问缓存有网络开销
场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
进程本地缓存,例如HashMap、GuavaCache
优点:读取本地内存,没有网络开销,速度更快
缺点:存储容量有限、可靠性较低、无法共享
场景:性能要求较高,缓存数据量较小
初识Caffeine
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine
实现进程缓存
Caffeine提供了三种缓存驱逐策略
基于容量:设置缓存的数量上限
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1) // 设置缓存大小上限为 1
.build();
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1) // 设置缓存大小上限为 1
.build();
基于时间:设置缓存的有效时间
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(10)) // 设置缓存有效期为 10 秒,从最后一次写入开始计时
.build();
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(10)) // 设置缓存有效期为 10 秒,从最后一次写入开始计时
.build();
基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。
在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。
实现进程缓存
Lua语法入门
初识Lua
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
HelloWorld
在Linux虚拟机的任意目录下,新建一个hello.lua文件
添加下面的内容
运行
变量和循环
数据类型
变量
Lua声明变量的时候,并不需要指定数据类型
-- 声明字符串
local str = 'hello'
-- 声明数字
local num = 21
-- 声明布尔类型
local flag = true
-- 声明数组
key为索引的
tablelocal arr = {'java', 'python', 'lua'}
-- 声明table,类似java的map
local map = {name='Jack', age=21}
local str = 'hello'
-- 声明数字
local num = 21
-- 声明布尔类型
local flag = true
-- 声明数组
key为索引的
tablelocal arr = {'java', 'python', 'lua'}
-- 声明table,类似java的map
local map = {name='Jack', age=21}
访问table
-- 访问数组,lua数组的角标从1开始
print(arr[1])
-- 访问table
print(map['name'])
print(map.name)
print(arr[1])
-- 访问table
print(map['name'])
print(map.name)
循环
数组、table都可以利用for循环来遍历
遍历数组
遍历table
条件控制、函数
函数
定义函数的语法
条件控制
多级缓存
安装OpenResty
OpenResty® 是一个基于 Nginx的高性能 Web 平台,用于方便地搭建能够处理超
高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关
高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关
特点
具备Nginx的完整功能
基于Lua语言进行扩展,集成了大量精良的 Lua 库、第三方模块
允许使用Lua自定义业务逻辑、自定义库
基于Lua语言进行扩展,集成了大量精良的 Lua 库、第三方模块
允许使用Lua自定义业务逻辑、自定义库
OpenResty快速入门
OpenResty快速入门,实现商品详情页数据查询
步骤一:修改nginx.conf文件
步骤二:编写item.lua文件
请求参数处理
OpenResty获取请求参数
获取请求路径中的商品id信息,拼接到json结果中返回
查询Tomcat
获取请求路径中的商品id信息,根据id向Tomcat查询商品信息
nginx内部发送Http请求
封装http查询的函数
JSON结果处理
Redis缓存预热
冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力。
缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。
1.利用Docker安装Redis
docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes
2.在item-service服务中引入Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.配置Redis地址
spring:
redis:
host: 192.168.150.101
redis:
host: 192.168.150.101
编写初始化类
@Component
public class RedisHandler implements InitializingBean {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void afterPropertiesSet() throws Exception { // 初始化缓存 ... } }
public class RedisHandler implements InitializingBean {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void afterPropertiesSet() throws Exception { // 初始化缓存 ... } }
查询Redis缓存
OpenResty的Redis模块
OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用
引入Redis模块,并初始化Redis对象
-- 引入redis模块
local redis = require("resty.redis")
-- 初始化Redis对象
local red = redis:new()
-- 设置Redis超时时间
red:set_timeouts(1000, 1000, 1000)
local redis = require("resty.redis")
-- 初始化Redis对象
local red = redis:new()
-- 设置Redis超时时间
red:set_timeouts(1000, 1000, 1000)
封装函数,用来释放Redis连接,其实是放入连接池
-- 关闭redis连接的工具方法,其实是放入连接池
local function close_redis(red)
local pool_max_idle_time = 10000
-- 连接的空闲时间,单位是毫秒
local pool_size = 100
--连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.log(ngx.ERR, "放入Redis连接池失败: ", err)
end
end
local function close_redis(red)
local pool_max_idle_time = 10000
-- 连接的空闲时间,单位是毫秒
local pool_size = 100
--连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.log(ngx.ERR, "放入Redis连接池失败: ", err)
end
end
OpenResty提供了操作Redis的模块,我们只要引入该模块就能直接使用
Nginx本地缓存
OpenResty为Nginx提供了shard dict的功能,可以在nginx的多个worker之间共享数据,实现缓存功能
开启共享字典,在nginx.conf的http下添加配置:
# 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
lua_shared_dict item_cache 150m;
lua_shared_dict item_cache 150m;
操作共享字典
-- 获取本地缓存对象
local item_cache = ngx.shared.item_cache
-- 存储, 指定key、value、过期时间,单位s,默认为0代表永不过期
item_cache:set('key', 'value', 1000)
-- 读取
local val = item_cache:get('key')
local item_cache = ngx.shared.item_cache
-- 存储, 指定key、value、过期时间,单位s,默认为0代表永不过期
item_cache:set('key', 'value', 1000)
-- 读取
local val = item_cache:get('key')
在查询商品时,优先查询OpenResty的本地缓存
缓存同步策略
数据同步策略
设置有效期:给缓存设置有效期,到期后自动删除。再次查询时更新
优势:简单、方便
缺点:时效性差,缓存过期之前可能不一致
场景:更新频率较低,时效性要求低的业务
同步双写:在修改数据库的同时,直接修改缓存
优势:时效性强,缓存与数据库强一致
缺点:有代码侵入,耦合度高
场景:对一致性、时效性要求较高的缓存数据
异步通知:修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据
优势:低耦合,可以同时通知多个缓存服务
缺点:时效性一般,可能存在中间不一致状态
场景:时效性要求一般,有多个服务需要同步
缓存同步策略
安装Canal
基于Java开发。基于数据库增量日志解析,提供增量数据订阅&消费
Canal是基于mysql的主从同步来实现的,MySQL主从同步的原理如下
MySQL master 将数据变更写入二进制日志( binary log),其中记录的数据叫做binary log events
MySQL slave 将 master 的 binary log events拷贝到它的中继日志(relay log)
MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
Canal就是把自己伪装成MySQL的一个slave节点,从而监听master的binary log变化。
再把得到的变化信息通知给Canal的客户端,进而完成对其它数据库的同步。
再把得到的变化信息通知给Canal的客户端,进而完成对其它数据库的同步。
监听Canal
Canal提供了各种语言的客户端,当Canal监听到binlog变化时,会通知Canal的客户端。
Canal客户端
编写监听器,监听Canal消息
Canal推送给canal-client的是被修改的这一行数据(row),而我们引入的canal-client
则会帮我们把行数据封装到Item实体类中。这个过程中需要知道数据库与实体的映射关系,要用到JPA的几个注解
则会帮我们把行数据封装到Item实体类中。这个过程中需要知道数据库与实体的映射关系,要用到JPA的几个注解
总结
前端学习
Html
HTML基础
Hyper Text Markup Language(超文本标记语言)
HTML的优势
多个浏览器使用
市场需求大
跨平台
W3C
W3C
World Wide Web Consortium(万维网联盟)
成立于1994年,Web技术领域最权威和具影响力的国际中立性技术标准机构
http://www.w3.org/
http://www.chinaw3c.org/
World Wide Web Consortium(万维网联盟)
成立于1994年,Web技术领域最权威和具影响力的国际中立性技术标准机构
http://www.w3.org/
http://www.chinaw3c.org/
W3C标准包括
结构化标准语言(XHTML 、XML)
表现标准语言(CSS)
行为标准(DOM、ECMAScript )
结构化标准语言(XHTML 、XML)
表现标准语言(CSS)
行为标准(DOM、ECMAScript )
演示案例:静夜思
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>静夜思</title>
</head>
<body>
<h1>静夜思</h1>
<em>朝代:唐代</em> 作者:<strong>李白</strong></em><br/>
<hr/>
原文:
<p>
床前明月光,<br/>
疑是地上霜。<br/>
举头望明月,<br/>
低头思故乡。<br/>
</p>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>静夜思</title>
</head>
<body>
<h1>静夜思</h1>
<em>朝代:唐代</em> 作者:<strong>李白</strong></em><br/>
<hr/>
原文:
<p>
床前明月光,<br/>
疑是地上霜。<br/>
举头望明月,<br/>
低头思故乡。<br/>
</p>
</body>
</html>
常用编辑器
记事本
NotePad++
Sublime
VsCode
WebStorm
HBuidler
IDEA
NotePad++
Sublime
VsCode
WebStorm
HBuidler
IDEA
HTML的基本结构
1. 强调HTML标签都以“< >”开始、“</ >”结束
2. 说明网页基本结构中这几个标签的用法
3. 网页中所有的内容都放在之间
2. 说明网页基本结构中这几个标签的用法
3. 网页中所有的内容都放在之间
<html>
<head></head>
<body></body>
</html>
<head></head>
<body></body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>小樊Java之路</title>
<meta charset="UTF-8">
<meta name="keywords" content="小樊说Java,"/>
<meta name="description" content="学Java"/>
</head>
<body>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>小樊Java之路</title>
<meta charset="UTF-8">
<meta name="keywords" content="小樊说Java,"/>
<meta name="description" content="学Java"/>
</head>
<body>
</body>
</html>
<!DOCTYPE> 声明位于文档中的最前面的位置,处于 <html> 标签之前。
<!DOCTYPE> 声明不是一个 HTML 标签;它是用来告知 Web 浏览器页面使用了哪种 HTML 版本。
在 HTML 4.01 中,<!DOCTYPE> 声明需引用 DTD (文档类型声明),因为 HTML 4.01 是基于 SGML (Standard Generalized Markup Language 标准通用标记语言)。DTD 指定了标记语言的规则,确保了浏览器能够正确的渲染内容。
<!DOCTYPE> 声明不是一个 HTML 标签;它是用来告知 Web 浏览器页面使用了哪种 HTML 版本。
在 HTML 4.01 中,<!DOCTYPE> 声明需引用 DTD (文档类型声明),因为 HTML 4.01 是基于 SGML (Standard Generalized Markup Language 标准通用标记语言)。DTD 指定了标记语言的规则,确保了浏览器能够正确的渲染内容。
网页的基本标签
<h>
段落标签<p>
换行标签<br/>
水平线标签<hr/>
字体样式标签
加粗:<strong></strong>
斜体:<em></em>
加粗:<strong></strong>
斜体:<em></em>
注释和特殊符号
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>特殊符号</title>
</head>
<body>
<!--空格-->
<p>
狂神说 Java<br/>
狂神说 Java<br/>
狂神说 Java<br/>
</p>
<!--大于小于-->
<p>
> < <br>
> <
</p>
<!--引号-->
<p>
"狂神"
</p>
<!--版权-->
<p>
© 2017-2019 狂神说Java
</p>
<!--万能的公式 &符号+xxx -->
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>特殊符号</title>
</head>
<body>
<!--空格-->
<p>
狂神说 Java<br/>
狂神说 Java<br/>
狂神说 Java<br/>
</p>
<!--大于小于-->
<p>
> < <br>
> <
</p>
<!--引号-->
<p>
"狂神"
</p>
<!--版权-->
<p>
© 2017-2019 狂神说Java
</p>
<!--万能的公式 &符号+xxx -->
</body>
</html>
图像标签
链接标签
target:常用有_self _blank前者为本页刷新,后者为跳出本页
锚链接
行内元素和块儿元素
列表表格和媒体元素
列表
无序元素
<!--ul 声明无序列表-->
<ul>
<!--li 声明列表项-->
<li>语文</li>
<li>数学</li>
<li>英语</li>
<li>计算机</li>
</ul>
<ul>
<!--li 声明列表项-->
<li>语文</li>
<li>数学</li>
<li>英语</li>
<li>计算机</li>
</ul>
列表项中可以包含图片、文本,还可以嵌套列表、其他标签等
无序列表的特性
没有顺序,每个< li>标签独占一行(块元素)
默认< li>标签项前面有个实心小圆点
一般用于无序类型的列表,如导航、侧边栏新闻、有规律的图文组合模块等
无序列表的特性
没有顺序,每个< li>标签独占一行(块元素)
默认< li>标签项前面有个实心小圆点
一般用于无序类型的列表,如导航、侧边栏新闻、有规律的图文组合模块等
有序列表
<!--ol 声明有序列表-->
<ol>
<li>语文</li>
<li>数学</li>
<li>英语</li>
<li>计算机</li>
</ol>
<ol>
<li>语文</li>
<li>数学</li>
<li>英语</li>
<li>计算机</li>
</ol>
有序列表默认以数字序号显示
有序列表与无序列表一样,也可以嵌套列表、可以包含图片、文本、其他标签等
有序列表的特性
有顺序,每个< li>标签独占一行(块元素)
默认< li>标签项前面有顺序标记
一般用于排序类型的列表,如试卷、问卷选项等
有序列表与无序列表一样,也可以嵌套列表、可以包含图片、文本、其他标签等
有序列表的特性
有顺序,每个< li>标签独占一行(块元素)
默认< li>标签项前面有顺序标记
一般用于排序类型的列表,如试卷、问卷选项等
自定义列表
<!--dl 声明定义列表-->
<dl>
<!--dt 声明列表项-->
<dt>水果</dt>
<!--dd 定义列表项内容-->
<dd>苹果</dd>
<dd>桃子</dd>
<dd>李子</dd>
</dl>
<dl>
<!--dt 声明列表项-->
<dt>水果</dt>
<!--dd 定义列表项内容-->
<dd>苹果</dd>
<dd>桃子</dd>
<dd>李子</dd>
</dl>
定义列表也可以嵌套列表、包含图片、文本、其他标签等
以后的网页制作中经常会用到定义列表,特别是图文混排的情况
定义列表的特性
没有顺序,每个< dt>标签、< dd>标签独占一行(块元素)
默认没有标记
一般用于一个标题下有一个或多个列表项的情况
以后的网页制作中经常会用到定义列表,特别是图文混排的情况
定义列表的特性
没有顺序,每个< dt>标签、< dd>标签独占一行(块元素)
默认没有标记
一般用于一个标题下有一个或多个列表项的情况
总结
表格
基本语法:
<!--table表格标签-->
<table border="1px">
<!--tr 行标签-->
<tr>
<!--td 单元格标签-->
<td>第1个单元格的内容</td>
<td>第2个单元格的内容</td>
……
</tr>
<tr>
<td>第1个单元格的内容</td>
<td>第2个单元格的内容</td>
……
</tr>
</table>
<!--table表格标签-->
<table border="1px">
<!--tr 行标签-->
<tr>
<!--td 单元格标签-->
<td>第1个单元格的内容</td>
<td>第2个单元格的内容</td>
……
</tr>
<tr>
<td>第1个单元格的内容</td>
<td>第2个单元格的内容</td>
……
</tr>
</table>
表格的跨列
<table>
<tr>
<!--colspan 所跨的列数-->
<td colspan="n">单元格内容</td>
</tr>
<tr>
<td>单元格内容</td>
……
</tr>
......
</table>
<tr>
<!--colspan 所跨的列数-->
<td colspan="n">单元格内容</td>
</tr>
<tr>
<td>单元格内容</td>
……
</tr>
......
</table>
表格的跨行
<table >
<tr>
<!--rowspan 所跨的行数-->
<td rowspan="n"> </td>
<td> </td>
</tr>
<tr>
<td> </td>
</tr>
</table>
<tr>
<!--rowspan 所跨的行数-->
<td rowspan="n"> </td>
<td> </td>
</tr>
<tr>
<td> </td>
</tr>
</table>
表格的跨行和跨列
<tr>
<!--跨列-->
<td colspan="3">学生成绩</td>
</tr>
<tr>
<!--跨行-->
<td rowspan="2">张三</td>
<td>语文</td>
<td>98</td>
</tr>
<!--跨列-->
<td colspan="3">学生成绩</td>
</tr>
<tr>
<!--跨行-->
<td rowspan="2">张三</td>
<td>语文</td>
<td>98</td>
</tr>
音频视频
视频
src:指定要播放的视频文件的路径
controls:提供播放、暂停和音量的控件
autoplay:自动播放属性
loop:视频的循环播放
<video src="视频路径" controls autoplay></video>
controls:提供播放、暂停和音量的控件
autoplay:自动播放属性
loop:视频的循环播放
<video src="视频路径" controls autoplay></video>
音频
src:指定要播放的音频文件的路径
trols:提供播放、暂停和音量的控件
<audio src="音频路径" controls autoplay></video>
trols:提供播放、暂停和音量的控件
<audio src="音频路径" controls autoplay></video>
内联框架
iframe单页面连接
src:引用页面地址
name:框架标识名
<iframe src="path" name="mainFrame" ></iframe>
name:框架标识名
<iframe src="path" name="mainFrame" ></iframe>
页面间相互跳转
在被打开的框架上加name属性
<iframe name="mainFrame"></iframe>
在超链接上设置target目标窗口属性为希望显示的框架窗口名
<a href="https://www.baidu.com/" target="mainFrame">加载</a>
<iframe name="mainFrame"></iframe>
在超链接上设置target目标窗口属性为希望显示的框架窗口名
<a href="https://www.baidu.com/" target="mainFrame">加载</a>
表单
表单样式
method: 规定如何发送表单数据常用值:get post
在实际网页开发中通常采用post方式提交表单数据
action: 表示向何处发送表单数据
<form method="post" action="result.html">
<p>名字:<input name="name" type="text" > </p>
<p>密码:<input name="pass" type="password" > </p>
<p>
<input type="submit" name="Button" value="提交"/>
<input type="reset" name="Reset" value="重填"/>
</p>
</form>
在实际网页开发中通常采用post方式提交表单数据
action: 表示向何处发送表单数据
<form method="post" action="result.html">
<p>名字:<input name="name" type="text" > </p>
<p>密码:<input name="pass" type="password" > </p>
<p>
<input type="submit" name="Button" value="提交"/>
<input type="reset" name="Reset" value="重填"/>
</p>
</form>
讲解表单的创建方法,以及method和action的作用
分别把method的值设置为get和post,然后提交表单,查看页面效果;通过演示可看到method设
置不同值时,表单数据在地址栏显示的不同情况
最后根据演示情况说明get和post两者的区别
最后总结:post方式提交的数据安全性要明显高于get方式提交的数据。因此在实际开发中通常采
用post方式提交表单数据。
分别把method的值设置为get和post,然后提交表单,查看页面效果;通过演示可看到method设
置不同值时,表单数据在地址栏显示的不同情况
最后根据演示情况说明get和post两者的区别
最后总结:post方式提交的数据安全性要明显高于get方式提交的数据。因此在实际开发中通常采
用post方式提交表单数据。
表单元素
文本框
<!--type="text"
name:文本框名称(必填)
value:文本框初始值
size:文本框长度
maxlength:文本框可输入最多字符
-->
<input type="text" name="userName" value="用户名" size="30" maxlength="20"
/>
name:文本框名称(必填)
value:文本框初始值
size:文本框长度
maxlength:文本框可输入最多字符
-->
<input type="text" name="userName" value="用户名" size="30" maxlength="20"
/>
密码框
<!--type="password"
name:密码框名称(必填)
size:密码框长度
-->
<input type="password" name="pass" size="20"/>
name:密码框名称(必填)
size:密码框长度
-->
<input type="password" name="pass" size="20"/>
单选框
<!--type="radio"
name:单选框名称(必填),一组的名称需要相同
checked:单选按钮选中状态
value:单选框的值
-->
<input name="gen" type="radio" value="男" checked />男
<input name="gen" type="radio" value="女" />女
name:单选框名称(必填),一组的名称需要相同
checked:单选按钮选中状态
value:单选框的值
-->
<input name="gen" type="radio" value="男" checked />男
<input name="gen" type="radio" value="女" />女
复选框
<!--type="checkbox"
name:复选框名称(必填),一组的名称需要相同
checked:复选按钮选中状态
value:复选框的值
-->
<input type="checkbox" name="interest" value="sports"/>运动
<input type="checkbox" name="interest" value="talk" checked />聊天
<input type="checkbox" name="interest" value="play"/>玩游戏
name:复选框名称(必填),一组的名称需要相同
checked:复选按钮选中状态
value:复选框的值
-->
<input type="checkbox" name="interest" value="sports"/>运动
<input type="checkbox" name="interest" value="talk" checked />聊天
<input type="checkbox" name="interest" value="play"/>玩游戏
下拉列表框
<!--select:下拉列表框-->
<!--option:选项-->
<select name="列表名称" size="行数">
<option value="选项的值" selected="selected">…</option >
<option value="选项的值">…</option >
</select>
按钮
<!--重置按钮-->
<input type="reset" name="butReset" value="reset按钮">
<!--提交按钮-->
<input type="submit" name="butSubmit" value="submit按钮">
<!--普通按钮-->
<input type="button" name="butButton" value="button按钮"/>
<!--图片按钮-->
<input type="image" src="images/login.gif" />
<input type="reset" name="butReset" value="reset按钮">
<!--提交按钮-->
<input type="submit" name="butSubmit" value="submit按钮">
<!--普通按钮-->
<input type="button" name="butButton" value="button按钮"/>
<!--图片按钮-->
<input type="image" src="images/login.gif" />
多行文本框
textarea:多行文本域
cols:显示的列数
rows:显示的行数
<textarea name="showText" cols="x" rows="y">文本内容 </textarea>
cols:显示的列数
rows:显示的行数
<textarea name="showText" cols="x" rows="y">文本内容 </textarea>
文件域
enctype:表单编码属性
<form action="" method="post" enctype="multipart/form-data">
<p>
<!--type="file" 文件域-->
<input type="file" name="files" />
<input type="submit" name="upload" value="上传" />
</p>
</form>
<form action="" method="post" enctype="multipart/form-data">
<p>
<!--type="file" 文件域-->
<input type="file" name="files" />
<input type="submit" name="upload" value="上传" />
</p>
</form>
邮箱
邮箱:<input type="email" name="email"/>
网址
请输入你的网址:<input type="url" name="userUrl"/>
数字
min:最小值
max:最大值
step:步长
请输入数字:<input type="number" name="num" min="0" max="100" step="10"/>
max:最大值
step:步长
请输入数字:<input type="number" name="num" min="0" max="100" step="10"/>
滑块
type值为range即为滑块。
请输入数字:<input type="range" name="range1" min="0" max="10" step="2"/>
请输入数字:<input type="range" name="range1" min="0" max="10" step="2"/>
搜索框
请输入搜索的关键词:<input type="search" name="sousuo"/>
表单高级应用
在某些注册页面或本图片中订单信息页面,必须同意一些条款按钮才能使用等等
隐藏域
在浏览器中看不到隐藏域,但是在提交表单时可以看到隐藏域的内容被提交至服务器
<input type="hidden" value="666" name="userid">
在浏览器中看不到隐藏域,但是在提交表单时可以看到隐藏域的内容被提交至服务器
<input type="hidden" value="666" name="userid">
只读、禁用
讲解只读和禁用的语法,强调不能单写readonly或disabled,必须写readonly
=”readonly”和disabled=“disabled”,介绍只读和禁用的使用场合
<input name="name" type="text" value="张三" readonly>
<input type="submit" disabled value="保存" >
讲解只读和禁用的语法,强调不能单写readonly或disabled,必须写readonly
=”readonly”和disabled=“disabled”,介绍只读和禁用的使用场合
<input name="name" type="text" value="张三" readonly>
<input type="submit" disabled value="保存" >
表单元素的注用
<!--它的for属性对应的id与表单元素id一致-->
<label for="id">标注的文本</label>
<input type="radio" name="gender" id="male"/>
<label for="id">标注的文本</label>
<input type="radio" name="gender" id="male"/>
表单的初级认证
placeholder
提示语默认显示,当文本框中输入内容时提示语消失
required
规定文本框填写内容不能为空,否则不允许用户提交表单
pattern
用户输入的内容必须符合正则表达式所指的规则,否则就不能提交表单
提示语默认显示,当文本框中输入内容时提示语消失
required
规定文本框填写内容不能为空,否则不允许用户提交表单
pattern
用户输入的内容必须符合正则表达式所指的规则,否则就不能提交表单
CSS
CSS概述
概念
Cascading Style Sheet 级联样式表。
表现HTML或XHTML文件样式的计算机语言。
包括对字体、颜色、边距、高度、宽度、背景图片、网页定位等设定。
表现HTML或XHTML文件样式的计算机语言。
包括对字体、颜色、边距、高度、宽度、背景图片、网页定位等设定。
引入css方式
行内引入
<h1 style="color:red;">style属性的应用</h1>
<p style="font-size:14px; color:green;">直接在HTML标签中设置的样式</p>
<p style="font-size:14px; color:green;">直接在HTML标签中设置的样式</p>
内部样式表
<style>
h1{color: green; }
</style>
h1{color: green; }
</style>
外部样式表
css选择器
HTML标签作为标签选择器的名称
<h1>…<h6>、<p>、<img/>
<h1>…<h6>、<p>、<img/>
类选择器和id选择器
css高级选择器
后代选择器
body p{
background: red;
}
background: red;
}
子选择器
body>p{
background: pink;
}
background: pink;
}
相邻兄弟选择器
.active+p {
background: green;
}
background: green;
}
通用兄弟选择器
.active~p{
background: yellow;
}
background: yellow;
}
结构伪类选择器
<html>
<head lang="en">
<meta charset="UTF-8">
<title>使用CSS3结构伪类选择器</title>
</head>
<body>
<p>p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>li1</li>
<li>li2</li>
<li>li3</li>
</ul>
</body>
</html>
<head lang="en">
<meta charset="UTF-8">
<title>使用CSS3结构伪类选择器</title>
</head>
<body>
<p>p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>li1</li>
<li>li2</li>
<li>li3</li>
</ul>
</body>
</html>
ul li:first-child{ background: red;}
ul li:last-child{ background: green;}
p:nth-child(1){ background: yellow;}
p:nth-of-type(2){ background: blue;}
ul li:last-child{ background: green;}
p:nth-child(1){ background: yellow;}
p:nth-of-type(2){ background: blue;}
属性选择器
E[attr]
a[id] {
background: yellow;
}
a[id] {
background: yellow;
}
美化网页元素
span标签
< span>标签 的作用:能让某几个文字或者某个词语凸显出来,从而添加对应的样式!
字体样式
字体类型
p{font-family:Verdana,"楷体";}
body{font-family: Times,"Times New Roman", "楷体";}
body{font-family: Times,"Times New Roman", "楷体";}
字体大小
单位
px(像素)
em、rem、cm、mm、pt、pc
px(像素)
em、rem、cm、mm、pt、pc
h1{font-size:24px;}
h2{font-size:16px;}
h3{font-size:2em;}
span{font-size:12pt;}
strong{font-size:13pc;}
h2{font-size:16px;}
h3{font-size:2em;}
span{font-size:12pt;}
strong{font-size:13pc;}
字体风格 font-style
normal、italic和oblique
字体属性 font
字体属性的顺序:字体风格→字体粗细→字体大小→字体类型
p span{
font:oblique bold 12px "楷体";
}
font:oblique bold 12px "楷体";
}
文体样式
文本颜色
RGB
十六进制方法表示颜色:前两位表示红色分量,
中间两位表示绿色分量,最后两位表示蓝色分量
rgb(r,g,b) : 正整数的取值为0~255
中间两位表示绿色分量,最后两位表示蓝色分量
rgb(r,g,b) : 正整数的取值为0~255
color:#A983D8;
color:#EEFF66;
color:rgb(0,255,255);
color:rgba(0,0,255,0.5);
color:#EEFF66;
color:rgb(0,255,255);
color:rgba(0,0,255,0.5);
RGBA
在RGB基础上增加了控制alpha透明度的参数,其中这个透明通道值为0~1
在RGB基础上增加了控制alpha透明度的参数,其中这个透明通道值为0~1
排版文本段落
水平对齐方式:text-align属性
首行缩进:text-indent:em或px
行高:line-height:px
行高:line-height:px
文本修饰和垂直对齐
文本装饰:text-decoration属性(后面的讲解中会大量用到)
垂直对齐方式:vertical-align属性:middle、top、bottom
文本阴影
超链接伪类
列表样式
list-style-type
list-style-image
list-style-position
list-style
list-style-image
list-style-position
list-style
背景样式
常见的背景样式:
背景图像
background-image
背景颜色
background-color
背景图像
background-image
背景颜色
background-color
设置背景
.title {
font-size:18px;
font-weight:bold;
color:#FFF;
text-indent:1em;
line-height:35px;
background:#C00 url(../image/arrow-down.gif) 205px 10px no-repeat;
}
background: 背景颜色 背景图像 背景定位 背景不重复显示
font-size:18px;
font-weight:bold;
color:#FFF;
text-indent:1em;
line-height:35px;
background:#C00 url(../image/arrow-down.gif) 205px 10px no-repeat;
}
background: 背景颜色 背景图像 背景定位 背景不重复显示
背景尺寸
参考网站
http://color.oulu.me/
盒子模型
概念图
边框
边框颜色 border-color
边框颜色设置方式与文本颜色对比讲解,都是使用十六进制
强调同时设置4个边框颜色时,顺序为上右下左
详细讲解分别上、下、左、右各边框颜色的不同设置方式,及属性值的顺序
边框颜色设置方式与文本颜色对比讲解,都是使用十六进制
强调同时设置4个边框颜色时,顺序为上右下左
详细讲解分别上、下、左、右各边框颜色的不同设置方式,及属性值的顺序
边框粗细
border-width
thin
medium
thick
thin
medium
thick
像素值
border-top-width:5px;
border-right-width:10px;
border-bottom-width:8px;
border-left-width:22px;
border-width:5px ;
border-width:20px 2px;
border-width:5px 1px 6px;
border-width:1px 3px 5px 2px;
border-right-width:10px;
border-bottom-width:8px;
border-left-width:22px;
border-width:5px ;
border-width:20px 2px;
border-width:5px 1px 6px;
border-width:1px 3px 5px 2px;
边框样式
border-style
none
hidden
dotted
dashed
solid
double
none
hidden
dotted
dashed
solid
double
border-top-style:solid;
border-right-style:solid;
border-bottom-style:solid;
border-left-style:solid;
border-style:solid ;
border-style:solid dotted;
border-style:solid dotted dashed;
border-style:solid dotted dashed double;
border-right-style:solid;
border-bottom-style:solid;
border-left-style:solid;
border-style:solid ;
border-style:solid dotted;
border-style:solid dotted dashed;
border-style:solid dotted dashed double;
border简写
同时设置边框的颜色、粗细和样式
border:1px solid #3a6587;
border: 1px dashed red;
border:1px solid #3a6587;
border: 1px dashed red;
内外边距
外边距
外边距 margin
margin-top
margin-right
margin-bottom
margin-left
———————————
margin-top: 1 px
margin-right : 2 px
margin-bottom : 2 px
margin-left : 1 px
margin :3px 5px 7px 4px;
margin :3px 5px;
margin :3px 5px 7px;
margin :8px;
margin-top
margin-right
margin-bottom
margin-left
———————————
margin-top: 1 px
margin-right : 2 px
margin-bottom : 2 px
margin-left : 1 px
margin :3px 5px 7px 4px;
margin :3px 5px;
margin :3px 5px 7px;
margin :8px;
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>box-sizing</title>
<style>
div{
width: 100px;
height: 100px;
padding: 5px;
margin: 10px;
border: 1px solid #000000;
box-sizing: border-box;
/*box-sizing: content-box; /!* 默认值*!/*/
}
</style>
</head>
<body>
<div></div>
</body>
</html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>box-sizing</title>
<style>
div{
width: 100px;
height: 100px;
padding: 5px;
margin: 10px;
border: 1px solid #000000;
box-sizing: border-box;
/*box-sizing: content-box; /!* 默认值*!/*/
}
</style>
</head>
<body>
<div></div>
</body>
</html>
圆角边框
border-radius: 20px 10px 50px 30px;
border-radius制作特殊图形:圆形
利用border-radius属性制作圆形的两个要点
元素的宽度和高度必须相同
圆角的半径为元素宽度的一半,或者直接设置圆角半径值为50%
元素的宽度和高度必须相同
圆角的半径为元素宽度的一半,或者直接设置圆角半径值为50%
div{
width: 100px;
height: 100px;
border: 4px solid red;
border-radius: 50%;
}
width: 100px;
height: 100px;
border: 4px solid red;
border-radius: 50%;
}
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>border-radius制作圆形</title>
<style>
div{
width: 100px;
height: 100px;
border: 4px solid red;
border-radius: 50%;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>border-radius制作圆形</title>
<style>
div{
width: 100px;
height: 100px;
border: 4px solid red;
border-radius: 50%;
}
</style>
</head>
<body>
<div></div>
</body>
</html>
使用border-radius制作特殊图形:半圆形
利用border-radius属性制作半圆形的两个要点
制作上半圆或下半圆时,元素的宽度是高度的2倍,而且圆角半径为元素的高度值
制作左半圆或右半圆时,元素的高度是宽度的2倍,而且圆角半径为元素的宽度值
制作上半圆或下半圆时,元素的宽度是高度的2倍,而且圆角半径为元素的高度值
制作左半圆或右半圆时,元素的高度是宽度的2倍,而且圆角半径为元素的宽度值
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>border-radius制作半圆形</title>
<style>
div{
background: red;
margin: 30px;
}
div:nth-of-type(1){
width: 100px;
height: 50px;
border-radius: 50px 50px 0 0;
}
div:nth-of-type(2){
width: 100px;
height: 50px;
border-radius:0 0 50px 50px;
}
div:nth-of-type(3){
width: 50px;
height: 100px;
border-radius:0 50px 50px 0;
}
div:nth-of-type(4){
width: 50px;
height: 100px;
border-radius: 50px 0 0 50px;
}
</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>border-radius制作半圆形</title>
<style>
div{
background: red;
margin: 30px;
}
div:nth-of-type(1){
width: 100px;
height: 50px;
border-radius: 50px 50px 0 0;
}
div:nth-of-type(2){
width: 100px;
height: 50px;
border-radius:0 0 50px 50px;
}
div:nth-of-type(3){
width: 50px;
height: 100px;
border-radius:0 50px 50px 0;
}
div:nth-of-type(4){
width: 50px;
height: 100px;
border-radius: 50px 0 0 50px;
}
</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>
使用border-radius制作特殊图形:扇形
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>border-radius制作扇形</title>
<style>
div{
background: red;
margin: 30px;
}
div:nth-of-type(1){
width: 50px;
height: 50px;
border-radius: 50px 0 0 0;
}
div:nth-of-type(2){
width: 50px;
height: 50px;
border-radius:0 50px 0 0;
}
div:nth-of-type(3){
width: 50px;
height: 50px;
border-radius:0 0 50px 0;
}
div:nth-of-type(4){
width: 50px;
height: 50px;
border-radius: 0 0 0 50px;
}
</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>border-radius制作扇形</title>
<style>
div{
background: red;
margin: 30px;
}
div:nth-of-type(1){
width: 50px;
height: 50px;
border-radius: 50px 0 0 0;
}
div:nth-of-type(2){
width: 50px;
height: 50px;
border-radius:0 50px 0 0;
}
div:nth-of-type(3){
width: 50px;
height: 50px;
border-radius:0 0 50px 0;
}
div:nth-of-type(4){
width: 50px;
height: 50px;
border-radius: 0 0 0 50px;
}
</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>
盒子阴影
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>box-shadow的使用</title>
<style>
div{
width: 100px;
height: 100px;
border: 1px solid red;
border-radius: 8px;
margin: 20px;
/*box-shadow: 20px 10px 10px #06c; /!*内阴影*!/*/
/*box-shadow: 0px 0px 20px #06c; /!*只设置模糊半径的阴影
*!/*/
box-shadow: inset 3px 3px 10px #06c; /*内阴影*/
}
</style>
</head>
<body>
<div></div>
</body>
</html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>box-shadow的使用</title>
<style>
div{
width: 100px;
height: 100px;
border: 1px solid red;
border-radius: 8px;
margin: 20px;
/*box-shadow: 20px 10px 10px #06c; /!*内阴影*!/*/
/*box-shadow: 0px 0px 20px #06c; /!*只设置模糊半径的阴影
*!/*/
box-shadow: inset 3px 3px 10px #06c; /*内阴影*/
}
</style>
</head>
<body>
<div></div>
</body>
</html>
浮动
标准文档流
概念
标准文档流:指元素根据块元素或行内元素的特性按从上到下,从左到右的方式自然排列。这也是元素
默认的排列方式
组成
块级元素
<h1>…<h6>、<p>、<div>、列表
内联元素
<span>、<a>、<img/>、<strong>...
display
特性
块级元素与行级元素的转变(block、inline)
控制块元素排到一行(inline-block)
控制元素的显示和隐藏(none)
控制块元素排到一行(inline-block)
控制元素的显示和隐藏(none)
块元素排在一起的方法
可以使用什么属性使块元素排在一行?
inline-block
float
inline-block
float
浮动
float属性
<body>
<div id="father">
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" />
</div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
</body>
<div id="father">
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" />
</div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
</body>
边框塌陷
clear属性
.layer04 {
clear:both; #清除两侧浮动
}
.layer04 {
clear:left; #清除左侧浮动
}
.layer04 {
clear:right; #清除右侧浮动
}
clear:both; #清除两侧浮动
}
.layer04 {
clear:left; #清除左侧浮动
}
.layer04 {
clear:right; #清除右侧浮动
}
解决父级边框塌陷的方法
浮动元素后边添加div
<div id="father">
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
<div class="clear"></div>
</div>
.clear{ clear: both; margin: 0; padding: 0;}
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
<div class="clear"></div>
</div>
.clear{ clear: both; margin: 0; padding: 0;}
设置父元素的高度
<div id="father">
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
#father {height: 400px; border:1px #000 solid; }
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
#father {height: 400px; border:1px #000 solid; }
父级添加overflow属性
hidden属性值,这个值在网页中经常使用
通常与< div>宽度结合使用设置< div>自动扩展高度,
或者隐藏超出的内容
通常与< div>宽度结合使用设置< div>自动扩展高度,
或者隐藏超出的内容
<div id="father">
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
#father {overflow: hidden;border:1px #000 solid; }
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
#father {overflow: hidden;border:1px #000 solid; }
父级添加伪类after
<div id="father" class="clear">
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
.clear:after{
content: ''; /*在clear类后面添加内容为空*/
display: block; /*把添加的内容转化为块元素*/
clear: both; /*清除这个元素两边的浮动*/
}
<div class="layer01"><img src="image/photo-1.jpg" alt="日用品" /></div>
<div class="layer02"><img src="image/photo-2.jpg" alt="图书" /></div>
<div class="layer03"><img src="image/photo-3.jpg" alt="鞋子" /></div>
<div class="layer04">浮动的盒子……</div>
</div>
.clear:after{
content: ''; /*在clear类后面添加内容为空*/
display: block; /*把添加的内容转化为块元素*/
clear: both; /*清除这个元素两边的浮动*/
}
inline-block和float区别
display:inline-block
可以让元素排在一行,并且支持宽度和高度,代码实现起来方便
位置方向不可控制,会解析空格
IE 6、IE 7上不支持
float
可以让元素排在一行并且支持宽度和高度,可以决定排列方向
float 浮动以后元素脱离文档流,会对周围元素产生影响,必须在它的父级上添加清除浮动的
样式
可以让元素排在一行,并且支持宽度和高度,代码实现起来方便
位置方向不可控制,会解析空格
IE 6、IE 7上不支持
float
可以让元素排在一行并且支持宽度和高度,可以决定排列方向
float 浮动以后元素脱离文档流,会对周围元素产生影响,必须在它的父级上添加清除浮动的
样式
定位
position属性
static:默认值,没有定位
static:默认值,没有定位
relative:相对定位
相对自身原来位置进行偏移,偏移设置:top、left、right、bottom
相对自身原来位置进行偏移,偏移设置:top、left、right、bottom
设置俩个盒子的相对定位
#first {
background-color:#FC9;
border:1px #B55A00 dashed;
position:relative;
right:20px;
bottom:20px;
}
#second {
background-color:#CCF;
border:1px #0000A8 dashed;
float:right;
position:relative;
left:20px;
top:-20px;
}
#first {
background-color:#FC9;
border:1px #B55A00 dashed;
position:relative;
right:20px;
bottom:20px;
}
#second {
background-color:#CCF;
border:1px #0000A8 dashed;
float:right;
position:relative;
left:20px;
top:-20px;
}
绝对定位
absolute属性值:偏移设置: left、right、top、bottom
绝对定位:
使用了绝对定位的元素以它最近的一个“已经定位”的“祖先元素” 为基准进行偏移
如果没有已经定位的祖先元素,会以浏览器窗口为基准进行定位
使用了绝对定位的元素以它最近的一个“已经定位”的“祖先元素” 为基准进行偏移
如果没有已经定位的祖先元素,会以浏览器窗口为基准进行定位
固定定位
fixed属性值
偏移设置: left、right、top、bottom
偏移设置: left、right、top、bottom
z-index属性
调整元素定位时重叠层的上下位置
z-index属性值:整数,默认值为0
设置了positon属性时,z-index属性可以设置各元素之间的重叠高低关系
z-index值大的层位于其值小的层上方
z-index属性值:整数,默认值为0
设置了positon属性时,z-index属性可以设置各元素之间的重叠高低关系
z-index值大的层位于其值小的层上方
制作网页动画
CSS变形
变形函数
translate():平移函数,基于X、Y坐标重新定位元素的位置
scale():缩放函数,可以使任意元素对象尺寸发生变化
rotate():旋转函数,取值是一个度数值
skew():倾斜函数,取值是一个度数值
translate():平移函数,基于X、Y坐标重新定位元素的位置
scale():缩放函数,可以使任意元素对象尺寸发生变化
rotate():旋转函数,取值是一个度数值
skew():倾斜函数,取值是一个度数值
CSS过渡
CSS动画
JS
js基础
使用
<html>
<head>
<script>
alert('Hello, world');
</script>
</head>
<body>
...
</body>
</html
<head>
<script>
alert('Hello, world');
</script>
</head>
<body>
...
</body>
</html
调试:在console中进行
快速入门
基本语法
数据类型和变量
Number
123; // 整数123
0.456; // 浮点数0.456
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就
表示为Infinity
0.456; // 浮点数0.456
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就
表示为Infinity
字符串
"a","b","c"
布尔值,与或非
(1)== 比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
(2)=== 比较,它不会自动转换数据类型,如果数据类型不一致,返回 false ,如果一致,
再比较。
(2)=== 比较,它不会自动转换数据类型,如果数据类型不一致,返回 false ,如果一致,
再比较。
NaN === NaN; // false 这个特殊值与任何值都不相等
判断方法
isNaN(NaN); // true
null和undefined
null 表示一个“空”的值,它和 0 以及空字符串 '' 不同, 0 是一个数值, '' 表示长度为0
的字符串,而 null 表示“空”。
的字符串,而 null 表示“空”。
数组
var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; // 返回索引为0的元素,即1
arr[5]; // 返回索引为5的元素,即true
arr[6]; // 索引超出了范围,返回undefined
arr[0]; // 返回索引为0的元素,即1
arr[5]; // 返回索引为5的元素,即true
arr[6]; // 索引超出了范围,返回undefined
对象
JavaScript的对象是一组由键-值组成的无序集合,例如:
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
变量
var a; // 申明了变量a,此时a的值为undefined
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
var s_007 = '007'; // s_007是一个字符串
var Answer = true; // Answer是一个布尔值true
var t = null; // t的值是null
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
var s_007 = '007'; // s_007是一个字符串
var Answer = true; // Answer是一个布尔值true
var t = null; // t的值是null
strict模式
不用 var 申明的变量会被视为全局变量,为了避免这一缺陷,所有的JavaScript代码都应该使用strict
模式。我们在后面编写的JavaScript代码将全部采用strict模式。
i = 10; // i现在是全局变量
模式。我们在后面编写的JavaScript代码将全部采用strict模式。
i = 10; // i现在是全局变量
字符串
JavaScript的字符串就是用 '' 或 "" 括起来的字符表示
转义字符 \ 可以转义很多字符,比如 \n 表示换行, \t 表示制表符,字符 \ 本身也要转义,
所以 \\ 表示的字符就是 \ 。
所以 \\ 表示的字符就是 \ 。
'\x41'; // 完全等同于 'A'
多行字符串
由于多行字符串用 \n 写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用反
引号 ``表示
引号 ``表示
toUpperCase() 把一个字符串全部变为大写
var s = 'Hello';
s.toUpperCase(); // 返回'HELLO'
s.toUpperCase(); // 返回'HELLO'
toLowerCase() 把一个字符串全部变为小写
var s = 'Hello';
s.toUpperCase(); // 返回'HELLO'
s.toUpperCase(); // 返回'HELLO'
indexOf() 会搜索指定字符串出现的位置
var s = 'hello, world';
s.indexOf('world'); // 返回7
s.indexOf('World'); // 没有找到指定的子串,返回-1
s.indexOf('world'); // 返回7
s.indexOf('World'); // 没有找到指定的子串,返回-1
substring() 返回指定索引区间的子串
var s = 'hello, world'
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
s.substring(7); // 从索引7开始到结束,返回'world'
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
s.substring(7); // 从索引7开始到结束,返回'world'
数组
var arr = [1, 2, 3.14, 'Hello', null, true];
arr.length; // 6
arr.length; // 6
常用方法
indexOf:查索引位置
var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引为0
arr.indexOf(20); // 元素20的索引为1
arr.indexOf(30); // 元素30没有找到,返回-1
arr.indexOf('30'); // 元素'30'的索引为2
arr.indexOf(10); // 元素10的索引为0
arr.indexOf(20); // 元素20的索引为1
arr.indexOf(30); // 元素30没有找到,返回-1
arr.indexOf('30'); // 元素'30'的索引为2
slice() 就是对应String的 substring() 版本,
它截取 Array 的部分元素,然后返回一个新的 Array
它截取 Array 的部分元素,然后返回一个新的 Array
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G'
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice();
aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
aCopy === arr; // false
var aCopy = arr.slice();
aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
aCopy === arr; // false
push和pop
push() 向 Array 的末尾添加若干元素, pop() 则把 Array 的最后一个元素删除掉
unshift和shift
如果要往 Array 的头部添加若干元素,使用 unshift() 方法, shift() 方法则把
Array 的第一个元素删掉
Array 的第一个元素删掉
sort
sort() 可以对当前 Array 进行排序,它会直接修改
当前 Array 的元素位置,直接调用时,按照默认顺序排序
当前 Array 的元素位置,直接调用时,按照默认顺序排序
var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
arr.sort();
arr; // ['A', 'B', 'C']
reverse
var arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']
arr.reverse();
arr; // ['three', 'two', 'one']
splice
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL',
'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL',
'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
concat
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
join
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'
arr.join('-'); // 'A-B-C-1-2-3'
对象
对象格式
var xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
'name' in xiaoming; // true
'grade' in xiaoming; // false
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
'name' in xiaoming; // true
'grade' in xiaoming; // false
流程控制
if
var age = 3;
if (age >= 18) {
alert('adult');
} else if (age >= 6) {
alert('teenager');
} else {
alert('kid');
}
if (age >= 18) {
alert('adult');
} else if (age >= 6) {
alert('teenager');
} else {
alert('kid');
}
for
var x = 0;
var i;
for (i=1; i<=10000; i++) {
x = x + i;
}
x; // 50005000
var i;
for (i=1; i<=10000; i++) {
x = x + i;
}
x; // 50005000
遍历数组
var arr = ['Apple', 'Google', 'Microsoft'];
var i, x;
for (i=0; i<arr.length; i++) {
x = arr[i];
console.log(x);
}
var i, x;
for (i=0; i<arr.length; i++) {
x = arr[i];
console.log(x);
}
无限循环
var x = 0;
for (;;) { // 将无限循环下去
if (x > 100) {
break; // 通过if判断来退出循环
}
x ++;
}
for (;;) { // 将无限循环下去
if (x > 100) {
break; // 通过if判断来退出循环
}
x ++;
}
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
if (o.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
}
}
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
if (o.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
}
}
由于 Array 也是对象,而它的每个元素的索引被视为对象的属性,所以遍历出来是下标
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
基本操作
var x = 0;
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500
do while
var n = 0;
do {
n = n + 1;
} while (n < 100);
n; // 100
do {
n = n + 1;
} while (n < 100);
n; // 100
Map和Set
Map键值对集合
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
Set
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
s; // Set {1, 2, 3, "3"}
通过 add(key) 方法可以添加元素到 Set 中,可以重复添加,但是没有效果
通过 delete(key) 方法可以删除元素
Iterable
Array,Map,Set 属于;
具有 iterable 类型的集合可以通过新的 for ... of 循环来遍历
具有 iterable 类型的集合可以通过新的 for ... of 循环来遍历
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});
s.forEach(function (element, sameElement, set) {
console.log(element);
});
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
m.forEach(function (value, key, map) {
console.log(value);
});
函数
函数的定义和调用
定义
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
if (x >= 0) {
return x;
} else {
return -x;
}
};
arguments
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
rest
function foo(a, b) {
var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i<arguments.length; i++) {
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i<arguments.length; i++) {
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
为了获取除了已定义参数 a 、 b 之外的参数,我们不得不用 arguments ,并且循环要从索引
2 开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 rest 参数,有没有更好的方
法?
2 开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 rest 参数,有没有更好的方
法?
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
变量作用域
方法
var xiaoming = {
name: '小明',
birth: 1990
};
name: '小明',
birth: 1990
};
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了
apply
要指定函数的 this 指向哪个对象,可以用函数本身的 apply 方法,它接收两个参数,第一个参数
就是需要绑定的 this 变量,第二个参数是 Array ,表示函数本身的参数。
就是需要绑定的 this 变量,第二个参数是 Array ,表示函数本身的参数。
标准对象
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
date
var now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间
JSON
{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}
{"age": "3"}
{"sex": "男"}
var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹
的
var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个
字符串
的
var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个
字符串
JSON和JS之间的相互转换
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSON_秦疆</title>
</head>
<body>
<script type="text/javascript">
//编写一个js的对象
var user = {
name:"秦疆",
age:3,
sex:"男"
};
//将js对象转换成json字符串
var str = JSON.stringify(user);
console.log(str);
//将json字符串转换为js对象
var user2 = JSON.parse(str);
console.log(user2.age,user2.name,user2.sex);
</script>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSON_秦疆</title>
</head>
<body>
<script type="text/javascript">
//编写一个js的对象
var user = {
name:"秦疆",
age:3,
sex:"男"
};
//将js对象转换成json字符串
var str = JSON.stringify(user);
console.log(str);
//将json字符串转换为js对象
var user2 = JSON.parse(str);
console.log(user2.age,user2.name,user2.sex);
</script>
</body>
</html>
面向对象编程
操作Bom
window
window 对象不但充当全局作用域,而且表示浏览器窗口
window 对象有 innerWidth 和 innerHeight 属性,可以获取浏览器窗口的内部宽度和高度。
内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高
内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高
navigator
navigator 对象表示浏览器的信息,最常用的属性包括:
navigator.appName:浏览器名称;
navigator.appVersion:浏览器版本;
navigator.language:浏览器设置的语言;
navigator.platform:操作系统类型;
navigator.userAgent:浏览器设定的 User-Agent 字符串。
navigator.appName:浏览器名称;
navigator.appVersion:浏览器版本;
navigator.language:浏览器设置的语言;
navigator.platform:操作系统类型;
navigator.userAgent:浏览器设定的 User-Agent 字符串。
'use strict';
console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);
console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);
var width;
if (getIEVersion(navigator.userAgent) < 9) {
width = document.body.clientWidth;
} else {
width = window.innerWidth;
}
if (getIEVersion(navigator.userAgent) < 9) {
width = document.body.clientWidth;
} else {
width = window.innerWidth;
}
var width = window.innerWidth || document.body.clientWidth;
screen
screen 对象表示屏幕的信息,常用的属性有:
screen.width:屏幕宽度,以像素为单位;
screen.height:屏幕高度,以像素为单位;
screen.colorDepth:返回颜色位数,如8、16、24。
screen.width:屏幕宽度,以像素为单位;
screen.height:屏幕高度,以像素为单位;
screen.colorDepth:返回颜色位数,如8、16、24。
console.log('Screen size = ' + screen.width + ' x ' + screen.height);
location
location.protocol; // 'http'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'
要加载一个新页面,可以调用 location.assign() 。如果要重新加载当前页面,调用
location.reload() 方法非常方便。
location.reload() 方法非常方便。
location.reload();
location.assign('https://blog.kuangstudy.com/'); // 设置一个新的URL地址
location.assign('https://blog.kuangstudy.com/'); // 设置一个新的URL地址
document
<dl id="code-menu" style="border:solid 1px #ccc;padding:6px;">
<dt>Java</dt>
<dd>Spring</dd>
<dt>Python</dt>
<dd>Django</dd>
<dt>Linux</dt>
<dd>Docker</dd>
</dl>
用 document 对象提供的 getElementById() 和 getElementsByTagName() 可以按ID获得一个
DOM节点和按Tag名称获得一组DOM节点:
var menu = document.getElementById('code-menu');
var drinks = document.getElementsByTagName('dt');
var i, s;
s = '提供的饮料有:';
for (i=0; i<drinks.length; i++) {
s = s + drinks[i].innerHTML + ',';
}
console.log(s);
DOM节点和按Tag名称获得一组DOM节点:
var menu = document.getElementById('code-menu');
var drinks = document.getElementsByTagName('dt');
var i, s;
s = '提供的饮料有:';
for (i=0; i<drinks.length; i++) {
s = s + drinks[i].innerHTML + ',';
}
console.log(s);
history(尽量不使用)
JavaScript可以调用 history 对象的 back() 或
forward () ,相当于用户点击了浏览器的“后退”或“前进”按钮。
forward () ,相当于用户点击了浏览器的“后退”或“前进”按钮。
操作Dom
选择器
选取dom节点
document.getElementById() 和 document.getElementsByTagName() ,以及CSS选择器
document.getElementsByClassName() 。
document.getElementsByClassName() 。
querySelector() 和 querySelectorAll()
更新DOM
一种是修改 innerHTML 属性
// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p>的内部结构已修改
var p = document.getElementById('p-id');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p>的内部结构已修改
用 innerHTML 时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到了,要注意对字符
编码来避免XSS攻击。
编码来避免XSS攻击。
第二种是修改 innerText 属性
// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id"><script>alert("Hi")</script></p>
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id"><script>alert("Hi")</script></p>
插入DOM
<!-- HTML结构 -->
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
var
js = document.getElementById('js'),
list = document.getElementById('list');
list.appendChild(js);
js = document.getElementById('js'),
list = document.getElementById('list');
list.appendChild(js);
结果
<!-- HTML结构 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="js">JavaScript</p>
</div>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="js">JavaScript</p>
</div>
动态添加新节点
var
list = document.getElementById('list'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
list = document.getElementById('list'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
删除DOM
// 拿到待删除节点:
var self = document.getElementById('to-be-removed');
// 拿到父节点:
var parent = self.parentElement;
// 删除:
var removed = parent.removeChild(self);
removed === self; // true
var self = document.getElementById('to-be-removed');
// 拿到父节点:
var parent = self.parentElement;
// 删除:
var removed = parent.removeChild(self);
removed === self; // true
操作表单
获取值
// <input type="text" id="email">
var input = document.getElementById('email');
input.value; // '用户输入的值'
// <label><input type="radio" name="weekday" id="monday" value="1">
Monday</label>
// <label><input type="radio" name="weekday" id="tuesday" value="2">
Tuesday</label>
var mon = document.getElementById('monday');
var tue = document.getElementById('tuesday');
mon.value; // '1'
tue.value; // '2'
mon.checked; // true或者false
tue.checked; // true或者false
var input = document.getElementById('email');
input.value; // '用户输入的值'
// <label><input type="radio" name="weekday" id="monday" value="1">
Monday</label>
// <label><input type="radio" name="weekday" id="tuesday" value="2">
Tuesday</label>
var mon = document.getElementById('monday');
var tue = document.getElementById('tuesday');
mon.value; // '1'
tue.value; // '2'
mon.checked; // true或者false
tue.checked; // true或者false
设置值
// <input type="text" id="email">
var input = document.getElementById('email');
input.value = 'test@example.com'; // 文本框的内容已更新
var input = document.getElementById('email');
input.value = 'test@example.com'; // 文本框的内容已更新
提交表单
方式一是通过 <form> 元素的 submit() 方法提交一个表单
<!-- HTML -->
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
VUE
前端体系
基础语法
ES6语法
let 和 const 命令
let 所声明的变量,只在 let 命令所在的代码块内有效
const 声明的变量是常量,不能被修改,类似于java中final关键字。
字符串扩展
includes() :返回布尔值,表示是否找到了参数字符串。
startsWith() :返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith() :返回布尔值,表示参数字符串是否在原字符串的尾部。
解构表达式
什么是解构? -- ES6中允许按照一定模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构 (
Destructuring)。
Destructuring)。
数组解构
let arr = [1,2,3] const [x,y,z] = arr;
// x,y,z将与arr中的每个位置对应来取值
// 然后打印 console.log(x,y,z);
const [a] = arr;
//只匹配1个参数 console.log(a);
// x,y,z将与arr中的每个位置对应来取值
// 然后打印 console.log(x,y,z);
const [a] = arr;
//只匹配1个参数 console.log(a);
函数优化
函数参数默认值
箭头函数
一个参数
多个参数
没有参数
代码不止一行,可以用 {} 括起来。
对象的函数属性简写
箭头函数结合解构表达式
map和reduce
map() :接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回。
举例:有一个字符串数组,我们希望转为int数组
reduce() :接收一个函数(必须)和一个初始值(可选),该函数接收两个参数:
第一个参数是上一次reduce处理的结果
第二个参数是数组中要处理的下一个元素
第一个参数是上一次reduce处理的结果
第二个参数是数组中要处理的下一个元素
reduce() 会从左到右依次把数组中的元素用reduce处理,并把处理的结果作为下次reduce的第一个参数。如果
是第一次,会把前两个元素作为计算参数,或者把用户指定的初始值作为起始参数
是第一次,会把前两个元素作为计算参数,或者把用户指定的初始值作为起始参数
扩展运算符
Promise
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语
法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用
同样的方法进行处理。
法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用
同样的方法进行处理。
set和map
set
// Set构造函数可以接收一个数组或空
let set = new Set();
set.add(1);// [1]
// 接收数组
let set2 = new Set([2,3,4,5,5]);
// 得到[2,3,4,5]
let set = new Set();
set.add(1);// [1]
// 接收数组
let set2 = new Set([2,3,4,5,5]);
// 得到[2,3,4,5]
方法
set.add(1);
// 添加 set.clear();
// 清空 set.delete(2);
// 删除指定元素 set.has(2);
// 判断是否存在 set.forEach(function(){})
//遍历元素 set.size;
// 元素个数。是属性,不是方法。
// 添加 set.clear();
// 清空 set.delete(2);
// 删除指定元素 set.has(2);
// 判断是否存在 set.forEach(function(){})
//遍历元素 set.size;
// 元素个数。是属性,不是方法。
map
map,本质是与Object类似的结构。不同在于,Object强制规定key只能是字符串。而Map结构的key可以是任意
对象。
对象。
class(类)的基本语法
基本使用
继承
Generator函数
Generator函数有两个特征: 一是 function命令与函数名 之间有一个星号: 二是 函数体内部使用 yield吾句定义不同
的内部状态。
的内部状态。
通过hello()返回的h对象,每调用一次next()方法返回一个对象,该对象包含了value值和done状态。直
到遇到return关键字或者函数执行完毕,这个时候返回的状态为ture,表示已经执行结束了。
到遇到return关键字或者函数执行完毕,这个时候返回的状态为ture,表示已经执行结束了。
for...of循环
修饰器(Decorator)
修饰器(Decorator)是一个函数, 用来修改类的行为
转码器
Babel (babeljs.io)是一个广为使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而 在浏览器或其他环境
执行 。
Google 公司的 Traceur 转码器 Cgithub.com/google/traceur-compiler), 也可 以将 ES6 代码转为ES5的代
码。
执行 。
Google 公司的 Traceur 转码器 Cgithub.com/google/traceur-compiler), 也可 以将 ES6 代码转为ES5的代
码。
模块化
类似于Java中的导出类
React
认识React
react概述
什么是react
react是一个构建用户界面的JavaScript库
用户界面:HTML界面
React主要用来写HTML界面或构建web应用
用户界面:HTML界面
React主要用来写HTML界面或构建web应用
react特点
声明式
基于组件
基于组件
组件最重要,通过组合复用多个组件,可以实现完整的页面功能
react基本使用
react的安装
npm i react react-dom
react包是核心,提供创建元素、组件等功能
react-dom 包括dom相关功能等
react-dom 包括dom相关功能等
react的使用
引入俩个文件
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
2.创建React元素
3.渲染React元素到页面中
3.渲染React元素到页面中
<div id="root"></div>
<script>
const title = React.createElement('h1', null, 'Hello React')
ReactDOM.render(title, document.getElementById('root'))
</script>
<script>
const title = React.createElement('h1', null, 'Hello React')
ReactDOM.render(title, document.getElementById('root'))
</script>
can1:要渲染的react元素
can2:渲染的位置
ReactDOM.render(can1,can2)
can2:渲染的位置
ReactDOM.render(can1,can2)
react的脚手架使用
脚手架意义
脚手架开发是现代应用的必备
充分利用Webpack、Babel、ESLint等工具辅助项目开发。
零配置,无需手动配置过多的东西
关注业务而不是工具配置
使用脚手化初始化项目
npx create-react-app my-app
启动项目
在项目根目录执行npm start
JSX
JSX的基本使用
//使用JSX语法,创建react元素:
const title = <h1>Hello JSX</h1>
const title = <h1>Hello JSX</h1>
//渲染创建好的React元素
ReactDOM.render(title, root)
ReactDOM.render(title, root)
为什么脚手架中可以使用
JSX 语法?
JSX 语法?
1.JSX 不是标准的ECMAScript 语法,它是ECMAScript 的语法扩展。
2.需要使用babel 编译处理后,才能在浏览器环境中使用。
3.create-react-app 脚手架中已经默认有该配置,无需手动配置。
4.编译JSX 语法的包为:@babel/preset-react。
2.需要使用babel 编译处理后,才能在浏览器环境中使用。
3.create-react-app 脚手架中已经默认有该配置,无需手动配置。
4.编译JSX 语法的包为:@babel/preset-react。
注意点
驼峰命名法 class->className for -> htmlFor tabindex -> tabIndex
没有子节点的react元素可以靠/>结束
可以使用小括号包括,避免js插入分号
JSX中使用JavaScript表达式
嵌入js表达式
const name="张三"
const dv=(<div>my name is {name}</div>)
const name="张三"
const dv=(<div>my name is {name}</div>)
不能在括号中出现if for
JSX的条件渲染
根据特定的场景,可以使用 if for 逻辑运算符, 或者三元运算符来实现
const loadData= () => {
if (isLoading) {
return <div>数据加载中,请稍后...</div>}
return (<div>数据加载完成,此处显示加载后的数据</div>)
}
const dv = (<div>{loadData()}</div>
)
if (isLoading) {
return <div>数据加载中,请稍后...</div>}
return (<div>数据加载完成,此处显示加载后的数据</div>)
}
const dv = (<div>{loadData()}</div>
)
JSX的列表渲染
如果要渲染一组数据用map
注意:渲染列表时应该添加key 属性,key 属性的值要保证唯一
原则:map() 遍历谁,就给谁添加key 属性
注意:尽量避免使用索引号作为key
原则:map() 遍历谁,就给谁添加key 属性
注意:尽量避免使用索引号作为key
const songs = [
{id: 1, name: '痴心绝对'},
{id: 2, name: '像我这样的人'},
{id: 3, name: '南山南'},]
const list = (<ul>{songs.map(item => <li>{item.name}</li>)}</ul>)
{id: 1, name: '痴心绝对'},
{id: 2, name: '像我这样的人'},
{id: 3, name: '南山南'},]
const list = (<ul>{songs.map(item => <li>{item.name}</li>)}</ul>)
{songs.map(item => <li key={item.id}>{item.name}</li>)}
JSX的样式处理
行内样式
<h1 style={{ color: 'red', backgroundColor: 'skyblue' }}>
JSX的样式处理
</h1>
JSX的样式处理
</h1>
类名 className
<h1 className="title">
JSX的样式处理
</h1>
JSX的样式处理
</h1>
React组件
React组件介绍
特点:可复用、独立、可组合
React组件创建的俩种方式
1. 使用函数创建组件
函数名称必须以大写字母开头
函数组件必须有返回值,表示该组件的结构
如果返回值为 null,表示不渲染任何内容
如果返回值为 null,表示不渲染任何内容
function Hello() {
return (
<div>这是我的第一个函数组件!</div>
) }
return (
<div>这是我的第一个函数组件!</div>
) }
用函数名作为组件标签名
ReactDOM.render(<Hello />, root)
2. 使用类创建组件
类组件:使用 ES6 的 class 创建的组件
约定1:类名称也必须以大写字母开头
约定2:类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
约定3:类组件必须提供 render() 方法
约定4:render() 方法必须有返回值,表示该组件的结构
约定1:类名称也必须以大写字母开头
约定2:类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
约定3:类组件必须提供 render() 方法
约定4:render() 方法必须有返回值,表示该组件的结构
class Hello extends React.Component {
render() {
return <div>Hello Class Component!</div>
} }
ReactDOM.render(<Hello />, root)
render() {
return <div>Hello Class Component!</div>
} }
ReactDOM.render(<Hello />, root)
3 抽离为独立 JS 文件
选择一:将所有组件放在同一个JS文件中
选择二:将每个组件放到单独的JS文件中
组件作为一个独立的个体,一般都会放到一个单独的 JS 文件中
选择二:将每个组件放到单独的JS文件中
组件作为一个独立的个体,一般都会放到一个单独的 JS 文件中
1. 创建Hello.js
2. 在 Hello.js 中导入React
3. 创建组件(函数 或 类)
4. 在 Hello.js 中导出该组件
5. 在 index.js 中导入 Hello 组件
6. 渲染组件
2. 在 Hello.js 中导入React
3. 创建组件(函数 或 类)
4. 在 Hello.js 中导出该组件
5. 在 index.js 中导入 Hello 组件
6. 渲染组件
// index.js
import Hello from './Hello'
// 渲染导入的Hello组件
ReactDOM.render(<Hello />, root)
import Hello from './Hello'
// 渲染导入的Hello组件
ReactDOM.render(<Hello />, root)
// Hello.js
import React from 'react'
class Hello extends React.Component {
render() {
return <div>Hello Class Component!</div>
} }
// 导出Hello组件
export default Hello
import React from 'react'
class Hello extends React.Component {
render() {
return <div>Hello Class Component!</div>
} }
// 导出Hello组件
export default Hello
React事件处理
事件绑定
语法:on+事件名称={事件处理程序}
,onClick={() => {}}
React 事件采用驼峰命名法
onMouseEnter、onFocus
,onClick={() => {}}
React 事件采用驼峰命名法
onMouseEnter、onFocus
class App extends React.Component {
handleClick() {
console.log('单击事件触发了')
}
render() {
return (
<button onClick={this.handleClick}></button>
) } }
handleClick() {
console.log('单击事件触发了')
}
render() {
return (
<button onClick={this.handleClick}></button>
) } }
function App() {
function handleClick() {
console.log('单击事件触发了')
}
return (
<button onClick={handleClick}>点我</button>
)
}
function handleClick() {
console.log('单击事件触发了')
}
return (
<button onClick={handleClick}>点我</button>
)
}
事件对象
可以通过事件处理程序的参数获取到事件对象
React 中的事件对象叫做:合成事件(对象)
合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
React 中的事件对象叫做:合成事件(对象)
合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
function handleClick(e) {
e.preventDefault()
console.log('事件对象', e)
}
<a onClick={handleClick}>点我,不会跳转页面</a>
e.preventDefault()
console.log('事件对象', e)
}
<a onClick={handleClick}>点我,不会跳转页面</a>
有状态组件和无状态组件
函数组件又叫做无状态组件,类组件又叫做有状态组件
状态(state)即数据
函数组件没有自己的状态,只负责数据展示(静)
类组件有自己的状态,负责更新 UI,让页面“动” 起来
状态(state)即数据
函数组件没有自己的状态,只负责数据展示(静)
类组件有自己的状态,负责更新 UI,让页面“动” 起来
组件中的state和setState
1. state的基本使用
state 的值是对象,表示一个组件中可以有多个数据
class Hello extends React.Component {
constructor() {
super()
// 初始化state
this.state = {
count: 0
} }
render() {
return (
<div>有状态组件</div>
) } }
constructor() {
super()
// 初始化state
this.state = {
count: 0
} }
render() {
return (
<div>有状态组件</div>
) } }
class Hello extends React.Component {
// 简化语法
state= {
count: 0
}
render() {
return (
<div>有状态组件</div>
) } }
// 简化语法
state= {
count: 0
}
render() {
return (
<div>有状态组件</div>
) } }
获取状态state
class Hello extends React.Component {
// 简化语法
state= {
count: 0
}
render() {
return (
<div>有状态组件,{this.state.count}</div>
) } }
class Hello extends React.Component {
// 简化语法
state= {
count: 0
}
render() {
return (
<div>有状态组件,{this.state.count}</div>
) } }
2. setState()修改状态
setState() 作用:1. 修改 state 2. 更新UI
不能直接修改state值
不能直接修改state值
事件绑定this指向
1. 箭头函数
利用箭头函数自身不绑定this的特点
render() 方法中的 this 为组件实例,可以获取到 setState()
render() 方法中的 this 为组件实例,可以获取到 setState()
class Hello extends React.Component {
onIncrement() {
this.setState({ … })
}
render() {
// 箭头函数中的this指向外部环境,此处为:render()方法
return (
<button onClick={() => this.onIncrement()}></button>
) } }
onIncrement() {
this.setState({ … })
}
render() {
// 箭头函数中的this指向外部环境,此处为:render()方法
return (
<button onClick={() => this.onIncrement()}></button>
) } }
2. Function.prototype.bind()
class Hello extends React.Component {
constructor() {
super()
this.onIncrement = this.onIncrement.bind(this) }
// ...省略 onIncrement
render() {
return (
<button onClick={this.onIncrement}></button>
)
}
}
constructor() {
super()
this.onIncrement = this.onIncrement.bind(this) }
// ...省略 onIncrement
render() {
return (
<button onClick={this.onIncrement}></button>
)
}
}
利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
3. class 的实例方法
class Hello extends React.Component {
onIncrement = () => {
this.setState({ … })
}
render() {
return (
<button onClick={this.onIncrement}></button>
) } }
onIncrement = () => {
this.setState({ … })
}
render() {
return (
<button onClick={this.onIncrement}></button>
) } }
表单处理
1. 受控组件
HTML 中的表单元素是可输入的,也就是有自己的可变状态
而React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改
React将 state 与表单元素值value绑定到一起,由 state 的值来控制表单元素的值
受控组件:其值受到 React 控制的表单元素
而React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改
React将 state 与表单元素值value绑定到一起,由 state 的值来控制表单元素的值
受控组件:其值受到 React 控制的表单元素
2. 非受控组件(DOM方式)
借助ref使用dom元素
案例:评论列表
组件通讯
使用props接收数据
props的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收数据,类组件通过 this.props 接收数据
props特点:
1. 可以给组件传递任意类型的数据
2. props 是只读的对象,只能读取属性的值,无法修改对象
3. 注意:使用类组件时,如果写了构造函数,应该将 props
传递给 super(),否则,无法在构造函数中获取到 props!
传递给 super(),否则,无法在构造函数中获取到 props!
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。
在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好
的完成整个应用的功能。而在这个过程中,多个组件之间不可避免
的要共享某些数据。为了实现这些功能,就需要打破组件的独立封
闭性,让其与外界沟通。这个过程就是组件通讯。
在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好
的完成整个应用的功能。而在这个过程中,多个组件之间不可避免
的要共享某些数据。为了实现这些功能,就需要打破组件的独立封
闭性,让其与外界沟通。这个过程就是组件通讯。
组件通讯的三种方式
父组件 -> 子组件
1. 父组件提供要传递的state数据
2. 给子组件标签添加属性,值为 state 中的数据
3. 子组件中通过 props 接收父组件中传递的数据
2. 给子组件标签添加属性,值为 state 中的数据
3. 子组件中通过 props 接收父组件中传递的数据
class Parent extends React.Component {
state = { lastName: '王' }
render() {
return (
<div>
传递数据给子组件:<Child name={this.state.lastName} />
</div>
) } }
state = { lastName: '王' }
render() {
return (
<div>
传递数据给子组件:<Child name={this.state.lastName} />
</div>
) } }
function Child(props) {
return <div>子组件接收到数据:{props.name}</div> }
子组件 -> 父组件
1. 父组件提供一个回调函数(用于接收数据)
2. 将该函数作为属性的值,传递给子组件
2. 将该函数作为属性的值,传递给子组件
class Parent extends React.Component {
getChildMsg = (msg) => {
console.log('接收到子组件数据', msg)
}
render() {
return (
<div>
子组件:<Child getMsg={this.getChildMsg} />
</div>
) } }
getChildMsg = (msg) => {
console.log('接收到子组件数据', msg)
}
render() {
return (
<div>
子组件:<Child getMsg={this.getChildMsg} />
</div>
) } }
3. 子组件通过 props 调用回调函数
4. 将子组件的数据作为参数传递给回调函数
4. 将子组件的数据作为参数传递给回调函数
class Child extends React.Component {
state = { childMsg: 'React' }
handleClick = () => {
this.props.getMsg(this.state.childMsg) }
return (
<button onClick={this.handleClick}>点我,给父组件传递数据</button>
) }
state = { childMsg: 'React' }
handleClick = () => {
this.props.getMsg(this.state.childMsg) }
return (
<button onClick={this.handleClick}>点我,给父组件传递数据</button>
) }
兄弟组件
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
思想:状态提升
公共父组件职责:1. 提供共享状态 2. 提供操作共享状态的方法
要通讯的子组件只需通过 props 接收状态或操作状态的方法
思想:状态提升
公共父组件职责:1. 提供共享状态 2. 提供操作共享状态的方法
要通讯的子组件只需通过 props 接收状态或操作状态的方法
Context
作用
跨组件传递数据
使用步骤
1. 调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。
const { Provider, Consumer } = React.createContext()
const { Provider, Consumer } = React.createContext()
2. 使用 Provider 组件作为父节点。
<Provider>
<div className="App">
<Child1 />
</div>
</Provider>
<Provider>
<div className="App">
<Child1 />
</div>
</Provider>
3. 设置 value 属性,表示要传递的数据。
<Provider value="pink">
<Provider value="pink">
4. 调用 Consumer 组件接收数据。
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
总结
总结:
1. 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
2. Context提供了两个组件:Provider 和 Consumer
3. Provider组件:用来提供数据
4. Consumer组件:用来消费数据
1. 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
2. Context提供了两个组件:Provider 和 Consumer
3. Provider组件:用来提供数据
4. Consumer组件:用来消费数据
props深入
Children属性
children 属性
children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性
children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性
children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
function Hello(props) {
return (
<div>
组件的子节点:{props.children}
</div>
) }
<Hello>我是子节点</Hello>
return (
<div>
组件的子节点:{props.children}
</div>
) }
<Hello>我是子节点</Hello>
props校验
// 小明创建的组件App
function App(props) {
const arr = props.colors
const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
return (
<ul>{lis}</ul> ) }
// 小红使用组件App
<App colors={19} />
function App(props) {
const arr = props.colors
const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
return (
<ul>{lis}</ul> ) }
// 小红使用组件App
<App colors={19} />
2.props 校验:允许在创建组件的时候,就指定 props 的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
1. 安装包 prop-types (yarn add prop-types / npm i props-types)
2. 导入 prop-types 包
3. 使用组件名.propTypes = {} 来给组件的props添加校验规则
4. 校验规则通过 PropTypes 对象来指定
2. 导入 prop-types 包
3. 使用组件名.propTypes = {} 来给组件的props添加校验规则
4. 校验规则通过 PropTypes 对象来指定
import PropTypes from 'prop-types'
function App(props) {
return (
<h1>Hi, {props.colors}</h1>
) }
App.propTypes = {
// 约定colors属性为array类型
// 如果类型不对,则报出明确错误,便于分析错误原因
colors: PropTypes.array
}
function App(props) {
return (
<h1>Hi, {props.colors}</h1>
) }
App.propTypes = {
// 约定colors属性为array类型
// 如果类型不对,则报出明确错误,便于分析错误原因
colors: PropTypes.array
}
约束规则
1. 常见类型:array、bool、func、number、object、string
2. React元素类型:element
3. 必填项:isRequired
4. 特定结构的对象:shape({ })
1. 常见类型:array、bool、func、number、object、string
2. React元素类型:element
3. 必填项:isRequired
4. 特定结构的对象:shape({ })
// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
props 的默认值
场景:分页组件 每页显示条数
作用:给 props 设置默认值,在未传入 props 时生效
场景:分页组件 每页显示条数
作用:给 props 设置默认值,在未传入 props 时生效
function App(props) {
return (
<div>
此处展示props的默认值:{props.pageSize}
</div>
) }
// 设置默认值
App.defaultProps = {
pageSize: 10
}
// 不传入pageSize属性
<App />
return (
<div>
此处展示props的默认值:{props.pageSize}
</div>
) }
// 设置默认值
App.defaultProps = {
pageSize: 10
}
// 不传入pageSize属性
<App />
组件的生命周期
意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数。
钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
只有 类组件 才有生命周期
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数。
钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
只有 类组件 才有生命周期
三个阶段
1. 每个阶段的执行时机
2. 每个阶段钩子函数的执行顺序
3. 每个阶段钩子函数的作用
2. 每个阶段钩子函数的执行顺序
3. 每个阶段钩子函数的作用
创建时
更新时
卸载时
render-props和高阶组件
思路:将要复用的state和操作state的方法封装到一个组件中
问题1:如何拿到该组件中复用的state?
在使用组件时,添加一个值为函数的prop,通过 函数参数 来获取(需要组件内部实现)
问题1:如何拿到该组件中复用的state?
在使用组件时,添加一个值为函数的prop,通过 函数参数 来获取(需要组件内部实现)
问题2:如何渲染任意的UI?
使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
<Mouse render={(mouse) => {}}/>
<Mouse render={(mouse) => (
<p>鼠标当前位置 {mouse.x},{mouse.y}</p>
)}
使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
<Mouse render={(mouse) => {}}/>
<Mouse render={(mouse) => (
<p>鼠标当前位置 {mouse.x},{mouse.y}</p>
)}
使用步骤
使用步骤
1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
2. 将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
3. 使用 props.render() 的返回值作为要渲染的内容
1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
2. 将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
3. 使用 props.render() 的返回值作为要渲染的内容
class Mouse extends React.Component {
// … 省略state和操作state的方法
render() {
return this.props.render(this.state) } }
<Mouse render={(mouse) => <p>鼠标当前位置 {mouse.x},{mouse.y}</p>}/>
// … 省略state和操作state的方法
render() {
return this.props.render(this.state) } }
<Mouse render={(mouse) => <p>鼠标当前位置 {mouse.x},{mouse.y}</p>}/>
演示Mouse组件的复用
Mouse组件负责:封装复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
状态:鼠标坐标(x, y)
操作状态的方法:鼠标移动事件
传入的render prop负责:使用复用的状态来渲染UI结构
状态:鼠标坐标(x, y)
操作状态的方法:鼠标移动事件
传入的render prop负责:使用复用的状态来渲染UI结构
class Mouse extends React.Component {
// … 省略state和操作state的方法
render() {
return this.props.render(this.state) } }
<Mouse render={(mouse) => <p>鼠标当前位置 {mouse.x},{mouse.y}</p>}/>
// … 省略state和操作state的方法
render() {
return this.props.render(this.state) } }
<Mouse render={(mouse) => <p>鼠标当前位置 {mouse.x},{mouse.y}</p>}/>
children代替render属性
<Mouse>
{({x, y}) => <p>鼠标的位置是 {x},{y}</p> }
</Mouse>
// 组件内部:
this.props.children(this.state)
// Context 中的用法:
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
{({x, y}) => <p>鼠标的位置是 {x},{y}</p> }
</Mouse>
// 组件内部:
this.props.children(this.state)
// Context 中的用法:
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
代码优化
代码优化
1. 推荐:给 render props 模式添加 props校验
2. 应该在组件卸载时解除 mousemove 事件绑定
Mouse.propTypes = {
chidlren: PropTypes.func.isRequired
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove) }
1. 推荐:给 render props 模式添加 props校验
2. 应该在组件卸载时解除 mousemove 事件绑定
Mouse.propTypes = {
chidlren: PropTypes.func.isRequired
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove) }
高阶组件
高阶组件(HOC,Higher-Order Component)是一个函数,接收要包装的组件,返回增强后的组件
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给
被包装组件 WrappedComponent
const EnhancedComponent = withHOC(WrappedComponent)
// 高阶组件内部创建的类组件:
class Mouse extends React.Component {
render() {
return <WrappedComponent {...this.state} />
} }
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给
被包装组件 WrappedComponent
const EnhancedComponent = withHOC(WrappedComponent)
// 高阶组件内部创建的类组件:
class Mouse extends React.Component {
render() {
return <WrappedComponent {...this.state} />
} }
使用步骤
1. 创建一个函数,名称约定以 with 开头
2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
4. 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
4. 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
设置displayname
使用高阶组件存在的问题:得到的两个组件名称相同
原因:默认情况下,React使用组件名称作为 displayName
解决方式:为 高阶组件 设置 displayName 便于调试时区分不同的组件
displayName的作用:用于设置调试信息(React Developer Tools信息)
设置方式:
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component' }
原因:默认情况下,React使用组件名称作为 displayName
解决方式:为 高阶组件 设置 displayName 便于调试时区分不同的组件
displayName的作用:用于设置调试信息(React Developer Tools信息)
设置方式:
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component' }
传递props
问题:props丢失
原因:高阶组件没有往下传递props
解决方式:渲染 WrappedComponent 时,将 state 和 this.props 一起传递给组件
传递方式:
<WrappedComponent {...this.state} {...this.props} />
原因:高阶组件没有往下传递props
解决方式:渲染 WrappedComponent 时,将 state 和 this.props 一起传递给组件
传递方式:
<WrappedComponent {...this.state} {...this.props} />
React原理
setState() 的说明
setState() 是异步更新数据的
注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()
可以多次调用 setState() ,只会触发一次重新渲染
this.state = { count: 1 }
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 1
注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()
可以多次调用 setState() ,只会触发一次重新渲染
this.state = { count: 1 }
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 1
推荐语法
推荐使用 setState((state, props) => {}) 语法
参数state:表示最新的state
参数props:表示最新的props
this.setState((state, props) => {
return {
count: state.count + 1
}
})
console.log(this.state.count) // 1
推荐使用 setState((state, props) => {}) 语法
参数state:表示最新的state
参数props:表示最新的props
this.setState((state, props) => {
return {
count: state.count + 1
}
})
console.log(this.state.count) // 1
第二个参数
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法: setState(updater[, callback])
this.setState(
(state, props) => {},
() => {console.log('这个回调函数会在状态更新后立即执行')}
)
this.setState(
(state, props) => {},
() => {
document.title = '更新state后的标题:' + this.state.count
} )
语法: setState(updater[, callback])
this.setState(
(state, props) => {},
() => {console.log('这个回调函数会在状态更新后立即执行')}
)
this.setState(
(state, props) => {},
() => {
document.title = '更新state后的标题:' + this.state.count
} )
JSX语法的转化
组件更新机制
setState() 的两个作用: 1. 修改 state 2. 更新组件(UI
过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
组件性能优化
减轻state
避免不必要的重新渲染
组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
问题:子组件没有任何变化时也会重新渲染
如何避免不必要的重新渲染呢?
解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState) 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate render)
class Hello extends Component {
shouldComponentUpdate() {
// 根据条件,决定是否重新渲染组件
return false
}
render() {…}
}
问题:子组件没有任何变化时也会重新渲染
如何避免不必要的重新渲染呢?
解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState) 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate render)
class Hello extends Component {
shouldComponentUpdate() {
// 根据条件,决定是否重新渲染组件
return false
}
render() {…}
}
案例:随机数
class Hello extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextState.number !== this.state.number
}
render() {…}
}
class Hello extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.number !== this.props.number
}
render() {…}
}
class Hello extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextState.number !== this.state.number
}
render() {…}
}
class Hello extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.number !== this.props.number
}
render() {…}
}
纯组件
纯组件:PureComponent 与 React.Component 功能相似
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {
render() {
return (
<div>纯组件</div>
) } }
render() {
return (
<div>纯组件</div>
) } }
let number = 0
let newNumber = number
newNumber = 2
console.log(number === newNumber) // false
state = { number: 0 }
setState({
number: Math.floor(Math.random() * 3)
})
// PureComponent内部对比:
最新的state.number === 上一次的state.number // false,重新渲染组件
let newNumber = number
newNumber = 2
console.log(number === newNumber) // false
state = { number: 0 }
setState({
number: Math.floor(Math.random() * 3)
})
// PureComponent内部对比:
最新的state.number === 上一次的state.number // false,重新渲染组件
对于引用类型来说:只比较对象的引用(地址)是否相同
const obj = { number: 0 }
const newObj = obj
newObj.number = 2
console.log(newObj === obj) // true
state = { obj: { number: 0 } }
// 错误做法
state.obj.number = 2
setState({ obj: state.obj })
// PureComponent内部比较:
最新的state.obj === 上一次的state.obj // true,不重新渲染组件
const obj = { number: 0 }
const newObj = obj
newObj.number = 2
console.log(newObj === obj) // true
state = { obj: { number: 0 } }
// 错误做法
state.obj.number = 2
setState({ obj: state.obj })
// PureComponent内部比较:
最新的state.obj === 上一次的state.obj // true,不重新渲染组件
state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!
// 正确!创建新数据
const newObj = {...state.obj, number: 2}
setState({ obj: newObj })
// 正确!创建新数据
// 不要用数组的push / unshift 等直接修改当前数组的的方法
// 而应该用 concat 或 slice 等这些返回新数组的方法
this.setState({
list: [...this.state.list, {新数据}]
})
// 正确!创建新数据
const newObj = {...state.obj, number: 2}
setState({ obj: newObj })
// 正确!创建新数据
// 不要用数组的push / unshift 等直接修改当前数组的的方法
// 而应该用 concat 或 slice 等这些返回新数组的方法
this.setState({
list: [...this.state.list, {新数据}]
})
虚拟 DOM 和 Diff 算法
Conten
Conten
React 更新视图的思想是:只要 state 变化就重新渲染视图
特点:思路非常清晰
问题:组件中只有一个 DOM 元素需要更新时,也得把整个组件的内容重新渲染到页面中? 不是
理想状态:部分更新,只更新变化的地方。
问题:React 是如何做到部分更新的? 虚拟 DOM 配合 Diff 算法
特点:思路非常清晰
问题:组件中只有一个 DOM 元素需要更新时,也得把整个组件的内容重新渲染到页面中? 不是
理想状态:部分更新,只更新变化的地方。
问题:React 是如何做到部分更新的? 虚拟 DOM 配合 Diff 算法
执行过程
1. 初次渲染时,React 会根据初始state(Model),创建一个虚拟 DOM 对象(树)。
2. 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。
3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
5. 最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。
1. 初次渲染时,React 会根据初始state(Model),创建一个虚拟 DOM 对象(树)。
2. 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。
3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
5. 最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。
代码演示
组件 render() 调用后,根据 状态 和 JSX结构 生成虚拟DOM对象
示例中,只更新 p 元素的文本节点内容
{
type: 'div',
props: {
children: [
{ type: 'h1', props: {children: '随机数'} },
{ type: 'p', props: {children: 0} }
] } }
// ...省略其他结构
{ type: 'p', props: {children: 2} }
组件 render() 调用后,根据 状态 和 JSX结构 生成虚拟DOM对象
示例中,只更新 p 元素的文本节点内容
{
type: 'div',
props: {
children: [
{ type: 'h1', props: {children: '随机数'} },
{ type: 'p', props: {children: 0} }
] } }
// ...省略其他结构
{ type: 'p', props: {children: 2} }
React 原理揭秘
1. 工作角度:应用第一,原理第二。
2. 原理有助于更好地理解 React 的自身运行机制。
3. setState()异步更新数据。
4. 父组件更新导致子组件更新,纯组件提升性能。
5. 思路清晰简单为前提,虚拟 DOM 和 Diff 保效率。
6. 虚拟 DOM state + JSX。
7. 虚拟 DOM 的真正价值从来都不是性能。
1. 工作角度:应用第一,原理第二。
2. 原理有助于更好地理解 React 的自身运行机制。
3. setState()异步更新数据。
4. 父组件更新导致子组件更新,纯组件提升性能。
5. 思路清晰简单为前提,虚拟 DOM 和 Diff 保效率。
6. 虚拟 DOM state + JSX。
7. 虚拟 DOM 的真正价值从来都不是性能。
React路由
概述
前端路由是一套映射规则,在React中,是 URL路径 与 组件 的对应关系
使用React路由简单来说,就是配置 路径和组件(配对)
使用React路由简单来说,就是配置 路径和组件(配对)
基本使用
使用步骤
1. 安装:yarn add react-router-dom
2. 导入路由的三个核心组件:Router / Route / Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
3. 使用 Router 组件包裹整个应用(重要)
<Router>
<div className="App">
// … 省略页面内容
</div>
</Router>
2. 导入路由的三个核心组件:Router / Route / Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
3. 使用 Router 组件包裹整个应用(重要)
<Router>
<div className="App">
// … 省略页面内容
</div>
</Router>
4. 使用 Link 组件作为导航菜单(路由入口)
<Link to="/first">页面一</Link>
5. 使用 Route 组件配置路由规则和要展示的组件(路由出口)
const First = () => <p>页面一的页面内容</p>
<Router>
<div className="App">
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
<Link to="/first">页面一</Link>
5. 使用 Route 组件配置路由规则和要展示的组件(路由出口)
const First = () => <p>页面一的页面内容</p>
<Router>
<div className="App">
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
常用组件说明
Router 组件:包裹整个应用,一个 React 应用只需要使用一次
两种常用 Router:HashRouter 和 BrowserRouter
HashRouter:使用 URL 的哈希值实现(localhost:3000/#/first) (推荐)BrowserRouter:使用 H5 的 history API 实现(localhost:3000/first) Router 组件:包裹整个应用,一个 React 应用只需要使用一次
两种常用 Router:HashRouter 和 BrowserRouter
HashRouter:使用 URL 的哈希值实现(localhost:3000/#/first) (推荐)BrowserRouter:使用 H5 的 history API 实现(localhost:3000/first)
两种常用 Router:HashRouter 和 BrowserRouter
HashRouter:使用 URL 的哈希值实现(localhost:3000/#/first) (推荐)BrowserRouter:使用 H5 的 history API 实现(localhost:3000/first) Router 组件:包裹整个应用,一个 React 应用只需要使用一次
两种常用 Router:HashRouter 和 BrowserRouter
HashRouter:使用 URL 的哈希值实现(localhost:3000/#/first) (推荐)BrowserRouter:使用 H5 的 history API 实现(localhost:3000/first)
Link 组件:用于指定导航链接(a 标签)
// to属性:浏览器地址栏中的pathname(location.pathname) <Link to="/first">页面一</Link>
Route 组件:指定路由展示组件相关信息
// path属性:路由规则
// component属性:展示的组件
// Route组件写在哪,渲染出来的组件就展示在哪 <Route path="/first" component={First}></Route>
// to属性:浏览器地址栏中的pathname(location.pathname) <Link to="/first">页面一</Link>
Route 组件:指定路由展示组件相关信息
// path属性:路由规则
// component属性:展示的组件
// Route组件写在哪,渲染出来的组件就展示在哪 <Route path="/first" component={First}></Route>
路由的执行过程
1. 点击 Link 组件(a标签),修改了浏览器地址栏中的 url 。
2. React 路由监听到地址栏 url 的变化。
3. React 路由内部遍历所有 Route 组件,使用路由规则( path )与 pathname 进行匹配。
4. 当路由规则(path)能够匹配地址栏中的 pathname 时,就展示该 Route 组件的内容。
2. React 路由监听到地址栏 url 的变化。
3. React 路由内部遍历所有 Route 组件,使用路由规则( path )与 pathname 进行匹配。
4. 当路由规则(path)能够匹配地址栏中的 pathname 时,就展示该 Route 组件的内容。
编程式导航
场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?
编程式导航:通过 JS 代码来实现页面跳转
history 是 React 路由提供的,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数 path 表示要跳转的路径
go(n): 前进或后退到某个页面,参数 n 表示前进或后退页面数量(比如:-1 表示后退到上一页)
class Login extends Component {
handleLogin = () => {
// ...
this.props.history.push('/home')
}
render() {...省略其他代码} }
编程式导航:通过 JS 代码来实现页面跳转
history 是 React 路由提供的,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数 path 表示要跳转的路径
go(n): 前进或后退到某个页面,参数 n 表示前进或后退页面数量(比如:-1 表示后退到上一页)
class Login extends Component {
handleLogin = () => {
// ...
this.props.history.push('/home')
}
render() {...省略其他代码} }
默认路由
问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?
默认路由:表示进入页面时就会匹配的路由
默认路由path为:/ <Route path="/" component={Home} />
默认路由:表示进入页面时就会匹配的路由
默认路由path为:/ <Route path="/" component={Home} />
匹配模式
模糊匹配模式
问题:当 Link组件的 to 属性值为 “/login”时,为什么 默认路由 也被匹配成功?
默认情况下,React 路由是模糊匹配模式
模糊匹配规则:只要 pathname 以 path 开头就会匹配成功
默认情况下,React 路由是模糊匹配模式
模糊匹配规则:只要 pathname 以 path 开头就会匹配成功
<Link to="/login">登录页面</Link>
<Route path="/" component={Home} /> 匹配成功
<Route path="/" component={Home} /> 匹配成功
// path 代表Route组件的path属性
// pathname 代表Link组件的to属性(也就是 location.pathname)
// pathname 代表Link组件的to属性(也就是 location.pathname)
精确匹配
问题:默认路由任何情况下都会展示,如何避免这种问题?
给 Route 组件添加 exact 属性,让其变为精确匹配模式
精确匹配:只有当 path 和 pathname 完全匹配时才会展示该路由
// 此时,该组件只能匹配 pathname=“/” 这一种情况
<Route exact path="/" component=... />
推荐:给默认路由添加 exact 属性。
给 Route 组件添加 exact 属性,让其变为精确匹配模式
精确匹配:只有当 path 和 pathname 完全匹配时才会展示该路由
// 此时,该组件只能匹配 pathname=“/” 这一种情况
<Route exact path="/" component=... />
推荐:给默认路由添加 exact 属性。
React 路由基础
1. React 路由可以有效的管理多个视图(组件)实现 SPA
2. Router组件包裹整个应用,只需要使用一次
3. Link组件是入口,Route组件是出口
4. 通过 props.history 实现编程式导航
5. 默认模糊匹配,添加 exact 变精确匹配
6. React 路由的一切都是组件,可以像思考组件一样思考路由
1. React 路由可以有效的管理多个视图(组件)实现 SPA
2. Router组件包裹整个应用,只需要使用一次
3. Link组件是入口,Route组件是出口
4. 通过 props.history 实现编程式导航
5. 默认模糊匹配,添加 exact 变精确匹配
6. React 路由的一切都是组件,可以像思考组件一样思考路由
项目
宜居商城
好客租房
JavaSE
JavaSE基础
环境基础
环境搭建
Java环境搭建
Java——1995年sun公司提出的高级语言,2009年被Oracle收购
Java之父——詹姆斯高斯林
Java具有可移植性,安全可靠,性能较好,开发社区完善,,功能丰富
- JDK,jre 1. java -verison查看版本号,确认是否安装成
第一个程序
开发步骤
HelloWord-程序
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
public static void main(String[] args) {
System.out.println("Hello World");
}
}
常见问题
windows名没有勾选
代码写了未保存
文件名和类名不一致
大小写错误
括号不对等
......
JDK组成
Jvm
Java的虚拟机,真正代码运行的地方
jre
Java的运行环境
jdk
Java开发工具包
Java跨平台
Idea
常用快捷键
Java语言
Java基础
注释
1、单行:// .....
2、多行:/* 注释内容可以多行 */
3、文档:/** 注释内容可以多行 */ 一般用到类上
数据类型
byte(1字节) short(2字节) int(4字节) long(8字节) float(4字节) double(8字节) boolean char
ASCLL编码表
计算机要存字符,但是又不能直接存储字符,只能存储字符编号的二进制形式。
结论:字符在计算机底层其实可以当整数使用的!
关键字、标志符
关键字:Java自己保留的特殊单词,有特殊含义;同学们需要注意的是不能用关键字取类名,变量名
标志符
字母,下划线,美元符,数字组成的名称
不能是数字,关键字,特殊符号开头
变量名称,有意义,全英文,首字母小写,满足驼峰模式
类型运算符
类型转换
自动类型转换
byte short int long float double
存在小范围类型的变量赋值给大范围类型的变量
表达式的自动类型转换
表达式的最终结果类型示由表达式中的最高类型决定
byte、short、char直接提升成int运算的、int、long、float、double
强制转换
强制类型转换可能出现 数据丢失
小数强制转换成整数,会截断小数部分,直接返回整数。如5.99 转换之后为5
面试题:
10进制转2进制
2进制转10进制
8进制和16进制
运算符
基本运算符
+ - * /
自增自减
++ --
a++和++a
++在前面先赋值,在计算
在后面,先计算再赋值
关系运算符
== > >= < <= !=
逻辑运算符
& | !^ && ||
& &&
作用:都可以判断条件都为true结果才是true
区别
& 即使第一个条件是false,依然执行后面的条件
第一个条件是false,后面条件不执行,性能好一点
| ||
都可以判断条件只要有一个为true结果就是true
区别
与&一样
^
相同为false,不同为true
三元运算符
类型 a>b?a:b;
嵌套为 a>b?(a>c?a:c):(b>c?b:c);
运算优先级
面试
1数字拆分
请输入整数:123整数123的个位为:3整数123的十位为:2整数123的百位为:1
键盘录入
Scanner
Scanner sc =new Scanner(System.in);
流程控制,random
程序流程控制
分支结构
if else
作用:根据条件有选择的执行代码
如果{}中只有一行代码,可以省略{}不写,不建议省略大扩展
switch case
表达式的类型:byte short char int ,JDK 5支持枚举,JDK 7开始支持String , 不支持double float long
值不能是变量,也不能重复
穿透性:含义:如果case中没有写break,遇到这个case会直接往下一个case执行,不会判断了,直到遇到break截止
循环结构
三种循环
for
for(初始化语句 ; 循环条件; 迭代语句) { 循环体代码 }
水仙花数
while
折纸与珠穆朗玛峰
do-while
三种循环的差别
1、for和while是判断后执行 ; do-while 第一次直接执行,再判断
2、for和while功能上没有任何区别,while能做的for一定能做
3、for的变量只能循环内部有效,while的变量从定义开始到循环结束后还可以被使用。
死循环
代码逻辑错误,程序一直执行
循环嵌套
循环套循环
跳转关键字:break、continue
break 结束
continue 跳出当前循环继续
random类
随机数类
猜数字游戏
array
数组
数组的定义
数组:得到一个容器,存储一批同种类型的数据。
静态初始化数组
特点:定义数组的时候,数据已经确定了,数据也存入进去了
int arr[]={1,2,3};
动态初始化数组
特点:定义的时候只确定数组的元素类型和长度,不存入具体的数据(先定义后赋值)
int arr[]=new int[3];
数组的访问
格式
数组名称[索引]
属性
从0开始,长度length为元素个数
数组的遍历
for循环
for each
数组的案例
数组求和
猜数字
求最值
随机排名
数组排序
数组内存图
方法区
放类信息class文件
栈内存
方法执行的区域,变量存储的区域
堆内存
new出来的东西都在这里,数组对象
常见问题
数组越界
Debug工具使用
作用:断点调试,看代码流程、看代码是否有bug,定位错误理解代码的重要手段
使用步骤
在需要控制的代码行左侧,点击一下,形成断点
选择使用Debug方式启动程序,启动后程序会在断点暂停
控制代码一行一行的往下执行
方法
方法:运行在栈内存
概念:方法是一种语法结构,它可以把一段代码封装成一个功能,以方便重复调用。
优点
提高代码的复用,减少代码冗余,提高开发效率
提高程序的逻辑性
定义
调用形式
方法是否需要返回值类型申明,以及参数要具体业务具体分析
调用
方法必须调用才可以跑起来
格式:方法名称(....)
常见问题
方法的编写顺序无所谓。
方法与方法之间是平级关系,不能嵌套定义。
注意是否有返回值
方法不调用就不执行, 调用时必须严格匹配方法的参数情况。
调用流程
方法没有被调用的时候,在方法区中的字节码文件中存放方法
被调用的时候,需要进入到栈内存中运行
方法的参数传递机制
值传递
传输的是实参存储的数据值,不是传输本身
基本类型传输的就是存储的数据
引用类型传输的就是存储的地址值
参数传递
使用方法接收了数组
方法重载
同一个类中,出现方法名称相同,形参列表不同的多个方法,他们就是重载的方法。
方法重载的识别技巧:
同一个类中,方法名称相同,形参列表不同就是重载的方法。
形参列表不同指的是个数,类型,顺序不同
return
在任何方法中都可以出现,立即跳出并结束当前方法的执行
小练习
买飞机票
需求
机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。按照如下规则计算机票价格:
旺季(5-10月)头等舱9折,经济舱8.5折,淡季(11月到来年4月)头等舱7折,经济舱6.5折。
找素数
需求
素数:如果除了1和它本身以外,不能被其他正整数整除,就叫素数。
开发验证码
需求
定义方法实现随机产生一个5位的验证码,每位可能是数字、大写字母、小写字母。
数组元素复制
需求
把一个数组中的元素复制到另一个新数组中去。
评委打分
需求
在唱歌比赛中,有6名评委给选手打分,分数范围是[0 - 100]之间的整数。选手的最后得分为:去掉最高分、最低分后的4个评委的平均分,请完成上述过程并计算出选手的得分。
数字加密
需求
某系统的数字密码,比如1983,采用加密方式进行传输,规则如下:先得到每位数,然后每位数都加上5 , 再对10求余,最后将所有数字反转,得到一串新数。
模拟双色球
需求
中奖号码由6个红球和1个篮球组成(注意:6个红球要求不能重复)。
可以定义方法用于返回一组中奖号码(7个数据),返回的形式是一个整型数组。
面向对象OOP
面向对象
概述
全部使用对象来解决问题,模仿现实世界的
面向对象中的重要2个概念
类:设计图,对象的共同特征的描述
对象:类的具体实例
在Java代码中:必须先有类,才能new对象
定义类,创建对象的代码写法
成员变量的格式
修饰符 数据类型 变量名称 = 初始化值
存在默认值的,一般不需要给初始化值
定义类的补充注意事项
首字母大写,满足驼峰,不能用关键字,必须是标志符,有意义
一个代码文件可以定义多个类,只能一个类是public修饰的,public修饰的类名必须成为代码的文件名
创建对象的
类名 对象变量 = new 类名();
对象内存图
对象放在堆内存中
对象变量栈内存中,存储的是对象在堆内存中的地址
成员变量的数据存放在对象中,存在于堆内存中。
垃圾回收
当堆内存中的类对象或数组对象,没有被任何
变量引用(指向)时,就会被判定为内存中的“垃圾
变量引用(指向)时,就会被判定为内存中的“垃圾
Java存在自动垃圾回收器,会定期进行清理。
构造器
初始化一个类的对象,并返回这个对象的地址
分类
有参数构造器:在初始化对象的时候,同时可以为对象进行赋值。
无参数构造器(默认存在的):初始化的对象时,成员变量的数据均采用默认值。
注意
任何类定义出来,默认就自带了无参数构造器,写不写都有。
一旦定义了有参数构造器,无参数构造器就没有了,此时就需要自己写一个无参数构造器了。
this关键字
作用
代表了当前对象的地址
可以访问当前对象的成员变量,可以区分变量是局部的还是对象的成员变量
出现位置
有参构造函数中
可以在成员方法中出现,用于指定当前变量是访问成员变量
封装
合理隐藏,合理的暴露
常见形式
一般把成员变量使用private修饰隐藏,只能本类访问
提供public修饰的getter setter方法暴露取值和赋值
好处
可以提升代码的安全性,可以提高开发的一个效率
注意:封装已经成为Java代码的标准,即使毫无意义,代码风格也要满足封装的要求来书写。
标准 JavaBean
是实体类,学生类、老师类、用户类
书写规范
提供无参数构造器
成员变量全部私有
提供getter setter方法暴露成员变量的取值和赋值
继承
概念
Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系。
提高代码复用性,减少代码冗余,增强类的功能扩展性。
格式
子类extens父类
继承后的特点
子类 继承父类,子类可以得到父类的属性和行为,子类可以使用。
Java中子类更强大
Java中子类更强大
子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
Java是单继承模式:一个类只能继承一个直接父类。
Java不支持多继承、但是支持多层继承。
Java中所有的类都是Object类的子类。
Java是单继承模式:一个类只能继承一个直接父类。
Java不支持多继承、但是支持多层继承。
Java中所有的类都是Object类的子类。
问题
子类是否可以继承父类的构造器?
不可以的,子类有自己的构造器,父类构造器用于初始化父类对象。
子类是否可以继承父类的私有成员?
可以的,只是不能直接访问。
子类是否可以继承父类的静态成员?
子类可以直接使用父类的静态成员(共享)
设计规范和内存原理
子类们相同特征(共性属性,共性方法)放在父类中定义,
子类独有的的属性和行为应该定义在子类自己里面
子类独有的的属性和行为应该定义在子类自己里面
为什么
如果子类的独有属性、行为定义在父类中,会导致其它子类
也会得到这些属性和行为,这不符合面向对象逻辑。
也会得到这些属性和行为,这不符合面向对象逻辑。
图解内存原理
要求
1.子类们相同特征(共性属性,共性方法)放在父类中定义。
2.子类独有的的属性和行为应该定义在子类自己里面。
2.子类独有的的属性和行为应该定义在子类自己里面。
继承后成员方法和变量访问特点
在子类方法中访问成员(成员变量、成员方法)满足:就近原则
1.先子类局部范围找
2.然后子类成员范围找
3.然后父类成员范围找如果父类范围还没有找到则报错。
1.先子类局部范围找
2.然后子类成员范围找
3.然后父类成员范围找如果父类范围还没有找到则报错。
方法重写
在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。
满足要求
当子类需要父类的功能,但父类的该功能不完全满足自己的需求时。子类可以重写父类中的方法。
注意
方法重写注意事项和要求重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
私有方法不能被重写。
子类重写父类方法时,访问权限必须大于或者等于父类 (暂时了解 :缺省 < protected < public)
子类不能重写父类的静态方法,如果重写会报错的。
私有方法不能被重写。
子类重写父类方法时,访问权限必须大于或者等于父类 (暂时了解 :缺省 < protected < public)
子类不能重写父类的静态方法,如果重写会报错的。
子类构造器的特点
1.子类继承父类后构造器的特点:
子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。
2.为什么?
子类在初始化的时候,有可能会使用到父类中的数据,
如果父类没有完成初始化,子类将无法使用父类的数据。
子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
3.怎么调用父类构造器的?
子类构造器的第一行语句默认都是:super(),不写也存在。
子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。
2.为什么?
子类在初始化的时候,有可能会使用到父类中的数据,
如果父类没有完成初始化,子类将无法使用父类的数据。
子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
3.怎么调用父类构造器的?
子类构造器的第一行语句默认都是:super(),不写也存在。
子类构造访问父类构造
super调用父类有参数构造器的作用: 初始化继承自父类的数据。
1.如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?会报错。
因为子类默认是调用父类无参构造器的。
2.如何解决?子类构造器中可以通过书写 super(…),手动调用父类的有参数构造器
1.如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?会报错。
因为子类默认是调用父类无参构造器的。
2.如何解决?子类构造器中可以通过书写 super(…),手动调用父类的有参数构造器
this super使用详解
注意
this(...)和super(…)使用注意点:
子类通过 this (...)去调用本类的其他构造器,本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类构造器的。注意:
this(…) super(…) 都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
子类通过 this (...)去调用本类的其他构造器,本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类构造器的。注意:
this(…) super(…) 都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
多态
概念
同类型的对象,调用同一个行为,表现出不同的行为特征
形式
父类 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器
多态的识别技巧
对于方法调用:编译看左边,运行看右边
对于变量调用:编译看左边,运行也看左边
多态的使用前提
必须继承或实现关系
必须父类类型指向子类对象
存在方法重写
多态的优势
右边对象解耦合
父类类型变量作为方法的参数,可以接收一切子类对象
多态的劣势
多态下无法调用子类独有功能
多态的类型转换
自动类型转换:Animal a = new Dog();
强制类型转换 : Dog d = (Dog)a;
出现类型转换异常
有继承关系的两个类就可以强制转换,编译不报错
运行可能出错
Cat c = (Cat)a;
Java建议强制转换前先判断真实类型
a instanceof Dog
a instanceof Cat
String ArrayList
string ArrayList
String
概念
定义字符串变量存储字符串对象,同时String类提供了很多操作字符串的功能
为什么不可变
String变量每次的修改其实都是产生并指向了新的字符串对象。
原来的字符串对象都是没有改变的,所以称不可变字符串。
String其实常被称为不可变字符串类型,它的对象在创建后不能被更改
创建对象的方式
以“”方式给出的字符串对象,在字符串常量池中存储,而且相同内容只会在其中存储一份
通过构造器new对象,每new一次都会产生一个新对象,放在堆内存中。
注意面试题== 和 equals的区别
面试题
如果是字符串比较应该使用使用什么方式进行比较,为什么?
使用String提供的equlas方法。
只关心内容一样就返回true。
子主题
基本数据类型比较时使用。
开发中什么时候使用==比较数据
基本数据类型比较时使用。
常用API
public int length()
返回此字符串的长度
public char charAt(int index)
获取某个索引位置处的字符
public char[] toCharArray():
将当前字符串转换成字符数组返回
public String substring(int beginIndex, int endIndex)
根据开始和结束索引进行截取,得到新的字符串(包前不包后)
public String substring(int beginIndex)
从传入的索引处截取,截取到末尾,得到新的字符串
public String replace(CharSequence target,CharSequence replacement)
使用新值,将字符串中的旧值替换,得到新的字符串
public String[] split(String regex)
根据传入的规则切割字符串,得到字符串数组返回
小练习
模拟用户登录,验证码问题
ArrayList
优点
ArrayList代表的是集合类,集合是一种容器,与数组类似,不同的是集合的大小是不固定的
集合非常适合做元素个数不确定,且要进行增删操作的业务场景。
集合的提供了许多丰富、好用的功能,而数组的功能很单一
创建,添加元素
ArrayList类如何创建集合对象的,如何添加元素?
⚫ ArrayList list = new ArrayList();
⚫ public boolean add(E e)
⚫ public void add(int index,E element)
⚫ ArrayList list = new ArrayList();
⚫ public boolean add(E e)
⚫ public void add(int index,E element)
集合中只能存储引用类型,不支持基本数据类型
常用API
public E get(int index) 返回指定索引处的元素
public int size() 返回集合中的元素的个数
public E remove(int index) 删除指定索引处的元素,返回被删除的元素
public boolean remove(Object o) 删除指定的元素,返回删除是否成功
public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
ATM系统
ATM系统
需求分析
面向对象分析
每个用户对象要对应一个账户对象:
所以需要设计账户类Account
所以需要设计账户类Account
使用集合容器分析
系统需要提供一个容器用于存储这些
账户对象的信息,我们选ArrayList
集合。
账户对象的信息,我们选ArrayList
集合。
程序流程控制
需要结合分支、循环、跳转等相关操
作控制程序的业务逻辑
作控制程序的业务逻辑
使用常见API
内容比较,分析,数据处理等需要用
到String等常用API
到String等常用API
功能设计
账户类、首页设计
用户开户功能实现
用户登录功能实现
用户操作页设计、查询账户、退出账户功能
用户存款、取款功能设计
用户转账功能设计
用户密码修改功能、销户功能
JavaSE加强
Static单例代码块
Static单例代码块
定义
static是静态的意思,可以修饰成员变量和成员方法。
static修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
static修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
成员变量
成员变量的分类和访问分别是什么样的?
静态成员变量(有static修饰,属于类、加载一次,可以被共享访问),访问格式
类名.静态成员变量(推荐)对象.
静态成员变量(不推荐)。
实例成员变量(无static修饰,属于对象),访问格式:
对象.实例成员变量。
类名.静态成员变量(推荐)对象.
静态成员变量(不推荐)。
实例成员变量(无static修饰,属于对象),访问格式:
对象.实例成员变量。
两种成员变量各自在什么情况下定义?
静态成员变量:表示在线人数等需要被共享的信息。
实例成员变量:属于每个对象,且每个对象信息不同时(name,age,…等)
实例成员变量:属于每个对象,且每个对象信息不同时(name,age,…等)
成员方法
静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象访问。
实例成员方法(无static修饰,属于对象),只能用对象触发访问。
使用场景
表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。
如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。
练习
需求:请完成一个标准实体类的设计,并提供如下要求实现。
①:某公司的员工信息系统中,需要定义一个公司的员工类Employee,
包含如下信息(name, age , 所在部门名称dept ) , 定义一个静态的成
员变量company记录公司的名称。
②:需要在Employee类中定义一个方法showInfos(),用于输出当前
员工对象的信息。如name, age ,dept 以及公司名称company的信息。
③:需要在Employee类中定义定义一个通用的静态方法compareByAge,
用于传输两个员工对象的年龄进入,并返回比较较大的年龄,
例如:2个人中的最大年龄是:45岁。
①:某公司的员工信息系统中,需要定义一个公司的员工类Employee,
包含如下信息(name, age , 所在部门名称dept ) , 定义一个静态的成
员变量company记录公司的名称。
②:需要在Employee类中定义一个方法showInfos(),用于输出当前
员工对象的信息。如name, age ,dept 以及公司名称company的信息。
③:需要在Employee类中定义定义一个通用的静态方法compareByAge,
用于传输两个员工对象的年龄进入,并返回比较较大的年龄,
例如:2个人中的最大年龄是:45岁。
工具类
工具类是什么,有什么好处?
内部都是一些静态方法,每个方法完成一个功能一次编写,处处可用,提高代码的重用性。
工具类有什么要求?
建议工具类的构造器私有化处理。工具类不需要创建对象。
练习
需求:在实际开发中,经常会遇到一些数组使用的工具类。请按照如下要求编写一个数组的工具类:
1.ArraysUtils:我们知道数组对象直接输出的时候是输出对象的地址的,
而项目中很多地方都需要返回数组的内容,请在ArraysUtils中提供一
个工具类方法toString,用于返回整数数组的内容,返回的字符串格式
如:[10, 20, 50, 34, 100](只考虑整数数组,且只考虑一维数组):
2.经常需要统计平均值,平均值为去掉最低分和最高分后的分值,请提供这样一个工具方法getAerage
,用于返回平均分。(只考虑浮点型数组,且只考虑一维数组):
3.定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果。
1.ArraysUtils:我们知道数组对象直接输出的时候是输出对象的地址的,
而项目中很多地方都需要返回数组的内容,请在ArraysUtils中提供一
个工具类方法toString,用于返回整数数组的内容,返回的字符串格式
如:[10, 20, 50, 34, 100](只考虑整数数组,且只考虑一维数组):
2.经常需要统计平均值,平均值为去掉最低分和最高分后的分值,请提供这样一个工具方法getAerage
,用于返回平均分。(只考虑浮点型数组,且只考虑一维数组):
3.定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果。
注意事项
static访问注意实现:
静态方法只能访问静态的成员,不可以直接访问实例成员。
实例方法可以访问静态的成员,也可以访问实例成员。
静态方法中是不可以出现this关键字的。
静态方法只能访问静态的成员,不可以直接访问实例成员。
实例方法可以访问静态的成员,也可以访问实例成员。
静态方法中是不可以出现this关键字的。
代码块
概念
代码块是类的5大成分之一(成员变量、构造器,方法,代码块,内部类),定义在类中方法外。
在Java类下,使用 { } 括起来的代码被称为代码块 。
在Java类下,使用 { } 括起来的代码被称为代码块 。
静态代码块:
格式:static{}
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
构造代码块
格式:{}
特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
使用场景:初始化实例资源。
特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
使用场景:初始化实例资源。
斗地主游戏
设计模式之——单例(standalone)
概念:单例模式可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象
饿汉单例模式:在创建对象时已经创建好了
/** a、定义一个单例类 */ public class SingleInstance {
/** c.定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance = new SingleInstance ();
/** b.单例必须私有构造器*/
private SingleInstance (){
System.out.println("创建了一个对象");
}
}
/** c.定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance = new SingleInstance ();
/** b.单例必须私有构造器*/
private SingleInstance (){
System.out.println("创建了一个对象");
}
}
实现步骤
饿汉单例的实现步骤?
1.定义一个类,把构造器私有
2.定义一个静态变量存储一个对象
1.定义一个类,把构造器私有
2.定义一个静态变量存储一个对象
懒汉单例模式:先准备好,在调用时才会创建
实现步骤
1.定义一个类,把构造器私有。
2.定义一个静态变量存储一个对象。
3.提供一个返回单例对象的方法。
2.定义一个静态变量存储一个对象。
3.提供一个返回单例对象的方法。
/** 定义一个单例类 */
class SingleInstance{
/** 定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance ; // null
/** 单例必须私有构造器*/
private SingleInstance(){}
/** 必须提供一个方法返回一个单例对象 */
public static SingleInstance getInstance(){
... return ...;
}
}
class SingleInstance{
/** 定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance ; // null
/** 单例必须私有构造器*/
private SingleInstance(){}
/** 必须提供一个方法返回一个单例对象 */
public static SingleInstance getInstance(){
... return ...;
}
}
面向对象进阶(包,抽象类等)
面向对象进阶
包
同一个包下的类可以互相访问
不同包下的类,必须导包才可以访问:import com....
同一个类中可以使用多个同名的类,但是默认只能导入一个,其他用包名.类名
权限修饰符
private --> 缺省 --- > protected ---> public
final
final修饰类,类不能被继承
final修饰方法,方法不能被重写
fInal修饰变量,有且仅能被赋值一次
常量
public static final修饰的成员变量
特点:有且仅能被赋值一次
作用
做系统配置信息
做信息标志和分类
枚举
枚举:特殊类型
写法:public enum Season{ SPRING, .... }
枚举:信息的标志和分类
枚举类不能被继承
抽象类
充当一种模板,能确定能复用的抽象类定义,功能不能确定的定义成抽象方法,子类实现
public abstract class Animal{}
抽象方法:只有方法签名,没有方法体,必须abstract修饰
抽象方法特点:
得到抽象方法,失去了创建对象的能力
抽象类其他成分全部有
抽象类的意义
面试注意
抽象类为什么不能创建对象?
类有的成员(成员变量、方法、构造器)抽象类都具备
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
不能用abstract修饰变量、代码块、构造器。
类有的成员(成员变量、方法、构造器)抽象类都具备
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
不能用abstract修饰变量、代码块、构造器。
final和abstract是什么关系?
互斥关系
abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
互斥关系
abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
作业:银行利息结算系统
需求
某软件公司要为某银行的业务支撑系统开发一个利息结算系统,
账户有活期和定期账户两种,活期是0.35%,定期是 1.75%,
定期如果满10万额外给予3%的收益。结算利息要先进行用户名、
密码验证,验证失败直接提示,登录成功进行结算
账户有活期和定期账户两种,活期是0.35%,定期是 1.75%,
定期如果满10万额外给予3%的收益。结算利息要先进行用户名、
密码验证,验证失败直接提示,登录成功进行结算
分析
步骤
1.创建一个抽象的账户类Account作为父类模板,提供属性(卡号,余额)
2.在父类Account中提供一个模板方法实现登录验证,利息结算、利息输出。
3.具体的利息结算定义成抽象方法,交给子类实现。
4.定义活期账户类,让子类重写实现具体的结算方法
5.定义定期账户类,让子类重写实现具体的结算方法
6.创建账户对象,完成相关功能。
2.在父类Account中提供一个模板方法实现登录验证,利息结算、利息输出。
3.具体的利息结算定义成抽象方法,交给子类实现。
4.定义活期账户类,让子类重写实现具体的结算方法
5.定义定期账户类,让子类重写实现具体的结算方法
6.创建账户对象,完成相关功能。
接口
定义
接口用关键字interface来定义
public interface 接口名 {
// 常量
// 抽象方法
}
public interface 接口名 {
// 常量
// 抽象方法
}
面试注意
类和类的关系:单继承。
类和接口的关系:多实现。
接口和接口的关系:多继承,一个接口可以同时继承多个接口。
类和接口的关系:多实现。
接口和接口的关系:多继承,一个接口可以同时继承多个接口。
1、接口不能创建对象
2、一个类实现多个接口,多个接口中有同样的静态方法不冲突。
3、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
4、一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
5、一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
2、一个类实现多个接口,多个接口中有同样的静态方法不冲突。
3、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
4、一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
5、一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
内部类,API
内部类
概念:一个类里面的类就是内部类
静态 成员 匿名
匿名内部类
方便构建子类对象,最终为了简化代码
格式:new 类|抽象类|接口() { 方法重写 }
特点
是一个没有名字的内部类
本身也是一个对象,是自己匿名内部类的对象
这个对象相当于是一个子类对象
使用形式:作为方法的参数,完成对象回调
常用API
object
常用方法
public String toString()
默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址
默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址
展示子类内容
public boolean equals(Object o)
是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false
是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false
Object的equals方法的作用是什么?
默认是与另一个对象比较地址是否一样让子类重写,
以便比较2个子类对象的内容是否相同
默认是与另一个对象比较地址是否一样让子类重写,
以便比较2个子类对象的内容是否相同
Object的equals相比较于string的equals更安全,因为在比较的时候多了一些判断
StringBulider
为什么拼接、反转字符串建议使用StringBuilder?
1.String :内容是不可变的、拼接字符串性能差。
2.StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
3.定义字符串使用String拼接,修改等操作字符串使用StringBuilder
1.String :内容是不可变的、拼接字符串性能差。
2.StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
3.定义字符串使用String拼接,修改等操作字符串使用StringBuilder
StringBuffer与StringBulider差别
为什么说StringBuffer会更安全
Stringbuffer在使用时,方法上大多数都有synchronized修饰
多线程,帮助buffer更好的拼接字符串
Math
System
BigDecimal
作用
解决浮点型运算精度失真问题。
获取方式
BigDecimal b1 = BigDecimal.valueOf(0.1);
日期
data
Date类代表当前所在系统的日期时间信息。
public Date() 创建一个Date对象,代表的是系统当前此刻日期时间
public long getTime() 返回从1970年1月1日 00:00:00走到此刻的总的毫秒数
时间毫秒值怎么恢复成日期对象
public Date(long time);
public void setTime(long time);
public Date(long time);
public void setTime(long time);
SimpleDateFormat
作用:可以去完成日期时间的格式化操作
public SimpleDateFormat(String pattern)
构造一个SimpleDateFormat,使用指定的格式
构造一个SimpleDateFormat,使用指定的格式
格式化方法
public final String format(Date date)
将日期格式化成日期/时间字符串
public final String format(Object time)
将时间毫秒值式化成日期/时间字符串
转化方式
SimpleDateFormat解析字符串时间成为日期对象
public Date parse(String source)
从给定字符串的开始解析文本以生成日期
从给定字符串的开始解析文本以生成日期
例题;请计算出当前时间往后走2天14小时49分06秒后的时间是多少
秒杀系统面试题
Calendar
获取一年中的某一天,或者一年中的某一周等
public static Calendar getInstance()获取当前日历对象
Java8新增日期工具
LocalDate:不包含具体时间的日期。
LocalTime:不含日期的时间。
LocalDateTime:包含了日期及时间。
Instant:代表的是时间戳。
DateTimeFormatter 用于做时间的格式化和解析的
Duration:用于计算两个“时间”间隔
Period:用于计算两个“日期”间隔
LocalTime:不含日期的时间。
LocalDateTime:包含了日期及时间。
Instant:代表的是时间戳。
DateTimeFormatter 用于做时间的格式化和解析的
Duration:用于计算两个“时间”间隔
Period:用于计算两个“日期”间隔
LocalDate、LocalTime、LocalDateTime
他们 分别表示日期,时间,日期时间对象,他们的类的实例是不可变的对象。他们三者构建对象和API都是通用的
public static Xxxx now();
静态方法,根据当前时间创建对象
LocaDate localDate = LocalDate.now();
LocalTime llocalTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
LocaDate localDate = LocalDate.now();
LocalTime llocalTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
public static Xxxx of(…);
静态方法,指定日期/时间创建对象
LocalDate localDate1 = LocalDate.of(2099 , 11,11);
LocalTime localTime1 = LocalTime.of(11, 11, 11);
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
LocalDate localDate1 = LocalDate.of(2099 , 11,11);
LocalTime localTime1 = LocalTime.of(11, 11, 11);
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
instant
包装类
1、包装类是什么,作用是什么?
基本数据类型对应的引用类型,实现了一切皆对象。
后期集合和泛型不支持基本类型,只能使用包装类。
2、包装类有哪些特殊功能?
可以把基本类型的数据转换成字符串类型(用处不大)
可以把字符串类型的数值转换成真实的数据类型(真的很有用)
基本数据类型对应的引用类型,实现了一切皆对象。
后期集合和泛型不支持基本类型,只能使用包装类。
2、包装类有哪些特殊功能?
可以把基本类型的数据转换成字符串类型(用处不大)
可以把字符串类型的数值转换成真实的数据类型(真的很有用)
正则表达式
public String replaceAll(String regex,String newStr)
按照正则表达式匹配的内容进行替换
按照正则表达式匹配的内容进行替换
public String[] split(String regex)
按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
Arrays
Lambda
使用方法和注意事项
1、Lambda表达式的基本作用?
简化函数式接口的匿名内部类的写法。
2、Lambda表达式有什么使用前提?
必须是接口的匿名内部类,接口中只能有一个抽象方法
简化函数式接口的匿名内部类的写法。
2、Lambda表达式有什么使用前提?
必须是接口的匿名内部类,接口中只能有一个抽象方法
Lambda表达式的省略写法
参数类型可以省略不写。
如果只有一个参数,参数类型可以省略,同时()也可以省略。
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
参数类型可以省略不写。
如果只有一个参数,参数类型可以省略,同时()也可以省略。
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
集合
集合
概述
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
特点
集合非常适合做元素的增删操作。
Collection单列集合,每个元素(数据)只包含一个值。
Map双列集合,每个元素包含两个值(键值对)。
Map双列集合,每个元素包含两个值(键值对)。
问题
如何约定集合存储数据的类型,需要注意什么?
集合支持泛型。
集合和泛型不支持基本类型,只支持引用数据类型
集合支持泛型。
集合和泛型不支持基本类型,只支持引用数据类型
collection常用方法
集合遍历存储
遍历
遍历方式
迭代器
Iterator<E> iterator() 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引
boolean hasNext() 询问当前位置是否有元素存在,存在返回true ,不存在返回false
E next() 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。
使用方法
for each
lambda
存储
集合中存储的是元素对象的地址。
常见数据结构
数据结构概述、栈、队列
栈
后进先出,先进后出
队列
先进先出,后进后出
数组
查询速度快
查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
删除效率低
要将原始数据删除,同时后面每个数据前移。
添加效率低
添加位置后的每个数据后移,再添加元素
链表
链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址
链表查询慢。无论查询哪个数据都要从头开始找
链表增删相对快
二叉树
1.只能有一个根节点: 每个节点最多支持2个直接子节点。
2.节点的度: 节点拥有的子树的个数,二叉树的度不大于2 叶子节点 度为0的节点,也称之为终端结点。
3.高度:叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。
4.层:根节点在第一层,以此类推
5.兄弟节点 :拥有共同父节点的节点互称为兄弟节点
2.节点的度: 节点拥有的子树的个数,二叉树的度不大于2 叶子节点 度为0的节点,也称之为终端结点。
3.高度:叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。
4.层:根节点在第一层,以此类推
5.兄弟节点 :拥有共同父节点的节点互称为兄弟节点
二叉查找树
二叉查找树又称二叉排序树或者二叉搜索树。
特点:1,每一个节点上最多有两个子节点
2,左子树上所有节点的值都小于根节点的值
3,右子树上所有节点的值都大于根节点的值
特点:1,每一个节点上最多有两个子节点
2,左子树上所有节点的值都小于根节点的值
3,右子树上所有节点的值都大于根节点的值
目的:提高检索数据的性能。
平衡二叉树
任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
如何保证平衡
基本策略是进行左旋,或者右旋保证平衡。
旋转的情况
左左
左右
右右
右左
左右
右右
右左
红黑树
概述
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的"红黑树"。
每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。
1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的"红黑树"。
每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。
规则
每一个节点或是红色的,或者是黑色的,根节点必须是黑色。
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
List
常见
ArrayList、LinekdList :
有序,可重复,有索引。
有序:存储和取出的元素顺序一致有索引
可以通过索引操作元素可重复
存储的元素可以重复
有序,可重复,有索引。
有序:存储和取出的元素顺序一致有索引
可以通过索引操作元素可重复
存储的元素可以重复
ArrayList
底层原理
ArrayList集合底层原理
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
面试题
扩容问题
在添加第11个元素时,数组会扩容,扩容大小为1.5倍((oldCapacity * 3)/2 + 1)
当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
迭代器会出现
for each 不会出现
for each 不会出现
LinkedList
底层是双向链表,增删快,而且多头尾操作
泛型深入
统一数据格式
通配符
set collection map
Set
集合特点
无序:存取顺序不一致
不重复:可以去除重复无索引
没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
不重复:可以去除重复无索引
没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
HashSet : 无序、不重复、无索引。
LinkedHashSet:有序、不重复、无索引。
TreeSet:排序、不重复、无索引。
Set集合的功能上基本上与Collection的API一致。
LinkedHashSet:有序、不重复、无索引。
TreeSet:排序、不重复、无索引。
Set集合的功能上基本上与Collection的API一致。
HashSet
HashSet集合底层采取哈希表存储的数据。哈希表是一种对于增删改查数据性能都较好的结构
哈希值: 是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
对象的哈希特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。
默认情况下,不同对象的哈希值是不同的。
JDK8之前由数组和链表组成
当挂在元素下面的数据过多时,查询性能降低
从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树
当挂在元素下面的数据过多时,查询性能降低
从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树
面试题
如何保证去重
判断位置是否为null,如果是则存储,不是的话使用equals方法比较值,相同不存储,不相同存储
如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
LinkedHashSet
底层原理
有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
TreeSet
不重复、无索引、可排序
按照元素的大小默认升序(有小到大)排序。
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
按照元素的大小默认升序(有小到大)排序。
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet无法直接排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet无法直接排序。
TreeSet集合自定义排序规则有几种方式2种。
1.类实现Comparable接口,重写比较规则。
2.集合自定义Comparator比较器对象,重写比较规则。
1.类实现Comparable接口,重写比较规则。
2.集合自定义Comparator比较器对象,重写比较规则。
collection
调用collections方法
Collections.sort(apples ,(o1,o2)->Double.compare(o1.getPrice()-o2.getPrice()));//根据价格的升序默认为升序
Collections.addAll();
Map
概述和使用
Map集合是一种双列集合,每个元素包含两个数据。
Map集合的每个元素的格式:key=value(键值对元素)。
Map集合也被称为“键值对集合”。
Map集合的每个元素的格式:key=value(键值对元素)。
Map集合也被称为“键值对集合”。
整体格式
Collection集合的格式: [元素1,元素2,元素3..]
Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}
Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}
特点
Map集合的特点都是由键决定的。
Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
Map集合后面重复的键对应的值会覆盖前面重复键的值。
Map集合的键值对都可以为null
Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
Map集合后面重复的键对应的值会覆盖前面重复键的值。
Map集合的键值对都可以为null
集合实现类的特点
HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
TreeMap:元素按照建是排序,只能对键排序,不重复,无索引的,值不做要求。
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
TreeMap:元素按照建是排序,只能对键排序,不重复,无索引的,值不做要求。
常用API
遍历方式
方式一:键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
先获取Map集合的全部键的Set集合。遍历键的Set集合,然后通过键提取对应值。
方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。遍历Set集合,然后提取键以及提取值。
方式三:JDK 1.8开始之后的新技术:Lambda表达式。
Map.foreach()
HashMap
由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
依赖hashCode方法和equals方法保证键的唯一。
如果键要存储的是自定义对象,需要重写hashCode和equals方法。
基于哈希表。增删改查的性能都较好。
依赖hashCode方法和equals方法保证键的唯一。
如果键要存储的是自定义对象,需要重写hashCode和equals方法。
基于哈希表。增删改查的性能都较好。
LinkedMap
由键决定:有序、不重复、无索引。这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
TreeMap
由键决定特性:不重复、无索引、可排序
可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
注意:TreeMap集合是一定要排序的,可以默认排序,
也可以将键按照指定的规则进行排序TreeMap跟TreeSet一样底层原理是一样的。
可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
注意:TreeMap集合是一定要排序的,可以默认排序,
也可以将键按照指定的规则进行排序TreeMap跟TreeSet一样底层原理是一样的。
可变集合和不可变集合
不可变
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
或者当集合对象被不可信的库调用时,不可变形式是安全的。
或者当集合对象被不可信的库调用时,不可变形式是安全的。
概念
不可变集合,就是不可被修改的集合。
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
如何创建
List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
Stream 异常
Stream流
概念
1、Stream流的作用是什么,结合了什么技术?简化集合、数组操作的API。结合了Lambda表达式。
2、说说Stream流的思想和使用步骤。
先得到集合或者数组的Stream流(就是一根传送带)。
把元素放上去。
用Stream流简化的API来方便的操作元素。
先得到集合或者数组的Stream流(就是一根传送带)。
把元素放上去。
用Stream流简化的API来方便的操作元素。
获取方式
集合:可以使用Collection接口中的默认方法stream()生成流
数组:public static <T> Stream<T> stream(T[] array)
public static<T> Stream<T> of(T... values)
public static<T> Stream<T> of(T... values)
操作方法
void forEach(Consumer action)
long count()
常用API
注意
Stream流无法直接修改数据
终结和非终结的含义是什么?
终结方法后流不可以继续使用,非终结方法会返回新的流,支持链式编程。
收集Stream流的含义:
就是把Stream流操作后的结果数据转回到集合或者数组中去。
Stream流:方便操作集合/数组的手段。
集合/数组:才是开发中的目的。
就是把Stream流操作后的结果数据转回到集合或者数组中去。
Stream流:方便操作集合/数组的手段。
集合/数组:才是开发中的目的。
方法
Collections.toList()
Collections.toMap()
Collections.toSet()
异常
概念
异常是程序在“编译”或者“执行”的过程中可能出现的问题,
注意:语法错误不算在异常体系中。 比如:数组索引越界、空指针异常、 日期格式化异常,等…
注意:语法错误不算在异常体系中。 比如:数组索引越界、空指针异常、 日期格式化异常,等…
异常分类
编译异常
编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错。
运行异常
运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
异常的执行流程
默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
直接从当前执行的异常点干掉当前程序。
后续代码没有机会执行了,因为程序已经死亡。
异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
直接从当前执行的异常点干掉当前程序。
后续代码没有机会执行了,因为程序已经死亡。
编译时异常处理形式
一、出现异常直接抛出去给调用者,调用者也继续抛出去。
二、出现异常自己捕获处理,不麻烦别人。
三、前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
二、出现异常自己捕获处理,不麻烦别人。
三、前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
throws
try catch finally
两者结合
日志框架
日志框架
概念
日志技术具备的优势
1.可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。
2.可以随时以开关的形式控制是否记录日志,无需修改源代码。
1.可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。
2.可以随时以开关的形式控制是否记录日志,无需修改源代码。
类型
日志规范接口
Commons Logging
JCL
Commons Logging
JCL
Simple Logging Facade for Java
日志实现框架
Log4j
JUL
(java.util.loggiing)
(java.util.loggiing)
Logback
其他实现
LogBack
Logback主要分为三个技术模块:
logback-core: logback-core 模块为其他两个模块奠定了基础,必须有。
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API。
logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能
logback-core: logback-core 模块为其他两个模块奠定了基础,必须有。
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API。
logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能
开发步骤
①:在项目下新建文件夹lib,导入Logback的相关jar包到该文件夹下,并添加到项目库中去。
②:必须将Logback的核心配置文件logback.xml直接拷贝到src目录下。
③:在代码中获取日志的对象
④:使用日志对象输出日志信息
②:必须将Logback的核心配置文件logback.xml直接拷贝到src目录下。
③:在代码中获取日志的对象
④:使用日志对象输出日志信息
输出位置
通过logback.xml 中的<append>标签可以设置输出位置和日志信息的详细格式。
通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中
通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中
日志级别
级别程度依次是:TRACE< DEBUG< INFO<WARN<ERROR ;
默认级别是debug(忽略大小写)
对应其方法。作用:用于控制系统中哪些日志级别是可以输出的,
只输出级别不低于设定级别的日志信息。ALL 和 OFF分别是打开全部日志信息,及关闭全部日志信息。
默认级别是debug(忽略大小写)
对应其方法。作用:用于控制系统中哪些日志级别是可以输出的,
只输出级别不低于设定级别的日志信息。ALL 和 OFF分别是打开全部日志信息,及关闭全部日志信息。
具体在<root level=“INFO”>标签的level属性中设置日志级别。
电影售票系统
需求分析
系统角色准备
集成日志框架、用于后期记录日志信息。
定义一个电影类Movie类,Movie类包含:片名、主演、评分、时长、票价、余票
系统包含2个用户角色:客户、商家。存在大量相同属性信息。
定义User类作为父类,属性:登录名称、密码、真实名称、性别、电话、账户金额
定义Business类代表商家角色,属性:店铺名称、地址。
定义Customer类代表客户角色,属性:
定义集合List<User>用户存放系统注册的用户对象信息。
定义集合Map<Business, List<Movie>>存放商家和其排片信息。
准备一些测试数据。
定义一个电影类Movie类,Movie类包含:片名、主演、评分、时长、票价、余票
系统包含2个用户角色:客户、商家。存在大量相同属性信息。
定义User类作为父类,属性:登录名称、密码、真实名称、性别、电话、账户金额
定义Business类代表商家角色,属性:店铺名称、地址。
定义Customer类代表客户角色,属性:
定义集合List<User>用户存放系统注册的用户对象信息。
定义集合Map<Business, List<Movie>>存放商家和其排片信息。
准备一些测试数据。
首页、登录、商家界面、用户界面设计
首页需要包含登录,商家入驻,客户注册功能。
商家和客户可以共用一个登录功能。
判断登录成功的用户的真实类型,根据用户类型完成对应的操作界面设计。
商家和客户可以共用一个登录功能。
判断登录成功的用户的真实类型,根据用户类型完成对应的操作界面设计。
商家功能-展示详情、影片上架、退出
展示本商家的信息和其排片情况。
提供影片上架功能:就是创建一个影片对象,存入到商家的集合中去。
退出,需要回到登录的首页。
提供影片上架功能:就是创建一个影片对象,存入到商家的集合中去。
退出,需要回到登录的首页。
商家功能-影片下架、影片修改
提供影片下架功能:其实就是从商家的集合中删除影片对象。
影片修改功能:拿到需要修改的影片对象,修改里面的数据。
影片修改功能:拿到需要修改的影片对象,修改里面的数据。
用户功能-展示全部影片信息
用户功能-购票操作
用户可以选择需要购买票的商家和其电影信息。
可以选择购买的数量。
购买成功后需要支付金额,并更新商家金额和客户金额
可以选择购买的数量。
购买成功后需要支付金额,并更新商家金额和客户金额
用户功能-评分功能
用户只能对自己已经购买过的电影进行评分。
需要在当前用户对象中记录购买的电影信息(包括是否已经评价过)。
每部电影的评分信息应该是系统所有用户评分的平均值。
应该在系统定义一个集合存储每部电影的评分详情。
需要在当前用户对象中记录购买的电影信息(包括是否已经评价过)。
每部电影的评分信息应该是系统所有用户评分的平均值。
应该在系统定义一个集合存储每部电影的评分详情。
JavaSE高级
file 递归 IO流
file
File类概述
File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)。
File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能
File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能
常用API
遍历
public String[] list()
pubic File[] listFiles()(常用)
注意事项
当调用者不存在时,返回null
当调用者是一个文件时,返回null
当调用者是一个空文件夹时返回一个长度为0的数组
当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,
包含隐藏内容当调用者是一个需要权限才能进入的文件夹时,返回null
当调用者是一个文件时,返回null
当调用者是一个空文件夹时返回一个长度为0的数组
当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,
包含隐藏内容当调用者是一个需要权限才能进入的文件夹时,返回null
递归
阶乘问题
斐波那契
三要素
递归的公式: f(n) = f(n-1) * n;
递归的终结点:f(1)
递归的方向必须走向终结点:
递归的终结点:f(1)
递归的方向必须走向终结点:
猴子吃桃问题
描述:
猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,
于是又多吃了一个第二天又吃了前天剩余桃子数量的一半,
觉得好不过瘾,于是又多吃了一个以后每天都是吃前天剩余桃子数量的一半,
觉得好不过瘾,又多吃了一个等到第10天的时候发现桃子只有1个了。需求:请问猴子第一天摘了多少个桃子?
猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,
于是又多吃了一个第二天又吃了前天剩余桃子数量的一半,
觉得好不过瘾,于是又多吃了一个以后每天都是吃前天剩余桃子数量的一半,
觉得好不过瘾,又多吃了一个等到第10天的时候发现桃子只有1个了。需求:请问猴子第一天摘了多少个桃子?
买啤酒问题
啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
IO流
流的4大类
字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
流的体系
资源释放的方式
try-catch-finally
finally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源
特点:被finally控制的语句最终一定会执行,除非JVM退出
异常处理标准格式:try….catch…finally
特点:被finally控制的语句最终一定会执行,除非JVM退出
异常处理标准格式:try….catch…finally
try-catch-resource 自动释放资源、代码简洁
字节流
FileInputStream
public FileInputStream(File file)
创建字节输入流管道与源文件对象接通
public FileInputStream(String pathname)
创建字节输入流管道与源文件路径接通
public int read()
每次读取一个字节返回,如果字节已经没有可读的返回-1
public int read(byte[] buffer)
每次读取一个字节数组返回,如果字节已经没有可读的返回-1
存在问题:性能较慢,会出现中文乱码问题
问题注意
1、如何使用字节输入流读取中文内容输出不乱码呢?
定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。
2、直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
如果文件过大,字节数组可能引起内存溢出。
定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。
2、直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
如果文件过大,字节数组可能引起内存溢出。
FileOutputStream
public void write(int a)
写一个字节出去
public void write(byte[] buffer)
写一个字节数组
public void write(byte[] buffer , int pos , int len)
写一部分字节数组
文件拷贝
字符流
字节流读取中文输出会存在什么问题?
答:会乱码。或者内存溢出。
读取中文输出,哪个流更合适,为什么?
答:字符流更合适,最小单位是按照单个字符读取的。
答:会乱码。或者内存溢出。
读取中文输出,哪个流更合适,为什么?
答:字符流更合适,最小单位是按照单个字符读取的。
Reader
public FileReader(File file)
public FileReader(String pathname)
public int read()
每次读取一个字符返回,如果字符已经没有可读的返回-1
public int read(char arr[])
每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1
FileWriter
字节流、字符流如何选择使用?
字节流适合做一切文件数据的拷贝(音视频,文本)
字节流不适合读取中文内容输出
字符流适合做文本文件的操作(读,写)
字节流不适合读取中文内容输出
字符流适合做文本文件的操作(读,写)
缓存流
概念
缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
作用
作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
体系图
字节缓存输入输出流
public BufferedInputStream(InputStream is)
public BufferedOutputStream(OutputStream is)
字符缓冲输入输出流
BufferedReader
public BufferedReader(Reader r)
public String readLine()
BufferedWriter
public BufferedWriter(Writer w)
public void newLine()
字符缓冲流为什么提高了操作数据的性能?
字符缓冲流自带8K缓冲区
可以提高原始字符流读写数据的性能字符缓冲
流的功能如何使用?
public BufferedReader(Reader r)性能提升了,多了readLine()按照行读取的功能
public BufferedWriter(Writer w)性能提升了,多了newLine()换行的功能
字符缓冲流自带8K缓冲区
可以提高原始字符流读写数据的性能字符缓冲
流的功能如何使用?
public BufferedReader(Reader r)性能提升了,多了readLine()按照行读取的功能
public BufferedWriter(Writer w)性能提升了,多了newLine()换行的功能
拷贝出师表
转换流
如何使用字符流直接读取文件不乱码
使用字符输入转换流
可以提取文件(GBK)的原始字节流,原始字节不会存在问题。
然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码
可以提取文件(GBK)的原始字节流,原始字节不会存在问题。
然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码
字符输入
public InputStreamReader(InputStream is)
public InputStreamReader(InputStream is ,String charset)
可以把原始的字节流按照指定编码转换成字符输入流
这样字符流中的字符就不乱码了(重点)
这样字符流中的字符就不乱码了(重点)
字符输出
OutputStreamWriter
public OutputStreamWriter(OutputStream os)
public OutputStreamWriter(OutputStream os,String charset)
可以把原始的字节输出流按照指定编码转换成字符输出流(重点)
序列化对象
序列化
作用:以内存为基准,把内存中的对象存储到磁盘文件中去。
使用到的流是对象字节输出流:ObjectOutputStream
使用到的流是对象字节输出流:ObjectOutputStream
public ObjectOutputStream(OutputStream out)
序列化方法
public final void writeObject(Object obj)
反序列化
public ObjectInputStream(InputStream out)
public Object readObject()
打印流
PrintStream
PrintWriter
两者区别
PrintWriter继承自字符输出流Writer,支持写字符数据出去
PrintStream继承自字节输出流OutputStream,支持写字节
Properties
Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的
属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的
拷贝文件夹
点名
IO框架
概述
commons-io是apache开源基金组织提供的一组有关IO操作的类库,
可以提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类。
有两个主要的类FileUtils, IOUtils
可以提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类。
有两个主要的类FileUtils, IOUtils
FileUtils
String readFileToString(File file, String encoding)
读取文件中的数据, 返回字符串
void copyFile(File srcFile, File destFile)
复制文件。
void copyDirectoryToDirectory(File srcDir, File destDir)
复制文件夹。
IOUtils
多线程
线程
线程(thread)是一个程序内部的一条执行路径。
多线程
概念
多线程是指从软硬件上实现多条执行流程的技术。
创建方法
继承Thread类
缺点:不利于扩展
缺点:不利于扩展
定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
创建MyThread类的对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
创建MyThread类的对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
注意
1、为什么不直接调用了run方法,而是调用start启动线程。
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
只有调用start方法才是启动一个新的线程执行。
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
只有调用start方法才是启动一个新的线程执行。
2、把主线程任务放在子线程之前了。
这样主线程一直是先跑完的,相当于是一个单线程的效果了。
这样主线程一直是先跑完的,相当于是一个单线程的效果了。
实现Runnable接口
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
创建MyRunnable任务对象
把MyRunnable任务对象交给Thread处理。
调用线程对象的start()方法启动线程
创建MyRunnable任务对象
把MyRunnable任务对象交给Thread处理。
调用线程对象的start()方法启动线程
实现Callable接口
可以获取到线程执行结果
可以获取到线程执行结果
得到任务对象定义类实现Callable接口,重写call方法,封装要做的事情。
用FutureTask把Callable对象封装成线程任务对象。
把线程任务对象交给Thread处理。
调用Thread的start方法启动线程,执行任务、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果
用FutureTask把Callable对象封装成线程任务对象。
把线程任务对象交给Thread处理。
调用Thread的start方法启动线程,执行任务、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果
FutureTask的API
public FutureTask<>(Callable call)
把Callable对象封装成FutureTask对象。
public V get() throws Exception
获取线程执行call方法返回的结果。
Thread常用方法
线程安全
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。
出现原因
存在多线程并发
同时访问共享资源
存在修改共享资源
同时访问共享资源
存在修改共享资源
取钱业务
线程同步
加锁:让多个线程实现先后依次访问共享资源,这样就解决了安全问题
同步代码块
作用:把出现线程安全问题的核心代码给上锁。
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
锁的规范和要求
规范上:建议使用共享资源作为锁对象。对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
同步方法
作用:把出现线程安全问题的核心方法给上锁。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
格式
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
操作共享资源的代码
}
synchronize
Lock锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
public ReentrantLock()
获得Lock锁的实现类对象
void lock()
void unlock()
线程通信
线程池
线程池就是一个可以复用线程的技术
如何得到线程池对象
使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
ThreadPoolExecutor构造器的参数说明
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数一:指定线程池的线程数量(核心线程): corePoolSize
参数二:指定线程池可支持的最大线程数: maximumPoolSize
参数三:指定临时线程的最大存活时间: keepAliveTime
参数四:指定存活时间的单位(秒、分、时、天): unit
参数五:指定任务队列: workQueue
参数六:指定用哪个线程工厂创建线程: threadFactory
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler
参数二:指定线程池可支持的最大线程数: maximumPoolSize
参数三:指定临时线程的最大存活时间: keepAliveTime
参数四:指定存活时间的单位(秒、分、时、天): unit
参数五:指定任务队列: workQueue
参数六:指定用哪个线程工厂创建线程: threadFactory
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler
不能小于0
最大数量 >= 核心线程数量
不能小于0
时间单位
不能为null
不能为null
不能为null
最大数量 >= 核心线程数量
不能小于0
时间单位
不能为null
不能为null
不能为null
面试题
临时线程什么时候创建啊?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会拒绝任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
谁代表线程池?
ExecutorService接口
ExecutorService接口
ThreadPoolExecutor创建线程池对象示例
ExecutorService pools = new ThreadPoolExecutor(3, 5, 8 ,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy());
TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy());
ExecutorService的常用方法
新任务拒绝策略
Executors工具类实现线程池
存在的陷阱
定时器
定义
定时器是一种控制任务延时调用,或者周期调用的技术。
作用:闹钟、定时邮件发送。
作用:闹钟、定时邮件发送。
实现方式
方式一:Timer
方式二: ScheduledExecutorService
方式二: ScheduledExecutorService
public Timer()
创建Timer定时器对象
public void schedule(TimerTask task, long delay, long period)
开启一个定时器,按照计划处理TimerTask任务
存在问题
Timer定时器的特点和存在的问题
1、Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
2、可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。
1、Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
2、可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。
ScheduledExecutorService定时器
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 得到定时器对象
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
TimeUnit unit)周期调度方法
TimeUnit unit)周期调度方法
优点1、基于线程池,某个任务的执行情况不会影响其他定时任务的执行。
并发,并行
并发和并行的含义
并发:CPU分时轮询的执行线程。
并行:同一个时刻同时在执行。
并发:CPU分时轮询的执行线程。
并行:同一个时刻同时在执行。
线程的生命周期
六种状态
线程的六种状态转换
网络编程
网络编程定义
常见的通信模式有如下2种形式:Client-Server(CS) 、 Browser/Server(BS)
网络通信三要素
IP地址
设备在网络中的地址,是唯一的标识。
IPV4和IPV6
端口
应用程序在设备中唯一的标识。
端口号:标识正在计算机设备上运行的进程(程序),被规定为一个 16 位的二进制,范围是 0~65535。
端口类型周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占 用8080,MySQL占用3306)
动态端口:49152到65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配。
注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
端口类型周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占 用8080,MySQL占用3306)
动态端口:49152到65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配。
注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
协议
数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。
TCP
特点
使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
传输前,采用“三次握手”方式建立连接,所以是可靠的 。
在连接中可进行大数据量的传输 。 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。
传输前,采用“三次握手”方式建立连接,所以是可靠的 。
在连接中可进行大数据量的传输 。 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。
场景
TCP协议通信场景对信息安全要求较高的场景,例如:文件下载、金融等数据通信。
三次握手建立
四次挥手断开
TCP获取
客户端发送消息
需求:
客户端实现步骤创建客户端的Socket对象,请求与服务端的连接。
使用socket对象调用getOutputStream()方法得到字节输出流。
使用字节输出流完成数据的发送。
释放资源,关闭socket管道。
客户端实现步骤创建客户端的Socket对象,请求与服务端的连接。
使用socket对象调用getOutputStream()方法得到字节输出流。
使用字节输出流完成数据的发送。
释放资源,关闭socket管道。
通信模拟演示
socket
public Socket(String host , int port)
创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。
OutputStream getOutputStream()
InputStream getInputStream()
serversocket
public ServerSocket(int port)
public Socket accept()
等待接收客户端的Socket通信连接连接成功返回Socket对象与客户端建立端到端通信
服务端实现接收消息
需求:
服务端实现步骤创建ServerSocket对象,注册服务端端口。
调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
释放资源:关闭socket管道
服务端实现步骤创建ServerSocket对象,注册服务端端口。
调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
释放资源:关闭socket管道
TCP多发多收消息
需求:
使用TCP通信方式实现:多发多收消息。
具体要求:可以使用死循环控制服务端收完消息继续等待接收下一个消息。
客户端也可以使用死循环等待用户不断输入消息。
客户端一旦输入了exit,则关闭客户端程序,并释放资源。
使用TCP通信方式实现:多发多收消息。
具体要求:可以使用死循环控制服务端收完消息继续等待接收下一个消息。
客户端也可以使用死循环等待用户不断输入消息。
客户端一旦输入了exit,则关闭客户端程序,并释放资源。
同时接收多个客户端的消息?不可以的。因为服务端现在只有一个线程,只能与一个客户端进行通信。
TCP 同时接收多个客户端消息
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它。
每接收到一个Socket通信管道后分配一个独立的线程负责处理它。
TCP通信-使用线程池优化
存在的问题
1、目前的通信架构存在什么问题?
客户端与服务端的线程模型是: N-N的关系。
客户端并发越多,系统瘫痪的越快。
客户端与服务端的线程模型是: N-N的关系。
客户端并发越多,系统瘫痪的越快。
使用线程池的优势
服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
适合客户端通信时长较短的场景。
适合客户端通信时长较短的场景。
TCP通信实战案例-即时通信
TCP通信实战案例-模拟BS系统
UDP
UDP协议: UDP是一种无连接、不可靠传输的协议。
将数据源IP、目的地IP和端口封装成数据包,
不需要建立连接 每个数据包的大小限制在64KB内 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。
UDP协议通信场景语音通话,视频会话等。
将数据源IP、目的地IP和端口封装成数据包,
不需要建立连接 每个数据包的大小限制在64KB内 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。
UDP协议通信场景语音通话,视频会话等。
DatagramPacket:数据包对象(韭菜盘子)
public DatagramPacket(byte[] buf, int length)
创建接收端的数据包对象
buf:用来存储接收的内容
length:能够接收内容的长度
buf:用来存储接收的内容
length:能够接收内容的长度
public DatagramPacket(byte[] buf, int length, InetAddress address, int port)
创建发送端数据包对象
buf:要发送的内容,字节数组
length:要发送内容的字节长度
address:接收端的IP地址对象
port:接收端的端口号
buf:要发送的内容,字节数组
length:要发送内容的字节长度
address:接收端的IP地址对象
port:接收端的端口号
DatagramPacket常用方法
public int getLength()
获得实际接收到的字节个数
DatagramSocket:发送端和接收端对象(人)
public DatagramSocket()
创建发送端的Socket对象,系统会随机分配一个端口号。
public DatagramSocket(int port)
创建接收端的Socket对象并指定端口号
DatagramSocket类成员方法
public void send(DatagramPacket dp)发送
public void receive(DatagramPacket p)接收
使用UDP发送消息的步骤
客户端实现步骤
创建DatagramSocket对象(发送端对象)
创建DatagramPacket对象封装需要发送的数据(数据包对象)
使用DatagramSocket对象的send方法传入DatagramPacket对象
释放资源
创建DatagramSocket对象(发送端对象)
创建DatagramPacket对象封装需要发送的数据(数据包对象)
使用DatagramSocket对象的send方法传入DatagramPacket对象
释放资源
接收端实现步骤
创建DatagramSocket对象并指定端口(接收端对象)
创建DatagramPacket对象接收数据(数据包对象)
使用DatagramSocket对象的receive方法传入DatagramPacket对象
释放资源
创建DatagramSocket对象并指定端口(接收端对象)
创建DatagramPacket对象接收数据(数据包对象)
使用DatagramSocket对象的receive方法传入DatagramPacket对象
释放资源
使用UDP通信实现:多发多收消息
需求
使用UDP通信方式开发接收端和发送端。
分析
发送端可以一直发送消息。
接收端可以不断的接收多个发送端的消息展示。
发送端输入了exit则结束发送端程序。
使用UDP通信方式开发接收端和发送端。
分析
发送端可以一直发送消息。
接收端可以不断的接收多个发送端的消息展示。
发送端输入了exit则结束发送端程序。
广播,组播通信
单播:单台主机与单台主机之间的通信。
广播:当前主机与所在网络中的所有主机通信。
组播:当前主机与选定的一组主机的通信。
广播:当前主机与所在网络中的所有主机通信。
组播:当前主机与选定的一组主机的通信。
如何实现广播
使用广播地址:255.255.255.255具体操作:发送端发送的数据包的目的地写的是广播地址、
且指定端口。 (255.255.255.255 , 9999)
本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。(9999)
且指定端口。 (255.255.255.255 , 9999)
本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。(9999)
如何实现组播
使用组播地址:
224.0.0.0 ~ 239.255.255.255
具体操作:发送端的数据包的目的地是组播IP (例如:224.0.1.1, 端口:9999)
接收端必须绑定该组播IP(224.0.1.1),端口还要注册发送端的目的端口9999 ,
这样即可接收该组播消息。DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP。
224.0.0.0 ~ 239.255.255.255
具体操作:发送端的数据包的目的地是组播IP (例如:224.0.1.1, 端口:9999)
接收端必须绑定该组播IP(224.0.1.1),端口还要注册发送端的目的端口9999 ,
这样即可接收该组播消息。DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP。
MulticastSocket
实现组播通信
socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1") , 9898),
NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
InetAddress 的使用
网络协议模型
注解反射
单元测试
单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,
因此,单元测试就是针对Java方法的测试,进而检查方法的正确性。
因此,单元测试就是针对Java方法的测试,进而检查方法的正确性。
反射
概述
反射是指对于任何一个Class类,在"运行的时候"都可以直接得到这个类全部成分。
在运行时,可以直接得到这个类的构造器对象:Constructor
在运行时,可以直接得到这个类的成员变量对象:Field
在运行时,可以直接得到这个类的成员方法对象:Method
这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制。
在运行时,可以直接得到这个类的构造器对象:Constructor
在运行时,可以直接得到这个类的成员变量对象:Field
在运行时,可以直接得到这个类的成员方法对象:Method
这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制。
作用,关键
反射的基本作用、关键?
反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分。
反射的核心思想和关键就是:得到编译以后的class文件对象。
反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分。
反射的核心思想和关键就是:得到编译以后的class文件对象。
反射的第一步是什么
获取Class类对象,如此才可以解析类的全部成分
获取的三种方式
方式一:Class c1 = Class.forName(“全类名”);
方式二:Class c2 = 类名.class
方式三:Class c3 = 对象.getClass();
方式二:Class c2 = 类名.class
方式三:Class c3 = 对象.getClass();
使用反射技术获取构造器对象并使用
Constructor<?>[] getConstructors()
返回所有构造器对象的数组(只能拿public的)
Constructor<?>[] getDeclaredConstructors()
返回所有构造器对象的数组,存在就能拿到
Constructor<T> getConstructor(Class<?>... parameterTypes)
返回单个构造器对象(只能拿public的)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
返回单个构造器对象,存在就能拿到
Constructor类中用于创建对象的方法
T newInstance(Object... initargs)
根据指定的构造器创建对象
public void setAccessible(boolean flag)
设置为true,表示取消访问检查,进行暴力反射
使用反射技术获取方法对象并使用
Object invoke(Object obj, Object... args)
运行方法参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
反射的作用
反射的作用?
可以在运行时得到一个类的全部成分然后操作。
可以破坏封装性。
也可以破坏泛型的约束性。
更重要的用途是适合:做Java高级框架
基本上主流框架都会基于反射设计一些通用技术功能。
可以在运行时得到一个类的全部成分然后操作。
可以破坏封装性。
也可以破坏泛型的约束性。
更重要的用途是适合:做Java高级框架
基本上主流框架都会基于反射设计一些通用技术功能。
注解
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。
自定义注解
格式
ublic @interface 注解名称 {
public 属性类型 属性名() default 默认值 ;
}
public 属性类型 属性名() default 默认值 ;
}
元注解有两个:
@Target: 约束自定义注解只能在哪些地方使用,
@Retention:申明注解的生命周期
@Target: 约束自定义注解只能在哪些地方使用,
@Retention:申明注解的生命周期
XML 解析 设计模式
XML
XML的几个特点和使用场景一是纯文本,默认使用UTF-8编码;
二是可嵌套;如果把XML内容存为文件,那么它就是一个XML文件。
XML的使用场景:XML内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息。
二是可嵌套;如果把XML内容存为文件,那么它就是一个XML文件。
XML的使用场景:XML内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息。
特殊字符
DOM解析
通常数据会封装成Java的对象,如单个对象,或者集合对象形式
Xpath解析
绝对路径
相对路径
全文检索
属性查找
相对路径
全文检索
属性查找
工厂设计模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一,
这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式。
这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式。
Dubbo
学习网站:https://dubbo.apache.org/zh/index.html
学习网站:https://dubbo.apache.org/zh/index.html
概念
Dubbo 是一款高性能、轻量级的开源Java RPC框架,提供面向接口代理的高性
能RPC调用、智能负载均衡、服务自动注册和发现、运行期流量调度、可视化服务治理和运维等功能
能RPC调用、智能负载均衡、服务自动注册和发现、运行期流量调度、可视化服务治理和运维等功能
分布式中相关概述
大型互联网架构目标
特点
目标
高性能
提供快速的访问体验
高可用
网站服务可以一直使用
可伸缩
通过硬件增加/减少,提高/降低处理能力
高可扩展
系统间的耦合性,方便的通过新增和删除,增加和减少新的功能模块
安全性
提供网站安全访问和数据加密,安全存储策略等等
敏捷性
随需求应变,快速响应
集群和分布式
集群
很多人一起干一样的事儿
分布式
很多人一起干不一样的事儿,合起来就是一件大事儿
架构演进
单体架构
优点
开发部署方便
缺点
项目启动变慢
可靠性差,一旦某一个模块出问题,影响全局
扩展性差可伸缩性差
性能低
垂直架构
垂直架构是将单体中多个模块拆分成多个独立的项目,形成多个单体
存在问题
重复功能太多
分布式架构
指的是在垂直架构的基础上,将公共调用的模块抽取出来
作为独立的模块,供系统消费者调用,以便于实现服务共享和重用
作为独立的模块,供系统消费者调用,以便于实现服务共享和重用
RPC调用
存在的问题,一旦一个服务存在变更,所有调用它的服务都会产生变更
SOA架构
微服务架构
微服务是在SOA架构上的升华
强调重点是:业务需要彻底的组件化和服务化
原有的单个业务系统会拆分成为多个独立开发,设计,运行的小应用
这些小应用之间通过调用服务完成交互和集成
这些小应用之间通过调用服务完成交互和集成
特点
服务实现组件化
开发者可以自由的选择开发技术
服务之间交互一般用Rest API
去中心化,每一个微服务都有自己的数据库
自动化部署
Dubbo概述
阿里轻量级 开源RPCjava框架
架构
Dubbo快速入门
zk安装
Dubbo快速入门
集成springMVC
Dubbo高级
dubbo-admin安装使用
使用
*代表所有服务
默认端口20880
序列化
dubbo内部已经将序列化和反序列化封装了
地址缓存
注册中心挂了,服务还能正常使用吗?
超时与重试
超时
dubbo利用超时机制来解决这个问题,设置一个超时时间,在一个时间段内,无法完成服务访问,自动断开连接
timeout
timeout
配置在服务的提供方
重试
重试属性retries默认为2
多版本
概念
version(服务者提供,版本方式)
负载均衡
Random:权重随机
RoundRobin:按权重轮询
LeastActive
最少活跃调用数,相同数的随机
ConsistentHash
一致性Hash,相同参数的请求总是发送到同一提供者
集群容错
服务降级
fail:return null 失败后返回null,什么也不显示
资源网站
学习
TED(最优质的演讲):
https://www.ted.com/
谷粉学术:
https://gfsoso.99lb.net/scholar.html
大学资源网:http://www.dxzy163.com/
简答题:http://www.jiandati.com/
网易公开课:https://open.163.com/ted/
网易云课堂:https://study.163.com/
中国大学MOOC:www.icourse163.org
哔哩哔哩弹幕网:www.bilibili.com
我要自学网:www.51zxw.net
GitHub:https://github.com/
码云:https://gitee.com/
源码之家:https://www.mycodes.net/
知乎:www.zhihu.com
学堂在线:www.xuetangx.com
CSDN:https://www.csdn.net/
爱课程:www.icourses.cn
猫咪论文:https://lunwen.im/
iData(论文搜索):www.cn-ki.net
文泉考试:https://www.wqkaoshi.com
https://www.ted.com/
谷粉学术:
https://gfsoso.99lb.net/scholar.html
大学资源网:http://www.dxzy163.com/
简答题:http://www.jiandati.com/
网易公开课:https://open.163.com/ted/
网易云课堂:https://study.163.com/
中国大学MOOC:www.icourse163.org
哔哩哔哩弹幕网:www.bilibili.com
我要自学网:www.51zxw.net
GitHub:https://github.com/
码云:https://gitee.com/
源码之家:https://www.mycodes.net/
知乎:www.zhihu.com
学堂在线:www.xuetangx.com
CSDN:https://www.csdn.net/
爱课程:www.icourses.cn
猫咪论文:https://lunwen.im/
iData(论文搜索):www.cn-ki.net
文泉考试:https://www.wqkaoshi.com
书籍
书栈网:https://www.bookstack.cn/
码农之家(计算机电子书下载):
www.xz577.com
鸠摩搜书:www.jiumodiary.com
云海电子图书馆:www.pdfbook.cn
周读(书籍搜索):ireadweek.com
知轩藏书:http://www.zxcs.me/
脚本之家电子书下载:
https://www.jb51.net/books/
搜书VIP-电子书搜索:
http://www.soshuvip.com/all.html
书格(在线古籍图书馆):
https://new.shuge.org/
caj云阅读:
http://cajviewer.cnki.net/cajcloud/
必看网(人生必看的书籍):
https://www.biikan.com/
码农之家(计算机电子书下载):
www.xz577.com
鸠摩搜书:www.jiumodiary.com
云海电子图书馆:www.pdfbook.cn
周读(书籍搜索):ireadweek.com
知轩藏书:http://www.zxcs.me/
脚本之家电子书下载:
https://www.jb51.net/books/
搜书VIP-电子书搜索:
http://www.soshuvip.com/all.html
书格(在线古籍图书馆):
https://new.shuge.org/
caj云阅读:
http://cajviewer.cnki.net/cajcloud/
必看网(人生必看的书籍):
https://www.biikan.com/
冷知识 / 黑科技
上班摸鱼必备(假装电脑系统升级):http://fakeupdate.net/
PIECES 拼图(30 个 CSS 碎片进行拼图,呈现 30 种濒临灭绝的动物):
http://www.species-in-pieces.com/
图片立体像素画:
https://pissang.github.io/voxelize-image/
福利单词(一个不太正经的背单词网站):
http://dict.ftqq.com
查无此人(刷新网站,展现一张AI 生成的人脸照片):
https://thispersondoesnotexist.com/
在线制作地图图例:https://mapchart.net/
创意光线绘画:http://weavesilk.com/
星系观察:https://stellarium-web.org/
煎蛋:http://jandan.net/
渣男-说话的艺术:https://lovelive.tools/
全历史:https://www.allhistory.com/
iData:https://www.cn-ki.net/
术语在线:http://www.termonline.cn/
PIECES 拼图(30 个 CSS 碎片进行拼图,呈现 30 种濒临灭绝的动物):
http://www.species-in-pieces.com/
图片立体像素画:
https://pissang.github.io/voxelize-image/
福利单词(一个不太正经的背单词网站):
http://dict.ftqq.com
查无此人(刷新网站,展现一张AI 生成的人脸照片):
https://thispersondoesnotexist.com/
在线制作地图图例:https://mapchart.net/
创意光线绘画:http://weavesilk.com/
星系观察:https://stellarium-web.org/
煎蛋:http://jandan.net/
渣男-说话的艺术:https://lovelive.tools/
全历史:https://www.allhistory.com/
iData:https://www.cn-ki.net/
术语在线:http://www.termonline.cn/
资源搜索
DogeDoge搜索引擎:www.dogedoge.com
秘迹搜索:https://mijisou.com/
小白盘:https://www.xiaobaipan.com/
云盘精灵(资源搜索):
www.yunpanjingling.com
虫部落(资源搜索):
www.chongbuluo.com
如风搜(资源搜索):
http://www.rufengso.net/
爱扒:https://www.zyboe.com/
秘迹搜索:https://mijisou.com/
小白盘:https://www.xiaobaipan.com/
云盘精灵(资源搜索):
www.yunpanjingling.com
虫部落(资源搜索):
www.chongbuluo.com
如风搜(资源搜索):
http://www.rufengso.net/
爱扒:https://www.zyboe.com/
小工具
奶牛快传(在线传输文件利器):cowtransfer.com
文叔叔(大文件传输,不限速):
https://www.wenshushu.cn/
云端超级应用空间(PS,PPT,Excel,Ai):https://uzer.me/
香当网(年终总结,个人简历,事迹材料,租赁合同,演讲稿):
https://www.xiangdang.net/
二维码生成:https://cli.im/
搜狗翻译:fanyi.sogou.com
熵数(图表制作,数据可视化):
https://dydata.io/appv2/#/pages/index/home
拷贝兔:https://cp.anyknew.com/
AI人工智能图片放大:http://bigjpg.com/zh
幕布(在线大纲笔记工具):mubu.com
在线转换器(在线转换器转换任何测量单位):https://zh.justcnw.com/
调查问卷制作:
https://www.wenjuan.com/
果核剥壳(软件下载):
https://www.ghpym.com/
软件下载:https://www.unyoo.com/
MSDN我告诉你(windows10系统镜像下载):https://msdn.itellyou.cn/
文叔叔(大文件传输,不限速):
https://www.wenshushu.cn/
云端超级应用空间(PS,PPT,Excel,Ai):https://uzer.me/
香当网(年终总结,个人简历,事迹材料,租赁合同,演讲稿):
https://www.xiangdang.net/
二维码生成:https://cli.im/
搜狗翻译:fanyi.sogou.com
熵数(图表制作,数据可视化):
https://dydata.io/appv2/#/pages/index/home
拷贝兔:https://cp.anyknew.com/
AI人工智能图片放大:http://bigjpg.com/zh
幕布(在线大纲笔记工具):mubu.com
在线转换器(在线转换器转换任何测量单位):https://zh.justcnw.com/
调查问卷制作:
https://www.wenjuan.com/
果核剥壳(软件下载):
https://www.ghpym.com/
软件下载:https://www.unyoo.com/
MSDN我告诉你(windows10系统镜像下载):https://msdn.itellyou.cn/
导航页
世界各国网址大全:
http://www.world68.com/
小森林导航:http://www.xsldh6.com/
简捷工具:http://www.shulijp.com/
NiceTool.net 好工具网:
http://www.nicetool.net/
现实君工具箱(综合型在线工具集成网站):http://tool.uixsj.cn/
蓝调网站:http://lcoc.top/
偷渡鱼:https://touduyu.com/
牛导航:http://www.ziliao6.com/
小呆导航:
https://www.webjike.com/index.html
简法主页:http://www.jianfast.com/
KIM主页:https://kim.plopco.com/
聚BT:https://jubt.net/cn/index.html
精准云工具合集:
https://jingzhunyun.com/
兔2工具合集:https://www.tool2.cn/
爱资料工具(在线实用工具集合):
www.toolnb.com
工具导航:https://hao.logosc.cn/
http://www.world68.com/
小森林导航:http://www.xsldh6.com/
简捷工具:http://www.shulijp.com/
NiceTool.net 好工具网:
http://www.nicetool.net/
现实君工具箱(综合型在线工具集成网站):http://tool.uixsj.cn/
蓝调网站:http://lcoc.top/
偷渡鱼:https://touduyu.com/
牛导航:http://www.ziliao6.com/
小呆导航:
https://www.webjike.com/index.html
简法主页:http://www.jianfast.com/
KIM主页:https://kim.plopco.com/
聚BT:https://jubt.net/cn/index.html
精准云工具合集:
https://jingzhunyun.com/
兔2工具合集:https://www.tool2.cn/
爱资料工具(在线实用工具集合):
www.toolnb.com
工具导航:https://hao.logosc.cn/
看视频
阿木影视:https://www.aosk.online/
电影推荐:http://www.mvcat.com
APP影院:https://app.movie
去看TV:https://www.qukantv.net/
动漫视频网:http://www.zzzfun.com/
94神马电影网:http://www.9rmb.com/
NO视频官网:http://www.novipnoad.com/
蓝光画质电影:http://www.languang.co/
在线看剧:http://dy.27234.cn/
大数据导航:http://hao.199it.com/
多功能图片网站:
https://www.logosc.cn/so/
牛牛TV:http://www.ziliao6.com/tv/
VideoFk解析视频:
http://www.videofk.com/
蓝调网站:http://lcoc.top/vip2.3/
永久资源采集网:
http://www.yongjiuzy1.com/
电影推荐:http://www.mvcat.com
APP影院:https://app.movie
去看TV:https://www.qukantv.net/
动漫视频网:http://www.zzzfun.com/
94神马电影网:http://www.9rmb.com/
NO视频官网:http://www.novipnoad.com/
蓝光画质电影:http://www.languang.co/
在线看剧:http://dy.27234.cn/
大数据导航:http://hao.199it.com/
多功能图片网站:
https://www.logosc.cn/so/
牛牛TV:http://www.ziliao6.com/tv/
VideoFk解析视频:
http://www.videofk.com/
蓝调网站:http://lcoc.top/vip2.3/
永久资源采集网:
http://www.yongjiuzy1.com/
学设计
码力全开(产品/设计师/独立开发者的资源库):https://www.maliquankai.com/designnav/
免费音频素材:https://icons8.cn/music
新CG儿(视频素材模板,无水印+免费下载):
https://www.newcger.com/
Iconfont(阿里巴巴矢量图标库):
https://www.iconfont.cn/
小图标下载:https://www.easyicon.net/
Flight Icon:https://www.flighticon.co/
第一字体转换器:http://www.diyiziti.com/
doyoudosh(平面设计):
www.doyoudo.com
企业宣传视频在线制作:https://duomu.tv/
MAKE海报设计官网:http://maka.im/
一键海报神器:
https://www.logosc.cn/photo/utm_source=hao.logosc.cn&utm_medium=referral
字由(字体设计):
http://www.hellofont.cn/
查字体网站:https://fonts.safe.360.cn/
爱给网(免费素材下载的网站,包括音效、配乐,3D、视频、游戏,平面、教程):http://www.aigei.com/
在线视频剪辑:
https://bilibili.clipchamp.com/editor
免费音频素材:https://icons8.cn/music
新CG儿(视频素材模板,无水印+免费下载):
https://www.newcger.com/
Iconfont(阿里巴巴矢量图标库):
https://www.iconfont.cn/
小图标下载:https://www.easyicon.net/
Flight Icon:https://www.flighticon.co/
第一字体转换器:http://www.diyiziti.com/
doyoudosh(平面设计):
www.doyoudo.com
企业宣传视频在线制作:https://duomu.tv/
MAKE海报设计官网:http://maka.im/
一键海报神器:
https://www.logosc.cn/photo/utm_source=hao.logosc.cn&utm_medium=referral
字由(字体设计):
http://www.hellofont.cn/
查字体网站:https://fonts.safe.360.cn/
爱给网(免费素材下载的网站,包括音效、配乐,3D、视频、游戏,平面、教程):http://www.aigei.com/
在线视频剪辑:
https://bilibili.clipchamp.com/editor
处理文档
即书(在线制作PPT):
https://www.keysuper.com/
PDF处理:https://smallpdf.com/cn
PDF处理:https://www.ilovepdf.com/zh-cn
PDF处理:https://www.pdfpai.com/
PDF处理:https://www.hipdf.cn/
图片压缩,PDF处理:
https://docsmall.com/
腾讯文档(在线协作编辑和管理文档):
docs.qq.com
ProcessOn(在线协作制作结构图):
www.processon.com
iLovePDF(在线转换PDF利器):
www.ilovepdf.com
PPT在线制作:https://www.woodo.cn/
PDF24工具(pdf处理工具):
https://tools.pdf24.org/en
IMGBOT(在线图片处理):
www.imgbot.ai
福昕云编辑(在线编辑PDF):
edit.foxitcloud.cn
TinyPNG(在线压缩图片):tinypng.com
UZER.ME(在线使用各种大应用,在线使用CAD,MATLAB,Office三件套
):uzer.me
优品PPT(模板下载):
http://www.ypppt.com/
第一PPT(模板下载):
http://www.1ppt.com/xiazai/
三顿PPT导航:sandunppt.com
Excel函数表:
https://support.office.com/zh-cn/article/excel-%E5%87%BD%E6%95%B0%EF%BC%88%E6%8C%89%E5%AD%97%E6%AF%8D%E9%A1%BA%E5%BA%8F%EF%BC%89-b3944572-255d-4efb-bb96-c6d90033e188
https://www.keysuper.com/
PDF处理:https://smallpdf.com/cn
PDF处理:https://www.ilovepdf.com/zh-cn
PDF处理:https://www.pdfpai.com/
PDF处理:https://www.hipdf.cn/
图片压缩,PDF处理:
https://docsmall.com/
腾讯文档(在线协作编辑和管理文档):
docs.qq.com
ProcessOn(在线协作制作结构图):
www.processon.com
iLovePDF(在线转换PDF利器):
www.ilovepdf.com
PPT在线制作:https://www.woodo.cn/
PDF24工具(pdf处理工具):
https://tools.pdf24.org/en
IMGBOT(在线图片处理):
www.imgbot.ai
福昕云编辑(在线编辑PDF):
edit.foxitcloud.cn
TinyPNG(在线压缩图片):tinypng.com
UZER.ME(在线使用各种大应用,在线使用CAD,MATLAB,Office三件套
):uzer.me
优品PPT(模板下载):
http://www.ypppt.com/
第一PPT(模板下载):
http://www.1ppt.com/xiazai/
三顿PPT导航:sandunppt.com
Excel函数表:
https://support.office.com/zh-cn/article/excel-%E5%87%BD%E6%95%B0%EF%BC%88%E6%8C%89%E5%AD%97%E6%AF%8D%E9%A1%BA%E5%BA%8F%EF%BC%89-b3944572-255d-4efb-bb96-c6d90033e188
找图片
电脑壁纸:http://lcoc.top/bizhi/
https://unsplash.com/
https://pixabay.com/
https://www.pexels.com/
https://visualhunt.com/
https://www.ssyer.com/
彼岸图网:http://pic.netbian.com/
极像素(超高清大图):
https://www.sigoo.com/
免费版权图片搜索:
https://www.logosc.cn/so/
https://unsplash.com/
https://pixabay.com/
https://www.pexels.com/
https://visualhunt.com/
https://www.ssyer.com/
彼岸图网:http://pic.netbian.com/
极像素(超高清大图):
https://www.sigoo.com/
免费版权图片搜索:
https://www.logosc.cn/so/
MySQL
基础
基础部分
MySQL概述
数据库
存储数据的仓库,数据是有组织的进行存储
数据库管
理系统
理系统
操纵和管理数据库的大型软件
SQL
操作关系型数据库的编程语言,定义了一套操作
关系型数据库统一标准
关系型数据库统一标准
MySQL安装和卸载
SQL
通用语法
1). SQL语句可以单行或多行书写,以分号结尾。
2). SQL语句可以使用空格/缩进来增强语句的可读性。
3). MySQL数据库的SQL语句不区分大小写,关键字建议使用大写。
4). 注释:
单行注释:-- 注释内容 或 # 注释内容
多行注释:/* 注释内容 */
2). SQL语句可以使用空格/缩进来增强语句的可读性。
3). MySQL数据库的SQL语句不区分大小写,关键字建议使用大写。
4). 注释:
单行注释:-- 注释内容 或 # 注释内容
多行注释:/* 注释内容 */
SQL语句,根据其功能,主要分为四类:DDL、DML、DQL、DCL
DDL
数据定义语言,用来定义数据库对象(数据库,表,
字段)
字段)
DML
数据操作语言,用来对数据库表中的数据进行增删改
DQL
数据查询语言,用来查询数据库中表的记录
DCL
数据控制语言,用来创建数据库用户、控制数据库的
访问权限
访问权限
数据库操作
查询所有数据库
show databases ;
查询当前数据库
select database() ; 1
创建数据库
create database [ if not exists ] 数据库名 [ default charset 字符集 ] [ collate 排序 规则 ] ;
if not exists 解决数据库重名问题
创建指定字符集数据库
create database itheima default charset utf8mb4;
删除数据库
drop database [ if exists ] 数据库名 ;
切换数据库
use 数据库名 ;
表操作
查询当前数据库所有表
show tables
查询指定表结构
desc 表名 ;
查询指定表的建表语句
show create table 表名 ;
创建一张表
create table tb_user(
id int comment '编号',
name varchar(50) comment '姓名',
age int comment '年龄',
gender varchar(1) comment '性别' )
comment '用户表';
id int comment '编号',
name varchar(50) comment '姓名',
age int comment '年龄',
gender varchar(1) comment '性别' )
comment '用户表';
数据类型
数值类型
应用举例
1). 年龄字段 -- 不会出现负数, 而且人的年龄不会太大
age tinyint unsigned
2). 分数 -- 总分100分, 最多出现一位小数
score double(4,1)
age tinyint unsigned
2). 分数 -- 总分100分, 最多出现一位小数
score double(4,1)
字符串类型
char 与 varchar 都可以描述字符串,char是定长字符串,指定长度多长,就占用多少个字符,和
字段值的长度无关 。而varchar是变长字符串,指定的长度为最大占用长度 。相对来说,char的性
能会更高些
字段值的长度无关 。而varchar是变长字符串,指定的长度为最大占用长度 。相对来说,char的性
能会更高些
应用
1). 用户名 username ------> 长度不定, 最长不会超过50
username varchar(50)
2). 性别 gender ---------> 存储值, 不是男,就是女
gender char(1)
3). 手机号 phone --------> 固定长度为11
phone char(11)
username varchar(50)
2). 性别 gender ---------> 存储值, 不是男,就是女
gender char(1)
3). 手机号 phone --------> 固定长度为11
phone char(11)
日期类型
类型
常用
1). 生日字段 birthday birthday date
2). 创建时间 createtime createtime datetime
2). 创建时间 createtime createtime datetime
表操作案例
设计一张员工信息表,要求如下:
1. 编号(纯数字)
2. 员工工号 (字符串类型,长度不超过10位) 3. 员工姓名(字符串类型,长度不超过10位)
4. 性别(男/女,存储一个汉字)
5. 年龄(正常人年龄,不可能存储负数)
6. 身份证号(二代身份证号均为18位,身份证中有X这样的字符)
7. 入职时间(取值年月日即可)
1. 编号(纯数字)
2. 员工工号 (字符串类型,长度不超过10位) 3. 员工姓名(字符串类型,长度不超过10位)
4. 性别(男/女,存储一个汉字)
5. 年龄(正常人年龄,不可能存储负数)
6. 身份证号(二代身份证号均为18位,身份证中有X这样的字符)
7. 入职时间(取值年月日即可)
create table emp( id int comment '编号',
workno varchar(10) comment '工号',
name varchar(10) comment '姓名',
gender char(1) comment '性别',
age tinyint unsigned comment '年龄',
idcard char(18) comment '身份证号',
entrydate date comment '入职时间' )
comment '员工表';
workno varchar(10) comment '工号',
name varchar(10) comment '姓名',
gender char(1) comment '性别',
age tinyint unsigned comment '年龄',
idcard char(18) comment '身份证号',
entrydate date comment '入职时间' )
comment '员工表';
修改
添加字段
ALTER TABLE 表名 ADD 字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];
例子:
ALTER TABLE emp ADD nickname varchar(20) COMMENT '昵称';
ALTER TABLE emp ADD nickname varchar(20) COMMENT '昵称';
修改数据类型
ALTER TABLE 表名 MODIFY 字段名 新数据类型 (长度);
修改字段名和字段类型
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];
将emp表的nickname字段修改为username,类型为varchar(30)
ALTER TABLE emp CHANGE nickname username varchar(30) COMMENT '昵称';
ALTER TABLE emp CHANGE nickname username varchar(30) COMMENT '昵称';
删除字段
ALTER TABLE 表名 DROP 字段名;
ALTER TABLE emp DROP username;
ALTER TABLE emp DROP username;
修改表名
ALTER TABLE 表名 RENAME TO 新表名;
ALTER TABLE emp RENAME TO employee;
ALTER TABLE emp RENAME TO employee;
删除
删除表
DROP TABLE [ IF EXISTS ] 表名;
可视化工具
DML
添加数据
INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);
批量添加
INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...), (值 1, 值2, ...) ;
INSERT INTO 表名 VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ;
INSERT INTO 表名 VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ;
修改数据
UPDATE 表名 SET 字段名1 = 值1 , 字段名2 = 值2 , .... [ WHERE 条件 ] ;
删除数据
DELETE FROM 表名 [ WHERE 条件 ] ;
DQL
数据库查询语言
语法结构
SELECT 字段列表 FROM
表名列表
WHERE 条件列表
GROUP BY 分组字段列表
HAVING 分组后条件列表
ORDER BY 排序字段列表
LIMIT分页参数
表名列表
WHERE 条件列表
GROUP BY 分组字段列表
HAVING 分组后条件列表
ORDER BY 排序字段列表
LIMIT分页参数
基础查询
查询多个
SELECT 字段1, 字段2, 字段3 ... FROM 表名 ;
SELECT * FROM 表名
SELECT * FROM 表名
字段设置别名
SELECT 字段1 [ AS 别名1 ] , 字段2 [ AS 别名2 ] ... FROM 表名;
去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
条件查询
SELECT 字段列表 FROM 表名 WHERE 条件列表 ;
聚合函数
分组查询
SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组 后过滤条件 ];
where与having区别
执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组
之后对结果进行过滤。
判断条件不同:where不能对聚合函数进行判断,而having可以。
之后对结果进行过滤。
判断条件不同:where不能对聚合函数进行判断,而having可以。
注意事项:
• 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。
• 执行顺序: where > 聚合函数 > having 。
• 支持多字段分组, 具体语法为 : group by columnA,columnB
• 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。
• 执行顺序: where > 聚合函数 > having 。
• 支持多字段分组, 具体语法为 : group by columnA,columnB
案例
根据性别分组 , 统计男性员工 和 女性员工的数量
select gender, count(*) from emp group by gender ;
select gender, count(*) from emp group by gender ;
根据性别分组 , 统计男性员工 和 女性员工的平均年龄
select gender, avg(age) from emp group by gender ;
select gender, avg(age) from emp group by gender ;
查询年龄小于45的员工 , 并根据工作地址分组 , 获取员工数量大于等于3的工作地址
select workaddress, count(*) address_count from emp where age < 45
group by workaddress having address_count >= 3;
select workaddress, count(*) address_count from emp where age < 45
group by workaddress having address_count >= 3;
统计各个工作地址上班的男性及女性员工的数量
select workaddress, gender, count(*) '数量' from emp group by gender , workaddress ;
select workaddress, gender, count(*) '数量' from emp group by gender , workaddress ;
排序查询
排序在日常开发中是非常常见的一个操作,有升序排序,也有降序排序。
语法:
SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1 , 字段2 排序方式2 ;
SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1 , 字段2 排序方式2 ;
排序方式
ASC : 升序(默认值)
DESC: 降序
ASC : 升序(默认值)
DESC: 降序
根据年龄排序
select * from emp order by age asc;
select * from emp order by age;
select * from emp order by age asc;
select * from emp order by age;
根据入职时间, 对员工进行降序排序
select * from emp order by entrydate desc;
select * from emp order by entrydate desc;
根据年龄对公司的员工进行升序排序 , 年龄相同 , 再按照入职时间进行降序排序
select * from emp order by age asc , entrydate desc;
select * from emp order by age asc , entrydate desc;
分页查询
SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数 ;
注意事项:
• 起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
• 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT。
• 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10。
• 起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
• 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT。
• 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10。
案例
查询第1页员工数据, 每页展示10条记录
select * from emp limit 0,10;
select * from emp limit 10;
select * from emp limit 0,10;
select * from emp limit 10;
查询第2页员工数据, 每页展示10条记录
select * from emp limit 10,10;
select * from emp limit 10,10;
案例
查询年龄为20,21,22,23岁的员工信息。
select * from emp where gender = '女' and age in(20,21,22,23);
select * from emp where gender = '女' and age in(20,21,22,23);
查询性别为 男 ,并且年龄在 20-40 岁(含)以内的姓名为三个字的员工。
select * from emp where gender = '男' and ( age between 20 and 40 ) and name like '___';
select * from emp where gender = '男' and ( age between 20 and 40 ) and name like '___';
统计员工表中, 年龄小于60岁的 , 男性员工和女性员工的人数。
select gender, count(*) from emp where age < 60 group by gender;
select gender, count(*) from emp where age < 60 group by gender;
查询所有年龄小于等于35岁员工的姓名和年龄,并对查询结果按年龄升序排序,如果年龄相同按
入职时间降序排序。
入职时间降序排序。
- select name , age from emp where age <= 35 order by age asc , entrydate desc;
查询性别为男,且年龄在20-40 岁(含)以内的前5个员工信息,对查询的结果按年龄升序排序,
年龄相同按入职时间升序排序。
select * from emp where gender = '男' and age between 20 and 40 order by age asc , entrydate asc limit 5 ;
年龄相同按入职时间升序排序。
select * from emp where gender = '男' and age between 20 and 40 order by age asc , entrydate asc limit 5 ;
执行顺序
DCL
Data Control Language(数据控制语言)
管理用户
select * from mysql.user;
在MySQL中需要通过Host和User来唯一标识一
个用户
个用户
创建用户
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
修改用户密码
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码' ;
删除用户
DROP USER '用户名'@'主机名' ;
注意事项:
• 在MySQL中需要通过用户名@主机名的方式,来唯一标识一个用户。
• 主机名可以使用 % 通配。
• 这类SQL开发人员操作的比较少,主要是DBA( Database Administrator 数据库管理员)使用。
• 在MySQL中需要通过用户名@主机名的方式,来唯一标识一个用户。
• 主机名可以使用 % 通配。
• 这类SQL开发人员操作的比较少,主要是DBA( Database Administrator 数据库管理员)使用。
案例
创建用户itcast, 只能够在当前主机localhost访问, 密码123456;
create user 'itcast'@'localhost' identified by '123456';
create user 'itcast'@'localhost' identified by '123456';
创建用户heima, 可以在任意主机访问该数据库, 密码123456;
create user 'heima'@'%' identified by '123456';
create user 'heima'@'%' identified by '123456';
修改用户heima的访问密码为1234;
alter user 'heima'@'%' identified with mysql_native_password by '1234';
alter user 'heima'@'%' identified with mysql_native_password by '1234';
删除 itcast@localhost 用户
drop user 'itcast'@'localhost';
drop user 'itcast'@'localhost';
权限控制
权限 说明
ALL, ALL PRIVILEGES 所有权限
SELECT 查询数据
INSERT 插入数据
UPDATE 修改数据
DELETE 删除数据
ALTER 修改表
DROP 删除数据库/表/视图
CREATE 创建数据库/表
ALL, ALL PRIVILEGES 所有权限
SELECT 查询数据
INSERT 插入数据
UPDATE 修改数据
DELETE 删除数据
ALTER 修改表
DROP 删除数据库/表/视图
CREATE 创建数据库/表
查询权限
SHOW GRANTS FOR '用户名'@'主机名' ;
授予权限
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
撤销权限
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
• 多个权限之间,使用逗号分隔
• 授权时, 数据库名和表名可以使用 * 进行通配,代表所有。
• 授权时, 数据库名和表名可以使用 * 进行通配,代表所有。
案例
查询 'heima'@'%' 用户的权限
show grants for 'heima'@'
show grants for 'heima'@'
授予 'heima'@'%' 用户itcast数据库所有表的所有操作权限
grant all on itcast.* to 'heima'@'%';
grant all on itcast.* to 'heima'@'%';
撤销 'heima'@'%' 用户的itcast数据库的所有权限
revoke all on itcast.* from 'heima'@'%';
revoke all on itcast.* from 'heima'@'%';
函数
字符串函数
concat演示
select concat('Hello' , ' MySQL');
小写
select lower('Hello');
大写
select upper('Hello');
左填充
select lpad('01', 5, '-');
案例:
由于业务需求变更,企业员工的工号,统一为5位数,目前不足5位数的全部在前面补0。比如: 1号员
工的工号应该为00001。
SQL语句:update emp set workno = lpad(workno, 5, '0')
由于业务需求变更,企业员工的工号,统一为5位数,目前不足5位数的全部在前面补0。比如: 1号员
工的工号应该为00001。
SQL语句:update emp set workno = lpad(workno, 5, '0')
右填充
select rpad('01', 5, '-');
去除空格
select trim(' Hello MySQL ');
截取子字符串
select substring('Hello MySQL',1,5);
数值函数
A. ceil:向上取整
B. floor:向下取整
C. mod:取模
D. rand:获取随机数
E. round:四舍五入
案例:
通过数据库的函数,生成一个六位数的随机验证码。
思路: 获取随机数可以通过rand()函数,但是获取出来的随机数是在
0-1之间的,所以可以在其基础
上乘以1000000,然后舍弃小数部分,如果长度不足6位,补0
select ceil(1.1); select floor(1.9); select mod(7,4); select rand();
select round(2.344,2);
B. floor:向下取整
C. mod:取模
D. rand:获取随机数
E. round:四舍五入
案例:
通过数据库的函数,生成一个六位数的随机验证码。
思路: 获取随机数可以通过rand()函数,但是获取出来的随机数是在
0-1之间的,所以可以在其基础
上乘以1000000,然后舍弃小数部分,如果长度不足6位,补0
select ceil(1.1); select floor(1.9); select mod(7,4); select rand();
select round(2.344,2);
日期函数
select curdate();
select curtime()
select now();
select YEAR(now());
select MONTH(now());
select DAY(now());
select MONTH(now());
select DAY(now());
select date_add(now(), INTERVAL 70 YEAR );
select datediff('2021-10-01', '2021-12-01');
案例
查询所有员工的入职天数,并根据入职天数倒序排序。
思路: 入职天数,就是通过当前日期 - 入职日期,所以需要使用datediff函数来完成。
select name, datediff(curdate(), entrydate) as 'entrydays' from emp order by entrydays desc;
流程函数
A. if
select if(false, 'Ok', 'Error');
select if(false, 'Ok', 'Error');
ifnull
select ifnull('Ok','Default');
select ifnull('','Default');
select ifnull(null,'Default');
select ifnull('Ok','Default');
select ifnull('','Default');
select ifnull(null,'Default');
case when then else end
需求: 查询emp表的员工姓名和工作地址 (北京/上海 ----> 一线城市 , 其他 ----> 二线城市)
需求: 查询emp表的员工姓名和工作地址 (北京/上海 ----> 一线城市 , 其他 ----> 二线城市)
selectname, ( case workaddress
when '北京' then '一线城市'
when '上海' then '一线城市'
else '二线城市' end ) as '工作地址' from emp;
when '北京' then '一线城市'
when '上海' then '一线城市'
else '二线城市' end ) as '工作地址' from emp;
案例
create table score( id int comment 'ID',
name varchar(20) comment '姓名',
math int comment '数学',
english int comment '英语',
chinese int comment '语文' ) comment '学员成绩表';
insert into score(id, name, math, english, chinese)
VALUES (1, 'Tom', 67, 88, 95 ),
(2, 'Rose' , 23, 66, 90),
(3, 'Jack', 56, 98, 76);
name varchar(20) comment '姓名',
math int comment '数学',
english int comment '英语',
chinese int comment '语文' ) comment '学员成绩表';
insert into score(id, name, math, english, chinese)
VALUES (1, 'Tom', 67, 88, 95 ),
(2, 'Rose' , 23, 66, 90),
(3, 'Jack', 56, 98, 76);
selectid, name, (case when math >= 85 then '优秀'
when math >=60 then '及格' else '不及格' end ) '数学', (case
when english >= 85 then '优秀' when english >=60 then '及格' else '不及格' end ) '英语', (case when chinese >= 85 then '优秀'
when chinese >=60 then '及格' else '不及格' end ) '语文' from score;
when math >=60 then '及格' else '不及格' end ) '数学', (case
when english >= 85 then '优秀' when english >=60 then '及格' else '不及格' end ) '英语', (case when chinese >= 85 then '优秀'
when chinese >=60 then '及格' else '不及格' end ) '语文' from score;
约束
概述
约束是作用于表中字段上的规则,用于限制存储在表中的数据
目的
保证数据库中数据的正确、有效性和完整性。
约束演示
级联
CASCADE
删除父节点时,会相应的删除子节点信息
CASCADE
删除父节点时,会相应的删除子节点信息
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id)
on update cascade on delete cascade ;
on update cascade on delete cascade ;
set null
当删除更新父节点时,子节点会被设置为null
当删除更新父节点时,子节点会被设置为null
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id)
on update set null on delete set null ;
on update set null on delete set null ;
多表查询
类型
一对一
一对多
多对多
分类
连接查询
内连接:相当于查询A、B交集部分数据
外连接:
左外连接:查询左表所有数据,以及两张表交集部分数据
右外连接:查询右表所有数据,以及两张表交集部分数据
自连接:当前表与自身的连接查询,自连接必须使用表别名
外连接:
左外连接:查询左表所有数据,以及两张表交集部分数据
右外连接:查询右表所有数据,以及两张表交集部分数据
自连接:当前表与自身的连接查询,自连接必须使用表别名
数据准备
-- 创建dept表,并插入数据
create table dept( id int auto_increment comment 'ID' primary key,
name varchar(50) not null comment '部门名称' )comment '部门表';
INSERT INTO dept (id, name)
VALUES (1, '研发部'), (2, '市场部'),(3, '财务部'), (4, '销售部'), (5, '总经办'), (6, '人事部');
-- 创建emp表,并插入数据 create table emp( id int auto_increment comment 'ID' primary key,
name varchar(50) not null comment '姓名', age int comment '年龄', job varchar(20) comment '职位', salary int comment '薪资', entrydate date comment '入职时间', managerid int comment '直属领导ID', dept_id int comment '部门ID' )comment '员工表';
-- 添加外键 alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
//添加数据
INSERT INTO emp (id, name, age, job,salary, entrydate, managerid, dept_id) VALUES (1, '金庸', 66, '总裁',20000, '2000-01-01', null,5), (2, '张无忌', 20, '项目经理',12500, '2005-12-05', 1,1), (3, '杨逍', 33, '开发', 8400,'2000-11-03', 2,1), (4, '韦一笑', 48, '开发',11000, '2002-02-05', 2,1), (5, '常遇春', 43, '开发',10500, '2004-09-07', 3,1), (6, '小昭', 19, '程序员鼓励师',6600, '2004-10-12', 2,1), (7, '灭绝', 60, '财务总监',8500, '2002-09-12', 1,3), (8, '周芷若', 19, '会计',48000, '2006-06-02', 7,3), (9, '丁敏君', 23, '出纳',5250, '2009-05-13', 7,3), (10, '赵敏', 20, '市场部总监',12500, '2004-10-12', 1,2), (11, '鹿杖客', 56, '职员',3750, '2006-10-03', 10,2), (12, '鹤笔翁', 19, '职员',3750, '2007-05-09', 10,2), (13, '方东白', 19, '职员',5500, '2009-02-12', 10,2), (14, '张三丰', 88, '销售总监',14000, '2004-10-12', 1,4), (15, '俞莲舟', 38, '销售',4600, '2004-10-12', 14,4), (16, '宋远桥', 40, '销售',4600, '2004-10-12', 14,4), (17, '陈友谅', 42, null,2000, '2011-10-12', 1,null);
create table dept( id int auto_increment comment 'ID' primary key,
name varchar(50) not null comment '部门名称' )comment '部门表';
INSERT INTO dept (id, name)
VALUES (1, '研发部'), (2, '市场部'),(3, '财务部'), (4, '销售部'), (5, '总经办'), (6, '人事部');
-- 创建emp表,并插入数据 create table emp( id int auto_increment comment 'ID' primary key,
name varchar(50) not null comment '姓名', age int comment '年龄', job varchar(20) comment '职位', salary int comment '薪资', entrydate date comment '入职时间', managerid int comment '直属领导ID', dept_id int comment '部门ID' )comment '员工表';
-- 添加外键 alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
//添加数据
INSERT INTO emp (id, name, age, job,salary, entrydate, managerid, dept_id) VALUES (1, '金庸', 66, '总裁',20000, '2000-01-01', null,5), (2, '张无忌', 20, '项目经理',12500, '2005-12-05', 1,1), (3, '杨逍', 33, '开发', 8400,'2000-11-03', 2,1), (4, '韦一笑', 48, '开发',11000, '2002-02-05', 2,1), (5, '常遇春', 43, '开发',10500, '2004-09-07', 3,1), (6, '小昭', 19, '程序员鼓励师',6600, '2004-10-12', 2,1), (7, '灭绝', 60, '财务总监',8500, '2002-09-12', 1,3), (8, '周芷若', 19, '会计',48000, '2006-06-02', 7,3), (9, '丁敏君', 23, '出纳',5250, '2009-05-13', 7,3), (10, '赵敏', 20, '市场部总监',12500, '2004-10-12', 1,2), (11, '鹿杖客', 56, '职员',3750, '2006-10-03', 10,2), (12, '鹤笔翁', 19, '职员',3750, '2007-05-09', 10,2), (13, '方东白', 19, '职员',5500, '2009-02-12', 10,2), (14, '张三丰', 88, '销售总监',14000, '2004-10-12', 1,4), (15, '俞莲舟', 38, '销售',4600, '2004-10-12', 14,4), (16, '宋远桥', 40, '销售',4600, '2004-10-12', 14,4), (17, '陈友谅', 42, null,2000, '2011-10-12', 1,null);
子查询
内连接
隐式内连接
SELECT 字段列表 FROM 表1 , 表2 WHERE 条件 ... ;
显示内连接
SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ... ;
案例
查询每一个员工的姓名 , 及关联的部门的名称 (隐式内连接实现)
select e.name,d.name from emp e , dept d where e.dept_id = d.id;
查询每一个员工的姓名 , 及关联的部门的名称 (显式内连接实现)
select e.name, d.name from emp e join dept d on e.dept_id = d.id;
外连接
左外
SELECT 字段列表 FROM 表1 LEFT [ OUTER ] JOIN 表2 ON 条件 ... ;
右外
SELECT 字段列表 FROM 表1 RIGHT [ OUTER ] JOIN 表2 ON 条件 ... ;
案例
查询emp表的所有数据, 和对应的部门信息
select e.*, d.name from emp e left join dept d on e.dept_id = d.id;
查询dept表的所有数据, 和对应的员工信息(右外连接)
select d.*, e.* from dept d left outer join emp e on e.dept_id = d.id;
自连接
自连接
SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ... ;
案例
查询员工 及其 所属领导的名字
select a.name , b.name from emp a , emp b where a.managerid = b.id;
查询所有员工 emp 及其领导的名字 emp , 如果员工没有领导, 也需要查询出来
select a.name '员工', b.name '领导' from emp a left join emp b on a.managerid = b.id;
联合查询
SELECT 字段列表 FROM 表A ...
UNION [ ALL ]
SELECT 字段列表 FROM 表B ....;
UNION [ ALL ]
SELECT 字段列表 FROM 表B ....;
案例
将薪资低于 5000 的员工 , 和 年龄大于 50 岁的员工全部查询出来.
select * from emp where salary < 5000
union all
select * from emp where age > 50;
union all
select * from emp where age > 50;
子查询
SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2 );
分类
A. 标量子查询(子查询结果为单个值)
B. 列子查询(子查询结果为一列)
C. 行子查询(子查询结果为一行)
D. 表子查询(子查询结果为多行多列)
B. 列子查询(子查询结果为一列)
C. 行子查询(子查询结果为一行)
D. 表子查询(子查询结果为多行多列)
查询位置
A. WHERE之后
B. FROM之后
C. SELECT之后
B. FROM之后
C. SELECT之后
标量子查询
子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询称为标量子查询
常用的操作符:= <> > >= < <=
案例
查询 "销售部" 的所有员工信息
select * from emp where dept_id = (select id from dept where name = '销售部');
查询在 "方东白" 入职之后的员工信息
select * from emp where entrydate > (select entrydate from emp where name = '方东白');
列子查询
子查询返回的结果是一列(可以是多行),这种子查询称为列子查询。
常用的操作符:IN 、NOT IN 、 ANY 、SOME 、 ALL
案例
查询 "销售部" 和 "市场部" 的所有员工信息
select * from emp where dept_id in (select id from dept where name = '销售部' or name = '市场部');
查询比 财务部 所有人工资都高的员工信息
select * from emp where salary > all ( select salary from emp where dept_id = (select id from dept where name = '财务部') );
查询比研发部其中任意一人工资高的员工信息
select * from emp where salary > any ( select salary from emp where dept_id = (select id from dept where name = '研发部') );
行子查询
子查询返回的结果是一行(可以是多列),这种子查询称为行子查询。
常用的操作符:= 、<> 、IN 、NOT IN
案例
查询与 "张无忌" 的薪资及直属领导相同的员工信息 ;
select * from emp where (salary,managerid) = (select salary, managerid from emp where name = '张无忌');
表子查询
子查询返回的结果是多行多列,这种子查询称为表子查询。
常用的操作符:IN
案例
查询与 "鹿杖客" , "宋远桥" 的职位和薪资相同的员工信息
select * from emp where (job,salary) in ( select job, salary from emp where name = '鹿杖客' or name = '宋远桥' );
查询入职日期是 "2006-01-01" 之后的员工信息 , 及其部门信息
select e.*, d.* from (select * from emp where entrydate > '2006-01-01') e left
join dept d on e.dept_id = d.id ;
多表查询案例
create table salgrade( grade int,
losal int,
hisal int ) comment '薪资等级表';
losal int,
hisal int ) comment '薪资等级表';
insert into salgrade values (1,0,3000); insert into salgrade values (2,3001,5000); insert into salgrade values (3,5001,8000); insert into salgrade values (4,8001,10000); insert into salgrade values (5,10001,15000); insert into salgrade values (6,15001,20000); insert into salgrade values (7,20001,25000); insert into salgrade values (8,25001,30000);
查询员工的姓名、年龄、职位、部门信息 (隐式内连接)
select e.name , e.age , e.job , d.name from emp e , dept d where e.dept_id = d.id
查询年龄小于30岁的员工的姓名、年龄、职位、部门信息(显式内连接)
select e.name , e.age , e.job , d.name from emp e inner join dept d on e.dept_id = d.id where e.age < 30;
查询拥有员工的部门ID、部门名称
select distinct d.id , d.name from emp e , dept d where e.dept_id = d.id;
查询所有年龄大于40岁的员工, 及其归属的部门名称; 如果员工没有分配部门, 也需要展示出
来(外连接)
来(外连接)
select e.*, d.name from emp e left join dept d on e.dept_id = d.id where e.age > 40 ;
查询所有员工的工资等级
方式一:
select e.* , s.grade , s.losal, s.hisal from emp e , salgrade s where e.salary >= s.losal and e.salary <= s.hisal;
select e.* , s.grade , s.losal, s.hisal from emp e , salgrade s where e.salary >= s.losal and e.salary <= s.hisal;
方式二:
select e.* , s.grade , s.losal, s.hisal from emp e , salgrade s where e.salary between s.losal and s.hisal;
select e.* , s.grade , s.losal, s.hisal from emp e , salgrade s where e.salary between s.losal and s.hisal;
查询 "研发部" 所有员工的信息及 工资等级
select e.* , s.grade from emp e , dept d , salgrade s where e.dept_id = d.id and ( e.salary between s.losal and s.hisal ) and d.name = '研发部';
查询 "研发部" 员工的平均工资
select avg(e.salary) from emp e, dept d where e.dept_id = d.id and d.name = '研发 部';
查询工资比 "灭绝" 高的员工信息。
select * from emp where salary > ( select salary from emp where name = '灭绝' );
查询比平均薪资高的员工信息
select * from emp where salary > ( select avg(salary) from emp );
查询低于本部门平均工资的员工信息
select * from emp e2 where e2.salary < ( select avg(e1.salary) from emp e1 where e1.dept_id = e2.dept_id )
查询所有的部门信息, 并统计部门的员工人数
select d.id, d.name , ( select count(*) from emp e where e.dept_id = d.id ) '人数' from dept d;
查询所有学生的选课情况, 展示出学生名称, 学号, 课程名称
select s.name , s.no , c.name from student s , student_course sc , course c where s.id = sc.studentid and sc.courseid = c.id ;
事务
概念
是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系
统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
事务操作
数据准备
drop table if exists account;
create table account( id int primary key AUTO_INCREMENT comment 'ID',
name varchar(10) comment '姓名',
money double(10,2) comment '余额' )
comment '账户表';
insert into account(name, money) VALUES ('张三',2000), ('李四',2000);
create table account( id int primary key AUTO_INCREMENT comment 'ID',
name varchar(10) comment '姓名',
money double(10,2) comment '余额' )
comment '账户表';
insert into account(name, money) VALUES ('张三',2000), ('李四',2000);
未控制事务
控制事务一
查看/设置事务提交方式
SELECT @@autocommit ;
SET @@autocommit = 0 ;
SET @@autocommit = 0 ;
提交事务
COMMIT;
回滚事务
ROLLBACK;
控制事务二
开启事务
START TRANSACTION 或 BEGIN ;
提交事务
COMMIT;
回滚事务
ROLLBACK;
转账案例:
-- 开启事务 start transaction
-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';
-- 如果正常执行完毕, 则提交事务
commit;
-- 如果执行过程中报错, 则回滚事务
-- rollback;
-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';
-- 如果正常执行完毕, 则提交事务
commit;
-- 如果执行过程中报错, 则回滚事务
-- rollback;
四大特性(ACID)
原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立
环境下运行。
环境下运行。
持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
并发事务问题
赃读:一个事务读到另外一个事务还没有提交的数据。
不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读
幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据
已经存在,好像出现了 "幻影"。
已经存在,好像出现了 "幻影"。
事务的隔离级别
查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
进阶
运维
redis
NoSQL概述
为什么用NoSQL
单机
数据存储的瓶颈
1. 数据量的总大小,一个机器放不下时
2. 数据的索引(B+ Tree)一个机器的内存放不下时
3. 访问量(读写混合)一个实例不能承受
2. 数据的索引(B+ Tree)一个机器的内存放不下时
3. 访问量(读写混合)一个实例不能承受
Memcached(缓存)+ MySQL + 垂直拆分
MySQL主从读写分离
分表分库 + 水平拆分 + Mysql 集群
在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始
出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高
并发MySQL应用开始使用InnoDB引擎代替MyISAM。
出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高
并发MySQL应用开始使用InnoDB引擎代替MyISAM。
MySQL 的扩展性瓶颈
MySQL数据库也经常存储一些大文本的字段,导致数据库表非常的大,在做数据库恢复的时候就导致非
常的慢,不容易快速恢复数据库,比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据
从MySQL省去,MySQL将变的非常的小,关系数据库很强大,但是它并不能很好的应付所有的应用场
景,MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使
用MySQL的开发人员面临的问题。
如今的状态
为什么用NoSQL
今天我们可以通过第三方平台(如:Google,FaceBook等)可以很容易的访问和抓取数据。用户的个
人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加、我们如果要对这些用
户数据进行挖掘,那SQL数据库已经不适合这些应用了,而NoSQL数据库的发展却能很好的处理这些大
的数据!
人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加、我们如果要对这些用
户数据进行挖掘,那SQL数据库已经不适合这些应用了,而NoSQL数据库的发展却能很好的处理这些大
的数据!
什么是NoSQL
概述
非关系型数据库
特点
易扩展
NoSQL 数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。
数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力
数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力
大数据量高性能
NoSQL数据库都具有非常高的读写性能,尤其是在大数据量下,同样表现优秀。这得益于它的非关系
性,数据库的结构简单。
一般MySQL使用Query Cache,每次表的更新Cache就失效,是一种大力度的Cache,在针对Web2.0的
交互频繁应用,Cache性能不高,而NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL
在这个层面上来说就要性能高很多了。
官方记录:Redis 一秒可以写8万次,读11万次!
多样灵活的数据模型
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式,而在关系数据库里,增删
字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是噩梦
字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是噩梦
传统的RDBMS VS NoSQL
传统的关系型数据库 RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性 和 可伸缩性
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性 和 可伸缩性
经典应用分析
大型互联网应用(大数据,高并发,多样数据类型)的难点和解决方案
难点:
数据类型的多样性
数据源多样性和变化重构
数据源改造而数据服务平台不需要大面积重构
数据类型的多样性
数据源多样性和变化重构
数据源改造而数据服务平台不需要大面积重构
NoSQL数据模型分类
KV键值:
新浪:BerkeleyDB+redis
美团:redis+tair
阿里、百度:memcache+redis
新浪:BerkeleyDB+redis
美团:redis+tair
阿里、百度:memcache+redis
文档型数据库(bson格式比较多):
CouchDB
MongoDB
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可
扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰
富,最像关系数据库的。
CouchDB
MongoDB
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可
扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰
富,最像关系数据库的。
列存储数据库:
Cassandra, HBase
分布式文件系统
Cassandra, HBase
分布式文件系统
图关系数据库
它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统
社交网络,推荐系统等。专注于构建关系图谱
Neo4J, InfoGrid
它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统
社交网络,推荐系统等。专注于构建关系图谱
Neo4J, InfoGrid
CAP+Base
传统的ACID分别是什么?
A (Atomicity) 原子性
这两步要么一起完成,要么一起不完成,如果只完成第一步,第二步失败,钱会莫名其妙少了100
元。
元。
C (Consistency) 一致性
事务前后数据的完整性必须保持一致。
I (Isolation) 隔离性
所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修
改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。
改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。
D (Durability) 持久性
持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
CAP
C : Consistency(强一致性)
A : Availability(可用性)
P : Partition tolerance(分区容错性)
A : Availability(可用性)
P : Partition tolerance(分区容错性)
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些
Base
其核心思想是即使无法做到强一致性,但
每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性
每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性
Redis入门
概述
Redis是什么
Redis:REmote DIctionary Server(远程字典服务器)
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使
用。
Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存
储。
Redis支持数据的备份,即master-slave模式的数据备份。
用。
Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存
储。
Redis支持数据的备份,即master-slave模式的数据备份。
Redis能干嘛
内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
发布、订阅消息系统
地图信息分析
定时器、计数器
取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
发布、订阅消息系统
地图信息分析
定时器、计数器
特性
数据类型、基本操作和配置
持久化和复制,RDB、AOF
事务的控制
持久化和复制,RDB、AOF
事务的控制
windows安装
linux安装
1、下载获得 redis-5.0.7.tar.gz 后将它放到我们Linux的目录下 /opt
2、/opt 目录下,解压命令 : tar -zxvf redis-5.0.7.tar.gz
3、解压完成后出现文件夹:redis-5.0.7
4、进入目录: cd redis-5.0.7
5、在 redis-5.0.7 目录下执行 make 命令
6、如果make完成后继续执行 make install
7、查看默认安装目录:usr/local/bin
8、拷贝配置文件(备用)
2、/opt 目录下,解压命令 : tar -zxvf redis-5.0.7.tar.gz
3、解压完成后出现文件夹:redis-5.0.7
4、进入目录: cd redis-5.0.7
5、在 redis-5.0.7 目录下执行 make 命令
6、如果make完成后继续执行 make install
7、查看默认安装目录:usr/local/bin
8、拷贝配置文件(备用)
redis.conf配置文件中daemonize守护线程,默认是NO。
daemonize是用来指定redis是否要用守护线程的方式启动。
daemonize是用来指定redis是否要用守护线程的方式启动。
daemonize:yes
redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启
守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项
pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
daemonize:no
当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭
连接工具(putty,xshell等)都会导致redis进程退出。
redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启
守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项
pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
daemonize:no
当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭
连接工具(putty,xshell等)都会导致redis进程退出。
基础知识
redis压力测试工具-----Redis-benchmark
# 测试一:100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性
能
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 测试出来的所有命令只举例一个!
====== SET ======
100000 requests completed in 1.88 seconds # 对集合写入测试
100 parallel clients # 每次请求有100个并发客户端
3 bytes payload # 每次写入3个字节的数据,有效载荷
keep alive: 1 # 保持一个连接,一台服务器来处理这些请求
17.05% <= 1 milliseconds
97.35% <= 2 milliseconds
99.97% <= 3 milliseconds
100.00% <= 3 milliseconds # 所有请求在 3 毫秒内完成
53248.14 requests per second # 每秒处理 53248.14 次请求
能
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 测试出来的所有命令只举例一个!
====== SET ======
100000 requests completed in 1.88 seconds # 对集合写入测试
100 parallel clients # 每次请求有100个并发客户端
3 bytes payload # 每次写入3个字节的数据,有效载荷
keep alive: 1 # 保持一个连接,一台服务器来处理这些请求
17.05% <= 1 milliseconds
97.35% <= 2 milliseconds
99.97% <= 3 milliseconds
100.00% <= 3 milliseconds # 所有请求在 3 毫秒内完成
53248.14 requests per second # 每秒处理 53248.14 次请求
默认16个数据库
查看 redis.conf ,里面有默认的配置
databases 16
databases 16
select 切换数据库
27.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]>
# 不同的库可以存不同的数据
OK
127.0.0.1:6379[7]>
# 不同的库可以存不同的数据
Dbsize查看当前数据库的key的数量
127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]> DBSIZE
(integer) 0
127.0.0.1:6379[7]> select 0
OK
127.0.0.1:6379> DBSIZE
(integer) 5
127.0.0.1:6379> keys * # 查看具体的key
1) "counter:__rand_int__"
2) "mylist"
3) "k1"
4) "myset:__rand_int__"
5) "key:__rand_int__"
OK
127.0.0.1:6379[7]> DBSIZE
(integer) 0
127.0.0.1:6379[7]> select 0
OK
127.0.0.1:6379> DBSIZE
(integer) 5
127.0.0.1:6379> keys * # 查看具体的key
1) "counter:__rand_int__"
2) "mylist"
3) "k1"
4) "myset:__rand_int__"
5) "key:__rand_int__"
Flushdb:清空当前库
Flushall:清空全部的库
Flushall:清空全部的库
127.0.0.1:6379> DBSIZE
(integer) 5
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> DBSIZE
(integer) 0
(integer) 5
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> DBSIZE
(integer) 0
Redis为什么这么快?
redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的
五大数据类型
Redis键(key,value)
key 的操作
# keys * 查看所有的key
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> keys *
1) "name"
# exists key 的名字,判断某个key是否存在
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
# move key db ---> 当前库就没有了,被移除了
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
(empty list or set)
# expire key 秒钟:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删
除。
# ttl key 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> EXPIRE name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> keys *
(empty list or set)
(empty list or set)
# type key 查看你的key是什么类型
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> get name
"qinjiang"
127.0.0.1:6379> type name
string
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> keys *
1) "name"
# exists key 的名字,判断某个key是否存在
127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
# move key db ---> 当前库就没有了,被移除了
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
(empty list or set)
# expire key 秒钟:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删
除。
# ttl key 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> EXPIRE name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> keys *
(empty list or set)
(empty list or set)
# type key 查看你的key是什么类型
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> get name
"qinjiang"
127.0.0.1:6379> type name
string
字符串(String)
String是redis最基本的类型,你可以理解成Memcached一模一样的类型,一个key对应一个value。
String类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象。
String类型是redis最基本的数据类型,一个redis中字符串value最多可以是512M。
String类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象。
String类型是redis最基本的数据类型,一个redis中字符串value最多可以是512M。
# ===================================================
# set、get、del、append、strlen
# ===================================================
127.0.0.1:6379> set key1 value1 # 设置值
OK
127.0.0.1:6379> get key1 # 获得key
"value1"
127.0.0.1:6379> del key1 # 删除key
(integer) 1
127.0.0.1:6379> keys * # 查看全部的key
(empty list or set)
127.0.0.1:6379> exists key1 # 确保 key1 不存在
(integer) 0
127.0.0.1:6379> append key1 "hello" # 对不存在的 key 进行 APPEND ,等同于 SET
key1 "hello"
(integer) 5 # 字符长度
127.0.0.1:6379> APPEND key1 "-2333" # 对已存在的字符串进行 APPEND
(integer) 10 # 长度从 5 个字符增加到 10 个字符
127.0.0.1:6379> get key1
"hello-2333"
127.0.0.1:6379> STRLEN key1 # # 获取字符串的长度
(integer) 10
# ===================================================
# incr、decr 一定要是数字才能进行加减,+1 和 -1。
# incrby、decrby 命令将 key 中储存的数字加上指定的增量值。
# ===================================================
127.0.0.1:6379> set views 0 # 设置浏览量为0
OK
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 1
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 2
127.0.0.1:6379> decr views # 浏览 - 1
(integer) 1
127.0.0.1:6379> incrby views 10 # +10
(integer) 11
127.0.0.1:6379> decrby views 10 # -10
(integer) 1
# ===================================================
# range [范围]
# getrange 获取指定区间范围内的值,类似between...and的关系,从零到负一表示全部
# ===================================================
127.0.0.1:6379> set key2 abcd123456 # 设置key2的值
OK
127.0.0.1:6379> getrange key2 0 -1 # 获得全部的值
"abcd123456"
127.0.0.1:6379> getrange key2 0 2 # 截取部分字符串
"abc"
# ===================================================
# setrange 设置指定区间范围内的值,格式是setrange key值 具体值
# ===================================================
127.0.0.1:6379> get key2
"abcd123456"
127.0.0.1:6379> SETRANGE key2 1 xx # 替换值
(integer) 10
127.0.0.1:6379> get key2
"axxd123456"
# ===================================================
# setex(set with expire)键秒值
# setnx(set if not exist)
# ===================================================
127.0.0.1:6379> setex key3 60 expire # 设置过期时间
OK
127.0.0.1:6379> ttl key3 # 查看剩余的时间
(integer) 55
127.0.0.1:6379> setnx mykey "redis" # 如果不存在就设置,成功返回1
(integer) 1
127.0.0.1:6379> setnx mykey "mongodb" # 如果存在就设置,失败返回0
(integer) 0
127.0.0.1:6379> get mykey
"redis"
# ===================================================
# mset Mset 命令用于同时设置一个或多个 key-value 对。
# mget Mget 命令返回所有(一个或多个)给定 key 的值。
# 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
# msetnx 当所有 key 都成功设置,返回 1 。
# 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。原子操
作
# ===================================================
127.0.0.1:6379> mset k10 v10 k11 v11 k12 v12
OK
127.0.0.1:6379> keys *
1) "k12"
2) "k11"
3) "k10"
127.0.0.1:6379> mget k10 k11 k12 k13
1) "v10"
2) "v11"
3) "v12"
4) (nil)
127.0.0.1:6379> msetnx k10 v10 k15 v15 # 原子性操作!
(integer) 0
127.0.0.1:6379> get key15
(nil)
# 传统对象缓存
set user:1 value(json数据)
# 可以用来缓存对象
mset user:1:name zhangsan user:1:age 2
mget user:1:name user:1:age
# ===================================================
# getset(先get再set)
# ===================================================
127.0.0.1:6379> getset db mongodb # 没有旧值,返回 nil
(nil)
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db redis # 返回旧值 mongodb
"mongodb"
127.0.0.1:6379> get db
"redis"
# set、get、del、append、strlen
# ===================================================
127.0.0.1:6379> set key1 value1 # 设置值
OK
127.0.0.1:6379> get key1 # 获得key
"value1"
127.0.0.1:6379> del key1 # 删除key
(integer) 1
127.0.0.1:6379> keys * # 查看全部的key
(empty list or set)
127.0.0.1:6379> exists key1 # 确保 key1 不存在
(integer) 0
127.0.0.1:6379> append key1 "hello" # 对不存在的 key 进行 APPEND ,等同于 SET
key1 "hello"
(integer) 5 # 字符长度
127.0.0.1:6379> APPEND key1 "-2333" # 对已存在的字符串进行 APPEND
(integer) 10 # 长度从 5 个字符增加到 10 个字符
127.0.0.1:6379> get key1
"hello-2333"
127.0.0.1:6379> STRLEN key1 # # 获取字符串的长度
(integer) 10
# ===================================================
# incr、decr 一定要是数字才能进行加减,+1 和 -1。
# incrby、decrby 命令将 key 中储存的数字加上指定的增量值。
# ===================================================
127.0.0.1:6379> set views 0 # 设置浏览量为0
OK
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 1
127.0.0.1:6379> incr views # 浏览 + 1
(integer) 2
127.0.0.1:6379> decr views # 浏览 - 1
(integer) 1
127.0.0.1:6379> incrby views 10 # +10
(integer) 11
127.0.0.1:6379> decrby views 10 # -10
(integer) 1
# ===================================================
# range [范围]
# getrange 获取指定区间范围内的值,类似between...and的关系,从零到负一表示全部
# ===================================================
127.0.0.1:6379> set key2 abcd123456 # 设置key2的值
OK
127.0.0.1:6379> getrange key2 0 -1 # 获得全部的值
"abcd123456"
127.0.0.1:6379> getrange key2 0 2 # 截取部分字符串
"abc"
# ===================================================
# setrange 设置指定区间范围内的值,格式是setrange key值 具体值
# ===================================================
127.0.0.1:6379> get key2
"abcd123456"
127.0.0.1:6379> SETRANGE key2 1 xx # 替换值
(integer) 10
127.0.0.1:6379> get key2
"axxd123456"
# ===================================================
# setex(set with expire)键秒值
# setnx(set if not exist)
# ===================================================
127.0.0.1:6379> setex key3 60 expire # 设置过期时间
OK
127.0.0.1:6379> ttl key3 # 查看剩余的时间
(integer) 55
127.0.0.1:6379> setnx mykey "redis" # 如果不存在就设置,成功返回1
(integer) 1
127.0.0.1:6379> setnx mykey "mongodb" # 如果存在就设置,失败返回0
(integer) 0
127.0.0.1:6379> get mykey
"redis"
# ===================================================
# mset Mset 命令用于同时设置一个或多个 key-value 对。
# mget Mget 命令返回所有(一个或多个)给定 key 的值。
# 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
# msetnx 当所有 key 都成功设置,返回 1 。
# 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。原子操
作
# ===================================================
127.0.0.1:6379> mset k10 v10 k11 v11 k12 v12
OK
127.0.0.1:6379> keys *
1) "k12"
2) "k11"
3) "k10"
127.0.0.1:6379> mget k10 k11 k12 k13
1) "v10"
2) "v11"
3) "v12"
4) (nil)
127.0.0.1:6379> msetnx k10 v10 k15 v15 # 原子性操作!
(integer) 0
127.0.0.1:6379> get key15
(nil)
# 传统对象缓存
set user:1 value(json数据)
# 可以用来缓存对象
mset user:1:name zhangsan user:1:age 2
mget user:1:name user:1:age
# ===================================================
# getset(先get再set)
# ===================================================
127.0.0.1:6379> getset db mongodb # 没有旧值,返回 nil
(nil)
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db redis # 返回旧值 mongodb
"mongodb"
127.0.0.1:6379> get db
"redis"
列表(List)
Redis列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾
部(右边)。
它的底层实际是
部(右边)。
它的底层实际是
单值多value
# ===================================================
# Lpush:将一个或多个值插入到列表头部。(左)
# rpush:将一个或多个值插入到列表尾部。(右)
# lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
# 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
# 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此
类推。
# ===================================================
127.0.0.1:6379> LPUSH list "one"
(integer) 1
127.0.0.1:6379> LPUSH list "two"
(integer) 2
127.0.0.1:6379> RPUSH list "right"
(integer) 3
127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> Lrange list 0 1
1) "two"
2) "one"
# ===================================================
# lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
# rpop 移除列表的最后一个元素,返回值为移除的元素。
# ===================================================
127.0.0.1:6379> Lpop list
"two"
127.0.0.1:6379> Rpop list
"right"
127.0.0.1:6379> Lrange list 0 -1
1) "one"
# ===================================================
# Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个)
# ===================================================
127.0.0.1:6379> Lindex list 1
(nil)
127.0.0.1:6379> Lindex list 0
"one"
127.0.0.1:6379> Lindex list -1
"one"
# ===================================================
# llen 用于返回列表的长度。
# ===================================================
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> Lpush list "one"
(integer) 1
127.0.0.1:6379> Lpush list "two"
(integer) 2
127.0.0.1:6379> Lpush list "three"
(integer) 3
127.0.0.1:6379> Llen list # 返回列表的长度
(integer) 3
# ===================================================
# lrem key 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
# ===================================================
127.0.0.1:6379> lrem list 1 "two"
(integer) 1
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "one"
# ===================================================
# Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区
间之内的元素都将被删除。
# ===================================================
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 2
127.0.0.1:6379> RPUSH mylist "hello2"
(integer) 3
127.0.0.1:6379> RPUSH mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello2"
# ===================================================
# rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
# ===================================================
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist
"bar"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "bar"
# ===================================================
# lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。
# ===================================================
127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item # 报错
(error) ERR no such key
127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 "new" # 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new" # index 超出范围报错
(error) ERR index out of range
# ===================================================
# linsert key before/after pivot value 用于在列表的元素前或者后插入元素。
# 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
# ===================================================
redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2
redis> LINSERT mylist BEFORE "World" "There"
(integer) 3
redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"
# Lpush:将一个或多个值插入到列表头部。(左)
# rpush:将一个或多个值插入到列表尾部。(右)
# lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
# 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
# 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此
类推。
# ===================================================
127.0.0.1:6379> LPUSH list "one"
(integer) 1
127.0.0.1:6379> LPUSH list "two"
(integer) 2
127.0.0.1:6379> RPUSH list "right"
(integer) 3
127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> Lrange list 0 1
1) "two"
2) "one"
# ===================================================
# lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
# rpop 移除列表的最后一个元素,返回值为移除的元素。
# ===================================================
127.0.0.1:6379> Lpop list
"two"
127.0.0.1:6379> Rpop list
"right"
127.0.0.1:6379> Lrange list 0 -1
1) "one"
# ===================================================
# Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个)
# ===================================================
127.0.0.1:6379> Lindex list 1
(nil)
127.0.0.1:6379> Lindex list 0
"one"
127.0.0.1:6379> Lindex list -1
"one"
# ===================================================
# llen 用于返回列表的长度。
# ===================================================
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> Lpush list "one"
(integer) 1
127.0.0.1:6379> Lpush list "two"
(integer) 2
127.0.0.1:6379> Lpush list "three"
(integer) 3
127.0.0.1:6379> Llen list # 返回列表的长度
(integer) 3
# ===================================================
# lrem key 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
# ===================================================
127.0.0.1:6379> lrem list 1 "two"
(integer) 1
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "one"
# ===================================================
# Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区
间之内的元素都将被删除。
# ===================================================
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 2
127.0.0.1:6379> RPUSH mylist "hello2"
(integer) 3
127.0.0.1:6379> RPUSH mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello2"
# ===================================================
# rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
# ===================================================
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist
"bar"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "bar"
# ===================================================
# lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。
# ===================================================
127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item # 报错
(error) ERR no such key
127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 "new" # 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new" # index 超出范围报错
(error) ERR index out of range
# ===================================================
# linsert key before/after pivot value 用于在列表的元素前或者后插入元素。
# 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
# ===================================================
redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2
redis> LINSERT mylist BEFORE "World" "There"
(integer) 3
redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"
集合(set)
Redis的Set是String类型的无序集合,它是通过HashTable实现的 !
# ===================================================
# sadd 将一个或多个成员元素加入到集合中,不能重复
# smembers 返回集合中的所有的成员。
# sismember 命令判断成员元素是否是集合的成员。
# ===================================================
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "hello"
127.0.0.1:6379> SISMEMBER myset "hello"
(integer) 1
127.0.0.1:6379> SISMEMBER myset "world"
(integer) 0
# ===================================================
# scard,获取集合里面的元素个数
# ===================================================
127.0.0.1:6379> scard myset
(integer) 2
# ===================================================
# srem key value 用于移除集合中的一个或多个成员元素
# ===================================================
127.0.0.1:6379> srem myset "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
# ===================================================
# srandmember key 命令用于返回集合中的一个随机元素。
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "world"
2) "kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "kuangshen"
2) "hello"
# ===================================================
# spop key 用于移除集合中的指定 key 的一个或多个随机元素
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset
"kuangshen"
127.0.0.1:6379> spop myset
"hello"
# ===================================================
# smove SOURCE DESTINATION MEMBER
# 将指定成员 member 元素从 source 集合移动到 destination 集合。
# ===================================================
# sadd 将一个或多个成员元素加入到集合中,不能重复
# smembers 返回集合中的所有的成员。
# sismember 命令判断成员元素是否是集合的成员。
# ===================================================
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "hello"
127.0.0.1:6379> SISMEMBER myset "hello"
(integer) 1
127.0.0.1:6379> SISMEMBER myset "world"
(integer) 0
# ===================================================
# scard,获取集合里面的元素个数
# ===================================================
127.0.0.1:6379> scard myset
(integer) 2
# ===================================================
# srem key value 用于移除集合中的一个或多个成员元素
# ===================================================
127.0.0.1:6379> srem myset "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
# ===================================================
# srandmember key 命令用于返回集合中的一个随机元素。
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "world"
2) "kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "kuangshen"
2) "hello"
# ===================================================
# spop key 用于移除集合中的指定 key 的一个或多个随机元素
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset
"kuangshen"
127.0.0.1:6379> spop myset
"hello"
# ===================================================
# smove SOURCE DESTINATION MEMBER
# 将指定成员 member 元素从 source 集合移动到 destination 集合。
# ===================================================
哈希(Hash)
Redis hash 是一个键值对集合。
Redis hash 是一个String类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
Redis hash 是一个String类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
# ===================================================
# hset、hget 命令用于为哈希表中的字段赋值 。
# hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
# hgetall 用于返回哈希表中,所有的字段和值。
# hdel 用于删除哈希表 key 中的一个或多个指定字段
# ===================================================
127.0.0.1:6379> hset myhash field1 "kuangshen"
(integer) 1
127.0.0.1:6379> hget myhash field1
"kuangshen"
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "World"
# ===================================================
# hlen 获取哈希表中字段的数量。
# ===================================================
127.0.0.1:6379> hlen myhash
(integer) 1
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> hlen myhash
(integer) 2
# ===================================================
# hexists 查看哈希表的指定字段是否存在。
# ===================================================
# hset、hget 命令用于为哈希表中的字段赋值 。
# hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
# hgetall 用于返回哈希表中,所有的字段和值。
# hdel 用于删除哈希表 key 中的一个或多个指定字段
# ===================================================
127.0.0.1:6379> hset myhash field1 "kuangshen"
(integer) 1
127.0.0.1:6379> hget myhash field1
"kuangshen"
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "World"
# ===================================================
# hlen 获取哈希表中字段的数量。
# ===================================================
127.0.0.1:6379> hlen myhash
(integer) 1
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> hlen myhash
(integer) 2
# ===================================================
# hexists 查看哈希表的指定字段是否存在。
# ===================================================
127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
# ===================================================
# hkeys 获取哈希表中的所有域(field)。
# hvals 返回哈希表所有域(field)的值。
# ===================================================
127.0.0.1:6379> HKEYS myhash
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash
1) "World"
2) "Hello"
# ===================================================
# hincrby 为哈希表中的字段值加上指定增量值。
# ===================================================
127.0.0.1:6379> hset myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5
# ===================================================
# hsetnx 为哈希表中不存在的的字段赋值 。
# ===================================================
127.0.0.1:6379> HSETNX myhash field1 "hello"
(integer) 1 # 设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field1 "world"
(integer) 0 # 如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field1
"hello"
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
# ===================================================
# hkeys 获取哈希表中的所有域(field)。
# hvals 返回哈希表所有域(field)的值。
# ===================================================
127.0.0.1:6379> HKEYS myhash
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash
1) "World"
2) "Hello"
# ===================================================
# hincrby 为哈希表中的字段值加上指定增量值。
# ===================================================
127.0.0.1:6379> hset myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5
# ===================================================
# hsetnx 为哈希表中不存在的的字段赋值 。
# ===================================================
127.0.0.1:6379> HSETNX myhash field1 "hello"
(integer) 1 # 设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field1 "world"
(integer) 0 # 如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field1
"hello"
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
存储部分变更的数据,如用户信息等。
存储部分变更的数据,如用户信息等。
有序集合(Zset)
Redis zset 和 set 一样,也是String类型元素的集合,且不允许重复的成员
# ===================================================
# zadd 将一个或多个成员元素及其分数值加入到有序集当中。
# zrange 返回有序集中,指定区间内的成员
# ===================================================
127.0.0.1:6379> zadd myset 1 "one"
(integer) 1
127.0.0.1:6379> zadd myset 2 "two" 3 "three"
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
# ===================================================
# zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)
次序排列。
# ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
# Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列
1) "xiaohong"
2) "5000"
3) "xiaoming"
4) "2500"
5) "kuangshen"
6) "500"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES # 显示工资 <=2500
的所有成员
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
# ===================================================
# zrem 移除有序集中的一个或多个成员
# ===================================================
127.0.0.1:6379> ZRANGE salary 0 -1
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> zrem salary kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaohong"
# ===================================================
# zcard 命令用于计算集合中元素的数量。
# ===================================================
127.0.0.1:6379> zcard salary
(integer) 2
OK
# ===================================================
# zcount 计算有序集合中指定分数区间的成员数量。
# ===================================================
127.0.0.1:6379> zadd myset 1 "hello"
(integer) 1
127.0.0.1:6379> zadd myset 2 "world" 3 "kuangshen"
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 2
(integer) 2
# ===================================================
# zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
# ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> zrank salary kuangshen # 显示 kuangshen 的薪水排名,最少
(integer) 0
127.0.0.1:6379> zrank salary xiaohong # 显示 xiaohong 的薪水排名,第三
(integer) 2
# ===================================================
# zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
# ===================================================
127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三
(integer) 2
127.0.0.1:6379> ZREVRANK salary xiaohong # 小红第一
(integer) 0
# zadd 将一个或多个成员元素及其分数值加入到有序集当中。
# zrange 返回有序集中,指定区间内的成员
# ===================================================
127.0.0.1:6379> zadd myset 1 "one"
(integer) 1
127.0.0.1:6379> zadd myset 2 "two" 3 "three"
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
# ===================================================
# zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)
次序排列。
# ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
# Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列
1) "xiaohong"
2) "5000"
3) "xiaoming"
4) "2500"
5) "kuangshen"
6) "500"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES # 显示工资 <=2500
的所有成员
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
# ===================================================
# zrem 移除有序集中的一个或多个成员
# ===================================================
127.0.0.1:6379> ZRANGE salary 0 -1
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> zrem salary kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaohong"
# ===================================================
# zcard 命令用于计算集合中元素的数量。
# ===================================================
127.0.0.1:6379> zcard salary
(integer) 2
OK
# ===================================================
# zcount 计算有序集合中指定分数区间的成员数量。
# ===================================================
127.0.0.1:6379> zadd myset 1 "hello"
(integer) 1
127.0.0.1:6379> zadd myset 2 "world" 3 "kuangshen"
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 2
(integer) 2
# ===================================================
# zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
# ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> zrank salary kuangshen # 显示 kuangshen 的薪水排名,最少
(integer) 0
127.0.0.1:6379> zrank salary xiaohong # 显示 xiaohong 的薪水排名,第三
(integer) 2
# ===================================================
# zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
# ===================================================
127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三
(integer) 2
127.0.0.1:6379> ZREVRANK salary xiaohong # 小红第一
(integer) 0
三种特殊数据类型
GEO地理位置
GEO 的数据结构总共有六个常用命令:geoadd、geopos、geodist、georadius、
georadiusbymember、gethash
georadiusbymember、gethash
geoadd
127.0.0.1:6379> geoadd china:city 116.23 40.22 北京
(integer) 1
127.0.0.1:6379> geoadd china:city 121.48 31.40 上海 113.88 22.55 深圳 120.21
30.20 杭州
(integer) 3
127.0.0.1:6379> geoadd china:city 106.54 29.40 重庆 108.93 34.23 西安 114.02
30.58 武汉
(integer) 3
(integer) 1
127.0.0.1:6379> geoadd china:city 121.48 31.40 上海 113.88 22.55 深圳 120.21
30.20 杭州
(integer) 3
127.0.0.1:6379> geoadd china:city 106.54 29.40 重庆 108.93 34.23 西安 114.02
30.58 武汉
(integer) 3
geopos
127.0.0.1:6379> geopos china:city 北京
1) 1) "116.23000055551528931"
2) "40.2200010338739844"
127.0.0.1:6379> geopos china:city 上海 重庆
1) 1) "121.48000091314315796"
2) "31.40000025319353938"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"
127.0.0.1:6379> geopos china:city 新疆
1) (nil)
1) 1) "116.23000055551528931"
2) "40.2200010338739844"
127.0.0.1:6379> geopos china:city 上海 重庆
1) 1) "121.48000091314315796"
2) "31.40000025319353938"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"
127.0.0.1:6379> geopos china:city 新疆
1) (nil)
geodist
127.0.0.1:6379> geodist china:city 北京 上海
"1088785.4302"
127.0.0.1:6379> geodist china:city 北京 上海 km
"1088.7854"
127.0.0.1:6379> geodist china:city 重庆 北京 km
"1491.6716"
"1088785.4302"
127.0.0.1:6379> geodist china:city 北京 上海 km
"1088.7854"
127.0.0.1:6379> geodist china:city 重庆 北京 km
"1491.6716"
georadius
重新连接 redis-cli,增加参数 --raw ,可以强制输出中文,不然会乱码
[root@kuangshen bin]# redis-cli --raw -p 6379
# 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
127.0.0.1:6379> georadius china:city 100 30 1000 km
重庆
西安
# withdist 返回位置名称和中心距离
127.0.0.1:6379> georadius china:city 100 30 1000 km withdist
重庆
635.2850
西安
963.3171
# withcoord 返回位置名称和经纬度
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord
重庆
106.54000014066696167
29.39999880018641676
西安
108.92999857664108276
34.23000121926852302
# withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
1
重庆
635.2850
106.54000014066696167
29.39999880018641676
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
2
重庆
635.2850
106.54000014066696167
29.39999880018641676
西安
963.3171
108.92999857664108276
34.23000121926852302
# 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
127.0.0.1:6379> georadius china:city 100 30 1000 km
重庆
西安
# withdist 返回位置名称和中心距离
127.0.0.1:6379> georadius china:city 100 30 1000 km withdist
重庆
635.2850
西安
963.3171
# withcoord 返回位置名称和经纬度
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord
重庆
106.54000014066696167
29.39999880018641676
西安
108.92999857664108276
34.23000121926852302
# withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
1
重庆
635.2850
106.54000014066696167
29.39999880018641676
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
2
重庆
635.2850
106.54000014066696167
29.39999880018641676
西安
963.3171
108.92999857664108276
34.23000121926852302
georadiusbymember
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 北京 1000 km
北京
西安
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 上海 400 km
杭州
上海
北京
西安
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 上海 400 km
杭州
上海
geohash
127.0.0.1:6379> geohash china:city 北京 重庆
wx4sucu47r0
wm5z22h53v0
127.0.0.1:6379> geohash china:city 北京 上海
wx4sucu47r0
wtw6sk5n300
wx4sucu47r0
wm5z22h53v0
127.0.0.1:6379> geohash china:city 北京 上海
wx4sucu47r0
wtw6sk5n300
zrem
127.0.0.1:6379> geoadd china:city 116.23 40.22 beijin
1
127.0.0.1:6379> zrange china:city 0 -1 # 查看全部的元素
重庆
西安
深圳
武汉
杭州
上海
beijin
北京
127.0.0.1:6379> zrem china:city beijin # 移除元素
1
127.0.0.1:6379> zrem china:city 北京 # 移除元素
1
127.0.0.1:6379> zrange china:city 0 -1
重庆
西安
深圳
武汉
杭州
上海
1
127.0.0.1:6379> zrange china:city 0 -1 # 查看全部的元素
重庆
西安
深圳
武汉
杭州
上海
beijin
北京
127.0.0.1:6379> zrem china:city beijin # 移除元素
1
127.0.0.1:6379> zrem china:city 北京 # 移除元素
1
127.0.0.1:6379> zrange china:city 0 -1
重庆
西安
深圳
武汉
杭州
上海
HyperLogLog
简介
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积
非常非常大时,计算基数所需的空间总是固定 的、并且是很小的
非常非常大时,计算基数所需的空间总是固定 的、并且是很小的
HyperLogLog则是一种算法,它提供了不精确的去重计数方案。
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。
127.0.0.1:6379> PFADD mykey a b c d e f g h i j
1
127.0.0.1:6379> PFCOUNT mykey
10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
1
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
15
1
127.0.0.1:6379> PFCOUNT mykey
10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
1
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
15
BitMap
概述
Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1
操作
setbit
# 使用 bitmap 来记录上述事例中一周的打卡记录如下所示:
# 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)
127.0.0.1:6379> setbit sign 0 1
0
127.0.0.1:6379> setbit sign 1 0
0
127.0.0.1:6379> setbit sign 2 0
0
127.0.0.1:6379> setbit sign 3 1
0
127.0.0.1:6379> setbit sign 4 1
0
127.0.0.1:6379> setbit sign 5 0
0
127.0.0.1:6379> setbit sign 6 0
# 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)
127.0.0.1:6379> setbit sign 0 1
0
127.0.0.1:6379> setbit sign 1 0
0
127.0.0.1:6379> setbit sign 2 0
0
127.0.0.1:6379> setbit sign 3 1
0
127.0.0.1:6379> setbit sign 4 1
0
127.0.0.1:6379> setbit sign 5 0
0
127.0.0.1:6379> setbit sign 6 0
getbit
127.0.0.1:6379> getbit sign 3 # 查看周四是否打卡
1
127.0.0.1:6379> getbit sign 6 # 查看周七是否打卡
0
1
127.0.0.1:6379> getbit sign 6 # 查看周七是否打卡
0
bitcount统计操作
# 统计这周打卡的记录,可以看到只有3天是打卡的状态:
127.0.0.1:6379> bitcount sign
3
127.0.0.1:6379> bitcount sign
3
Redis.conf
熟悉基本配置
Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf
unit单位
1、配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
2、对 大小写 不敏感
NETWORK 网络配置
bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式
port 6379 # 默认端口
protected-mode yes # 保护模式
port 6379 # 默认端口
GENERAL 通用
daemonize yes # 默认情况下,Redis不作为守护进程运行。需要开启的话,改为 yes
supervised no # 可通过upstart和systemd管理Redis守护进程
pidfile /var/run/redis_6379.pid # 以后台进程方式运行redis,则需要指定pid 文件
loglevel notice # 日志级别。可选项有:
# debug(记录大量日志信息,适用于开发、测试阶段);
# verbose(较多日志信息)
# notice(适量日志信息,使用于生产环境);
# warning(仅有部分重要、关键信息才会被记录)。
logfile "" # 日志文件的位置,当指定为空字符串时,为标准输出
databases 16 # 设置数据库的数目。默认的数据库是DB 0
always-show-logo yes # 是否总是显示logo
supervised no # 可通过upstart和systemd管理Redis守护进程
pidfile /var/run/redis_6379.pid # 以后台进程方式运行redis,则需要指定pid 文件
loglevel notice # 日志级别。可选项有:
# debug(记录大量日志信息,适用于开发、测试阶段);
# verbose(较多日志信息)
# notice(适量日志信息,使用于生产环境);
# warning(仅有部分重要、关键信息才会被记录)。
logfile "" # 日志文件的位置,当指定为空字符串时,为标准输出
databases 16 # 设置数据库的数目。默认的数据库是DB 0
always-show-logo yes # 是否总是显示logo
SNAPSHOPTING快照
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
save 900 1
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
save 300 10
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 60 10000
stop-writes-on-bgsave-error yes # 持久化出现错误后,是否依然进行继续进行工作
rdbcompression yes # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压
缩,需要更多的磁盘空间
rdbchecksum yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时
候,会有大概10%的性能损耗
dbfilename dump.rdb # dbfilenamerdb文件名称
dir ./ # dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
save 900 1
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
save 300 10
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 60 10000
stop-writes-on-bgsave-error yes # 持久化出现错误后,是否依然进行继续进行工作
rdbcompression yes # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压
缩,需要更多的磁盘空间
rdbchecksum yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时
候,会有大概10%的性能损耗
dbfilename dump.rdb # dbfilenamerdb文件名称
dir ./ # dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
SECURITY安全
# 启动redis
# 连接客户端
# 获得和设置密码
config get requirepass
config set requirepass "123456"
#测试ping,发现需要验证
127.0.0.1:6379> ping
NOAUTH Authentication required.
# 验证
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
# 连接客户端
# 获得和设置密码
config get requirepass
config set requirepass "123456"
#测试ping,发现需要验证
127.0.0.1:6379> ping
NOAUTH Authentication required.
# 验证
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
applyonly
appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种
方式在许多应用中已经足够用了
appendfilename "appendonly.aof" # appendfilename AOF 文件名称
appendfsync everysec # appendfsync aof持久化策略的配置
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
# always表示每次写入都执行fsync,以保证数据同步到磁盘。
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
方式在许多应用中已经足够用了
appendfilename "appendonly.aof" # appendfilename AOF 文件名称
appendfsync everysec # appendfsync aof持久化策略的配置
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
# always表示每次写入都执行fsync,以保证数据同步到磁盘。
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
限制
maxclients 10000 # 设置能连上redis的最大客户端连接数量
maxmemory <bytes> # redis配置的最大内存容量
maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略
#volatile-lru:利用LRU算法移除设置过过期时间的key。
#volatile-random:随机移除设置过过期时间的key。
#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
#allkeys-lru:利用LRU算法移除任何key。
#allkeys-random:随机移除任何key。
#noeviction:不移除任何key,只是返回一个写错误。
maxmemory <bytes> # redis配置的最大内存容量
maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略
#volatile-lru:利用LRU算法移除设置过过期时间的key。
#volatile-random:随机移除设置过过期时间的key。
#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
#allkeys-lru:利用LRU算法移除任何key。
#allkeys-random:随机移除任何key。
#noeviction:不移除任何key,只是返回一个写错误。
常用配置介绍
Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no
当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指
定
定
pidfile /var/run/redis.pid
指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认
端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
绑定的主机地址
bind 127.0.0.1
当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为
verbose
verbose
loglevel verbose
日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方
式为标准输出,则日志将会发送给/dev/null
式为标准输出,则日志将会发送给/dev/null
logfile stdout
设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
database 16
指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更
改。
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更
改。
指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时
间,可以关闭该选项,但会导致数据库文件变的巨大
间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
指定本地数据库存放目录
dir ./
设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master
进行数据同步
slaveof
当master服务设置了密码保护时,slav服务连接master的密码
master auth
设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,
默认关闭
requirepass foobared
设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可
以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,
Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝
试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,
但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,
但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory
指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不
开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来
同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将
访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中
访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中
vm-enabled no
虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据
都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有
value都存在于磁盘。默认值为0
vm-max-memory 0
Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多
个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page
大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用
默认值
个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page
大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用
默认值
vm-page-size 32
设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中
的,,在磁盘上每8个pages将消耗1byte的内存。
的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都
是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
hash-max-zipmap-value 512
指定是否激活重置哈希,默认为开启
activerehashing yes
指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各
个实例又拥有自己的特定配置文件
include /path/to/local.conf
Redis的持久化
RDB(Redis Database)
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快
照文件直接读到内存里。
照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程
都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那
RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那
RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Rdb 保存的是 dump.rdb 文件
如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以
如何恢复
1、将备份文件(dump.rdb)移动到redis安装目录并启动服务即可
2、CONFIG GET dir 获取目录
2、CONFIG GET dir 获取目录
127.0.0.1:6379> config get dir
dir
/usr/local/bin
dir
/usr/local/bin
优点
1、适合大规模的数据恢复
2、对数据完整性和一致性要求不高
2、对数据完整性和一致性要求不高
缺点
1、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
2、Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。
2、Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。
AOF(Append ONLY File)
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件
但不可以改写文件,redis启动之初会读取该文件重新构建数据
AOF保存的是appendonly.aof
但不可以改写文件,redis启动之初会读取该文件重新构建数据
AOF保存的是appendonly.aof
配置
appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这
种方式在许多应用中已经足够用了
appendfilename "appendonly.aof" # appendfilename AOF 文件名称
appendfsync everysec # appendfsync aof持久化策略的配置
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
# always表示每次写入都执行fsync,以保证数据同步到磁盘。
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安
全性
Auto-aof-rewrite-min-size # 设置重写的基准值
Auto-aof-rewrite-percentage #设置重写的基准值
种方式在许多应用中已经足够用了
appendfilename "appendonly.aof" # appendfilename AOF 文件名称
appendfsync everysec # appendfsync aof持久化策略的配置
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
# always表示每次写入都执行fsync,以保证数据同步到磁盘。
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安
全性
Auto-aof-rewrite-min-size # 设置重写的基准值
Auto-aof-rewrite-percentage #设置重写的基准值
AOF恢复
正常恢复:
启动:设置Yes,修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复:
启动:设置Yes
故意破坏 appendonly.aof 文件!
修复: redis-check-aof --fix appendonly.aof 进行修复
恢复:重启 redis 然后重新加载
启动:设置Yes,修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复:
启动:设置Yes
故意破坏 appendonly.aof 文件!
修复: redis-check-aof --fix appendonly.aof 进行修复
恢复:重启 redis 然后重新加载
Rewrite
是什么
AOF 采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小
超过所设定的阈值时,Redis 就会启动AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以
使用命令 bgrewriteaof !
超过所设定的阈值时,Redis 就会启动AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以
使用命令 bgrewriteaof !
重写原理
AOF 文件持续增长而过大时,会增加出一条新进程来将文件重写
(也是先写临时文件最后再rename),遍历新进程的内存中数据,
每条记录有一条的Set语句。重写aof文件的操作
并没有读取旧的aof文件,这点和快照有点类似!
(也是先写临时文件最后再rename),遍历新进程的内存中数据,
每条记录有一条的Set语句。重写aof文件的操作
并没有读取旧的aof文件,这点和快照有点类似!
触发机制
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍
且文件大于64M的触发。
且文件大于64M的触发。
优点
1、每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差
但数据完整性比较好
2、每秒同步: appendfsync everysec 异步操作,每秒记录 ,如果一秒内宕机,有数据丢失
3、不同步: appendfsync no 从不同步
但数据完整性比较好
2、每秒同步: appendfsync everysec 异步操作,每秒记录 ,如果一秒内宕机,有数据丢失
3、不同步: appendfsync no 从不同步
缺点
1、相同数据集的数据而言,aof 文件要远大于 rdb文件,恢复速度慢于 rdb。
2、Aof 运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和rdb相同
2、Aof 运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和rdb相同
总结
1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始
的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重
写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF
文件保存的数据集要比RDB文件保存的数据集要完整。
RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者
建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有
AOF可能潜在的Bug,留着作为一个万一的手段。
性能建议
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够
了,只保留 save 900 1 这条规则。
如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自
己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产
生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite
的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重
写可以改到适当的数值。
如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也
减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,
启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。
了,只保留 save 900 1 这条规则。
如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自
己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产
生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite
的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重
写可以改到适当的数值。
如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也
减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,
启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。
Redis事务
理论
概念
Redis事务的概念:
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列
化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事
务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列
化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事
务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
Redis事务没有隔离级别的概念:
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!
Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其
余的命令仍会被执行。
余的命令仍会被执行。
Redis事务的三个阶段
开始事务
命令入队
执行事务
命令入队
执行事务
Redis事务相关命令
watch key1 key2 ... #监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则
事务被打断 ( 类似乐观锁 )
multi # 标记一个事务块的开始( queued )
exec # 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard # 取消事务,放弃事务块中的所有命令
unwatch # 取消watch对所有key的监控
实践
正常执行
MULTI-->开启事务
set k1 v1
set k2 v2
get k2
set k3 v3
exec-->执行事务
set k1 v1
set k2 v2
get k2
set k3 v3
exec-->执行事务
放弃事务
MULTI-->开启事务
set k1 v1
set k2 v2
DISCARD
get k2
set k1 v1
set k2 v2
DISCARD
get k2
若在事务队列中存在命令性错误(类似于java编译性错误),
则执行EXEC命令时,所有命令都不会执行
则执行EXEC命令时,所有命令都不会执行
若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确
命令会被执行,错误命令抛出异常。
命令会被执行,错误命令抛出异常。
Watch 监控
悲观锁
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在
拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就
用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。
拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就
用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。
乐观锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会
上锁。但是在更新的时候会判断一下再此期间别人有没有去更新这个数据,可以使用版本号等机制,乐
观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能
执行更新
测试
1、初始化信用卡可用余额和欠额
127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> set debt 0
OK
2、使用watch检测balance,事务期间balance数据未变动,事务执行成功
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
3、使用watch检测balance,事务期间balance数据变动,事务执行失败!
# 窗口一
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI # 执行完毕后,执行窗口二代码测试
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 修改失败!
(nil)
# 窗口二
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 200
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI # 执行完毕后,执行窗口二代码测试
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 修改失败!
(nil)
# 窗口二
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 200
OK
# 窗口一:出现问题后放弃监视,然后重来!
127.0.0.1:6379> UNWATCH # 放弃监视
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 成功!
1) (integer) 180
2) (integer) 40
127.0.0.1:6379> UNWATCH # 放弃监视
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 成功!
1) (integer) 180
2) (integer) 40
小结
watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端
更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事
务执行失败。
更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事
务执行失败。
发布订阅
是什么
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
Redis 客户端可以订阅任意数量的频道。
测试
创建了订阅频道名为 redisChat:
redis 127.0.0.1:6379> SUBSCRIBE redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收
到消息
到消息
redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Redis"
(integer) 1
redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Kuangshen"
(integer) 1
# 订阅者的客户端会显示如下消息
1) "message"
2) "redisChat"
3) "Hello,Redis"
1) "message"
2) "redisChat"
3) "Hello,Kuangshen"
(integer) 1
redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Kuangshen"
(integer) 1
# 订阅者的客户端会显示如下消息
1) "message"
2) "redisChat"
3) "Hello,Redis"
1) "message"
2) "redisChat"
3) "Hello,Kuangshen"
原理
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel
,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关
键,就是将客户端添加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel
字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
使用场景
Pub/Sub构建实时消息系统
Redis的Pub/Sub系统可以构建实时的消息系统
比如很多用Pub/Sub构建的实时聊天系统的例子
Redis的Pub/Sub系统可以构建实时的消息系统
比如很多用Pub/Sub构建的实时聊天系统的例子
Redis主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点
(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
Master以写为主,Slave 以读为主。
(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
Master以写为主,Slave 以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从
节点只能有一个主节点。
节点只能有一个主节点。
作用
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务
的冗余。
的冗余。
3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务
(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写
少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写
少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
4、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是
Redis高可用的基础。
Redis高可用的基础。
环境配置
基本配置
配从库不配主库,从库配置
slaveof 主库ip 主库端口 # 配置主从
Info replication # 查看信息
slaveof 主库ip 主库端口 # 配置主从
Info replication # 查看信息
每次与 master 断开之后,都需要重新连接,除非你配置进 redis.conf 文件!
修改配置文件
1、拷贝每一个redis的redis.conf 文件
2、指定端口 6379,依次类推
3、开启daemonize yes
4、Pid文件名字 pidfile /var/run/redis_6379.pid , 依次类推
5、Log文件名字 logfile "6379.log" , 依次类推
6、Dump.rdb 名字 dbfilename dump6379.rdb , 依次类推
3、开启daemonize yes
4、Pid文件名字 pidfile /var/run/redis_6379.pid , 依次类推
5、Log文件名字 logfile "6379.log" , 依次类推
6、Dump.rdb 名字 dbfilename dump6379.rdb , 依次类推
一主二从
测试一:主机挂了,查看从机信息,主机恢复,再次查看信息
测试二:从机挂了,查看主机信息,从机恢复,查看从机信息
层层链路
上一个Slave 可以是下一个slave 和 Master,Slave 同样可以接收其他 slaves 的连接和同步请求,那么
该 slave 作为了链条中下一个的master,可以有效减轻 master 的写压力
该 slave 作为了链条中下一个的master,可以有效减轻 master 的写压力
测试:6379 设置值以后 6380 和 6381 都可以获取到!OK
谋朝篡位
一主二从的情况下,如果主机断了,从机可以使用命令 SLAVEOF NO ONE 将自己改为主机!这个时
候其余的从机链接到这个节点。对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器
关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
候其余的从机链接到这个节点。对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器
关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
主机再回来,也只是一个光杆司令了,从机为了正常使用跑到了新的主机上!
复制原理
Slave 启动成功连接到 master 后会发送一个sync命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行
完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行
完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
哨兵模式
概述
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工
干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑
哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题
干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑
哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题
原理
哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例
作用
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服
务器,修改配置文件,让它们切换主机。
多哨兵
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认
为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一
定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为
客观下线。
为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一
定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为
客观下线。
测试
1、调整结构,6379带着80、81
2、自定义的 /myredis 目录下新建 sentinel.conf 文件,名字千万不要错
3、配置哨兵,填写内容
sentinel monitor 被监控主机名字 127.0.0.1 6379 1
上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机
4、启动哨兵
Redis-sentinel /myredis/sentinel.conf
上述目录依照各自的实际情况配置,可能目录不同
5、正常主从演示
6、原有的Master 挂了
7、投票新选
8、重新主从继续开工,info replication 查查看
2、自定义的 /myredis 目录下新建 sentinel.conf 文件,名字千万不要错
3、配置哨兵,填写内容
sentinel monitor 被监控主机名字 127.0.0.1 6379 1
上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机
4、启动哨兵
Redis-sentinel /myredis/sentinel.conf
上述目录依照各自的实际情况配置,可能目录不同
5、正常主从演示
6、原有的Master 挂了
7、投票新选
8、重新主从继续开工,info replication 查查看
问题?
问题:如果之前的master 重启回来,会不会双master 冲突? 之前的回来只能做小弟了
优点
1. 哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。
2. 主从可以切换,故障可以转移,系统可用性更好。
3. 哨兵模式是主从模式的升级,系统更健壮,可用性更高。
2. 主从可以切换,故障可以转移,系统可用性更好。
3. 哨兵模式是主从模式的升级,系统更健壮,可用性更高。
缺点
1. Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
2. 实现哨兵模式的配置也不简单,甚至可以说有些繁琐
哨兵配置说明
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都
要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同
步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的
master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超
时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮
件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执
行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等
等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常
运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果
sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执
行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master
地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的
slave)通信的
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都
要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同
步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的
master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超
时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮
件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执
行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等
等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常
运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果
sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执
行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master
地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的
slave)通信的
缓存穿透和雪崩
缓存穿透
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于
是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是
都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是
都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则
丢弃,从而避免了对底层存储系统的查询压力
丢弃,从而避免了对底层存储系统的查询压力
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数
据将会从缓存中获取,保护了后端数据源;
据将会从缓存中获取,保护了后端数据源;
存在问题:
1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多
的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于
需要保持一致性的业务会有影响。
的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于
需要保持一致性的业务会有影响。
缓存击穿
概念
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中
对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一
个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访
问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一
个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访
问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布
式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考
验很大。
式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考
验很大。
缓存雪崩
概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效。
解决方案
redis高可用
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然
形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就
是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知
的,很有可能瞬间就把数据库压垮。
形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就
是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知
的,很有可能瞬间就把数据库压垮。
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对
某个key只允许一个线程查询数据和写缓存,其他线程等待。
某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数
据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让
缓存失效的时间点尽量均匀。
据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让
缓存失效的时间点尽量均匀。
Jedis
测试联通
1、新建一个普通的Maven项目
2、导入redis的依赖
2、导入redis的依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
3、编写测试代码
import redis.clients.jedis.Jedis;
public class Ping {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println("连接成功");
//查看服务是否运行
System.out.println("服务正在运行: "+jedis.ping());
}
public class Ping {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println("连接成功");
//查看服务是否运行
System.out.println("服务正在运行: "+jedis.ping());
}
4、启动redis服务
5、启动测试,结果
5、启动测试,结果
连接成功
服务正在运行: PONG
服务正在运行: PONG
常用API
public class TestPassword {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
//验证密码,如果没有设置密码这段代码省略
// jedis.auth("password");
jedis.connect(); //连接
jedis.disconnect(); //断开连接
jedis.flushAll(); //清空所有的key
}
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
//验证密码,如果没有设置密码这段代码省略
// jedis.auth("password");
jedis.connect(); //连接
jedis.disconnect(); //断开连接
jedis.flushAll(); //清空所有的key
}
对key操作的命令
public class TestKey {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增<'username','kuangshen'>的键值
对:"+jedis.set("username", "kuangshen"));
System.out.println("新增<'password','password'>的键值
对:"+jedis.set("password", "password"));
System.out.print("系统中所有的键如下:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除键password:"+jedis.del("password"));
System.out.println("判断键password是否存
在:"+jedis.exists("password"));
System.out.println("查看键username所存储的值的类
型:"+jedis.type("username"));
System.out.println("随机返回key空间的一个:"+jedis.randomKey());
System.out.println("重命名key:"+jedis.rename("username","name"));
System.out.println("取出改后的name:"+jedis.get("name"));
System.out.println("按索引查询:"+jedis.select(0));
System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
}
}
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增<'username','kuangshen'>的键值
对:"+jedis.set("username", "kuangshen"));
System.out.println("新增<'password','password'>的键值
对:"+jedis.set("password", "password"));
System.out.print("系统中所有的键如下:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除键password:"+jedis.del("password"));
System.out.println("判断键password是否存
在:"+jedis.exists("password"));
System.out.println("查看键username所存储的值的类
型:"+jedis.type("username"));
System.out.println("随机返回key空间的一个:"+jedis.randomKey());
System.out.println("重命名key:"+jedis.rename("username","name"));
System.out.println("取出改后的name:"+jedis.get("name"));
System.out.println("按索引查询:"+jedis.select(0));
System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
}
}
对String操作的命令
public class TestString {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("===========增加数据===========");
System.out.println(jedis.set("key1","value1"));
System.out.println(jedis.set("key2","value2"));
System.out.println(jedis.set("key3", "value3"));
System.out.println("删除键key2:"+jedis.del("key2"));
System.out.println("获取键key2:"+jedis.get("key2"));
System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));
System.out.println("获取key1的值:"+jedis.get("key1"));
System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));
System.out.println("key3的值:"+jedis.get("key3"));
System.out.println("增加多个键值
对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));
System.out.println("获取多个键值
对:"+jedis.mget("key01","key02","key03"));
System.out.println("获取多个键值
对:"+jedis.mget("key01","key02","key03","key04"));
System.out.println("删除多个键值对:"+jedis.del("key01","key02"));
System.out.println("获取多个键值
对:"+jedis.mget("key01","key02","key03"));
jedis.flushDB();
System.out.println("===========新增键值对防止覆盖原先值==============");
System.out.println(jedis.setnx("key1", "value1"));
System.out.println(jedis.setnx("key2", "value2"));
System.out.println(jedis.setnx("key2", "value2-new"));
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
System.out.println("===========新增键值对并设置有效时间=============");
System.out.println(jedis.setex("key3", 2, "value3"));
System.out.println(jedis.get("key3"));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(jedis.get("key3"));
System.out.println("===========获取原值,更新为新值==========");
System.out.println(jedis.getSet("key2", "key2GetSet"));
System.out.println(jedis.get("key2"));
System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2,
4));
}
}
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("===========增加数据===========");
System.out.println(jedis.set("key1","value1"));
System.out.println(jedis.set("key2","value2"));
System.out.println(jedis.set("key3", "value3"));
System.out.println("删除键key2:"+jedis.del("key2"));
System.out.println("获取键key2:"+jedis.get("key2"));
System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));
System.out.println("获取key1的值:"+jedis.get("key1"));
System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));
System.out.println("key3的值:"+jedis.get("key3"));
System.out.println("增加多个键值
对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));
System.out.println("获取多个键值
对:"+jedis.mget("key01","key02","key03"));
System.out.println("获取多个键值
对:"+jedis.mget("key01","key02","key03","key04"));
System.out.println("删除多个键值对:"+jedis.del("key01","key02"));
System.out.println("获取多个键值
对:"+jedis.mget("key01","key02","key03"));
jedis.flushDB();
System.out.println("===========新增键值对防止覆盖原先值==============");
System.out.println(jedis.setnx("key1", "value1"));
System.out.println(jedis.setnx("key2", "value2"));
System.out.println(jedis.setnx("key2", "value2-new"));
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
System.out.println("===========新增键值对并设置有效时间=============");
System.out.println(jedis.setex("key3", 2, "value3"));
System.out.println(jedis.get("key3"));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(jedis.get("key3"));
System.out.println("===========获取原值,更新为新值==========");
System.out.println(jedis.getSet("key2", "key2GetSet"));
System.out.println(jedis.get("key2"));
System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2,
4));
}
}
对List
public class TestList {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("===========添加一个list===========");
jedis.lpush("collections", "ArrayList", "Vector", "Stack","HashMap", "WeakHashMap", "LinkedHashMap");
jedis.lpush("collections", "HashSet");
jedis.lpush("collections", "TreeSet");
jedis.lpush("collections", "TreeMap");
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
//-1代表倒数第一个元素,-2代表倒数第二个元素,end为-1表示查询全部
System.out.println("collections区间0-3的元素:"+jedis.lrange("collections",0,3));
System.out.println("===============================");
// 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈
System.out.println("删除指定元素个数:"+jedis.lrem("collections", 2,"HashMap"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("删除下表0-3区间之外的元素:"+jedis.ltrim("collections", 0, 3));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("collections列表出栈(左端):"+jedis.lpop("collections"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("collections添加元素,从列表右端,与lpush相对应:"+jedis.rpush("collections", "EnumMap"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("collections列表出栈(右端):"+jedis.rpop("collections"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("修改collections指定下标1的内容:"+jedis.lset("collections", 1, "LinkedArrayList"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("===============================");
System.out.println("collections的长度:"+jedis.llen("collections"));
System.out.println("获取collections下标为2的元素:"+jedis.lindex("collections", 2));
System.out.println("===============================");
jedis.lpush("sortedList", "3","6","2","0","7","4");
System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0,-1));
System.out.println(jedis.sort("sortedList"));
System.out.println("sortedList排序后:"+jedis.lrange("sortedList", 0,-1));
}
}
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("===========添加一个list===========");
jedis.lpush("collections", "ArrayList", "Vector", "Stack","HashMap", "WeakHashMap", "LinkedHashMap");
jedis.lpush("collections", "HashSet");
jedis.lpush("collections", "TreeSet");
jedis.lpush("collections", "TreeMap");
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
//-1代表倒数第一个元素,-2代表倒数第二个元素,end为-1表示查询全部
System.out.println("collections区间0-3的元素:"+jedis.lrange("collections",0,3));
System.out.println("===============================");
// 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈
System.out.println("删除指定元素个数:"+jedis.lrem("collections", 2,"HashMap"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("删除下表0-3区间之外的元素:"+jedis.ltrim("collections", 0, 3));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("collections列表出栈(左端):"+jedis.lpop("collections"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("collections添加元素,从列表右端,与lpush相对应:"+jedis.rpush("collections", "EnumMap"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("collections列表出栈(右端):"+jedis.rpop("collections"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("修改collections指定下标1的内容:"+jedis.lset("collections", 1, "LinkedArrayList"));
System.out.println("collections的内容:"+jedis.lrange("collections",0, -1));
System.out.println("===============================");
System.out.println("collections的长度:"+jedis.llen("collections"));
System.out.println("获取collections下标为2的元素:"+jedis.lindex("collections", 2));
System.out.println("===============================");
jedis.lpush("sortedList", "3","6","2","0","7","4");
System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0,-1));
System.out.println(jedis.sort("sortedList"));
System.out.println("sortedList排序后:"+jedis.lrange("sortedList", 0,-1));
}
}
对set操作的命令
public class TestSet {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("============向集合中添加元素(不重复)
============");
System.out.println(jedis.sadd("eleSet",
"e1","e2","e4","e3","e0","e8","e7","e5"));
System.out.println(jedis.sadd("eleSet", "e6"));
System.out.println(jedis.sadd("eleSet", "e6"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("删除一个元素e0:"+jedis.srem("eleSet", "e0"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("删除两个元素e7和e6:"+jedis.srem("eleSet","e7","e6"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("eleSet中包含元素的个数:"+jedis.scard("eleSet"));
System.out.println("e3是否在eleSet中:"+jedis.sismember("eleSet","e3"));
System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet","e1"));
System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet","e5"));
System.out.println("=================================");
System.out.println(jedis.sadd("eleSet1","e1","e2","e4","e3","e0","e8","e7","e5"));
System.out.println(jedis.sadd("eleSet2","e1","e2","e4","e3","e0","e8"));
System.out.println("将eleSet1中删除e1并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e1"));//移到集合元素
System.out.println("将eleSet1中删除e2并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e2"));
System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));
System.out.println("============集合运算=================");
System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));
System.out.println("eleSet1和eleSet2的交集:"+jedis.sinter("eleSet1","eleSet2"));
System.out.println("eleSet1和eleSet2的并集:"+jedis.sunion("eleSet1","eleSet2"));
System.out.println("eleSet1和eleSet2的差集:"+jedis.sdiff("eleSet1","eleSet2"));//eleSet1中有,eleSet2中没有
jedis.sinterstore("eleSet4","eleSet1","eleSet2");//求交集并将交集保存到dstkey的集合
System.out.println("eleSet4中的元素:"+jedis.smembers("eleSet4"));
}
}
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("============向集合中添加元素(不重复)
============");
System.out.println(jedis.sadd("eleSet",
"e1","e2","e4","e3","e0","e8","e7","e5"));
System.out.println(jedis.sadd("eleSet", "e6"));
System.out.println(jedis.sadd("eleSet", "e6"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("删除一个元素e0:"+jedis.srem("eleSet", "e0"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("删除两个元素e7和e6:"+jedis.srem("eleSet","e7","e6"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("eleSet中包含元素的个数:"+jedis.scard("eleSet"));
System.out.println("e3是否在eleSet中:"+jedis.sismember("eleSet","e3"));
System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet","e1"));
System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet","e5"));
System.out.println("=================================");
System.out.println(jedis.sadd("eleSet1","e1","e2","e4","e3","e0","e8","e7","e5"));
System.out.println(jedis.sadd("eleSet2","e1","e2","e4","e3","e0","e8"));
System.out.println("将eleSet1中删除e1并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e1"));//移到集合元素
System.out.println("将eleSet1中删除e2并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e2"));
System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));
System.out.println("============集合运算=================");
System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));
System.out.println("eleSet1和eleSet2的交集:"+jedis.sinter("eleSet1","eleSet2"));
System.out.println("eleSet1和eleSet2的并集:"+jedis.sunion("eleSet1","eleSet2"));
System.out.println("eleSet1和eleSet2的差集:"+jedis.sdiff("eleSet1","eleSet2"));//eleSet1中有,eleSet2中没有
jedis.sinterstore("eleSet4","eleSet1","eleSet2");//求交集并将交集保存到dstkey的集合
System.out.println("eleSet4中的元素:"+jedis.smembers("eleSet4"));
}
}
对Hash操作
public class TestHash {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
Map<String,String> map = new HashMap<>();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
//添加名称为hash(key)的hash元素
jedis.hmset("hash",map);
//向名称为hash的hash中添加key为key5,value为value5元素
jedis.hset("hash", "key5", "value5");
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));//return Map<String,String>
System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>
System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 6));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 3));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash","key2"));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
System.out.println("判断hash中是否存在key2:"+jedis.hexists("hash","key2"));
System.out.println("判断hash中是否存在key3:"+jedis.hexists("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key4"));
}
}
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
Map<String,String> map = new HashMap<>();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
//添加名称为hash(key)的hash元素
jedis.hmset("hash",map);
//向名称为hash的hash中添加key为key5,value为value5元素
jedis.hset("hash", "key5", "value5");
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));//return Map<String,String>
System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>
System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 6));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 3));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash","key2"));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
System.out.println("判断hash中是否存在key2:"+jedis.hexists("hash","key2"));
System.out.println("判断hash中是否存在key3:"+jedis.hexists("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key4"));
}
}
事务
基本操作
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestMulti {
public static void main(String[] args) {
//创建客户端连接服务端,redis服务端需要被开启
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "java");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try{
//向redis存入一条数据
multi.set("json", result);
//再存入一条数据
multi.set("json2", result);
//这里引发了异常,用0作为被除数
int i = 100/0;
//如果没有引发异常,执行进入队列的命令
multi.exec();
}catch(Exception e){
e.printStackTrace();
//如果出现异常,回滚
multi.discard();
}finally{
System.out.println(jedis.get("json"));
System.out.println(jedis.get("json2"));
//最终关闭客户端
jedis.close();
}
}
}
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestMulti {
public static void main(String[] args) {
//创建客户端连接服务端,redis服务端需要被开启
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "java");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try{
//向redis存入一条数据
multi.set("json", result);
//再存入一条数据
multi.set("json2", result);
//这里引发了异常,用0作为被除数
int i = 100/0;
//如果没有引发异常,执行进入队列的命令
multi.exec();
}catch(Exception e){
e.printStackTrace();
//如果出现异常,回滚
multi.discard();
}finally{
System.out.println(jedis.get("json"));
System.out.println(jedis.get("json2"));
//最终关闭客户端
jedis.close();
}
}
}
Springboot整合
基础使用
1、 JedisPoolConfig (这个是配置连接池)
2、 RedisConnectionFactory 这个是配置连接信息,这里的RedisConnectionFactory是一个接
口,我们需要使用它的实现类,在SpringData Redis方案中提供了以下四种工厂模型
口,我们需要使用它的实现类,在SpringData Redis方案中提供了以下四种工厂模型
JredisConnectionFactory
JedisConnectionFactory
LettuceConnectionFactory
SrpConnectionFactory
JedisConnectionFactory
LettuceConnectionFactory
SrpConnectionFactory
3、RedisTemplate 基本操作
//1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
//2.yaml基本配置
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0ms
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0ms
测试
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("myKey","myValue");
System.out.println(redisTemplate.opsForValue().get("myKey"));
}
}
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("myKey","myValue");
System.out.println(redisTemplate.opsForValue().get("myKey"));
}
}
封装工具类
1、新建一个SpringBoot项目
2、导入redis的启动器
2、导入redis的启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3、配置redis,可以查看 RedisProperties 分析
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
4、分析 RedisAutoConfiguration 自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class,
JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object>
redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory
redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class,
JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object>
redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory
redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
5、既然自动配置不好用,就重新配置一个RedisTemplate
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new
StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new
StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
6、写一个Redis工具类(直接用RedisTemplate操作Redis,需要很多行代码,因此直接封装好一个
RedisUtils,这样写代码更方便点。这个RedisUtils交给Spring容器实例化,使用时直接注解注入。
RedisUtils,这样写代码更方便点。这个RedisUtils交给Spring容器实例化,使用时直接注解注入。
链接:https://pan.baidu.com/s/19uaB7IjUxKLfnNVarUd8Xg
提取码:p8mv
提取码:p8mv
Docker
初识docker
docker概念
常遇到的问题
概念
Docker 是一个开源的应用容器引擎
诞生于 2013 年初,基于 Go 语言实现, dotCloud 公司出品(后改名为Docker Inc) •
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的
Linux 机器上
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的
Linux 机器上
容器是完全使用沙箱机制,相互隔离
• 容器性能开销极低。
• Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版)
• 容器性能开销极低。
• Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版)
安装docker
Docker可以运行在MAC、Windows、CentOS、UBUNTU等操作系统上,本课程基于CentOS 7 安装
Docker。官网:https://www.docker.com
Docker。官网:https://www.docker.com
docker架构
• 镜像(Image):Docker 镜像(Image),就相当于是
一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包
含了完整的一套 Ubuntu16.04 最小系统的 root 文件系
统。
• 容器(Container):镜像(Image)和容器(Contain
er)的关系,就像是面向对象程序设计中的类和对象一
样,镜像是静态的定义,容器是镜像运行时的实体。容
器可以被创建、启动、停止、删除、暂停等。
• 仓库(Repository):仓库可看成一个代码控制中心,
用来保存镜像。
一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包
含了完整的一套 Ubuntu16.04 最小系统的 root 文件系
统。
• 容器(Container):镜像(Image)和容器(Contain
er)的关系,就像是面向对象程序设计中的类和对象一
样,镜像是静态的定义,容器是镜像运行时的实体。容
器可以被创建、启动、停止、删除、暂停等。
• 仓库(Repository):仓库可看成一个代码控制中心,
用来保存镜像。
配置 Docker 镜像加速器
默认情况下,将来从docker hub(https://hub.docker.com/)上下载
docker镜像,太慢。一般都会配置镜像加速器:
• USTC:中科大镜像加速器(https://docker.mirrors.ustc.edu.cn) • 阿里云
• 网易云
• 腾讯云
docker镜像,太慢。一般都会配置镜像加速器:
• USTC:中科大镜像加速器(https://docker.mirrors.ustc.edu.cn) • 阿里云
• 网易云
• 腾讯云
Docker命令
进程相关命令
• 启动docker服务
systemctl start docker
• 停止docker服务
systemctl stop docker
• 重启docker服务
systemctl restart docker
• 查看docker服务状态
systemctl status docker
• 开机启动docker服务
systemctl enable docker
镜像相关命令
• 查看镜像
docker images
docker images –q # 查看所用镜像的id
docker images –q # 查看所用镜像的id
• 搜索镜像
docker search 镜像名称
• 拉取镜像
从Docker仓库下载镜像到本地,镜像名称格式为 名称:版本号,如果版本号不指定则是最新的版本。
如果不知道镜像版本,可以去docker hub 搜索对应镜像查看
docker pull 镜像名称
如果不知道镜像版本,可以去docker hub 搜索对应镜像查看
docker pull 镜像名称
• 删除镜像
docker rmi 镜像id # 删除指定本地镜像
docker rmi `docker images -q` # 删除所有本地镜像
容器相关命令
查看
docker ps # 查看正在运行的容器
docker ps –a # 查看所有容器
docker ps –a # 查看所有容器
创建
docker run 参数
进入
docker exec 参数 # 退出容器,容器不会关闭
启动
docker start 容器名称
停止
docker stop 容器名称
删除
docker rm 容器名称
查看容器信息
docker inspect 容器名称
Docker 容器数据卷
数据卷概念及作用
思考
Docker 容器删除后,在容器中产生的数据还在吗?
Docker 容器和外部机器可以直接交换文件吗?
Docker 容器和外部机器可以直接交换文件吗?
数据卷是宿主机中的一个目录或文件
当容器目录和数据卷目录绑定后,对方的修改会立即同步
一个数据卷可以被多个容器同时挂载
一个容器也可以被挂载多个数据卷
当容器目录和数据卷目录绑定后,对方的修改会立即同步
一个数据卷可以被多个容器同时挂载
一个容器也可以被挂载多个数据卷
作用
数据卷作用
• 容器数据持久化
• 外部机器和容器间接通信
• 容器之间数据交换
• 容器数据持久化
• 外部机器和容器间接通信
• 容器之间数据交换
配置数据卷
创建启动容器时,使用 –v 参数 设置数据卷
docker run ... –v 宿主机目录(文件):容器内目录(文件) ...
配置数据卷容器
数据卷容器
多容器进行数据交换
1. 多个容器挂载同一个数据卷
2. 数据卷容器
1. 多个容器挂载同一个数据卷
2. 数据卷容器
1. 创建启动c3数据卷容器,使用 –v 参数 设置数据卷
docker run –it --name=c3 –v /volume centos:7 /bin/bash
2. 创建启动 c1 c2 容器,使用 –-volumes-from 参数 设置数据卷
docker run –it --name=c1 --volumes-from c3 centos:7 /bin/bash
docker run –it --name=c2 --volumes-from c3 centos:7 /bin/bash
docker run –it --name=c2 --volumes-from c3 centos:7 /bin/bash
Docker 应用部署
MySQL部署
在Docker容器中部署MySQL,并通过外部mysql客户端操作MySQL Server。
① 搜索mysql镜像
容器内的网络服务和外部机器不能直接通信
• 外部机器和宿主机可以直接通信
• 宿主机和容器可以直接通信
• 当容器中的网络服务需要被外部机器访问时,可以将容器中提供服务的端口映射到宿主机的端口上。外部机
器访问宿主机的该端口,从而间接访问容器的服务。
• 这种操作称为:端口映射
② 拉取mysql镜像
③ 创建容器
④ 操作容器中的mysql
Tomcat部署
① 搜索tomcat镜像
② 拉取tomcat镜像
③ 创建容器
④ 部署项目
⑤ 测试访问
② 拉取tomcat镜像
③ 创建容器
④ 部署项目
⑤ 测试访问
Nginx部署
① 搜索Nginx镜像
② 拉取Nginx镜像
③ 创建容器
④ 测试访问
② 拉取Nginx镜像
③ 创建容器
④ 测试访问
Redis部署
① 搜索Redis镜像
② 拉取Redis镜像
③ 创建容器
④ 测试访问
Dockerfile
Docker 镜像原理
操作系统组成部分
• 进程调度子系统
• 进程通信子系统
• 内存管理子系统
• 设备管理子系统
• 文件管理子系统
• 网络通信子系统
• 作业控制子系统
• 进程通信子系统
• 内存管理子系统
• 设备管理子系统
• 文件管理子系统
• 网络通信子系统
• 作业控制子系统
Linux文件系统由bootfs和rootfs两部分组成
bootfs:包含bootloader(引导加载程序)和 kernel(内核)
rootfs: root文件系统,包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc等标准目录和文件
rootfs: root文件系统,包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc等标准目录和文件
不同的linux发行版,bootfs基本一样,而rootfs不同,如ubuntu
,centos等
,centos等
Docker镜像是由特殊的文件系统叠加而成
• 最底端是 bootfs,并使用宿主机的bootfs
• 第二层是 root文件系统rootfs,称为base image
• 然后再往上可以叠加其他的镜像文件
• 统一文件系统(Union File System)技术能够将不同的
层整合成一个文件系统,为这些层提供了一个统一的视角
,这样就隐藏了多层的存在,在用户的角度看来,只存在
一个文件系统。
• 一个镜像可以放在另一个镜像的上面。位于下面的镜像称
为父镜像,最底部的镜像成为基础镜像。
• 当从一个镜像启动容器时,Docker会在最顶层加载一个读
写文件系统作为容器
• 最底端是 bootfs,并使用宿主机的bootfs
• 第二层是 root文件系统rootfs,称为base image
• 然后再往上可以叠加其他的镜像文件
• 统一文件系统(Union File System)技术能够将不同的
层整合成一个文件系统,为这些层提供了一个统一的视角
,这样就隐藏了多层的存在,在用户的角度看来,只存在
一个文件系统。
• 一个镜像可以放在另一个镜像的上面。位于下面的镜像称
为父镜像,最底部的镜像成为基础镜像。
• 当从一个镜像启动容器时,Docker会在最顶层加载一个读
写文件系统作为容器
镜像制作
容器转为镜像
docker commit 容器id 镜像名称:版本号
docker save -o 压缩文件名称 镜像名称:版本号
docker load –i 压缩文件名称
Dockerfile概念及作用
dockerfile
• Dockerfile 是一个文本文件
• 包含了一条条的指令
• 每一条指令构建一层,基于基础镜像,最终构建出一个新的镜像
• 对于开发人员:可以为开发团队提供一个完全一致的开发环境
• 对于测试人员:可以直接拿开发时所构建的镜像或者通过Dockerfile文件
构建一个新的镜像开始工作了
• 对于运维人员:在部署时,可以实现应用的无缝移植
• 包含了一条条的指令
• 每一条指令构建一层,基于基础镜像,最终构建出一个新的镜像
• 对于开发人员:可以为开发团队提供一个完全一致的开发环境
• 对于测试人员:可以直接拿开发时所构建的镜像或者通过Dockerfile文件
构建一个新的镜像开始工作了
• 对于运维人员:在部署时,可以实现应用的无缝移植
Dochub网址:https://hub.docker.com
Dockerfile关键字
Dockerfile 关键字
查看 文档目录中的《dockerfile.md》
案例
需求
自定义centos7镜像。要求:
1. 默认登录路径为 /usr
2. 可以使用vim
1. 默认登录路径为 /usr
2. 可以使用vim
① 定义父镜像:FROM centos:7
② 定义作者信息:MAINTAINER itheima <itheima@itcast.cn>
③ 执行安装vim命令: RUN yum install -y vim
④ 定义默认的工作目录:WORKDIR /usr
⑤ 定义容器启动执行的命令:CMD /bin/bash
⑥ 通过dockerfile构建镜像:docker bulid –f dockerfile文件路径 –t 镜像名称:版本
② 定义作者信息:MAINTAINER itheima <itheima@itcast.cn>
③ 执行安装vim命令: RUN yum install -y vim
④ 定义默认的工作目录:WORKDIR /usr
⑤ 定义容器启动执行的命令:CMD /bin/bash
⑥ 通过dockerfile构建镜像:docker bulid –f dockerfile文件路径 –t 镜像名称:版本
Docker 服务编排
微服务架构的应用系统中一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停
,维护的工作量会很大。
• 要从Dockerfile build image 或者去dockerhub拉取image
• 要创建多个container
• 要管理这些container(启动停止删除)
,维护的工作量会很大。
• 要从Dockerfile build image 或者去dockerhub拉取image
• 要创建多个container
• 要管理这些container(启动停止删除)
服务编排: 按照一定的业务规则批量管理容器
Docker Compose是一个编排多容器分布式部署的工具,提供命令集管理容器化应用的完整开发周期,包括服务构建
,启动和停止。使用步骤:
,启动和停止。使用步骤:
1. 利用 Dockerfile 定义运行环境镜像
2. 使用 docker-compose.yml 定义组成应用的各服务
3. 运行 docker-compose up 启动应用
2. 使用 docker-compose.yml 定义组成应用的各服务
3. 运行 docker-compose up 启动应用
Docker 私有仓库
搭建私有仓库
Docker官方的Docker hub(https://hub.docker.com)是一个用于管理公共镜像的仓库,我们可以从上面拉取镜像
到本地,也可以把我们自己的镜像推送上去。但是,有时候我们的服务器无法访问互联网,或者你不希望将自己的镜
像放到公网当中,那么我们就需要搭建自己的私有仓库来存储和管理自己的镜像。
到本地,也可以把我们自己的镜像推送上去。但是,有时候我们的服务器无法访问互联网,或者你不希望将自己的镜
像放到公网当中,那么我们就需要搭建自己的私有仓库来存储和管理自己的镜像。
上传镜像到私有仓库
从私有仓库拉取镜像
Docker相关概念
docker容器虚拟化 与 传统虚拟机比较
MongoDB
MongoDB相关概念
业务应用场景
MongoDB简介
体系结构
MySQL与MongoDB对比
数据模型
介绍
数据参考类型
MongoDB特点
高性能
1.MongoDB提供高性能的数据持久性。特别是对嵌入式数据模型的支持减少了数据库系统上的I/O活动
2.索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。
(文本索引解决搜索的需求,TTL索引解决历史数据自动过期的需求,地理位置索引可用于构建各种O2O应用)
3.mmpav1、wiredtiger、mongorocks(rocksdb)、in-memory等多引擎支持满足各种场景需求
4.Gridfs解决文件存储的需求
2.索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。
(文本索引解决搜索的需求,TTL索引解决历史数据自动过期的需求,地理位置索引可用于构建各种O2O应用)
3.mmpav1、wiredtiger、mongorocks(rocksdb)、in-memory等多引擎支持满足各种场景需求
4.Gridfs解决文件存储的需求
高可用
MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余
高扩展
1.MongoDB提供了水平可扩展性作为其核心功能的一部分
2.分片将数据分部在一组集群的机器上。(海量数据存储,服务能力水平扩展)
3.从3.4开始MongoDB支持基于片键创建数据区域。在一个平衡的集群中,MongoDB将一个
区域所覆盖的读写定向到该区域内的那些片
2.分片将数据分部在一组集群的机器上。(海量数据存储,服务能力水平扩展)
3.从3.4开始MongoDB支持基于片键创建数据区域。在一个平衡的集群中,MongoDB将一个
区域所覆盖的读写定向到该区域内的那些片
丰富的查询支持
MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等
其它特点
无模式(动态模式)、灵活的文档模型
单机部署
windows系统中的安装部署
1.下载
MongoDB官网
2.解压安装启动
将压缩包解压到一个目录中。
在解压目录中,手动建立一个目录用于存放数据文件,如 data/db
在解压目录中,手动建立一个目录用于存放数据文件,如 data/db
shell连接(Mongo命令)
启动方式
方式1:命令行参数方式启动服务
在 bin 目录中打开命令行提示符,输入如下命令:
mongod --dbpath=..\data\db
我们在启动信息中可以看到,mongoDB的默认端口是27017,如果我们想改变默认的启动端口,可以通过–port来指定端口。
为了方便我们每次启动,可以将安装目录的bin目录设置到环境变量的path中, bin 目录下是一些常用命令,比如 mongod 启动服务用的,mongo 客户端连接服务用的。
在 bin 目录中打开命令行提示符,输入如下命令:
mongod --dbpath=..\data\db
我们在启动信息中可以看到,mongoDB的默认端口是27017,如果我们想改变默认的启动端口,可以通过–port来指定端口。
为了方便我们每次启动,可以将安装目录的bin目录设置到环境变量的path中, bin 目录下是一些常用命令,比如 mongod 启动服务用的,mongo 客户端连接服务用的。
方式2:配置文件方式启动服务
在解压目录中新建 config 文件夹,该文件夹中新建配置文件 mongod.conf
在解压目录中新建 config 文件夹,该文件夹中新建配置文件 mongod.conf
storage:
#The directory where the mongod instance stores its data.Default Value is "\data\db" on Windows.
dbPath: D:\mongodb-win32-x86_64-2008plus-ssl-4.0.1\data
#The directory where the mongod instance stores its data.Default Value is "\data\db" on Windows.
dbPath: D:\mongodb-win32-x86_64-2008plus-ssl-4.0.1\data
详细配置项内容
https://docs.mongodb.com/manual/reference/configuration-options/
注意
配置文件中如果使用双引号,比如路径地址,自动会将双引号的内容转义。如果不转义,则会报错:error-parsing-yaml-config-file-yaml-cpp-error-at-line-3-column-15-unknown-escape-character-d
解决:
a. 对 \ 换成 / 或 \
b. 如果路径中没有空格,则无需加引号。
解决:
a. 对 \ 换成 / 或 \
b. 如果路径中没有空格,则无需加引号。
配置文件是以yaml格式写的,所以在具体配置前要一个tab键,否则会报如下错误Unrecognized option: storage
配置文件中不能以Tab分割字段
解决:
将其转换成空格。
启动方式:mongod -f ../config/mongod.conf 或 mongod --config ../config/mongod.conf
更多参数配置:
解决:
将其转换成空格。
启动方式:mongod -f ../config/mongod.conf 或 mongod --config ../config/mongod.conf
更多参数配置:
systemLog:
destination: file
#The path of the log file to which mongod or mongos should send all diagnostic logging information
path: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/log/mongod.log"
logAppend: true
storage:
journal:
enabled: true
#The directory where the mongod instance stores its data.Default Value is "/data/db".
dbPath: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/data"
net:
#bindIp: 127.0.0.1
port: 27017
setParameter:
enableLocalhostAuthBypass: false
destination: file
#The path of the log file to which mongod or mongos should send all diagnostic logging information
path: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/log/mongod.log"
logAppend: true
storage:
journal:
enabled: true
#The directory where the mongod instance stores its data.Default Value is "/data/db".
dbPath: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/data"
net:
#bindIp: 127.0.0.1
port: 27017
setParameter:
enableLocalhostAuthBypass: false
Compass-图形化界面客户端
Navicat也可以做
Navicat也可以做
Linux系统中的安装启动和连接
基本常用命令
案例需求
数据库操作
选择和创建数据库
数据库的删除
集合操作
集合的显示创建
集合的隐式创建
集合的删除
文档基本的CURD
文档的插入
文档的基本查询
文档的更新修改
删除文档
文档的查询分页
统计查询
分页列表查询
排序查询
数据结构
一、初识算法
什么是算法
什么是数据结构
二分查找
子主题
框架
Spring
Spring简介
Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和
AOP(Aspect Oriented Programming:面向切面编程)为内核。
AOP(Aspect Oriented Programming:面向切面编程)为内核。
优势
1)方便解耦,简化开发
通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。
用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2)AOP 编程的支持
通过 Spring的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。
3)声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。
4)方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
5)方便集成各种优秀框架
Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。
6)降低 JavaEE API 的使用难度
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
7)Java 源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深
造诣。它的源代码无意是 Java 技术的最佳实践的范例。
通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。
用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2)AOP 编程的支持
通过 Spring的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。
3)声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。
4)方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
5)方便集成各种优秀框架
Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。
6)降低 JavaEE API 的使用难度
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
7)Java 源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深
造诣。它的源代码无意是 Java 技术的最佳实践的范例。
Spring体系架构
Spring快速入门
Spring程序开发步骤
① 导入 Spring 开发的基本包坐标
② 编写 Dao 接口和实现类
③ 创建 Spring 核心配置文件
④ 在 Spring 配置文件中配置 UserDaoImpl
⑤ 使用 Spring 的 API 获得 Bean 实例
② 编写 Dao 接口和实现类
③ 创建 Spring 核心配置文件
④ 在 Spring 配置文件中配置 UserDaoImpl
⑤ 使用 Spring 的 API 获得 Bean 实例
导入Spring开发的基本包坐标
<properties> <spring.version>5.0.5.RELEASE</spring.version>
</properties> <dependencies>
<!--导入spring的context坐标,context依赖core、beans、expression--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version>
</dependency>
</dependencies>
</properties> <dependencies>
<!--导入spring的context坐标,context依赖core、beans、expression--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version>
</dependency>
</dependencies>
编写Dao接口和实现类
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDao save method running....");
} }
public void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDao save method running....");
} }
创建Spring核心配置文件
在类路径下(resources)创建applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
在Spring配置文件中配置UserDaoImpl
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
</
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
</
使用Spring的API获得Bean实例
@Test
public void test1(){
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
public void test1(){
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
Spring配置文件
Bean标签基本配置
用于配置对象交由Spring 来创建。
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
基本属性:
id:Bean实例在Spring容器中的唯一标识
class:Bean的全限定名称
id:Bean实例在Spring容器中的唯一标识
class:Bean的全限定名称
Bean标签范围配置
1)当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
2)当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
2)当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
Bean的生命周期配置
init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称
destroy-method:指定类中销毁方法名称
Bean实例化三种方式
无参构造方法实例化
它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
工厂静态方法实例化
工厂的静态方法返回Bean实例
public class StaticFactoryBean {
public static UserDao createUserDao(){
return new UserDaoImpl();
} }
工厂的静态方法返回Bean实例
public class StaticFactoryBean {
public static UserDao createUserDao(){
return new UserDaoImpl();
} }
<bean id="userDao" class="com.itheima.factory.StaticFactoryBean"
factory-method="createUserDao" />
factory-method="createUserDao" />
工厂实例方法实例化
工厂的非静态方法返回Bean实例
public class DynamicFactoryBean {
public UserDao createUserDao(){
return new UserDaoImpl();
} }
<bean id="factoryBean" class="com.itheima.factory.DynamicFactoryBean"/> <bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>
public class DynamicFactoryBean {
public UserDao createUserDao(){
return new UserDaoImpl();
} }
<bean id="factoryBean" class="com.itheima.factory.DynamicFactoryBean"/> <bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>
Bean的依赖注入入门
创建 UserService,UserService 内部在调用 UserDao的save() 方法
① 创建 UserService,UserService 内部在调用 UserDao的save() 方法
public class UserServiceImpl implements UserService {
@Override
public void save() {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
} }
② 将 UserServiceImpl 的创建权交给 Spring
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
③ 从 Spring 容器中获得 UserService 进行操作
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.save();
public class UserServiceImpl implements UserService {
@Override
public void save() {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
} }
② 将 UserServiceImpl 的创建权交给 Spring
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
③ 从 Spring 容器中获得 UserService 进行操作
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.save();
Bean的依赖注入分析
Bean的依赖注入概念
概念
依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
怎么将UserDao怎样注入到UserService内部呢?
构造方法
set方法
set方法
set
1)set方法注入
在UserServiceImpl中添加setUserDao方法
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao; }
@Override
public void save() {
userDao.save();
} }
在UserServiceImpl中添加setUserDao方法
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao; }
@Override
public void save() {
userDao.save();
} }
1)set方法注入
配置Spring容器调用set方法进行注入
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/>
</bean>
配置Spring容器调用set方法进行注入
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/>
</bean>
1)set方法注入
P命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中,如下:
首先,需要引入P命名空间:
xmlns:p="http://www.springframework.org/schema/p"
其次,需要修改注入方式
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDaoref="userDao"/>
P命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中,如下:
首先,需要引入P命名空间:
xmlns:p="http://www.springframework.org/schema/p"
其次,需要修改注入方式
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDaoref="userDao"/>
构造方法
2)构造方法注入
创建有参构造
public class UserServiceImpl implements UserService {
@Override
public void save() {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
} }
创建有参构造
public class UserServiceImpl implements UserService {
@Override
public void save() {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
} }
配置Spring容器调用有参构造时进行注入
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
Bean的依赖注入的数据类型
普通数据类型
public class UserDaoImpl implements UserDao {
private String company;
private int age;
public void setCompany(String company) {
this.company = company;
}
public void setAge(int age) {
this.age = age;
}
public void save() {
System.out.println(company+"==="+age);
System.out.println("UserDao save method running....");
} }
private String company;
private int age;
public void setCompany(String company) {
this.company = company;
}
public void setAge(int age) {
this.age = age;
}
public void save() {
System.out.println(company+"==="+age);
System.out.println("UserDao save method running....");
} }
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="company" value="传智播客"></property> <property name="age" value="15"></property>
</bean>
</bean>
引用数据类型
集合数据类型
public class UserDaoImpl implements UserDao {
private List<String> strList;
public void setStrList(List<String> strList) {
this.strList = strList;
}
public void save() {
System.out.println(strList);
System.out.println("UserDao save method running....");
} }
private List<String> strList;
public void setStrList(List<String> strList) {
this.strList = strList;
}
public void save() {
System.out.println(strList);
System.out.println("UserDao save method running....");
} }
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="strList"> <list><value>aaa</value> <value>bbb</value> <value>ccc</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
集合List<User>
public class UserDaoImpl implements UserDao {
private List<User> userList;
public void setUserList(List<User> userList) {
this.userList = userList;
}
public void save() {
System.out.println(userList);
System.out.println("UserDao save method running....");
} }
public class UserDaoImpl implements UserDao {
private List<User> userList;
public void setUserList(List<User> userList) {
this.userList = userList;
}
public void save() {
System.out.println(userList);
System.out.println("UserDao save method running....");
} }
<bean id="u1" class="com.itheima.domain.User"/>
<bean id="u2" class="com.itheima.domain.User"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="userList"> <list><bean class="com.itheima.domain.User"/>
<bean class="com.itheima.domain.User"/>
<ref bean="u1"/>
<ref bean="u2"/>
</list>
</property>
</bean>
<bean id="u2" class="com.itheima.domain.User"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="userList"> <list><bean class="com.itheima.domain.User"/>
<bean class="com.itheima.domain.User"/>
<ref bean="u1"/>
<ref bean="u2"/>
</list>
</property>
</bean>
public class UserDaoImpl implements UserDao {
private Map<String,User> userMap;
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
public void save() {
System.out.println(userMap);
System.out.println("UserDao save method running....");
}
private Map<String,User> userMap;
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
public void save() {
System.out.println(userMap);
System.out.println("UserDao save method running....");
}
集合数据类型( Map<String,User> )的注入
<bean id="u1" class="com.itheima.domain.User"/>
<bean id="u2" class="com.itheima.domain.User"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="userMap"> <map><entry key="user1" value-ref="u1"/>
<entry key="user2" value-ref="u2"/>
</map>
</property>
</bean>
<bean id="u1" class="com.itheima.domain.User"/>
<bean id="u2" class="com.itheima.domain.User"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="userMap"> <map><entry key="user1" value-ref="u1"/>
<entry key="user2" value-ref="u2"/>
</map>
</property>
</bean>
集合数据类型(Properties)的注入
public class UserDaoImpl implements UserDao {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void save() {
System.out.println(properties);
System.out.println("UserDao save method running....");
} }
public class UserDaoImpl implements UserDao {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void save() {
System.out.println(properties);
System.out.println("UserDao save method running....");
} }
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"> <property name="properties"> <props> <prop key="p1">aaa</prop> <prop key="p2">bbb</prop> <prop key="p3">ccc</prop>
</props>
</property>
</bean>
</props>
</property>
</bean>
引入其他配置文件
spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,
所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置
文件通过import标签进行加载<import resource="applicationContext-xxx.xml"/>
所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置
文件通过import标签进行加载<import resource="applicationContext-xxx.xml"/>
Spring相关API
ApplicationContext的继承体系
applicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象
ApplicationContext的实现类
1)ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件 推荐使用这种
2)FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
3)AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解
它是从类的根路径下加载配置文件 推荐使用这种
2)FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
3)AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解
getBean()方法使用
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
public <T> T getBean(Class<T> requiredType) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(requiredType);
}
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
public <T> T getBean(Class<T> requiredType) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(requiredType);
}
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService1 = (UserService)
applicationContext.getBean("userService");
UserService userService2 = applicationContext.getBean(UserService.class);
ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService1 = (UserService)
applicationContext.getBean("userService");
UserService userService2 = applicationContext.getBean(UserService.class);
Spring的重点API
ApplicationContext app = new ClasspathXmlApplicationContext("xml文件")
app.getBean("id")
app.getBean(Class)
app.getBean("id")
app.getBean(Class)
Spring配置数据源
数据源(连接池)的作用
数据源(连接池)是提高程序性能如出现的
事先实例化数据源,初始化部分连接资源
使用连接资源时从数据源中获取
使用完毕后将连接资源归还给数据源
事先实例化数据源,初始化部分连接资源
使用连接资源时从数据源中获取
使用完毕后将连接资源归还给数据源
常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等
数据源的开发步骤
① 导入数据源的坐标和数据库驱动坐标
② 创建数据源对象
③ 设置数据源的基本连接数据
④ 使用数据源获取连接资源和归还连接资源
② 创建数据源对象
③ 设置数据源的基本连接数据
④ 使用数据源获取连接资源和归还连接资源
数据源的手动创建
① 导入c3p0和druid的坐标
<!-- C3P0连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!-- C3P0连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
② 创建C3P0连接池
@Test
public void testC3P0() throws Exception {
//创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//设置数据库连接参数
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");
//获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
@Test
public void testC3P0() throws Exception {
//创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//设置数据库连接参数
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");
//获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
② 创建Druid连接池
@Test
public void testDruid() throws Exception {
//创建数据源
DruidDataSource dataSource = new DruidDataSource();
//设置数据库连接参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
//获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
@Test
public void testDruid() throws Exception {
//创建数据源
DruidDataSource dataSource = new DruidDataSource();
//设置数据库连接参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
//获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
③ 提取jdbc.properties配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
④ 读取jdbc.properties配置文件创建连接池
@Test
public void testC3P0ByProperties() throws Exception {
//加载类路径下的jdbc.properties
ResourceBundle rb = ResourceBundle.getBundle("jdbc");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(rb.getString("jdbc.driver"));
dataSource.setJdbcUrl(rb.getString("jdbc.url"));
dataSource.setUser(rb.getString("jdbc.username"));
dataSource.setPassword(rb.getString("jdbc.password"));
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
@Test
public void testC3P0ByProperties() throws Exception {
//加载类路径下的jdbc.properties
ResourceBundle rb = ResourceBundle.getBundle("jdbc");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(rb.getString("jdbc.driver"));
dataSource.setJdbcUrl(rb.getString("jdbc.url"));
dataSource.setUser(rb.getString("jdbc.username"));
dataSource.setPassword(rb.getString("jdbc.password"));
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
Spring配置数据源
DataSource有无参构造方法,而Spring默认就是通过无参构造方法实例化对象的
DataSource要想使用需要通过set方法设置数据库连接信息,而Spring可以通过set方法进行字符串注入
DataSource要想使用需要通过set方法设置数据库连接信息,而Spring可以通过set方法进行字符串注入
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
Spring注解开发
原始注解
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置
文件可以简化配置,提高开发效率。
文件可以简化配置,提高开发效率。
使用注解进行开发时,需要在applicationContext.xml
中配置组件扫描,作用是指定哪个包及其子包下的Bean
需要进行扫描以便识别使用注解配置的类、字段和方法。
中配置组件扫描,作用是指定哪个包及其子包下的Bean
需要进行扫描以便识别使用注解配置的类、字段和方法。
<!--注解的组件扫描-->
<context:component-scan base-package="com.itheima">
</context:component-scan>
<context:component-scan base-package="com.itheima">
</context:component-scan>
使用@Compont或@Repository标识UserDaoImpl需要Spring进行实例化。
//@Component("userDao")
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running... ...");
} }
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running... ...");
} }
使用@Compont或@Service标识UserServiceImpl需要Spring进行实例化
使用@Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入
使用@Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入
//@Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {
/*@Autowired
@Qualifier("userDao")*/
@Resource(name="userDao")
private UserDao userDao;
@Override
public void save() {
userDao.save();
} }
@Service("userService")
public class UserServiceImpl implements UserService {
/*@Autowired
@Qualifier("userDao")*/
@Resource(name="userDao")
private UserDao userDao;
@Override
public void save() {
userDao.save();
} }
使用@Value进行字符串注入
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Value("注入普通数据")
private String str;
@Value("${jdbc.driver}")
private String driver;
@Override
public void save() {
System.out.println(str);
System.out.println(driver);
System.out.println("save running... ...");
} }
public class UserDaoImpl implements UserDao {
@Value("注入普通数据")
private String str;
@Value("${jdbc.driver}")
private String driver;
@Override
public void save() {
System.out.println(str);
System.out.println(driver);
System.out.println("save running... ...");
} }
使用@Scope标注Bean的范围
//@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}
使用@PostConstruct标注初始化方法,使用@PreDestroy标注销毁方法
@PostConstruct
public void init(){
System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法.....");
}
public void init(){
System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法.....");
}
新注解
非自定义的Bean的配置:<bean>
加载properties文件的配置:<context:property-placeholder>
组件扫描的配置:<context:component-scan>
引入其他文件:<import>
加载properties文件的配置:<context:property-placeholder>
组件扫描的配置:<context:component-scan>
引入其他文件:<import>
@Configuration
@ComponentScan("com.itheima")
@Import({DataSourceConfiguration.class})
public class SpringConfiguration { }
@ComponentScan("com.itheima")
@Import({DataSourceConfiguration.class})
public class SpringConfiguration { }
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
public class DataSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name="dataSource")
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource; }
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource; }
Spring整合JUnit
原始Junit测试Spring的问题
每个测试都有以下俩个方法
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
解决思路
让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
将需要进行测试Bean直接在测试类中进行注入
将需要进行测试Bean直接在测试类中进行注入
步骤
① 导入spring集成Junit的坐标
② 使用@Runwith注解替换原来的运行期
③ 使用@ContextConfiguration指定配置文件或配置类
④ 使用@Autowired注入需要测试的对象
⑤ 创建测试方法进行测试
② 使用@Runwith注解替换原来的运行期
③ 使用@ContextConfiguration指定配置文件或配置类
④ 使用@Autowired注入需要测试的对象
⑤ 创建测试方法进行测试
一
<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
二
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}
三
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}
四
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService; }
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService; }
五
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void testUserService(){
userService.save();
} }
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void testUserService(){
userService.save();
} }
Spring与web环境集成
导入Spring集成web的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
配置ContextLoaderListener监听器
<!--全局参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
通过工具获得应用上下文对象
ApplicationContext applicationContext =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
Object obj = applicationContext.getBean("id");
WebApplicationContextUtils.getWebApplicationContext(servletContext);
Object obj = applicationContext.getBean("id");
Spring练习环境搭建
用户界面
角色列表的展示和添加操作
用户列表的展示和添加操作
删除用户操作
AOP
简介
概念
AOP 为 Aspect Oriented Programming 的缩写,
意思为面向切面编程,是通过预编译方式和运行期动态代理
实现程序功能的统一维护的一种技术
意思为面向切面编程,是通过预编译方式和运行期动态代理
实现程序功能的统一维护的一种技术
AOP 的作用及其优势
作用
在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势
减少重复代码,提高开发效率,并且便于维护
AOP的底层实现
AOP 的底层是通过 Spring 提供的的动态代理技术实现的。
在运行期间,Spring通过动态代理技术动态的生成代理对象,
代理对象方法执行时进行增强功能的介入,在去调用目标对
象的方法,从而完成功能的增强。
在运行期间,Spring通过动态代理技术动态的生成代理对象,
代理对象方法执行时进行增强功能的介入,在去调用目标对
象的方法,从而完成功能的增强。
AOP 的动态代理技术
常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术
① 目标类接口
public interface TargetInterface {
public void method();
}
② 目标类
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
③ 动态代理代码
Target target = new Target(); //创建目标对象
//创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass()
.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("前置增强代码...");
Object invoke = method.invoke(target, args);
System.out.println("后置增强代码...");
return invoke;
} }
);
④ 调用代理对象的方法测试
// 测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();
public interface TargetInterface {
public void method();
}
② 目标类
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
③ 动态代理代码
Target target = new Target(); //创建目标对象
//创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass()
.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("前置增强代码...");
Object invoke = method.invoke(target, args);
System.out.println("后置增强代码...");
return invoke;
} }
);
④ 调用代理对象的方法测试
// 测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();
cglib 代理:基于父类的动态代理技术
① 目标类
public class Target {
public void method() {
System.out.println("Target running....");
} }
② 动态代理代码
Target target = new Target(); //创建目标对象
Enhancer enhancer = new Enhancer(); //创建增强器
enhancer.setSuperclass(Target.class); //设置父类
enhancer.setCallback(new MethodInterceptor() { //设置回调
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("前置代码增强....");
Object invoke = method.invoke(target, objects);
System.out.println("后置代码增强....");
return invoke;
}
});
Target proxy = (Target) enhancer.create(); //创建代理对象
③ 调用代理对象的方法测试
//测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();
public class Target {
public void method() {
System.out.println("Target running....");
} }
② 动态代理代码
Target target = new Target(); //创建目标对象
Enhancer enhancer = new Enhancer(); //创建增强器
enhancer.setSuperclass(Target.class); //设置父类
enhancer.setCallback(new MethodInterceptor() { //设置回调
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("前置代码增强....");
Object invoke = method.invoke(target, objects);
System.out.println("后置代码增强....");
return invoke;
}
});
Target proxy = (Target) enhancer.create(); //创建代理对象
③ 调用代理对象的方法测试
//测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();
AOP 相关概念
AOP相关术语
AOP
1. 需要编写的内容
编写核心业务代码(目标类的目标方法)
编写切面类,切面类中有通知(增强功能方法)
在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
编写切面类,切面类中有通知(增强功能方法)
在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
2. AOP 技术实现的内容
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,
使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代
理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代
理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
3. AOP 底层使用哪种代理方式
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
基于 XML 的 AOP 开发
快速入门
① 导入 AOP 相关坐标
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
② 创建目标接口和目标类(内部有切点)
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
③ 创建切面类(内部有增强方法)
public class MyAspect {
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
} }
public class MyAspect {
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
} }
④ 将目标类和切面类的对象创建权交给 spring
<!--配置目标类--> <bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面类--> <bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
<!--配置目标类--> <bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面类--> <bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
配置切点表达式和前置增强的织入关系
<aop:config>
<!--引用myAspect的Bean为切面对象--> <aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强--> <aop:before method="before" pointcut="execution(public void
com.itheima.aop.Target.method())"></aop:before>
</aop:aspect>
</aop:config>
<aop:config>
<!--引用myAspect的Bean为切面对象--> <aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强--> <aop:before method="before" pointcut="execution(public void
com.itheima.aop.Target.method())"></aop:before>
</aop:aspect>
</aop:config>
⑥ 测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")
public class AopTest { @Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
} }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")
public class AopTest { @Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
} }
XML 配置 AOP 详解
1. 切点表达式的写法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
访问修饰符可以省略
返回值类型、包名、类名、方法名可以使用星号* 代表任意
包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
访问修饰符可以省略
返回值类型、包名、类名、方法名可以使用星号* 代表任意
包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
execution(public void com.itheima.aop.Target.method())
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..))
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..))
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
2.通知的类型
3.切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,
在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽
取后的切点表达式。
在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽
取后的切点表达式。
基于注解的 AOP 开发
开发步骤
① 创建目标接口和目标类(内部有切点)
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
② 创建切面类(内部有增强方法)
public class MyAspect {
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
} }
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
} }
③ 将目标类和切面类的对象创建权交给 spring
@Component("target")
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
@Component("myAspect")
public class MyAspect {
public void before(){
System.out.println("前置代码增强.....");
} }
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
} }
@Component("myAspect")
public class MyAspect {
public void before(){
System.out.println("前置代码增强.....");
} }
④ 在切面类中使用注解配置织入关系
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("execution(* com.itheima.aop.*.*(..))")
public void before(){
System.out.println("前置代码增强.....");
} }
@Aspect
public class MyAspect {
@Before("execution(* com.itheima.aop.*.*(..))")
public void before(){
System.out.println("前置代码增强.....");
} }
⑤ 在配置文件中开启组件扫描和 AOP 的自动代理
<!--组件扫描-->
<context:component-scan base-package="com.itheima.aop"/>
<!--aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
⑥测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
注解通知的类型
切点表达式的抽取
@@Component("myAspect")
@Aspect
public class MyAspect {
@Before("MyAspect.myPoint()")
public void before(){
System.out.println("前置代码增强.....");
}@Pointcut("execution(* com.itheima.aop.*.*(..))")
public void myPoint(){}
}
@Aspect
public class MyAspect {
@Before("MyAspect.myPoint()")
public void before(){
System.out.println("前置代码增强.....");
}@Pointcut("execution(* com.itheima.aop.*.*(..))")
public void myPoint(){}
}
声明式事务控制
编程式事务控制相关对象
PlatformTransactionManager
————>spring 的事务管理器
————>spring 的事务管理器
TransactionDefinition
是事务的定义信息对象
是事务的定义信息对象
事务隔离级别
事务传播行为
TransactionStatus
事务具体的运行状态
事务具体的运行状态
基于 XML 的声明式事务控制
作用
使用
① 引入tx命名空间
② 配置事务增强
③ 配置事务 AOP 织入
切点方法的事务参数的配置
基于注解的声明式事务控制
注解配置声明式事务控制解析
SpringMVC
SpringMVC的简介
SpringMVC概述
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于
SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。
SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。
SpringMVC快速入门
导入Servlet和Jsp的坐标
<!--Spring坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version>
</dependency>
<!--SpringMVC坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</version>
</dependency>
<!--Servlet坐标--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version>
</dependency>
<!--Jsp坐标--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version>
</dependency>
</dependency>
<!--SpringMVC坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</version>
</dependency>
<!--Servlet坐标--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version>
</dependency>
<!--Jsp坐标--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version>
</dependency>
在web.xml配置SpringMVC的核心控制器
<servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value>
</init-param> <load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern>
</servlet-mapping>
</init-param> <load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern>
</servlet-mapping>
创建Controller和业务层方法
public class QuickController {
public String quickMethod(){
System.out.println("quickMethod running.....");
return "index"; } }
public String quickMethod(){
System.out.println("quickMethod running.....");
return "index"; } }
创建视图jsp
<html> <body><h2>Hello SpringMVC!</h2>
</body>
</html>
</body>
</html>
配置注解
@Controller
public class QuickController {
@RequestMapping("/quick")
public String quickMethod(){
System.out.println("quickMethod running.....");
return "index"; } }
public class QuickController {
@RequestMapping("/quick")
public String quickMethod(){
System.out.println("quickMethod running.....");
return "index"; } }
创建spring-mvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置注解扫描--> <context:component-scan base-package="com.itheima"/>
</beans>
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置注解扫描--> <context:component-scan base-package="com.itheima"/>
</beans>
访问测试地址
http://localhost:8080/itheima_springmvc1/quick
流程图示
SpringMVC的组件分析
SpringMVC的执行流程
① 用户发送请求至前端控制器DispatcherServlet。
② DispatcherServlet收到请求调用HandlerMapping处理器映射器。
③ 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果
有则生成)一并返回给DispatcherServlet。
④ DispatcherServlet调用HandlerAdapter处理器适配器。
⑤ HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
⑥ Controller执行完成返回ModelAndView。
⑦ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
⑧ DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
⑨ ViewReslover解析后返回具体View。
⑩ DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。
② DispatcherServlet收到请求调用HandlerMapping处理器映射器。
③ 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果
有则生成)一并返回给DispatcherServlet。
④ DispatcherServlet调用HandlerAdapter处理器适配器。
⑤ HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
⑥ Controller执行完成返回ModelAndView。
⑦ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
⑧ DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
⑨ ViewReslover解析后返回具体View。
⑩ DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。
组件解析
1. 前端控制器:DispatcherServlet
用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由
它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。
2. 处理器映射器:HandlerMapping
HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的
映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3. 处理器适配器:HandlerAdapter
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理
器进行执行。
4. 处理器:Handler
它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由
Handler 对具体的用户请求进行处理。
5. 视图解析器:View Resolver
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即
具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
6. 视图:View
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最
常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程
序员根据业务需求开发具体的页面
用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由
它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。
2. 处理器映射器:HandlerMapping
HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的
映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3. 处理器适配器:HandlerAdapter
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理
器进行执行。
4. 处理器:Handler
它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由
Handler 对具体的用户请求进行处理。
5. 视图解析器:View Resolver
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即
具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
6. 视图:View
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最
常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程
序员根据业务需求开发具体的页面
注解解析
@RequestMapping
作用:用于建立请求 URL 和处理请求方法之间的对应关系
位置:
类上,请求URL 的第一级访问目录。此处不写的话,就相当于应用的根目录
方法上,请求 URL 的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径
属性:
value:用于指定请求的URL。它和path属性的作用是一样的
method:用于指定请求的方式
params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样
例如:
params = {"accountName"},表示请求参数必须有accountName
params = {"moeny!100"},表示请求参数中money不能是100
作用:用于建立请求 URL 和处理请求方法之间的对应关系
位置:
类上,请求URL 的第一级访问目录。此处不写的话,就相当于应用的根目录
方法上,请求 URL 的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径
属性:
value:用于指定请求的URL。它和path属性的作用是一样的
method:用于指定请求的方式
params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样
例如:
params = {"accountName"},表示请求参数必须有accountName
params = {"moeny!100"},表示请求参数中money不能是100
1. mvc命名空间引入
命名空间:xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
约束地址:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
2. 组件扫描
SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使
用@Controller注解标注的话,就需要使用<context:component-scan basepackage=“com.itheima.controller"/>进行组件扫描。
命名空间:xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
约束地址:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
2. 组件扫描
SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使
用@Controller注解标注的话,就需要使用<context:component-scan basepackage=“com.itheima.controller"/>进行组件扫描。
XML配置解析
1. 视图解析器
SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址
org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.I
nternalResourceViewResolver
翻看该解析器源码,可以看到该解析器的默认设置,如下:
REDIRECT_URL_PREFIX = "redirect:" --重定向前缀
FORWARD_URL_PREFIX = "forward:" --转发前缀(默认值)
prefix = ""; --视图名称前缀
suffix = ""; --视图名称后缀
SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址
org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.I
nternalResourceViewResolver
翻看该解析器源码,可以看到该解析器的默认设置,如下:
REDIRECT_URL_PREFIX = "redirect:" --重定向前缀
FORWARD_URL_PREFIX = "forward:" --转发前缀(默认值)
prefix = ""; --视图名称前缀
suffix = ""; --视图名称后缀
<!--配置内部资源视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property>
</bean>
</bean>
SpringMVC数据响应
数据响应方式
1) 页面跳转
直接返回字符串
通过ModelAndView对象返回
2) 回写数据
直接返回字符串
返回对象或集合
直接返回字符串
通过ModelAndView对象返回
2) 回写数据
直接返回字符串
返回对象或集合
返回ModelAndView对象
@RequestMapping("/quick2")
public ModelAndView quickMethod2(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:index.jsp");
return modelAndView;
}
@RequestMapping("/quick3")
public ModelAndView quickMethod3(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:/WEB-INF/views/index.jsp");
return modelAndView;
}
public ModelAndView quickMethod2(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:index.jsp");
return modelAndView;
}
@RequestMapping("/quick3")
public ModelAndView quickMethod3(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:/WEB-INF/views/index.jsp");
return modelAndView;
}
3向request域存储数据
① 通过SpringMVC框架注入的request对象setAttribute()方法设置
@RequestMapping("/quick")
public String quickMethod(HttpServletRequest request){
request.setAttribute("name","zhangsan");
return "index";
}
② 通过ModelAndView的addObject()方法设置
@RequestMapping("/quick3")
public ModelAndView quickMethod3(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:/WEB-INF/views/index.jsp");
modelAndView.addObject("name","lisi");
return modelAndView;
}
@RequestMapping("/quick3")
public ModelAndView quickMethod3(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:/WEB-INF/views/index.jsp");
modelAndView.addObject("name","lisi");
return modelAndView;
}
回写数据
① 通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”) 回写数
据,此时不需要视图跳转,业务方法返回值为void。
@RequestMapping("/quick4")
public void quickMethod4(HttpServletResponse response) throws
IOException {
response.getWriter().print("hello world");
}
据,此时不需要视图跳转,业务方法返回值为void。
@RequestMapping("/quick4")
public void quickMethod4(HttpServletResponse response) throws
IOException {
response.getWriter().print("hello world");
}
② 将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架,方法
返回的字符串不是跳转是直接在http响应体中返回。
@RequestMapping("/quick5")
@ResponseBody
public String quickMethod5() throws IOException {
return "hello springMVC!!!"; }
返回的字符串不是跳转是直接在http响应体中返回。
@RequestMapping("/quick5")
@ResponseBody
public String quickMethod5() throws IOException {
return "hello springMVC!!!"; }
在异步项目中,客户端与服务器端往往要进行json格式字符串交互,此时我们可以手动拼接json字符串返回。
@RequestMapping("/quick6")
@ResponseBody
public String quickMethod6() throws IOException {
return "{\"name\":\"zhangsan\",\"age\":18}"; }
@RequestMapping("/quick6")
@ResponseBody
public String quickMethod6() throws IOException {
return "{\"name\":\"zhangsan\",\"age\":18}"; }
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
@RequestMapping("/quick7")
@ResponseBody
public String quickMethod7() throws IOException {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(user);
return s;
}
@ResponseBody
public String quickMethod7() throws IOException {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(user);
return s;
}
返回对象或集合
<bean class="org.springframework.web.servlet.mvc.method.annotation
.RequestMappingHandlerAdapter"> <property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation
.RequestMappingHandlerAdapter"> <property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>
</list>
</property>
</bean>
@RequestMapping("/quick8")
@ResponseBody
public User quickMethod8() throws IOException {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
return user;
}
@ResponseBody
public User quickMethod8() throws IOException {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
return user;
}
<!--mvc的注解驱动--> <mvc:annotation-driven/>
使用<mvc:annotation-driven>默认底层就会集成jackson进行对象或集合的json格式字符串的转换
SpringMVC获得请求数据
可获取的类型
基本类型参数
POJO类型参数
数组类型参数
集合类型参数
POJO类型参数
数组类型参数
集合类型参数
Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配
http://localhost:8080/itheima_springmvc1/quick9?username=zhangsan&age=12
http://localhost:8080/itheima_springmvc1/quick9?username=zhangsan&age=12
@RequestMapping("/quick9")
@ResponseBody
public void quickMethod9(String username,int age) throws IOException {
System.out.println(username);
System.out.println(age);
}
@ResponseBody
public void quickMethod9(String username,int age) throws IOException {
System.out.println(username);
System.out.println(age);
}
Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。
http://localhost:8080/itheima_springmvc1/quick9?username=zhangsan&age=12
http://localhost:8080/itheima_springmvc1/quick9?username=zhangsan&age=12
public class User {
private String username;
private int age;
getter/setter…
}
@RequestMapping("/quick10")
@ResponseBody
public void quickMethod10(User user) throws IOException {
System.out.println(user);
}
private String username;
private int age;
getter/setter…
}
@RequestMapping("/quick10")
@ResponseBody
public void quickMethod10(User user) throws IOException {
System.out.println(user);
}
获得数组类型
http://localhost:8080/itheima_springmvc1/quick11?strs=111&strs=222&strs=333
@RequestMapping("/quick11")
@ResponseBody
public void quickMethod11(String[] strs) throws IOException {
System.out.println(Arrays.asList(strs));
}
@RequestMapping("/quick11")
@ResponseBody
public void quickMethod11(String[] strs) throws IOException {
System.out.println(Arrays.asList(strs));
}
获得集合类型参数
<form action="${pageContext.request.contextPath}/quick12" method="post">
<input type="text" name="userList[0].username"><br>
<input type="text" name="userList[0].age"><br>
<input type="text" name="userList[1].username"><br>
<input type="text" name="userList[1].age"><br>
<input type="submit" value="提交"><br>
</form>
@RequestMapping("/quick12")
@ResponseBody
public void quickMethod12(Vo vo) throws IOException {
System.out.println(vo.getUserList());
}
<input type="text" name="userList[0].username"><br>
<input type="text" name="userList[0].age"><br>
<input type="text" name="userList[1].username"><br>
<input type="text" name="userList[1].age"><br>
<input type="submit" value="提交"><br>
</form>
@RequestMapping("/quick12")
@ResponseBody
public void quickMethod12(Vo vo) throws IOException {
System.out.println(vo.getUserList());
}
当使用ajax提交时,可以指定contentType为json形式,
那么在方法参数位置使用@RequestBody可以
直接接收集合数据而无需使用POJO进行包装。
那么在方法参数位置使用@RequestBody可以
直接接收集合数据而无需使用POJO进行包装。
<script>
//模拟数据
var userList = new Array();
userList.push({username: "zhangsan",age: "20"});
userList.push({username: "lisi",age: "20"});
$.ajax({
type: "POST",
url: "/itheima_springmvc1/quick13",
data: JSON.stringify(userList),
contentType : 'application/json;charset=utf-8'
});
</script>
//模拟数据
var userList = new Array();
userList.push({username: "zhangsan",age: "20"});
userList.push({username: "lisi",age: "20"});
$.ajax({
type: "POST",
url: "/itheima_springmvc1/quick13",
data: JSON.stringify(userList),
contentType : 'application/json;charset=utf-8'
});
</script>
@RequestMapping("/quick13")
@ResponseBody
public void quickMethod13(@RequestBody List<User> userList) throws
IOException {
System.out.println(userList);
}
@ResponseBody
public void quickMethod13(@RequestBody List<User> userList) throws
IOException {
System.out.println(userList);
}
注意:通过谷歌开发者工具抓包发现,没有加载到jquery文件,原因是SpringMVC的前端控制器
DispatcherServlet的url-pattern配置的是/,代表对所有的资源都进行过滤操作,我们可以通过以下两种
方式指定放行静态资源:
• 在spring-mvc.xml配置文件中指定放行的资源
<mvc:resources mapping="/js/**" location="/js/"/>
• 使用<mvc:default-servlet-handler/>标签
DispatcherServlet的url-pattern配置的是/,代表对所有的资源都进行过滤操作,我们可以通过以下两种
方式指定放行静态资源:
• 在spring-mvc.xml配置文件中指定放行的资源
<mvc:resources mapping="/js/**" location="/js/"/>
• 使用<mvc:default-servlet-handler/>标签
请求数据乱码问题
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
参数绑定注解@requestParam
<form action="${pageContext.request.contextPath}/quick14" method="post">
<input type="text" name="name"><br>
<input type="submit" value="提交"><br>
</form>
<input type="text" name="name"><br>
<input type="submit" value="提交"><br>
</form>
@RequestMapping("/quick14")
@ResponseBody
public void quickMethod14(@RequestParam("name") String username) throws
IOException {
System.out.println(username);
}
@ResponseBody
public void quickMethod14(@RequestParam("name") String username) throws
IOException {
System.out.println(username);
}
value:与请求参数名称
required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错
defaultValue:当没有指定请求参数时,则使用指定的默认值赋值
@RequestMapping("/quick14")
@ResponseBody
public void quickMethod14(@RequestParam(value="name",required =
false,defaultValue = "itcast") String username) throws IOException {
System.out.println(username);
}
required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错
defaultValue:当没有指定请求参数时,则使用指定的默认值赋值
@RequestMapping("/quick14")
@ResponseBody
public void quickMethod14(@RequestParam(value="name",required =
false,defaultValue = "itcast") String username) throws IOException {
System.out.println(username);
}
获得Restful风格的参数
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务
器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
GET:用于获取资源
POST:用于新建资源
PUT:用于更新资源
DELETE:用于删除资源
GET:用于获取资源
POST:用于新建资源
PUT:用于更新资源
DELETE:用于删除资源
http://localhost:8080/itheima_springmvc1/quick19/zhangsan
@RequestMapping("/quick19/{name}")
@ResponseBody
public void quickMethod19(@PathVariable(value = "name",required = true) String name){
System.out.println(name);
}
@RequestMapping("/quick19/{name}")
@ResponseBody
public void quickMethod19(@PathVariable(value = "name",required = true) String name){
System.out.println(name);
}
自定义类型转换器
自定义类型转换器的开发步骤:
① 定义转换器类实现Converter接口
① 定义转换器类实现Converter接口
public class DateConverter implements Converter<String,Date>{
@Override
public Date convert(String source) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
try {
Date date = format.parse(source);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null; } }
@Override
public Date convert(String source) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
try {
Date date = format.parse(source);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null; } }
② 在配置文件中声明转换器
<bean id="converterService"
class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list><bean class="com.itheima.converter.DateConverter"/>
</list>
</property>
</bean>
class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list><bean class="com.itheima.converter.DateConverter"/>
</list>
</property>
</bean>
③ 在<annotation-driven>中引用转换器
<mvc:annotation-driven conversion-service="converterService"/>
获得Servlet相关API
SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:
HttpServletRequest
HttpServletResponse
HttpSession
HttpServletRequest
HttpServletResponse
HttpSession
@RequestMapping("/quick16")
@ResponseBody
public void quickMethod16(HttpServletRequest request,HttpServletResponse
response,
HttpSession session){
System.out.println(request);
System.out.println(response);
System.out.println(session);
}
@ResponseBody
public void quickMethod16(HttpServletRequest request,HttpServletResponse
response,
HttpSession session){
System.out.println(request);
System.out.println(response);
System.out.println(session);
}
1. @RequestHeader
使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)
@RequestHeader注解的属性如下:
value:请求头的名称
required:是否必须携带此请求头
@RequestMapping("/quick17")
@ResponseBody
public void quickMethod17(
@RequestHeader(value = "User-Agent",required = false) String
headerValue){
System.out.println(headerValue);
}
使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)
@RequestHeader注解的属性如下:
value:请求头的名称
required:是否必须携带此请求头
@RequestMapping("/quick17")
@ResponseBody
public void quickMethod17(
@RequestHeader(value = "User-Agent",required = false) String
headerValue){
System.out.println(headerValue);
}
2. @CookieValue
使用@CookieValue可以获得指定Cookie的值
@CookieValue注解的属性如下:
value:指定cookie的名称
required:是否必须携带此cookie
@RequestMapping("/quick18")
@ResponseBody
public void quickMethod18(
@CookieValue(value = "JSESSIONID",required = false) String jsessionid){
System.out.println(jsessionid);
}
使用@CookieValue可以获得指定Cookie的值
@CookieValue注解的属性如下:
value:指定cookie的名称
required:是否必须携带此cookie
@RequestMapping("/quick18")
@ResponseBody
public void quickMethod18(
@CookieValue(value = "JSESSIONID",required = false) String jsessionid){
System.out.println(jsessionid);
}
文件上传
1. 文件上传客户端三要素
表单项type=“file”
表单的提交方式是post
表单的enctype属性是多部分表单形式,及enctype=“multipart/form-data”
<form action="${pageContext.request.contextPath}/quick20" method="post"
enctype="multipart/form-data">
名称:<input type="text" name="name"><br>
文件:<input type="file" name="file"><br> <input type="submit" value="提交"><br>
</form>
表单项type=“file”
表单的提交方式是post
表单的enctype属性是多部分表单形式,及enctype=“multipart/form-data”
<form action="${pageContext.request.contextPath}/quick20" method="post"
enctype="multipart/form-data">
名称:<input type="text" name="name"><br>
文件:<input type="file" name="file"><br> <input type="submit" value="提交"><br>
</form>
单文件上传步骤
① 导入fileupload和io坐标
② 配置文件上传解析器
③ 编写文件上传代码
② 配置文件上传解析器
③ 编写文件上传代码
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--上传文件总大小--> <property name="maxUploadSize" value="5242800"/>
<!--上传单个文件的大小--> <property name="maxUploadSizePerFile" value="5242800"/>
<!--上传文件的编码类型--> <property name="defaultEncoding" value="UTF-8"/>
</bean>
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--上传文件总大小--> <property name="maxUploadSize" value="5242800"/>
<!--上传单个文件的大小--> <property name="maxUploadSizePerFile" value="5242800"/>
<!--上传文件的编码类型--> <property name="defaultEncoding" value="UTF-8"/>
</bean>
@RequestMapping("/quick20")
@ResponseBody
public void quickMethod20(String name,MultipartFile uploadFile) throws
IOException {
//获得文件名称
String originalFilename = uploadFile.getOriginalFilename();
//保存文件
uploadFile.transferTo(new File("C:\\upload\\"+originalFilename));
}
@ResponseBody
public void quickMethod20(String name,MultipartFile uploadFile) throws
IOException {
//获得文件名称
String originalFilename = uploadFile.getOriginalFilename();
//保存文件
uploadFile.transferTo(new File("C:\\upload\\"+originalFilename));
}
多文件上传实现
<h1>多文件上传测试</h1> <form action="${pageContext.request.contextPath}/quick21" method="post"
enctype="multipart/form-data">
名称:<input type="text" name="name"><br>
文件1:<input type="file" name="uploadFiles"><br>
文件2:<input type="file" name="uploadFiles"><br>
文件3:<input type="file" name="uploadFiles"><br> <input type="submit" value="提交"><br>
</form>
enctype="multipart/form-data">
名称:<input type="text" name="name"><br>
文件1:<input type="file" name="uploadFiles"><br>
文件2:<input type="file" name="uploadFiles"><br>
文件3:<input type="file" name="uploadFiles"><br> <input type="submit" value="提交"><br>
</form>
@RequestMapping("/quick21")
@ResponseBody
public void quickMethod21(String name,MultipartFile[] uploadFiles) throws
IOException {
for (MultipartFile uploadFile : uploadFiles){
String originalFilename = uploadFile.getOriginalFilename();
uploadFile.transferTo(new File("C:\\upload\\"+originalFilename));
} }
@ResponseBody
public void quickMethod21(String name,MultipartFile[] uploadFiles) throws
IOException {
for (MultipartFile uploadFile : uploadFiles){
String originalFilename = uploadFile.getOriginalFilename();
uploadFile.transferTo(new File("C:\\upload\\"+originalFilename));
} }
Spring JdbcTemplate基本使用
它是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作
模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操
作消息队列的JmsTemplate等等。
模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操
作消息队列的JmsTemplate等等。
开发步骤
① 导入spring-jdbc和spring-tx坐标
② 创建数据库表和实体
③ 创建JdbcTemplate对象
④ 执行数据库操作
② 创建数据库表和实体
③ 创建JdbcTemplate对象
④ 执行数据库操作
<!--导入spring的jdbc坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.5.RELEASE</version>
</dependency>
<!--导入spring的tx坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.5.RELEASE</version>
</dependency>
</dependency>
<!--导入spring的tx坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.5.RELEASE</version>
</dependency>
//1、创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");
//2、创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//3、设置数据源给JdbcTemplate
jdbcTemplate.setDataSource(dataSource);
//4、执行操作
jdbcTemplate.update("insert into account values(?,?)","tom",5000);
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");
//2、创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//3、设置数据源给JdbcTemplate
jdbcTemplate.setDataSource(dataSource);
//4、执行操作
jdbcTemplate.update("insert into account values(?,?)","tom",5000);
SpringMVC拦截器
概念
Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理
拦截器和过滤器的区别
快速入门
① 创建拦截器类实现HandlerInterceptor接口
② 配置拦截器
③ 测试拦截器的拦截效果
② 配置拦截器
③ 测试拦截器的拦截效果
public class MyHandlerInterceptor1 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) {
System.out.println("preHandle running...");
return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler, ModelAndView modelAndView) {
System.out.println("postHandle running...");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse
response, Object handler, Exception ex) {
System.out.println("afterCompletion running...");
} }
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) {
System.out.println("preHandle running...");
return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler, ModelAndView modelAndView) {
System.out.println("postHandle running...");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse
response, Object handler, Exception ex) {
System.out.println("afterCompletion running...");
} }
<!--配置拦截器--> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/>
<bean class="com.itheima.interceptor.MyHandlerInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
<bean class="com.itheima.interceptor.MyHandlerInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
@RequestMapping("/quick23") @ResponseBody
public ModelAndView quickMethod23() throws IOException, ParseException {
System.out.println("目标方法执行....");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("name","itcast");
modelAndView.setViewName("index");
return modelAndView; }
public ModelAndView quickMethod23() throws IOException, ParseException {
System.out.println("目标方法执行....");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("name","itcast");
modelAndView.setViewName("index");
return modelAndView; }
拦截器方法说明
SpringMVC异常处理机制
异常处理的思路
预期异常
运行时异常RuntimeException
异常处理两种方式
使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver
实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器
自定义异常处理步骤
① 创建异常处理器类实现HandlerExceptionResolver
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
//处理异常的代码实现
//创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("exceptionPage");
return modelAndView;
} }
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
//处理异常的代码实现
//创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("exceptionPage");
return modelAndView;
} }
② 配置异常处理器
<bean id="exceptionResolver"
class="com.itheima.exception.MyExceptionResolver"/>
class="com.itheima.exception.MyExceptionResolver"/>
③ 编写异常页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html> <head><title>Title</title>
</head> <body>
这是一个最终异常的显示页面
</body>
</html>
<html> <head><title>Title</title>
</head> <body>
这是一个最终异常的显示页面
</body>
</html>
④ 测试异常跳转
@RequestMapping("/quick22")
@ResponseBody
public void quickMethod22() throws IOException, ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
simpleDateFormat.parse("abcde");
}
@ResponseBody
public void quickMethod22() throws IOException, ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
simpleDateFormat.parse("abcde");
}
知识要点
异常处理方式
配置简单异常处理器SimpleMappingExceptionResolver
自定义异常处理器
自定义异常处理器
自定义异常处理步骤
① 创建异常处理器类实现HandlerExceptionResolver
② 配置异常处理器
③ 编写异常页面
④ 测试异常跳转
② 配置异常处理器
③ 编写异常页面
④ 测试异常跳转
MyBatis
MyBatis的简介
mybatis 是一个优秀的基于java的持久层框架,它内部封装了
jdbc,使开发者只需要关注sql语句本身,而不需要花费精力
去处理加载驱动、创建连接、创建statement等繁杂的过程。
mybatis通过xml或注解的方式将要执行的各种 statement配
置起来,并通过java对象和statement中sql的动态参数进行
映射生成最终执行的sql语句。
最后mybatis框架执行sql并将结果映射为java对象并返回。采
用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了
封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api
打交道,就可以完成对数据库的持久化操作。
jdbc,使开发者只需要关注sql语句本身,而不需要花费精力
去处理加载驱动、创建连接、创建statement等繁杂的过程。
mybatis通过xml或注解的方式将要执行的各种 statement配
置起来,并通过java对象和statement中sql的动态参数进行
映射生成最终执行的sql语句。
最后mybatis框架执行sql并将结果映射为java对象并返回。采
用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了
封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api
打交道,就可以完成对数据库的持久化操作。
MyBatis的快速入门
① 添加MyBatis的坐标
② 创建user数据表
③ 编写User实体类
public class User {
private int id;
private String username;
private String password;
//省略get个set方法
}
private int id;
private String username;
private String password;
//省略get个set方法
}
④ 编写映射文件UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="userMapper">
<select id="findAll" resultType="com.itheima.domain.User">
select * from User
</select>
</mapper>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="userMapper">
<select id="findAll" resultType="com.itheima.domain.User">
select * from User
</select>
</mapper>
⑤ 编写核心文件SqlMapConfig.xml
⑥ 编写测试类
//加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//获得sqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行sql语句
List<User> userList = sqlSession.selectList("userMapper.findAll");
//打印结果
System.out.println(userList);
//释放资源
sqlSession.close();
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//获得sqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行sql语句
List<User> userList = sqlSession.selectList("userMapper.findAll");
//打印结果
System.out.println(userList);
//释放资源
sqlSession.close();
MyBatis的映射文件概述
MyBatis的增删改查操作
1. 编写UserMapper映射文件
<mapper namespace="userMapper">
<insert id="add" parameterType="com.itheima.domain.User">
insert into user values(#{id},#{username},#{password})
</insert>
</mapper>
<insert id="add" parameterType="com.itheima.domain.User">
insert into user values(#{id},#{username},#{password})
</insert>
</mapper>
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int insert = sqlSession.insert("userMapper.add", user);
System.out.println(insert);
//提交事务
sqlSession.commit();
sqlSession.close();
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int insert = sqlSession.insert("userMapper.add", user);
System.out.println(insert);
//提交事务
sqlSession.commit();
sqlSession.close();
<mapper namespace="userMapper">
<update id="update" parameterType="com.itheima.domain.User">
update user set username=#{username},password=#{password} where id=#{id}
</update>
</mapper>
<update id="update" parameterType="com.itheima.domain.User">
update user set username=#{username},password=#{password} where id=#{id}
</update>
</mapper>
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int update = sqlSession.update("userMapper.update", user);
System.out.println(update);
sqlSession.commit();
sqlSession.close();
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int update = sqlSession.update("userMapper.update", user);
System.out.println(update);
sqlSession.commit();
sqlSession.close();
<mapper namespace="userMapper"> <delete id="delete" parameterType="java.lang.Integer">
delete from user where id=#{id}
</delete>
</mapper>
delete from user where id=#{id}
</delete>
</mapper>
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int delete = sqlSession.delete("userMapper.delete",3);
System.out.println(delete);
sqlSession.commit();
sqlSession.close()
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int delete = sqlSession.delete("userMapper.delete",3);
System.out.println(delete);
sqlSession.commit();
sqlSession.close()
MyBatis的核心配置文件概述
MyBatis核心配置文件层级关系
MyBatis常用配置解析
environments标签
mapper标签
该标签的作用是加载映射的,加载方式有如下几种:
• 使用相对于类路径的资源引用,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
• 使用完全限定资源定位符(URL),例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
• 使用映射器接口实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>
• 将包内的映射器接口实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>
• 使用相对于类路径的资源引用,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
• 使用完全限定资源定位符(URL),例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
• 使用映射器接口实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>
• 将包内的映射器接口实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>
Properties标签
typeAliases标签
MyBatis的相应API
SqlSession工厂构建器SqlSessionFactoryBuilder
常用API:SqlSessionFactory build(InputStream inputStream)
通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
SqlSession工厂对象SqlSessionFactory
SqlSession会话对象
Mybatis的DAO层
传统开发方式
编写UserDao接口
public interface UserDao {
List<User> findAll() throws IOException; }
List<User> findAll() throws IOException; }
编写UserDaoImpl实现
public class UserDaoImpl implements UserDao {
public List<User> findAll() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> userList = sqlSession.selectList("userMapper.findAll");
sqlSession.close();
return userList; } }
public List<User> findAll() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> userList = sqlSession.selectList("userMapper.findAll");
sqlSession.close();
return userList; } }
测试传统方式
@Test
public void testTraditionDao() throws IOException {
UserDao userDao = new UserDaoImpl();
List<User> all = userDao.findAll();
System.out.println(all);
}
public void testTraditionDao() throws IOException {
UserDao userDao = new UserDaoImpl();
List<User> all = userDao.findAll();
System.out.println(all);
}
代理开发方式
采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接
口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper 接口开发需要遵循以下规范:
1、 Mapper.xml文件中的namespace与mapper接口的全限定名相同
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
4、 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接
口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper 接口开发需要遵循以下规范:
1、 Mapper.xml文件中的namespace与mapper接口的全限定名相同
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
4、 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
编写UserMapper接口
测试代理方式
@Test
public void testProxyDao() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findById(1);
System.out.println(user);
sqlSession.close();
}
public void testProxyDao() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findById(1);
System.out.println(user);
sqlSession.close();
}
子主题
动态sql语句
概念
Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,
此时在前面的学习中我们的 SQL 就不能满足要求了。
此时在前面的学习中我们的 SQL 就不能满足要求了。
动态 SQL 之<if>
我们根据实体类的不同取值,使用不同的 SQL语句来进行查
询。比如在 id如果不为空时可以根据id查询,如果username
不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到
询。比如在 id如果不为空时可以根据id查询,如果username
不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到
当查询条件id和username都存在时,控制台打印的sql语句如下:
… … …
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
condition.setUsername("lucy");
User user = userMapper.findByCondition(condition);
… … …
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
condition.setUsername("lucy");
User user = userMapper.findByCondition(condition);
… … …
当只有id时
动态 SQL 之<foreach>
SQL片段抽取
Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的
MyBatis核心配置文件深入
typeHandlers标签
① 定义转换类继承类BaseTypeHandler<T>
public class MyDateTypeHandler extends BaseTypeHandler<Date> {
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType type)
{
preparedStatement.setString(i,date.getTime()+"");
}
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
return new Date(resultSet.getLong(s));
}
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
return new Date(resultSet.getLong(i));
}
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return callableStatement.getDate(i);
} }
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType type)
{
preparedStatement.setString(i,date.getTime()+"");
}
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
return new Date(resultSet.getLong(s));
}
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
return new Date(resultSet.getLong(i));
}
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return callableStatement.getDate(i);
} }
② 覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult
为查询时 mysql的字符串类型转换成 java的Type类型的方法
为查询时 mysql的字符串类型转换成 java的Type类型的方法
③ 在MyBatis核心配置文件中进行注册
④ 测试转换是否正确
plugins标签
① 导入通用PageHelper的坐标
<!-- 分页助手 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId> <version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId> <version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId> <version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId> <version>0.9.1</version>
</dependency>
② 在mybatis核心配置文件中配置PageHelper插件
<!-- 注意:分页助手的插件 配置在通用馆mapper之前 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
③ 测试分页数据获取
@Test
public void testPageHelper(){
//设置分页参数
PageHelper.startPage(1,2);
List<User> select = userMapper2.select(null);
for(User user : select){
System.out.println(user);
} }
public void testPageHelper(){
//设置分页参数
PageHelper.startPage(1,2);
List<User> select = userMapper2.select(null);
for(User user : select){
System.out.println(user);
} }
分页其它相关参数
//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
MyBatis的多表操作
一对一
一对多
select *,o.id oid from user u left join orders o on u.id=o.uid;
public interface UserMapper {
List<User> findAll();
}
List<User> findAll();
}
配置UserMapper.xml
<mapper namespace="com.itheima.mapper.UserMapper">
<resultMap id="userMap" type="com.itheima.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="orderList" ofType="com.itheima.domain.Order">
<result column="oid" property="id"></result>
<result column="ordertime" property="ordertime">
</result> <result column="total" property="total"></result>
</collection>
</resultMap> <select id="findAll" resultMap="userMap">
select *,o.id oid from user u left join orders o on u.id=o.uid
</select>
</mapper>
<mapper namespace="com.itheima.mapper.UserMapper">
<resultMap id="userMap" type="com.itheima.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="orderList" ofType="com.itheima.domain.Order">
<result column="oid" property="id"></result>
<result column="ordertime" property="ordertime">
</result> <result column="total" property="total"></result>
</collection>
</resultMap> <select id="findAll" resultMap="userMap">
select *,o.id oid from user u left join orders o on u.id=o.uid
</select>
</mapper>
多对多
MyBatis注解开发
MyBatis常用注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
MyBatis的增删改查
修改MyBatis的核心配置文件,我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可
<mappers>
<!--扫描使用注解的类--> <mapper class="com.itheima.mapper.UserMapper"></mapper>
</mappers>
<mappers>
<!--扫描使用注解的类--> <mapper class="com.itheima.mapper.UserMapper"></mapper>
</mappers>
或者指定扫描包含映射关系的接口所在的包也可以
<mappers>
<!--扫描使用注解的类所在的包--> <package name="com.itheima.mapper"></package>
</mappers>
<mappers>
<!--扫描使用注解的类所在的包--> <package name="com.itheima.mapper"></package>
</mappers>
MyBatis的注解实现复杂映射开发
一对一
一对多
多对多
SSM整合
原始方式整合
创建数据库
创建Maven工程
导入Maven坐标
编写实体类
编写Mapper接口
编写Service接口
编写Service接口实现
编写Controller
编写添加页面
编写列表页面
编写相应配置文件
测试
Spring整合MyBatis
整合思路
将SqlSessionFactory配置到Spring容器中
扫描Mapper,让Spring容器产生Mapper实现类
<!--配置Mapper扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.mapper"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.mapper"/>
</bean>
配置声明式事务控制
<!--配置声明式事务控制-->
<bean id="transacionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transacionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(*
com.itheima.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<bean id="transacionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transacionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(*
com.itheima.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
修改Service实现类代码
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
public void save(Account account) {
accountMapper.save(account);
}
public List<Account> findAll() {
return accountMapper.findAll();
} }
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
public void save(Account account) {
accountMapper.save(account);
}
public List<Account> findAll() {
return accountMapper.findAll();
} }
MyBatis Plus
Mybatis-Plus介绍
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,
在 MyBatis 的基础上只做增强不做改变,为简化开发、提高
效率而生。
在 MyBatis 的基础上只做增强不做改变,为简化开发、提高
效率而生。
特性
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,
更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、
SQLServer2005、SQLServer 等多种数据库
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解
决主键问题
支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操 作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
支持关键词自动转义:支持数据库关键词(order、key......)自动转义,还可自定义关键词
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,
支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List
查询
内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,
更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、
SQLServer2005、SQLServer 等多种数据库
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解
决主键问题
支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操 作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
支持关键词自动转义:支持数据库关键词(order、key......)自动转义,还可自定义关键词
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,
支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List
查询
内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击
架构
author
Mybatis-Plus是由baomidou(苞米豆)组织开发并且开源的,目前该组织大概有30人左右
Quick start
创建数据库以及表
CREATE TABLE `tb_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `user_name` varchar(20) NOT NULL COMMENT '用户名', `password` varchar(20) NOT NULL COMMENT '密码', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 插入测试数据 INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('1', 'zhangsan', '123456', '张三', '18', 'test1@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('2', 'lisi', '123456', '李四', '20', 'test2@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('3', 'wangwu', '123456', '王五', '28', 'test3@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('4', 'zhaoliu', '123456', '赵六', '21', 'test4@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('5', 'sunqi', '123456', '孙七', '24', 'test5@itcast.cn');
-- 插入测试数据 INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('1', 'zhangsan', '123456', '张三', '18', 'test1@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('2', 'lisi', '123456', '李四', '20', 'test2@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('3', 'wangwu', '123456', '王五', '28', 'test3@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('4', 'zhaoliu', '123456', '赵六', '21', 'test4@itcast.cn'); INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('5', 'sunqi', '123456', '孙七', '24', 'test5@itcast.cn');
创建工程
导入依赖
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.itcast.mp</groupId> <artifactId>itcast-mybatis-plus</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>itcast-mybatis-plus-simple</module> </modules> <packaging>pom</packaging> <dependencies> <!-- mybatis-plus插件依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.1.1</version> </dependency> <!-- MySql --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.11</version> </dependency> <!--简化bean代码的工具包--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <version>1.18.4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
Mybatis + MP
创建子Module
log4j.properties
log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
Mybatis实现查询User
Mybatis+MP实现查询User
第一步,将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有方法:
第二步,使用MP中的MybatisSqlSessionFactoryBuilder进程构建:
运行报错解决
Spring + Mybatis + MP
创建子Module
实现查询User
编写jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL =false jdbc.username=root jdbc.password=root
编写applicationContext.xml
编写User对象以及UserMapper接口
SpringBoot + Mybatis + MP
使用SpringBoot将进一步的简化MP的整合,需要注意的是,
由于使用SpringBoot需要继承parent,所以需要重新创建工程,并不是创建子Module。
由于使用SpringBoot需要继承parent,所以需要重新创建工程,并不是创建子Module。
新建工程
导入依赖
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent> <groupId>cn.itcast.mp</groupId> <artifactId>itcast-mp-springboot</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--简化代码的工具包--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--mybatis-plus的springboot支持--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.1</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--简化代码的工具包--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--mybatis-plus的springboot支持--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.1</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
log4j.properties:
log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
编写application.properties
spring.application.name = itcast-mp-springboot
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL =false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL =false
spring.datasource.username=root
spring.datasource.password=root
编写pojo
编写mapper
启动类mapper扫描
测试
通用CRUD
@TableField
解释:
在MP中通过@TableField注解可以指定字段的一些属性,常常解决的问题有2个:
1、对象中的属性名和字段名不一致的问题(非驼峰)
2、对象中的属性字段在表中不存在的问题
在MP中通过@TableField注解可以指定字段的一些属性,常常解决的问题有2个:
1、对象中的属性名和字段名不一致的问题(非驼峰)
2、对象中的属性字段在表中不存在的问题
更新操作
通过QueryWrapper
通过UpdateWrapper
删除操作
deleteById
deleteByMap
@Test
public void testDeleteByMap() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("age",20);
columnMap.put("name","张三");
//将columnMap中的元素设置为删除的条件,多个之间为and关系
int result = this.userMapper.deleteByMap(columnMap);
System.out.println("result = " + result);
}
public void testDeleteByMap() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("age",20);
columnMap.put("name","张三");
//将columnMap中的元素设置为删除的条件,多个之间为and关系
int result = this.userMapper.deleteByMap(columnMap);
System.out.println("result = " + result);
}
delete
@Test
public void testDeleteByMap() {
User user = new User();
user.setAge(20);
user.setName("张三");
//将实体对象进行包装,包装为操作条件
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
int result = this.userMapper.delete(wrapper);
System.out.println("result = " + result);
}
public void testDeleteByMap() {
User user = new User();
user.setAge(20);
user.setName("张三");
//将实体对象进行包装,包装为操作条件
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
int result = this.userMapper.delete(wrapper);
System.out.println("result = " + result);
}
deleteBatchIds根据id集合批量删除
查询操作
selectById
selectBatchIds
selectOne
selectCount
selectList
selectPage
@Configuration
@MapperScan("cn.itcast.mp.mapper")
//设置mapper接口的扫描包
public class MybatisPlusConfig {
/*** 分页插件 */
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
@MapperScan("cn.itcast.mp.mapper")
//设置mapper接口的扫描包
public class MybatisPlusConfig {
/*** 分页插件 */
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
SQL注入的原理
在MP中,ISqlInjector负责SQL的注入工作,它是一个接口,AbstractSqlInjector是它的实现类
配置
基本配置
configLocation
mapperLocations
子主题
typeAliasesPackage
进阶配置
mapUnderscoreToCamelCase
类型: boolean
默认值: true
默认值: true
是否开启自动驼峰命名规则(camel case)映射,
即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属
性名 aColumn(驼峰命名) 的类似映射。
即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属
性名 aColumn(驼峰命名) 的类似映射。
注意
此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body
如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
#关闭自动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
mybatis-plus.configuration.map-underscore-to-camel-case=false
mybatis-plus.configuration.map-underscore-to-camel-case=false
cacheEnabled
类型: boolean
默认值: true
默认值: true
全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true。
mybatis-plus.configuration.cache-enabled=false 1
DB 策略配置
idType
类型: com.baomidou.mybatisplus.annotation.IdType
默认值: ID_WORKER
默认值: ID_WORKER
全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置
tablePrefix
类型: String
默认值: null
默认值: null
条件构造器
allEq
说明:
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
基本比较操作
eq
等于 =
ne
不等于 <>
gt
大于 >
ge
大于等于 >=
lt
小于 <
le
小于等于 <=
between
BETWEEN 值1 AND 值2
notBetween
NOT BETWEEN 值1 AND 值2
in
字段 IN (value.get(0), value.get(1), ...)
notIn
字段 NOT IN (v0, v1, ...)
等于 =
ne
不等于 <>
gt
大于 >
ge
大于等于 >=
lt
小于 <
le
小于等于 <=
between
BETWEEN 值1 AND 值2
notBetween
NOT BETWEEN 值1 AND 值2
in
字段 IN (value.get(0), value.get(1), ...)
notIn
字段 NOT IN (v0, v1, ...)
模糊查询
like
LIKE '%值%'
例: like("name", "王") ---> name like '%王%'
notLike
NOT LIKE '%值%'
LIKE '%值%'
例: like("name", "王") ---> name like '%王%'
notLike
NOT LIKE '%值%'
排序
orderBy
逻辑查询
or
拼接 OR
主动调用 or 表示紧接着下一个方法不是用 and 连接!(不调用 or 则默认为使用 and 连接)
and
AND 嵌套
例: and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status <> '活着')
and
AND 嵌套
例: and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status <> '活着')
select
在MP查询中,默认查询所有的字段,如果有需要也可以通过select方法进行指定字段
ActiveRecord
ActiveRecord(简称AR)一直广受动态语言( PHP 、 Ruby 等)的喜爱
ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记
录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而
且简洁易懂
录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而
且简洁易懂
主要思想
每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段
在类中都有相应的Field;
ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;;
ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑;
在类中都有相应的Field;
ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;;
ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑;
在MP中,开启AR非常简单,只需要将实体对象继承Model即可
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User extends Model<User> {
@NoArgsConstructor
@AllArgsConstructor
public class User extends Model<User> {
根据主键查询
新增数据
更新操作
删除操作
根据条件查询
Mybatis-Plus的插件
mybatis的插件机制
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。
默认情况下,MyBatis 允许使用插件来拦截的方法
调用包括:
1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2. ParameterHandler (getParameterObject, setParameters)
3. ResultSetHandler (handleResultSets, handleOutputParameters)
4. StatementHandler (prepare, parameterize, batch, update, query)
默认情况下,MyBatis 允许使用插件来拦截的方法
调用包括:
1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2. ParameterHandler (getParameterObject, setParameters)
3. ResultSetHandler (handleResultSets, handleOutputParameters)
4. StatementHandler (prepare, parameterize, batch, update, query)
1. 拦截执行器的方法
2. 拦截参数的处理
3. 拦截结果集的处理
4. 拦截Sql语法构建的处理
示例
注入到spring容器中
/*** 自定义拦截器 */
@Bean
public MyInterceptor myInterceptor(){ return new MyInterceptor(); }
@Bean
public MyInterceptor myInterceptor(){ return new MyInterceptor(); }
或者通过xml配置,mybatis-config.xml
执行分析插件
在MP中提供了对SQL执行的分析的插件,可用作阻断全表更新、删除的操作,
注意:该插件仅适用于开发环境,不适用于生产环境。
注意:该插件仅适用于开发环境,不适用于生产环境。
springboot配置
当执行全表更新时,会抛出异常,这样有效防止了一些误操作
性能分析插件
性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间,超过时间会抛出异常
配置
乐观锁插件
主要适用场景
意图:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
插件配置
spring xml:
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>
spring boot:
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); }
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>
spring boot:
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); }
注解实体字段
特别说明
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1 newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
整数类型下 newVersion = oldVersion + 1 newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
Sql 注入器实现自定义全局操作
编写MyBaseMapper
其他的Mapper都可以继承该Mapper,这样实现了统一的扩展。
编写MySqlInjector
如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的方法将失效,
所以我们选择继承DefaultSqlInjector进行扩展
所以我们选择继承DefaultSqlInjector进行扩展
编写FindAll
注册到Spring容器
测试
自动填充功能
添加@TableField注解
编写MyMetaObjectHandler
逻辑删除
开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删除,而并非真正
的物理删除(非DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免
数据被真正的删除。
的物理删除(非DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免
数据被真正的删除。
修改表结构
配置
测试
结果
通用枚举
修改表结构
定义枚举
配置
修改实体
测试
结果
代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper
XML、Service、Controller 等各个模块的代码,极大的提升了开发效率
XML、Service、Controller 等各个模块的代码,极大的提升了开发效率
创建工程
pom.xml
代码
MybatisX 快速开发插件
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装
功能
Java 与 XML 调回跳转
Mapper 方法自动生成 XML
Mapper 方法自动生成 XML
SpringBoot
基础
quickstart
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程
创建过程
创建SpringBoot工程的四种方式
基于Idea创建SpringBoot工程
基于官网创建SpringBoot工程
基于阿里云创建SpringBoot工程
手工创建Maven工程修改为SpringBoot工程
基于官网创建SpringBoot工程
基于阿里云创建SpringBoot工程
手工创建Maven工程修改为SpringBoot工程
Idea中隐藏指定文件或指定类型文件
Setting → File Types → Ignored Files and Folders
输入要隐藏的文件名,支持*号通配符
回车确认添加
输入要隐藏的文件名,支持*号通配符
回车确认添加
springboot程序优点相较于spring
起步依赖(简化依赖配置)
自动配置(简化常用工程相关配置)
辅助功能(内置服务器,……)
自动配置(简化常用工程相关配置)
辅助功能(内置服务器,……)
入门案例解析
parent
1. 开发SpringBoot程序要继承spring-boot-starter-parent
2. spring-boot-starter-parent中定义了若干个依赖管理
3. 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
4. 继承parent的形式也可以采用引入依赖的形式实现效果
2. spring-boot-starter-parent中定义了若干个依赖管理
3. 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
4. 继承parent的形式也可以采用引入依赖的形式实现效果
starter
SpringBoot中常见项目名称,定义了当前项目使用的所有依赖坐标,以达到减少依赖配置的目的
引导类
内置tomcat
基础配置
复制工程
原则
保留工程基础结构
抹掉原始工程痕迹
保留工程基础结构
抹掉原始工程痕迹
1. 在工作空间中复制对应工程,并修改工程名称
2. 删除与Idea相关配置文件,仅保留src目录与pom.xml文件
3. 修改pom.xml文件中的artifactId与新工程/模块名相同
4. 删除name标签(可选)
5. 保留备份工程供后期使用
2. 删除与Idea相关配置文件,仅保留src目录与pom.xml文件
3. 修改pom.xml文件中的artifactId与新工程/模块名相同
4. 删除name标签(可选)
5. 保留备份工程供后期使用
属性配置
文件加载顺序
yaml
yaml语法规则
使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
yaml数据读取
1. 使用@ConfigurationProperties注解绑定配置信息到封装类中
2. 封装类需要定义为Spring管理的bean,否则无法进行属性注入
2. 封装类需要定义为Spring管理的bean,否则无法进行属性注入
自动提升功能消失解决方案
整合第三方技术
整合JUnit
1. 导入测试对应的starter
2. 测试类使用@SpringBootTest修饰
3. 使用自动装配的形式添加要测试的对象
2. 测试类使用@SpringBootTest修饰
3. 使用自动装配的形式添加要测试的对象
1. 测试类如果存在于引导类所在包或子包中无需指定引导类
2. 测试类如果不存在于引导类所在的包或子包中需要通过classes
属性指定引导类
2. 测试类如果不存在于引导类所在的包或子包中需要通过classes
属性指定引导类
整合MyBatis
核心配置:数据库连接相关信息(连什么?连谁?什么权限)
映射配置:SQL映射(XML/注解)
映射配置:SQL映射(XML/注解)
驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
整合MyBatis-Plus
整合Druid
基于SpringBoot的SSMP整合案例
实体类开发————使用Lombok快速制作实体类
常用注解@Data
Dao开发————整合MyBatisPlus,制作数据层测试类
导入MyBatisPlus与Druid对应的starter
配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)
继承BaseMapper并指定泛型继承BaseMapper并指定泛型
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
public interface BookDao extends BaseMapper<Book> {
}
开启日志
分页功能
分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动
态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现
态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现
条件查询功能
Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
定义接口
实现
定义实现类
快速开发方案
使用MyBatisPlus提供有业务层通用接口(ISerivce<T>)与业务层通用实现类(ServiceImpl<M,T>)
在通用类基础上做功能重载或功能追加
注意重载时不要覆盖原始操作,避免原始提供的功能丢失
在通用类基础上做功能重载或功能追加
注意重载时不要覆盖原始操作,避免原始提供的功能丢失
Controller开发————基于Restful开发,使用PostMan测试接口功能
Controller开发————前后端开发协议制作
页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
列表、新增、修改、删除、分页、查询
项目异常处理
按条件查询————页面功能调整、Controller修正功能、Service修正功能
运维实用
打包与运行
• 程序打包与运行(Windows版)
步骤
①:对SpringBoot项目打包(执行Maven构建指令package)
mvn package
mvn package
②:运行项目(执行启动指令)
java –jar springboot.jar
java –jar springboot.jar
jar支持命令行启动需要依赖maven插件支持,请确认打包时是否具有SpringBoot对应的maven插件
jar包描述文件(MANIFEST.MF)
普通工程
基于spring-boot-maven-plugin打包的工程
命令行启动常见问题及解决方案
# 查询端口
netstat -ano
# 查询指定端口
netstat -ano |findstr "端口号"
# 根据进程PID查询进程名称
tasklist |findstr "进程PID号"
# 根据PID杀死任务
taskkill /F /PID "进程PID号"
# 根据进程名称杀死任务
taskkill -f -t -im "进程名称"
netstat -ano
# 查询指定端口
netstat -ano |findstr "端口号"
# 根据进程PID查询进程名称
tasklist |findstr "进程PID号"
# 根据PID杀死任务
taskkill /F /PID "进程PID号"
# 根据进程名称杀死任务
taskkill -f -t -im "进程名称"
• 程序运行(Linux版)
基于Linux(CenterOS7)
安装JDK,且版本不低于打包时使用的JDK版本
安装包保存在/usr/local/自定义目录中或$HOME下
其他操作参照Windows版进行
安装JDK,且版本不低于打包时使用的JDK版本
安装包保存在/usr/local/自定义目录中或$HOME下
其他操作参照Windows版进行
配置高级
• 临时属性设置
属性加载优先顺序
看https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config
开发环境
• 配置文件分类
SpringBoot中4级配置文件
1级: file :config/application.yml 【最高】
2级: file :application.yml
3级:classpath:config/application.yml
4级:classpath:application.yml 【最低】
2级: file :application.yml
3级:classpath:config/application.yml
4级:classpath:application.yml 【最低】
作用:
1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控
3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控
3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控
• 自定义配置文件
通过启动参数加载配置文件(无需书写配置文件扩展名)
通过启动参数加载指定文件路径下的配置文件
通过启动参数加载指定文件路径下的配置文件时可以加载多个配置
单服务器项目:使用自定义配置文件需求较低
多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息
多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息
多环境开发
• 多环境开发(YAML版)
• 多环境开发(Properties版)
1. 主启动配置文件application.yml
spring:
profiles:
active: dev
2. 环境分类配置文件application-pro.yml
server:
port: 80
3. 环境分类配置文件application-dev.yml
server:
port: 81
4. 环境分类配置文件application-test.yml
server:
port: 82
spring:
profiles:
active: dev
2. 环境分类配置文件application-pro.yml
server:
port: 80
3. 环境分类配置文件application-dev.yml
server:
port: 81
4. 环境分类配置文件application-test.yml
server:
port: 82
多环境开发配置文件书写技巧(一)
主配置文件中设置公共配置(全局)
环境分类配置文件中常用于设置冲突属性(局部)
环境分类配置文件中常用于设置冲突属性(局部)
主启动配置文件application.properties
spring.profiles.active=pro
环境分类配置文件application-pro.properties
server.port=80
环境分类配置文件application-dev.properties
server.port=81
环境分类配置文件application-test.properties
server.port=82
spring.profiles.active=pro
环境分类配置文件application-pro.properties
server.port=80
环境分类配置文件application-dev.properties
server.port=81
环境分类配置文件application-test.properties
server.port=82
多环境开发独立配置文件书写技巧(二)
根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下
application-devDB.yml
application-devRedis.yml
application-devMVC.yml
application-devDB.yml
application-devRedis.yml
application-devMVC.yml
多环境开发使用group属性设置配置文件分组,便于线上维护管理
• 多环境开发控制
Maven与SpringBoot多环境兼容
Maven中设置多环境属性
SpringBoot中引用Maven属性
1. 当Maven与SpringBoot同时对多环境进行控制时,以Mavn为主,
SpringBoot使用@..@占位符读取Maven对应的配置属性值
2. 基于SpringBoot读取Maven配置属性的前提下,如果在Idea下测试
工程时pom.xml每次更新需要手动compile方可生效
SpringBoot使用@..@占位符读取Maven对应的配置属性值
2. 基于SpringBoot读取Maven配置属性的前提下,如果在Idea下测试
工程时pom.xml每次更新需要手动compile方可生效
日志
• 日志基础
作用
1 编程期调试代码
2 运营期记录信息
记录日常运营重要信息(峰值流量、平均响应时长……)
记录应用报错信息(错误堆栈)
记录运维过程数据(扩容、宕机、报警……)
2 运营期记录信息
记录日常运营重要信息(峰值流量、平均响应时长……)
记录应用报错信息(错误堆栈)
记录运维过程数据(扩容、宕机、报警……)
添加日志操作记录
日志级别
TRACE:运行堆栈信息,使用率低
DEBUG:程序员调试代码使用
INFO:记录运维过程数据
WARN:记录运维过程报警数据
ERROR:记录错误堆栈信息
FATAL:灾难信息,合并计入ERROR
DEBUG:程序员调试代码使用
INFO:记录运维过程数据
WARN:记录运维过程报警数据
ERROR:记录错误堆栈信息
FATAL:灾难信息,合并计入ERROR
设置日志输出级别
设置日志组,控制指定包对应的日志输出级别,也可以直接控制指定包对应的日志输出级别
优化日志对象创建代码
使用lombok提供的注解@Slf4j简化开发,减少日志对象的声明操作
• 日志输出格式控制
设置日志输出格式
• 日志文件
设置日志文件
日志文件详细配置
开发实用
热部署
手动启动热部署
开启开发者工具
激活热部署:Ctrl + F9
重启(Restart):自定义开发代码,包含类、页面、配置文件等,加载位置restart类加载器
重载(ReLoad):jar包,加载位置base类加载器
重载(ReLoad):jar包,加载位置base类加载器
自动启动热部署
激活方式:Idea失去焦点5秒后启动热部署
热部署范围配置
自定义不参与重启排除项
默认不触发重启的目录列表
/META-INF
/maven
/META-INF/resources
/resources
/static
/public
/templates
/maven
/META-INF/resources
/resources
/static
/public
/templates
自定义不参与重启排除项
关闭热部署
禁用热部署
配置高级
@ConfigurationProperties
使用@ConfigurationProperties为第三方bean绑定属性
@EnableConfigurationProperties
可以将使用@ConfigurationProperties注解对应的类加入Spring容器
@EnableConfigurationProperties与@Component不能同时使用
宽松绑定/松散绑定
@ConfigurationProperties绑定属性支持属性名宽松绑定
宽松绑定不支持注解@Value引用单个属性的方式
绑定前缀名命名规范:仅能使用纯小写字母、数字、下划线作为合法的字符
常用计量单位绑定
SpringBoot支持JDK8提供的时间与空间计量单位
JDK8支持的时间与空间计量单位
数据校验
开启数据校验有助于系统安全性,J2EE规范中JSR303规范定义了一组有关数据校验相关的API
开启Bean数据校验
添加JSR303规范坐标与Hibernate校验框架对应坐标
对bean开启校验功能
设置校验规则
测试
加载测试专用属性
在启动测试环境时可以通过properties参数设置测试环境专用的属性
优势:
比多环境开发中的测试环境影响范围更小,仅对当前测试类有效
比多环境开发中的测试环境影响范围更小,仅对当前测试类有效
在启动测试环境时可以通过args参数设置测试环境专用的传入参数
加载测试专用配置
使用@Import注解加载当前测试类专用的配置
Web环境模拟测试
模拟端口
子主题
虚拟请求测试
虚拟请求状态匹配
虚拟请求响应体匹配
虚拟请求响应体(json)匹配
虚拟请求响应头匹配
数据层测试回滚
为测试用例添加事务,SpringBoot会对测试用例对应的事务提交操作进行回滚
如果想在测试用例中提交事务,可以通过@Rollback注解设置
测试用例数据设定
测试用例数据通常采用随机值进行测试,使用SpringBoot提供的随机数为其赋值
数据层解决方案
SQL
数据源配置格式
格式一
格式二
数据源配置
SpringBoot提供了3种内嵌的数据源对象供开发者选择
HikariCP:默认内置数据源对象
Tomcat提供DataSource HikariCP不可用的情况下,且在web环境中,将使用tomcat服务器配置的数据源对象
Commons DBCP Hikari不可用,tomcat数据源也不可用,将使用dbcp数据源
Tomcat提供DataSource HikariCP不可用的情况下,且在web环境中,将使用tomcat服务器配置的数据源对象
Commons DBCP Hikari不可用,tomcat数据源也不可用,将使用dbcp数据源
通用配置无法设置具体的数据源配置信息,仅提供基本的连接相关配置,如需配置,在下一级配置中设置具体设定
数据层解决方案
内置持久化解决方案——JdbcTemplate
jdbc-template配置
内嵌数据库
SpringBoot提供了3种内嵌数据库供开发者选择,提高开发测试效率
H2
HSQL
Derby
H2
HSQL
Derby
H2
导入坐标
操作数据库创建表
create table tbl_book (id int,name varchar,type varchar,description varchar)
create table tbl_book (id int,name varchar,type varchar,description varchar)
设置访问数据源
H2数据库控制台仅用于开发阶段,线上项目请务必关闭控制台功能
SpringBoot可以根据url地址自动识别数据库种类,在保障驱动类存在的情况下,可以省略配置
NoSQL
Redis
Mongo
ES
Solr
Mongo
ES
Solr
Redis
Redis是一款key-value存储结构的内存级NoSQL数据库
支持多种数据存储格式
支持持久化
支持集群
支持多种数据存储格式
支持持久化
支持集群
导入SpringBoot整合Redis坐标
配置Redis(采用默认配置)
RedisTemplate提供操作各种数据存储类型的接口API
客户端:RedisTemplate
客户端:RedisTemplate以对象作为key和value,内部对数据进行序列化
客户端选择:jedis
配置属性
lettcus与jedis区别
MongoDB
MongoDB是一个开源、高性能、无模式的文档型数据库。
NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库
NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库
解压缩后设置数据目录
服务端启动
mongod --dbpath=..\data\db
客户端启动
mongo --host=127.0.0.1 --port=27017
window安装MongoDB问题解决
可视化客户端——Robo 3T
操作
新增
db.集合名称.insert/save/insertOne(文档)
修改
db.集合名称.remove(条件)
删除
db.集合名称.update(条件,{操作种类:{文档}})
基础查询
查询全部:db.集合.find();
查第一条:db.集合.findOne()
查询指定数量文档:db.集合.find().limit(10) //查10条文档
跳过指定数量文档:db.集合.find().skip(20) //跳过20条文档
统计:db.集合.count()
排序:db.集合.sort({age:1}) //按age升序排序
投影:db.集合名称.find(条件,{name:1,age:1}) //仅保留name与age域
条件查询
基本格式:db.集合.find({条件})
模糊查询:db.集合.find({域名:/正则表达式/}) //等同SQL中的like,比like强大,可以执行正则所有规则条件
比较运算:db.集合.find({域名:{$gt:值}}) //等同SQL中的数值比较操作,例如:name>18
包含查询:db.集合.find({域名:{$in:[值1,值2]}}) //等同于SQL中的in条件
连接查询:db.集合.find({$and:[{条件1},{条件2}]}) //等同于SQL中的and、or
模糊查询:db.集合.find({域名:/正则表达式/}) //等同SQL中的like,比like强大,可以执行正则所有规则条件
比较运算:db.集合.find({域名:{$gt:值}}) //等同SQL中的数值比较操作,例如:name>18
包含查询:db.集合.find({域名:{$in:[值1,值2]}}) //等同于SQL中的in条件
连接查询:db.集合.find({$and:[{条件1},{条件2}]}) //等同于SQL中的and、or
导入Mongodb驱动
配置客户端
客户端读写
ES
Solr
整合第三方技术
缓存
定义:
缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
作用
缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能
缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间
使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能
缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间
使用
启用缓存
设置进入缓存的数据
设置读取缓存的数据
设置进入缓存的数据
设置读取缓存的数据
导入依赖
启用缓存
设置数据进入缓存
SpringBoot提供的缓存技术除了提供默认的缓存方案,
还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理
还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理
Generic
JCache
Ehcache
缓存供应商变更:Ehcache
Hazelcast
Infinispan
Couchbase
Redis
Caffeine
Simple
memcached
缓存使用案例——手机验证码
需求
输入手机号获取验证码,组织文档以短信形式发送给用户
(页面模拟)输入手机号和验证码验证结果
(页面模拟)输入手机号和验证码验证结果
需求分析
提供controller,传入手机号,业务层通过手机号计算出独有的6位验证码数据,存入缓存后返回此数据
提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,
返回比对结果
提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,
返回比对结果
开启缓存
业务层接口
业务层设置获取验证码操作,并存储缓存,手机号为key,验证码为value
业务层设置校验验证码操作,校验码通过缓存读取,返回校验结果
实现
缓存供应商变更Ehcache
缓存变更——redis
memcache
下载memcached
地址:https://www.runoob.com/memcached/window-install-memcached.html
地址:https://www.runoob.com/memcached/window-install-memcached.html
memcached客户端选择
Memcached Client for Java:最早期客户端,稳定可靠,用户群广
SpyMemcached:效率更高Xmemcached:并发处理更好
SpringBoot未提供对memcached的整合,需要使用硬编码方式实现客户端初始化管理
Memcached Client for Java:最早期客户端,稳定可靠,用户群广
SpyMemcached:效率更高Xmemcached:并发处理更好
SpringBoot未提供对memcached的整合,需要使用硬编码方式实现客户端初始化管理
jetcache
jetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能
jetCache设定了本地缓存与远程缓存的多级缓存解决方案
本地缓存(local)LinkedHashMap
Caffeine
远程缓存(remote)Redis
Tair
jetCache设定了本地缓存与远程缓存的多级缓存解决方案
本地缓存(local)LinkedHashMap
Caffeine
远程缓存(remote)Redis
Tair
使用
j2cache
j2cache是一个缓存整合框架,可以提供缓存的整合方案,
使各种缓存搭配使用,自身不提供缓存功能基于 ehcache + redis 进行整合
使各种缓存搭配使用,自身不提供缓存功能基于 ehcache + redis 进行整合
配置使用j2cache(application.yml)
任务
定时任务是企业级应用中的常见操作年度报表缓存统计报告… …
市面上流行的定时任务技术
Quartz
Spring Task
市面上流行的定时任务技术
Quartz
Spring Task
SpringBoot整合Quartz
相关概念工作(Job):用于定义具体执行的工作
工作明细(JobDetail):用于描述定时工作相关的信息
触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定义调度规则
调度器(Scheduler):描述了工作明细与触发器的对应关系
工作明细(JobDetail):用于描述定时工作相关的信息
触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定义调度规则
调度器(Scheduler):描述了工作明细与触发器的对应关系
整合
邮件
SpringBoot整合JavaMail
SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于发送电子邮件的传输协议
POP3(Post Office Protocol - Version 3):用于接收电子邮件的标准协议
IMAP(Internet Mail Access Protocol):互联网消息协议,是POP3的替代协议
使用
消息
消息发送方 生产者
消息接收方 消费者
消息接收方 消费者
同步消息
异步消息
企业级应用中广泛使用的三种异步消息传递技术
JMS
JMS(Java Message Service):一个规范,等同于JDBC规范,提供了与消息服务相关的API接口
JMS消息模型
peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息。队列的消息只能被一个消费者消费,或超时
publish-subscribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
JMS消息种类
TextMessage
MapMessage
BytesMessage
StreamMessage
ObjectMessage
Message
MapMessage
BytesMessage
StreamMessage
ObjectMessage
Message
JMS实现
JMS实现:ActiveMQ、Redis、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守JMS规范)
AMQP
AMQP(advanced message queuing protocol):
一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS
一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS
优点:具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现
AMQP消息模型
direct exchange
fanout exchange
topic exchange
headers exchang
esystem exchange
fanout exchange
topic exchange
headers exchang
esystem exchange
AMQP消息种类:byte[]
AMQP实现:RabbitMQ、StormMQ、RocketMQ
MQTT
MQTT(Message Queueing Telemetry Transport)
消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一
消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一
Kafka
Kafka,一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能。
消息案例——订单短信通知
ActiveMQ
下载地址:https://activemq.apache.org/components/classic/download/
安装:解压缩
安装:解压缩
启动服务
activemq.bat
访问服务器
http://127.0.0.1:8161/
服务端口:61616,
管理后台端口:8161
用户名&密码:admin
activemq.bat
访问服务器
http://127.0.0.1:8161/
服务端口:61616,
管理后台端口:8161
用户名&密码:admin
使用
RabbitMQ
基础
RabbitMQ基于Erlang语言编写,需要安装Erlang
Erlang下载地址:https://www.erlang.org/downloads
安装:一键傻瓜式安装,安装完毕需要重启,
需要依赖Windows组件
环境变量配置ERLANG_HOMEPATH
安装:一键傻瓜式安装,安装完毕需要重启,
需要依赖Windows组件
环境变量配置ERLANG_HOMEPATH
启动服务
rabbitmq-service.bat start
关闭服务
rabbitmq-service.bat stop
查看服务状态
rabbitmqctl status
rabbitmq-service.bat start
关闭服务
rabbitmq-service.bat stop
查看服务状态
rabbitmqctl status
服务管理可视化(插件形式)查看已安装的插件列表
rabbitmq-plugins.bat list
rabbitmq-plugins.bat list
开启服务管理插件
rabbitmq-plugins.bat enable rabbitmq_management
rabbitmq-plugins.bat enable rabbitmq_management
访问服务器
http://localhost:15672
http://localhost:15672
服务端口:5672,管理后台端口:15672用户名&密码:guest
使用
RocketMQ
基础
下载地址:https://rocketmq.apache.org/
安装:解压缩默认服务端口:9876
环境变量配置
ROCKETMQ_HOME
PATH
NAMESRV_ADDR (建议): 127.0.0.1:9876
命名服务器与broker
启动命名服务器
mqnamesrv
启动broker
mqbroker
服务器功能测试:生产者
tools org.apache.rocketmq.example.quickstart.Producer
服务器功能测试:消费者
tools org.apache.rocketmq.example.quickstart.Consumer
使用
Kafka
基础
下载地址:https://kafka.apache.org/downloadswindows
系统下3.0.0版本存在BUG,建议使用2.X版本安装:解压缩
启动zookeeper
zookeeper-server-start.bat ..\..\config\zookeeper.properties
zookeeper-server-start.bat ..\..\config\zookeeper.properties
默认端口:2181
启动kafka
kafka-server-start.bat ..\..\config\server.properties
kafka-server-start.bat ..\..\config\server.properties
默认端口:9092
创建topic
kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic itheima
查看topic
kafka-topics.bat --zookeeper 127.0.0.1:2181 --list
删除topic
kafka-topics.bat --delete --zookeeper localhost:2181 --topic itheima
生产者功能测试
kafka-console-producer.bat --broker-list localhost:9092 --topic itheima
消费者功能测试
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic itheima --from-beginning
使用
监控
监控的意义
监控服务状态是否宕机
监控服务运行指标(内存、虚拟机、线程、请求等)
监控日志
管理服务(服务下线)
监控的实施方式
显示监控信息的服务器:用于获取服务信息,并显示对应的信息
运行的服务:启动时主动上报,告知监控服务器自己需要受到监控
运行的服务:启动时主动上报,告知监控服务器自己需要受到监控
可视化监控平台
Spring Boot Admin,开源社区项目,用于管理和监控SpringBoot应用程序。
客户端注册到服务端后,通过HTTP请求方式,服务端定期从客户端获取对应
的信息,并通过UI界面展示对应信息。
客户端注册到服务端后,通过HTTP请求方式,服务端定期从客户端获取对应
的信息,并通过UI界面展示对应信息。
注意版本一致
监控原理
Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
访问当前应用所有端点信息:/actuator
访问端点详细信息:/actuator/端点名称
Web程序专用端点
自定义监控指标
为info端点添加自定义指标
为Health端点添加自定义指标
为Metrics端点添加自定义指标
自定义端点
原理篇
自动配置
bean加载方式
一,XML方式声明bean
二、XML+注解方式声明bean
使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean
使用@Bean定义第三方bean,并将所在类定义为配置类或Bean
注解方式声明配置类
扩展1
初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作
扩展2
加载配置类并加载配置文件(系统迁移)
扩展3
使用proxyBeanMethods=true可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的
bean的加载方式(四)
使用@Import注解导入要注入的bean对应的字节码
被导入的bean无需使用注解声明为bean
bean的加载方式——扩展4
bean的加载方式(五)
使用上下文对象在容器初始化完毕后注入bean
bean的加载方式(六)
导入实现了ImportSelector接口的类,实现对导入源的编程式处理
bean的加载方式(七)
导入实现了ImportBeanDefinitionRegistrar接口的类,
通过BeanDefinition的注册器注册实名bean,实现对
容器中bean的裁定,例如对现有bean的覆盖,进而达
成不修改源代码的情况下更换实现的效果
通过BeanDefinition的注册器注册实名bean,实现对
容器中bean的裁定,例如对现有bean的覆盖,进而达
成不修改源代码的情况下更换实现的效果
bean的加载方式(八)
导入实现了BeanDefinitionRegistryPostProcessor接口的类,
通过BeanDefinition的注册器注册实名bean,实现对容器中bean的最终裁定
通过BeanDefinition的注册器注册实名bean,实现对容器中bean的最终裁定
bean加载控制
概念
bean的加载控制指根据特定情况对bean进行选择性加载以达到适用于项目的目标
bean的加载方式5-8可以控制
根据任意条件确认是否加载bean
使用@Conditional注解的派生注解设置各种组合条件控制bean的加载
匹配指定类
未匹配指定类
匹配指定类型的bean
匹配指定名称的bean
匹配指定环境
匹配指定环境
bean依赖属性配置
将业务功能bean运行需要的资源抽取成独立的属性类(******Properties),设置读取配置文件信息
配置文件中使用固定格式为属性类注入数据
定义业务功能bean,通常使用@Import导入,解耦强制加载bean
使用@EnableConfigurationProperties注解设定使用属性类时加载bean
自动配置原理
变更自动配置
自定义自动配置(META-INF/spring.factories)
控制SpringBoot内置自动配置类加载
变更自动配置:去除tomcat自动配置(条件激活),添加jetty自动配置(条件激活)
自定义starter
统计独立IP访问次数
记录系统访客独立IP访问次数
每次访问网站行为均进行统计
后台每10秒输出一次监控信息(格式:IP+访问次数)
需求分析
1. 数据记录位置:Map / Redis
2. 功能触发位置:每次web请求(拦截器)
① 步骤一:降低难度,主动调用,仅统计单一操作访问次数(例如查询)
② 步骤二:开发拦截器
3. 业务参数(配置项)
输出频度,默认10秒
数据特征:累计数据 / 阶段数据,默认累计数据
输出格式:详细模式 / 极简模式
4. 校验环境,设置加载条件
业务功能开发
自动配置类
配置
模拟调用
开启定时任务功能
设置定时任务
自定义starter
定义属性类,加载对应属性
设置加载Properties类为bean
根据配置切换设置
明细模式报表模板
极简模式报表模板
配置信息
自定义bean名称
放弃配置属性创建bean方式,改为手工控制
使用#{beanName.attrName}读取bean的属性
自定义拦截器
设置核心配置类,加载拦截器
辅助功能开发
导入配置处理器坐标
进行自定义提示功能开发
核心原理
springboot启动流程
1. 初始化各种属性,加载成对象
读取环境属性(Environment)
系统配置(spring.factories)
参数(Arguments、application.properties)
2. 创建Spring容器对象ApplicationContext,加载各种配置
3. 在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求
容器类型选择
4. 容器初始化过程中追加各种功能,例如统计时间、输出日志等
监听器
1. 在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent。
2. 当Environment被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent。
3. 在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent。
4. 在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent。
5. 在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求。
6. 启动时发生异常,将发送 ApplicationFailedEvent。
项目
传智健康项目
Maven项目模块构建
powerdesigner构架数据库
ElementUI使用
基础环境搭建
功能开发
检查项
Redis使用
收藏
收藏
0 条评论
下一页