-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 296 KB
/
content.json
1
{"meta":{"title":"Xraytf","subtitle":"","description":"希望明天,也是好天气。","author":"阿灯","url":"http://example.com","root":"/"},"pages":[{"title":"","date":"2022-11-14T08:47:09.692Z","updated":"2022-11-14T08:47:09.692Z","comments":true,"path":"about/index.html","permalink":"http://example.com/about/index.html","excerpt":"","text":"个人介绍 纯大学生一枚,纯凭兴趣制作博客,玩github ︿( ̄︶ ̄)︿ 探索网络知识是我的一大爱好,我也愿意为之努力 博客介绍本博客绑定github,基于hexo建成。 制作本身涉及:域名创建解析,nodejs,git,hexo,web 喜欢的可以关注我的github查看代码 网站:【github】https://github.com/adeng127/adeng127.github.io 其他【bilibili】 记录我有一些剪的视频在里面 #觉得有喜欢的可以看看(三连也是可以滴 ♪(^∇^*) 【邮箱】:1276576156@qq.com #有什么问题可以留个言 【QQ】:1276576156 #主联系方式,交朋友问问题可以加一下。"},{"title":"我的博客友人","date":"2022-10-12T09:30:35.988Z","updated":"2022-10-12T09:30:35.988Z","comments":true,"path":"friends/index.html","permalink":"http://example.com/friends/index.html","excerpt":"在此感谢每一个友人,相遇即是有缘,嘻嘻)","text":"在此感谢每一个友人,相遇即是有缘,嘻嘻) 如果你看到这里,也是一位博主,想交个朋友,或者和我一样是个小白,希望互相学习,共同进步 不妨在下面留个言,放上自己的博客。 相逢即是朋友,嘿嘿~"},{"title":"所有标签","date":"2022-10-11T14:44:00.577Z","updated":"2022-10-11T14:44:00.577Z","comments":true,"path":"tags/index.html","permalink":"http://example.com/tags/index.html","excerpt":"","text":""},{"title":"Hi! I'm Xraytf!","date":"2023-03-03T11:01:28.035Z","updated":"2023-03-03T11:01:28.035Z","comments":true,"path":"footprint/index.html","permalink":"http://example.com/footprint/index.html","excerpt":"","text":"欢迎来到我的足迹~ 本人: 大二在读,一位想要流浪的中二少年~ 我的脚步博客建成博客知识总结:域名,nodejs,git,github,hexo,web ——————————(当然学会还不至于,只是在浪,以后,可能,也会用上) 在这里重点感谢千千,还有其他帮我解答,甚至联机帮我做的大佬,谢谢谢谢谢谢谢谢....!(鞠躬) 博客修缮通过千千开始接触volantis主题,并启用。 在此感谢B站,Mrhuanhao大佬,我通过他的volantis教程一步步完善初始博客,真的太棒了! 如果你也做博客,看到我的,喜欢这个主题,也可以去看看 主题在这~:——————volantis 更新日志 23.3.3 今天python的学习正式告一段落,最近要准备c语言的蓝桥杯比赛和chatgpt的接口调用。 — chatgpt最近很火,我也申请成功了使用了chatgpt的new bing聊天搜索引擎,不得不说真的很棒呢。所以我准备做一个chatgpt+vits+live2d人物的结合。嘿嘿 — 4月比赛结束后,会准备pygame的上手,实战游戏,到时候也会放在这里。 22.11.13 感觉网页比较卡,在查询network时发现不蒜子有问题,询问后更改api成功解决 — 网页卡顿。寻找加速方法,询问千千后将网页托管由github pages更换为vercel — 目前发现vercel功能很多,并认识到静态与动态网页,之后会去尝试动态网页。 22.11.4 博客主要样式功能基本修理完毕 – 在此整理主要有: 鼠标样式(custom_css)、全局颜色(color_scheme)、自定义右键(rightmenus)、消息提示(message) 各类插件(plugins):暗黑模式(darkmode)、动态背景图(parallax)、底部音乐播放(aplayer) – 不会过多地优化,比如精灵,樱雨特效,点击特效啥的,个人觉得没必要 我的目的是只突出简洁,美观,达到最佳观看效果。"},{"title":"所有分类","date":"2022-10-11T14:44:01.411Z","updated":"2022-10-11T14:44:01.411Z","comments":true,"path":"categories/index.html","permalink":"http://example.com/categories/index.html","excerpt":"","text":""}],"posts":[{"title":"支线技术","slug":"支线技术","date":"2023-03-07T16:00:00.000Z","updated":"2023-04-21T04:13:03.594Z","comments":true,"path":"2023/03/08/支线技术/","link":"","permalink":"http://example.com/2023/03/08/%E6%94%AF%E7%BA%BF%E6%8A%80%E6%9C%AF/","excerpt":"我平时喜欢学习一些新的技术,比如api,脚本等,这个就当乱放一些其他技术的代码库了。","text":"我平时喜欢学习一些新的技术,比如api,脚本等,这个就当乱放一些其他技术的代码库了。 不会有单一类型的代码,什么代码都会放的。 api的学习new bing给我写的调用api制作音乐播放器的python代码 使用方法,运行后,输入音乐名,输入作者。 音乐播放器 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960import webbrowserimport requests# 使用一个免费的音乐API,网址是https://api.deezer.com/# 你可以在这里查看文档:https://developers.deezer.com/api# 定义一个函数,根据歌曲名和歌手名,获取一首歌曲的信息和播放链接def get_song_info(song_name, artist_name): # 构造一个搜索请求的URL,使用q参数来指定搜索条件 search_url = f"https://api.deezer.com/search?q=track:\\"{song_name}\\" artist:\\"{artist_name}\\"" # 发送请求,获取响应 response = requests.get(search_url) # 检查响应状态码是否为200,表示成功 if response.status_code == 200: # 解析响应内容为JSON格式 data = response.json() # 检查data中是否有data键,表示搜索结果列表 if "data" in data: # 获取搜索结果列表中的第一项,表示最匹配的歌曲 song = data["data"][0] # 返回歌曲的信息和播放链接,包括id, title, artist, album, preview等 return song else: # 如果没有data键,表示没有找到匹配的歌曲,返回None return None else: # 如果响应状态码不为200,表示请求失败,返回None return None# 定义一个函数,根据播放链接,在浏览器中打开并播放音乐def play_song(preview_url): # 使用webbrowser模块的open方法,打开播放链接 webbrowser.open(preview_url)# 调用get_song_info函数,传入歌曲名和歌手名,得到歌曲信息# 从我的回答中获取歌曲名和歌手名song_name = input("What is the song name? ")artist_name = input("Who is the artist name? ")# 调用get_song_info函数,传入歌曲名和歌手名song_info = get_song_info(song_name, artist_name)# 检查是否获取到了歌曲信息if song_info is not None: # 打印歌曲信息 print(f"Song ID: {song_info['id']}") print(f"Song Title: {song_info['title']}") print(f"Artist Name: {song_info['artist']['name']}") print(f"Album Name: {song_info['album']['title']}") # 获取播放链接 preview_url = song_info["preview"] # 调用play_song函数,传入播放链接,在浏览器中打开并播放音乐 play_song(preview_url)else: # 如果没有获取到歌曲信息,打印提示信息 print("Sorry, I could not find the song you requested.") 搭建动态网页最近参加了这样的比赛,就,记录一下过程吧。 搭建动态网页和静态网页的区别 1234567搭建动态网页和静态网页的主要区别在于它们的内容是否可以根据用户的交互而改变。静态网页的内容是固定不变的,它通常由HTML、CSS和JavaScript等技术构建,不需要服务器端脚本来生成页面内容。而动态网页则可以根据用户的交互、时间或其他因素来改变页面内容,它通常需要服务器端脚本语言(如PHP、ASP.NET等)来生成页面内容。因此,在搭建动态网页时,你需要使用服务器端脚本语言来编写代码,并在服务器上运行这些代码来生成页面内容。而在搭建静态网页时,则只需编写HTML、CSS和JavaScript等前端代码即可。","categories":[{"name":"知识","slug":"知识","permalink":"http://example.com/categories/%E7%9F%A5%E8%AF%86/"}],"tags":[{"name":"学习","slug":"学习","permalink":"http://example.com/tags/%E5%AD%A6%E4%B9%A0/"},{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"}]},{"title":"git入门","slug":"git入门","date":"2022-11-05T16:00:00.000Z","updated":"2022-11-07T04:18:48.165Z","comments":true,"path":"2022/11/06/git入门/","link":"","permalink":"http://example.com/2022/11/06/git%E5%85%A5%E9%97%A8/","excerpt":"git入门的相关操作,也就是一些基础指令","text":"git入门的相关操作,也就是一些基础指令 我了解到的git操作相关笔记,记录学习。 git基础功能 作用 —— 最主要的作用是保存代码到本地仓库,以备不时之需,相当于是买了后悔药 比如你写了一串代码,用户让你修改,然后改了几次又想要原来的,就可以进行类似回档的效果。 —— 其他的功能还有比如提交到github,hexo博客制作等。 git安装及打开使用 —— 去官网downlode自己下去,然后去桌面右击再点击第二个:git bush Here(通过命令行打开git) —— 觉得页面不好看自己换,右击框可以options设置,自己看看吧。 —— 还有一个功能先做了,配置用户名和邮箱。 12$ git config --global user.name "Your Name" $ git config --global user.email "[email protected]" 就这个样子 git基础语言git clone: ———–如果你想白嫖github上别人的一些源码(开源项目)就可以在你想存的地方比如桌面 右击打开 输入git clone 再去github右边的code复制其链接添加到右面格式: 1git clone 链接 —— 弄好之后就可以看到桌面有个文件夹,.git不用你管,那个是版本管理的文件。其他的就是源码了 git init: ———–让git管理自己的代码库。需要进行初始化。首先需要创建新文件夹,然后输入git init完成操作。 格式: 1git init —— 这样就出现了.git文件夹,也就可以进行代码管理。 ———–接下来你就可以创建文件撸代码了~ 提交操作——-提交是两步操作,比如我把饭做好了放在架子上(add),然后机器在交上去(commit) git add —— 如果你想把当前文件夹全部提交,就用: 1git add . —— 那个点是全部文件和非空文件夹的意思,不是我写作了哈~ 不过这里也不是提交,而是进入暂存区。—— 如果你想一个个提交,用下面的代码: 1git add 文件名 —— 这样就把你想提交的那个文件加入了暂存区。也就是放在架子上等着用机器去送货,你再用就再放上去了一个。 git commit —— 将暂存区的文件全部提交的操作格式: 1git commit -m "备注" —— 这个备注最好要养成习惯,不要觉得不在意就瞎写,在进步的过程中,良好的习惯很重要,好好写以后会方便很多。 这些命令就是提交到本地仓库,便于管理。—–仓库在哪?.git文件夹。 git log —— 查看提交记录,可以自己去试试看能出现什么格式: 1git log —— 里面会有 commit (一大串字母数字)后面的就相当于编号,唯一标识。 git checkout —— 如果有人随便修改了你的代码并保存了,你就可以利用这个进行回档格式: 1git checkout HEAD 文件名 —— 他的作用是恢复成最后一次提交的状态。但是会覆盖,要小心。—— 所以在你把代码改的很乱结果跑不起来了,就可以利用这个命令,恢复成以前能跑的代码。 至此,大致的入门基本结束了,基础的写代码,提交,回档管理就是这些。git还有许多其他命令。我们之后再说。","categories":[{"name":"知识","slug":"知识","permalink":"http://example.com/categories/%E7%9F%A5%E8%AF%86/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"},{"name":"学习","slug":"学习","permalink":"http://example.com/tags/%E5%AD%A6%E4%B9%A0/"}]},{"title":"springboot搭建","slug":"springboot","date":"2022-11-05T16:00:00.000Z","updated":"2023-10-22T02:02:39.455Z","comments":true,"path":"2022/11/06/springboot/","link":"","permalink":"http://example.com/2022/11/06/springboot/","excerpt":"仅为笔记查看,过后删除。","text":"仅为笔记查看,过后删除。 八、终极实战:SpringBoot版微头条实战目录 一、微头条案例介绍 微头条业务简介 技术栈介绍 功能展示 二、微头条前端搭建 三、基于SpringBoot搭建项目基础架构 1. 数据库脚本执行 2. 搭建SprintBoot工程 3. MybatisX逆向工程 四、后台功能开发 4.1 用户模块开发 4.2 首页模块开发 4.3 头条模块开发 五、前后端联调 一、微头条案例介绍微头条业务简介 用户功能 注册功能 登录功能 jwt实现 头条新闻 新闻的分页浏览 通过标题关键字搜索新闻 查看新闻详情 新闻的修改和删除 技术栈介绍 前端技术栈 ES6作为基础JS语法 nodejs用于运行环境 npm用于项目依赖管理工具 vite用于项目的构建架工具 Vue3用于项目数据的渲染框架 Axios用于前后端数据的交互 Router用于页面的跳转 Pinia用于存储用户的数据 LocalStorage作为用户校验token的存储手段 Element-Plus提供组件 后端技术栈 JAVA作为开发语言,版本为JDK17 Tomcat作为服务容器,版本为10.1.7 Mysql8用于项目存储数据 SpringMVC用于控制层实现前后端数据交互 MyBatis-Plus用于实现数据的CURD Druid用于提供数据源的连接池 SpringBoot作为项目基础架构 MD5用于用户密码的加密 Jwt用于token的生成和校验 Jackson用于转换JSON 功能展示 头条首页信息搜索 登录功能 注册功能 展示功能 发布头条功能 修改头条功能 删除头条功能 二、微头条前端搭建 确保本地node,npm,vscode安装完毕! 如果没有安装可以回看ssm整合实战! 解压前端项目代码并存放到磁盘的合适位置 atguigu-headline.rar 使用vscode打开工程 进入项目后打开集成终端或者在src上右击选择在集成终端中打开 通过 npm run dev启动前端项目 12npm install npm run dev 三、基于SpringBoot搭建项目基础架构1. 数据库脚本执行执行数据库脚本: top_news.sql 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293CREATE DATABASE sm_db;USE sm_db;SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for news_headline-- ----------------------------DROP TABLE IF EXISTS `news_headline`;CREATE TABLE `news_headline` ( `hid` INT NOT NULL AUTO_INCREMENT COMMENT '头条id', `title` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头条标题', `article` VARCHAR(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头条新闻内容', `type` INT NOT NULL COMMENT '头条类型id', `publisher` INT NOT NULL COMMENT '头条发布用户id', `page_views` INT NOT NULL COMMENT '头条浏览量', `create_time` DATETIME(0) NULL DEFAULT NULL COMMENT '头条发布时间', `update_time` DATETIME(0) NULL DEFAULT NULL COMMENT '头条最后的修改时间', `version` INT DEFAULT 1 COMMENT '乐观锁', `is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除 0 未删除', PRIMARY KEY (`hid`) USING BTREE) ENGINE = INNODB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;-- ------------------------------ Records of news_headline-- ----------------------------INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (1, '特色产业激发乡村振兴新活力', '推进中国式现代化,必须全面推进乡村振兴。习近平总书记指出,产业振兴是乡村振兴的重中之重,也是实际工作的切入点。近日,记者走进乡村一线,看到各地以特色产业为抓手,拓展产业链发展产业集群,一二三产业融合发展,培育乡村振兴新动能。\\n\\n 这个端午,广东茂名高州市根子镇柏桥村的荔枝迎来了丰收。今年4月,习近平总书记来到柏桥村考察调研。总书记走进荔枝种植园,了解当地发展特色种植产业和文旅产业等情况,并同现场技术人员亲切交流。', 1, 1, 0, '2023-06-25 09:26:20', '2023-06-25 09:26:20', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (2, '北京连续三天最高温超40℃,6月“炎值”因何爆表?', ' 中新社北京6月24日电 (记者 陈杭 徐婧)京城连续三日“热晴不减”,且高温红色预警持续生效。截至24日13时51分,作为北京地区气象观测代表站的南郊观象台气温突破40℃,这是该站观测史上首次连续三天气温超40℃。22日以来,北京高温“烤验”突出。22日,北京南郊观象台最高气温达41.1℃,这是有观测纪录以来历史第二高(并列)。北京市气象局表示,观象台1951年建站以来极端最高气温为41.9℃,出现在1999年7月24日。\\n\\n 23日,北京南郊观象台最高气温为40.3℃,这是该观象台建站以来首次出现连续两天最高气温超40℃。当天,北京时隔9年再次发布最高级别的高温红色预警信号。', 1, 1, 0, '2023-06-25 09:28:06', '2023-06-25 09:28:06', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (3, '今年夏天,极端高温是否会成为常态?', '针对京津冀地区持续高温天气,23日下午,中国气象局召开高温天气新闻通气会。\\n\\n 刚过6月就出现极端高温天,今年夏天还会有多少高温天呢?对此,国家气候中心首席预报员高辉表示,根据国家气候中心预计,今年夏天全国大部分地区气温都比常年同期要偏高,这也对应着高温日数也要高于常年同期。但不同的地区,高温集中时段不一样,比如南方地区是在盛夏时间段进入高温季,而北方地区往往是在初夏时间段,所以从今年夏季来说,要区分不同的地区来考虑高温的影响。\\n\\n 我国各地高温集中时段有明显的地域差异。对华北地区来说,通常雨季前的6月至7月初更容易出现高温天气,连续数天的高温在6月也比较常见。高辉说,这段时间主要是干热型高温为主,表现为气温高湿度小。进入7月后期,随着副高北跳和夏季风往北推进,水汽输送和大气湿度增加,云量也会增多,会出现闷热天气,也就是湿热型高温。就最高气温而言,前一时段气温最高值通常高于后一时段。但也需要说明的是,人体体感温度不仅和气温有关,还受到湿度影响,往往这种湿热型高温会加重人体体感温度。', 1, 1, 0, '2023-06-25 09:31:00', '2023-06-25 09:31:00', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (4, '中央气象台发布今年首个高温橙色预警', '新华社北京6月22日电(记者黄垚)22日18时,中央气象台升级发布今年首个高温橙色预警。预计23日白天,华北、黄淮等地将继续出现35℃以上的高温天气,北京、天津、河北中南部、山东中北部等地部分地区最高气温可达40℃左右。\\n\\n 气象监测显示,22日8时至16时,北京、天津、河北中部、山东北部等地气温上升迅猛,最高气温升至40℃以上。上述4省份共有17个国家气象观测站最高气温突破历史极值。', 1, 1, 0, '2023-06-25 09:31:36', '2023-06-25 09:31:36', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (5, '江南水乡 龙舟竞渡', '江南水乡 龙舟竞渡---6月18日,浙江省湖州市“我们的节日·端午”暨第七届江南·民当端午民俗文化旅游节在南浔区和孚镇民当村开幕,来自南浔区各个乡镇的农民选手在河道中赛龙舟、划菱桶,体验传统端午民俗。', 1, 1, 0, '2023-06-25 09:32:13', '2023-06-25 09:32:13', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (6, '螃蟹粽、印花蛋、艾草凉粉……你知道端午有哪些创意美食吗?', '端午有旅行路上的见闻,有诗画里的艺术,也少不了舌尖上的风韵。听风入夏粽香佐茶,您还知道端午有哪些创意美食吗?端午至味,总少不了粽子这一味。甜的、咸的,肉馅的、蛋黄的、红枣的、豆沙的……一起来寻味端午!\\n\\n 古人其实早就喜欢把各种果干放进粽子里,美食家苏轼还发明了杨梅粽。《玉台新咏》中说,“酒中喜桃子,粽里觅杨梅。”后来苏轼曾借用过这个典故,在元祐三年所写的端午帖子中说,“不独盘中见卢橘,时于粽里得杨梅”。', 1, 1, 0, '2023-06-25 09:32:40', '2023-06-25 09:32:40', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (7, '尼克斯拒绝执行罗斯球队选项 罗斯成自由球员', '北京时间6月25日,据多方消息源报道,尼克斯拒绝执行德里克-罗斯下赛季的球队选项,罗斯成为完全自由球员。\\n\\n 34岁的罗斯在刚刚结束的赛季队内角色严重下滑,他仅出战27场比赛,场均登场12.5分钟,得到5.6分1.5篮板1.7助攻。\\n\\n 2021年,罗斯与尼克斯签下3年4300万美元的续约合同,其中最后一年为1560万美元球队选项。', 2, 2, 0, '2023-06-25 09:34:26', '2023-06-25 09:34:26', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (8, '班凯罗承诺代表美国男篮打世界杯 名单仅差1人', '北京时间6月25日,据著名NBA记者沙姆斯-查拉尼亚报道,魔术前锋保罗-班凯罗承诺将代表美国男篮参加2023年男篮世界杯。\\n\\n 班凯罗在刚刚结束的赛季场均能够砍下20.0分6.9篮板3.7助攻,获得了NBA2022-23赛季年度最佳新秀。', 2, 2, 0, '2023-06-25 09:34:59', '2023-06-25 09:34:59', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (9, 'F1加拿大大奖赛正赛:维斯塔潘冠军 阿隆索亚军', '2023年F1加拿大大奖赛正式比赛结束。红牛车队维斯塔潘杆位发车一路轻松领跑,再次完成了Pole-to-Win!这是红牛车队历史上的第100座分站冠军!同时也是维斯塔潘F1生涯的第41座分站冠军,追平了“车神”埃尔顿·塞纳的冠军数!阿斯顿马丁车队阿隆索亚军,梅赛德斯车队汉密尔顿季军。', 2, 2, 0, '2023-06-25 09:35:43', '2023-06-25 09:35:43', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (10, 'CTCC绍兴柯桥站圆满落幕 张志强曹宏炜各取一冠', '6月24日,2023赛季CTCC中国汽车场地职业联赛绍兴柯桥站在雨中的浙江国际赛车场上演了两回合决赛的巅峰角逐。在线上线下观众的共同见证下,超级杯-TCR中国系列赛、运动杯-长三角赛车节联袂献上高水平对决,以精彩的比赛献礼这个端午节假期!TCR 中国系列赛第三回合于今天上午率先开战。来自壳牌捷凯领克车队的张志强穿云破雾夺得冠军;夺得该回合亚军的是驾驶新赛车出战的东风本田车手高度,季军则由Z.SPEED N车队的张臻东斩获。这也是超级杯四冠王本赛季首次登台。', 2, 2, 0, '2023-06-25 09:36:18', '2023-06-25 09:36:18', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (11, '国象联赛常规赛收兵:杭州银行第一 山东成功上岸', '6月17日,“武陵山大裂谷杯”中国国际象棋甲级联赛常规赛在武陵云海国际酒店进行了最后一轮的争夺,杭州银行弈和山东队,抢到常规赛的冠军;山东队也是凭借这场平局,成功脱离保级区。本轮最大的悬念是第八名的争夺——在年底进行的甲级联赛总决赛中,前八名为上半区争冠组,保级无忧;而第九至十二名为保级区,不仅夺冠无望,还要为保级而苦战。', 2, 2, 0, '2023-06-25 09:36:51', '2023-06-25 09:36:51', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (12, '围棋名宿解读高考作文:人生如棋 要先学会下“本手”', '今年高考开考了,在语文考试后,体育借势冲上了社交媒体的热搜榜。奥运相关话题进入高考,是意料之中。不过当记者看到关于围棋术语“本手、妙手和俗手”的作文命题时,着实觉得有些难。在被迅速刷屏的朋友圈里,记者感受到了很多从业者的激动、兴奋乃至油然而生的自豪感。但也有人则为那些没学过棋的孩子感到担心,这么难的题目,究竟该如何解题?\\n\\n “围棋正在深入人心。题目有些难,‘俗手’如何定义?但确实应该先下好‘本手’。”翻到中国围棋协会副主席、国家围棋队领队华学明的这条朋友圈动态时,记者瞬间觉得这道公认的难题有了解题的思路。正如高考作文材料中所说,本手是基础。只有持之以恒地打好基础,补强短板,守住不发生系统性风险的底线,才有可能在本手的基础上,下出妙手,避免俗手。而如果脱离了基础,所谓的妙手很可能就是花拳绣腿,经不起推敲,更经不起对手的冲击。世界冠军柯洁表示:“很多人在对局中经常会拘泥于局部,下出假妙手。想下出真正的妙手,必须在平日里有一定的经验积累和训练,才可能完成真正卓越的妙手。”人生如棋,棋如人生。“其实人生中大部分时间都是在下本手”,围棋名宿曹大元九段说。', 2, 2, 0, '2023-06-25 09:37:43', '2023-06-25 09:37:43', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (13, '不甘人后:被生成式AI弥漫的亚马逊', '今年早些时候,随着ChatGPT席卷全球,亚马逊的经理们要求员工开动脑筋,想想如何使用人工智能(AI)聊天机器人技术来改进自家产品和工作流程。\\n\\n 其中一些想法被分享在一份名为《生成式AI——ChatGPT的影响和机会分析》的内部文件中。这份文件共列了ChatGPT和类似应用程序在亚马逊多个团队中的67个潜在应用案例。\\n\\n 早在20世纪90年代,亚马逊就靠在网上卖书创造了互联网界首个真正的商业奇迹。\\n\\n 随后,Kindle阅读器带来革命性体验,Alexa和Echo智能音箱又带来了语音计算,而AWS则创造了云计算行业,ChatGPT就运行在这个行业之上。\\n\\n 但这次热潮中拿到先发优势的是同为科技大厂的微软。微软现在是OpenAI背后的金主,且还在忙着把ChatGPT的底层技术融进微软产品和服务中。', 4, 5, 0, '2023-06-25 09:40:20', '2023-06-25 09:40:20', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (14, '微创新超实用:米家旅行箱居然想到了这一点', '旅行说走就走,除非老板没安排。名义上是旅游,实则执行任务,对内讲“为公司负重前行”,对外称“带薪游山玩水”,一介打工人,两副扑克脸,个中苦乐谁人知!\\n\\n“差旅人”精明如我,随身携带更偏向实用。\\n\\n必备日用之外,能路上买的尽量不带,华而不实的东西,往包里多塞一个都算我输。行李箱尺寸自然也要浓缩到小巧但够装的20英寸,拉着轻松又顺手,常用小物件转移到背包,“轻装上战场”。', 4, 5, 0, '2023-06-25 09:41:04', '2023-06-25 09:41:04', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (15, '小鹏G6动态试驾:辅助驾驶很惊喜', '这次我们开着小鹏G6上了赛道,又体验了最新版本的高速NGP和城市NGP,小鹏,还顺便测了下充电速度,那么小鹏G6驾驶感受如何?辅助驾驶表现怎么样?', 4, 5, 0, '2023-06-25 09:42:07', '2023-06-25 09:42:07', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (16, '养车市场陷入低价内卷,“虎猫狗”还没等到春天', '今年“618”期间,汽车后市场的玩家们都打出了“低价牌”。比如途虎养车宣布推出“6.18全民养车季”活动,在此期间北京车主可享受“轮胎买一送一”以及多品牌轮胎降价促销的活动。\\n\\n 与此同时,京东养车和天猫养车两大大厂玩家,在本次618期间也喊出了各自的营销口号。\\n\\n 前者不仅喊出了“养车爱车立省不止30%”的口号,还推出了轮胎、保养买贵赔两倍、“轮胎免费装、三年无忧质保”、5公里无服务门店赔双倍安装费等举措;天猫养车的618活动,则覆盖了更大的零部件范围,比如推出了空调清洗、机油和轮胎更换等低价服务。\\n\\n 这样看,在本次618期间,途虎养车、京东养车和天猫养车均贯彻着“以价换量”的战略,以至于让行业价格战一触即发。这些玩家会这样做,主要是为了与传统4S店、以及与彼此竞争,以便保证自身获得更多的市场份额。', 4, 5, 0, '2023-06-25 09:42:51', '2023-06-25 09:42:51', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (17, '微软股价历史新高 聊天机器人技术潜力显现', '周四,微软股价创下历史新高,成为今年继英伟达和苹果之后,又一家市值达到新高点的大型科技公司。这家软件巨头正致力于在其产品和服务中添加生成式人工智能功能,旨在全面改造其Office产品阵容,其中包括Excel、PowerPoint、Outlook和Word等。股价上涨3.2%,收于每股348.1美元,为2021年11月19日以来的最高收盘价。自今年初起,微软股价累计上涨了45%,市值增加约8006亿美元。微软持有OpenAI的大部分股份,这家初创公司凭借聊天机器人ChatGPT引发生成式人工智能的热潮。近几个月来,该工具广受欢迎,展示了聊天机器人技术所具有的巨大潜力。微软于今年1月宣布将再向OpenAI投资100亿美元。然而有报道称,微软与OpenAI之间既有合作,亦存竞争,这种特殊的双重关系导致了双方关系的紧张和潜在冲突。科技股如英伟达等同样受益于生成式人工智能技术的应用,各公司将此技术融入各自产品,进而推动相关芯片需求。英伟达股价今年已飙升192%,被视为最大赢家。', 4, 5, 0, '2023-06-25 09:43:48', '2023-06-25 09:43:48', 0);INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (18, '再获11亿美元投资:蔚来“长期主义”的底气', '如果说全系降价3万是李斌的“阳谋”,那么蔚来ET5T的发布,则是李斌的又一次诚意之作。\\n\\n ET5T是蔚来首款售价下探到30万元以下的新车,作为ET5的姐妹车型,ET5T和ET5的双车合璧,得以在30万以下快速开疆辟土。\\n\\n 这样的做法有迹可循:特斯拉曾经在Model Y上实践过,并大获成功。\\n\\n Model Y和Model 3共用平台,零部件复用率高达75%,研发成本骤降。尽管Model Y最初被用户吐槽是Model 3的放大版,但不置可否的是Model Y确实解决了用户对Model 3空间不足的槽点。\\n\\n 不过,最为关键的还是Model Y的价格足够低,直接降低了特斯拉的购买门槛,给那些对价格敏感,本犹豫要不要多花四五万的消费者一个充足的理由。\\n\\n 蔚来ET5T正在用一种经受了市场验证过的方式,直面与特斯拉的竞争。但同时,蔚来ET5T在智能化、空间表现、设计以及产品力上,都正在接近、超越特斯拉Model Y。\\n\\n 蔚来ET5T,平替特斯拉Model Y?\\n\\n 小家庭,预算30万左右,消费者到底会选哪款纯电动车?\\n\\n 全球市场的反馈是,特斯拉Model Y ——一款紧凑型SUV。2022年,Model Y的全球销量为74.7万辆,其在中国的销量为31.5万台,约占其全球份额的42.2%。\\n\\n 按照车型大小,SUV可以分为大型、中型、小型、紧凑型四大类。按照价位,SUV又可以分为实用型、经济型、中高档型、豪华型、超豪华型等。\\n\\n Model Y 在中高端SUV的细分市场中一骑绝尘,可以说是没有对手。因为无论是奔驰EQC、宝马iX3,还是国产的比亚迪唐EV等,和Model Y相比,都不能对其构成威胁。奔驰EQC、宝马iX3这两款车型都是“油改电”,算不上真正的电动车。而比亚迪的智能化能力,远及不特斯拉,座舱、智驾上的核心模块还来自于供应商方案,并非自研。\\n\\n 雷峰网认为,此前,国内的自主品牌中只有蔚来的ES6能和Model Y一较高下。不过ES6的均价比Model Y高出一大截,二者入门版之间的价差大约在10万左右。但在蔚来推出ET5T后,局势必然会发生逆转。', 4, 5, 0, '2023-06-25 09:44:20', '2023-06-25 09:44:20', 0);-- ------------------------------ Table structure for news_type-- ----------------------------DROP TABLE IF EXISTS `news_type`;CREATE TABLE `news_type` ( `tid` INT NOT NULL AUTO_INCREMENT COMMENT '新闻类型id', `tname` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '新闻类型描述', `version` INT DEFAULT 1 COMMENT '乐观锁', `is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除 0 未删除', PRIMARY KEY (`tid`) USING BTREE) ENGINE = INNODB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;-- ------------------------------ Records of news_type-- ----------------------------INSERT INTO `news_type` (tid,tname) VALUES (1, '新闻');INSERT INTO `news_type` (tid,tname) VALUES (2, '体育');INSERT INTO `news_type` (tid,tname) VALUES (3, '娱乐');INSERT INTO `news_type` (tid,tname) VALUES (4, '科技');INSERT INTO `news_type` (tid,tname) VALUES (5, '其他');-- ------------------------------ Table structure for news_user-- ----------------------------DROP TABLE IF EXISTS `news_user`;CREATE TABLE `news_user` ( `uid` INT NOT NULL AUTO_INCREMENT COMMENT '用户id', `username` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户登录名', `user_pwd` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户登录密码密文', `nick_name` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称', `version` INT DEFAULT 1 COMMENT '乐观锁', `is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除 0 未删除', PRIMARY KEY (`uid`) USING BTREE, UNIQUE INDEX `username_unique`(`username`) USING BTREE) ENGINE = INNODB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;-- ------------------------------ Records of news_user-- ----------------------------INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (1, 'zhangsan', 'e10adc3949ba59abbe56e057f20f883e', '张三');INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (2, 'lisi', 'e10adc3949ba59abbe56e057f20f883e', '李四');INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (5, 'zhangxiaoming', 'e10adc3949ba59abbe56e057f20f883e', '张小明');INSERT INTO `news_user` (uid,username,user_pwd,nick_name)VALUES (6, 'xiaohei', 'e10adc3949ba59abbe56e057f20f883e', '李小黑');SET FOREIGN_KEY_CHECKS = 1; 2. 搭建SprintBoot工程 创建boot工程 导入依赖 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.5</version></parent><groupId>com.atguigu</groupId><artifactId>springboot-headline</artifactId><version>1.0-SNAPSHOT</version><properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- 数据库相关配置启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- druid启动器的依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.18</version> </dependency> <!-- 驱动类--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.28</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies><!-- SpringBoot应用打包插件--><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build> 编写配置 application.yaml 123456789101112131415161718192021222324# server配置server: port: 8080 servlet: context-path: /# 连接池配置spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: url: jdbc:mysql:///sm_db1 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver# mybatis-plus的配置mybatis-plus: type-aliases-package: com.atguigu.pojo global-config: db-config: logic-delete-field: isDeleted #全局逻辑删除 id-type: auto #主键策略自增长 table-prefix: news_ # 设置表的前缀 druid兼容springboot3文件 文件名和内容 12文件名:org.springframework.boot.autoconfigure.AutoConfiguration.imports内容:com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure 启动类和mybatis-plus配置 包: com.atguigu 1234567891011121314151617181920@SpringBootApplication@MapperScan("com.atguigu.mapper")public class Main { public static void main(String[] args) { SpringApplication.run(Main.class,args); } //配置mybatis-plus插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //分页 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //乐观锁 interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); //防全局修改和删除 return interceptor; }} 工具类准备 结果封装类 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667/** * 全局统一返回结果类 */public class Result<T> { // 返回码 private Integer code; // 返回消息 private String message; // 返回数据 private T data; public Result(){} // 返回数据 protected static <T> Result<T> build(T data) { Result<T> result = new Result<T>(); if (data != null) result.setData(data); return result; } public static <T> Result<T> build(T body, Integer code, String message) { Result<T> result = build(body); result.setCode(code); result.setMessage(message); return result; } public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) { Result<T> result = build(body); result.setCode(resultCodeEnum.getCode()); result.setMessage(resultCodeEnum.getMessage()); return result; } /** * 操作成功 * @param data baseCategory1List * @param <T> * @return */ public static<T> Result<T> ok(T data){ Result<T> result = build(data); return build(data, ResultCodeEnum.SUCCESS); } public Result<T> message(String msg){ this.setMessage(msg); return this; } public Result<T> code(Integer code){ this.setCode(code); return this; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; }} 解决枚举类 1234567891011121314151617181920212223242526/** * 统一返回结果状态信息类 * */public enum ResultCodeEnum { SUCCESS(200,"success"), USERNAME_ERROR(501,"usernameError"), PASSWORD_ERROR(503,"passwordError"), NOTLOGIN(504,"notLogin"), USERNAME_USED(505,"userNameUsed"); private Integer code; private String message; private ResultCodeEnum(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public String getMessage() { return message; }} MD5加密工具类 12345678910111213141516171819202122232425262728import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;@Componentpublic final class MD5Util { public static String encrypt(String strSrc) { try { char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; byte[] bytes = strSrc.getBytes(); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(bytes); bytes = md.digest(); int j = bytes.length; char[] chars = new char[j * 2]; int k = 0; for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; chars[k++] = hexChars[b >>> 4 & 0xf]; chars[k++] = hexChars[b & 0xf]; } return new String(chars); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("MD5加密出错!!+" + e); } }} 3. MybatisX逆向工程 逆向工程 完善实体类注解 删除tableName注解,全局统一设置 添加主键,乐观锁,逻辑删除注解!!! 1234567891011121314151617181920@Datapublic class User implements Serializable { @TableId private Integer uid; private String username; private String userPwd; private String nickName; @Version private Integer version; @TableLogic private Integer isDeleted; private static final long serialVersionUID = 1L;} 四、后台功能开发4.1 用户模块开发 1.1 jwt和token介绍 token介绍 令牌(Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等。 简单理解 : 每个用户生成的唯一字符串标识,可以进行用户识别和校验 类似技术: 天王盖地虎 ,小鸡炖蘑菇 优势: token验证标识无法直接识别用户的信息,盗取token后也无法`登录`程序! 相对安全! jwt介绍 Token是一项规范和标准(接口) JWT(JSON Web Token)是具体可以生成,校验,解析等动作Token的技术(实现类) jwt工作流程 用户提供其凭据(通常是用户名和密码)进行身份验证。 服务器对这些凭据进行验证,并在验证成功后创建一个JWT。 服务器将JWT发送给客户端,并客户端在后续的请求中将JWT附加在请求头或参数中。 服务器接收到请求后,验证JWT的签名和有效性,并根据JWT中的声明进行身份验证和授权操作 jwt数据组成和包含信息 JWT由三部分组成: header(头部).payload(载荷).signature(签名) 我们需要理解的是, jwt可以携带很多信息! 一般情况,需要加入:有效时间,签名秘钥,其他用户标识信息! 有效时间为了保证token的时效性,过期可以重新登录获取! 签名秘钥为了防止其他人随意解析和校验token数据! 用户信息为了我们自己解析的时候,知道Token对应的具体用户! jwt使用和测试 导入依赖 1234567891011<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version></dependency><dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version></dependency> 编写配置 application.yaml 12345#jwt配置jwt: token: tokenExpiration: 120 #有效时间,单位分钟 tokenSignKey: headline123456 #当前程序签名秘钥 自定义 导入工具类 封装jwt技术工具类 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162package com.atguigu.utils;import com.alibaba.druid.util.StringUtils;import io.jsonwebtoken.*;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import java.util.Date;@Data@Component@ConfigurationProperties(prefix = "jwt.token")public class JwtHelper { private long tokenExpiration; //有效时间,单位毫秒 1000毫秒 == 1秒 private String tokenSignKey; //当前程序签名秘钥 //生成token字符串 public String createToken(Long userId) { System.out.println("tokenExpiration = " + tokenExpiration); System.out.println("tokenSignKey = " + tokenSignKey); String token = Jwts.builder() .setSubject("YYGH-USER") .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration*1000*60)) //单位分钟 .claim("userId", userId) .signWith(SignatureAlgorithm.HS512, tokenSignKey) .compressWith(CompressionCodecs.GZIP) .compact(); return token; } //从token字符串获取userid public Long getUserId(String token) { if(StringUtils.isEmpty(token)) return null; Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token); Claims claims = claimsJws.getBody(); Integer userId = (Integer)claims.get("userId"); return userId.longValue(); } //判断token是否有效 public boolean isExpiration(String token){ try { boolean isExpire = Jwts.parser() .setSigningKey(tokenSignKey) .parseClaimsJws(token) .getBody() .getExpiration().before(new Date()); //没有过期,有效,返回false return isExpire; }catch(Exception e) { //过期出现异常,返回true return true; } }} 使用和测试 12345678910111213141516171819202122@org.springframework.boot.test.context.SpringBootTestpublic class SpringBootTest { @Autowired private JwtHelper jwtHelper; @Test public void test(){ //生成 传入用户标识 String token = jwtHelper.createToken(1L); System.out.println("token = " + token); //解析用户标识 int userId = jwtHelper.getUserId(token).intValue(); System.out.println("userId = " + userId); //校验是否到期! false 未到期 true到期 boolean expiration = jwtHelper.isExpiration(token); System.out.println("expiration = " + expiration); }} 1.2 登录功能实现 需求描述 用户在客户端输入用户名密码并向后端提交,后端根据用户名和密码判断登录是否成功,用户有误或者密码有误响应不同的提示信息! 接口描述 url地址: user/login 请求方式:POST 请求参数: 1234{ "username":"zhangsan", //用户名 "userPwd":"123456" //明文密码} 响应数据: 成功 1234567{ "code":"200", // 成功状态码 "message":"success" // 成功状态描述 "data":{ "token":"... ..." // 用户id的token }} 失败 12345{ "code":"501", "message":"用户名有误" "data":{}} 12345{ "code":"503", "message":"密码有误" "data":{}} 实现代码 controller1234567891011121314151617181920212223242526272829303132333435363738394041@RestController@RequestMapping("user")@CrossOriginpublic class UserController { @Autowired private UserService userService; /** * 登录需求 * 地址: /user/login * 方式: post * 参数: * { * "username":"zhangsan", //用户名 * "userPwd":"123456" //明文密码 * } * 返回: * { * "code":"200", // 成功状态码 * "message":"success" // 成功状态描述 * "data":{ * "token":"... ..." // 用户id的token * } * } * * 大概流程: * 1. 账号进行数据库查询 返回用户对象 * 2. 对比用户密码(md5加密) * 3. 成功,根据userId生成token -> map key=token value=token值 - result封装 * 4. 失败,判断账号还是密码错误,封装对应的枚举错误即可 */ @PostMapping("login") public Result login(@RequestBody User user){ Result result = userService.login(user); System.out.println("result = " + result); return result; }} service123456789101112131415161718192021222324252627282930313233343536373839404142434445@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{ @Autowired private JwtHelper jwtHelper; @Autowired private UserMapper userMapper; /** * 登录业务实现 * @param user * @return result封装 */ @Override public Result login(User user) { //根据账号查询 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUsername,user.getUsername()); User loginUser = userMapper.selectOne(queryWrapper); //账号判断 if (loginUser == null) { //账号错误 return Result.build(null, ResultCodeEnum.USERNAME_ERROR); } //判断密码 if (!StringUtils.isEmpty(user.getUserPwd()) && loginUser.getUserPwd().equals(MD5Util.encrypt(user.getUserPwd()))) { //账号密码正确 //根据用户唯一标识生成token String token = jwtHelper.createToken(Long.valueOf(loginUser.getUid())); Map data = new HashMap(); data.put("token",token); return Result.ok(data); } //密码错误 return Result.build(null,ResultCodeEnum.PASSWORD_ERROR); }} 1.3 根据token获取用户数据 需求描述 客户端发送请求,提交token请求头,后端根据token请求头获取登录用户的详细信息并响应给客户端进行存储 接口描述 url地址:user/getUserInfo 请求方式:GET 请求头: 1token: token内容 响应数据: 成功 123456789101112{ "code": 200, "message": "success", "data": { "loginUser": { "uid": 1, "username": "zhangsan", "userPwd": "", "nickName": "张三" } }} 失败 12345{ "code": 504, "message": "notLogin", "data": null} 代码实现 controller1234567891011121314151617181920212223242526272829/** * 地址: user/getUserInfo * 方式: get * 请求头: token = token内容 * 返回: * { * "code": 200, * "message": "success", * "data": { * "loginUser": { * "uid": 1, * "username": "zhangsan", * "userPwd": "", * "nickName": "张三" * } * } * } * * 大概流程: * 1.获取token,解析token对应的userId * 2.根据userId,查询用户数据 * 3.将用户数据的密码置空,并且把用户数据封装到结果中key = loginUser * 4.失败返回504 (本次先写到当前业务,后期提取到拦截器和全局异常处理器) */@GetMapping("getUserInfo")public Result userInfo(@RequestHeader String token){ Result result = userService.getUserInfo(token); return result;} service1234567891011121314151617181920212223242526272829/** * 查询用户数据 * @param token * @return result封装 */@Overridepublic Result getUserInfo(String token) { //1.判定是否有效期 if (jwtHelper.isExpiration(token)) { //true过期,直接返回未登录 return Result.build(null,ResultCodeEnum.NOTLOGIN); } //2.获取token对应的用户 int userId = jwtHelper.getUserId(token).intValue(); //3.查询数据 User user = userMapper.selectById(userId); if (user != null) { user.setUserPwd(null); Map data = new HashMap(); data.put("loginUser",user); return Result.ok(data); } return Result.build(null,ResultCodeEnum.NOTLOGIN);} 1.4 注册用户名检查 需求描述 用户在注册时输入用户名时,立刻将用户名发送给后端,后端根据用户名查询用户名是否可用并做出响应 接口描述 url地址:user/checkUserName 请求方式:POST 请求参数:param形式 1username=zhangsan 响应数据: 成功 12345{ "code":"200", "message":"success" "data":{}} 失败 12345{ "code":"505", "message":"用户名占用" "data":{}} 代码实现 controller12345678910111213141516171819202122/** * url地址:user/checkUserName * 请求方式:POST * 请求参数:param形式 * username=zhangsan * 响应数据: * { * "code":"200", * "message":"success" * "data":{} * } * * 实现步骤: * 1. 获取账号数据 * 2. 根据账号进行数据库查询 * 3. 结果封装 */@PostMapping("checkUserName")public Result checkUserName(String username){ Result result = userService.checkUserName(username); return result;} service&#x20;12345678910111213141516171819/** * 检查账号是否可以注册 * * @param username 账号信息 * @return */@Overridepublic Result checkUserName(String username) { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUsername,username); User user = userMapper.selectOne(queryWrapper); if (user != null){ return Result.build(null,ResultCodeEnum.USERNAME_USED); } return Result.ok(null);} 1.5 用户注册功能 需求描述 客户端将新用户信息发送给服务端,服务端将新用户存入数据库,存入之前做用户名是否被占用校验,校验通过响应成功提示,否则响应失败提示 接口描述 url地址:user/regist 请求方式:POST 请求参数: 12345{ "username":"zhangsan", "userPwd":"123456", "nickName":"张三"} 响应数据: 成功 12345{ "code":"200", "message":"success" "data":{}} 失败 12345{ "code":"505", "message":"用户名占用" "data":{}} 代码实现 controller123456789101112131415161718192021222324252627/*** url地址:user/regist* 请求方式:POST* 请求参数:* {* "username":"zhangsan",* "userPwd":"123456",* "nickName":"张三"* }* 响应数据:* {* "code":"200",* "message":"success"* "data":{}* }** 实现步骤:* 1. 将密码加密* 2. 将数据插入* 3. 判断结果,成 返回200 失败 505*/@PostMapping("regist")public Result regist(@RequestBody User user){ Result result = userService.regist(user); return result;} service&#x20;123456789101112131415@Overridepublic Result regist(User user) { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUsername,user.getUsername()); Long count = userMapper.selectCount(queryWrapper); if (count > 0){ return Result.build(null,ResultCodeEnum.USERNAME_USED); } user.setUserPwd(MD5Util.encrypt(user.getUserPwd())); int rows = userMapper.insert(user); System.out.println("rows = " + rows); return Result.ok(null);} 4.2 首页模块开发 2.1 查询首页分类 需求描述 进入新闻首页,查询所有分类并动态展示新闻类别栏位 接口描述 url地址:portal/findAllTypes 请求方式:get 请求参数:无 响应数据: 成功 12345678910111213141516171819202122232425262728{ "code":"200", "message":"OK" "data":{ [ { "tid":"1", "tname":"新闻" }, { "tid":"2", "tname":"体育" }, { "tid":"3", "tname":"娱乐" }, { "tid":"4", "tname":"科技" }, { "tid":"5", "tname":"其他" } ] }} 代码实现 controller12345678910111213141516171819@RestController@RequestMapping("portal")@CrossOriginpublic class PortalController { @Autowired private TypeService typeService; /** * 查询全部类别信息 * @return */ @GetMapping("findAllTypes") public Result findAllTypes(){ //直接调用业务层,查询全部数据 List<Type> list = typeService.list(); return Result.ok(list); }} 2.2 分页查询首页头条信息 需求描述 客户端向服务端发送查询关键字,新闻类别,页码数,页大小 服务端根据条件搜索分页信息,返回含页码数,页大小,总页数,总记录数,当前页数据等信息,并根据时间降序,浏览量降序排序 接口描述 url地址:portal/findNewsPage 请求方式:post 请求参数: 123456{ "keyWords":"马斯克", // 搜索标题关键字 "type":0, // 新闻类型 "pageNum":1, // 页码数 "pageSize":10 // 页大小} 响应数据: 成功 1234567891011121314151617181920212223242526272829303132333435363738{ "code":"200", "message":"success" "data":{ "pageInfo":{ "pageData":[ { "hid":"1", // 新闻id "title":"尚硅谷宣布 ... ...", // 新闻标题 "type":"1", // 新闻所属类别编号 "pageViews":"40", // 新闻浏览量 "pastHours":"3" , // 发布时间已过小时数 "publisher":"1" // 发布用户ID }, { "hid":"1", // 新闻id "title":"尚硅谷宣布 ... ...", // 新闻标题 "type":"1", // 新闻所属类别编号 "pageViews":"40", // 新闻浏览量 "pastHours":"3", // 发布时间已过小时数 "publisher":"1" // 发布用户ID }, { "hid":"1", // 新闻id "title":"尚硅谷宣布 ... ...", // 新闻标题 "type":"1", // 新闻所属类别编号 "pageViews":"40", // 新闻浏览量 "pastHours":"3", // 发布时间已过小时数 "publisher":"1" // 发布用户ID } ], "pageNum":1, //页码数 "pageSize":10, // 页大小 "totalPage":20, // 总页数 "totalSize":200 // 总记录数 } }} 代码实现 准备条件实体类 12345678@Datapublic class PortalVo { private String keyWords; private Integer type; private Integer pageNum = 1; private Integer pageSize =10;} controller 123456789/** * 首页分页查询 * @return */@PostMapping("findNewPage")public Result findNewPage(@RequestBody PortalVo portalVo){ Result result = headlineService.findNewPage(portalVo); return result;} service 12345678910111213141516171819202122232425262728293031323334353637383940414243@Servicepublic class HeadlineServiceImpl extends ServiceImpl<HeadlineMapper, Headline> implements HeadlineService{ @Autowired private HeadlineMapper headlineMapper; /** * 首页数据查询 * @param portalVo * @return */ @Override public Result findNewPage(PortalVo portalVo) { //1.条件拼接 需要非空判断 LambdaQueryWrapper<Headline> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(!StringUtils.isEmpty(portalVo.getKeyWords()),Headline::getTitle,portalVo.getKeyWords()) .eq(portalVo.getType()!= null,Headline::getType,portalVo.getType()); //2.分页参数 IPage<Headline> page = new Page<>(portalVo.getPageNum(),portalVo.getPageSize()); //3.分页查询 //查询的结果 "pastHours":"3" // 发布时间已过小时数 我们查询返回一个map //自定义方法 headlineMapper.selectPageMap(page, portalVo); //4.结果封装 //分页数据封装 Map<String,Object> pageInfo =new HashMap<>(); pageInfo.put("pageData",page.getRecords()); pageInfo.put("pageNum",page.getCurrent()); pageInfo.put("pageSize",page.getSize()); pageInfo.put("totalPage",page.getPages()); pageInfo.put("totalSize",page.getTotal()); Map<String,Object> pageInfoMap=new HashMap<>(); pageInfoMap.put("pageInfo",pageInfo); // 响应JSON return Result.ok(pageInfoMap); }} mapper 接口: 123456public interface HeadlineMapper extends BaseMapper<Headline> { //自定义分页查询方法 IPage<Map> selectPageMap(IPage<Headline> page, @Param("portalVo") PortalVo portalVo);} mapperxml: 12345678910<select id="selectPageMap" resultType="map"> select hid,title,type,page_views pageViews,TIMESTAMPDIFF(HOUR,create_time,NOW()) pastHours, publisher from news_headline where is_deleted=0 <if test="portalVo.keyWords !=null and portalVo.keyWords.length()>0 "> and title like concat('%',#{portalVo.keyWords},'%') </if> <if test="portalVo.type != null and portalVo.type != 0"> and type = #{portalVo.type} </if></select> 2.3 查询头条详情 需求描述 用户点击”查看全文”时,向服务端发送新闻id 后端根据新闻id查询完整新闻文章信息并返回 后端要同时让新闻的浏览量+1 接口描述 url地址:portal/showHeadlineDetail 请求方式:post 请求参数: 1hid=1 param形成参数 响应数据: 成功 1234567891011121314151617{ "code":"200", "message":"success", "data":{ "headline":{ "hid":"1", // 新闻id "title":"马斯克宣布 ... ...", // 新闻标题 "article":"... ..." // 新闻正文 "type":"1", // 新闻所属类别编号 "typeName":"科技", // 新闻所属类别 "pageViews":"40", // 新闻浏览量 "pastHours":"3" , // 发布时间已过小时数 "publisher":"1" , // 发布用户ID "author":"张三" // 新闻作者 } }} 代码实现 controller 12345678910 /** * 首页详情接口 * @param hid * @return */@PostMapping("showHeadlineDetail")public Result showHeadlineDetail(Integer hid){ Result result = headlineService.showHeadlineDetail(hid); return result;} service 12345678910111213141516171819202122232425262728293031323334/** * 详情数据查询 * "headline":{ * "hid":"1", // 新闻id * "title":"马斯克宣布 ... ...", // 新闻标题 * "article":"... ..." // 新闻正文 * "type":"1", // 新闻所属类别编号 * "typeName":"科技", // 新闻所属类别 * "pageViews":"40", // 新闻浏览量 * "pastHours":"3" , // 发布时间已过小时数 * "publisher":"1" , // 发布用户ID * "author":"张三" // 新闻作者 * } * 注意: 是多表查询 , 需要更新浏览量+1 * * @param hid * @return */@Overridepublic Result showHeadlineDetail(Integer hid) { //1.实现根据id的查询(多表 Map headLineDetail = headlineMapper.selectDetailMap(hid); //2.拼接头条对象(阅读量和version)进行数据更新 Headline headline = new Headline(); headline.setHid(hid); headline.setPageViews((Integer) headLineDetail.get("pageViews")+1); //阅读量+1 headline.setVersion((Integer) headLineDetail.get("version")); //设置版本 headlineMapper.updateById(headline); Map<String,Object> pageInfoMap=new HashMap<>(); pageInfoMap.put("headline",headLineDetail); return Result.ok(pageInfoMap);} mapper 接口: 123456/** * 分页查询头条详情 * @param hid * @return */Map selectDetailMap(Integer hid); mapperxml: 123456789<!-- Map selectDetailMap(Integer hid);--><select id="selectDetailMap" resultType="map"> select hid,title,article,type, h.version ,tname typeName ,page_views pageViews ,TIMESTAMPDIFF(HOUR,create_time,NOW()) pastHours,publisher ,nick_name author from news_headline h left join news_type t on h.type = t.tid left join news_user u on h.publisher = u.uid where hid = #{hid}</select> 4.3 头条模块开发 **3.1 登陆验证和保护 ** 需求描述 客户端在进入发布页前、发布新闻前、进入修改页前、修改前、删除新闻前先向服务端发送请求携带token请求头 后端接收token请求头后,校验用户登录是否过期并做响应 前端根据响应信息提示用户进入登录页还是进入正常业务页面 接口描述 url地址:user/checkLogin 请求方式:get 请求参数: 无 请求头: token: 用户token 响应数据: 未过期: 12345{ "code":"200", "message":"success", "data":{}} 过期: 12345{ "code":"504", "message":"loginExpired", "data":{}} 代码实现 controller 【登录检查】123456789@GetMapping("checkLogin")public Result checkLogin(@RequestHeader String token){ if (StringUtils.isEmpty(token) || jwtHelper.isExpiration(token)){ //没有传或者过期 未登录 return Result.build(null, ResultCodeEnum.NOTLOGIN); } return Result.ok(null);} 拦截器 【所有/headline开头都需要检查登陆】1234567891011121314151617181920212223@Componentpublic class LoginProtectInterceptor implements HandlerInterceptor { @Autowired private JwtHelper jwtHelper; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); if (StringUtils.isEmpty(token) || jwtHelper.isExpiration(token)){ Result result = Result.build(null, ResultCodeEnum.NOTLOGIN); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(result); response.getWriter().print(json); //拦截 return false; }else{ //放行 return true; } }} 拦截器配置1234567891011@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Autowired private LoginProtectInterceptor loginProtectInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginProtectInterceptor).addPathPatterns("/headline/**"); }} 3.2 头条发布实现 需求描述 用户在客户端输入发布的新闻信息完毕后 发布前先请求后端的登录校验接口验证登录 登录通过则提交新闻信息 后端将新闻信息存入数据库 接口描述 url地址:headline/publish 请求方式:post 请求头:&#x20; 1token: ... ... 请求参数: 12345{ "title":"尚硅谷宣布 ... ...", // 文章标题 "article":"... ...", // 文章内容 "type":"1" // 文章类别} 响应数据: 未登录 12345{ "code":"504", "message":"loginExpired", "data":{}} 成功 123456{ "code":"200", "message":"success", "data":{}} 代码实现 controller1234567891011121314/** * 实现步骤: * 1. token获取userId [无需校验,拦截器会校验] * 2. 封装headline数据 * 3. 插入数据即可 */@PostMapping("publish")public Result publish(@RequestBody Headline headline,@RequestHeader String token){ int userId = jwtHelper.getUserId(token).intValue(); headline.setPublisher(userId); Result result = headlineService.publish(headline); return result;} service12345678910111213/** * 发布数据 * @param headline * @return */@Overridepublic Result publish(Headline headline) { headline.setCreateTime(new Date()); headline.setUpdateTime(new Date()); headline.setPageViews(0); headlineMapper.insert(headline); return Result.ok(null);} 3.3 修改头条回显 需求描述 前端先调用登录校验接口,校验登录是否过期 登录校验通过后 ,则根据新闻id查询新闻的完整信息并响应给前端 接口描述 url地址:headline/findHeadlineByHid 请求方式:post 请求参数: 1hid=1 param形成参数 响应数据: 成功 123456789101112{ "code":"200", "message":"success", "data":{ "headline":{ "hid":"1", "title":"马斯克宣布", "article":"... ... ", "type":"2" } }} 代码实现 controller12345@PostMapping("findHeadlineByHid")public Result findHeadlineByHid(Integer hid){ Result result = headlineService.findHeadlineByHid(hid); return result;} service123456789101112/** * 根据id查询详情 * @param hid * @return */@Overridepublic Result findHeadlineByHid(Integer hid) { Headline headline = headlineMapper.selectById(hid); Map<String,Object> pageInfoMap=new HashMap<>(); pageInfoMap.put("headline",headline); return Result.ok(pageInfoMap);} 3.4 头条修改实现 需求描述 客户端将新闻信息修改后,提交前先请求登录校验接口校验登录状态 登录校验通过则提交修改后的新闻信息,后端接收并更新进入数据库 接口描述 url地址:headline/update 请求方式:post 请求参数: 123456{ "hid":"1", "title":"尚硅谷宣布 ... ...", "article":"... ...", "type":"2"} 响应数据: 成功 12345{ "code":"200", "message":"success", "data":{}} 代码实现 controller12345@PostMapping("update")public Result update(@RequestBody Headline headline){ Result result = headlineService.updateHeadLine(headline); return result;} service123456789101112131415161718192021 /** * 修改业务 * 1.查询version版本 * 2.补全属性,修改时间 , 版本! * * @param headline * @return */@Overridepublic Result updateHeadLine(Headline headline) { //读取版本 Integer version = headlineMapper.selectById(headline.getHid()).getVersion(); headline.setVersion(version); headline.setUpdateTime(new Date()); headlineMapper.updateById(headline); return Result.ok(null);} 3.5 删除头条功能 需求描述 将要删除的新闻id发送给服务端 服务端校验登录是否过期,未过期则直接删除,过期则响应登录过期信息 接口描述 url地址:headline/removeByHid 请求方式:post 请求参数: 1hid=1 param形成参数 响应数据: 成功 12345{ "code":"200", "message":"success", "data":{}} 代码实现 controller12345@PostMapping("removeByHid")public Result removeById(Integer hid){ headlineService.removeById(hid); return Result.ok(null);} 五、前后端联调","categories":[{"name":"知识","slug":"知识","permalink":"http://example.com/categories/%E7%9F%A5%E8%AF%86/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"},{"name":"学习","slug":"学习","permalink":"http://example.com/tags/%E5%AD%A6%E4%B9%A0/"}]},{"title":"spring笔记","slug":"spring笔记","date":"2022-11-05T16:00:00.000Z","updated":"2023-10-22T02:02:02.516Z","comments":true,"path":"2022/11/06/spring笔记/","link":"","permalink":"http://example.com/2022/11/06/spring%E7%AC%94%E8%AE%B0/","excerpt":"仅为笔记查看,过后删除。","text":"仅为笔记查看,过后删除。 二、SpringFramework实战指南目录 一、技术体系结构 1.1 总体技术体系 1.2 框架概念和理解 二、SpringFramework介绍 2.1 Spring 和 SpringFramework概念 2.2 SpringFramework主要功能模块 2.3 SpringFramework 主要优势 三、Spring IoC容器和核心概念 3.1 组件和组件管理概念 3.2 Spring IoC容器和容器实现 3.3 Spring IoC / DI概念总结 四、Spring IoC实践和应用 4.1 Spring IoC / DI 实现步骤 4.2 基于XML配置方式组件管理 4.2.1 实验一: 组件(Bean)信息声明配置(IoC) 4.2.2 实验二: 组件(Bean)依赖注入配置(DI) 4.2.3 实验三: IoC容器创建和使用 4.2.4 实验四: 高级特性:组件(Bean)作用域和周期方法配置 4.2.5 实验五: 高级特性:FactoryBean特性和使用 4.2.6 实验六: 基于XML方式整合三层架构组件 4.3 基于 注解 方式管理 Bean 4.3.1 实验一: Bean注解标记和扫描 (IoC) 4.3.2 实验二: 组件(Bean)作用域和周期方法注解 4.3.3 实验三: Bean属性赋值:引用类型自动装配 (DI) 4.3.4 实验四: Bean属性赋值:基本类型属性赋值 (DI) 4.3.5 实验五: 基于注解+XML方式整合三层架构组件 4.4 基于 配置类 方式管理 Bean 4.4.1 完全注解开发理解 4.4.2 实验一:配置类和扫描注解 4.4.3 实验二:@Bean定义组件 4.4.4 实验三:高级特性:@Bean注解细节 4.4.5 实验四:高级特性:@Import扩展 4.4.6 实验五:基于注解+配置类方式整合三层架构组件 4.5 三种配置方式总结 4.5.1 XML方式配置总结 4.5.2 XML+注解方式配置总结 4.5.3 完全注解方式配置总结 4.6 整合Spring5-Test5搭建测试环境 五、Spring AOP面向切面编程 5.1 场景设定和问题复现 5.2 解决技术代理模式 5.3 面向切面编程思维(AOP) 5.4 Spring AOP框架介绍和关系梳理 5.5 Spring AOP基于注解方式实现和细节 5.5.1 Spring AOP底层技术组成 5.5.2 初步实现 5.5.3 获取通知细节信息 5.5.4 切点表达式语法 5.5.5 重用(提取)切点表达式 5.5.6 环绕通知 5.5.7 切面优先级设置 5.5.8 CGLib动态代理生效 5.5.9 注解实现小结 5.6 Spring AOP基于XML方式实现(了解) 5.7 Spring AOP对获取Bean的影响理解 5.7.1 根据类型装配 bean 5.7.2 使用总结 六、Spring 声明式事务 6.1 声明式事务概念 6.1.1 编程式事务 6.1.2 声明式事务 6.1.3 Spring事务管理器 6.2 基于注解的声明式事务 6.2.1 准备工作 6.2.2 基本事务控制 6.2.3 事务属性:只读 6.2.4 事务属性:超时时间 6.2.5 事务属性:事务异常 6.2.6 事务属性:事务隔离级别 6.2.7 事务属性:事务传播行为 七、Spring核心掌握总结 一、技术体系结构1.1 总体技术体系 单一架构 一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。 单一架构,项目主要应用技术框架为:Spring , SpringMVC , Mybatis 分布式架构 一个项目(对应 IDEA 中的一个 project),拆分成很多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。 分布式架构,项目主要应用技术框架:SpringBoot (SSM), SpringCloud , 中间件等 1.2 框架概念和理解框架( Framework )是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。 框架的优点包括以下几点: 提高开发效率:框架提供了许多预先设计好了的组件和工具,能够帮助开发人员快速进行开发。相较于传统手写代码,在框架提供的规范化环境中,开发者可以更快地实现项目的各种要求。 降低开发成本:框架的提供标准化的编程语言、数据操作等代码片段,避免了重复开发的问题,降低了开发成本,提供深度优化的系统,降低了维护成本,增强了系统的可靠性。 提高应用程序的稳定性:框架通常经过了很长时间的开发和测试,其中的许多组件、代码片段和设计模式都得到了验证。重复利用这些组件有助于减少bug的出现,从而提高了应用程序的稳定性。 提供标准化的解决方案:框架通常是针对某个特定领域的,通过提供标准化的解决方案,可以为开发人员提供一种共同的语言和思想基础,有助于更好地沟通和协作。 框架的缺点包括以下几个方面: 学习成本高:框架通常具有特定的语言和编程范式。对于开发人员而言,需要花费时间学习其背后的架构、模式和逻辑,这对于新手而言可能会耗费较长时间。 可能存在局限性:虽然框架提高了开发效率并可以帮助开发人员解决常见问题,但是在某些情况下,特定的应用需求可能超出框架的范围,从而导致应用程序无法满足要求。开发人员可能需要更多的控制权和自由度,同时需要在框架和应用程序之间进行权衡取舍。 版本变更和兼容性问题:框架的版本发布和迭代通常会导致代码库的大规模变更,进而导致应用程序出现兼容性问题和漏洞。当框架变更时,需要考虑框架是否向下兼容,以及如何进行适当的测试、迁移和升级。 架构风险:框架涉及到很多抽象和概念,如果开发者没有足够的理解和掌握其架构,可能会导致系统出现设计和架构缺陷,从而影响系统的健康性和安全性。 站在文件结构的角度理解框架,可以将框架总结:框架 = jar包+配置文件 莎士比亚说,”一千个观众眼中有一千个哈姆雷特” 即仁者见仁,智者见智.说每个人都会对作品有不同的理解,每个人对待任何事物都有自己的看法,同样的技术解决同样的问题会产生不同流程和风格的解决方案,而采用一种框架其实就是限制用户必须使用其规定的方案来实现,可以降低程序员之间沟通以及日后维护的成本! 常用的单一架构JavaEE项目框架演进,从SSH、SSH2过渡到了SSM:SpringMVC、Spring、MyBatis。 总之,框架已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度。 二、SpringFramework介绍2.1 Spring 和 SpringFramework概念https://spring.io/projects 广义的 Spring:Spring 技术栈(全家桶) 广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。 经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。 这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。 狭义的 Spring:Spring Framework(基础框架) 狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。 Spring Framework(Spring框架)是一个开源的应用程序框架,由SpringSource公司开发,最初是为了解决企业级开发中各种常见问题而创建的。它提供了很多功能,例如:依赖注入(Dependency Injection)、面向切面编程(AOP)、声明式事务管理(TX)等。其主要目标是使企业级应用程序的开发变得更加简单和快速,并且Spring框架被广泛应用于Java企业开发领域。 Spring全家桶的其他框架都是以SpringFramework框架为基础! 对比理解: QQ 和 腾讯 腾讯 = Spring QQ = SpringFramework 2.2 SpringFramework主要功能模块SpringFramework框架结构图: 功能模块 功能介绍 Core Container 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。 AOP&Aspects 面向切面编程 TX 声明式事务管理。 Spring MVC 提供了面向Web应用程序的集成功能。 2.3 SpringFramework 主要优势 丰富的生态系统:Spring 生态系统非常丰富,支持许多模块和库,如 Spring Boot、Spring Security、Spring Cloud 等等,可以帮助开发人员快速构建高可靠性的企业应用程序。 模块化的设计:框架组件之间的松散耦合和模块化设计使得 Spring Framework 具有良好的可重用性、可扩展性和可维护性。开发人员可以轻松地选择自己需要的模块,根据自己的需求进行开发。 简化 Java 开发:Spring Framework 简化了 Java 开发,提供了各种工具和 API,可以降低开发复杂度和学习成本。同时,Spring Framework 支持各种应用场景,包括 Web 应用程序、RESTful API、消息传递、批处理等等。 不断创新和发展:Spring Framework 开发团队一直在不断创新和发展,保持与最新技术的接轨,为开发人员提供更加先进和优秀的工具和框架。 因此,这些优点使得 Spring Framework 成为了一个稳定、可靠、且创新的框架,为企业级 Java 开发提供了一站式的解决方案。 Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中采用 Java 语言所需的一切,支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种架构。从Spring Framework 6.0.6开始,Spring 需要 Java 17+。 三、Spring IoC容器和核心概念3.1 组件和组件管理概念 3.1.1 什么是组件? 回顾常规的三层架构处理请求流程: 整个项目就是由各种组件搭建而成的: 3.1.2 我们的期待 有人替我们创建组件的对象 有人帮我们保存组件的对象 有人帮助我们自动组装 有人替我们管理事务 有人协助我们整合其他框架 …… 3.1.3 Spring充当组件管理角色(IoC) 那么谁帮我们完成我们的期待,帮我们管理组件呢? 当然是Spring 框架了! 组件可以完全交给Spring 框架进行管理,Spring框架替代了程序员原有的new对象和对象属性赋值动作等! Spring具体的组件管理动作包含: 组件对象实例化 组件属性属性赋值 组件对象之间引用 组件对象存活周期管理 ……我们只需要编写元数据(配置文件)告知Spring 管理哪些类组件和他们的关系即可!注意:组件是映射到应用程序中所有可重用组件的Java对象,应该是可复用的功能对象! 组件一定是对象 对象不一定是组件综上所述,Spring 充当一个组件容器,创建、管理、存储组件,减少了我们的编码压力,让我们更加专注进行业务编写! 3.1.4 组件交给Spring管理优势! 降低了组件之间的耦合性:Spring IoC容器通过依赖注入机制,将组件之间的依赖关系削弱,减少了程序组件之间的耦合性,使得组件更加松散地耦合。 提高了代码的可重用性和可维护性:将组件的实例化过程、依赖关系的管理等功能交给Spring IoC容器处理,使得组件代码更加模块化、可重用、更易于维护。 方便了配置和管理:Spring IoC容器通过XML文件或者注解,轻松的对组件进行配置和管理,使得组件的切换、替换等操作更加的方便和快捷。 交给Spring管理的对象(组件),方可享受Spring框架的其他功能(AOP,声明事务管理)等 3.2 Spring IoC容器和容器实现 3.2.1 普通和复杂容器 普通容器 生活中的普通容器 普通容器只能用来存储,没有更多功能。程序中的普通容器 数组 集合:List 集合:Set复杂容器生活中的复杂容器 政府管理我们的一生,生老病死都和政府有关。程序中的复杂容器Servlet 容器能够管理 Servlet(init,service,destroy)、Filter、Listener 这样的组件的一生,所以它是一个复杂容器。 名称 时机 次数 创建对象 默认情况:接收到第一次请求 &#xA;修改启动顺序后:Web应用启动过程中 一次 初始化操作 创建对象之后 一次 处理请求 接收到请求 多次 销毁操作 Web应用卸载之前 一次 我们即将要学习的 SpringIoC 容器也是一个复杂容器。它们不仅要负责创建组件的对象、存储组件的对象,还要负责调用组件的方法让它们工作,最终在特定情况下销毁组件。 总结:Spring管理组件的容器,就是一个复杂容器,不仅存储组件,也可以管理组件之间依赖关系,并且创建和销毁组件等! 3.2.2 SpringIoC容器介绍 Spring IoC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系。 上图显示了 Spring 容器工作原理的高级视图。应用程序类与配置元数据相结合,您拥有完全配置且可执行的系统或应用程序。 3.2.3 SpringIoC容器具体接口和实现类 SpringIoc容器接口:&#x20; BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口! ApplicationContext 是 BeanFactory 的子接口。它扩展了以下功能: 更容易与 Spring 的 AOP 功能集成 消息资源处理(用于国际化) 特定于应用程序给予此接口实现,例如Web 应用程序的 WebApplicationContext简而言之, BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。 ApplicationContext 是 BeanFactory 的完整超集!ApplicationContext容器实现类: 类型名 简介 ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 AnnotationConfigApplicationContext 通过读取Java配置类创建 IOC 容器对象 WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 3.2.4 SpringIoC容器管理配置方式 Spring IoC 容器使用多种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化、配置和组装应用程序中的对象。 Spring框架提供了多种配置方式:XML配置方式、注解方式和Java配置类方式 XML配置方式:是Spring框架最早的配置方式之一,通过在XML文件中定义Bean及其依赖关系、Bean的作用域等信息,让Spring IoC容器来管理Bean之间的依赖关系。该方式从Spring框架的第一版开始提供支持。 注解方式:从Spring 2.5版本开始提供支持,可以通过在Bean类上使用注解来代替XML配置文件中的配置信息。通过在Bean类上加上相应的注解(如@Component, @Service, @Autowired等),将Bean注册到Spring IoC容器中,这样Spring IoC容器就可以管理这些Bean之间的依赖关系。 Java配置类方式:从Spring 3.0版本开始提供支持,通过Java类来定义Bean、Bean之间的依赖关系和配置信息,从而代替XML配置文件的方式。Java配置类是一种使用Java编写配置信息的方式,通过@Configuration、@Bean等注解来实现Bean和依赖关系的配置。为了迎合当下开发环境,我们将以配置类+注解方式为主进行讲解! 3.3 Spring IoC / DI概念总结 IoC容器 Spring IoC 容器,负责实例化、配置和组装 bean(组件)核心容器。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。 IoC(Inversion of Control)控制反转 IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。 DI (Dependency Injection) 依赖注入 DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。 四、Spring IoC实践和应用4.1 Spring IoC / DI 实现步骤 我们总结下,组件交给Spring IoC容器管理,并且获取和使用的基本步骤! 配置元数据(配置) 配置元数据,既是编写交给SpringIoC容器管理组件的信息,配置方式有三种。 基于 XML 的配置元数据的基本结构: <bean id=”…” [1] class=”…” [2]> &#x20;&#x20; <!– collaborators and configuration for this bean go here –>&#x20; </bean> 12345678910111213141516<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="..." [1] class="..." [2]> <!-- collaborators and configuration for this bean go here --> </bean> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions go here --></beans> Spring IoC 容器管理一个或多个组件。这些 组件是使用你提供给容器的配置元数据(例如,以 XML <bean/> 定义的形式)创建的。 <bean /> 标签 == 组件信息声明 id 属性是标识单个 Bean 定义的字符串。 class 属性定义 Bean 的类型并使用完全限定的类名。 实例化IoC容器 提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据。 我们应该选择一个合适的容器实现类,进行IoC容器的实例化工作: 123//实例化ioc容器,读取外部配置文件,最终会在容器内进行ioc和di动作ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); 获取Bean(组件) ApplicationContext 是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class<T> requiredType) ,您可以检索 bean 的实例。 允许读取 Bean 定义并访问它们,如以下示例所示: 123456//创建ioc容器对象,指定配置文件,ioc也开始实例组件对象ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");//获取ioc容器的组件对象PetStoreService service = context.getBean("petStore", PetStoreService.class);//使用组件对象List<String> userList = service.getUsernameList(); 4.2 基于XML配置方式组件管理4.2.1 实验一: 组件(Bean)信息声明配置(IoC) 目标 Spring IoC 容器管理一个或多个 bean。这些 Bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/> 定义的形式)。 我们学习,如何通过定义XML配置文件,声明组件类信息,交给 Spring 的 IoC 容器进行组件管理! 思路 准备项目 创建maven工程(spring-ioc-xml-01) 导入SpringIoC相关依赖 pom.xml 123456789101112131415<dependencies> <!--spring context依赖--> <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.6</version> </dependency> <!--junit5测试--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version> </dependency></dependencies> 基于无参数构造函数 当通过构造函数方法创建一个 bean(组件对象) 时,所有普通类都可以由 Spring 使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 Bean 类信息就足够了。但是,默认情况下,我们需要一个默认(空)构造函数。 准备组件类 1234567891011package com.atguigu.ioc;public class HappyComponent { //默认包含无参数构造函数 public void doWork() { System.out.println("HappyComponent.doWork"); }} xml配置文件编写 创建携带spring约束的xml配置文件 编写配置文件: 文件:resources/spring-bean-01.xml 12<!-- 实验一 [重要]创建bean --><bean id="happyComponent" class="com.atguigu.ioc.HappyComponent"/> bean标签:通过配置bean标签告诉IOC容器需要创建对象的组件信息 id属性:bean的唯一标识,方便后期获取Bean! class属性:组件类的全限定符! 注意:要求当前组件类必须包含无参数构造函数! 基于静态工厂方法实例化 除了使用构造函数实例化对象,还有一类是通过工厂模式实例化对象。接下来我们讲解如何定义使用静态工厂方法创建Bean的配置 ! 准备组件类 123456789public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; }} xml配置文件编写 文件:resources/spring-bean-01.xml 123<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/> class属性:指定工厂类的全限定符! factory-method: 指定静态工厂方法,注意,该方法必须是static方法。 基于实例工厂方法实例化 接下来我们讲解下如何定义使用实例工厂方法创建Bean的配置 ! 准备组建类 12345678public class DefaultServiceLocator { private static ClientServiceImplclientService = new ClientServiceImpl(); public ClientService createClientServiceInstance() { return clientService; }} xml配置文件编写 文件:resources/spring-bean-01.xml 12345678<!-- 将工厂类进行ioc配置 --><bean id="serviceLocator" class="examples.DefaultServiceLocator"></bean><!-- 根据工厂对象的实例工厂方法进行实例化组件对象 --><bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> factory-bean属性:指定当前容器中工厂Bean 的名称。 factory-method: 指定实例工厂方法名。注意,实例方法必须是非static的! 图解IoC配置流程 4.2.2 实验二: 组件(Bean)依赖注入配置(DI) 目标 通过配置文件,实现IoC容器中Bean之间的引用(依赖注入DI配置)。 主要涉及注入场景:基于构造函数的依赖注入和基于 Setter 的依赖注入。 思路 基于构造函数的依赖注入(单个构造参数) 介绍 基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。 下面的示例演示一个只能通过构造函数注入进行依赖项注入的类! 准备组件类 12345678910111213public class UserDao {}public class UserService { private UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; }} 编写配置文件 文件:resources/spring-02.xml 123456789<beans> <!-- 引用类bean声明 --> <bean id="userService" class="x.y.UserService"> <!-- 构造函数引用 --> <constructor-arg ref="userDao"/> </bean> <!-- 被引用类bean声明 --> <bean id="userDao" class="x.y.UserDao"/></beans> constructor-arg标签:可以引用构造参数 ref引用其他bean的标识。 基于构造函数的依赖注入(多构造参数解析) 介绍 基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。 下面的示例演示通过构造函数注入多个参数,参数包含其他bean和基本数据类型! 准备组件类 123456789101112131415161718public class UserDao {}public class UserService { private UserDao userDao; private int age; private String name; public UserService(int age , String name ,UserDao userDao) { this.userDao = userDao; this.age = age; this.name = name; }} 编写配置文件 12345678910111213141516171819202122232425262728293031323334353637383940<!-- 场景1: 多参数,可以按照相应构造函数的顺序注入数据 --><beans> <bean id="userService" class="x.y.UserService"> <!-- value直接注入基本类型值 --> <constructor-arg value="18"/> <constructor-arg value="赵伟风"/> <constructor-arg ref="userDao"/> </bean> <!-- 被引用类bean声明 --> <bean id="userDao" class="x.y.UserDao"/></beans><!-- 场景2: 多参数,可以按照相应构造函数的名称注入数据 --><beans> <bean id="userService" class="x.y.UserService"> <!-- value直接注入基本类型值 --> <constructor-arg name="name" value="赵伟风"/> <constructor-arg name="userDao" ref="userDao"/> <constructor-arg name="age" value="18"/> </bean> <!-- 被引用类bean声明 --> <bean id="userDao" class="x.y.UserDao"/></beans><!-- 场景2: 多参数,可以按照相应构造函数的角标注入数据 index从0开始 构造函数(0,1,2....)--><beans> <bean id="userService" class="x.y.UserService"> <!-- value直接注入基本类型值 --> <constructor-arg index="1" value="赵伟风"/> <constructor-arg index="2" ref="userDao"/> <constructor-arg index="0" value="18"/> </bean> <!-- 被引用类bean声明 --> <bean id="userDao" class="x.y.UserDao"/></beans> constructor-arg标签:指定构造参数和对应的值 constructor-arg标签:name属性指定参数名、index属性指定参数角标、value属性指定普通属性值 基于Setter方法依赖注入 介绍 开发中,除了构造函数注入(DI)更多的使用的Setter方法进行注入! 下面的示例演示一个只能使用纯 setter 注入进行依赖项注入的类。 准备组件类 1234567891011121314151617181920public Class MovieFinder{}public class SimpleMovieLister { private MovieFinder movieFinder; private String movieName; public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } public void setMovieName(String movieName){ this.movieName = movieName; } // business logic that actually uses the injected MovieFinder is omitted...} 编写配置文件 1234567891011121314<bean id="simpleMovieLister" class="examples.SimpleMovieLister"> <!-- setter方法,注入movieFinder对象的标识id name = 属性名 ref = 引用bean的id值 --> <property name="movieFinder" ref="movieFinder" /> <!-- setter方法,注入基本数据类型movieName name = 属性名 value= 基本类型值 --> <property name="movieName" value="消失的她"/></bean><bean id="movieFinder" class="examples.MovieFinder"/> property标签: 可以给setter方法对应的属性赋值 property 标签: name属性代表set方法标识、ref代表引用bean的标识id、value属性代表基本属性值 总结: 依赖注入(DI)包含引用类型和基本数据类型,同时注入的方式也有多种!主流的注入方式为setter方法注入和构造函数注入,两种注入语法都需要掌握! 需要特别注意:引用其他bean,使用ref属性。直接注入基本类型值,使用value属性。 4.2.3 实验三: IoC容器创建和使用 介绍 上面的实验只是讲解了如何在XML格式的配置文件编写IoC和DI配置! 如图: 想要配置文件中声明组件类信息真正的进行实例化成Bean对象和形成Bean之间的引用关系,我们需要声明IoC容器对象,读取配置文件,实例化组件和关系维护的过程都是在IoC容器中实现的! 容器实例化 12345678910111213//方式1:实例化并且指定配置文件//参数:String...locations 传入一个或者多个配置文件ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); //方式2:先实例化,再指定配置文件,最后刷新容器触发Bean实例化动作 [springmvc源码和contextLoadListener源码方式] ApplicationContext context = new ClassPathXmlApplicationContext(); //设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置iocContainer1.setConfigLocations("services.xml", "daos.xml");//后配置的文件,需要调用refresh方法,触发刷新配置iocContainer1.refresh(); Bean对象读取 1234567891011121314151617181920//方式1: 根据id获取//没有指定类型,返回为Object,需要类型转化!HappyComponent happyComponent = (HappyComponent) iocContainer.getBean("bean的id标识"); //使用组件对象 happyComponent.doWork();//方式2: 根据类型获取//根据类型获取,但是要求,同类型(当前类,或者之类,或者接口的实现类)只能有一个对象交给IoC容器管理//配置两个或者以上出现: org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);happyComponent.doWork();//方式3: 根据id和类型获取HappyComponent happyComponent = iocContainer.getBean("bean的id标识", HappyComponent.class);happyComponent.doWork();根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。 4.2.4 实验四: 高级特性:组件(Bean)作用域和周期方法配置 组件周期方法配置 周期方法概念 我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们成为生命周期方法! 类似于Servlet的init/destroy方法,我们可以在周期方法完成初始化和释放资源等工作。 周期方法声明 1234567891011121314public class BeanOne { //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表 public void init() { // 初始化逻辑 }}public class BeanTwo { public void cleanup() { // 释放资源逻辑 }} 周期方法配置 1234<beans> <bean id="beanOne" class="examples.BeanOne" init-method="init" /> <bean id="beanTwo" class="examples.BeanTwo" destroy-method="cleanup" /></beans> 组件作用域配置 Bean作用域概念 <bean 标签声明Bean,只是将Bean的信息配置给SpringIoC容器! 在IoC容器中,这些<bean标签对应的信息转成Spring内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)! 这意味着,BeanDefinition与类概念一样,SpringIoC容器可以可以根据BeanDefinition对象反射创建多个Bean对象实例。 具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定! 作用域可选值 取值 含义 创建对象的时机 默认值 singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IOC 容器初始化时 是 prototype 这个 bean 在 IOC 容器中有多个实例 获取 bean 时 否 如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用): 取值 含义 创建对象的时机 默认值 ——- ———- ——- — request 请求范围内有效的实例 每次请求 否 session 会话范围内有效的实例 每次会话 否 作用域配置 配置scope范围 123456789101112<!--bean的作用域 准备两个引用关系的组件类即可!!--><!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 --><!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 --><bean id="happyMachine8" scope="prototype" class="com.atguigu.ioc.HappyMachine"> <property name="machineName" value="happyMachine"/></bean><bean id="happyComponent8" scope="singleton" class="com.atguigu.ioc.HappyComponent"> <property name="componentName" value="happyComponent"/></bean> 作用域测试 1234567891011121314@Testpublic void testExperiment08() { ApplicationContext iocContainer = new ClassPathXmlApplicationContext("配置文件名"); HappyMachine bean = iocContainer.getBean(HappyMachine.class); HappyMachine bean1 = iocContainer.getBean(HappyMachine.class); //多例对比 false System.out.println(bean == bean1); HappyComponent bean2 = iocContainer.getBean(HappyComponent.class); HappyComponent bean3 = iocContainer.getBean(HappyComponent.class); //单例对比 true System.out.println(bean2 == bean3);} 4.2.5 实验五: 高级特性:FactoryBean特性和使用 FactoryBean简介 FactoryBean 接口是Spring IoC容器实例化逻辑的可插拔性点。 用于配置复杂的Bean对象,可以将创建过程存储在FactoryBean 的getObject方法! FactoryBean<T> 接口提供三种方法: T getObject():&#x20; 返回此工厂创建的对象的实例。该返回值会被存储到IoC容器! boolean isSingleton():&#x20; 如果此 FactoryBean 返回单例,则返回 true ,否则返回 false 。此方法的默认实现返回 true (注意,lombok插件使用,可能影响效果)。 Class<?> getObjectType(): 返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 null 。 FactoryBean使用场景 代理类的创建 第三方框架整合 复杂对象实例化等 Factorybean应用 准备FactoryBean实现类1234567891011121314151617181920212223242526272829303132// 实现FactoryBean接口时需要指定泛型// 泛型类型就是当前工厂要生产的对象的类型public class HappyFactoryBean implements FactoryBean<HappyMachine> { private String machineName; public String getMachineName() { return machineName; } public void setMachineName(String machineName) { this.machineName = machineName; } @Override public HappyMachine getObject() throws Exception { // 方法内部模拟创建、设置一个对象的复杂过程 HappyMachine happyMachine = new HappyMachine(); happyMachine.setMachineName(this.machineName); return happyMachine; } @Override public Class<?> getObjectType() { // 返回要生产的对象的类型 return HappyMachine.class; }} 配置FactoryBean实现类123456<!-- FactoryBean机制 --><!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 --><bean id="happyMachine7" class="com.atguigu.ioc.HappyFactoryBean"> <!-- property标签仍然可以用来通过setXxx()方法给属性赋值 --> <property name="machineName" value="iceCreamMachine"/></bean> 测试读取FactoryBean和FactoryBean.getObject对象12345678910111213@Testpublic void testExperiment07() { ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-07.xml"); //注意: 直接根据声明FactoryBean的id,获取的是getObject方法返回的对象 HappyMachine happyMachine = iocContainer.getBean("happyMachine7",HappyMachine.class); System.out.println("happyMachine = " + happyMachine); //如果想要获取FactoryBean对象, 直接在id前添加&符号即可! &happyMachine7 这是一种固定的约束 Object bean = iocContainer.getBean("&happyMachine7"); System.out.println("bean = " + bean);} FactoryBean和BeanFactory区别 **FactoryBean **是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。 一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!! BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。 总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。 4.2.6 实验六: 基于XML方式整合三层架构组件 需求分析 搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用XML方式进行组件管理! 数据库准备 1234567891011121314151617181920212223create database studb;use studb;CREATE TABLE students ( id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, gender VARCHAR(10) NOT NULL, age INT, class VARCHAR(50));INSERT INTO students (id, name, gender, age, class)VALUES (1, '张三', '男', 20, '高中一班'), (2, '李四', '男', 19, '高中二班'), (3, '王五', '女', 18, '高中一班'), (4, '赵六', '女', 20, '高中三班'), (5, '刘七', '男', 19, '高中二班'), (6, '陈八', '女', 18, '高中一班'), (7, '杨九', '男', 20, '高中三班'), (8, '吴十', '男', 19, '高中二班'); 项目准备 项目创建 spring-xml-practice-02 依赖导入 123456789101112131415161718192021222324252627282930<dependencies> <!--spring context依赖--> <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.6</version> </dependency> <!-- 数据库驱动和连接池--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <!-- spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.6</version> </dependency></dependencies> 实体类准备 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960public class Student { private Integer id; private String name; private String gender; private Integer age; private String classes; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getClasses() { return classes; } public void setClasses(String classes) { this.classes = classes; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\\'' + ", gender='" + gender + '\\'' + ", age=" + age + ", classes='" + classes + '\\'' + '}'; }} JdbcTemplate技术讲解 为了在特定领域帮助我们简化代码,Spring 封装了很多 『Template』形式的模板类。例如:RedisTemplate、RestTemplate 等等,包括我们今天要学习的 JdbcTemplate。jdbc.properties提取数据库连接信息 1234atguigu.url=jdbc:mysql://localhost:3306/studbatguigu.driver=com.mysql.cj.jdbc.Driveratguigu.username=rootatguigu.password=root springioc配置文件 1234567891011121314151617181920212223242526<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 导入外部属性文件 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 配置数据源 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${atguigu.url}"/> <property name="driverClassName" value="${atguigu.driver}"/> <property name="username" value="${atguigu.username}"/> <property name="password" value="${atguigu.password}"/> </bean> <!-- 配置 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 装配数据源 --> <property name="dataSource" ref="druidDataSource"/> </bean> </beans> 基于jdbcTemplate的CRUD使用 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586public class JdbcTemplateTest { /** * 使用jdbcTemplate进行DML动作 */ @Test public void testDML(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml"); JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class); //TODO 执行插入一条学员数据 String sql = "insert into students (id,name,gender,age,class) values (?,?,?,?,?);"; /* 参数1: sql语句 参数2: 可变参数,占位符的值 */ int rows = jdbcTemplate.update(sql, 9,"十一", "男", 18, "二年三班"); System.out.println("rows = " + rows); } /** * 查询单条实体对象 * public class Student { * private Integer id; * private String name; * private String gender; * private Integer age; * private String classes; */ @Test public void testDQLForPojo(){ String sql = "select id , name , age , gender , class as classes from students where id = ? ;"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml"); JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class); //根据id查询 Student student = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { //自己处理结果映射 Student stu = new Student(); stu.setId(rs.getInt("id")); stu.setName(rs.getString("name")); stu.setAge(rs.getInt("age")); stu.setGender(rs.getString("gender")); stu.setClasses(rs.getString("classes")); return stu; }, 2); System.out.println("student = " + student); } /** * 查询实体类集合 */ @Test public void testDQLForListPojo(){ String sql = "select id , name , age , gender , class as classes from students ;"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml"); JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class); /* query可以返回集合! BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可 */ List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class)); System.out.println("studentList = " + studentList); }} 三层架构搭建和实现 持久层1234567891011121314151617181920212223242526272829303132333435363738//接口public interface StudentDao { /** * 查询全部学生数据 * @return */ List<Student> queryAll();}//实现类public class StudentDaoImpl implements StudentDao { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 查询全部学生数据 * @return */ @Override public List<Student> queryAll() { String sql = "select id , name , age , gender , class as classes from students ;"; /* query可以返回集合! BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可 */ List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class)); return studentList; }} 业务层123456789101112131415161718192021222324252627282930313233//接口public interface StudentService { /** * 查询全部学员业务 * @return */ List<Student> findAll();}//实现类public class StudentServiceImpl implements StudentService { private StudentDao studentDao; public void setStudentDao(StudentDao studentDao) { this.studentDao = studentDao; } /** * 查询全部学员业务 * @return */ @Override public List<Student> findAll() { List<Student> studentList = studentDao.queryAll(); return studentList; }} 表述层12345678910111213public class StudentController { private StudentService studentService; public void setStudentService(StudentService studentService) { this.studentService = studentService; } public void findAll(){ List<Student> studentList = studentService.findAll(); System.out.println("studentList = " + studentList); }} 三层架构IoC配置 1234567891011121314151617181920212223242526272829303132333435363738<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 导入外部属性文件 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 配置数据源 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${atguigu.url}"/> <property name="driverClassName" value="${atguigu.driver}"/> <property name="username" value="${atguigu.username}"/> <property name="password" value="${atguigu.password}"/> </bean> <!-- 配置 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 装配数据源 --> <property name="dataSource" ref="druidDataSource"/> </bean> <bean id="studentDao" class="com.atguigu.dao.impl.StudentDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> <bean id="studentService" class="com.atguigu.service.impl.StudentServiceImpl"> <property name="studentDao" ref="studentDao" /> </bean> <bean id="studentController" class="com.atguigu.controller.StudentController"> <property name="studentService" ref="studentService" /> </bean></beans> 运行测试 12345678910public class ControllerTest { @Test public void testRun(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml"); StudentController studentController = applicationContext.getBean(StudentController.class); studentController.findAll(); }} XMLIoC方式问题总结 注入的属性必须添加setter方法、代码结构乱! 配置文件和Java代码分离、编写不是很方便! XML配置文件解析效率低 4.3 基于 注解 方式管理 Bean4.3.1 实验一: Bean注解标记和扫描 (IoC) 注解理解 和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。 本质上:所有一切的操作都是 Java 代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。 举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴上气球。 班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面同学们做的工作,相当于框架的具体操作。 扫描理解 Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。 准备Spring项目和组件 准备项目pom.xml 12345678910111213141516<dependencies> <!--spring context依赖--> <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.6</version> </dependency> <!--junit5测试--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version> </dependency></dependencies> 准备组件类 普通组件 12345678/** * projectName: com.atguigu.components * * description: 普通的组件 */public class CommonComponent {} Controller组件 12345678/** * projectName: com.atguigu.components * * description: controller类型组件 */public class XxxController {} Service组件 12345678/** * projectName: com.atguigu.components * * description: service类型组件 */public class XxxService {} Dao组件 12345678/** * projectName: com.atguigu.components * * description: dao类型组件 */public class XxxDao {} 组件添加标记注解 组件标记注解和区别 Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。 注解 说明 @Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 @Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 @Service 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 @Controller 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。 对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。 注意:虽然它们本质上一样,但是为了代码的可读性、程序结构严谨!我们肯定不能随便胡乱标记。 使用注解标记 普通组件 123456789/** * projectName: com.atguigu.components * * description: 普通的组件 */@Componentpublic class CommonComponent {} Controller组件 123456789/** * projectName: com.atguigu.components * * description: controller类型组件 */@Controllerpublic class XxxController {} Service组件 123456789/** * projectName: com.atguigu.components * * description: service类型组件 */@Servicepublic class XxxService {} Dao组件 123456789/** * projectName: com.atguigu.components * * description: dao类型组件 */@Repositorypublic class XxxDao {} 配置文件确定扫描范围 情况1:基本扫描配置 12345678910111213<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置自动扫描的包 --> <!-- 1.包要精准,提高性能! 2.会扫描指定的包和子包内容 3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service等 --> <context:component-scan base-package="com.atguigu.components"/> </beans> 情况2:指定排除组件 12345678<!-- 情况三:指定不扫描的组件 --><context:component-scan base-package="com.atguigu.components"> <!-- context:exclude-filter标签:指定排除规则 --> <!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 --> <!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan> 情况3:指定扫描组件 12345678<!-- 情况四:仅扫描指定的组件 --><!-- 仅扫描 = 关闭默认规则 + 追加规则 --><!-- use-default-filters属性:取值false表示关闭默认扫描规则 --><context:component-scan base-package="com.atguigu.ioc.components" use-default-filters="false"> <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan> 组件BeanName问题 在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。 默认情况: 类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。 使用value属性指定: 123@Controller(value = "tianDog")public class SoldierController {} 当注解中只设置一个属性时,value属性的属性名可以省略: 123@Service("smallDog")public class SoldierService {} 总结 注解方式IoC只是标记哪些类要被Spring管理 最终,我们还需要XML方式或者后面讲解Java配置类方式指定注解生效的包 现阶段配置方式为 注解 (标记)+ XML(扫描) 4.3.2 实验二: 组件(Bean)作用域和周期方法注解&#x20; 组件周期方法配置 周期方法概念 我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们成为生命周期方法! 类似于Servlet的init/destroy方法,我们可以在周期方法完成初始化和释放资源等工作。 周期方法声明 12345678910111213141516public class BeanOne { //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表 @PostConstruct //注解制指定初始化方法 public void init() { // 初始化逻辑 }}public class BeanTwo { @PreDestroy //注解指定销毁方法 public void cleanup() { // 释放资源逻辑 }} 组件作用域配置 Bean作用域概念 <bean 标签声明Bean,只是将Bean的信息配置给SpringIoC容器! 在IoC容器中,这些<bean标签对应的信息转成Spring内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)! 这意味着,BeanDefinition与类概念一样,SpringIoC容器可以可以根据BeanDefinition对象反射创建多个Bean对象实例。 具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定! 作用域可选值 取值 含义 创建对象的时机 默认值 singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IOC 容器初始化时 是 prototype 这个 bean 在 IOC 容器中有多个实例 获取 bean 时 否 如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用): 取值 含义 创建对象的时机 默认值 ——- ———- ——- — request 请求范围内有效的实例 每次请求 否 session 会话范围内有效的实例 每次会话 否 作用域配置 12345678910@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例 二选一public class BeanOne { //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表 @PostConstruct //注解制指定初始化方法 public void init() { // 初始化逻辑 }} 4.3.3 实验三: Bean属性赋值:引用类型自动装配 (DI) 设定场景 SoldierController 需要 SoldierService SoldierService 需要 SoldierDao&#x20; 同时在各个组件中声明要调用的方法。 SoldierController中声明方法123456789101112import org.springframework.stereotype.Controller;@Controller(value = "tianDog")public class SoldierController { private SoldierService soldierService; public void getMessage() { soldierService.getMessage(); }} SoldierService中声明方法123456789@Service("smallDog")public class SoldierService { private SoldierDao soldierDao; public void getMessage() { soldierDao.getMessage(); }} SoldierDao中声明方法12345678@Repositorypublic class SoldierDao { public void getMessage() { System.out.print("I am a soldier"); }} 自动装配实现 前提 参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。 注意:不区分IoC的方式!XML和注解都可以! @Autowired注解 在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。 给Controller装配Service 1234567891011@Controller(value = "tianDog")public class SoldierController { @Autowired private SoldierService soldierService; public void getMessage() { soldierService.getMessage(); } } 给Service装配Dao 12345678910@Service("smallDog")public class SoldierService { @Autowired private SoldierDao soldierDao; public void getMessage() { soldierDao.getMessage(); }} @Autowired注解细节 标记位置 成员变量 这是最主要的使用方式! 与xml进行bean ref引用不同,他不需要有set方法! 12345678910@Service("smallDog")public class SoldierService { @Autowired private SoldierDao soldierDao; public void getMessage() { soldierDao.getMessage(); }} 构造器 12345678910@Controller(value = "tianDog")public class SoldierController { private SoldierService soldierService; @Autowired public SoldierController(SoldierService soldierService) { this.soldierService = soldierService; } …… setXxx()方法 12345678910@Controller(value = "tianDog")public class SoldierController { private SoldierService soldierService; @Autowired public void setSoldierService(SoldierService soldierService) { this.soldierService = soldierService; } …… 工作流程 首先根据所需要的组件类型到 IOC 容器中查找 能够找到唯一的 bean:直接执行装配 如果完全找不到匹配这个类型的 bean:装配失败 和所需类型匹配的 bean 不止一个 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配 能够找到:执行装配 找不到:装配失败 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配 能够找到:执行装配 找不到:装配失败1234567@Controller(value = "tianDog")public class SoldierController { @Autowired @Qualifier(value = "maomiService222") // 根据面向接口编程思想,使用接口类型引入Service组件 private ISoldierService soldierService; 佛系装配 给 @Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性 123456@Controller(value = "tianDog")public class SoldierController { // 给@Autowired注解设置required = false属性表示:能装就装,装不上就不装 @Autowired(required = false) private ISoldierService soldierService; 扩展JSR-250注解@Resource 理解JSR系列注解 JSR(Java Specification Requests)是Java平台标准化进程中的一种技术规范,而JSR注解是其中一部分重要的内容。按照JSR的分类以及注解语义的不同,可以将JSR注解分为不同的系列,主要有以下几个系列: JSR-175: 这个JSR是Java SE 5引入的,是Java注解最早的规范化版本,Java SE 5后的版本中都包含该JSR中定义的注解。主要包括以下几种标准注解: @Deprecated: 标识一个程序元素(如类、方法或字段)已过时,并且在将来的版本中可能会被删除。 @Override: 标识一个方法重写了父类中的方法。 @SuppressWarnings: 抑制编译时产生的警告消息。 @SafeVarargs: 标识一个有安全性警告的可变参数方法。 @FunctionalInterface: 标识一个接口只有一个抽象方法,可以作为lambda表达式的目标。 JSR-250: 这个JSR主要用于在Java EE 5中定义一些支持注解。该JSR主要定义了一些用于进行对象管理的注解,包括: @Resource: 标识一个需要注入的资源,是实现Java EE组件之间依赖关系的一种方式。 @PostConstruct: 标识一个方法作为初始化方法。 @PreDestroy: 标识一个方法作为销毁方法。 @Resource.AuthenticationType: 标识注入的资源的身份验证类型。 @Resource.AuthenticationType: 标识注入的资源的默认名称。 JSR-269: 这个JSR主要是Java SE 6中引入的一种支持编译时元数据处理的框架,即使用注解来处理Java源文件。该JSR定义了一些可以用注解标记的注解处理器,用于生成一些元数据,常用的注解有: @SupportedAnnotationTypes: 标识注解处理器所处理的注解类型。 @SupportedSourceVersion: 标识注解处理器支持的Java源码版本。 JSR-330: 该JSR主要为Java应用程序定义了一个依赖注入的标准,即Java依赖注入标准(javax.inject)。在此规范中定义了多种注解,包括: @Named: 标识一个被依赖注入的组件的名称。 @Inject: 标识一个需要被注入的依赖组件。 @Singleton: 标识一个组件的生命周期只有一个唯一的实例。 JSR-250: 这个JSR主要是Java EE 5中定义一些支持注解。该JSR包含了一些支持注解,可以用于对Java EE组件进行管理,包括: @RolesAllowed: 标识授权角色 @PermitAll: 标识一个活动无需进行身份验证。 @DenyAll: 标识不提供针对该方法的访问控制。 @DeclareRoles: 声明安全角色。但是你要理解JSR是Java提供的技术规范,也就是说,他只是规定了注解和注解的含义,JSR并不是直接提供特定的实现,而是提供标准和指导方针,由第三方框架(Spring)和库来实现和提供对应的功能。 JSR-250 @Resource注解 @Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别? @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。) @Autowired注解是Spring框架自己的。 @Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配。 @Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用。 @Resource注解用在属性上、setter方法上。 @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【高于JDK11或低于JDK8需要引入以下依赖】12345<dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version></dependency> @Resource使用 123456789101112131415161718@Controllerpublic class XxxController { /** * 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService * 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找 * 3. 可以指定name名称查找! @Resource(name='test') == @Autowired + @Qualifier(value='test') */ @Resource private XxxService xxxService; //@Resource(name = "指定beanName") //private XxxService xxxService; public void show(){ System.out.println("XxxController.show"); xxxService.show(); }} 4.3.4 实验四: Bean属性赋值:基本类型属性赋值 (DI)@Value 通常用于注入外部化属性 声明外部配置 application.properties 1catalog.name=MovieCatalog xml引入外部配置 12<!-- 引入外部配置文件--><context:property-placeholder location="application.properties" /> @Value注解读取配置 1234567891011121314151617181920212223242526272829package com.atguigu.components;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;/** * projectName: com.atguigu.components * * description: 普通的组件 */@Componentpublic class CommonComponent { /** * 情况1: ${key} 取外部配置key对应的值! * 情况2: ${key:defaultValue} 没有key,可以给与默认值 */ @Value("${catalog:hahaha}") private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }} catalog 4.3.5 实验五: 基于注解+XML方式整合三层架构组件 需求分析 搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用XML+注解方式进行组件管理! 数据库准备 1234567891011121314151617181920212223create database studb;use studb;CREATE TABLE students ( id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, gender VARCHAR(10) NOT NULL, age INT, class VARCHAR(50));INSERT INTO students (id, name, gender, age, class)VALUES (1, '张三', '男', 20, '高中一班'), (2, '李四', '男', 19, '高中二班'), (3, '王五', '女', 18, '高中一班'), (4, '赵六', '女', 20, '高中三班'), (5, '刘七', '男', 19, '高中二班'), (6, '陈八', '女', 18, '高中一班'), (7, '杨九', '男', 20, '高中三班'), (8, '吴十', '男', 19, '高中二班'); 项目准备 项目创建 spring-annotation-practice-04 依赖导入 123456789101112131415161718192021222324252627282930313233343536<dependencies> <!--spring context依赖--> <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.6</version> </dependency> <!-- 数据库驱动和连接池--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency> <!-- spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.6</version> </dependency></dependencies> 实体类准备 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960public class Student { private Integer id; private String name; private String gender; private Integer age; private String classes; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getClasses() { return classes; } public void setClasses(String classes) { this.classes = classes; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\\'' + ", gender='" + gender + '\\'' + ", age=" + age + ", classes='" + classes + '\\'' + '}'; }} 三层架构搭建和实现 持久层123456789101112131415161718192021222324252627282930313233343536//接口public interface StudentDao { /** * 查询全部学生数据 * @return */ List<Student> queryAll();}//实现类@Repositorypublic class StudentDaoImpl implements StudentDao { @Autowired private JdbcTemplate jdbcTemplate; /** * 查询全部学生数据 * @return */ @Override public List<Student> queryAll() { String sql = "select id , name , age , gender , class as classes from students ;"; /* query可以返回集合! BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可 */ List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class)); return studentList; }} 业务层12345678910111213141516171819202122232425262728293031//接口public interface StudentService { /** * 查询全部学员业务 * @return */ List<Student> findAll();}//实现类@Servicepublic class StudentServiceImpl implements StudentService { @Autowired private StudentDao studentDao; /** * 查询全部学员业务 * @return */ @Override public List<Student> findAll() { List<Student> studentList = studentDao.queryAll(); return studentList; }} 表述层123456789101112@Controllerpublic class StudentController { @Autowired private StudentService studentService; public void findAll(){ List<Student> studentList = studentService.findAll(); System.out.println("studentList = " + studentList); }} 三层架构IoC配置 1234567891011121314151617181920212223242526<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 导入外部属性文件 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 配置数据源 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${atguigu.url}"/> <property name="driverClassName" value="${atguigu.driver}"/> <property name="username" value="${atguigu.username}"/> <property name="password" value="${atguigu.password}"/> </bean> <bean class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="druidDataSource" /> </bean> <!-- 扫描Ioc/DI注解 --> <context:component-scan base-package="com.atguigu.dao,com.atguigu.service,com.atguigu.controller" /></beans> 运行测试 12345678910public class ControllerTest { @Test public void testRun(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml"); StudentController studentController = applicationContext.getBean(StudentController.class); studentController.findAll(); }} 注解+XML IoC方式问题总结 自定义类可以使用注解方式,但是第三方依赖的类依然使用XML方式! XML格式解析效率低! 4.4 基于 配置类 方式管理 Bean4.4.1 完全注解开发理解Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。 两种方式思维转化: 4.4.2 实验一:配置类和扫描注解xml+注解方式 配置文件application.xml 1234567891011121314151617<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置自动扫描的包 --> <!-- 1.包要精准,提高性能! 2.会扫描指定的包和子包内容 3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service等 --> <context:component-scan base-package="com.atguigu.components"/> <!-- 引入外部配置文件--> <context:property-placeholder location="application.properties" /></beans> 测试创建IoC容器 123// xml方式配置文件使用ClassPathXmlApplicationContext容器读取ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); 配置类+注解方式(完全注解方式) 配置类 使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类。 12345678910111213import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;//标注当前类是配置类,替代application.xml @Configuration//使用注解读取外部配置,替代 <context:property-placeholder标签@PropertySource("classpath:application.properties")//使用@ComponentScan注解,可以配置扫描包,替代<context:component-scan标签@ComponentScan(basePackages = {"com.atguigu.components"})public class MyConfiguration { } 测试创建IoC容器 123// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class); 可以使用 no-arg 构造函数实例化 AnnotationConfigApplicationContext ,然后使用 register() 方法对其进行配置。此方法在以编程方式生成 AnnotationConfigApplicationContext 时特别有用。以下示例演示如何执行此操作: 12345678// AnnotationConfigApplicationContext-IOC容器对象ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext();//外部设置配置类iocContainerAnnotation.register(MyConfiguration.class);//刷新后方可生效!!iocContainerAnnotation.refresh(); 总结: @Configuration指定一个类为配置类,可以添加配置注解,替代配置xml文件 @ComponentScan(basePackages = {“包”,”包”}) 替代<context:component-scan标签实现注解扫描 @PropertySource(“classpath:配置文件地址”) 替代 <context:property-placeholder标签 配合IoC/DI注解,可以进行完整注解开发! 4.4.3 实验二:@Bean定义组件场景需求:将Druid连接池对象存储到IoC容器 需求分析:第三方jar包的类,添加到ioc容器,无法使用@Component等相关注解!因为源码jar包内容为只读模式! xml方式实现: 12345678910111213141516171819<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 引入外部属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 实验六 [重要]给bean的属性赋值:引入外部属性文件 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean></beans> 配置类方式实现: @Bean 注释用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉 Spring 的 <beans/> XML 配置的人来说, @Bean 注释与 <bean/> 元素起着相同的作用。 12345678910111213141516171819202122232425//标注当前类是配置类,替代application.xml @Configuration//引入jdbc.properties文件@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})@ComponentScan(basePackages = {"com.atguigu.components"})public class MyConfiguration { //如果第三方类进行IoC管理,无法直接使用@Component相关注解 //解决方案: xml方式可以使用<bean标签 //解决方案: 配置类方式,可以使用方法返回值+@Bean注解 @Bean public DataSource createDataSource(@Value("${jdbc.user}") String username, @Value("${jdbc.password}")String password, @Value("${jdbc.url}")String url, @Value("${jdbc.driver}")String driverClassName){ //使用Java代码实例化 DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driverClassName); //返回结果即可 return dataSource; }} 4.4.4 实验三:高级特性:@Bean注解细节 @Bean生成BeanName问题 @Bean注解源码: 123456789101112131415161718public @interface Bean { //前两个注解可以指定Bean的标识 @AliasFor("name") String[] value() default {}; @AliasFor("value") String[] name() default {}; //autowireCandidate 属性来指示该 Bean 是否候选用于自动装配。 //autowireCandidate 属性默认值为 true,表示该 Bean 是一个默认的装配目标, //可被候选用于自动装配。如果将 autowireCandidate 属性设置为 false,则说明该 Bean 不是默认的装配目标,不会被候选用于自动装配。 boolean autowireCandidate() default true; //指定初始化方法 String initMethod() default ""; //指定销毁方法 String destroyMethod() default "(inferred)";} 指定@Bean的名称: 12345678@Configurationpublic class AppConfig { @Bean("myThing") //指定名称 public Thing thing() { return new Thing(); }} @Bean 注释注释方法。使用此方法在指定为方法返回值的类型的 ApplicationContext 中注册 Bean 定义。缺省情况下,Bean 名称与方法名称相同。下面的示例演示 @Bean 方法声明: 12345678@Configurationpublic class AppConfig { @Bean public TransferServiceImpl transferService() { return new TransferServiceImpl(); }} 前面的配置完全等同于下面的Spring XML: 123<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/></beans> @Bean 初始化和销毁方法指定 @Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-method 和 destroy-method 属性,如以下示例所示: 123456789101112131415161718192021222324252627public class BeanOne { public void init() { // initialization logic }}public class BeanTwo { public void cleanup() { // destruction logic }}@Configurationpublic class AppConfig { @Bean(initMethod = "init") public BeanOne beanOne() { return new BeanOne(); } @Bean(destroyMethod = "cleanup") public BeanTwo beanTwo() { return new BeanTwo(); }} @Bean Scope作用域 可以指定使用 @Bean 注释定义的 bean 应具有特定范围。您可以使用在 Bean 作用域部分中指定的任何标准作用域。 默认作用域为 singleton ,但您可以使用 @Scope 注释覆盖此范围,如以下示例所示: 123456789@Configurationpublic class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... }} @Bean方法之间依赖 准备组件 123456789101112public class HappyMachine { private String machineName; public String getMachineName() { return machineName; } public void setMachineName(String machineName) { this.machineName = machineName; }} 1234567891011121314151617public class HappyComponent { //引用新组件 private HappyMachine happyMachine; public HappyMachine getHappyMachine() { return happyMachine; } public void setHappyMachine(HappyMachine happyMachine) { this.happyMachine = happyMachine; } public void doWork() { System.out.println("HappyComponent.doWork"); }} Java配置类实现: 方案1: 直接调用方法返回 Bean 实例:在一个 @Bean 方法中直接调用其他 @Bean 方法来获取 Bean 实例,虽然是方法调用,也是通过IoC容器获取对应的Bean,例如: 1234567891011121314151617@Configurationpublic class JavaConfig { @Bean public HappyMachine happyMachine(){ return new HappyMachine(); } @Bean public HappyComponent happyComponent(){ HappyComponent happyComponent = new HappyComponent(); //直接调用方法即可! happyComponent.setHappyMachine(happyMachine()); return happyComponent; }} 方案2: 参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系,例如: 123456789101112131415161718192021222324252627282930313233343536373839404142434445package com.atguigu.config;import com.atguigu.ioc.HappyComponent;import com.atguigu.ioc.HappyMachine;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * projectName: com.atguigu.config * description: 配置HappyComponent和HappyMachine关系 */@Configurationpublic class JavaConfig { @Bean public HappyMachine happyMachine(){ return new HappyMachine(); } /** * 可以直接在形参列表接收IoC容器中的Bean! * 情况1: 直接指定类型即可 * 情况2: 如果有多个bean,(HappyMachine 名称 ) 形参名称等于要指定的bean名称! * 例如: * @Bean * public Foo foo1(){ * return new Foo(); * } * @Bean * public Foo foo2(){ * return new Foo() * } * @Bean * public Component component(Foo foo1 / foo2 通过此处指定引入的bean) */ @Bean public HappyComponent happyComponent(HappyMachine happyMachine){ HappyComponent happyComponent = new HappyComponent(); //赋值 happyComponent.setHappyMachine(happyMachine); return happyComponent; }} 4.4.5 实验四:高级特性:@Import扩展@Import 注释允许从另一个配置类加载 @Bean 定义,如以下示例所示: 123456789101112131415161718@Configurationpublic class ConfigA { @Bean public A a() { return new A(); }}@Configuration@Import(ConfigA.class)public class ConfigB { @Bean public B b() { return new B(); }} 现在,在实例化上下文时不需要同时指定 ConfigA.class 和 ConfigB.class ,只需显式提供 ConfigB ,如以下示例所示: 1234567public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class);} 此方法简化了容器实例化,因为只需要处理一个类,而不是要求您在构造期间记住可能大量的 @Configuration 类。 4.4.6 实验五:基于注解+配置类方式整合三层架构组件 需求分析 搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用注解+配置类方式进行组件管理! 数据库准备 1234567891011121314151617181920212223create database studb;use studb;CREATE TABLE students ( id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, gender VARCHAR(10) NOT NULL, age INT, class VARCHAR(50));INSERT INTO students (id, name, gender, age, class)VALUES (1, '张三', '男', 20, '高中一班'), (2, '李四', '男', 19, '高中二班'), (3, '王五', '女', 18, '高中一班'), (4, '赵六', '女', 20, '高中三班'), (5, '刘七', '男', 19, '高中二班'), (6, '陈八', '女', 18, '高中一班'), (7, '杨九', '男', 20, '高中三班'), (8, '吴十', '男', 19, '高中二班'); 项目准备 项目创建 spring-java-practice-06 依赖导入 123456789101112131415161718192021222324252627282930313233343536<dependencies> <!--spring context依赖--> <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.6</version> </dependency> <!-- 数据库驱动和连接池--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency> <!-- spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.6</version> </dependency></dependencies> 实体类准备 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960public class Student { private Integer id; private String name; private String gender; private Integer age; private String classes; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getClasses() { return classes; } public void setClasses(String classes) { this.classes = classes; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\\'' + ", gender='" + gender + '\\'' + ", age=" + age + ", classes='" + classes + '\\'' + '}'; }} 三层架构搭建和实现 持久层123456789101112131415161718192021222324252627282930313233343536//接口public interface StudentDao { /** * 查询全部学生数据 * @return */ List<Student> queryAll();}//实现类@Repositorypublic class StudentDaoImpl implements StudentDao { @Autowired private JdbcTemplate jdbcTemplate; /** * 查询全部学生数据 * @return */ @Override public List<Student> queryAll() { String sql = "select id , name , age , gender , class as classes from students ;"; /* query可以返回集合! BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可 */ List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class)); return studentList; }} 业务层12345678910111213141516171819202122232425262728293031//接口public interface StudentService { /** * 查询全部学员业务 * @return */ List<Student> findAll();}//实现类@Servicepublic class StudentServiceImpl implements StudentService { @Autowired private StudentDao studentDao; /** * 查询全部学员业务 * @return */ @Override public List<Student> findAll() { List<Student> studentList = studentDao.queryAll(); return studentList; }} 表述层123456789101112@Controllerpublic class StudentController { @Autowired private StudentService studentService; public void findAll(){ List<Student> studentList = studentService.findAll(); System.out.println("studentList = " + studentList); }} 三层架构IoC配置类 123456789101112131415161718192021222324252627282930313233@Configuration@ComponentScan(basePackages = "com.atguigu")@PropertySource("classpath:jdbc.properties")public class JavaConfig { @Value("${atguigu.url}") private String url; @Value("${atguigu.driver}") private String driver; @Value("${atguigu.username}") private String username; @Value("${atguigu.password}") private String password; @Bean(destroyMethod = "close") public DruidDataSource dataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setDriverClassName(driver); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; }} 运行测试 1234567891011121314public class ControllerTest { @Test public void testRun(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class); StudentController studentController = applicationContext.getBean(StudentController.class); studentController.findAll(); }} 注解+配置类 IoC方式总结 完全摒弃了XML配置文件 自定义类使用IoC和DI注解标记 第三方类使用配置类声明方法+@Bean方式处理 完全注解方式(配置类+注解)是现在主流配置方式 4.5 三种配置方式总结4.5.1 XML方式配置总结 所有内容写到xml格式配置文件中 声明bean通过<bean标签 <bean标签包含基本信息(id,class)和属性信息 <property name value / ref 引入外部的properties文件可以通过<context:property-placeholder IoC具体容器实现选择ClassPathXmlApplicationContext对象 4.5.2 XML+注解方式配置总结 注解负责标记IoC的类和进行属性装配 xml文件依然需要,需要通过<context:component-scan标签指定注解范围 标记IoC注解:@Component,@Service,@Controller,@Repository&#x20; 标记DI注解:@Autowired @Qualifier @Resource @Value IoC具体容器实现选择ClassPathXmlApplicationContext对象 4.5.3 完全注解方式配置总结 完全注解方式指的是去掉xml文件,使用配置类 + 注解实现 xml文件替换成使用@Configuration注解标记的类 标记IoC注解:@Component,@Service,@Controller,@Repository&#x20; 标记DI注解:@Autowired @Qualifier @Resource @Value <context:component-scan标签指定注解范围使用@ComponentScan(basePackages = {“com.atguigu.components”})替代 <context:property-placeholder引入外部配置文件使用@PropertySource({“classpath:application.properties”,”classpath:jdbc.properties”})替代 <bean 标签使用@Bean注解和方法实现 IoC具体容器实现选择AnnotationConfigApplicationContext对象 4.6 整合Spring5-Test5搭建测试环境 整合测试环境作用 好处1:不需要自己创建IOC容器对象了 好处2:任何需要的bean都可以在测试类中直接享受自动装配 导入相关依赖 123456789101112<!--junit5测试--><dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>6.0.6</version> <scope>test</scope></dependency> 整合测试注解使用 123456789101112//@SpringJUnitConfig(locations = {"classpath:spring-context.xml"}) //指定配置文件xml@SpringJUnitConfig(value = {BeanConfig.class}) //指定配置类public class Junit5IntegrationTest { @Autowired private User user; @Test public void testJunit5() { System.out.println(user); }} 五、Spring AOP面向切面编程5.1 场景设定和问题复现 准备AOP项目 项目名:spring-aop-annotation pom.xml 123456789101112131415161718192021222324252627282930<dependencies> <!--spring context依赖--> <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.6</version> </dependency> <!--junit5测试--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>6.0.6</version> <scope>test</scope> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency></dependencies> 声明接口 1234567891011121314/** * + - * / 运算的标准接口! */public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); } 接口实现 12345678910111213141516171819202122232425262728293031323334353637383940package com.atguigu.proxy;/** * 实现计算接口,单纯添加 + - * / 实现! 掺杂其他功能! */public class CalculatorPureImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; }} 声明带日志接口实现 新需求: 需要在每个方法中,添加控制台输出,输出参数和输出计算后的返回值! 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package com.atguigu.proxy;/** * 在每个方法中,输出传入的参数和计算后的返回结果! */public class CalculatorLogImpl implements Calculator { @Override public int add(int i, int j) { System.out.println("参数是:" + i + "," + j); int result = i + j; System.out.println("方法内部 result = " + result); return result; } @Override public int sub(int i, int j) { System.out.println("参数是:" + i + "," + j); int result = i - j; System.out.println("方法内部 result = " + result); return result; } @Override public int mul(int i, int j) { System.out.println("参数是:" + i + "," + j); int result = i * j; System.out.println("方法内部 result = " + result); return result; } @Override public int div(int i, int j) { System.out.println("参数是:" + i + "," + j); int result = i / j; System.out.println("方法内部 result = " + result); return result; }} 代码问题分析 代码缺陷 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力 附加功能代码重复,分散在各个业务功能方法中!冗余,且不方便统一维护! 解决思路 &#x20; 核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。 &#x20; 将重复的代码统一提取,并且[[动态插入]]到每个业务方法! 技术困难 解决问题的困难:提取重复附加功能代码到一个类中,可以实现 但是如何将代码插入到各个方法中,我们不会,我们需要引用新技术!!! 5.2 解决技术代理模式 代理模式 二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。 无代理场景: 有代理场景: 生活中的代理: 广告商找大明星拍广告需要经过经纪人 合作伙伴找大老板谈合作要约见面时间需要经过秘书 房产中介是买卖双方的代理 太监是大臣和皇上之间的代理相关术语: 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。(中介) 动词:指做代理这个动作,或这项工作 名词:扮演代理这个角色的类、对象、方法 目标:被代理“套用”了核心逻辑代码的类、对象、方法。(房东)代理在开发中实现的方式具体有两种:静态代理,[动态代理技术] 静态代理 主动创建代理类: 1234567891011121314151617181920212223public class CalculatorStaticProxy implements Calculator { // 将被代理的目标对象声明为成员变量 private Calculator target; public CalculatorStaticProxy(Calculator target) { this.target = target; } @Override public int add(int i, int j) { // 附加功能由代理类中的代理方法来实现 System.out.println("参数是:" + i + "," + j); // 通过目标对象来实现核心业务逻辑 int addResult = target.add(i, j); System.out.println("方法内部 result = " + result); return addResult; } …… 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。 提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。 动态代理 动态代理技术分类 JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(拜把子) cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(认干爹)JDK动态代理技术实现(了解) 代理工程:基于jdk代理技术,生成代理对象 123456789101112131415161718192021222324252627282930313233343536373839404142434445public class ProxyFactory { private Object target; public ProxyFactory(Object target) { this.target = target; } public Object getProxy(){ /** * newProxyInstance():创建一个代理实例 * 其中有三个参数: * 1、classLoader:加载动态生成的代理类的类加载器 * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组 * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法 */ ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * proxy:代理对象 * method:代理对象需要实现的方法,即其中需要重写的方法 * args:method所对应方法的参数 */ Object result = null; try { System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args)); result = method.invoke(target, args); System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result); } catch (Exception e) { e.printStackTrace(); System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage()); } finally { System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕"); } return result; } }; return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); }} 测试代码: 1234567@Testpublic void testDynamicProxy(){ ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl()); Calculator proxy = (Calculator) factory.getProxy(); proxy.div(1,0); //proxy.div(1,1);} 代理总结 代理方式可以解决附加功能代码干扰核心代码和不方便统一维护的问题! 他主要是将附加功能代码提取到代理中执行,不干扰目标核心代码! 但是我们也发现,无论使用静态代理和动态代理(jdk,cglib),程序员的工作都比较繁琐! 需要自己编写代理工厂等! 但是,提前剧透,我们在实际开发中,不需要编写代理代码,我们可以使用[Spring AOP]框架, 他会简化动态代理的实现!!! 5.3 面向切面编程思维(AOP) 面向切面编程思想AOP AOP:Aspect Oriented Programming面向切面编程 AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。 AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。 使用AOP,可以在不修改原来代码的基础上添加新功能。 AOP思想主要的应用场景 AOP(面向切面编程)是一种编程范式,它通过将通用的横切关注点(如日志、事务、权限控制等)与业务逻辑分离,使得代码更加清晰、简洁、易于维护。AOP可以应用于各种场景,以下是一些常见的AOP应用场景: 日志记录:在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在方法执行前、执行后或异常抛出时记录日志。 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的功能,可以在方法开始前开启事务,在方法执行完毕后提交或回滚事务。 安全控制:在系统中包含某些需要安全控制的操作,如登录、修改密码、授权等,可以使用AOP来实现安全控制的功能。可以在方法执行前进行权限判断,如果用户没有权限,则抛出异常或转向到错误页面,以防止未经授权的访问。 性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈并进行优化。可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。 异常处理:系统中可能出现各种异常情况,如空指针异常、数据库连接异常等,可以使用AOP来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志、发送邮件等)。 缓存控制:在系统中有些数据可以缓存起来以提高访问速度,可以使用AOP来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有则返回,否则执行方法并将方法返回值存入缓存中。 动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能。综上所述,AOP可以应用于各种场景,它的作用是将通用的横切关注点与业务逻辑分离,使得代码更加清晰、简洁、易于维护。 AOP术语名词介绍 1-横切关注点 从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。 这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。 AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务、异常等。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。 2-通知(增强) 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。 前置通知:在被代理的目标方法前执行 返回通知:在被代理的目标方法成功结束后执行(寿终正寝) 异常通知:在被代理的目标方法异常结束后执行(死于非命) 后置通知:在被代理的目标方法最终结束后执行(盖棺定论) 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置3-连接点 joinpoint 这也是一个纯逻辑概念,不是语法定义的。 指那些被拦截到的点。在 Spring 中,可以被动态代理拦截目标类的方法 4-切入点 pointcut 定位连接点的方式,或者可以理解成被选中的连接点! 是一个表达式,比如execution(* com.spring.service.impl..(..))。符合条件的每个方法都是一个具体的连接点。 5-切面 aspect 切入点和通知的结合。是一个类。 6-目标 target 被代理的目标对象。 7-代理 proxy 向目标对象应用通知之后创建的代理对象。 8-织入 weave 指把通知应用到目标上,生成代理对象的过程。可以在编译期织入,也可以在运行期织入,Spring采用后者。 5.4 Spring AOP框架介绍和关系梳理 AOP一种区别于OOP的编程思维,用来完善和解决OOP的非核心代码冗余和不方便统一维护问题! 代理技术(动态代理|静态代理)是实现AOP思维编程的具体技术,但是自己使用动态代理实现代码比较繁琐! Spring AOP框架,基于AOP编程思维,封装动态代理技术,简化动态代理技术实现的框架!SpringAOP内部帮助我们实现动态代理,我们只需写少量的配置,指定生效范围即可,即可完成面向切面思维编程的实现! 5.5 Spring AOP基于注解方式实现和细节5.5.1 Spring AOP底层技术组成 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。 cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。 AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。 5.5.2 初步实现 加入依赖123456789101112<!-- spring-aspects会帮我们传递过来aspectjweaver --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.6</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.6</version></dependency> 准备接口1234567891011public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); } 纯净实现类1234567891011121314151617181920212223242526272829303132333435363738394041package com.atguigu.proxy;/** * 实现计算接口,单纯添加 + - * / 实现! 掺杂其他功能! */@Componentpublic class CalculatorPureImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; }} 声明切面类12345678910111213141516171819202122232425262728293031323334package com.atguigu.advice;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;// @Aspect表示这个类是一个切面类@Aspect// @Component注解保证这个切面类能够放入IOC容器@Componentpublic class LogAspect { // @Before注解:声明当前方法是前置通知方法 // value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上 @Before(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))") public void printLogBeforeCore() { System.out.println("[AOP前置通知] 方法开始了"); } @AfterReturning(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))") public void printLogAfterSuccess() { System.out.println("[AOP返回通知] 方法成功返回了"); } @AfterThrowing(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))") public void printLogAfterException() { System.out.println("[AOP异常通知] 方法抛异常了"); } @After(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))") public void printLogFinallyEnd() { System.out.println("[AOP后置通知] 方法最终结束了"); } } 开启aspectj注解支持 xml方式123456789101112<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 进行包扫描--> <context:component-scan base-package="com.atguigu" /> <!-- 开启aspectj框架注解支持--> <aop:aspectj-autoproxy /></beans> 配置类方式1234567@Configuration@ComponentScan(basePackages = "com.atguigu")//作用等于 <aop:aspectj-autoproxy /> 配置类上开启 Aspectj注解支持!@EnableAspectJAutoProxypublic class MyConfig {} 测试效果12345678910111213//@SpringJUnitConfig(locations = "classpath:spring-aop.xml")@SpringJUnitConfig(value = {MyConfig.class})public class AopTest { @Autowired private Calculator calculator; @Test public void testCalculator(){ calculator.add(1,1); }} 输出结果:1234"C:\\Program Files\\Java\\jdk-17\\bin\\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\\Program Files\\JetBrains\\IntelliJ IDEA 2022.3.2\\lib\\idea_rt.jar=65511:D:\\Program Files\\JetBrains\\IntelliJ IDEA 2022.3.2\\bin" -Dfile.encoding=UTF-8 -classpath "C:\\Users\\Jackiechan\\.m2\\repository\\org\\junit\\platform\\junit-platform-launcher\\1.3.1\\junit-platform-launcher-1.3.1.jar;C:\\Users\\Jackiechan\\.m2\\repository\\org\\apiguardian\\apiguardian-api\\1.0.0\\apiguardian-api-1.0.0.jar;C:\\Users\\Jackiechan\\.m2\\repository\\org\\junit\\platform\\junit-platform-engine\\1.3.1\\junit-platform-engine-1.3.1.jar;C:\\Users\\Jackiechan\\.m2\\repository\\org\\junit\\platform\\junit-platform-commons\\1.3.1\\junit-platform-commons-1.3.1.jar;C:\\Users\\Jackiechan\\.m2\\repository\\org\\opentest4j\\opentest4j\\1.1.1\\opentest4j-1.1.1.jar;C:\\Users\\Jackiechan\\.m2\\repository\\org\\junit\\jupiter\\junit-jupiter-engine\\5.3.1\\junit-jupiter-engine-5.3.1.jar;C:\\Users\\Jackiechan\\.m2\\repository\\org\\junit\\jupiter\\junit-jupiter-api\\5.3.1\\junit-jupiter-api-5.3.1.jar;D:\\Program Files\\JetBrains\\IntelliJ IDEA 2022.3.2\\lib\\idea_rt.jar;D:\\Program Files\\JetBrains\\IntelliJ IDEA 2022.3.2\\plugins\\junit\\lib\\junit5-rt.jar;D:\\Program Files\\JetBrains\\IntelliJ IDEA 2022.3.2\\plugins\\junit\\lib\\junit-rt.jar;D:\\javaprojects\\backend-engineering\\part01-spring\\spring-aop-annotation\\target\\test-classes;D:\\javaprojects\\backend-engineering\\part01-spring\\spring-aop-annotation\\target\\classes;D:\\repository\\org\\springframework\\spring-context\\6.0.6\\spring-context-6.0.6.jar;D:\\repository\\org\\springframework\\spring-beans\\6.0.6\\spring-beans-6.0.6.jar;D:\\repository\\org\\springframework\\spring-core\\6.0.6\\spring-core-6.0.6.jar;D:\\repository\\org\\springframework\\spring-jcl\\6.0.6\\spring-jcl-6.0.6.jar;D:\\repository\\org\\springframework\\spring-expression\\6.0.6\\spring-expression-6.0.6.jar;D:\\repository\\org\\junit\\jupiter\\junit-jupiter-api\\5.3.1\\junit-jupiter-api-5.3.1.jar;D:\\repository\\org\\apiguardian\\apiguardian-api\\1.0.0\\apiguardian-api-1.0.0.jar;D:\\repository\\org\\opentest4j\\opentest4j\\1.1.1\\opentest4j-1.1.1.jar;D:\\repository\\org\\junit\\platform\\junit-platform-commons\\1.3.1\\junit-platform-commons-1.3.1.jar;D:\\repository\\org\\springframework\\spring-test\\6.0.6\\spring-test-6.0.6.jar;D:\\repository\\jakarta\\annotation\\jakarta.annotation-api\\2.1.1\\jakarta.annotation-api-2.1.1.jar;D:\\repository\\mysql\\mysql-connector-java\\8.0.25\\mysql-connector-java-8.0.25.jar;D:\\repository\\com\\google\\protobuf\\protobuf-java\\3.11.4\\protobuf-java-3.11.4.jar;D:\\repository\\com\\alibaba\\druid\\1.2.8\\druid-1.2.8.jar;D:\\repository\\javax\\annotation\\javax.annotation-api\\1.3.2\\javax.annotation-api-1.3.2.jar;D:\\repository\\org\\springframework\\spring-aop\\6.0.6\\spring-aop-6.0.6.jar;D:\\repository\\org\\springframework\\spring-aspects\\6.0.6\\spring-aspects-6.0.6.jar;D:\\repository\\org\\aspectj\\aspectjweaver\\1.9.9.1\\aspectjweaver-1.9.9.1.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 com.atguigu.test.AopTest,testCalculator[AOP前置通知] 方法开始了[AOP返回通知] 方法成功返回了[AOP后置通知] 方法最终结束了 5.5.3 获取通知细节信息 JointPoint接口 需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。 要点1:JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息) 要点2:通过目标方法签名对象获取方法名 要点3:通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组1234567891011121314151617181920212223242526272829// @Before注解标记前置通知方法// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表@Before(value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))")public void printLogBeforeCore(JoinPoint joinPoint) { // 1.通过JoinPoint对象获取目标方法签名对象 // 方法的签名:一个方法的全部声明信息 Signature signature = joinPoint.getSignature(); // 2.通过方法的签名对象获取目标方法的详细信息 String methodName = signature.getName(); System.out.println("methodName = " + methodName); int modifiers = signature.getModifiers(); System.out.println("modifiers = " + modifiers); String declaringTypeName = signature.getDeclaringTypeName(); System.out.println("declaringTypeName = " + declaringTypeName); // 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表 Object[] args = joinPoint.getArgs(); // 4.由于数组直接打印看不到具体数据,所以转换为List集合 List<Object> argList = Arrays.asList(args); System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);} 方法返回值 在返回通知中,通过 @AfterReturning注解的returning属性获取目标方法的返回值! 1234567891011121314// @AfterReturning注解标记返回通知方法// 在返回通知中获取目标方法返回值分两步:// 第一步:在@AfterReturning注解中通过returning属性设置一个名称// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参@AfterReturning( value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))", returning = "targetMethodReturnValue")public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) { String methodName = joinPoint.getSignature().getName(); System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);} 异常对象捕捉 在异常通知中,通过@AfterThrowing注解的throwing属性获取目标方法抛出的异常对象 1234567891011121314// @AfterThrowing注解标记异常通知方法// 在异常通知中获取目标方法抛出的异常分两步:// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们@AfterThrowing( value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))", throwing = "targetMethodException")public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) { String methodName = joinPoint.getSignature().getName(); System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());} 5.5.4 切点表达式语法 切点表达式作用 AOP切点表达式(Pointcut Expression)是一种用于指定切点的语言,它可以通过定义匹配规则,来选择需要被切入的目标对象。 切点表达式语法 切点表达式总结 语法细节 第一位:execution( ) 固定开头 第二位:方法访问修饰符 1public private 直接描述对应修饰符即可 第三位:方法返回值 12int String void 直接描述返回值类型 注意: 特殊情况 不考虑 访问修饰符和返回值 execution(* * ) 这是错误语法 execution( *) == 你只要考虑返回值 或者 不考虑访问修饰符 相当于全部不考虑了 第四位:指定包的地址 12345固定的包: com.atguigu.api | service | dao单层的任意命名: com.atguigu.* = com.atguigu.api com.atguigu.dao * = 任意一层的任意命名任意层任意命名: com.. = com.atguigu.api.erdaye com.a.a.a.a.a.a.a ..任意层,任意命名 用在包上!注意: ..不能用作包开头 public int .. 错误语法 com..找到任何包下: *.. 第五位:指定类名称 12345固定名称: UserService任意类名: *部分任意: com..service.impl.*Impl任意包任意类: *..* 第六位:指定方法名称 12语法和类名一致任意访问修饰符,任意类的任意方法: * *..*.* 第七位:方法参数 12345678第七位: 方法的参数描述 具体值: (String,int) != (int,String) 没有参数 () 模糊值: 任意参数 有 或者 没有 (..) ..任意参数的意识 部分具体和模糊: 第一个参数是字符串的方法 (String..) 最后一个参数是字符串 (..String) 字符串开头,int结尾 (String..int) 包含int类型(..int..) 切点表达式案例 1234561.查询某包某类下,访问修饰符是公有,返回值是int的全部方法2.查询某包下类中第一个参数是String的方法3.查询全部包下,无参数的方法!4.查询com包下,以int参数类型结尾的方法5.查询指定包下,Service开头类的私有返回值int的无参数方法 5.5.5 重用(提取)切点表达式 重用切点表达式优点 123456789101112131415161718192021 // @Before注解:声明当前方法是前置通知方法// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上@Before(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")public void printLogBeforeCore() { System.out.println("[AOP前置通知] 方法开始了");}@AfterReturning(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")public void printLogAfterSuccess() { System.out.println("[AOP返回通知] 方法成功返回了");}@AfterThrowing(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")public void printLogAfterException() { System.out.println("[AOP异常通知] 方法抛异常了");}@After(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")public void printLogFinallyEnd() { System.out.println("[AOP后置通知] 方法最终结束了");} 上面案例,是我们之前编写切点表达式的方式,发现, 所有增强方法的切点表达式相同! 出现了冗余,如果需要切换也不方便统一维护! 我们可以将切点提取,在增强上进行引用即可! 同一类内部引用 提取 123// 切入点表达式重用@Pointcut("execution(public int com.atguigu.aop.api.Calculator.add(int,int)))")public void declarPointCut() {} 注意:提取切点注解使用@Pointcut(切点表达式) , 需要添加到一个无参数无返回值方法上即可! 引用 12@Before(value = "declarPointCut()")public void printLogBeforeCoreOperation(JoinPoint joinPoint) { 不同类中引用 不同类在引用切点,只需要添加类的全限定符+方法名即可! 12@Before(value = "com.atguigu.spring.aop.aspect.LogAspect.declarPointCut()")public Object roundAdvice(ProceedingJoinPoint joinPoint) { 切点统一管理 建议:将切点表达式统一存储到一个类中进行集中管理和维护! 123456789101112@Componentpublic class AtguiguPointCut { @Pointcut(value = "execution(public int *..Calculator.sub(int,int))") public void atguiguGlobalPointCut(){} @Pointcut(value = "execution(public int *..Calculator.add(int,int))") public void atguiguSecondPointCut(){} @Pointcut(value = "execution(* *..*Service.*(..))") public void transactionPointCut(){}} 5.5.6 环绕通知环绕通知对应整个 try…catch…finally 结构,包括前面四种通知的所有功能。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 使用@Around注解标明环绕通知方法@Around(value = "com.atguigu.aop.aspect.AtguiguPointCut.transactionPointCut()")public Object manageTransaction( // 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参, // Spring会将这个类型的对象传给我们 ProceedingJoinPoint joinPoint) { // 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组 Object[] args = joinPoint.getArgs(); // 通过ProceedingJoinPoint对象获取目标方法的签名对象 Signature signature = joinPoint.getSignature(); // 通过签名对象获取目标方法的方法名 String methodName = signature.getName(); // 声明变量用来存储目标方法的返回值 Object targetMethodReturnValue = null; try { // 在目标方法执行前:开启事务(模拟) log.debug("[AOP 环绕通知] 开启事务,方法名:" + methodName + ",参数列表:" + Arrays.asList(args)); // 过ProceedingJoinPoint对象调用目标方法 // 目标方法的返回值一定要返回给外界调用者 targetMethodReturnValue = joinPoint.proceed(args); // 在目标方法成功返回后:提交事务(模拟) log.debug("[AOP 环绕通知] 提交事务,方法名:" + methodName + ",方法返回值:" + targetMethodReturnValue); }catch (Throwable e){ // 在目标方法抛异常后:回滚事务(模拟) log.debug("[AOP 环绕通知] 回滚事务,方法名:" + methodName + ",异常:" + e.getClass().getName()); }finally { // 在目标方法最终结束后:释放数据库连接 log.debug("[AOP 环绕通知] 释放数据库连接,方法名:" + methodName); } return targetMethodReturnValue;} 5.5.7 切面优先级设置相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。 优先级高的切面:外面 优先级低的切面:里面 使用 @Order 注解可以控制切面的优先级: @Order(较小的数):优先级高 @Order(较大的数):优先级低 实际意义 实际开发时,如果有多个切面嵌套的情况,要慎重考虑。例如:如果事务切面优先级高,那么在缓存中命中数据的情况下,事务切面的操作都浪费了。 此时应该将缓存切面的优先级提高,在事务操作之前先检查缓存中是否存在目标数据。 5.5.8 CGLib动态代理生效在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。为了证明这一点,我们做下面的测试: 1234567@Servicepublic class EmployeeService { public void getEmpList() { System.out.print("方法内部 com.atguigu.aop.imp.EmployeeService.getEmpList"); }} 测试: 1234567@Autowiredprivate EmployeeService employeeService;@Testpublic void testNoInterfaceProxy() { employeeService.getEmpList();} 没有接口: 有接口: 使用总结: a. 如果目标类有接口,选择使用jdk动态代理 b. 如果目标类没有接口,选择cglib动态代理 c. 如果有接口,接口接值 d. 如果没有接口,类进行接值 5.5.9 注解实现小结 5.6 Spring AOP基于XML方式实现(了解) 准备工作 加入依赖 和基于注解的 AOP 时一样。 准备代码 把测试基于注解功能时的Java类复制到新module中,去除所有注解。 配置Spring配置文件 12345678910111213141516171819202122232425262728293031323334353637383940414243<!-- 配置目标类的bean --><bean id="calculatorPure" class="com.atguigu.aop.imp.CalculatorPureImpl"/> <!-- 配置切面类的bean --><bean id="logAspect" class="com.atguigu.aop.aspect.LogAspect"/> <!-- 配置AOP --><aop:config> <!-- 配置切入点表达式 --> <aop:pointcut id="logPointCut" expression="execution(* *..*.*(..))"/> <!-- aop:aspect标签:配置切面 --> <!-- ref属性:关联切面类的bean --> <aop:aspect ref="logAspect"> <!-- aop:before标签:配置前置通知 --> <!-- method属性:指定前置通知的方法名 --> <!-- pointcut-ref属性:引用切入点表达式 --> <aop:before method="printLogBeforeCore" pointcut-ref="logPointCut"/> <!-- aop:after-returning标签:配置返回通知 --> <!-- returning属性:指定通知方法中用来接收目标方法返回值的参数名 --> <aop:after-returning method="printLogAfterCoreSuccess" pointcut-ref="logPointCut" returning="targetMethodReturnValue"/> <!-- aop:after-throwing标签:配置异常通知 --> <!-- throwing属性:指定通知方法中用来接收目标方法抛出异常的异常对象的参数名 --> <aop:after-throwing method="printLogAfterCoreException" pointcut-ref="logPointCut" throwing="targetMethodException"/> <!-- aop:after标签:配置后置通知 --> <aop:after method="printLogCoreFinallyEnd" pointcut-ref="logPointCut"/> <!-- aop:around标签:配置环绕通知 --> <!--<aop:around method="……" pointcut-ref="logPointCut"/>--> </aop:aspect> </aop:config> 测试 123456789101112@SpringJUnitConfig(locations = "classpath:spring-aop.xml")public class AopTest { @Autowired private Calculator calculator; @Test public void testCalculator(){ System.out.println(calculator); calculator.add(1,1); }} 5.7 Spring AOP对获取Bean的影响理解5.7.1 根据类型装配 bean 情景一 bean 对应的类没有实现任何接口 根据 bean 本身的类型获取 bean 测试:IOC容器中同类型的 bean 只有一个 正常获取到 IOC 容器中的那个 bean 对象 测试:IOC 容器中同类型的 bean 有多个 会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个 情景二 bean 对应的类实现了接口,这个接口也只有这一个实现类 测试:根据接口类型获取 bean 测试:根据类获取 bean 结论:上面两种情况其实都能够正常获取到 bean,而且是同一个对象 情景三 声明一个接口 接口有多个实现类 接口所有实现类都放入 IOC 容器 测试:根据接口类型获取 bean 会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个 测试:根据类获取bean 正常 情景四 声明一个接口 接口有一个实现类 创建一个切面类,对上面接口的实现类应用通知 测试:根据接口类型获取bean 正常 测试:根据类获取bean 无法获取原因分析: 应用了切面后,真正放在IOC容器中的是代理类的对象 目标类并没有被放到IOC容器中,所以根据目标类的类型从IOC容器中是找不到的 情景五 声明一个类 创建一个切面类,对上面的类应用通知 测试:根据类获取 bean,能获取到debug查看实际类型: 5.7.2 使用总结对实现了接口的类应用切面 对没实现接口的类应用切面new 如果使用AOP技术,目标类有接口,必须使用接口类型接收IoC容器中代理组件! 六、Spring 声明式事务6.1 声明式事务概念6.1.1 编程式事务编程式事务是指手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。在 Java 中,通常使用事务管理器(如 Spring 中的 PlatformTransactionManager)来实现编程式事务。 编程式事务的主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响。 123456789101112131415161718192021Connection conn = ...; try { // 开启事务:关闭事务的自动提交 conn.setAutoCommit(false); // 核心操作 // 业务代码 // 提交事务 conn.commit(); }catch(Exception e){ // 回滚事务 conn.rollBack(); }finally{ // 释放数据库连接 conn.close(); } 编程式的实现方式存在缺陷: 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。 6.1.2 声明式事务声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。 开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作! 使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。 区别: 编程式事务需要手动编写代码来管理事务 而声明式事务可以通过配置文件或注解来控制事务。 6.1.3 Spring事务管理器 Spring声明式事务对应依赖 spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等) spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等 Spring声明式事务对应事务管理器接口 我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现! DataSourceTransactionManager类中的主要方法: doBegin():开启事务 doSuspend():挂起事务 doResume():恢复挂起的事务 doCommit():提交事务 doRollback():回滚事务 6.2 基于注解的声明式事务6.2.1 准备工作 准备项目 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970<dependencies> <!--spring context依赖--> <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.6</version> </dependency> <!--junit5测试--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>6.0.6</version> <scope>test</scope> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency> <!-- 数据库驱动 和 连接池--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <!-- spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.6</version> </dependency> <!-- 声明式事务依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>6.0.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.6</version> </dependency></dependencies> 外部配置文件 jdbc.properties 1234atguigu.url=jdbc:mysql://localhost:3306/studbatguigu.driver=com.mysql.cj.jdbc.Driveratguigu.username=rootatguigu.password=root spring配置文件 1234567891011121314151617181920212223242526272829303132333435363738@Configuration@ComponentScan("com.atguigu")@PropertySource("classpath:jdbc.properties")public class JavaConfig { @Value("${atguigu.driver}") private String driver; @Value("${atguigu.url}") private String url; @Value("${atguigu.username}") private String username; @Value("${atguigu.password}") private String password; //druid连接池 @Bean public DataSource dataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean //jdbcTemplate public JdbcTemplate jdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; }} 准备dao/service层 dao 1234567891011121314151617@Repositorypublic class StudentDao { @Autowired private JdbcTemplate jdbcTemplate; public void updateNameById(String name,Integer id){ String sql = "update students set name = ? where id = ? ;"; int rows = jdbcTemplate.update(sql, name, id); } public void updateAgeById(Integer age,Integer id){ String sql = "update students set age = ? where id = ? ;"; jdbcTemplate.update(sql,age,id); }} service 12345678910111213@Servicepublic class StudentService { @Autowired private StudentDao studentDao; public void changeInfo(){ studentDao.updateAgeById(100,1); System.out.println("-----------"); studentDao.updateNameById("test1",1); }} 测试环境搭建 1234567891011121314151617/** * projectName: com.atguigu.test * * description: */@SpringJUnitConfig(JavaConfig.class)public class TxTest { @Autowired private StudentService studentService; @Test public void testTx(){ studentService.changeInfo(); }} 6.2.2 基本事务控制 配置事务管理器 数据库相关的配置 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758/** * projectName: com.atguigu.config * * description: 数据库和连接池配置类 */@Configuration@ComponenScan("com.atguigu")@PropertySource(value = "classpath:jdbc.properties")@EnableTransactionManagementpublic class DataSourceConfig { /** * 实例化dataSource加入到ioc容器 * @param url * @param driver * @param username * @param password * @return */ @Bean public DataSource dataSource(@Value("${atguigu.url}")String url, @Value("${atguigu.driver}")String driver, @Value("${atguigu.username}")String username, @Value("${atguigu.password}")String password){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } /** * 实例化JdbcTemplate对象,需要使用ioc中的DataSource * @param dataSource * @return */ @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } /** * 装配事务管理实现对象 * @param dataSource * @return */ @Bean public TransactionManager transactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); }} 使用声明事务注解@Transactional 12345678910111213141516171819/** * projectName: com.atguigu.service * */@Servicepublic class StudentService { @Autowired private StudentDao studentDao; @Transactional public void changeInfo(){ studentDao.updateAgeById(100,1); System.out.println("-----------"); int i = 1/0; studentDao.updateNameById("test1",1); }} 测试事务效果 123456789101112131415161718/** * projectName: com.atguigu.test * * description: *///@SpringJUnitConfig(locations = "classpath:application.xml")@SpringJUnitConfig(classes = DataSourceConfig.class)public class TxTest { @Autowired private StudentService studentService; @Test public void testTx(){ studentService.changeInfo(); }} 6.2.3 事务属性:只读 只读介绍 对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。 设置方式 12// readOnly = true把当前事务设置为只读 默认是false!@Transactional(readOnly = true) 针对DML动作设置只读模式 会抛出下面异常: Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed @Transactional注解放在类上 生效原则 如果一个类中每一个方法上都使用了 @Transactional 注解,那么就可以将 @Transactional 注解提取到类上。反过来说:@Transactional 注解在类级别标记,会影响到类中的每一个方法。同时,类级别标记的 @Transactional 注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了 @Transactional 注解。 对一个方法来说,离它最近的 @Transactional 注解中的事务属性设置生效。 用法举例 在类级别@Transactional注解中设置只读,这样类中所有的查询方法都不需要设置@Transactional注解了。因为对查询操作来说,其他属性通常不需要设置,所以使用公共设置即可。 然后在这个基础上,对增删改方法设置@Transactional注解 readOnly 属性为 false。 1234567891011121314151617@Service@Transactional(readOnly = true)public class EmpService { // 为了便于核对数据库操作结果,不要修改同一条记录 @Transactional(readOnly = false) public void updateTwice(……) { …… } // readOnly = true把当前事务设置为只读 // @Transactional(readOnly = true) public String getEmpName(Integer empId) { …… } } 6.2.4 事务属性:超时时间 需求 事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。 此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。 概括来说就是一句话:超时回滚,释放资源。 设置超时时间 12345678910111213141516171819202122@Servicepublic class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! */ @Transactional(readOnly = false,timeout = 3) public void changeInfo(){ studentDao.updateAgeById(100,1); //休眠4秒,等待方法超时! try { Thread.sleep(4000); } catch (InterruptedException e) { throw new RuntimeException(e); } studentDao.updateNameById("test1",1); }} 测试超时效果 执行抛出事务超时异常 12345678org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Wed May 24 09:10:43 IRKT 2023 at org.springframework.transaction.support.ResourceHolderSupport.checkTransactionTimeout(ResourceHolderSupport.java:155) at org.springframework.transaction.support.ResourceHolderSupport.getTimeToLiveInMillis(ResourceHolderSupport.java:144) at org.springframework.transaction.support.ResourceHolderSupport.getTimeToLiveInSeconds(ResourceHolderSupport.java:128) at org.springframework.jdbc.datasource.DataSourceUtils.applyTimeout(DataSourceUtils.java:341) at org.springframework.jdbc.core.JdbcTemplate.applyStatementSettings(JdbcTemplate.java:1467) 6.2.5 事务属性:事务异常 默认情况 默认只针对运行时异常回滚,编译时异常不回滚。情景模拟代码如下: 12345678910111213141516171819@Servicepublic class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! */ @Transactional(readOnly = false,timeout = 3) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); }} 设置回滚异常 rollbackFor属性:指定哪些异常类才会回滚,默认是 RuntimeException and Error 异常方可回滚! 123456789101112/** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! */@Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class)public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1);} 设置不回滚的异常 在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。 noRollbackFor属性:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! 1234567891011121314151617181920@Servicepublic class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! */ @Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class,noRollbackFor = FileNotFoundException.class) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); }} 6.2.6 事务属性:事务隔离级别 事务隔离级别 数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括: 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。 事务隔离级别设置 123456789101112131415161718192021222324252627282930313233343536373839package com.atguigu.service;import com.atguigu.dao.StudentDao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Isolation;import org.springframework.transaction.annotation.Transactional;import java.io.FileInputStream;import java.io.FileNotFoundException;/** * projectName: com.atguigu.service */@Servicepublic class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! * isolation = 设置事务的隔离级别,mysql默认是repeatable read! */ @Transactional(readOnly = false, timeout = 3, rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class, isolation = Isolation.REPEATABLE_READ) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); }} 6.2.7 事务属性:事务传播行为 事务传播行为要研究的问题 举例代码: 12345678910111213@Transactionalpublic void MethodA(){ // ... MethodB(); // ...}//在被调用的子方法中设置传播行为,代表如何处理调用的事务! 是加入,还是新事务等!@Transactional(propagation = Propagation.REQUIRES_NEW)public void MethodB(){ // ...} propagation属性 @Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是: 12Propagation propagation() default Propagation.REQUIRED; propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供: 名称 含义 REQUIRED &#xA;默认值 如果父方法有事务,就加入,如果没有就新建自己独立! REQUIRES_NEW 不管父方法是否有事务,我都新建事务,都是独立的! 测试 声明两个业务方法12345678910111213141516171819202122232425262728293031323334353637383940@Servicepublic class StudentService { @Autowired private StudentDao studentDao; /** * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间! * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚! * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内! * isolation = 设置事务的隔离级别,mysql默认是repeatable read! */ @Transactional(readOnly = false, timeout = 3, rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class, isolation = Isolation.REPEATABLE_READ) public void changeInfo() throws FileNotFoundException { studentDao.updateAgeById(100,1); //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx"); studentDao.updateNameById("test1",1); } /** * 声明两个独立修改数据库的事务业务方法 */ @Transactional(propagation = Propagation.REQUIRED) public void changeAge(){ studentDao.updateAgeById(99,1); } @Transactional(propagation = Propagation.REQUIRED) public void changeName(){ studentDao.updateNameById("test2",1); int i = 1/0; }} 声明一个整合业务方法12345678910111213@Servicepublic class TopService { @Autowired private StudentService studentService; @Transactional public void topService(){ studentService.changeAge(); studentService.changeName(); }} 添加传播行为测试123456789101112131415@SpringJUnitConfig(classes = AppConfig.class)public class TxTest { @Autowired private StudentService studentService; @Autowired private TopService topService; @Test public void testTx() throws FileNotFoundException { topService.topService(); }} 注意: 在同一个类中,对于@Transactional注解的方法调用,事务传播行为不会生效。这是因为Spring框架中使用代理模式实现了事务机制,在同一个类中的方法调用并不经过代理,而是通过对象的方法调用,因此@Transactional注解的设置不会被代理捕获,也就不会产生任何事务传播行为的效果。 其他传播行为值(了解) Propagation.REQUIRED:如果当前存在事务,则加入当前事务,否则创建一个新事务。 Propagation.REQUIRES_NEW:创建一个新事务,并在新事务中执行。如果当前存在事务,则挂起当前事务,即使新事务抛出异常,也不会影响当前事务。 Propagation.NESTED:如果当前存在事务,则在该事务中嵌套一个新事务,如果没有事务,则与Propagation.REQUIRED一样。 Propagation.SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务方式执行。 Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,挂起该事务。 Propagation.MANDATORY:必须在一个已有的事务中执行,否则抛出异常。 Propagation.NEVER:必须在没有事务的情况下执行,否则抛出异常。 七、Spring核心掌握总结 核心点 掌握目标 spring框架理解 spring家族和spring framework框架 spring核心功能 ioc/di , aop , tx spring ioc / di 组件管理、ioc容器、ioc/di , 三种配置方式 spring aop aop和aop框架和代理技术、基于注解的aop配置 spring tx 声明式和编程式事务、动态事务管理器、事务注解、属性","categories":[{"name":"知识","slug":"知识","permalink":"http://example.com/categories/%E7%9F%A5%E8%AF%86/"}],"tags":[{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"},{"name":"学习","slug":"学习","permalink":"http://example.com/tags/%E5%AD%A6%E4%B9%A0/"}]},{"title":"python进阶","slug":"python进阶","date":"2022-10-29T16:00:00.000Z","updated":"2023-03-03T12:05:03.392Z","comments":true,"path":"2022/10/30/python进阶/","link":"","permalink":"http://example.com/2022/10/30/python%E8%BF%9B%E9%98%B6/","excerpt":"python入门后我开始学习进阶知识 这些是我写的小项目,里面涉及一些知识点,算是实践练习了","text":"python入门后我开始学习进阶知识 这些是我写的小项目,里面涉及一些知识点,算是实践练习了 仅作为参考笔记阅读。 python进阶学习序列应用—猜单词游戏随机抽选:random轮子使用 猜单词游戏: 1234567891011121314151617181920212223242526import random #------------random,随机选择模块def guess(): #-------------定义猜单词函数,便于执行 words=("python","juice","easy","difficult","answer","continue","phone","hello","pose","game") #------录入单词 newword="" #-----空字符 word=random.choice(words) #---------随机挑一个单词 recordword=word #—--------保存单词,猜的时候对比 while word: s=random.randrange(len(word)) #-----根据word长度,产生word随机位置 newword+=word[s] #-----s位置的字母组合到乱序后的单词 word=word[:s]+word[(s+1):] #-----原位置字母用切片删除。 print("乱序后的单词:",newword) x=str(input("让你猜:")) if x==recordword: print("真棒,你猜对了") else: print("可惜,猜错了")guess() #--------首次执行while True: #--------------进入循环 y=input("是否继续?(T/N):") if y=="T" or y=="y": guess() else: print("下次再见~") break 面向对象设计应用————发牌游戏类定义 简单来说就是去定义变量归属。 就好比 高二: Xraytf 阿灯 ------------ 调用的话就可以在高二这个类里面找 --- (学过c的可以简单的看为结构体) 基本样式代码 1234class Person num=1 #成员变量(属性) def sayHello(self): #成员函数 print("hello") 构造函数,实例属性,类属性。 基本样式代码 1234567891011121314151617181920class Person: num=1 #类属性 def __init__(self,str,n): #构造函数 self.name=str #实例属性 self.age=n def SayHello(self): #成员函数 print("Hello") def PrintName(self): #成员函数 print("姓名:",self.name,"年龄:",self.age) def PrintNum(self): #成员函数 print(Person.num) #由于是类属性,所以不写self.num#主程序P1=Person("夏敏",42)P2=Person("王琳",36)P1.PrintName()P2.PrintName()Person.num=2 #修改类属性P1.PrintNum()P2.PrintNum() 公有方法、私有方法、静态方法的定义和调用示例 基本样式代码 1234567891011121314151617181920212223242526class Person: num=0 #类属性 def __init__(self,str,n,w): #构造函数 self.name=str #对象实例属性(成员) self.age=n self.__weight=w #定义私有属性__weight Person.num+=1 def __outputWeight(self): #定义私有方法__outputWeight print("体重:",self.__weight) #访问私有属性 def PrintName(self): #定义公有方法(成员函数) print("姓名:",self.name,"年龄:",self.age,end=" ") self.__outputWeight( ) #调用私有方法 def PrintNum(self): #定义公有方法(成员函数) print(Person.num) #由于是类属性,所以不写self.num @ staticmethod def getNum(): #定义静态方法getNum return Person.nump1=Person("夏敏",42,120)p2=Person("张海",39,80)#P1.outputWeight()#错误'Person'object has no attribute 'outputWeight'p1.PrintName()p2.PrintName()Person.PrintName(p2)print("人数:",Person.getNum())print("人数:",p1.getNum()) 类的继承—-#定义基类:Person类 基本样式代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546import typesclass Person: def __init__(self,name=" ",age=20,sex="man"): self.setName(name) self.setAge(age) self.setSex(sex) def setName(self,name): if type(name)!=str: #内置函数type()返回被测对象的数据类型 print("姓名必须是字符串.") return self.__name=name def setAge(self,age): if type(age)!=int: print("年龄必须是整形.") return self.__age=age def setSex(self,sex): if sex!='男'and sex!="女": print("性别输入错误") return self.__sex=sex def show(self): print("姓名:",self.__name,"年龄:",self.__age,"性别:",self.__sex)#定义子类(Student类),其中增加一个入学年份私有属性(数据成员)class Student(Person): def __init__(self, name=" ", age=20, sex="man",schoolyear=2016): ########调用基类构造方法初始化基类的私有数据成员 super(Student,self).__init__(name,age,sex) #######------Person.__init__(self,name,age,sex) #######------也可以这样初始化基类私有数据成员 self.setSchoolyear(schoolyear) #初始化派生类的数据成员 def setSchoolyear(self,schoolyear): self.__schoolyear=schoolyear def show(self): Person.show(self) #调用基类show()方法 #######------super(Student,self).show() #也可以这样调用基类show()方法 print("入学年份:",self.__schoolyear)#主程序if __name__=="__main__": zhangsan=Person("张三",19,"男") zhangsan.show() lisi=Student("李四",18,"男",2015) lisi.show() lisi.setAge(20) #调用继承的方法修改年龄 lisi.show() 发牌游戏—–程序设计步骤—–设计类 基本样式代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#Card类class Card(): '''A play card''' RANKS=["A","2","3","4","5","6","7","8","9","10","J","Q","K"] SUITS=["梅","方","红","黑"] def __init__(self,rank,suit,face_up=True): self.rank=rank #指的是牌面1~3 self.suit=suit #suit指的是花色 self.is_face_up=face_up def __str__(self): #重写print()方法,打印一张牌的信息 if self.is_face_up: rep=self.suit+self.rank else: rep="XX" return rep def pic_order(self): #牌的顺序号 if self.rank=="A": FaceNum=1 elif self.rank=="J": FaceNum=11 elif self.rank=="Q": FaceNum=12 elif self.rank=="K": FaceNum=13 else: FaceNum=int(self.rnak) if self.suit=="梅": Suit=1 elif self.suit=="方": Suit=2 elif self.suit=="红": Suit=3 else: Suit=4 return(Suit-1)*13+FaceNum def flip(self): #翻牌方法 self.is_face_up=not self.is_face_up#Hand类class Hand( ): """A hand of playing cards""" def __init__(self): self.cards=[] #cards列表变量存储牌手的牌 def __str__(self): #重写print()方法,打印出牌手的所有牌 if self.cards: rep="" for card in self.cards: rep+=str(card)+"\\t" else: rep="无牌" return rep def clear(self): #清空拍手的牌 self.cards=[] def add(self,card): #增加牌 self.cards.append(card) def qive(self,card,other_hand): #把一张牌给别的牌手 self.cards.remove(card) other_hand.add(card)#Poke类class Poke(Hand): """A deck of playing cards""" def populate(self): #生成一副牌 for suit in Card.SUITS: for rank in Card.RANKS: self.add(Card(rank,suit)) def shuffle(self): #洗牌 import random random.shuffle(self.cards) #打乱牌的顺序 def deal(self,hands,per_hand=13): #发牌,默认发给每个牌手13张牌 for rounds in range(per_hand): for hand in hands: if self.cards: top_card=self.cards[0] self.cards.remove(top_card) hand.add(top_card) else: print("不能再发牌了,牌已经发完") 调用类发牌的主程序 主程序 1234567891011121314#主程序if __name__=="__main__": print("This is a module with classes for playing cards") players=[Hand(),Hand(),Hand(),Hand()] poke1=Poke() poke1.populate() #生成一副牌 poke1.shuffle() #洗牌 poke1.deal(players,13) #发给每个牌手13张牌 n=1 for hand in players: print("牌手",n,end=":") print(hand) n=n+1 input("\\npress the enter key to exit") python图形界面设计————猜数字游戏就是创建窗口,走出终端,在窗口内实现各种功能 创建windows窗口 基本样式代码 123456from tkinter import *win=Tk() #创建windows窗口对象win.title("我的第一个GUI程序") #设置窗口标题win.geometry("800x600") #设置初始大小win.configure(bg="#0050b3") #设置背景颜色 win.mainloop() #进入消息循环,也就是显示窗口 几何布局管理器 pack几何布局管理器 1234567891011from cProfile import labelimport tkinterroot=tkinter.Tk()label=tkinter.Label(root,text="hello world")root.geometry("800x600")label.pack()button1=tkinter.Button(root,text="BUTTON1") #创建文本是BUTTON1的button组件button1.pack(side=tkinter.LEFT) #将BUTTON1组件添加到窗口显示,左靠停button2=tkinter.Button(root,text="BUTTON2") #创建文本是BUTTON2的button组件button2.pack(side=tkinter.RIGHT) #将BUTTON2组件添加到窗口显示,右靠停root.mainloop() grid几何布局管理器 123456789101112131415161718192021222324252627282930from tkinter import *root=Tk()root.geometry("400x400+280+280") #200x200是初始化大小,280,280是初始化时位置root.title("计算器示例")#网格布局L1=Button(root,text='1',width=5,bg="yellow") #数字,大小,背景色L2=Button(root,text="2",width=5)L3=Button(root,text="3",width=5)L4=Button(root,text="4",width=5)L5=Button(root,text="5",width=5,bg="#0050b3")L6=Button(root,text="6",width=5)L7=Button(root,text="7",width=5)L8=Button(root,text="8",width=5)L9=Button(root,text="9",width=5,bg="yellow")L0=Button(root,text="0",width=5)Lp=Button(root,text=".",width=5)L1.grid(row=0,column=0) #放置位置----第0行0列L2.grid(row=0,column=1)L3.grid(row=0,column=2)L4.grid(row=1,column=0)L5.grid(row=1,column=1)L6.grid(row=1,column=2)L7.grid(row=2,column=0)L8.grid(row=2,column=1)L9.grid(row=2,column=2)L0.grid(row=3,column=0,columnspan=2,sticky=E+W) #跨两列,左右紧贴Lp.grid(row=3,column=2,sticky=E+W) #左右紧贴root.mainloop() place几何布局管理器 123456789101112from tkinter import *root=Tk()root.title("登录页面")root["width"]=200;root["height"]=80Label(root,text="用户名",width=6).place(x=1,y=1)Entry(root,width=20).place(x=45,y=1)Label(root,text="密码",width=6).place(x=1,y=20)Entry(root,width=20,show='*').place(x=45,y=20)Button(root,text='登录',width=8).place(x=40,y=40)Button(root,text='取消',width=8).place(x=110,y=40)root.mainloop() tkinter组件 列表点击 列表读取,添加,使用 1234567891011121314151617181920212223from tkinter import *root=Tk()def callbutton1(): for i in listb.curselection(): #遍历选中项 listb2.insert(0,listb.get(i)) #添加到右侧列表栏def callbutton2(): for i in listb2.curselection(): #遍历选中项 listb2.delete(i) #从列表栏删除li=['C','python','php','html','SQL','java']listb=Listbox(root) #创建两个列表框组件listb2=Listbox(root) for item in li: listb.insert(0,item)listb.grid(row=0,column=0,rowspan=2) #将列表框放左边窗口对象中b1=Button(root,text="添加>>",command=callbutton1,width=20) #创建按钮,读取函数实现添加和删除,设置大小b2=Button(root,text="删除<<",command=callbutton2,width=20)b1.grid(row=0,column=1,rowspan=1) #显示b2.grid(row=1,column=1,rowspan=2)listb2.grid(row=0,column=2,rowspan=2)root.mainloop() 复选框 复选框Checkbutton演示 123456789101112import tkinterroot=tkinter.Tk()c=tkinter.IntVar()a=tkinter.IntVar()root.geometry("400x400")c.set(2)a.set(2)check=tkinter.Checkbutton(root,text="喜 欢",variable=c,onvalue=1,offvalue=2)check.pack()check=tkinter.Checkbutton(root,text="芜 湖",variable=a,onvalue=1,offvalue=2)check.pack()root.mainloop() 单选按钮 单选按钮Radiobutton演示 12345678910111213141516171819import tkinterroot=tkinter.Tk()r=tkinter.StringVar()root.geometry("400x400")r.set("1")radio=tkinter.Radiobutton(root,variable=r,value='1',text='中国')radio.pack()radio=tkinter.Radiobutton(root,variable=r,value='2',text='美国')radio.pack()radio=tkinter.Radiobutton(root,variable=r,value='3',text='日本')radio.pack()radio=tkinter.Radiobutton(root,variable=r,value='4',text='加拿大')radio.pack()radio=tkinter.Radiobutton(root,variable=r,value='5',text='韩国')radio.pack()root.mainloop()print(r.get()) 菜单 菜单组件 1234567891011from tkinter import *root=Tk()def hello(): print("请点击主菜单")m=Menu(root)for item in ['文件','编辑','视图']: m.add_command(label=item,command=hello)root['menu']=mroot.mainloop() 消息窗口 消息窗口 1234567891011121314151617181920212223242526272829303132333435363738import tkinter as tkfrom tkinter import messagebox as msgboxdef btn1_clicked(): msgbox.showinfo("Info","Showinfo test.")def btn2_clicked(): msgbox.showwarning("warning","showwarining test")def btn3_clicked(): msgbox.showerror("Error","Showerror test.")def btn4_clicked(): msgbox.askquestion("question","Askquestion test.")def btn5_clicked(): msgbox.askokcancel("OkCancel","askokcancel test.")def btn6_clicked(): msgbox.askyesno("YesNo","askyesno test.")def btn7_clicked(): msgbox.askretrycancel("Retry","askretrycancel test.")root=tk.Tk()root.geometry("400x400")root.title("MsgBox Test")btn1=tk.Button(root,text="showinfo",command=btn1_clicked)btn1.pack(fill=tk.X)btn1=tk.Button(root,text="showwarning",command=btn2_clicked)btn1.pack(fill=tk.X)btn1=tk.Button(root,text="showerror",command=btn3_clicked)btn1.pack(fill=tk.X)btn1=tk.Button(root,text="askquestion",command=btn4_clicked)btn1.pack(fill=tk.X)btn1=tk.Button(root,text="askokcancel",command=btn5_clicked)btn1.pack(fill=tk.X)btn1=tk.Button(root,text="askyesno",command=btn6_clicked)btn1.pack(fill=tk.X)btn1=tk.Button(root,text="askretrycancel",command=btn7_clicked)btn1.pack(fill=tk.X)root.mainloop() 框架,分区 框架(Frame)组件 123456789101112131415161718192021222324from tkinter import *root=Tk()root.title("使用Frame组件的例子")root.geometry("400x400")f1=Frame(root) #创建第一个Frame组件f1.pack()f2=Frame(root) #创建第二个Frame组件f2.pack() f3=LabelFrame(root,text='第三个Frame')f3.pack(side=BOTTOM) #社会在窗口底部redbutton=Button(f1,text="Red",fg="red")redbutton.pack(side=LEFT)brownbutton=Button(f1,text="Brown",fg='brown')brownbutton.pack(side=LEFT)bluebutton=Button(f1,text="Blue",fg='blue')bluebutton.pack(side=LEFT)blackbutton=Button(f2,text='Black',fg='black')blackbutton.pack()greenbutton=Button(f3,text='Green',fg="Green")greenbutton.pack()root.mainloop() 刷新Frame 制作GUI图形界面 123456789101112131415161718192021from tkinter import *root=Tk()colors=('red','orange','yellow','green','blue','purple')root.geometry('400x400')f=Frame(root,height=400,width=400)f.color=0f['bg']=colors[f.color] #设置框架背景色lab1=Label(f,text='0')lab1.pack()def foo(): f.color=(f.color+1)%(len(colors)) lab1['bg']=colors[f.color] lab1['text']=str(int(lab1['text'])+1) f.after(500,foo) #每隔500毫秒就执行foo函数刷新屏幕f.pack()f.after(500,foo)root.mainloop() 用刷新Frame实现电子广告 after()实现移动电子广告 123456789101112131415161718from tkinter import *root=Tk()f=Frame(root,height=200,width=200)lab1=Label(f,text="欢迎")x=0def foo(): global x x=x+10 if x>200: x=0 lab1.place(x=x,y=0) f.after(500,foo) #-------每隔500毫秒就执行foo函数刷新屏幕f.pack()f.after(500,foo)root.mainloop() tkinter字体 Font对象设置标签label的字体 123456789#Font创建字体from tkinter import *import tkinter.fontroot=Tk()#指定字体名称,大小,样式ft=tkinter.font.Font(family='Fixdsy',size=20,weight='bold')Label(root,text='hello sticky',font=ft).grid()root.mainloop() 通过tkFont.families函数可以返回所有可用的字体 123456from tkinter import *import tkinter.fontroot=Tk()print(tkinter.font.families()) #事件类型 1234 <Button-1> #按下鼠标左键<KeyPrees-A> #按下键盘A键<Control-Shift=KeyPress-A> #同时按下Control,Shift,A三个键 事件类型 1234567891011121314151617181920212223242526from tkinter import *root=Tk()root.geometry('400x400')def printRect(event): print('rectangle左键事件')def printRect2(event): print('rectangle右键事件')def printLine(event): print('Line事件')cv=Canvas(root,bg='yellow') #创建一个Canvas,设置其背景为白色rt1=cv.create_rectangle( 10,10,110,110, width=8,tags='r1')cv.tag_bind('r1','<Button-1>',printRect) #绑定item与鼠标左键事件cv.tag_bind('r1','<Button-3>',printRect2) #绑定item与鼠标右键事件#创建一个line,并将其tags设置为'r2'cv.create_line(180,70,280,70,width=10,tags='r2')cv.tag_bind('r2','<Button-1>',printLine)cv.pack()root.mainloop() 键盘事件 触发KeyPress键盘事件 123456789101112from tkinter import * root=Tk() #实例化Tkinterdef printkey(event): #定义的函数监听键盘事件 print('你按下了:' + event.char)entry=Entry(root) #实例化一个单行输入框#为输入框绑定按键监听事件<KeyPress>监听任何按键#<KeyPress-x>监听某键x,如大写的A<KeyPress-A>、回车<KeyPress-Return>entry.bind('<KeyPress>',printkey)entry.pack()root.mainloop() 鼠标事件 获取鼠标单击标签Label时的鼠标事件示例 1234567891011121314from tkinter import *root=Tk()root.geometry("400x400")def leftClick(event): #定义的函数监听鼠标事件 print("x轴坐标:",event.x) print("y轴坐标",event.y) print("相对于屏幕左上角x轴坐标:",event.x_root) print("相对于屏幕左上角y轴坐标:",event.y_root)lab=Label(root,text="hello") #实例化一个Label组件lab.pack()#为Label绑定鼠标监听事件lab.bind("<Button-1>",leftClick)root.mainloop() 猜数字游戏–程序设计步骤 引入模块及设计函数 引入模块,设计函数 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859#导入相应模块import tkinter as tk #窗口import sys #系统,提供接口import random #随机import re #通过正则表达式对字符串进⾏匹配number = random.randint(0,1024) #随即在1~1024选数running = Truenum = 0 #猜的次数nmaxn = 1024 #提示 猜测范围的最大数nminn = 0 #提示 猜测范围的最小数def eBtnClose(event): #关闭按钮事件函数 root.destroy()def eBtnGuess(event): #猜按钮事件函数 global nmaxn #全局变量 global nminn global num global running #修改缺陷:用户答对了,提示标签还提示信息 Edit by Hongten 2013-09-09 #即用户在答对了以后,提示标签不应该再随着用户点击'猜'按钮而变化 if running: val_a = int(entry_a.get()) #获取猜的数字并转换成数字 if val_a == number: labelqval("恭喜答对了!") num+=1 running = False numGuess() #显示猜的次数 elif val_a < number: #猜小了 if val_a > nminn: nminn = val_a #修改猜测范围的最小数 num+=1 label_tip_min.config(label_tip_min,text=nminn) labelqval("小了哦") else: if val_a < nmaxn: nmaxn = val_a #修改猜测范围的最大数 num+=1 label_tip_max.config(label_tip_max,text=nmaxn) labelqval("大了哦") else: labelqval('你已经答对啦...')#修改提示标签文字来显示猜的次数def numGuess(): if num == 1: labelqval('我靠!一次答对!') elif num < 10: labelqval('= =十次以内就答对了,很棒。。。尝试次数:'+str(num)) elif num < 50: labelqval('还行哦尝试次数:'+str(num)) else: labelqval('好吧。。。。。您都试了超过50次了。。。。尝试次数:'+str(num))def labelqval(vText): label_val_q.config(label_val_q,text=vText) #修改提示标签文字 主程序 主程序实现游戏的窗体界面 12345678910111213141516171819202122232425262728293031323334353637383940#以下是主程序实现游戏的窗体界面root = tk.Tk(className="比大小游戏")root.geometry("400x90+200+200")line_a_tip = tk.Frame(root)label_tip_max = tk.Label(line_a_tip,text=nmaxn)label_tip_min = tk.Label(line_a_tip,text=nminn)label_tip_max.pack(side = "top",fill = "x")label_tip_min.pack(side = "bottom",fill = "x")line_a_tip.pack(side = "left",fill = "y")line_question = tk.Frame(root)label_val_q = tk.Label(line_question,width="80") #提示标签label_val_q.pack(side = "left")line_question.pack(side = "top",fill = "x")line_input = tk.Frame(root)entry_a = tk.Entry(line_input,width="40") #单行数字文本框btnGuess = tk.Button(line_input,text="猜") #猜按钮entry_a.pack(side = "left")entry_a.bind('<Return>',eBtnGuess) #绑定事件btnGuess.bind('<Button-1>',eBtnGuess) #猜按钮btnGuess.pack(side = "left")line_input.pack(side = "top",fill = "x")line_btn = tk.Frame(root)btnClose = tk.Button(line_btn,text="关闭") #关闭按钮btnClose.bind('<Button-1>',eBtnClose)btnClose.pack(side="left")line_btn.pack(side = "top")labelqval("请输入0到1024之间任意整数:")entry_a.focus_set()print(number)root.mainloop() Tkinter图形绘制——图形版发牌程序终于到图像进度了——ohhh! Canvas图形绘制技术 画布组件 创建画布 12345678#创建一个背景为白色,宽度为300,高度为120的ecanvas画布from tkinter import *root=Tk()cv=Canvas(root,bg='white',width=300,height=120) #绘制直线cv.create_line(10,10,100,80,width=2,dash=7)cv.pack() #显示画布root.mainloop() 图形对象 绘制图形对象 1234567891011#在canvas画布上可以绘制各种图形对象,常用绘制函数如下:———— create_arc():绘制圆弧———— create_line():绘制直线———— create_bitmap():绘制位图———— create_image():绘制位图图像———— create_oval():绘制椭圆———— create_polygon():绘制多边形———— create_window():绘制子窗口———— create_text():创建一个文字对象 示例/利用属性tags 12345678910111213141516#使用属性tas设置图形对象标记的示例from tkinter import *root=Tk()#创建一个canvas,设置其背景色为白色cv=Canvas(root,bg='white',width=200,height=200)#使用tags指定给第一个举行指定三个tagrt=cv.create_rectangle(10,10,110,110,tags=('r1','r2','r3'))cv.pack()cv.create_rectangle(20,20,80,80,tags='r3') #使用tags为第二个矩形指定一个tag#将所有与tag('r3')绑定的item边框颜色设置为蓝色for item in cv.find_withtag('r3'): cv.itemconfig(item,outline='blue')root.mainloop() 圆弧图像 绘制圆弧 1234567891011121314151617181920#创建圆弧from tkinter import *root=Tk()#创建一个Canvas,设置其背景色为白色cv=Canvas(root,bg='white')cv.create_arc((19,10,110,110),) # 使用默认参数创建一个圆弧,结果为90度的扇形d={1:PIESLICE,2:CHORD,3:ARC}for i in d: #使用三种样式,分别创建扇形,弓形和弧形 cv.create_arc((10,10+60*i,110,110+60*i),style=d[i]) print(i,d[i])#使用start/extent指定圆弧起始角度与偏移角度cv.create_arc( (150,150,250,250), start=10, #指定起始角度 extent=120 #指定角度偏移量(逆指针))cv.pack()root.mainloop() 绘制线条line=canvas.create_line(x0,y0,x1,y1, …, xn,yn,选项)参数x0,y0.。。都是线段的端点 绘制线条 123456789101112#绘制线条#使用create_line()方法绘制线条from tkinter import *root=Tk()cv=Canvas(root,bg='white',width=200,height=100)cv.create_line(10,10,100,10,arrow='none') #绘制没箭头的线段cv.create_line(10,20,100,20,arrow='first') #绘制起点有箭头的线段cv.create_line(10,30,100,30,arrow='last') #绘制终点有箭头的线段cv.create_line(10,40,100,40,arrow='both') #绘制两端有箭头的线段cv.create_line(10,50,100,100,width=3,dash=7) #绘制虚线cv.pack()root.mainloop() 绘制矩形 绘制矩形 123456789101112#绘制矩形#使用creat_rectangle()方法创建举行对象的示例from tkinter import *root=Tk()#创建一个Canvas,设置其背景色为白色cv=Canvas(root,bg='white',width=200,height=100)cv.create_rectangle(10,10,110,110,width=2,fill='red')#指定矩形的填充色为红色,宽度为2cv.create_rectangle(120,20,180,80,outline='green')#指定矩形的边框颜色为绿色cv.pack()root.mainloop() 绘制多边形 绘制多边形 12345678910111213#绘制多边形#创建三角形,正方形,对顶三角形对象的示例from tkinter import *root=Tk()cv=Canvas(root,bg='white',width=300,height=100)cv.create_polygon(35,10,10,60,60,60,outline='blue',fill='red',width=2) #等腰三角形cv.create_polygon(70,10,120,10,120,60,outline='blue',fill='white',width=2) #直角三角形cv.create_polygon(130,10,180,10,180,60,130,60,width=4) #黑色填充正方形cv.create_polygon(190,10,240,10,190,60,240,60,width=1) #对顶三角形cv.pack()root.mainloop() 绘制圆形 绘制圆形 1234567891011#绘制椭圆#绘制椭圆和圆形的示例from tkinter import *root=Tk()cv=Canvas(root,bg='white',width=200,height=100)cv.create_oval(10,10,100,50,outline='blue',fill='red',width=2) #椭圆cv.create_oval(100,10,190,100,outline='blue',fill='red',width=2) #圆形cv.pack()root.mainloop() 绘制文字 绘制文字 12345678910#绘制文字#创建文本的示例from tkinter import *root=Tk()cv=Canvas(root,bg='white',width=200,height=100)cv.create_text((10,10),text='hello,python',fill='red',anchor='nw') #依次是,坐标,文本内容,文本颜色,控制文字对象的位置,比如nw就是左上对齐。cv.create_text((200,50),text='你好,python',fill='blue',anchor='se')cv.pack()root.mainloop() 绘制位图和图像 绘制位图和图像 关于图像,有修改,移动,删除,缩放等程序设定,都放上去比较臃肿,就先不写了,也不算难,就是图像的扩展,去找一下资料吧,这里就先提供制作图像和位图的示例代码。 123456789101112131415#绘制位图和图像#绘制图像示例from tkinter import *root=Tk()cv=Canvas(root)img1=PhotoImage(file='图形相对路径')#比如 img1=PhotoImage(file='C:\\\\aa.png') 获取这个图片的图形cv.create_image((100,100),Image=img1) #绘制这个图形d={1:'error',2:'info',3:'question',4:'hourglass',5:'questhead',6:'warning',7:'gray12',8:'gray25',9:'gray50',10:'gray75'}#字典#以下遍历字典会之python内置的位图for i in d: cv.create_bitmap((20*i,20),bitmap=d[i])cv.pack()root.mainloop() Tkinter图形绘制——图形发牌程序 主程序 主程序代码和基本解释 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869#图形版发牌程序导入相关模块的代码如下from tkinter import *import random#假设包含52张牌,不包括大小王n=52#gen_pocker(n)函数实现对n张牌进行洗牌。方法是随机产生两个下标,将此下标的列表元素交换,达到洗牌目的。列表元素存储的是某张牌(实际上是牌的编号)def gen_pocker(n): x=100 while(x>0): x=x-1 p1=random.randint(0,n-1) p2=random.randint(0,n-1) t=pocker[p1] pocker[p1]=pocker[p2] pocker[p2]=t return pocker#以下是主程序#将要发的52张牌,按照梅花0~12,方块13~25,红桃26~38,黑桃39~51的顺序编号并存储在pocker列表(未洗牌之前)pocker=[i for i in random(n)]#调用gen_pocker(n)函数实现对n张牌的洗牌pocker=gen_pocker(n) #实现对n张牌的洗牌print(pocker)(player1,player2,player3,player4)=([],[],[],[]) #四位牌手各自手中牌的图片列表(p1,p2,p3,p4)=([],[],[],[]) #四位牌手各自手中拍得编号列表root=Tk()#创建一个Canvas,设置其背景色为白色cv=Canvas(root,bg='white',width=700,height=600)#将要发的52张牌的图片,按梅花0~12,方块13~25,红桃26~38,黑桃39~51的顺序编号,存储到扑克牌的图片imgs列表中。也就是说,imgs[10]存储梅花A的图片“1-1.gif”,imgs[1]存储梅花2的图片”1-2gif“,imgs[14]存储方块2的图片”2-2.gif“,依次类推。目的是让程序可以根据牌的编号找到对应的图片。imgs=[]for i in range(1,5): for j in range(1,14): imgs.insert((i-1)*13+(j-1),PhotoImage(file=str(i)+'-'+str(j)+'. gif'))#实现每轮发4张牌,每位牌手发一张,总计13轮发牌,每位牌手最终各有13张牌。for x in range(13): #13轮发牌 m=x*4 p1.append(pocker[m]) p2.append(pocker[m+1]) p3.append(pocker[m+2]) p4.append(pocker[m+3])#牌手对牌进行排序,就相当于理牌,使同花色牌连在一起p1.sort() #牌手对牌进行排序p2.sort()p3.sort()p4.sort()#根据每位牌手手中的编号绘制对应的图片进行显示for x in range(0,13): img=imgs[p1[x]] player1.append(cv.create_image((200+20*x,80),image=img)) img=imgs[p2[x]] player2.append(cv.create_image((100,150+20*x),image=img)) img=imgs[p3[x]] player3.append(cv.create_image((200+20*x,500),image=img)) img=imgs[p4[x]] player4.append(cv.create_image((560,150+20*x),image=img))print("player1:",player1)print("player2:",player2)print("player3:",player3)print("player4:",player4)cv.pack()root.mainloop()","categories":[{"name":"知识","slug":"知识","permalink":"http://example.com/categories/%E7%9F%A5%E8%AF%86/"}],"tags":[{"name":"学习","slug":"学习","permalink":"http://example.com/tags/%E5%AD%A6%E4%B9%A0/"},{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"}]},{"title":"python","slug":"python之旅","date":"2022-10-14T16:00:00.000Z","updated":"2022-11-14T10:21:35.783Z","comments":true,"path":"2022/10/15/python之旅/","link":"","permalink":"http://example.com/2022/10/15/python%E4%B9%8B%E6%97%85/","excerpt":"我学到的python教程,就当做回忆,和笔记存在了。","text":"我学到的python教程,就当做回忆,和笔记存在了。 如果你也想学,当然可以看看,咱们一起学!~ 如有错误,问题,欢迎qq:1276576156。 python入门学习安装### 解释器 python(也就是说,电脑无法直接理解你的代码,需要编译,解释给电脑,属于是中英翻译官那类的~) —— 1.python官网下载 python 2.点Download->(黄色按钮)Download Python 3.10.8(这里以后版本会不一样,但基本3开头,就没什么问题) 3.开始下载,【特别注意!】必须点最下面Add python…..不然电脑会无法识别,貌似涉及到环境变量。 4.下载好点击install Now开始安装,安装好的后面可能会有下面的提示,就是Disable……..可以点没,不点应该也没啥。 5.检测:点win+r输入cmd(这个是命令提示符,找计算机的文件位置,在后面安装轮子啥的都要用到,想当程序员,以后也要会很多指令,可以去学学),查找python,如果显示Python (版本号,比如我的3.10.8)。说明安装成功啦!~ ### 编辑器 写代码用的 —— 1.这里我用的是以前安装过的vs code,你可以去找个专门编译的,但我更喜欢vs,因为我需要编译html,css,c语言等其他代码 2.所以你专门做python的话可以去安装个pycharm。或者你什么都不喜欢,甚至可以直接用记事本,或者去找集成开发环境(ide) 3.集成开发环境(我这里是window 10)在开始界面,搜索p,找到IDLE…那个就是了,python自带的,如果经常使用,可以放到桌面上。 4.注意,IDLE是直接进行交互式编程,也就是你写一行就可以立即执行。 看着无聊吗,可以试试听听音乐去,我的博客左下是我的纯音乐歌单,我挺喜欢的,你可以去听听~~,模块需要手动操作,一边看一边学,学累了,就听听歌曲,挺好的 创建项目 ——我的话,是直接在我的git仓库建立xxx.py,然后打开去vs写,没有git仓库,不玩git,直接在桌面,或者文件夹建一个就行。 vs运行 ——vs写好代码,保存,可以直接点右上角的三角开始播放的那个按钮,下面会自动运行的,就不必去cmd打puthon.exe 项目名来运行了。 代码print ——1.print(‘hello world’) #进行打印,输出hello world。特别注意:那个() ‘ 啥的符号都是英文输入法状态的,中文无法识别,会出错。还有就是 ‘’ 和 “” ,这俩是一样的,没啥区别。print(‘hello world’)和print(“hello world”)一样。 ———————- 2.—— 拼接: print()也可以进行拼接,比如print(‘hello’+’world’+’!’) ——> 不给你看输出结果,自己去动手,不然记不住的,蠢蛋!~ ———————- 3.—— +: 如果你觉得紧凑,就在world前加个空格:print(‘hello’+’ world’+’!’) ———————- 4.—— 配对: 虽然 ‘’ 和 “ “ 没区别,但计算机会识别配对,比如print(‘hello “world”‘),这个就是’’配对,””会被实际打出来,结果为hello “world” ———————- 5.若出现语句需要’和”从而乱套该怎么办,比如” He said “Let’s go””,电脑会识别第二个”,从而报错。可以替换成” He said \\“Let\\‘s go\\“”。 #\\表示转义符,和后面符号一起读,变成个单纯的符号,不进入关键符号。(记得加print~) ———————- 6.——换行: 注意别傻不拉几直接enter换行,要再换行前的语句加入\\n,print(‘hello \\nworld’)自己试试去吧。 ————在这里提到’’’和”””,也就是三引号,这样就可以直接换行 比如 print(“””hello world”””) ———————- 7.—— 相乘,print可以相乘,比如打出100个x print(‘x’*100) 做个题?::::试着打出: Let’s dance舞起来~ Cause tonight we’re on fire今夜我们热情似火 Break out释放自我 “Going straight to the top”勇往直前 Let’s dance !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(30) 答案放在下个题后面~~ (づ ̄3 ̄)づ╭❤~ ——————好力_______print就到这啦,听首歌放松下,喜欢的可以接着看~ Σ( ° △ °|||)︴请要有耐心,把题认真做完巩固一下,再接着看哦 赋值 ——1.直接赋值 : 要赋值的=被赋值的 比如 x=100 #注意顺序别乱,而且赋好后,print不要带‘’ #赋值那里不要带空格,不要带 _ 以外的符号,不要以数字开头比如 520xraytf,xraytf!, x raytf,还有就是请不要对关键字赋值,比如printf=’xraytf’这样就会被视为变量,不会被视为关键字。 ———————- 2.—— 写代码,赋值建议用英文,善用下划线,比如name_student,name_teacher。拼音的话谐音严重,汉语可能有不兼容问题。多打打,建议养成习惯。 赋值被输出可以带逗号,比如定义了x1,x2,print(x1,x2)。 做个题?::::试着赋给水果各类的价格并排表打印,苹果,菠萝,香蕉,葡萄。价格自选答案放在下个题后面。︿( ̄︶ ̄)︿ 上个题我的答案: 题目:打出歌曲 1234567891011121314print('''Let\\'s dance舞起来~Cause tonight we\\'re on fire今夜我们热情似火Break out 释放自我Going straight to the top勇往直前Let\\'s dance '''+ '!'*30) 计算 ——1.加减乘除 + - * / 1+2-3*4/5。 #乘方 ** —————– 2.数字有要求的 “3”:浮点数。3:数字。3.0:浮点数。 —————– 3.进行其他运算,比如sin,开根等等,使用的话,需要导入轮子,math。 导入方法:代码(sin)—-其他的你想用啥可以去搜索python math库文档 import math math.sin(1) 做个题?:::计算-x²-2x+3=0用求根公式算出值。 #你说你不会求根公式?自己查去 = = 答案放在下个题后面o( ̄▽ ̄)ブ 上个题我的答案 题目:试着赋给水果各类的价格并排表打印,苹果,菠萝,香蕉,葡萄。价格自选 12345678910111213fruit_apple='35'fruit_pineapple='21'fiuit_banana='999'fruit_grape='233'print( '苹果价格:'+fruit_apple )print( '菠萝价格:'+fruit_pineapple )print( '香蕉价格:'+fiuit_banana )print( '葡萄价格:'+fruit_grape )#或者:print('苹果价格:'+fruit_apple +'\\n菠萝价格:'+fruit_pineapple+ '\\n香蕉价格:'+fiuit_banana+'\\n葡萄价格:'+fruit_grape) 注释 # ——注释,解释的东西,注释不会被编译器执行,也就是看代码,别人看不懂,加个解释给人看的,就是不执行的命令。 注释换行:可以选中,点ctrl+/就行。或者’’’的换行,这里不加print,所以不会被执行,虽然不是正经注释,但效果一样。 数据类型 —— 1.字符串 str:’233xraytf欧吼’里面的都是字符。想取长度,加一个函数定义len,就是len(‘233xraytf欧吼’) 注意:转义符如\\n是会被判定为一个字符。 想要提取字符,后面加一个[],比如”hello”[3], #请注意,程序是0优先哈~(牢记,牢记听没听懂!!) ——————————- —— 2.整数型 int: 44,-213 ——————————————— —— 3.浮点数 float:1.2,-233.4 ———————————————– —— 4.布尔类型 bloo:True和False(注意大写) #就真和假 ———————————————— —— 5.空值类型 NOneType:None,当你不知道要写什么的时候可以先定义none,比如赋值,相当于空集 ———————————————– ————6.当你不知道哪个对象类型的时候,可以定义type函数来验证。比如type(“hello”) type(True) type(None) ——做个题?::::对”hello world!”求字符串长度,通过索引获得字符串的单个字符w,用字符串长度,获取!。定义布尔类型和空值类型。验证之前定义的所有类型和1.9。答案放在下个题后面~o(*≧▽≦)ツ┏━┓ 上个题我滴答案 题目:计算-x²-2x+3=0用求根公式算出值。 12345678import matha=-1b=-2c=3q=b**2-4*a*cx1=(-b+math.sqrt(q))/(2* a)x2=(-b-math.sqrt(q))/(2* a)print(x1 ,x2) input —— 1.互动用,大概如input(“输入年龄:”),你现在会看到里面的,而且可以打进数值。 ———————————- 2.用input被赋值后,不管是什么,都属于字符串,无法进行算数。但是可以用int转换一下,比如x=input() int(x)。。其他的类如float转浮点,str转字符都可以 —— 做个题?::::求用户的BMI指数(身体质量指数:BMI=体重/(身高)的平方答案放在下个题后面~ ≡ω≡ ——上个题我的答案: 题目:对\"hello world!\"求字符串长度,通过索引获得字符串的单个字符w等操作。 1234567891011121314151617x="hello world!"print(len(x))print(x[6])print(x[len(x)-1])b1=Trueb2=Falsen=Noneprint(type(x))print(type(b1))print(type(b2))print(type(1.9))print(type(n)) 条件语句 if —— 1.if 条件:->判定执行,判定结果会是布尔值,也就是True,False。if后跟的执行语句都是需要缩进的,一般为四个空格,按一个Tab,如果你用编译器,他应该会回车自动缩进的哦~ ————————- 2.条件成立,则执行下方语句,否则跳过。如果你想判定条件为假执行语句,可以后面放一个else: #注意else要不要缩进, 给你个模板: if 3==3: —-print(“对”) else: —-print(“错”) ——做个题?::::写一个输入星期几能不能去玩的小问答答案放在下个题后面~ (ˉ▽ ̄~) ——上个题我的答案 题目:求用户的BMI指数(身体质量指数:BMI=体重/(身高)的平方 12345w=float(input("请输入你的体重/kg:"))h=float(input("请输入你的身高/m:"))x=w/(h)**2print("您的BMI指数为",x) 嵌套/多条件判断 (galgame常用语句了~) —— 1.if语句是可以嵌套的,就是if语句条件判断成功后在写一句if,当然,else也可以在第二层内部判断为1真2假后执行(注意缩进)。 ——————— 2.多个条件判断也可以用elif,就相当于else的用法,不需要缩进,直接在if后面写,最后还可以写上else。 #这里要注意顺序,如果第一个和第二个elif同时成立,只执行前面的,然后退出判断语句哦。 ——做个题?::::丰富一下你的求BMI指数的程序,告诉他属于哪一类x<18.5:偏瘦,18.5x<25:正常。(25<x<30): 偏胖。x>30:肥胖。答案放在下个题后面哦~ ┑( ̄Д  ̄)┍ 上个题我的答案: 题目:写一个输入星期几能不能去玩的小问答 12345x=int(input("请输入今天是星期几:"))if x==7: print("芜湖!")else: print("完。。") 逻辑运算(优雅~永不过时) ——1.条件太多的话,光用elif,if来写,就会显得不优雅,所以就用到了与,或,非—and,or,not。 ——————— 2.and: 所有条件都为True,才为True。比如3==3 and 4==5 and 5==5是False; ——————— 3.or: 所有条件都为False,才为Flase。#通俗来说就是。and一个错全体错,or一个对全体对o( ̄▽ ̄)ブ —————————— 4.not: 注意,是原先的操作对象是True,然后反过来变成False。比如not 10==10,会返回False。 —————– 优雅~太优雅了,但其实还是具体情况具体分析的哈◡‿◡ ——做个题?::::比如定义时间,动力,自行车,电动车判断看能外去吃饭,还是在家吃饭,还是都不行呢ヾ( ̄▽ ̄)ByeBye答案放在下个题后面 o( ̄▽ ̄)o 上个题我的答案: 题目:丰富一下你的求BMI指数的程序。 12345678910111213w=float(input("请输入你的体重/kg:"))h=float(input("请输入你的身高/m:"))x=w/(h)**2print("您的BMI指数为",x)if x<=18.5: print("您属于偏瘦范围︿( ̄︶ ̄)︿")elif x<=25: print("您属于正常范围o(*≧▽≦)ツ")elif x<=30: print("您属于偏胖范围(=。=)")else: print("你属于肥胖范围o(≧口≦)o") 列表,数据整合 —— 1.一个个赋值在多变量时过于麻烦,所以要用到列表。 —————————- 2.形式: x=[数据1,数据2]。 可以直接打印,需要看就输出就可以:print(x)。而且列表什么类型都可以放,不需要变来变去的。 ——————————————- 3.往列表里加东西的时候,可以用一个专用词,append。 用法: x.append(数据3)这样就加进去了。 #注意,后面进行操作添加,删除什么的都是直接对列表进行修改,不行要重新定义。比如x=x.append(数据3)是不成立的。 ——————————————- 4.删除某个值: x.remove(数据1)。 —————————– 5.len: 返回列表里有几个元素。len(x)。并且可以返回特定顺序的,比如print(x[1])返回数据2.这个也可以直接修改赋值。比如x[1]=数据3,那么数据2就被修改为数据3. —————————— 6.内置函数: min(x)—#打印最小值。max(x)—#打印最大值。sorted(x)—#从小到大打印排列好的列表。 ——做个题?::::1.试着往列表里加11,22,33。并查看2.添加44并输出3.删除22并输出4.查看列表里现在有几个元素。并且修改11为555.打印最小值,排序列表。 答案放在下个题后面~<( ̄︶ ̄)> 上个题的答案: 题目:比如定义时间,动力,自行车,电动车判断看能外去吃饭,还是在家吃饭,还是都不行呢ヾ( ̄▽ ̄)Bye~Bye~。 12345678910111213x=int(input("请输入有没有钱(0/1):"))y=int(input("请输入有没有精力(0/1):"))z=int(input("请输入有没有自行车(0/1):"))l=int(input("请输入有没有电动车(0/1):"))if not y==1: print("什么都不想干 ━( ̄ー ̄*|||━━") elif x==1 and z==1 or l==1: print("可以出去吃饭咯!")elif z==0 and l==0: print("没车,不想出去,在家吃好了= =")else: print("没钱aaaaao(≧口≦)o") 字典 ——1.由于你用直接赋值太不方便,用列表又不能按名字查。所以用一个字典就可以很好的解决~~~~ —————————— 2.基本: (字典名)contacts={key(键):value(值),key(键):value(值)}。比如contacts={“小明”:”10”,”小红”:”2312”} —————————— 3.查询: contacts[“小明”]。 ————————– 4.元组: 当你想根据年龄找人,而名字一样,由于列表是不可变的,不能放进字典,可以用元组,比如:contacts={(“小明”,23):23123,(“小明”,34):3123123,(“小明”,45):42545234},在找的时候,就能把元组作为键,contacts[(“小明”,34)] —————— 5.添加: contacts[“小亮”]=231。注意这是添加,但是如果以前有小亮了,会直接覆盖上去。更新字典。 ——————— 6.看键是不是在字典里,键 in 字典会返回布尔值,如:”小明” in contacts 会返回True ——————– 7.删除: del contacts[“小明”] ————————- 8.查询多少个键值对:len(contacts)。 ——————————- 其他用法:当()什么都没有时就会全部输出。比如x.keys():输出所有键,x.values():所有值,x.items():所有键值对 ——做个题?::::写3人份的姓名,学号,手机号通讯录并做到各个功能答案放在下个题后面~( ̄▽ ̄)~■干杯□~( ̄▽ ̄) 上个题答案: 题目:试着往列表里加11,22,33。并做到查看,添加等功能。 123456789101112x=[11,22,33]print(x)x.append(44)print(x)x.remove(22)print(x)print(len(x))x[0]=55print(x)print(min(x))print(sorted(x)) for循环 —— 1.基本样式为for x in 列表:,也就是比如: list=[1,2,3,4,5],—for x in list就表示为从第一个开始赋值到最后,去试试print吧,for和if他们一样,也是需要缩进,无缩进退出for的编辑。 ——————- 2.和字典混用。因为你如果想找目标,还是需要和字典结合。 比如x={“job”:34,”jone”:54,”jerry”:98} for name,socre in x.items(): if socre>35: print(“name”) 或者———– for t in x.items(): —-name=t[0] —-socre=t[1] —-if socre>35: —-print(“name”) ———————- 3.在此介绍一个常用绑定函数range: range是可以依次进入的,比如range(1,101)就是进入1-100(所以说不包括最后一个),形式:for x in range(1,100):这样~—–range函数还可以添加第三个,range(1,101,2)就是跳俩,1,3,5,7…….。不写默认为1。 ——做个题?::::求521到1314的和。答案放在下个题后面 φ(゜▽゜*)♪ 上个题答案: 题目:写3人份的姓名,学号,手机号通讯录。 12345678910111213141516171819t={("a",12):123121,("b",34):434324,("c",45):5645764645}print(t)x=input("请输入要查询的人和学号")if x in t: print(t[x])else: print("没有")print("添加键")t[("小白",87)]=423432print(t)print("查看键是否在词典内")y=input("请输入要查询的人和学号,判断是否在内")print(y in t)print("删除b")del t[("b",34)]print(t)print("查询有多少个键值对")print(len(t)) while循环 —— 1.适用于不知道具体多少循环次数,比如计算器。 ———— 2.基本形式: while 条件: ****行动 例如: while x>0: ****print(“x”) ————————– 3.注意一点,切记不能进入死循环,比如: i=0 while i<len(x): ****print(x[i]) ****i+=1————–这里这个不写,i就会一直小于len(x)这样循环就会一直持续下去。这也是while最需要注意的。 —— 做个题?::::做一个求平均值的计算器答案放在下个题后面~ヾ(≧∇≦*)ゝ 上个题我的答案: 题目:求521到1314的和。 12345sum=0for i in range(521,1315): sum+=iprint(sum) 函数 —— 1.当你需要定义多个算法,比如计算一个圆,扇形的面积,每次都写公式,未免太麻烦。而编码原则也是,尽量不要去重复你写的东西,所以,函数就显得 非常好用。直接输入值,输入一行执行函数的代码,自动帮你执行那个功能,听起来就很香吧 —————— 2.形式: 写函数:def 函数头(参数1,参数2):#记得缩进。 调用函数:函数头(参数1,参数2):参数对应。就比如说你输入函数头(半径,角度)会自动对应里面的,并且可以在函数内使用。 ——————— 3.作用域: 输出怎么输出,你可以在函数内获得你所赋的值,但出了函数,就不可以了,也就是内外的关系。 这里就用带了return 值。写在函数末尾。这个会帮助你在执行函数语句后,返回return的值就可以直接用这个函数了 ——做个题?::::用函数完善你的BMI计算器答案放在下个题的后面~(゜▽^*)) 上个题我的答案: 题目:做一个求平均值的计算器。 1234567891011121314num=0p=0j=0x=input("请输入数字(stop结束):")while x !="stop": num+=float(x) j+=1 x=input("请输入数字(stop结束):")if j==0: p=0else: #这里是因为如果一开始就输入stop,0/0不被允许会报错。 p=num/j print(p) 模块 —— 1.sum求和什么的都是内置模块,比较常用,所以python都会自带,而不常用的外置函数,就需要引入,或者自己写。 ——————- 2.引入模块: import 模块名 #可以导入所有模块,但并不推荐,所以知道能就行了。 ———————– 3.使用模块: 模块.函数,比如 import turtle –#导入海龟画图的模块 turtle.forward(100) —#前进100个像素 turtle.left(90) —-#箭头左转90度 turtle.forward(100) ——#前进100个像素。 画了个直角。 其他用法: turtle.pensize(像素)—#粗细线条 turtle.pencolor(“颜色”)—#加画笔颜色 turtle.circle(半径)—#画圆 turtle.penup()—#拿起笔,就是先不画 turtle.pendown()—-#放下笔,开始画 turtle.fillcolor(“颜色”)—–#默认填充白色,这个可以换成其他颜色。 —–光有填充是不行的,也需要开始和结束填充: turtle.begin_fill()—#开始填充 turtle.end_fill()—-#结束填充 turtle.hideturtle()——#隐藏海龟箭头图标。 ——————其他的自行去找吧~ ——做个题?::::画一个你喜欢的图形 上个题的我的答案: 题目:用函数完善你的BMI计算器。 12345678910111213141516171819202122w=float(input("请输入你的体重/kg:"))h=float(input("请输入你的身高/m:"))def BMI(w,h): x=w/(h)**2 print("您的BMI指数为:",x) if x<=18.5: print("您属于偏瘦范围︿( ̄︶ ̄)︿") elif x<=25: print("您属于正常范围o(*≧▽≦)ツ") elif x<=30: print("您属于偏胖范围(=。=)") else: print("你属于肥胖范围o(≧口≦)o") return xwhile w != '0': BMI(w,h) w=float(input("请输入你的体重/kg:")) h=float(input("请输入你的身高/m:")) ——我这里用海龟画图模块写一个,你可以先照着代码猜猜是什么,再去试试看。 12345678910111213141516171819202122232425262728293031323334353637383940import turtlefrom unittest import runnerturtle.pencolor("red")turtle.pensize(10)turtle.circle(105)turtle.left(90)turtle.penup()turtle.forward(50)turtle.right(90)turtle.pendown()turtle.fillcolor("blue")turtle.begin_fill()turtle.circle(55)turtle.end_fill()turtle.pensize(1)turtle.penup()turtle.left(90)turtle.forward(70)turtle.left(90)turtle.forward(48)turtle.right(180)turtle.pendown()turtle.fillcolor("white")turtle.pencolor("white")turtle.begin_fill()i=0while i<5: turtle.forward(100) turtle.right(144) i+=1turtle.end_fill()turtle.hideturtle()turtle.exitonclick() —–到这里,基础知识大概就没什么了。其他的去找找资料,实践写东西去吧~ヽ(✿゚▽゚)ノ","categories":[{"name":"知识","slug":"知识","permalink":"http://example.com/categories/%E7%9F%A5%E8%AF%86/"}],"tags":[{"name":"学习","slug":"学习","permalink":"http://example.com/tags/%E5%AD%A6%E4%B9%A0/"},{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"}]},{"title":"我的网上冲浪","slug":"我的第一篇文章","date":"2022-10-12T16:00:00.000Z","updated":"2022-11-02T03:31:06.724Z","comments":true,"path":"2022/10/13/我的第一篇文章/","link":"","permalink":"http://example.com/2022/10/13/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0/","excerpt":"在此,记录我网上的青春。","text":"在此,记录我网上的青春。 探索着探索着,我喜欢在网上当一个探索者,每次有新发现都会让我无比激动。 这次的博客,是一个全新的尝试,我正式去接触github,开启全面接触各业。 以下,是一个总想着探索的笨比的碰壁之旅….感兴趣的可以瞅两眼(可以当个小故事看,毕竟我是个话痨,会写的很多) 记忆回顾 小学 —— 基于我家有一个windows系统的老式笔记本电脑,我开始在互联网上流浪,正式接触互联网 起初,我尝试自己打开电脑这个东西,从会发光的小破盒子,发展成下载qq,开启qq空间(玩qq农场啥的破游戏),还不到3天,可以说对我的吸引力可见一般。 —— 后来,我尝试在qq上去寻找各式各样的人,探索qq的各个功能,学习一些小知识,比如认识到跳转这个功能,和网页(但是当时太笨,连一些词语都不懂,所以学的很有限) —— 最后,我开始向地址发展,在原式360浏览器中摸索(因为每次点qq空间都会跳到360)我开始学习网址,尝试自己照着他们的共同点(基本结构http啥的)找出其他网站,比如qq音乐的数字,但也仅此而已了。毕竟我家也不是富裕人家,都玩不好电脑(甚至我是家里玩的最好的)。 初中 —— 起初,我开始去探索360浏览器的各个网址,就是有着4399的一堆网站,想去看看都能干什么,但其实什么也没干,净碰壁了,有时候有个喜欢的就收藏起 来, 但其实也很少看,大部分都是垃圾网站,于是。。很长一段时间,我放弃了搜找,因为只有4399一个网站我喜欢(游戏嘛)。 —— 后来,我认识到了下载这个事,开始尝试各式各样的下载,但电脑貌似经不起我这样的折腾,变得越来越卡,具体情况就是,我想美化,或者想用一些网上说的小功能,而且我想试试看各个软件好不好用,就比如探索比360更好的浏览器,和电脑内部磁盘怎么占用,安装地址什么的。终于是。。把电脑玩崩了。。= =。。。。。 (于是电脑体验正式进入休眠期)—— 可能是当时我还是不太懂,比如下载完,不知道卸载这个事,只知道删除,于是删除了快捷方式(蠢b)。 —— 还有大概就是,我下的软件,比较。。。奇怪,比如2345,当时是有默认浏览器一说,我确实也设置了,(但删除的是快捷键就是了)于是开始探索怎么回事,但终究是没搞明白,后来我发现电脑的问题时,希望采取补救措施,于是就有了鲁大师,各式杀毒软件,但不懂什么事硬件,什么是电脑病毒,只是感觉他们是清理的,可能会让电脑好一点。然后更一天不如一天。——_—— (大概初二正式弃用) 初三后到高三 —— 在此之后,我就准备去祸害手机() —— 当然最开始的确实还是游戏,但没什么爱玩的,就去探索别的app,逐渐接触配音,演戏,直播,视频网站,音乐等等,当然,我自己也实践过(比如自己配音,对比专业配音,pia戏,找剧本,自我音乐创作,做视频等) 其中 —— 做视频:其实也就是录像,当时只有手机,用不好剪辑工具,所以我就放弃了,打算去大一买电脑了试试。 —— 音乐创作:我当时很热爱音乐,在网上甚至音乐app没有就去网上找(甚至我只有小盒子按键手机,在电视上听到喜欢的音乐,就启用录音录下来听,比如马上又的谁,还有一些动画的op,ed),后来吧,我爸看我很喜欢音乐,于是说要不要买点音乐设备,就小提琴,吉他啥的,我当时也蛮喜欢的,就答应下来的,买一个吉他,但并不好买,他也一直上班,所以一直在拖,我看了一些关于乐器的书,在网上也找了些资料(但,迫于时间,学习压力,最终还是作废了)。 —— 在这之间。我在留下来的学习机,和步步高点读机上探索,但功能实在太少,学习机倒是很不错,我学习了图书里的人文,历史,语文等等知识,也体验了里面自带的力学,电气实验室,但其实没有联网,没有其他设备能与其连接,所以能用的功能也实在太少,所以也是没学到什么有用的。 —— 在高一下半年,我接触了代码,开始在高层面冲浪~,我了解到学校有一个学习c语言,于是我就开始去试着参与进去,向老师报告我想学。后来也是成功混进去了,但迫于是后知道的,所以基础知识他们已经学完,不可能因为我重头开始,所以就处于零基础开始写一些基本问题的地步,当然,肯定是跟不上,听不懂,还不会,为此我也借了一个走了的他班的书来学习,后面多多少少好歹是跟上点了。但当然还是不如其他人学得好,因为他们本身是尖子生,全校前几才有这个信息,从他们那里招的,我只是兴趣使然,而且是中途插入,基础更差,因此在后面一次说是出去竞赛后,这个班就停止继续学习了,我也结束了我的大学代码学习。 —— 再后来,就没什么活动了,因为,没有电脑,玩手机的时间也很少,我只是去尝试,体验各个app,并不能用手机干什么,所以也就一直致力于摆烂,游戏,和学习了。 大一 —— 我再次开启我的电脑之旅,一边学习课程的c语言,一边开启学习视频制作,接触pr,ae等东西,学了一些社团里的课程资源,在b站找些视频看,正式开启剪辑之旅。 —— 后来,正式在b站注册账号发布视频,主要是音乐混剪(虽然中途因为疫情回家了,所以下半年在家摆烂不想做后来没发了。) 由于我在高中学习过c语言,所以学习起来方便不少,但老师讲的很差,我也就自学了一段时间,把高中不懂的一些,比如结构体,指针等完善了一下,后来下半年接触数据结构,在家上网课,效率比较低,但姑且是能做出来,在后面的大作业中写了(用单链表设计电影院售票管理系统)(拓展题目:航空售票处的服务系统)。起码是。。能看吧,还可以,算是合格了 —— 至于网页设计,我其实个人认为学习的并不多,其实记住的也就是一些指令,还有涉及html,css的相关知识,课程大作业我随便在b站找了一个模板,然后开始修改,还算蛮简单的,没用多久就完成交上去了。 大二 —— 从大人(我的高三同桌,互联网知识面很广)口中得知设计博客,软考等方面的知识,既能提高自己,还能写到简历,于是正式开始接触博客,从博客制作过程中了解到了github,并初步了解web前端知识等,虽然可能学的不多,但起码都接触了,好歹是有个了解。 —— 通过github和hexo成功设计好了这个博客,并且接触到了github,也是~很兴奋哈,啊哈哈哈哈! —— 在博客的过程中也碰了不少壁,但还好,通过在csdn上找,和问千千(我在制作博客中遇到的朋友,代码知识丰富,很早接触github,设计程序,比我小一届,我的第一个友链)和大人还有去群里找大佬帮忙。总算是有惊无险弄完了(具体过程我就不说了,大概的过程想知道可以去看看足迹)很辛苦,但也很充实,做完后完善(满满的成就感) —— 在这后我也会正式去github上浪,看项目,写代码,争取学好c,python,前端知识等,也设计插件啥的,嘿嘿,想想又兴奋起来了。","categories":[{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"}],"tags":[{"name":"记忆","slug":"记忆","permalink":"http://example.com/tags/%E8%AE%B0%E5%BF%86/"},{"name":"脚步","slug":"脚步","permalink":"http://example.com/tags/%E8%84%9A%E6%AD%A5/"}]}],"categories":[{"name":"知识","slug":"知识","permalink":"http://example.com/categories/%E7%9F%A5%E8%AF%86/"},{"name":"生活","slug":"生活","permalink":"http://example.com/categories/%E7%94%9F%E6%B4%BB/"}],"tags":[{"name":"学习","slug":"学习","permalink":"http://example.com/tags/%E5%AD%A6%E4%B9%A0/"},{"name":"python","slug":"python","permalink":"http://example.com/tags/python/"},{"name":"git","slug":"git","permalink":"http://example.com/tags/git/"},{"name":"记忆","slug":"记忆","permalink":"http://example.com/tags/%E8%AE%B0%E5%BF%86/"},{"name":"脚步","slug":"脚步","permalink":"http://example.com/tags/%E8%84%9A%E6%AD%A5/"}]}