Git
分支管理切一个新的本地分支123# 先切换到需要复制的分支,比如想从prod分支,切一个新的分支git checkout prod # 先到prod分支git checkout -b prod-fix # 从prod分支拷贝一个新的分支叫prod-fix 用命令pull request代码1234# 把本地的代码切换到mastergit checkout -b theme-next-master master# https://github.com/theme-next/hexo-theme-next.git 是要合并的代码git pull https://github.com/theme-next/hexo-theme-next.git master Git Key123cd ~/.sshssh-keygencat ~/.ssh/id_rsa.pub Git Bash启动慢123git config --global core.preloadindex truegit config --global core.fscache truegit config --global gc.auto 256 推送到远程12345git initgit add README.mdgit commit -m "first commit"git remote add origin [email protected]:YangAnLin/bbs.gitgit push -u origin master 创建新的分支推送到远程 先创建本地分支git checkout -b <branch-name> 本地分支推送到远程服务器时,远程分支自动创建,推送本地分支到远程 123456789101112git push --set-upstream <remote-name> <local-branch-name>:<remote-branch-name><remote-name>:远程git服务器名称,一般设为origin<local-branch-name>:本地分支名称<remote-branch-name>:远程分支名称最后的结果是: git push --set-upstream origin prerelease:prereleaseprerelease是分支的名字也可以这样操作:本地有个master_anthony,服务器中没有这个分支,git push -u origin master_anthony这个命令,就是把master_anthony分支的代码,提交到远程,还要在远程创建这个分支 删除远程仓库文件12345678# 加上 -n 这个参数,执行命令时,是不会删除任何文件,而是展示此命令要删除的文件列表预览。git rm -r -n --cached 文件/文件夹名称git rm -r --cached 文件/文件夹名称git commit -m "提交说明"git push 撤销修改1234567# 工作区修改过了,恢复工作区git checkout -- file# 已经commit了,但是还没有pushgit reset --soft HEAD^# 现在就已经把暂存区恢复到工作了 版本回退resetreset用于回退版本,可以遗弃不再使用的提交 1234567891011121314151617181920212223242526272829# 查看日志提交log$ git log --pretty=onelinec27561bbe42dc1ae1b08442ce0d8100d4e02a689 (HEAD -> master) stage1fd6bc87438d26b24c93441b82c8840a9ad47e32 test_366f444c9de0b3c2d1cddb8f17becca7ce774a7c7 test_22502c400cab4d7cb48ed3e4200577f428904da9b test_126d73f758411c3656b7fa243d77837943a6c782c 3213215fa733378d5eb187d1d46bf2d2395e6c67867a8d 测试e9fa63e3bafc275e99cc88b40cc68bde6933323c 1233d338b67e2b57dcd6887099cb3977f5d96b3cbb5 第一次提交# 远程地址回退版本# 先让本地回退到指定版本git reset --hard HEAD^#再推送到远程(这样也会把别人提交的给弄消息了)git push --force# 操作记录$ git reflogc27561b (HEAD -> master) HEAD@{0}: commit: stage1fd6bc8 HEAD@{1}: reset: moving to 1fd6bc866f444c HEAD@{2}: reset: moving to HEAD^1fd6bc8 HEAD@{3}: commit: test_366f444c HEAD@{4}: commit: test_22502c40 HEAD@{5}: commit: test_126d73f7 HEAD@{6}: commit: 3213215fa7333 HEAD@{7}: commit: 测试e9fa63e HEAD@{8}: commit: 1233d338b6 HEAD@{9}: commit (initial): 第一次提交 版本回退revert 12git reset <ID>git push 📌 操作同一个文件的时候,容易冲突 分支管理1234567891011121314# 查看提交历史git log --graph --pretty=oneline# 删除本地分支git branch -d localBranchName# 删除远程分支git push origin --delete remoteBranchName# 设置全局默认的分支名字git config --global init.defaultBranch <名称># 修改分支名字git branch -m <name> TAG管理查看本地所有的tag 1git tag 新建tag 12345678# 在本地创建一个taggit tag v2.0# 把本地所有tag推送到远程git push --tags# 把指定的tag推送到远程git push origin [tagname] Git子模块克隆项目指定分支1git clone -b prod https://git.oschina.net/oschina/android-app.git 克隆项目第一种方式: 123456789先clone父项目git clone sum.git再初始化子项目git submodule initgit submodule foreach git pull再更新子项目git submodule update 第二中方法 12# 克隆完整的项目git clone [email protected]:jjz/pod-project.git --recursive 添加项目1git submodule add module1.git Rebase12# 进入编辑模式git rebase -i N 这些 commit 自旧到新由上而下排列 在合并 commit 这个需求里,我们可以选择 pick(p) 最旧的 commit1,然后在后续的 commit_id 前添加 squash(s) 命令,将这些 commits 都合并到最旧的 commit1 上。 保存 rebase 结果后,再编辑 commit 信息,使这次 rebase 失效,git 会将之前的这些 commit 都删除,并将其更改合并为一个新的 commit5 提交的过程中,可能需要加 -f 强制推送 操作过程中,遇到出错,使用 1git rebase --abort/--continue/--edit-todo 要注意的 进入到子项目的目录中,默认的分支并不是master,需要手动切换到master 删除子模块的,没有这样的功能,需要手动修改.gitmodules文件 添加了新的子模块之后,.gitmodules就会被创建或者是修改,这个文件需要推送到远程仓库 储藏比如在当前分支正在开发,突然来了一个bug,但是当前写的代码又不能提交,需要先储藏起来 1git stash 等bug改好,再回来 1git stash pop 升级window 1git update-git-for-windows Github更新fork别人的项目1.打开自己的仓库,进入code下面 2.点击new pull request创建 终端配置github person token应用场景 Github Personal Access Token in Jenkins 12345678# 在命令行中的用户主目录创建如下文件vi .git-credentials# 写入如下内容,请注意将 {username}, {password} 替换成自己的 github 用户名和 tokenhttps://{username}:{password}@github.com# 退出保存,执行git config --global credential.helper store SourceTree各种状态1.添加文件或者修改文件uncommitted changes 这次是添加了f.txt文件 2.add的状态 3.提交的状态这里超前一个版本,是相对远程 4.推送完的状态 rebase分支1.这里的分割线的这次提交是用来分区之前的测试,表示新的测试,测试rebase分支,相当于原始的样子 2.如果现在想要开发一个新的功能先在远程的main分支上右键,检出一个自己的分支dev 3.在dev分支上新建/或者编辑文件,我这里创建的一个文件,叫dev分支创建的文件.txt,然后提交到本地,不要推送这里的超前1个版本,是相对于origin/main来说的 4.再切换到main分支 我这里创建了一个文件,叫master分支创建的文件.txt,提交并且推送到远程(这个操作相当于,你在dev分支 开发自己的功能,然后有别的同事提交了新的代码提交到main分支) pull main分支的代码 5.再切换的dev分支,选中main分支,右键,选择将当前变更变基到main 这里的: 超前1个版本: 相当于本地的dev分支来说 落后一个版本: 相对于远程分支来说 如果有冲突则合并冲突,点击左上角(中间位置)的提交,选择继续变基 1.第三步 的操作,已经提交到本地了 2.如果这里解决冲突,就相当于,有编辑了一次文件 3.再把编辑的提交一次到本地 6.此时我们的本地更新是基于最新的 main 分支点击推送,把dev分支,推送到远程 7.切换到main分支,点拉取,拉取dev分支到main分支 再推送main
PHP
安装Nginx与PHP 7.4123sudo apt updatesudo apt install php-fpmsystemctl status php7.4-fpm 1apt install nginx 123456789101112server { # . . . other code location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; }}# 重启sudo systemctl restart nginx 测试PHP12cd /var/www/html/vim info.php 123456<?phpphpinfo();# 访问# http://your_server_ip/info.php WordPressDocker安装1234567# 安装docker-composeapt install docker-compose# 查看版本docker-compose versionvim docker-compose.yml 1234567891011121314151617181920212223242526version: '3.3'services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest ports: - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpressvolumes: db_data: {} 123456# 后台运行docker-compose -f docker-compose.wordpress.yml up -d# 停止并删除服务docker-compose -f docker-compose.wordpress.yml down# IP:8000# IP:8000/wp-admin 12345678910111213141516171819202122232425# ningx反代WordPressserver { listen 80; listen [::]:80; # 域名 server_name .leozl.site; location / { ## service 的名称,docker-compose.yaml文件中的wordpress服务名称 proxy_pass http://wp; proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; }} Vultr上安装上传备份 安装SSL 1234567891011# 安装证书certbot --nginx --redirect -d blog.airports.ink -m [email protected] --agree-tos --no-eff-email# 配置证书vim /etc/nginx/conf.d/wordpress_http.confvim /etc/nginx/conf.d/wordpress_https.confserver{ ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key;} SEO优化
Python
安装虚拟环境12345678910111213141516171819202122232425262728293031323334# 判断有没有虚拟环境virtualenv -V# 安装虚拟环境# 需要sudopip install virtualenvpip install virtualenvwrapper# 查看有多少虚拟环境的文件夹workon# 创建虚拟环境文件夹mkvirtualenv 文件夹名字# 从虚拟文件夹退出deactiave# 进入虚拟环境中workon 虚拟环境名称# 删除虚拟环境rmvirutalenv# 查看虚拟环境有哪些框架,要在虚拟环境中执行pip freeze# 安装软件指定版本,要在虚拟环境中执行pip install flask==10.0.0.0# 导出虚拟环境中的所有扩展,要在虚拟环境中执行pip freeze > requirements.txt# 安装,要在虚拟环境中执行pip install -r requirements.txt centos7安装python3特别是在喜欢环境中已经安装的python2.x的版本中 1234567891011121314151617181920212223242526272829# 这个可能不一定要装sudo yum -y groupinstall "Development tools"# 需要的sudo yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel# 下载安装包wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0a1.tar.xz# 解压tar -xvxf Python-3.7.0a1.tar.xz# 复制文件夹mv Python-3.7.0 /usr/local# 进入到文件夹cd /usr/local/Python-3.7.0/# 编译,一定需要后面的参数./configure --prefix=/usr/local/bin/python3make & make install# 添加软连接ln -s /usr/local/bin/python3/bin/python3 /usr/bin/python3ln -s /usr/local/bin/python3/bin/pip3 /usr/bin/pip3# 验证python3pip3 基础1.注释123456789# 注释后面需要一个空格print("单行注释")print("单行注释") # 单行注释和代码之间至少要有两个空格"""多行注释"""print("这是多行注释") 2.算数运算符乘法的使用,用*可以拼接字符串 12345In [1]: "A" * 30Out[1]: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'In [2]: 1 * 30Out[2]: 30 优先级 幂 >* > / > % > // >+ > - 3.变量3.1.变量的类型数字型 整数 int 浮点 float(计算要小心) 布尔 布尔值可以用 and、or 和 not 运算 复数(用于科学技术的) 非数字型 String(字符串) List(列表) Tuple(元组) Dictionary(字典) 3.2.type函数12345678# 整数print(type(1))# 浮点数print(type(1.5))# 字符串print(type("hello world"))# 空值print(type(None)) 3.3.不同类型的变量之间的计算1234567891011121314151617181920212223242526272829In [10]: age =13In [11]: sex =TrueIn [12]: height = 180.1In [13]: age + sexOut[13]: 14In [14]: age + heightOut[14]: 193.1In [15]: age + heightOut[15]: 193.1# 字符串的拼接In [17]: first_name ="东尼"In [18]: last_name="安"In [19]: last_name+first_nameOut[19]: '安东尼'# 字符串不能和数字相加In [20]: last_name + 10---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-20-45feb354f2d0> in <module>----> 1 last_name + 10TypeError: can only concatenate str (not "int") to str 3.4.变量的输入12345678910# 可以不需要参数In [24]: input()123# 加参数,做提示In [27]: pwd = input("输入数字")输入数字123In [28]: pwd + "456"Out[28]: '123456' 3.5.数据类型转换 方法名 含义 str() 转成字符串 float() 转成浮点 int() 转成int 123456789101112131415161718192021222324# 数字和字符串相互转In [3]: age = 23In [4]: type(age)Out[4]: intIn [5]: age_str = str(age)In [6]: type(age_str)Out[6]: strIn [7]: type(int(age_str))Out[7]: int# 浮点转数值,缺失精度In [9]: pi = "3.14"In [10]: piOut[10]: '3.14'In [13]: type(float(pi))Out[13]: floatIn [15]: int(float(pi))Out[15]: 3 3.6.变量的格式化输出 符号 描述 %c 格式化字符(输出数值对应的ASCII码) %s 格式化字符串 %d 格式化整数(%06d,不足的补0) %x 格式化十六进制数(小写) %X 格式化十六进制数(大写) %o 格式化八进制数 %f 格式化浮点数字,可以指定小数点精度(%.2f) %% 输出%号 12345678910"""'我的名字是:anthony,请多多关照我的学号是:000001,请多多关照单价是:1.00,购买了2.00斤,总价是:2.000数据的比例是:20%"""print("我的名字是:%s,请多多关照" % "anthony")print("我的学号是:%06d,请多多关照" % 1)print("单价是:%.2f,购买了%.2f斤,总价是:%.3f" % (1,2,2))print("数据的比例是:%02d%%" % 20) 3.7.变量的命名 字母,和下划线和数字组成 不能以数字开始 不能与关键字重名 其余的符号都不行 区分大小写 =左右都要添加空格 两个单词之间用_ 4.条件控制4.1.if12345678910age = 15if age >= 18: print("成年了") print("在一个缩进的,是一个代码块")elif age<=18: print("没有成年")else: print("输入错误")print("结束了") 4.2.逻辑运算符123print(True and False)print(True or False)print(not True) 4.3.随机数1234In [17]: import randomIn [18]: random.randint(12,20)Out[18]: 12 4.3.while1234i = 0while i < 5: print("....") i += 1 4.4.continue 和 break如果是嵌套循环,用这个两个关键字,也只是结束当前的循环,不会影响外层的循环 5.函数5.1.函数注释1234# 这个也是可以注释的def test(): """这个也是可以注释的""" print("打印乘法表") 5.2.函数调用index.py 12def chengfabiao(): print("打印乘法表") test.py 12import indexindex.chengfabiao() 5.3.局部方法修改全局变量12345678910num =10def mo(): # 声明num是全部变量 global num num=100 print(num)mo()print(num) 5.4.多个返回值12345678def change(x,y): return y,xx =1y=2x,y = change(1,2)print(x)print(y) 5.5.缺省函数1234567891011121314151617def measure(age,gender=1): print(age) print(gender)def measure2(age,gender=1,name="anthony"): print(age) print(gender) print(name)measure(1)measure(1,2)# 有多个缺省的时候,需要指定参数名称measure2(1,name="anthonyyang",gender=2)# 拆包measure(1,*(2,3,4),**{"name":"anthony"}) 5.6.多值参数习惯存元祖的使用用*args,存字典的时候用**args 5.7.函数的参数-默认参数1234567891011121314def my_print(name,age): print(name,age)def my_print2(name,age=13): print(name,age)# 有默认值的形参,不能放在前面,会报错# def my_print3(age=13,name):# print(name,age)my_print("anthony",12)my_print2("anthony2",123)my_print2("anthony2") 5.8.函数的参数-关键字参数12345678910111213141516def my_print(name,address): print(name,address)my_print("anthony","广东")my_print("广东","anthony")my_print(name="anthony",address="广东")my_print(address="广东",name="anthony",)# ----------------**kw是关键字参数,且 hobby 就是一个 dict (字典)-------------def my_print2(name,address,**kw): if 'age' in kw: print("age=",kw["age"])my_print2("anthony","广东",age=123)my_print2("anthony","广东",kw={"age":123}) 5.8.函数的参数-只接受关键字参数123456789def my_print(name,*,address): print(name,address)# 报错# my_print("anthony","广东")# my_print("广东","anthony")my_print("anthony",address="广东")my_print(address="广东",name="anthony") 5.8.函数的参数-不定长参数hobby是可变参数,且 hobby 其实就是一个 tuple (元祖) 12345678910def print_user_info( name , age , sex = '男' , * hobby): # 打印用户信息 print('昵称:{}'.format(name) , end = ' ') print('年龄:{}'.format(age) , end = ' ') print('性别:{}'.format(sex) ,end = ' ' ) print('爱好:{}'.format(hobby)) return;# 调用 print_user_info 函数print_user_info( '两点水' ,18 , '女', '打篮球','打羽毛球','跑步 6.容器6.1.列表虽然列表可以存储不同类型的数据,但是在开发中,存储的都是相同类型数据,因为要迭代 123456789101112131415161718192021222324mylist=["a","b","c"]print(mylist)# 通过索引,访问列表中的值print(mylist[1])# 通过方括号的形式来截取列表中的数据,访问列表中的值# 就是从第 0 个开始取,取到第 2 个,但是不包含第 2 个print(mylist[0:2])# 通过索引对列表的数据项进行修改或更新mylist[1] = "bb"print(mylist)# 使用 append() 方法来添加列表项mylist.append("d")print(mylist)# 使用 del 语句来删除列表的的元素del mylist[3]print(mylist)# 列表长度print(len(mylist)) 6.2.元祖元祖用的是括号 与列表比较,元祖元素不能修改 123456789101112131415161718192021222324252627# 创建元祖方法1tuple1=('两点水','twowter','liangdianshui',123,456)tuple2='两点水','twowter','liangdianshui',123,456# 创建元祖方法2tuple3 = ()# 创建元祖方法3tuple4 = (123,)print(tuple1)print(tuple2)print(tuple3)print(tuple4)# 访问元祖print(tuple1[1])# 修改元祖的值mylist=[1,2,3]tuple5=("ddd",mylist)print(tuple5)mylist[1]=43print(tuple5)# 删除元祖,tuple 元组中的元素值是不允许删除的,但我们可以使用 del 语句来删除整个元组del tuple1 元祖和列表相互转换 123456789101112131415# 声明元祖In [54]: num_list = (1,2,3,4)In [55]: type(num_list)Out[55]: tuple# 元祖转成列表In [56]: my_list = list(num_list)# 修改值In [57]: my_list[0]=5# 再转成元祖In [58]: print(tuple(my_list))(5, 2, 3, 4) 6.2.字典列表是有序的 字典是无序的 12345678910111213141516171819202122232425262728names={"name":"xiaoming","age":"23"}# 取值print(names["name"])# 新增和修改(key存在,就是新增,不存在就是修改)names["address"] ="feilvb"names["name"] ="anthony123"print(names)# 删除names.pop("name")print(names)# 统计键值对的数量print(len(names))# 合并键值对,如果合并的时候有相同的key,那个value就是更新值temp = {"a":"b"}names.update(temp)print(names)# 遍历字典for k in names: print("遍历",k,names[k])# 清空字典names.clear() 6.3.setset可以理解为只有key的字典 12345678# 创建setset1 = set([1,2,3])# 添加元素set1.add(200)# 删除元素set1.remove(1) 6.4.字符串123456789101112131415str ="hello hello"print("字符串长度",len(str))print("字符串出现次数",str.count("llo"))print("取索引",str.index("llo"))print("取值",str[1])# 换行符,都是空白字符print("判断空白字符",str.isspace())print("是否以指定字符串开始",str.startswith("hello"))print("是否以指定字符串结束",str.endswith("LLO"))print("查找指定字符串",str.find("llo"))print("替换字符串",str.replace("hello","HELLO"))print(str[0:9:2])# bytes转字符串print(b"abcde".decode("utf-8")) 字符串前加 b:b 前缀代表的就是bytes 字符串前加 r:r/R:非转义的原始字符串 7.公共方法 内置函数: len max 只能比较字典的key min 只能比较字典的key 2.字符串,列表,元祖都可以切片 3.查看地址值 1id(str) 面向对象类名需要大驼峰命名法 1.基本语法1.1.创建对象1234567891011class Cat: def eat(self): print("小猫爱吃鱼") def drink(self): print("小猫爱喝水")tom = Cat()tom.eat()tom.drink() 1.2.对象内置方法(魔术方法)123456789101112131415161718192021222324252627class Cat: # 构造方法 def __init__(self,name): print("初始化方法") self.name=name # 成员方法 def eat(self): print(self.name+"爱吃鱼") # 成员方法 def drink(self): print(self.name+"爱喝水") # 魔术方法 def __del__(self): print("销毁方法") # 魔术方法 def __str__(self): return "重写tostring"tom = Cat("Tom")tom.eat()tom.drink()print(tom) 1.3.私有属性和方法1234567891011121314151617181920212223242526272829303132333435class Cat: # 构造方法 def __init__(self,name): print("初始化方法") self.name=name self.__age =18 def eat(self): print(self.name+"爱吃鱼") def drink(self): print(self.name+"爱喝水") def say_age(self): print("年纪是:"+str(self.__age)) # 调用私有方法 self.__private_method() def __private_method(self): print("私有方法") def __del__(self): print("销毁方法") def __str__(self): return "重写tostring"tom = Cat("Tom")tom.eat()tom.drink()tom.say_age()print(tom)# 这种访问方式,也是可以访问到私有的属性和方法的print(tom._Cat__age) 1.4.继承和重写123456789101112131415161718192021222324252627282930class Animal: def __init__(self): self.name1 =100 self.__num2 = 200 def eat(self): print("动物吃") def run(self): print("动物跑") # 子类不允许调用私有方法 def __test(self): print("父类可以访问到私有属性和私有方法")class Dog(Animal): def run(self): print("子类打印,开始调用父类方法") super().run() print("调用完父类方法")# animal = Animal()# animal.eat()# animal.run()dog = Dog()dog.eat()dog.run() 1.5.多继承尽量避免使用多继承,如果继承了两个累,两个类有相同的方法和属性,容易混淆 123456789101112131415161718192021222324252627282930class Animal: def __init__(self): self.name1 = 100 self.__num2 = 200 def eat(self): print("动物吃") def run(self): print("动物跑") # 子类不允许调用私有方法 def __test(self): print("父类可以访问到私有属性和私有方法")class Zoo: def eat(self): print("动物园吃饭")class Dog(Animal, Zoo): def run(self): print("子类打印,开始调用父类方法") super().run() print("调用完父类方法")dog = Dog()dog.eat() 1.6.多态12345678910111213141516171819202122232425262728class Dog(object): def __init__(self,name): self.name = name def game(self): print("蹦蹦跳跳",self.name)class Xiaotianquan(Dog): def game(self): print("哮天犬",self.name)class Person(object): def __init__(self,name): self.name = name def game_with_dog(self,dog): print("人和狗玩耍",self.name,dog.name) dog.game()# dog = Dog("旺财")dog = Xiaotianquan("旺财")xiaoming = Person("xiaoming")xiaoming.game_with_dog(dog) 1.7.类属性和类方法和静态方法类属性 相当于静态变量 123456class Dog(object): # 类属性 age = 12 def __init__(self,name): self.name = name 类方法 123456789101112class Dog(object): # 类属性 age = 12 # 类方法 @classmethod def show_age(cls): print("静态方法",cls.age)dog = Dog()Dog.show_age() 静态方法,在不用方法类属性和静态属性的时候,可以定义成静态方法 1234567891011121314151617181920class Dog(object): # 类属性 age = 12 # 类方法 @classmethod def show_age(cls): print("类方法",cls.age) @staticmethod def static_method(): print("静态方法")dog = Dog()# 调用类方法Dog.show_age()# 调用静态方法Dog.static_method() 2.异常2.1.异常的完整语法1234567891011try: num = int(input("输入一个整数:")) 10 / numexcept ZeroDivisionError: print("请不要输入数字0")except Exception as result: print("未知错误 %s" % result)else: print("没有异常才会执行的代码")finally: print("无论是否有异常,都会异常的代码") 2.2.主动抛异常123456789101112def check(name): if(name == "anthony"): return "是安东尼" else: # 主动抛异常 raise Exception("不是安东尼")# 捕获异常try: print(check("anthony2"))except Exception as result: print(result) 3.模块导入的语法如下:[from 模块名】import [模块 1类1变量1函数1x[as别名]常用的组合形式如: import 模块名 from 模块名 import 类、变量、方法等 from 模块名 import * import 模块名 as 别名 from 模块名import 功能名 as 别名 3.1.导入模块不推荐使用, 12import pkg1import pkg2 3.2.简单的使用my_module.py 1234567title = "模块2"def say_hello(): print("i am module : %s " % title)class Cat: pass index.py 1234567import my_module# use module methodmy_module.say_hello()dog = my_module.Cat()print(dog) 3.3.导入的时候也可以起别名别名要使用大驼峰命名 1import my_module as MyModule 3.4.from…import导入一部分工具 使用的时候,就不需要写那个模块名了,直接使用 1234from my_module import say_hellofrom my_module import Catsay_hello()cat = Cat() 3.5.作为模块的正常写法123456def main(): pass# 有了这个之后,被别的模块调用的时候if __name__ = "__main__" main 3.6.包包 包含多个模块 创建一个新的文件夹,在文件夹里面创建__init__.py 123# . 是相对路径名from . import send_messagefrom . import receive_message 在文件夹里面创建两个模块 receive_message.py 12def receive(): print("接受信息") send_message.py 12def send(text): print("发送 %s" % text) 调用模块 1234import hm_messagehm_message.send_message.send('hello')hm_message.receive_message.receive() 3.7.发布模块1.创建setup.py 1234567from distutils.core import setupsetup(name="hm_message", version="1.0", description="push", py_modules=["hm_message.send_message", "hm_message.receive_message"]) 2.命令 12python setup.py buildpython setup.py sdist 3.8.安装模块123# 手动安装模块tar xxx.tar.gzpython setup.py install pymssql访问https://www.lfd.uci.edu/~gohlke/pythonlibs/#pymssql 下载[pymssql‑2.1.4‑cp38‑cp38‑win32.whl] 网络编程0.socket的历史套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。 一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯或IPC。 套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 基于文件类型的套接字家族 - 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信 基于网络类型的套接字家族 - 套接字家族的名字:AF_INET (还有AF_INET6被用于ipv6, 还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) 套接字把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的 1.udp发送端 123456789101112131415161718192021import socketdef main(): # 创建一个udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: send_data = str = input("输入要发送的数据") if send_data == "exit": break # udp_socket.sendto(b"这是消息",("192.169.0.1",8000)) udp_socket.sendto(send_data.encode("utf-8"),("127.0.0.1",7788)) # 关闭套接字 udp_socket.close()if __name__ == '__main__': main() 接受者 1234567891011121314151617181920import socketdef main(): # 创建一个udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定本地相关信息 local_addr = ("",7788) udp_socket.bind(local_addr) while True: # 等待接收对方发送的数据 recv_data = udp_socket.recvfrom(1024) print(recv_data[0].decode("gbk")) # 关闭套接字 udp_socket.close()if __name__ == '__main__': main() 2.tcp3.socket使用socket访问redis 12345678910111213141516171819import sockethost = '10.0.2.110'port = 6379buf_size = 1conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)conn.connect((host, port))cmd = 'SELECT 2\n'.encode('utf-8')r = conn.sendall(cmd)cmd = 'PING\n'.encode('utf-8')conn.sendall(cmd)while True: res = conn.recv(buf_size) print(res) if not res: breakconn.close() 服务端套接字函数 123456789101112131415161718192021222324252627282930313233s.bind() #绑定(主机,端口号)到套接字s.listen() #开始TCP监听s.accept() #被动接受TCP客户的连接,(阻塞式)等待连接的到来**客户端套接字函数**s.connect() #主动初始化TCP服务器连接s.connect_ex() #connect()函数的扩展版本,出错时返回出错码,而不是抛出异常**公共用途的套接字函数(客户端和服务端都能使用)**s.recv() #接收TCP数据s.send() #发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)s.sendall() #发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)s.recvfrom() #接收UDP数据s.sendto() #发送UDP数据s.getpeername() #连接到当前套接字的远端的地址s.getsockname() #当前套接字的地址s.getsockopt() #返回指定套接字的参数s.setsockopt() #设置指定套接字的参数s.close() #关闭套接字 Requestpost请求参数123456789101112131415161718# 表单提交import requestsdata = { "name":"anthony", "age":"12"}requests.post(url=url,data=data)# json提交import requestsjson_data = { "name":"anthony", "age":"12"}requests.post(url=url,json=json_data) cookie操作1234cookies={ 'auther':'11223'}request.post(url=url,data=data,cookies=cookies) headers1234headers ={ 'auth':'123'}request.post(url=url,data=data,cookies=cookies,headers=headers) 请求超时12# 如果超过2s就超时报错requests.post(url=url,timeout=2) 鉴权有些页面,需要,比如spring secuity的页面 1requests.post(url=url,timeout=2,auth=('anthony','123456')) 编码12# 先编码 再解码r.text.encode('utf-8').decode(unicode_escape) 下载流21234567url = "http://wx4.sinaimg.cn/large/d030806aly1fq1vn8j0ajj21ho28bduy.jpg"rsp = requests.get(url, stream=True)with open('1.jpg', 'wb') as f: # 边下载边存硬盘, chunk_size 可以自由调整为可以更好地适合您的用例的数字 for i in rsp.iter_content(chunk_size=1024): f.write(i) requests.get(url)默认是下载在内存中的,下载完成才存到硬盘上 Response.iter_content来边下载边存硬盘 Flask路由入门12345678910from flask import Flaskapp = Flask(__name__)@app.route('/login')def hello_world(): return 'Hello World!'if __name__ == '__main__': app.run() URL上动态参数1234567891011121314from flask import Flaskapp = Flask(__name__)@app.route("/")def hello_world(): return "hello world"@app.route("/<int:age>")def play_game(age): return "hello world"+str(age)if __name__ == '__main__': app.run(debug=True) 自定义转换器1234567891011121314151617181920212223242526from flask import Flaskfrom werkzeug.routing import BaseConverterapp = Flask(__name__)# 自定义,并且继承BaseConverterclass MyConverter(BaseConverter): def __init__(self, map, regex): # map 就是app.url_map 也就是请求路径 # regex,就是d{3}, 也就是自定义的规则 super(MyConverter, self).__init__(map) self.regex = regexapp.url_map.converters["haha"] = [email protected]('/<haha("\d{3}"):age>')def play_game2(age): return "自定义转换器,接收3位" + str(age)@app.route('/<haha("\d{4}"):age>')def play_game3(age): return "自定义转换器,接收4位" + str(age)if __name__ == '__main__': app.run(debug=True) 给路由添加请求方式12345678910from flask import Flaskapp = Flask(__name__)@app.route('/',methods=['POST'])def play_game2(age): return "自定义转换器,接收3位" + str(age)if __name__ == '__main__': app.run(debug=True) 返回响应体123456789101112131415161718192021from flask import Flaskapp = Flask(__name__)@app.route('/')def method1(): """直接返回字符串信息""" return "返回字符串信息"@app.route('/2')def method2(): """直接返回字符串信息""" return "返回字符串信息",[email protected]('/3')def method3(): """直接返回字符串信息""" return {"name":"anthony"},200,{"Content-Type":"application/json","token":"123456"}if __name__ == '__main__': app.run(debug=True) 重定向12345678910from flask import Flask,redirectapp = Flask(__name__)@app.route('/')def method1(): return redirect("http://baidu.com")if __name__ == '__main__': app.run(debug=True) 跳转页面123456789101112131415161718192021222324from flask import Flask, render_template, request,redirectapp = Flask(__name__)@app.route('/login', methods=['GET', 'POST'])def hello_world(): print("请求来了") # 获取post传过来的值 user = request.form.get("user") pwd = request.form.get("pwd") if user == "anthony" and pwd == "123456": # return render_template("login.html", **{"msg": "登录成功"}) return redirect("/index") else: return render_template("login.html", **{"msg": "用户名或者密码错误"})@app.route("/index")def index(): return "欢迎登录"if __name__ == '__main__': app.run() 异常捕获123456789101112131415161718from flask import Flask,abortapp = Flask(__name__)@app.route('/')def method1(): """abort 异常抛出""" abort(404) return "hello world""""捕获异常"""@app.errorhandler(404)def method1(e): print(e) return "页面找不到"if __name__ == '__main__': app.run(debug=True) 获取请求参数Form12345678910111213141516171819from flask import Flask, render_template, requestapp = Flask(__name__)@app.route('/login', methods=['GET', 'POST'])def hello_world(): print("请求来了") # 获取post传过来的值 user = request.form.get("user") pwd = request.form.get("pwd") if user == "anthony" and pwd == "123456": return render_template("login.html", **{"msg": "登录成功"}) else: return render_template("login.html", **{"msg": "用户名或者密码错误"})if __name__ == '__main__': app.run() 获取请求参数Body123456789101112from flask import Flask, requestapp = Flask(__name__)@app.route('/callback')def hello_world(): data = request.get_data() print(data) return 'Hello, World!'if __name__ == "__main__": app.run(debug=True, port=10086) 环境变量/配置文件12345678910111213141516171819202122from flask import Flask,redirect,requestapp = Flask(__name__)# 1.从配置类中加载class MyConfig(object): DEBUG =True# app.config.from_object(MyConfig)# 2.从配置文件中加载# app.config.from_pyfile("Config.ini")# 在项目的根目录创建个Config.ini文件# 3.从环境变量# app.config.from_envvar("")@app.route('/')def method1(): passif __name__ == '__main__': app.run() 钩子,类似拦截器12345678910111213141516171819202122232425262728293031from flask import Flask,redirect,requestapp = Flask(__name__)@app.before_first_requestdef before_first_request(): """只请求一次""" print("before_first_request")@app.before_requestdef before_request(): """每次都会请求""" print("before_request")@app.after_requestdef after_request(resp): """比如做json统一的返回格式""" print("after_request") return [email protected]_requestdef teardown_request(e): """最后会请求到这里,适合做异常信息统计""" print("teardown_request")@app.route('/')def method1(): return "hello world"if __name__ == '__main__': app.run(debug=True) 视图内容和模板123456789101112131415161718192021from flask import Flask, make_response, requestapp = Flask(__name__)# 设置[email protected]("/set_cookie")def set_cookie(): response = make_response("set cookie") response.set_cookie("computer","macbook pro") response.set_cookie("age","13 pro",1000) return [email protected]("/get_cookie")def get_cookie(): name = request.cookies.get("computer") age = request.cookies.get("age") return "name:%s,age:%s"%(name,age)if __name__ == '__main__': app.run(debug=True) Session1234567891011121314151617181920from flask import Flask, make_response, request, sessionapp = Flask(__name__)app.config["SECRET_KEY"]="123456"# 设置[email protected]("/set_session/<path:name>")def set_session(name): session["name"] =name return "set session"@app.route("/get_session")def get_session(): value = session.get("name") return "value:%s"%(value)if __name__ == '__main__': app.run(debug=True) orm入门12pip install flask_sqlalchemypip install pymysql 1234567891011121314151617181920212223242526272829303132333435363738from flask import Flask,render_templatefrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)# 设置数据库的配置信息app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://Rong:[email protected]:3306/data36"app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False# 创建sqlAlchemy对象,关联appdb = SQLAlchemy(app)# 创建模型类class Role(db.Model): __tablename__ = "roles" id =db.Column(db.Integer,primary_key=True) name = db.Column(db.String(32))class Users(db.Model): __tablename__ = "users" id =db.Column(db.Integer,primary_key=True) name = db.Column(db.String(32)) # 建立外键 role_id =db.Column(db.Integer,db.ForeignKey(Role.id))@app.route("/")def demo(): return "response"if __name__ == '__main__': # 创建数据库的表 db.drop_all() db.create_all() app.run(debug=True) 蓝图基本使用demo01.py 12345678910from flask import Flask, Blueprintfrom demo02 import blueapp = Flask(__name__)# 讲蓝图注册到app中app.register_blueprint(blue)if __name__ == '__main__': app.run(debug=True) demo02.py 123456789101112from flask import Blueprint# 创建蓝图对象blue = Blueprint("my_blue", __name__)@blue.route("/")def demo(): return "response"@blue.route("/2")def demo2(): return "response2" 蓝图包的使用目录结构:|—demo.py|—-|user||—-|user|–__init__.py|—-|user|–views.py demo.py 123456789101112from flask import Flaskfrom user import user_blueapp = Flask(__name__)# 讲蓝图注册到app中app.register_blueprint(user_blue)if __name__ == '__main__': print(app.url_map) app.run(debug=True) init.py 123456from flask import Blueprint# 创建蓝图对象user_blue = Blueprint("user", __name__)from user import views views.py 123456789from user import user_blue@user_blue.route("/")def demo(): return "response"@user_blue.route("/2")def demo2(): return "response2" Django命令12345678# 安装djangopip install django# 生成数据库 迁移文件python manage.py makemigrations# 执行迁移生成表python manage.py migrate PyCharm和Django-admin创建的项目不一样12345678910111213# 项目创建django-admin startproject 项目名# 结构项目名|----|manage.py [项目管理,启动项目,创建app,数据管理]|----|项目名同名文件夹|----|----|__init__.py|----|----|asgi.py [项目配置]|----|----|settings.py [路由]|----|----|urls.py [接收网路请求,异步]|----|----|wsgi.py [接收网路请求,同步] 12345678910# 用PyCharm创建的目录结构项目名|----|manage.py |----|templates文件夹|----|项目名同名文件夹|----|----|__init__.py|----|----|asgi.py|----|----|settings.py|----|----|urls.py|----|----|wsgi.py PyCharm创建的根目录下有一个templates目录Django-admin 是没有的,要在app下的目录创建templates settings.py 1234567891011121314151617181920212223242526272829303132333435# PyCharm创建的TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },]# 命令行创建的TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },] 创建应用123|项目||----|app,后台系统[独立的表结构,模板,不相互影响]|----|app,前台系统[独立的表结构,模板,不相互影响] 123456789101112131415161718192021# 创建apppython manage.py startapp app名字djangoProject├── app01 [刚创建的app]│ ├── __init__.py│ ├── admin.py [django默认admin后台,不用动]│ ├── apps.py [app启动类,不用动]│ ├── migrations [数据库的迁移的,数据库变更记录]│ │ └── __init__.py│ ├── models.py [重要,对数据库进行操作]│ ├── tests.py [单元测试]│ └── views.py [重要,函数]├── djangoProject│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── manage.py└── templates 注册APP项目名→项目名同名文件夹→settings.py 12345678910INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 添加下面一行的配置,下面这个是怎么来的呢?看下面 'app01.apps.App01Config' ] 项目名→APP文件夹→apps.py 12345from django.apps import AppConfigclass App01Config(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'app01' 编写URL和视图函数对应关系编辑urls.py 在app文件夹下的views.py 1234567from django.http import HttpResponsefrom django.shortcuts import render# Create your views here.# request 必须要的def index(request): return HttpResponse("欢迎使用") 启动12# 命令行启动python ./manage.py runserver templates模板 静态资源12345678910111213141516171819202122232425djangoProject├── app01│ ├── __init__.py│ ├── admin.py│ ├── apps.py│ ├── migrations│ │ └── __init__.py│ ├── models.py│ ├── static [手动创建的]│ │ ├── css [手动创建的] │ │ ├── img [手动创建的]│ │ │ └── 1.png│ │ └── js│ ├── tests.py│ └── views.py├── db.sqlite3├── djangoProject│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── manage.py└── templates └── user_list.html 12345678910111213141516171819{% load static %}<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>用户列表</title></head><body><h1>用户列表</h1>加载静态资源写法一:<img src="/static/img/1.png">写法二:<img src="{% static 'img/1.png' %}"></body></html> 数据库主意:app需要先注册 12345python ./manage.py makemigrationspython ./manage.py migrate# Pycharm->tools->Run manager.py Task 开启,可以替代python manager.py 这个前缀 1pip3 install pymysql 12345678910111213141516171819from pymysql import connectcon = connect(host="43.240.30.52", port=3306, user="yanganli_github", password="yanganli_github", database="yanganli_github", charset="utf8")cursor = con.cursor()# 查询出条数print(cursor.execute("select * from post"))for one in cursor.fetchall(): print(one)cursor.close()con.close() 自动化测试安装和命令1234567891011121314# 安装pytestpip install pytest# 安装python-html包,生成html报告pip install pytest-html# 生成allure报告pip install allure-pytest# -s 打印printpytest -s test_auth.py# 只测试这一个文件pytest -q -s test_auth.py Pycharm识别pytest 规范测试文件已test_开头 测试类已Test开头,并且不能带有init方法 测试函数以test_开头 断言基本使用assert 生成报告123456789101112# 生成html'--html=../report/report.html'# 生成xml格式的报告'--junitxml=./report/report.xml'# 生成allure报告'--alluredir','../report/reportallure'# 生成allure报告,在report的文件夹执行pytest.main(['../test_case/','--alluredir','../report/allure_raw/'])./allure serve /myitem/autotest/report/allure_raw Locust1234567pip install locust# centos需要添加环境变量# 如果locust在哪个目录,如果python安装在这个目录:/usr/local/bin/python3ln -s /usr/local/bin/python3/bin/locust /usr/bin/locustlocust --version 快速入门新建一个redis.py 12345678910111213141516class QuickstartUser(HttpUser): wait_time = between(1, 10) @task def index_page(self): self.client.get("/hello") self.client.get("/world") @task(3) def view_item(self): for item_id in range(10): self.client.get(f"/item?id={item_id}", name="/item") time.sleep(1) def on_start(self): self.client.post("/login", json={"username": "foo", "password": "bar"}) 在文件目录,执行 123locust -f redis.py# 启动成功后,访问 http://localhost:8089 写一个Locust文件Locust文件是一个普通的python文件,唯一的要求就是至少有一个类继承的User类 User一个User类标识一个用户,Locust会给每个用户模拟一个User实例,公共的属性通常在User类里被定义 wait_time 属性用户的wwait_time方法通常作用于执行的任务简直等待多长时间 这里有三个内建的等待时间的函数 constant 一段固定的时间 between 最小和最大数之间的随机时间 constant_pacing 确保任务运行一次最多不超过x秒的适应时间 举个例子,让每个用户在每个任务执行之间等待0.5s到10s 12345678from locust import User, task, betweenclass MyUser(User): @task def my_task(self): print("executing my_task") wait_time = between(0.5, 10) 也可以直接在你的类里声明wait_time方法,举个例子,下面的User类,会睡眠1s,2s… 12345678class MyUser(User): last_wait_time = 0 def wait_time(self): self.last_wait_time += 1 return self.last_wait_time ... weight 属性如果文件中存在多个用户类,并且在命令行中没有指定用户类,则Locust将生成每个用户类的相同数量。您还可以通过将用户类作为命令行参数传递来指定从同一个locustfile中使用哪些用户类 1locust -f locust_file.py WebUser MobileUser 如果希望模拟某种类型的更多用户,可以在这些类上设置权重属性,比方说,web用户是移动用户的三倍多 1234567class WebUser(User): weight = 3 ...class MobileUser(User): weight = 1 ... host 属性host属性是host的URL前缀,通常,当locust启动的时候,在locust的web界面或者在命令上中使用--hosts选项中使用 如果一个类声明了声明的host属性,它将在没有使用--host命令行或web请求仲使用 tasks属性用户类可以使用@task装饰器,将任务声明为方法,但是也可以使用下面详细描述的tasks属性来指定任务。 environment 属性对用户正在其中运行的环境的引用,使用它影响这环境,或者是运行着在它的容器中,比如去停止运行从任务方法中 1self.environment.runner.quit() 如果运行的是独立的locust实例,它将停止全部,如果是运行在工作节点,它将停止节点 on_start 和 on_stop 方法用户 或者是任务集合可以声明``on_start方法或者on_stop方法,用户将调用它自己的on_start方法在它将要开始运行的时候,on_stop方法,将在停止运行的时候调用,比如TaskSet,on_start方法被调用在模拟的用户开始执行任务,on_stop方法在停止模拟的用户执行任务的时候调用(或者被interrupt()`方法调用,或者是用被用杀掉) Tasks当启动负载测试,一个用户实例将被创建, 下载mp4 12345678910111213141516171819202122232425262728293031323334353637383940414243import osimport timeimport requestsfrom tqdm import tqdm # 进度条模块def down_from_url(url, dst): # 设置stream=True参数读取大文件 response = requests.get(url, stream=True) # 通过header的content-length属性可以获取文件的总容量 file_size = int(response.headers['content-length']) if os.path.exists(dst): # 获取本地已经下载的部分文件的容量,方便继续下载,如果不存在就从头开始下载。 first_byte = os.path.getsize(dst) else: first_byte = 0 # 如果大于或者等于则表示已经下载完成,否则继续 if first_byte >= file_size: return file_size header = {"Range": f"bytes={first_byte}-{file_size}"} pbar = tqdm(total=file_size, initial=first_byte, unit='B', unit_scale=True, desc=dst) req = requests.get(url, headers=header, stream=True) with open(dst, 'ab') as f: # 每次读取一个1024个字节 for chunk in req.iter_content(chunk_size=1024): if chunk: f.write(chunk) pbar.update(1024) pbar.close() return file_sizeif __name__ == '__main__': url = input("请输入.mp4格式的视频链接地址,按回车键确认") # 根据时间戳生成文件名 down_from_url(url, str(time.time()) + ".mp4") SSH下载服务器文件 12345678910111213141516171819202122232425262728293031323334353637383940414243444546import paramikoclass LinuxFile: def __init__(self, ip, port, username, password): try: self.ip = ip self.port = port self.username = username self.password = password self.transport = paramiko.Transport((str(self.ip), int(self.port))) self.transport.connect(username=self.username, password=self.password) self.sftp = paramiko.SFTPClient.from_transport(self.transport) except Exception as e: raise e def up_file(self, localhost_file, server_file): """ 将本地文件上传至服务器 :param localhost_file: 本地文件路径 :param server_file: 服务器保存路径 :return: """ self.sftp.put(localhost_file, server_file) def down_file(self, localhost_file, server_file): """ 将服务器文件下载至本地 :param localhost_file: 本地文件路径 :param server_file: 服务器保存路径 :return: """ self.sftp.get(localhost_file, server_file) def close(self): """ 关闭服务器 :return: """ self.transport.close()if __name__ == '__main__': test = LinuxFile('47.242.218.75', '22', 'root', 'Qwer1234') # test.up_file('../2020-10-11_20-21-28.py', '/root/2020-10-11_20-21-28.py') test.down_file('/var/log/nginx/access.log','a.log')
Excel
行和列隐藏或显示行和列在电子表格中隐藏或取消隐藏列,以仅显示需要查看或打印的数据 📌 不影响计算 隐藏列 选择一个或多个列,然后按 Ctrl 选择不相邻的其他列。 右键单击选定的列,然后选择隐藏。📌 两列之间的双线表示隐藏了一个列。 ✅ 参考:微软官方文档 取消隐藏列 选择隐藏列的相邻左列和相邻右列。 右键单击选定的列,然后选择“取消隐藏”。 或双击隐藏列所存在的两列之间的双线。 自定义列的格式比如这里要设置列的格式是 年月日星期几 默认的日期和时间是没有符合要求的 自定义设置 📌 这样设置之后,比如在单元格里值设置的是 2021-01-02实际上显现的会是 2021年1月2日 星期几 ✅ 参考:微软官方文档
Kubernetes
主节点上操作定义主机名字 12345sudo vim /etc/hosts192.168.11.128 k8s-master192.168.11.129 k8s-node01192.168.11.130 k8s-node01 修改主机名 1234# 打印hostnamehostname# 修改hostname,需要重新连接,才能看到变化sduo hostnamectl set-hostname xxx 下载k8s需要的镜像 123456789101112# 打印所需要的镜像kubeadm config images list# docker pull 上面命令的镜像# 比如sudo docker pull registry.k8s.io/kube-apiserver:v1.28.3sudo docker pull registry.k8s.io/kube-controller-manager:v1.28.3sudo docker pull registry.k8s.io/kube-scheduler:v1.28.3sudo docker pull registry.k8s.io/kube-proxy:v1.28.3sudo docker pull registry.k8s.io/pause:3.9sudo docker pull registry.k8s.io/etcd:3.5.9-0sudo docker pull registry.k8s.io/coredns/coredns:v1.10.1 创建master节点 123456sudo kubeadm init --apiserver-advertise-address=192.168.110.128 --pod-network-cidr=172.16.0.0/16# 创建完之后会提示,并且执行mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config 查看k8s状态 123$ kubectl get nodeNAME STATUS ROLES AGE VERSIONk8s-node01 NotReady control-plane 4m9s v1.28.2 使用网络插件 123curl https://docs.projectcalico.org/manifests/calico.yaml -Okubectl apply -f calico.yaml
IDEA
教程IDEA查看方法被调用 修改光标所在行背景颜色 断点判断条件在断点上点右键,正常的写判断代码就行 插件Rabinbow Brackets作者:Zhihao Zhang 使用方法 Command+右键,高亮选中部分 Alt+右键,高亮选中部分,其余的代码变暗 CommitMessage作者:郭翰林 使用方法,提交的时候,点击✈️的图标 Codeium作者:Codeium 写代码的时候会有自动提示,按tab确定使用AI生成的代码 Key Promoter XTranslation作者:Yii.Guxing 选中要翻译的内容,右键选择翻译
Spring
SpringMVC 总结: 首先请求进入DispatcherServlet 由DispatcherServlet 从HandlerMappings中提取对应的Handler。 2.此时只是获取到了对应的Handle,然后得去寻找对应的适配器,即:HandlerAdapter。 拿到对应HandlerAdapter时,这时候开始调用对应的Handler处理业务逻辑了。 (这时候实际上已经执行完了我们的Controller) 执行完成之后返回一个ModeAndView 这时候交给我们的ViewResolver通过视图名称查找出对应的视图然后返回。 最后 渲染视图 返回渲染后的视图 –>响应请求。 初始化过程version 5.3.8 1234567// org.springframework.web.servlet.HttpServletBean#init@Overridepublic final void init() throws ServletException { // 子类实现,初始化web环境 // Let subclasses do whatever initialization they like. initServletBean();} 12345678// org.springframework.web.servlet.FrameworkServlet#initServletBean@Overrideprotected final void initServletBean() throws ServletException { // 初始化spring上下文 this.webApplicationContext = initWebApplicationContext(); // 子类实现 initFrameworkServlet();} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// org.springframework.web.servlet.FrameworkServlet#initWebApplicationContextprotected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } //配置和刷新spring容器(重要) //这个无非就是初始化spring ioc的环境,创建bean和实例化bean等操作 //这个方法最终也是调用refresh()方法,已在spring源码解析中解析过了 configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. synchronized (this.onRefreshMonitor) { // 初始化DispatcherServlet的配置initStrategies() onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; } 12345// org.springframework.web.servlet.DispatcherServlet#onRefresh protected void onRefresh(ApplicationContext context) { // //初始化springmvc的配置 initStrategies(context); } 总体流程: 执行DispatcherServlet的init()方法, 会执行父类的HttpServletBean的init()方法 然后调用了FrameworkServlet的initServletBean()方法 没看懂,执行initWebApplicationContext()方法,就是对spring ioc环境的初始化。那么这里就衍生出了一个面试题:spring容器和spring mvc的容器的区别?通过源码的分析,spring和spring mvc底层,都是调用了同一个refresh()方法,所以spring容器和spring mvc容器是没有区别的,都是指的是同一个容器。 (3)执行到onRefresh()方法,就是开始初始化DispatcherServlet了,也就是开始初始化spring mvc。 12345678910111213141516171819// org.springframework.web.servlet.DispatcherServlet#initStrategies protected void initStrategies(ApplicationContext context) { //上传文件 initMultipartResolver(context); //国际化 initLocaleResolver(context); //前段的主题样式 initThemeResolver(context); //初始化HandlerMappings(请求映射器)重点 initHandlerMappings(context); // 初始化HandlerAdapters(处理适配器) initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); //视图转换器 initViewResolvers(context); //重定向数据管理器 initFlashMapManager(context); } 1234567891011121314// org.springframework.web.servlet.DispatcherServlet#initHandlerMappingsprivate void initHandlerMappings(ApplicationContext context) { // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. // 通过配置文件中的配置信息,得到handlerMappings if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657// org.springframework.web.servlet.DispatcherServlet#getDefaultStrategiesprivate static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { if (defaultStrategies == null) { try { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. /** * 从属性文件加载默认策略实现 * 说白了这里的意思就是从DEFAULT_STRATEGIES_PATH这个文件当中拿出所有的配置 * 可以去数一下一共有8个: DispatcherServlet.properties == DEFAULT_STRATEGIES_PATH */ ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } } String key = strategyInterface.getName(); // defaultStrategies 是DispatcherServlet.properties 配置文件,在static静态代码块初始化 // 版本变了,不是从静态方法中获取到的 String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); for (String className : classNames) { try { // 获取class字节码文件 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); // 底层是通过调用spring的getBean的方式创建该对象(可以进行bean的属性装配) // 请求映射就是在这个方法实现装配的 Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } catch (ClassNotFoundException ex) { throw new BeanInitializationException( "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { throw new BeanInitializationException( "Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", err); } } return strategies; } else { return Collections.emptyList(); } } DispatcherServlet.properties 从DispatcherServlet.properties配置文件,可以看出handlerMapping默认是有两个: 1.BeanNameUrlHandlerMapping (主要处理object) 2.RequestMappingHandlerMapping(主要处理method) 123456789101112131415161718192021222324252627282930# Default implementation classes for DispatcherServlet's strategy interfaces.# Used as fallback when no matching beans are found in the DispatcherServlet context.# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver// HandlerMappingorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\ org.springframework.web.servlet.function.support.RouterFunctionMapping// HandlerAdapterorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager (1) initHandlerMappings方法,就是初始化我们的handlerMapping(请求映射器)。 (2) handlerMapping的主要作用是,找到请求路径对应的controller的方法。 例如:请求的路径 “/index”,然后这个handlerMapping,在初始化的时候,已经将所有controller的请求路径映射保存在一个map集合,当请求过来的时候,就将”/index”作为一个key,从map集合中找到对应的controller的index方法。 (3) 这里初始化handlerMappings ,默认是有两个handlerMappings ,是直接在defaultStrategies配置文件中获取。 (4) 那么defaultStrategies的值是什么时候初始化的呢? 通过查看源码,defaultStrategies这个值,是DispatcherServlet类的静态代码块初始化的。 全世界都知道,当一个类被初始化的时候,会执行该类的static静态代码块的。 请求阶段分析用户的一个请求过来,会由servlet接收到,然后一步一步调用到DispatcherServlet的doService方法。 123456// org.springframework.web.servlet.DispatcherServlet#doService@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { // 核心方法(重点) doDispatch(request, response);} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879// org.springframework.web.servlet.DispatcherServlet#doDispatchprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; // 异步编程 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { // 定义变量,哈哈哈,好熟悉呀 ModelAndView mv = null; Exception dispatchException = null; try { //检查请求中是否有文件上传操作 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 确定当前请求的处理程序(重点),推断controller和handler的类型, // 进到这里的getHandler方法 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //推断适配器,不同的controller类型,交给不同的适配器去处理 //如果是一个bean,mappedHandler.getHandler()返回的是一个对象 //如果是一个method,mappedHandler.getHandler()返回的是一个方法 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //到这里,spring才确定我要怎么反射调用 // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 前置拦截器处理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //通过适配器,处理请求(可以理解为,反射调用方法)(重点) // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } } 通过对DispatcherServlet的分析,得到请求的核心处理方法是doDispatch(), 主要是分了几步: (1) 检查请求中是否有文件上传操作 (2) 确定当前请求的处理的handler(重点) (3) 推断适配器,不同的controller类型,交给不同的适配器去处理 (4) 执行前置拦截器处理interceptor (5) 通过找到的HandlerAdapter ,反射执行相关的业务代码controller的方法。 (6) 返回结果。 123456789101112131415161718// org.springframework.web.servlet.DispatcherServlet#getHandler@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { //循环所有的HandlerMappings //this.handlerMappings这个是什么时候初始化的?(重点) //在handlerMappings初始化的时候 for (HandlerMapping mapping : this.handlerMappings) { //把请求传过去看能不能得到一个handler //注意:怎么得到handler和handlerMapping自己实现的逻辑有关系 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null;} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //获取handler(重点) Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // Ensure presence of cached lookupPath for interceptors and others if (!ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) { logger.debug("Mapped to " + executionChain.getHandler()); } if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain;} (1) getHandler()方法,主要是遍历在DispatcherServlet初始化是,初始化的handlerMappings。 (2) 这个方法的主要思想是,通过request的路径,去匹配对应的controller去处理。 (3) SpringMVC自己自带了2个HandlerMapping 来供我们选择 至于 为什么要有2个呢? 两种注册Controller的方式我们用2种方式来注册Controller 分别是: (1) 作为Bean的形式:实现Controller接口,重写handleRequest方法,请求路径为”/test” 123456789@Component("/test")public class TesrController implements org.springframework.web.servlet.mvc.Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("1"); return null; }} (2) 以Annotation形式: 12345678@Controllerpublic class AnnotationController { @RequestMapping("/test2") public Object test(){ System.out.println("test"); return null; }} 经过测试: (1)可以得到以Bean方式的controller,是通过BeanNameUrlHandlerMapping去匹配 (2)以注解方法的controller,是通过RequestMappingHandlerMapping去匹配 BeanNameUrlHandlerMappingBeanNameUrlHandlerMapping处理bean方式的源码分析: 12345678910111213141516171819202122232425262728293031323334353637// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal @Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求的路径 String lookupPath = initLookupPath(request); // 到对应的handler(重点)调用 lookupHandler() Object handler; if (usesPathPatterns()) { RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); handler = lookupHandler(path, lookupPath, request); } else { handler = lookupHandler(lookupPath, request); } if (handler == null) { // We need to care for the default handler directly, since we need to // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. Object rawHandler = null; if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler(java.lang.String, javax.servlet.http.HttpServletRequest)@Nullable protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { // 查看这里的方法 Object handler = getDirectMatch(lookupPath, request); if (handler != null) { return handler; } // Pattern match? List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, lookupPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); if (logger.isTraceEnabled() && matchingPatterns.size() > 1) { logger.trace("Matching patterns " + matchingPatterns); } bestMatch = matchingPatterns.get(0); } if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) { logger.trace("URI variables " + uriTemplateVariables); } return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; } 1234567891011121314151617// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getDirectMatch@Nullable private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { // 通过请求的路径,在handlerMap中去匹配。 // handlerMap这个值,什么时候填充值?在init初始化的时候,就已经存放在这个handlerMap种 Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } return null; } (1) 以Bean方式的controller,匹配请求的路径,是通过一个handlerMap去匹配,比较简单。 (2) 这里的问题是,这个handlerMap的值,是什么时候放进去的? 通过源码分析,BeanNameUrlHandlerMapping是实现了ApplicationContextAware接口。 如果你精通spring的源码,就知道spring的实例bean的时候,会回调这些类的setApplicationContext()方法。 12345678910111213141516171819202122232425262728// org.springframework.context.support.ApplicationObjectSupport#setApplicationContext@Override public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); // 初始化ApplicationContext,就会执行到子类的方法(重点) initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } } 123456789// 没看懂怎么走到这里来呢// org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#initApplicationContext @Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); // 检测出handler detectHandlers(); } 12345678910111213141516171819// org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#detectHandlers protected void detectHandlers() throws BeansException { // 获取spring ioc所有的beanName,然后判断beanName,那些是以 "/" 开头 ApplicationContext applicationContext = obtainApplicationContext(); String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); // Take any bean name that we can determine URLs for. for (String beanName : beanNames) { // 然后判断beanName,那些是以 "/" 开头 String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // 注册handler(重点) // URL paths found: Let's consider it a handler. registerHandler(urls, beanName); } } } 1234567// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler(java.lang.String[], java.lang.String) protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { Assert.notNull(urlPaths, "URL path array must not be null"); for (String urlPath : urlPaths) { registerHandler(urlPath, beanName); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler(java.lang.String, java.lang.Object)protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Assert.notNull(urlPath, "URL path must not be null"); Assert.notNull(handler, "Handler object must not be null"); Object resolvedHandler = handler; // Eagerly resolve handler if referencing singleton via name. if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { throw new IllegalStateException( "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); } } else { if (urlPath.equals("/")) { if (logger.isTraceEnabled()) { logger.trace("Root mapping to " + getHandlerDescription(handler)); } setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { if (logger.isTraceEnabled()) { logger.trace("Default mapping to " + getHandlerDescription(handler)); } setDefaultHandler(resolvedHandler); } else { // 最终put到map集合中(省略其他无关代码) this.handlerMap.put(urlPath, resolvedHandler); if (getPatternParser() != null) { this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); } if (logger.isTraceEnabled()) { logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } } BeanNameUrlHandlerMapping处理bean方式的源码分析,其实是很简单: (1) 在类初始化的时候,就已经将所有实现了Controller接口的controller类,拿到他们的@Componet(‘/test’) (2) 然后将’/test’这个作为key,controller类作为value,放入到一个map集合。 (3) 当一个请求过来的时候,拿到这个请求的uri,在map里面找,找到了即表示匹配上 RequestMappingHandlerMapping处理注解方式的源码分析: 1234567891011121314151617// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal// 对于RequestMappingHandlerMapping,indexController.index(),方法的请求路径映射 @Override @Nullable protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 获取请求路径 String lookupPath = initLookupPath(request); this.mappingRegistry.acquireReadLock(); try { // 通过请求路径,获取handler HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod@Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // 从mappingRegistry的urlLookup,匹配请求路径 List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { for (Match match : matches) { if (match.hasCorsConfig()) { return PREFLIGHT_AMBIGUOUS_MATCH; } } } else { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.getHandlerMethod().getMethod(); Method m2 = secondBestMatch.getHandlerMethod().getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); handleMatch(bestMatch.mapping, lookupPath, request); // 返回handler return bestMatch.getHandlerMethod(); } else { return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } } 12345// 3.AbstractHandlerMethodMapping.MappingRegistry#getMappingsByDirectPath@Nullablepublic List<T> getMappingsByDirectPath(String urlPath) { return this.pathLookup.get(urlPath);} RequestMappingHandlerMapping处理注解方式的源码分析,比较复杂,用一个MappingRegistry维护所有的请求路径映射。 MappingRegistry的初始化,也是在该bean实例化的时候,就已经做好的了。 原理也是和上一个差不多,都是从一个map集合里面匹配。所以这里就不再做解析了 总结:getHandler() 找适配器 123456789101112// org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } 其实能看见他是从一个handlerAdapters属性里面遍历了我们的适配器 这个handlerAdapters哪来的呢? 跟我们的HandlerMappings一样 在他的配置文件里面有写,就是我们刚刚所说的 。 至于什么是适配器,我们结合Handler来讲, 就如我们在最开始的总结时所说的, 一开始只是找到了Handler 现在要执行了,但是有个问题,Handler不止一个, 自然而然对应的执行方式就不同了, 这时候适配器的概念就出来了:对应不同的Handler的执行方案。当找到合适的适配器的时候, 基本上就已经收尾了,因为后面在做了一些判断之后(判断请求类型之类的),就开始执行了你的Handler了,上代码: mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 这个mv就是我们的ModlAndView 其实执行完这一行 我们的Controller的逻辑已经执行完了, 剩下的就是寻找视图 渲染图的事情了。 总结: 其实我们的SpringMVC关键的概念就在于Handler(处理器) 和Adapter(适配器) 通过一个关键的HandlerMappings 找到合适处理你的Controller的Handler 然后再通过HandlerAdapters找到一个合适的HandlerAdapter 来执行Handler即Controller里面的逻辑。 最后再返回ModlAndView… 参考:https://juejin.cn/post/6991290858880368676 Spring事务的传播 参考:https://segmentfault.com/a/1190000013341344 传播等级 描述 理解 REQUIRED 默认的事务传播级别表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 A有事务,B就跟着用A没有事务,B就开启自己的事务,只B方法用 SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 MANDATORY 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 REQUIRES_NEW 表示创建一个新的事务如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。 NESTED 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。 默认数据是anthony和0 REQUIRED A方法有事务,A方法报错,有一个报错都会回滚,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A方法没有事务,A方法有报错,结果是:anthony2和1 B方法自己开启事务,就不管A事务了,所以A方法,就算报错了,也成功写入数据库,B事务没有报错,也成功写入数据库 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A方法没有事务,A方法和B方法都报错,,结果是:anthony2和0 A方法没有事务,所以A方法插入数据库成功,就算报错,也没有回滚 B方法自己开始事务,B方法报错,所以回滚 1234567891011121314151617@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRED)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} SUPPORTS A方法有事务,A方法报错,都回滚,,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,,A方法报错,都没有回滚,结果是:anthony2和1 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,,A,B方法都报错,都没有回滚,结果是:anthony2和1 1234567891011121314151617@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.SUPPORTS)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} MANDATORY A有事务,A报错,都回滚,结果是:anthony和0 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.MANDATORY)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} A没有事务,运行报错了 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.MANDATORY)public void methodB(){ Test byId = testService.getById(1); byId.setParentId(1); byId.updateById();} REQUIRES_NEW 测试的时候,不要操作同一条数据,容易超时….. A开始事务,B也开始事务,B报错了,B回滚,A插入成功 1234567891011121314151617@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} 这样没有复现出问题 12345678910111213141516@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A没有事务,B有事务 A报错,没有回滚,B插入数据成功 外围方法异常,不影响内部调用的方法 12345678910111213141516@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} A没有事务,B有事务 A插入数据成功,B回滚 内部调用的方法,不影响外围的方法成功插入 123456789101112131415@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.REQUIRES_NEW)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} NOT_SUPPORTED A有事务,B也有事务,A回滚了,B报错了,没有回滚 12345678910111213141516171819@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NOT_SUPPORTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A有事务,B也有事务,A回滚了,B没有回滚 1234567891011121314151617181920@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB(); int i = 1 / 0;}@Transactional(propagation= Propagation.NOT_SUPPORTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} NEVER 直接报错 123456789101112131415@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NEVER)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} NESTED 全部提交成功 123456789101112131415@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById();} 全部失败 12345678910111213141516@PostMapping("/test1")@Transactionalpublic void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} A没有事务,B有事务 A执行成功,B回滚成功 123456789101112131415@PostMapping("/test1")public void methodA(){ Test byId = testService.getById(1); byId.setUsername("anthony2"); byId.updateById(); transactionalController.methodB();}@Transactional(propagation= Propagation.NESTED)public void methodB(){ Test byId = testService.getById(2); byId.setParentId(1); byId.updateById(); int i = 1 / 0;} 拦截器和过滤器1、过滤器和拦截器触发时机不一样,先拦截器,后过滤器 2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。 3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射 4、过滤器是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。 5、Filter的执行由Servlet容器回调完成,而拦截器通常通**过动态代理(反射)**的方式来执行。 6、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。 7、过滤器只能在请求的前后使用,而拦截器可以详细到每个方法 Spring IOCSpring提供了两种容器:BeanFactory和ApplicationContext **BeanFactory:**基本的IoC容器,默认采用延迟初始化策略(lazy-load),即只有当客户端对象需要访问容器中某个bean对象的时候,才会对该bean对象进行初始化以及依赖注入操作。所以BeanFactory容器的特点是启动初期速度快,所需资源有限,适合于资源有限功能要求不严格的场景。 ApplicationContext: ApplicationContext在BeanFactory基础上构建,支持其他的高级特性,如国际化,事件发布等。相对于BeanFactory容器来说,ApplicationContext在启动的时候即完成资源的初始化,所以启动时间较长,适合于系统资源充足,需要更多功能的场景 Spring BeanJava 中Bean的定义: 类中所有的属性都必须封装,即:使用private声明;这个不太确定 封装的属性如果需要被外部所操作,则必须编写对应的setter、getter方法; 一个JavaBean中至少存在一个无参构造方法。 12345678910111213141516171819202122public class Staff{ private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public int getAge() { return age; }} 而Spring IoC容器就是管理bean的工厂。Spring中bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的。Spring可以采用XML配置文件的方式来管理和配置Bean信息,如下: 12345678<?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="user" class="com.wgs.spring.bean.User"></bean></beans> <beans>是XML配置文件中根节点,下面可包含多个<bean>子节点。Spring的XML配置文件中的配置与<bean>元素是一一对应的。 属性 描述 id 注册到容器的对象都有一个唯一的id值,如id=”user” name bean的别名,name可以使用逗号、空格或冒号等分割指定多个name,而id就不可以 scope 作用域 constructor-arg 用来注入依赖关系 properties 用来注入依赖关系 autowiring mode 用来注入依赖关系 lazy-initialization mode 是否延迟加载 initialization method bean被创建的时候,初始化的的方法 destruction method 销毁指定的方法 Spring Bean 生命周期2.低昂registerBeanFactoryPostProcessor 完成扫描,运行之前,不会有我们自己的类,除了@componentScan这个注解的这个类,等完成之后,就会有我们自己的类 1:实例化一个ApplicationContext的对象;2:调用bean工厂后置处理器完成扫描;3:循环解析扫描出来的类信息;4:实例化一个BeanDefinition对象来存储解析出来的信息;5:把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,以便后面实例化bean;6:再次调用bean工厂后置处理器;7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证,需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否abstract等等;8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意我这里说的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;10:spring处理合并后的beanDefinition(合并?是spring当中非常重要的一块内容,后面的文章我会分析);11:判断是否支持循环依赖,如果支持则提前把一个工厂存入singletonFactories——map;12:判断是否需要完成属性注入13:如果需要完成属性注入,则开始注入属性14:判断bean的类型回调Aware接口15:调用生命周期回调方法16:如果需要代理则完成代理17:put到单例池——bean完成——存在spring容器当中 Spring Bean 循环依赖https://juejin.im/post/6844904166351978504#h5 AnnotationConfigApplicationContext#AnnotationConfigApplicationContext 123456public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); // 关键方法 refresh();} org.springframework.context.support.AbstractApplicationContext#refresh 1234567891011121314151617181920212223242526272829303132333435363738394041424344@Overridepublic void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. // 完成所有的扫描 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. // 实例化所有没有延迟的单例类 finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } }} org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization 1234567protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // .... // Instantiate all remaining (non-lazy-init) singletons. // 实例化所有单例,非lazy beanFactory.preInstantiateSingletons();} org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 12345678910111213141516171819202122232425262728293031323334353637383940414243@Overridepublic void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // Trigger initialization of all non-lazy singleton beans... for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); // 验证,判断这个是是不是抽象的和是不是单例的和是不是延迟加载的 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); if (bean instanceof FactoryBean) { FactoryBean<?> factory = (FactoryBean<?>) bean; boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged( (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext()); } else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } } else { // 验证一切都通过的类,开始实例化普通的bean,还不是spring bean getBean(beanName); } } } // ....} org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 1234@Overridepublic Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false);} org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 这里面大部分都是验证,比如depenon,或者import 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { // 理解bean的名字是否非法 String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. // 这里的方法啊 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. // 判断这个类是不是在创建过程中,循环依赖的时候要用 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // 方法注入 // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (!typeCheckOnly) { markBeanAsCreated(beanName); } try { RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // Create bean instance. // 判断类是不是单例 if (mbd.isSingleton()) { // getSingleton(String,facotory) 这个方法里有正在创建中的标识设置 sharedInstance = getSingleton(beanName, () -> { try { // 完成了目标对象的创建 // 如果需要代理,还创建了代理 return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if required type matches the type of the actual bean instance. if (requiredType != null && !requiredType.isInstance(bean)) { try { T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean;} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String) 12345678910111213141516171819202122232425262728293031323334// 上个代码块第七行调用的@Override@Nullablepublic Object getSingleton(String beanName) { return getSingleton(beanName, true);}/** Cache of singleton objects: bean name to bean instance. *//** 缓存单例对象: bean name to bean instance. */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) { // 初始化的时候这里肯定是null,但是在初始化完成之后,再调用getBean就肯定不是null // isSingletonCurrentlyInCreation 这个方法很重要,说明对象是不是正在创建 // singletonFactories 也很重要 Object singletonObject = this.singletonObjects.get(beanName); // 判断循环依赖的时候 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject;} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. // 从beanDefinition对象中获取出来bean的类型 Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. // 第一次调用个后置处理器 Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { // 调用方法 Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } } org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 实例化对象,里面第二次调调用后置处理器 // 反射调用对象的构造方法 // 这里java对象就已经有了 instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { // 第三次调用后置处理器 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // 判断是否需要循环依赖 boolean earlySingletonExposure = // 到这里了,也肯定是true (mbd.isSingleton() && // 默认值是true this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 第四次调用后置处理器,判断是否需要AOP addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { // 填充属性,也就是我们说的自动注入 // 里面会完成第五次和第六次后置处理器的调用 // 看这里 populateBean(beanName, mbd, instanceWrapper); // 初始化spring // 里面会进行第七次和第八次后置处理的调用个 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } // 省略代码 } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject;} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point. Class<?> beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // Shortcut when re-creating the same bean... boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { return instantiateBean(beanName, mbd); } } // Candidate constructors for autowiring? // 第二次调用后置处理器构造方法,通过反射实例化对象,这时候构造方法里有打印,就会打印出日志 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // Preferred constructors for default construction? ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); } // No special handling: simply use no-arg constructor. return instantiateBean(beanName, mbd);} org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { // Skip property population phase for null instance. return; } } // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } } } PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); int resolvedAutowireMode = mbd.getResolvedAutowireMode(); if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { // 这里的ibp常用的有两种类型 // 1.@Resouce 使用的是CommonAnnotationBeanPostProcessor // 2.@Autowire 使用的是AutoWireAnnotationBeanPostProcessor InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // 这里会调用属性的注入,也就是在这里,碰到循环依赖的时候,就会调用个 // org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } if (pvs != null) { applyPropertyValues(beanName, mbd, bw, pvs); }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); } if (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } // 重点,如果没有获取到,就设置个标识,表示正在创建 beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation 12345678910/** Names of beans that are currently in creation. */// 添加到这里来了之后就标识当前这个bean正在创建private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }} org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory 二级缓存 12345678910protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }} 三个缓存12345678// singletonObjects:第一级缓存,里面存放的都是创建好的成品Bean。private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object(256);// earlySingletonObjects : 第二级缓存,里面存放的都是半成品的Beanprivate final Map<String, Object> earlySingletonObjects = new HashMap<String, Object(16);// singletonFactories :第三级缓存, 不同于前两个存的是 Bean对象引用,此缓存存的bean 工厂对象,也就存的是 专门创建Bean的一个工厂对象。此缓存用于解决循环依赖private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); 两个缓存能解决不A引用创建后,提前暴露到半成品缓存中 依赖B,创建B ,B填充属性时发现依赖A, 先从成品缓存查找,没有,再从半成品缓存查找 取到A的早期引用。 1B顺利走完创建过程`, 将`B的早期引用从半成品缓存移动到成品缓存 B创建完成,A获取到B的引用,继续创建。 A创建完成,将A的早期引用从半成品缓存移动到成品缓存 为啥需要三个缓存上面两个缓存的地方,我们只是没有考虑代理的情况。 Bean在创建的最后阶段,会检查是否需要创建代理,如果创建了代理,那么最终返回的就是代理实例的引用。我们通过beanname获取到最终是代理实例的引用 也就是说:假设A最终会创建代理,提前暴露A的引用, B填充属性时填充的是A的原始对象引用。A最终放入成品库里是代理的引用。那么B中依然是A的早期引用。这种结果最终会与我们的期望的大相径庭了。 完整的流程关键点: A绑定到ObjectFactory 注册到工厂缓存singletonFactory中, B在填充A时,先查成品缓存有没有,再查半成品缓存有没有,最后看工厂缓存有没有单例工厂类,有A的ObjectFactory。调用getObject ,执行扩展逻辑,可能返回的代理引用,也可能返回原始引用。 成功获取到A的早期引用,将A放入到半成品缓存中,B填充A引用完毕。 代理问题, 循环依赖问题都解决了 Spring Bean 二次开发在实例化Bean之前,Spring会调用扩展的类,实现BeanFactoryPostProcessor,并且机上@component注解,如果没有实现,spring就不会调用 Spring AOPAOP是什么AOP的全称是Aspect Orient Programming,即面向切面编程。是对OOP(Object Orient Programming)的一种补充,战门用于处理一些具有横切性质的服务。常常用于日志输出、安全控制等。 上面说到是对OOP的一种补充,具体补充的是什么呢?考虑一种情况,如果我们需要在所有方法执行前打印一句日志,按照OOP的处理思想,我们需要在每个业务方法开始时加入一些语句,但是我们辛辛苦苦加完之后,如果又要求在这句日志打印后再打印一句,那是不是又要加一遍?这时候你一定会想到,在某个类中编写一个日志打印方法,该方法执行这些日志打印操作,然后在每个业务方法之前加入这句方法调用,这就是面向对象编程思想。但是如果要求我们在业务方法结束时再打印一些日志呢,是不是还要去每个业务方法结束时加一遍?这样始终不是办法,而且我们总是在改业务方法,在业务方法里面掺杂了太多的其他操作,侵入性太高。 这时候AOP就起到作用了,我们可以编写一个切面类(Aspect),在其中的方法中来编写横切逻辑(如打印日志),然后通过配置或者注解的方式来声明该横切逻辑起作用的位置。 实现技术AOP(这里的AOP指的是面向切面编程思想,而不是Spring AOP)主要的的实现技术主要有Spring AOP和AspectJ。 1、AspectJ的底层技术。 AspectJ的底层技术是静态代理,即用一种AspectJ支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,相对于下面说的运行时增强,编译时增强的性能更好。 2、Spring AOP Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持。 JDK动态代理只能为接口创建动态代理实例,而不能对类创建动态代理。需要获得被目标类的接口信息(应用Java的反射技术),生成一个实现了代理接口的动态代理类(字节码),再通过反射机制获得动态代理类的构造函数,利用构造函数生成动态代理类的实例对象,在调用具体方法前调用invokeHandler方法来处理。 CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类。 但是Spring AOP基于注解配置的情况下,需要依赖于AspectJ包的标准注解,但是不需要额外的编译以及AspectJ的织入器,而基于XML配置不需要。 知识点PointCut你想要去切某个东西之前总得先知道要在哪里切入是吧,切点格式如下:execution(* com.nuofankj.springdemo.aop.*Service.*(..))格式使用了正常表达式来定义那个范围内的类、那些接口会被当成切点 Advice通知,所谓的Advice其实就是定义了Aop何时被调用,确实有种通知的感觉 Before 在方法被调用之前调用 After 在方法完成之后调用 After-returning 在方法成功执行之后调用 After-throwing 在方法抛出异常之后调用 Around 在被通知的方法调用之前和调用之后调用 JoinPointJoinPoint连接点,其实很好理解,上面又有通知、又有切点,那和具体业务的连接点又是什么呢?没错,其实就是对应业务的方法对象,因为我们在横切代码中是有可能需要用到具体方法中的具体数据的,而连接点便可以做到这一点。 Aspect就是我们关注点的模块化。这个关注点可能会横切多个对象和模块,事务管理是横切关注点的很好的例子。它是一个抽象的概念,从软件的角度来说是指在应用程序不同模块中的某一个领域或方面。又pointcut 和advice组成。 Weaving把切面应用到目标对象来创建新的 advised 对象的过程。 原理简单说说 AOP 的设计 每个 Bean 都会被 JDK 或者 Cglib 代理。取决于是否有接口。 每个 Bean 会有多个“方法拦截器”。注意:拦截器分为两层,外层由 Spring 内核控制流程,内层拦截器是用户设置,也就是 AOP。 当代理方法被调用时,先经过外层拦截器,外层拦截器根据方法的各种信息判断该方法应该执行哪些“内层拦截器”。内层拦截器的设计就是职责连的设计。 流程代理的创建(按步骤): 首先,需要创建代理工厂,代理工厂需要 3 个重要的信息:拦截器数组,目标对象接口数组,目标对象。 创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。 当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。 注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。 代理的调用 当对代理对象进行调用时,就会触发外层拦截器。 外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。 当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。 SpringMCC临时用的https://zhuanlan.zhihu.com/p/62562499 设置属性123456789// 1. 设置属性// Make web application context availablerequest.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());// Make locale resolver availablerequest.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);// Make theme resolver availablerequest.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 根据 Request 请求的 URL 得到对应的 handler 执行链,其实就是拦截器和 Controller 代理对象12// 2. 找 handler 返回执行链HandlerExecutionChain mappedHandler = getHandler(request); 得到 handler 的适配器123// This will throw an exception if no adapter is found// 3. 返回 handler 的适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 循环执行 handler 的 pre 拦截器12345678// 4. 循环执行 handler 的 pre 拦截器for (int i = 0; i < mappedHandler.getInterceptors().length; i++) { HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; // pre 拦截器 if (!interceptor.preHandle(request, response, mappedHandler.getHandler())) { return; }} 执行真正的 handler,并返回 ModelAndView(Handler 是个代理对象,可能会执行 AOP )12// 5. 执行真正的 handler,并返回 ModelAndView(Handler 是个代理对象,可能会执行 AOP )ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler()); 循环执行 handler 的 post 拦截器123456789101112131415161718// 6. 循环执行 handler 的 post 拦截器for (int i = mappedHandler.getInterceptors().length - 1; i >=0 ; i--) { HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; // post 拦截器 interceptor.postHandle(request, response, mappedHandler.getHandler());}# 根据 ModelAndView 信息得到 View 实例 View view = null;if (mv.isReference()) { // We need to resolve this view name // 7. 根据 ModelAndView 信息得到 View 实例 view = this.viewResolver.resolveViewName(mv.getViewName(), locale);}# 渲染 View 返回// 8. 渲染 View 返回view.render(mv.getModel(), request, response); 其实理解这些才是最重要的。 用户发送请求至前端控制器DispatcherServlet DispatcherServlet收到请求调用HandlerMapping处理器映射器。 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 HandlerAdapter执行处理器(handler,也叫后端控制器)。 Controller执行完成返回ModelAndView HandlerAdapter将handler执行结果ModelAndView返回给DispatcherServlet DispatcherServlet将ModelAndView传给ViewReslover视图解析器 ViewReslover解析后返回具体View对象 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。 DispatcherServlet响应用户 Springboot 启动流程https://juejin.im/post/6844903669998026759 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象 然后由 SpringApplicationRunListener 来发出 starting 消息 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息 创建 ApplicationContext 初始化 ApplicationContext,并设置 Environment,加载相关配置等 由 SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK refresh ApplicationContext,完成IoC容器可用的最后一步 由 SpringApplicationRunListener 来发出 started 消息 完成最终的程序的启动 由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了 静态变量注入1234application.properties中配置下面两个配置项ccb.ip.address=10.25.177.31ccb.ip.port=1600下面问题代码中读取不到application.properties配置文件中的配置 123456789101112131415161718@Componentpublic class BISFrontFileUtil { private static Logger logger = LoggerFactory.getLogger(BISFrontFileUtil.class); private static String CCBIPADDRESS; private static int CCBIPPORT; @Value("${ccb.ip.address}") public void setCCBIPADDRESS(String cCBIPADDRESS) { CCBIPADDRESS = cCBIPADDRESS; } @Value("${ccb.ip.port}") public void setCCBIPPORT(int cCBIPPORT) { CCBIPPORT = cCBIPPORT; }} 注意: 修正代码中的@Component不可丢掉了 set方法要是非静态的 SpringBoot的注解 @Configuration @Configuration配置并启动Spring容器@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文) 123456789import org.springframework.context.annotation.Configuration;@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); }} 相当于 1234567891011121314<?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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false"></beans> 主方法进行测试: 1234567891011121314151617181920import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new // ClassPathXmlApplicationContext("spring-context.xml"); }}// 结果WARNING: All illegal access operations will be denied in a future releasetestconfig collection init successProcess finished with exit code 0 @Configuration启动容器+@Bean注册Bean@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的,作用为:注册bean对象 1234567891011121314151617181920212223242526272829303132333435@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } // @Bean注解注册bean,同时可以指定初始化和销毁方法 // @Bean(name="testBean",initMethod="start",destroyMethod="cleanup") //name属性相当于<bean>标签的id @Bean @Scope("prototype") public TestBean testBean() { return new TestBean(); }}class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }} 测试类 123456789101112131415161718192021public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); System.out.println(context); // 如果加载spring-context.xml文件: // ApplicationContext context = new // ClassPathXmlApplicationContext("spring-context.xml"); //获取bean TestBean testBean = (TestBean) context.getBean("testBean"); testBean.sayHello(); }}// 结果结果:testconfig collection init successTestBean sayHello... @Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同(第一个单词转小写) @Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域 既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描 scope属性1). singleton属性值(掌握):默认值,单例2). prototype属性值(掌握):多例(原型作用域)3). request属性值(了解):创建对象,把对象放到request域里4). session属性值(了解):创建对象,把对象放到session域里5). globalSession属性值(了解):创建对象,把对象放到globalSession域里 @Bean下管理bean的生命周期1234567// 用上面的例子//@Bean注解注册bean,同时可以指定初始化和销毁方法@Bean(name="testBean",initMethod="start",destroyMethod="cleanUp")@Scope("prototype")public TestBean testBean() { return new TestBean();} 测试类 12345// 结果testconfig collection init successorg.springframework.context.annotation.AnnotationConfigApplicationContext@41975e01, started on Mon Jul 19 09:51:42 PST 2021TestBean init...TestBean sayHello... @Configuration启动容器+@Component注册Beanbean类 1234567891011121314151617181920//添加注册bean的注解@Componentpublic class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }} 配置类: 1234567891011121314151617@Configuration//添加自动扫描注解,basePackages为TestBean包路径@ComponentScan(basePackages = "com.example.demo.spring2")public class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } // @Bean注解注册bean,同时可以指定初始化和销毁方法// @Bean(name="testBean",initMethod="start",destroyMethod="cleanup")//// @Bean// @Scope("prototype")// public TestBean testBean() {// return new TestBean();// }} 测试类: 123456789101112131415161718public class Main { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); //获取bean TestBean testBean1 = (TestBean) context.getBean("testBean"); testBean1.sayHello(); }}// 结果testconfig collection init successTestBean sayHello... AnnotationConfigApplicationContext 注册 AppContext 类的两种方法第一种: 123456789public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); //获取bean TestBean tb = (TestBean) context.getBean("testBean"); tb.sayHello();} 第二种: 1234567public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(TestConfig.class); annotationConfigApplicationContext.refresh(); }} @Configuration组合xml配置类 12345678@Configuration@ImportResource("classpath:configtest.xml")public class WebConfig { public WebConfig(){ System.out.println("WebConfig coolection init success"); }} 实体类 123456789101112131415161718192021222324252627282930public class TestBean2 { private String username; private String url; private String password; public void setUsername(String username) { this.username = username; } public void setUrl(String url) { this.url = url; } public void setPassword(String password) { this.password = password; } public void sayHello() { System.out.println("TestBean2 sayHello..."+username); } public void start() { System.out.println("TestBean2 init..."); } public void cleanUp() { System.out.println("TestBean2 destroy..."); }} spring的xml配置文件 123456789<?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="testBean2" class="com.example.demo.spring3.TestBean2"> <property name="username" value="ranjun"/> </bean></beans> 测试类 123456789101112131415161718public class TestMain2 { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); // 如果加载spring-context.xml文件: // ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); // 获取bean TestBean2 tb = (TestBean2) context.getBean("testBean2"); tb.sayHello(); }}// 结果WebConfig coolection init successTestBean2 sayHello...ranjun @Configuration组合xml和其它注解实体类: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public class TestBean { private String username; private String url; private String password; public void sayHello() { System.out.println("TestBean sayHello..."); } public void start() { System.out.println("TestBean init..."); } public void cleanup() { System.out.println("TestBean destroy..."); }}public class TestBean2 { private String username; private String url; private String password; public void setUsername(String username) { this.username = username; } public void setUrl(String url) { this.url = url; } public void setPassword(String password) { this.password = password; } public void sayHello() { System.out.println("TestBean2 sayHello..."+username); } public void start() { System.out.println("TestBean2 init..."); } public void cleanUp() { System.out.println("TestBean2 destroy..."); }} 配置类 1234567891011121314151617181920212223@Configurationpublic class TestConfig { public TestConfig(){ System.out.println("testconfig collection init success"); } @Bean @Scope("prototype") public TestBean testBean() { return new TestBean(); }}@Configuration@ImportResource("classpath:configtest.xml")@Import(TestConfig.class)public class WebConfig { public WebConfig(){ System.out.println("WebConfig coolection init success"); }} 测试类: 1234567891011121314151617181920public class TestMain2 { public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); // 获取bean TestBean tb = (TestBean) context.getBean("testBean"); tb.sayHello(); TestBean2 tb2 = (TestBean2) context.getBean("testBean2"); tb2.sayHello(); }}// 结果WebConfig coolection init successtestconfig collection init successTestBean sayHello...TestBean2 sayHello...ranjun
Mybatis
教程 参考: 官方文档 源码 Provider的使用1.一定要注意type的类名.class 和 method方法名,还要注意形参也得是一样的 2.Provider的方法,大概就三个方法sql.SELECT,sql.WHERE,sql.FROM 3.SQL 对象里的方法名跟别的不一样,小写的不行,idea也识别不到,要用大写,比如SLELECT 4.Provider里返回的是String 注解 一对多查询(多个参数) 注解 一对多查询(多个参数)123456789101112131415161718192021222324252627282930313233343536373839404142@SelectProvider(method="queryPageFloors",type=BarterGroupProvider.class)@Results({ @Result(property = "userId",column="user_id"), @Result(property = "floorNum",column="floor_num"), @Result(property = "floorSecond",column="id"), @Result(property = "say",column="note"), @Result(property = "joinOrPublish",column="join_or_publish"), @Result(property="categorys",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryCategory")), @Result(property="productIds",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryProducts")), @Result(property="beforeResult",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupEvaluationMapper.queryAll")), @Result(property="barterGroupUser",javaType = BarterGroupUser.class,column="{user_id=user_id,id = barter_group_id }",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.isHead")), @Result(property="futureResultNum",column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.futureResultNum"))})List<QueryFloors> queryFloors3(Page<QueryFloors> page);/** * 分类 * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_category WHERE barter_group_details_id = #{id} limit 0,7 ")List<BarterGroupCategory> queryCategory(@Param("id") Long id);/** * 产品 * @param id * @return */@Select("SELECT product_id from ecloud_marketing.barter_group_product_category WHERE barter_group_details_id = #{id} limit 0,3 ")List<Long> queryProducts(@Param("id") Long id);/** * 团员 * @param userId * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_user WHERE user_id =#{user_id} and barter_group_id = #{id} and status = 1")BarterGroupUser isHead(Map<String,Object> map);@Select("SELECT count(*) from ecloud_marketing.barter_group_evaluation WHERE barter_group_details_id = #{id}")Integer futureResultNum(@Param("id") Long id); 1234567891011121314151617181920212223242526272829303132333435363738public class QueryFloors implements Serializable { @ApiModelProperty("我有的产品的id") List<ProductBarter> list; @ApiModelProperty("封装前的评论数据") private List<BarterGroupEvaluation> beforeResult; @ApiModelProperty("封装后的评论数据有分页给功能") private Page<BarterGroupEvaluation> beforeResultPage; @ApiModelProperty("封装后的评论数据") private List<BarterGroupEvaluationResult> futureResult; @ApiModelProperty("剩余评论条数") private Integer futureResultNum; @ApiModelProperty("分类") List<BarterGroupCategory> categorys; @ApiModelProperty(value="楼层数") private Integer floorNum; @ApiModelProperty(value="楼层id") private Long floorSecond; @ApiModelProperty(value="要说的") private String say; @ApiModelProperty(value="加入或者是发布") private Integer joinOrPublish; @ApiModelProperty("会员表") private BarterGroupUser barterGroupUser; @ApiModelProperty(value="加入时间") private Long joinData;} 注解 一对多查询(一个参数) 标签mybatis的xml的常用标签: include和sql标签 12345678<sql id="query_column"> id,user_no</sql><select id="test"> select <include refid="query_column"></include> from user_info</select> where标签 12345678910<select id="test"> select * from user_info where <if test="userName != null and userName != ''"> user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if></select> 如果userName= null,则sql语句就变成 1select * from user_info where and password = #{password} where标签可以去除where语句中的第一个and 或 or。 1234567891011<select id="test"> select * from user_info <where> <if test="userName != null and userName != ''"> and user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if> </where></select> set标签 123456789101112<update id="myupdate"> update user_info <set> <if test="userName != null"> user_name = #{userName}, </if> <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> trim标签 where标签只能去除第一个and或or,不够灵活,trim标签功能更加强大。 trim标签的属性如下: prefix 在trim标签内容前面加上前缀 suffix 在trim标签内容后面加上后缀 prefixOverrides 移除前缀内容。即 属性会忽略通过管道分隔的文本序列,多个忽略序列用 “|” 隔开。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除。 suffixOverrides 移除前缀内容。 12345678<trim prefix="where" prefixOverrides="and"> <if test="userId != null"> user_id=#{userId} </if> <if test="pwd != null and pwd !=''"> user_id=#{userId} </if></trim> foreach标签 foreach元素的属性主要有item,index,collection,open,separator,close. collection 要做foreach的对象,作为入参时,List对象默认用”list”代替作为键,数组对象有”array”代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = “ids.id“,必填 item 合中元素迭代时的别名,必选 index 在list和数组中,index是元素的序号,在map中,index是元素的key,可选 open foreach代码的开始符号,一般是(和close=”)”合用。常用在in(),values()时。可选 separator 元素之间的分隔符,例如在in()的时候,separator=”,”会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样,可选。 close foreach代码的关闭符号,一般是)和open=”(“合用。常用在in(),values()时。可选 choose,when,otherwise标签 功能有点类似于Java中的 swicth - case - default 12345678910111213141516<select id="getUser" resultType="com.cat.pojo.User"> SELECT * FROM user <where> <choose> <when test="id != null and test.trim() != '' "> id = #{id} </when> <when test="name != null and name.trim() != '' "> name = #{name} </when> <otherwise> age = 17 </otherwise> </choose></where></select> if标签 123456<!-- 判断字符串--><if test="item != null and item != ''"></if><!-- 如果是Integer类型的需要把and后面去掉或是加上or--><if test="item != null"></if><if test="item != null and item != '' or item == 0"></if> 存过/函数存过 12345678<select id="pNextSupperUsers" parameterType="map" statementType="CALLABLE" resultType="vo.UserAgentVO"> { call p_next_supper_users( #{type,mode=IN,jdbcType=INTEGER}, #{userId,mode=IN,jdbcType=BIGINT} ) }</select> 123456List<UserAgentVO> pNextSupperUsers(Map<String, Object> param);Map<String, Object> param = new HashMap<>();param.put("type", 1);param.put("userId", userId);List<UserAgentVO> list = userInfoMapper.pNextSupperUsers(param); 函数 1234SELECT fn_next_user_count ( 1, u.id ) AS teamCount,FROMuser_info 源码参考 b站博学谷 架构设计 启动测试方法123456789101112131415161718192021# 第一种调用方法public static void test(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA"); sqlSession.close();}# 第二种调用方法public static void test2(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); Map map = userMapper.getUA(); sqlSession.close();} 下面的代码,大概意思就是能加载的配置文件的信息,解释 InputStream inputStream = Resources.getResourceAsStream(resource);这行代码的作用 读取mybatis的配置文件12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.String)public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource);}// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.ClassLoader, java.lang.String)public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) { throw new IOException("Could not find resource " + resource); } return in;}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader)public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { return getResourceAsStream(resource, getClassLoaders(classLoader));}// org.apache.ibatis.io.ClassLoaderWrapper#getClassLoadersClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader};}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader[])// 找到一个可以用的ClassloaderInputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // try to find the resource as passed InputStream returnValue = cl.getResourceAsStream(resource); if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } if (null != returnValue) { return returnValue; } } } return null;} 下面的代码,解释SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);,用来解析全局配置文件和解析mapper文件 下面是解析全局配置文件 解析全局配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null);}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 1.创建XpathParse解析器对象,根据inputStream解析成Document对象 // 2.创建全局配置Configuration对象 // 使用构建者模式,好处降低偶尔,分离复杂对象的创建 // 构建XMLConfig XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 根据Xpath解析xml配置文件,将配置文件封装到Configuration对象 // 返回DefaultSqlSessionFactory对象 // parse() 就是配置文件解析完成了 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)// 最终返回public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(java.io.InputStream, java.lang.String, java.util.Properties)public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // 点this,查看代码 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser;}// org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); // 创建解析器 this.document = createDocument(new InputSource(inputStream));}// org.apache.ibatis.parsing.XPathParser#createDocument// 不用太关系,只是创建一个解析器的对象,顺便检查xml文档有没有写错private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(entityResolver); builder.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { } }); // 解析器的源码了,跟mybatis没有关系了 return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } }// org.apache.ibatis.builder.xml.XMLConfigBuilder#parsepublic Configuration parse() { if (parsed) { // 每一个配置文件,只能解析一次 throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 从根节点开始解析,最终封装到Configuration对象 parseConfiguration(parser.evalNode("/configuration")); return configuration;}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.String)public XNode evalNode(String expression) { return evalNode(document, expression);}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.Object, java.lang.String)public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration// 可以配置这些信息:https://mybatis.org/mybatis-3/zh/configuration.htmlprivate void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); // 插件 pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 重点 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 重点 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElementprivate void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 三个值是互斥的 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 专门用来解析mapper映射文件 InputStream inputStream = Resources.getUrlAsStream(url); // 重点 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } }} 解释下environment作用,就是<environment id="development">这里的,不同的环境不同的配置 1234567891011121314151617181920212223<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> </environments></configuration> 用法:如下代码 1SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"development"); 下面是解析mapper文件,配置的属性,参考:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html 解析mapper配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223// org.apache.ibatis.builder.xml.XMLMapperBuilder#XMLMapperBuilder(java.io.InputStream, org.apache.ibatis.session.Configuration, java.lang.String, java.util.Map<java.lang.String,org.apache.ibatis.parsing.XNode>)// 这个方法,前面已经用过了 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); }// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsepublic void parse() { // mapper映射文件是否已经加载过 if (!configuration.isResourceLoaded(resource)) { // 从根节点解析 configurationElement(parser.evalNode("/mapper")); // 标记已经解析 configuration.addLoadedResource(resource); // 为命名空间绑定映射 bindMapperForNamespace(); } // parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();}// org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElementprivate void configurationElement(XNode context) { try { // 获取命名空间 String namespace = context.getStringAttribute("namespace"); // 命名空间不能为空 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 设置当前的命名空间的值 // 构建mappedStatement对象 builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 重点 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } // 没有配置过getDatabaseId,所以走这里 buildStatementFromContext(list, null);}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } }}// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNodepublic void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 没有设置过databaseId if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); // 解析Sql命令类型是什么,确实是 Select,update,insert,delete 类型 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); // 配置语言驱动 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // 替换占位符?,保存#{}里面的内容 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); // 通过构建者助手,创建mappedstatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }// org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 封装好MappedStatement,并返回 MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }// org.apache.ibatis.builder.MapperBuilderAssistant#applyCurrentNamespacepublic String applyCurrentNamespace(String base, boolean isReference) { if (base == null) { return null; } if (isReference) { // is it qualified with any namespace yet? if (base.contains(".")) { return base; } } else { // is it qualified with this namespace yet? if (base.startsWith(currentNamespace + ".")) { return base; } if (base.contains(".")) { throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); } } // namespacename+点+方法名 return currentNamespace + "." + base; } 到这里,配置文件就解析完成了,下面是创建SqlSessionFactory对象 SqlSession1234 // org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);} 下面是mybatis创建sql的流程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475// /Users/anthony/.m2/repository/org/mybatis/mybatis/3.5.2/mybatis-3.5.2-sources.jar!/org/apache/ibatis/builder/xml/XMLStatementBuilder.java:96// 占位符是如果进行替换的?动态sql如果进行的解析String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// org.apache.ibatis.builder.xml.XMLStatementBuilder#getLanguageDriverprivate LanguageDriver getLanguageDriver(String lang) { Class<? extends LanguageDriver> langClass = null; if (lang != null) { langClass = resolveClass(lang); } return configuration.getLanguageDriver(langClass);}// org.apache.ibatis.session.Configuration#getLanguageDriverpublic LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) { if (langClass == null) { return languageRegistry.getDefaultDriver(); } languageRegistry.register(langClass); return languageRegistry.getDriver(langClass);}// org.apache.ibatis.scripting.LanguageDriverRegistry#getDefaultDriver// 打断点可以看到是:XMLLanguageDriverpublic LanguageDriver getDefaultDriver() { return getDriver(getDefaultDriverClass());}public Class<? extends LanguageDriver> getDefaultDriverClass() { return defaultDriverClass;}// org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#XMLScriptBuilder(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; // 初始化动态sql的节点处理器结合 initNodeHandlerMap();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#initNodeHandlerMap private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode// 解析sql,还有参数类型和结果集类型public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource;} 全部就解析完成了,接下来 看看session = sqlSessionFactory.openSession(); 创建事务对象 创建了执行器对象CasheingExecutor 创建DefaultSqlSession对象 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()@Overridepublic SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}// org.apache.ibatis.session.Configuration#getDefaultExecutorType// protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;public ExecutorType getDefaultExecutorType() { return defaultExecutorType;}// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource// 参数一:执行器,参数二:隔离级别private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获取数据源环境信息 final Environment environment = configuration.getEnvironment(); // 获取事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 获取JdbcTransaction或者ManagedTransaction // ManagedTransaction 就相当于没有事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建Executor执行器 final Executor executor = configuration.newExecutor(tx, execType); // 创建DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory#newTransaction(java.sql.Connection)@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit);}// org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType) public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 如果允许缓存,会通过CachingExecutor去代理一层 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 拦截器插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }// org.apache.ibatis.executor.CachingExecutor#CachingExecutorpublic CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this);} 调用具体的API进行查询用启动类中的方法,对比可以发现两段代码不同之处为: 1234Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Map map = userMapper.getUA(); 在查看DefaultSqlSession中的selectOne方法,会执行以下的调用链 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205// org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)@Overridepublic <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)// RowBounds 分页对象public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 根据传入的statementId,获取mapperStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); // 调用执行器的方法 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)// 注意这里是CachingExecutor,默认的@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 获取带问号的sql语句,比如 select * from user_info where id = ? BoundSql boundSql = ms.getBoundSql(parameterObject); // 生成缓存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 二级缓存 // usermapper的标签里可以设置<Cache>缓存标签 Cache cache = ms.getCache(); if (cache != null) { // 刷新二级缓存,在<select> 标签里可以配置flushcache flushCacheIfRequired(ms); // 在<select> 标签里可以配置usecache if (ms.isUseCache() && resultHandler == null) { // 判断是不是存过 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") // 从二级缓存中查询数据 List<E> list = (List<E>) tcm.getObject(cache, key); // 如果从二级缓存没有查询到数据 if (list == null) { // 委托给BaseExecutor执行 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 将查询结果保存到二级缓存中,这里只是存到map集合中,没有真正存到二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 如果配置了FlushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { // 清空缓存 clearLocalCache(); } List<E> list; try { queryStack++; // 从一级缓存中获取数据 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { // 如果有数据,则处理本地缓存结果给输出参数 // 还是处理存过 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 没有缓存结果,则从数据库查询结果 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list;}// org.apache.ibatis.executor.BaseExecutor#queryFromDatabaseprivate <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 向本地缓存中存入一个ExecutionPlaceholder的枚举类占位 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 执行查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 执行玩移除这个key localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list;}// org.apache.ibatis.executor.SimpleExecutor#doQuery@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 获取全局配置文件实例 Configuration configuration = ms.getConfiguration(); // new一个statementHandler实例 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 准备处理器,主要包括创建statement以及动态参数的设置 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行真正的数据库操作调用 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); }}// org.apache.ibatis.session.Configuration#newStatementHandlerpublic StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建路由功能的StatementHanlder,根据MappedStatement中的StetementType StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 插件机制:对核心对象进行拦截 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler;}// org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandlerpublic RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); }}// org.apache.ibatis.executor.SimpleExecutor#prepareStatementprivate Statement prepareStatement(StatementHandler handler, Log statementLog){ Statement stmt; // 获取代理后,增加日志功能的Connection对象 Connection connection = getConnection(statementLog); // 创建Statement对象 stmt = handler.prepare(connection, transaction.getTimeout()); // 参数化处理 handler.parameterize(stmt); return stmt;}// org.apache.ibatis.executor.statement.PreparedStatementHandler#query @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps);} 解析结果1234567891011121314151617181920212223242526272829303132333435363738// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults);} 缓存1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253// org.apache.ibatis.executor.CachingExecutor#createCacheKey// CacheKey重新了 hacode和equals方法@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { // delegate 是具体类型的执行器的应用 // 默认是SimpleExecutor return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}// org.apache.ibatis.executor.BaseExecutor#createCacheKey@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // 通过构造器创建 CacheKey cacheKey = new CacheKey(); // id cacheKey.update(ms.getId()); // 分页参数 cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); // sql cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 参数的值 cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 // 当前环境的值 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey;} 面试题
设计模式
创建型模式简单工厂Pay.java 123public interface Pay { void pay(Integer money);} SimpleFactory.java 12345678910111213public class SimpleFactory { public static Pay create(String payType) { if (payType.equals("ZFB")) { return new ZFBPay(); } else if (payType.equals("WX")) { return new WXPay(); }else { return null; } }} WXPay.java 微信支付 12345public class WXPay implements Pay { public void pay(Integer money) { System.out.println("微信支付:"+money+"元"); }} ZFBPay.java 支付宝支付 12345public class ZFBPay implements Pay { public void pay(Integer money) { System.out.println("支付宝支付:"+money+"元"); }} Test.java 1234567891011121314/** * 简单工厂设计模式 */public class Test { public static void main(String[] args) { Pay wx = SimpleFactory.create("WX"); wx.pay(2); Pay zfb = SimpleFactory.create("ZFB"); zfb.pay(1); }} 行为模式策略模式基础 Strategy 1234567public interface Strategy { /** * 策略方法 */ public void strategyInterface();} ConcreteStrategyA 1234567public class ConcreteStrategyA implements Strategy { @Override public void strategyInterface() { System.out.println("ConcreteStrategyA"); }} ConcreteStrategyB 1234567public class ConcreteStrategyB implements Strategy { @Override public void strategyInterface() { System.out.println("ConcreteStrategyB"); }} ConcreteStrategyC 1234567public class ConcreteStrategyC implements Strategy { @Override public void strategyInterface() { System.out.println("ConcreteStrategyC"); }} Context 1234567891011121314151617181920public class Context { //持有一个具体策略的对象 private Strategy strategy; /** * 构造函数,传入一个具体策略对象 */ public Context(Strategy strategy){ this.strategy = strategy; } /** * 策略方法 */ public void contextInterface(){ strategy.strategyInterface(); }} Test.java 1234567public class Test { public static void main(String[] args) { Context context = new Context(new ConcreteStrategyA()); context.contextInterface(); }} 应用MemberStrategy.java 12345678910111213/** * 会员策略 */public interface MemberStrategy { /** * 计算图书的价格,根绝会员等级 * @param booksPrice 图书的原价 * @return 计算出打折后的价格 */ public double calcPrice(double booksPrice);} PrimaryMemberStrategy.java 初级会员 1234567public class PrimaryMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println("初级会员,没有折扣"); return 0; }} IntermediateMemberStrategy.java 中级会员 1234567public class IntermediateMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println("对于中级会员的折扣为10%"); return booksPrice * 0.9; }} AdvancedMemberStrategy.java 高级会员 1234567public class AdvancedMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println("对于高级会员的折扣为20%"); return booksPrice * 0.8; }} Price.java 1234567891011121314151617public class Price { private MemberStrategy strategy; public Price(MemberStrategy strategy) { this.strategy = strategy; } /** * 计算图书的价格 * @param booksPrice 图书的原价 * @return 计算出打折后的价格 */ public double quote(double booksPrice){ return this.strategy.calcPrice(booksPrice); }} Test.java 123456789101112public class Test { public static void main(String[] args) { //选择并创建需要使用的策略对象 MemberStrategy strategy = new AdvancedMemberStrategy(); //创建环境 Price price = new Price(strategy); //计算价格 double quote = price.quote(300); System.out.println("图书的最终价格为:" + quote); }} 结构模式装饰模式mybatis的执行期就用的是装饰模式 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162// 抽象构件角色// 比如这是一期开发的接口public interface Shape { void draw();}// 具体构件角色// 比如这是一期开发好的实现类public class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle"); }}// 到二期开发了,发现这个Rectangle的实现不太好用了,需要添加别的功能// 抽象装饰器public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape){ this.decoratedShape = decoratedShape; } public void draw(){ decoratedShape.draw(); } }// 具体装饰器角色// 这里就是开发的二期的功能public class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super(decoratedShape); } @Override public void draw() { // 调用一期的代码原本的功能 decoratedShape.draw(); // 调用二期的新代码的功能 setRedBorder(decoratedShape); } private void setRedBorder(Shape decoratedShape){ System.out.println("Border Color: Red"); }}// 比如一个人冷了,多套了一件毛衣,发现还是冷,又多穿了一件羽绒服,一层一层的,装饰public class DecoratorPatternDemo { public static void main(String[] args) { // 一期的代码 Shape circle = new Circle(); // 二期的代码 ShapeDecorator redCircle = new RedShapeDecorator(new Circle()); redCircle.draw(); }} 模板模式12345678910111213141516171819202122232425262728293031323334353637383940414243444546public interface Lifecycle { void init();}abstract class AbstractLifecycle implements Lifecycle{ abstract void run(); @Override public void init() { System.out.println("这里是公共方法"); run(); System.out.println("这里也是公共方法"); }}public class ServerStandard extends AbstractLifecycle { @Override void run() { System.out.println("我是实现,父类将调用我"); }}public class ServerStandard2 extends AbstractLifecycle { @Override void run() { System.out.println("我是实现2,父类将调用我"); }}public class Test { public static void main(String[] args) { ServerStandard serverStandard = new ServerStandard(); serverStandard.init(); System.out.println("============================================"); ServerStandard2 serverStandard2 = new ServerStandard2(); serverStandard2.init(); }} 责任链模式123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110public abstract class Handler { protected String name; // 处理者姓名 @Setter protected Handler nextHandler; // 下一个处理者 public Handler(String name) { this.name = name; } public abstract boolean process(LeaveRequest leaveRequest); // 处理请假}public class Director extends Handler { public Director(String name) { super(name); } @Override public boolean process(LeaveRequest leaveRequest) { // 随机数大于3则为批准,否则不批准 boolean result = (new Random().nextInt(10)) > 3; String log = "主管<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> "; System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准")); if (!result) { // 不批准 return false; } else if (leaveRequest.getNumOfDays() < 3) { // 批准且天数小于3,返回true return true; } return nextHandler.process(leaveRequest); // 批准且天数大于等于3,提交给下一个处理者处理 }}public class Manager extends Handler { public Manager(String name) { super(name); } @Override public boolean process(LeaveRequest leaveRequest) { boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准 String log = "经理<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> "; System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准")); if (!result) { // 不批准 return false; } else if (leaveRequest.getNumOfDays() < 7) { // 批准且天数小于7 return true; } return nextHandler.process(leaveRequest); // 批准且天数大于等于7,提交给下一个处理者处理 }}public class TopManager extends Handler { public TopManager(String name) { super(name); } @Override public boolean process(LeaveRequest leaveRequest) { boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准 String log = "总经理<%s> 审批 <%s> 的请假申请,请假天数: <%d> ,审批结果:<%s> "; System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准")); if (!result) { // 总经理不批准 return false; } return true; // 总经理最后批准 }}@Data@AllArgsConstructorpublic class LeaveRequest { private String name; // 请假人姓名 private int numOfDays; // 请假天数}public class Test { public static void main(String[] args) { // 创建审批人 Handler zhangsan = new Director("张三"); Handler lisi = new Manager("李四"); Handler wangwu = new TopManager("王五"); // 创建责任链 zhangsan.setNextHandler(lisi); lisi.setNextHandler(wangwu); // 发起请假申请第一次 boolean result1 = zhangsan.process(new LeaveRequest("小旋锋", 1)); System.out.println("最终结果:" + result1 + "\n"); // 发起请假申请第二次 boolean result2 = zhangsan.process(new LeaveRequest("小旋锋", 4)); System.out.println("最终结果:" + result2 + "\n"); // 发起请假申请第三次 boolean result3 = zhangsan.process(new LeaveRequest("小旋锋", 8)); System.out.println("最终结果:" + result3 + "\n"); }} Tomcat的过滤器也使用到了责任链ApplicationFilterChain,具体的还没有读,先写完这个模式,比模板模式感觉有点复杂 参考的是:https://juejin.im/post/6844903702260629512#heading-11, 感觉举的例子的业务显示还不是特别的好理解 也参考下这个:https://www.cnblogs.com/tanshaoshenghao/p/10741160.html 适配器模式SpringMVC的DispatchServlet的例子 具体实现12345678910111213141516171819202122232425262728293031323334/** * Description: 控制器接口 */public interface Controller {}/** * Controller 实现之1:HttpController */public class HttpController implements Controller { public void doHttpHandler() { System.out.println("HttpController:httpMethod()"); }}/** * Controller 实现之2:SimpleController */public class SimpleController implements Controller { public void doSimplerHandler() { System.out.println("SimpleController:simpleMethod()"); }}/** * Controller 实现之3:HttpController */public class AnnotationController implements Controller { public void doAnnotationHandler() { System.out.println("AnnotationController:annotationMethod()"); }} 处理器123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/** * 定义一个Adapter接口 */public interface HandlerAdapter { /** * 是否支持 * * @param handler * @return */ boolean supports(Object handler); /** * 处理 * * @param handler */ void handle(Object handler);}/** * HttpController 的适配器 */public class HttpHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof HttpController); } public void handle(Object handler) { ((HttpController) handler).doHttpHandler(); }}/** * SimpleController的适配器 */public class SimpleHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof SimpleController); } public void handle(Object handler) { ((SimpleController) handler).doSimplerHandler(); }}/** * AnnotationController 的适配器 */public class AnnotationHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof AnnotationController); } public void handle(Object handler) { ((AnnotationController) handler).doAnnotationHandler(); }} main方法1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/** * 模拟一个DispatcherServlet,适配器(适配者模式) * HandlerAdapter(适配器类) 作为适配器来适配各种 Handler(适配者类)(如Controller) */public class DispatchServlet { public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>(); /** * 注册所有 HandlerAdapter */ public DispatchServlet() { handlerAdapters.add(new AnnotationHandlerAdapter()); handlerAdapters.add(new HttpHandlerAdapter()); handlerAdapters.add(new SimpleHandlerAdapter()); } /** * 模拟DispatchServlet 中的 doDispatch()方法 */ public void doDispatch() { Controller handler = new AnnotationController(); // 通过handler来找到对应适配器 HandlerAdapter handlerAdapter = getHandler(handler); // 通过执行适配器的handle,来执行对应的controller对应方法 handlerAdapter.handle(handler); } /** * 找到与handler适配的适配器:通过handler 遍历 HandlerAdapter 适配器来实现 */ public HandlerAdapter getHandler(Controller handler) { for (HandlerAdapter adapter : handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } return null; } /** * 模拟运行 */ public static void main(String[] args) { new DispatchServlet().doDispatch(); }} 参考 https://refactoringguru.cn/design-patterns 装饰器模式