diff --git a/.gitignore b/.gitignore index b028bb7..a4275a2 100755 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,5 @@ node_modules/ blog/.vuepress/.cache/ blog/.vuepress/.temp/ blog/.vuepress/dist/ + +vcs.xml \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..7ddfc9e 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,5 +1,11 @@ + + + + + + diff --git a/blog/.vuepress/client.ts b/blog/.vuepress/client.ts new file mode 100644 index 0000000..434a743 --- /dev/null +++ b/blog/.vuepress/client.ts @@ -0,0 +1,10 @@ +import {defineClientConfig} from 'vuepress/client' +import {usePageData} from 'vuepress/client' + +export default defineClientConfig({ + setup() { + /*const page = usePageData() + page.value.path = page.value.path && page.value.path.toLowerCase() + console.log('page.value.path', page.value.path)*/ + } +}) \ No newline at end of file diff --git a/blog/.vuepress/components/BlogHero.vue b/blog/.vuepress/components/BlogHero.vue new file mode 100644 index 0000000..86f4fe4 --- /dev/null +++ b/blog/.vuepress/components/BlogHero.vue @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/blog/.vuepress/config.ts b/blog/.vuepress/config.ts index 4176548..513533c 100644 --- a/blog/.vuepress/config.ts +++ b/blog/.vuepress/config.ts @@ -1,39 +1,34 @@ -import { defineUserConfig } from "vuepress"; +import {defineUserConfig} from "vuepress"; import theme from "./theme.js"; import {registerComponentsPlugin} from "@vuepress/plugin-register-components"; -import { path } from '@vuepress/utils' -import {searchProPlugin} from "vuepress-plugin-search-pro"; +import {getDirname, path} from 'vuepress/utils' + +const __dirname = getDirname(import.meta.url); + // @ts-ignore export default defineUserConfig({ - base: "/", - locales: { - "/": { - lang: "zh-CN", - title: "zzz's Blog", - description: "zzz 的博客", - }, - }, - - theme, - // Enable it with pwa - // shouldPrefetch: false, - plugins:[registerComponentsPlugin({ - componentsDir: path.resolve(__dirname, '../../components'), - }), - searchProPlugin({ - // 索引全部内容 - indexContent: true, - // 为分类和标签添加索引 - customFields: [ - { - getter: (page) => page.frontmatter.category, - formatter: "分类:$content", + // 与pwa插件配合 https://theme-hope.vuejs.press/zh/guide/advanced/pwa.html + shouldPrefetch: false, + base: "/", + locales: { + "/": { + lang: "zh-CN", + title: "zzz's Blog", + description: "zzz 的博客", }, - { - getter: (page) => page.frontmatter.tag, - formatter: "标签:$content", - }, - ], - }), - ] + }, + theme, + // Enable it with pwa + // shouldPrefetch: false, + plugins: [ + registerComponentsPlugin({ + componentsDir: path.resolve(__dirname, '../../components'), + }), + ], + alias: { + "@theme-hope/modules/blog/components/BlogHero": path.resolve( + __dirname, + "./components/BlogHero.vue", + ), + }, }); diff --git a/blog/.vuepress/navbar/zh.ts b/blog/.vuepress/navbar/zh.ts index 3c66e0a..9434136 100644 --- a/blog/.vuepress/navbar/zh.ts +++ b/blog/.vuepress/navbar/zh.ts @@ -6,16 +6,23 @@ export const zhNavbar = navbar([ text: '前端', prefix: "/front/", icon: "fa-brands fa-html5", - children: [{ - text: 'Html', - link: 'html', - icon: "fa-brands fa-html5", - }, + children: [ + { + text: 'Html', + link: 'html', + icon: "fa-brands fa-html5", + }, { text: 'Js', link: 'js/', icon: "fa-brands fa-js", + }, + { + text: 'Ts', + link: 'ts/', + icon: "skill-icons typescript", + }, { text: 'React', @@ -25,7 +32,7 @@ export const zhNavbar = navbar([ }, { text: 'Vue', - link: 'vue', + link: 'vue/', icon: "fa-brands fa-vuejs", }, @@ -56,11 +63,15 @@ export const zhNavbar = navbar([ }, { text: 'Spring Security', - link: 'SpringSecurity/' + link: 'spring-security/' + }, + { + text: 'Spring Boot', + link: 'spring-boot/' }, { text: 'Spring Cloud', - link: 'SpringCloud/' + link: 'spring-cloud/' } ] }, @@ -82,33 +93,25 @@ export const zhNavbar = navbar([ }, { text: '分布式', - link: 'distributed' + link: 'distributed/' }, { - text: '消息中间间', - link: 'message-queue' + text: '消息中间件', + link: 'message-queue/' }, { text: '工作流', - link: 'workflow' + link: 'workflow/' } , - { - text: 'Dubbo', - link: 'Dubbo' - }, - { - text: 'Netty', - link: 'Netty' - }, { text: '一些数字', - link: 'Java体系中一些数字' + link: 'java-number' }, { text: 'C语言', - link: 'C语言' + link: 'c' } ] @@ -119,7 +122,7 @@ export const zhNavbar = navbar([ }, { text: '数据库', - icon: 'database', + icon: 'fa-solid fa-database', children: [ { text: 'MySql', @@ -128,29 +131,21 @@ export const zhNavbar = navbar([ { text: 'Redis', link: '/db/redis/' - }, - { - text: 'Oracle', - link: '/db/oracle/' - }, - { - text: 'Pg', - link: '/db/postgresql/' } ] }, { text: '网络', - icon: 'globe', + icon: 'fa-solid fa-globe', prefix: '/network/', - children:[ + children: [ { text: '协议', - link: 'protocol' + link: 'protocol/' }, { text: '抓包', - link: 'grab' + link: 'grab/' } ] @@ -200,6 +195,9 @@ export const zhNavbar = navbar([ }, { text: 'Sonar', link: '/ops/sonar/' + }, { + text: 'Jenkins', + link: '/ops/jenkins/' } ] } @@ -208,27 +206,30 @@ export const zhNavbar = navbar([ }, { text: '工具', - icon: 'toolbox', + icon: 'fa-solid fa-toolbox', children: [{ text: 'Typora', - link: '/tools/typora/1.installAndSetting' + link: '/tools/typora/install_setting' }, { text: 'Git', link: '/tools/git/' + }, { + text: 'B站字幕', + link: '/tools/b-srt.html' }] }, { text: '其他', - icon: 'box', + icon: 'fa-solid fa-box', children: [ { text: '算法', - link: '/other/algorithm' + link: '/other/algorithm/' }, { text: '性能调优', - link: '/other/performanceOptimize/' + link: '/other/optimize/' }, { text: '调试技巧', @@ -236,7 +237,7 @@ export const zhNavbar = navbar([ }, { text: '防重复请求', - link: '/other/repeatRequest' + link: '/other/repeat_request' }, { text: '批量插入', @@ -249,26 +250,20 @@ export const zhNavbar = navbar([ { text: 'PDF', link: '/other/pdf/' - }, { - text: 'Python', - link: '/python/' - }, { - text: 'Groovy', - link: '/groovy/' }, { text: 'RegExp', - link: '/other/regExp' + link: '/other/reg_exp' }, { text: 'VuePress', link: '/other/blog/' }, { - text: 'Intellij plugin', + text: 'Intellij Plugin', link: '/other/intellij/' } ] }, { text: '收藏', - icon: 'star', + icon: 'fa-solid fa-star', link: '/collect/network' }, diff --git a/blog/.vuepress/public/_headers b/blog/.vuepress/public/_headers new file mode 100644 index 0000000..ba30e0e --- /dev/null +++ b/blog/.vuepress/public/_headers @@ -0,0 +1,3 @@ +# https://docs.netlify.com/routing/headers/ +/assets/* + Cache-Control: public, max-age=604800 diff --git a/blog/.vuepress/public/assets/images/avatar copy.svg b/blog/.vuepress/public/assets/images/avatar copy.svg new file mode 100644 index 0000000..f2c0d8e --- /dev/null +++ b/blog/.vuepress/public/assets/images/avatar copy.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/blog/.vuepress/public/assets/images/avatar.svg b/blog/.vuepress/public/assets/images/avatar.svg new file mode 100644 index 0000000..fcd5911 --- /dev/null +++ b/blog/.vuepress/public/assets/images/avatar.svg @@ -0,0 +1,8 @@ + + + + + + Z + \ No newline at end of file diff --git a/blog/.vuepress/public/assets/images/bg.png b/blog/.vuepress/public/assets/images/bg.png new file mode 100644 index 0000000..33ebb63 Binary files /dev/null and b/blog/.vuepress/public/assets/images/bg.png differ diff --git a/blog/.vuepress/public/assets/images/greatway_snow.jpeg b/blog/.vuepress/public/assets/images/greatway_snow.jpeg new file mode 100644 index 0000000..c8d0a50 Binary files /dev/null and b/blog/.vuepress/public/assets/images/greatway_snow.jpeg differ diff --git a/blog/.vuepress/public/favicon copy.ico b/blog/.vuepress/public/favicon copy.ico new file mode 100644 index 0000000..b411f79 Binary files /dev/null and b/blog/.vuepress/public/favicon copy.ico differ diff --git a/blog/.vuepress/public/favicon.ico b/blog/.vuepress/public/favicon.ico old mode 100644 new mode 100755 index b411f79..9683d6b Binary files a/blog/.vuepress/public/favicon.ico and b/blog/.vuepress/public/favicon.ico differ diff --git a/blog/.vuepress/public/favicon.svg b/blog/.vuepress/public/favicon.svg new file mode 100644 index 0000000..c01f700 --- /dev/null +++ b/blog/.vuepress/public/favicon.svg @@ -0,0 +1,6 @@ + + + + Z + + \ No newline at end of file diff --git a/blog/.vuepress/public/logo copy 2.svg b/blog/.vuepress/public/logo copy 2.svg new file mode 100644 index 0000000..5a167ca --- /dev/null +++ b/blog/.vuepress/public/logo copy 2.svg @@ -0,0 +1,6 @@ + + + Z + \ No newline at end of file diff --git a/blog/.vuepress/public/logo copy.svg b/blog/.vuepress/public/logo copy.svg new file mode 100644 index 0000000..364d184 --- /dev/null +++ b/blog/.vuepress/public/logo copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/blog/.vuepress/public/logo.png b/blog/.vuepress/public/logo.png old mode 100644 new mode 100755 index ccd732e..77785dd Binary files a/blog/.vuepress/public/logo.png and b/blog/.vuepress/public/logo.png differ diff --git a/blog/.vuepress/public/logo.svg b/blog/.vuepress/public/logo.svg index 364d184..fcd5911 100644 --- a/blog/.vuepress/public/logo.svg +++ b/blog/.vuepress/public/logo.svg @@ -1 +1,8 @@ - \ No newline at end of file + + + + + + Z + \ No newline at end of file diff --git "a/blog/.vuepress/public/logo_\345\211\257\346\234\254.png" "b/blog/.vuepress/public/logo_\345\211\257\346\234\254.png" new file mode 100644 index 0000000..ccd732e Binary files /dev/null and "b/blog/.vuepress/public/logo_\345\211\257\346\234\254.png" differ diff --git a/blog/.vuepress/sidebar/zh.ts b/blog/.vuepress/sidebar/zh.ts index 73dc883..59e2293 100644 --- a/blog/.vuepress/sidebar/zh.ts +++ b/blog/.vuepress/sidebar/zh.ts @@ -9,200 +9,28 @@ export const zhSidebar = sidebar({ '/back/java/': "structure", '/back/struts/': "structure", '/back/spring/': "structure", - '/back/SpringSecurity/': "structure", - '/back/SpringCloud/': "structure", + '/back/spring-security/': "structure", + '/back/spring-boot/': "structure", + '/back/spring-cloud/': "structure", '/back/job/quartz/': "structure", '/back/mybatis/': "structure", '/back/distributed/': "structure", '/back/message-queue/': "structure", - '/ops/os/linux/': [{ - text: 'Linux', - - children: [{ - text: '介绍', - link: '/ops/os/linux/' - }, - { - text: '安装配置', - link: '1.installAndSetting_vm' - }, - { - text: '常用命令', - link: '2.command' - }, - { - text: '系统性能', - link: 'sys_performance' - }, - { - text: 'shell脚本', - link: '3.shell' - }, - { - text: '磁盘扩容', - link: 'fileExtend' - } - ] - }], - '/ops/os/mac/': [{ - text: 'Mac OS', - children: [{ - text: '介绍', - link: '/ops/os/mac/' - }, - { - text: '安装', - link: '1.install' - }, - { - text: '配置', - link: '2.settings' - }, - { - text: '软件推荐', - link: '3.software' - }, - { - text: '快捷键', - link: '4.keys' - }, - { - text: '使用技巧', - link: '5.skill' - } - ] - }], - '/ops/server/nginx/': [{ - text: 'Nginx', - children: [{ - text: '前言', - link: '/ops/server/nginx/' - }, - { - text: '安装配置', - link: '1.nginx(windows)' - }, - { - text: 'OpenResty', - link: 'openresty' - } - ] - }], - '/ops/server/apache/': [{ - text: 'Apache', - children: [{ - text: '前言', - link: '/ops/server/apache/' - }, - { - text: '安装配置', - link: '1.installAndSetting' - } - ] - }], - '/ops/server/tomcat/': [{ - text: 'Tomcat', - children: [{ - text: '介绍', - link: '/ops/server/tomcat/' - }, { - text: '源码编译与启动', - link: 'tomcatSource' - }, { - text: '日志', - link: 'tomcatLogging' - }, - { - text: '常见问题', - link: 'problems' - } - ] - }], + '/ops/os/linux/': "structure", + '/ops/os/mac/': "structure", + '/ops/server/nginx/': "structure", + '/ops/server/apache/': "structure", + '/ops/server/tomcat/': "structure", '/tools/git/': "structure", '/ops/sonar/': "structure", '/db/redis/': "structure", '/db/oracle/': "structure", '/db/mysql/': "structure", - '/other/debugger/': [{ - text: '调试技巧', - children: [{ - text: '介绍', - link: '/other/debugger/' - }, - { - text: '后端调试', - link: 'back_debug' - }, - { - text: '前端调试', - link: 'front_debug' - }, - { - text: '接口调试', - link: 'interface_debug' - } - ] - }], - '/other/performanceOptimize/': [{ - text: '性能调优', - children: [{ - text: '概述', - link: '/other/performanceOptimize/' - }, - { - text: '生产问题', - link: 'performance_optimize' - } - ] - }], - '/other/pdf/': [{ - text: 'PDF', - children: [{ - text: '介绍', - link: '/other/pdf/' - }, - { - text: 'PDF内容显示异常', - link: 'pdfContentError' - }, - { - text: 'PDF下载报错(字体)', - link: 'pdfFontError' - } - ] - }], - '/front/js/': [{ - text: 'JavaScript', - children: [{ - text: '介绍', - link: '/front/js/' - }, - { - text: '基本概念', - link: '1.base' - }, - { - text: '对象', - link: '2.object' - }, - { - text: '原型', - link: '3.propotype' - }, - { - text: '闭包', - link: '4.closure' - }, - { - text: 'Screenshots', - link: 'Screenshots' - }, - { - text: '模块化', - link: 'module' - } - ] - }], + '/other/debugger/': "structure", + '/other/optimize/': "structure", + '/other/pdf/': "structure", + '/front/vue/': "structure", + '/front/js/': "structure", '/network/protocol/': "structure", '/network/grab/': "structure", '/ops/virtualization/k8s/': "structure", diff --git a/blog/.vuepress/styles/config.scss b/blog/.vuepress/styles/config.scss index 127ccf2..3a080f4 100644 --- a/blog/.vuepress/styles/config.scss +++ b/blog/.vuepress/styles/config.scss @@ -2,3 +2,4 @@ $colors: #c0392b, #d35400, #f39c12, #27ae60, #16a085, #2980b9, #8e44ad, #2c3e50, #7f8c8d !default; $theme-colors: #2196f3, #f26d6d, #3eaf7c, #fb9b5f; +$theme-color: #2196f3; \ No newline at end of file diff --git a/blog/.vuepress/theme.ts b/blog/.vuepress/theme.ts index 3cf9f14..6e02863 100644 --- a/blog/.vuepress/theme.ts +++ b/blog/.vuepress/theme.ts @@ -5,36 +5,50 @@ import {enSidebar, zhSidebar} from "./sidebar/index.js"; const MR_HOPE_AVATAR = ''; +const hostname = "https://zengsl.me" export default hopeTheme({ - hostname: "https://zengsl.vercel.app", + hostname, fullscreen: true, themeColor: true, author: { - name: "zzz", + name: "Leo", url: "https://github.com/zengsl", }, - iconAssets: "fontawesome-with-brands", - - logo: "https://theme-hope-assets.vuejs.press/logo.svg", + logo: "/logo.png", repo: "https://github.com/zengsl/vuepress-blog", docsDir: "src", blog: { - /* avatar:"/assets/images/child.jpg",*/ + avatar:"/assets/images/avatar.svg", description: "CV工程师", intro: "/about/intro.html", medias: { - BiliBili: "https://space.bilibili.com/437799265?spm_id_from=333.1007.0.0", - Email: "zengsl0212@foxmail.com", + LinuxDo: { + link: "https://linux.do/u/zzzl/", + icon: "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + }, + Email: "mailto:zengsl0212@hotmail.com", + Gmail: "mailto:leo.oooolll@gmail.com", Gitee: "https://gitee.com/zengsl", GitHub: "https://github.com/zengsl", - Gmail: "leo.oooolll@gmail.com", - /*QQ: "779628465",*/ - Reddit: "https://www.reddit.com/user/zenglll", - Twitter: "https://twitter.com/zzzZeng3", + // Reddit: "https://www.reddit.com/user/zenglll", + BiliBili: "https://space.bilibili.com/437799265?spm_id_from=333.1007.0.0", + /* V2EX: { + link: "https://www.v2ex.com/?r=zzzlll0", + icon: "\n" + + " \n" + + " \n" + + "" + }, */ }, }, // navbar @@ -53,10 +67,114 @@ export default hopeTheme({ "/zh/demo/encrypt.html": ["1234"], }, }, + markdown: { + highlighter: { + type: "shiki", + // 配置项 + langs: ['ts', 'json', 'vue', 'md', 'bash', 'diff', 'java', 'javascript', 'shell', 'bash', 'markdown', 'regex', 'html', 'c', 'sql', 'xml', 'properties', 'python', 'kotlin', 'dockerfile', 'c++', 'yaml'], + }, + // 启用 kotlin 交互演示 + kotlinPlayground: true, + align: true, + attrs: true, + // 启用 GFM 警告 + alert: true, + // install chart.js before enabling it + // chart: true, + + codeTabs: true, + + // insert component easily + // component: true, + + demo: true, + + // install echarts before enabling it + // echarts: true, + + figure: true, + + // install flowchart.ts before enabling it + // flowchart: true, + + // gfm requires mathjax-full to provide tex support + // gfm: true, + + imgLazyload: true, + imgSize: true, + include: true, + + // install katex before enabling it + // katex: true, + + // install mathjax-full before enabling it + // mathjax: true, + mark: true, + + // install mermaid before enabling it + // mermaid: true, + + playground: { + presets: ["ts", "vue"], + }, + + // install reveal.js before enabling it + // revealJs: { + // plugins: ["highlight", "math", "search", "notes", "zoom"], + // }, + + stylize: [ + { + matcher: "Recommended", + replacer: ({tag}) => { + if (tag === "em") + return { + tag: "Badge", + attrs: {type: "tip"}, + content: "Recommended", + }; + }, + }, + ], + sub: true, + sup: true, + tabs: true, + vPre: true, + + // install @vue/repl before enabling it + // vuePlayground: true, + }, plugins: { + git: { + createdTime: true, + updatedTime: true, + contributors: false, + // contributors: {info: [{username: 'zengsl', alias: 'Leo'}]}, + }, + slimsearch: { + // 索引全部内容 + indexContent: true, + // 为分类和标签添加索引 + customFields: [ + { + getter: (page) => page.frontmatter.category, + formatter: "分类:$content", + }, + { + getter: (page) => page.frontmatter.tag, + formatter: "标签:$content", + }, + ], + }, blog: true, - + icon: { + assets: ["iconify", "fontawesome", "fontawesome-with-brands"] + }, + // seo: false, + seo: { + fallBackImage: hostname + "/assets/images/greatway_snow.jpeg" + }, // install @waline/client before enabling it // WARNING: This is a test server for demo only. // You should create and use your own comment service in production. @@ -97,132 +215,15 @@ export default hopeTheme({ */ // all features are enabled for demo, only preserve features you need here - mdEnhance: { - align: true, - attrs: true, - - // install chart.js before enabling it - // chart: true, - - codetabs: true, - - // insert component easily - // component: true, - - demo: true, - - // install echarts before enabling it - // echarts: true, - - figure: true, - - // install flowchart.ts before enabling it - // flowchart: true, - - // gfm requires mathjax-full to provide tex support - // gfm: true, - - imgLazyload: true, - imgSize: true, - include: true, - - // install katex before enabling it - // katex: true, - - // install mathjax-full before enabling it - // mathjax: true, - - mark: true, - - // install mermaid before enabling it - // mermaid: true, - - playground: { - presets: ["ts", "vue"], - }, - - // install reveal.js before enabling it - // revealJs: { - // plugins: ["highlight", "math", "search", "notes", "zoom"], - // }, - - stylize: [ - { - matcher: "Recommended", - replacer: ({tag}) => { - if (tag === "em") - return { - tag: "Badge", - attrs: {type: "tip"}, - content: "Recommended", - }; - }, - }, - ], - sub: true, - sup: true, - tabs: true, - vPre: true, - - // install @vue/repl before enabling it - // vuePlayground: true, - }, - + photoSwipe: true, // uncomment these if you want a PWA - // pwa: { - // favicon: "/favicon.ico", - // cacheHTML: true, - // cachePic: true, - // appendBase: true, - // apple: { - // icon: "/assets/icon/apple-icon-152.png", - // statusBarColor: "black", - // }, - // msTile: { - // image: "/assets/icon/ms-icon-144.png", - // color: "#ffffff", - // }, - // manifest: { - // icons: [ - // { - // src: "/assets/icon/chrome-mask-512.png", - // sizes: "512x512", - // purpose: "maskable", - // type: "image/png", - // }, - // { - // src: "/assets/icon/chrome-mask-192.png", - // sizes: "192x192", - // purpose: "maskable", - // type: "image/png", - // }, - // { - // src: "/assets/icon/chrome-512.png", - // sizes: "512x512", - // type: "image/png", - // }, - // { - // src: "/assets/icon/chrome-192.png", - // sizes: "192x192", - // type: "image/png", - // }, - // ], - // shortcuts: [ - // { - // name: "Demo", - // short_name: "Demo", - // url: "/demo/", - // icons: [ - // { - // src: "/assets/icon/guide-maskable.png", - // sizes: "192x192", - // purpose: "maskable", - // type: "image/png", - // }, - // ], - // }, - // ], - // }, - // }, + // pwa: false, + pwa: { + favicon: "/favicon.ico", + cacheHTML: true, + cacheImage: true, + appendBase: true, + update: 'available', + }, }, -}); +}, {custom: true}); diff --git a/blog/README.md b/blog/README.md index 7b420f2..f1945dc 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,18 +1,25 @@ --- home: true layout: BlogHome -icon: home +icon: fa-solid fa-house title: 博客主页 -heroImage: /logo.svg -heroText: FreeCode -heroFullScreen: false +# heroImage: /logo.svg +#heroText: FreeCode +heroText: zzz's Blog tagline: 记录翻山的过程。 +heroFullScreen: false +#bgImage: https://zengsl.me/assets/images/greatway_snow.jpeg +#tagline: 记录翻山的过程。 #projects: # - icon: project # name: 项目名称 # desc: 项目详细描述 # link: https://你的项目链接 - +copyright: Theme by VuePress Theme Hope | Copyright © 2025 zzz footer: Day Day Coding +head: + - - meta + - name: keywords + content: Technology,Coding,Blog,技术,编程,博客,zengsl,zzz,coder,java,go,python,docker --- diff --git a/blog/about/intro.md b/blog/about/intro.md index ff1e12a..164656a 100644 --- a/blog/about/intro.md +++ b/blog/about/intro.md @@ -1,11 +1,12 @@ --- icon: circle-info cover: /assets/images/cover3.jpg +timeline: false --- # 介绍页 ::: tip -一个代码的搬运工!!! +小镇代码的搬运工!!! ::: -这是一个PWA测试 \ No newline at end of file +爱好篮球、交友和编程。 \ No newline at end of file diff --git a/blog/back/Dubbo.md b/blog/back/Dubbo.md.bak similarity index 88% rename from blog/back/Dubbo.md rename to blog/back/Dubbo.md.bak index af8fe50..65dfcdc 100644 --- a/blog/back/Dubbo.md +++ b/blog/back/Dubbo.md.bak @@ -5,7 +5,7 @@ category: tag: - Dubbo - RPC - +timeline: false --- # Dubbo diff --git a/blog/back/SpringBoot/README.md b/blog/back/SpringBoot/README.md deleted file mode 100644 index 8056beb..0000000 --- a/blog/back/SpringBoot/README.md +++ /dev/null @@ -1,5 +0,0 @@ ---- - ---- -# 介绍 - diff --git a/blog/back/big-data/README.md b/blog/back/big-data/README.md new file mode 100644 index 0000000..aa81687 --- /dev/null +++ b/blog/back/big-data/README.md @@ -0,0 +1,24 @@ +--- + +date: 2024-10-18 +category: + - 后端 + - 大数据 +tag: + - 分布式 + - 架构 +--- + +# 数据流转架构 + +# 技术选型 + +## 数据同步 + + +常见数据同步软件:DataX、Sqoop、Maxwell、Canal + +| 框架 | DataX/Sqoop | Maxwell/Canal | +|---------|----------------------------------------------------------------------------------|------------------------------------| +| 对数据库要求 | 原理是基于查询,若想通过select查询获取新增及变化数据,就要求数据表中存在create_time、update_time等字段,然后根据这些字段获取变更数据 | 要求数据库记录变更记录,如:MySQL要开启binlog | +| 数据等中间状态 | 由于是离线批量同步,故若一条数据在一天中变化多次,该方案只能获取最后一个状态,中间状态无法获取。 | 由于是实时获取所有的数据变更操作,所以可以获取变更数据中所有中间状态 | diff --git "a/blog/back/C\350\257\255\350\250\200.md" b/blog/back/c.md similarity index 100% rename from "blog/back/C\350\257\255\350\250\200.md" rename to blog/back/c.md diff --git a/blog/back/distributed/distributedLocks.md b/blog/back/distributed/distributed-locks.md similarity index 100% rename from blog/back/distributed/distributedLocks.md rename to blog/back/distributed/distributed-locks.md diff --git a/blog/back/distributed/distributedTransaction.md b/blog/back/distributed/distributed-transaction.md similarity index 100% rename from blog/back/distributed/distributedTransaction.md rename to blog/back/distributed/distributed-transaction.md diff --git a/blog/back/distributed/Zookeeper.md b/blog/back/distributed/zookeeper.md similarity index 100% rename from blog/back/distributed/Zookeeper.md rename to blog/back/distributed/zookeeper.md diff --git "a/blog/back/Java\344\275\223\347\263\273\344\270\255\344\270\200\344\272\233\346\225\260\345\255\227.md" b/blog/back/java-number.md similarity index 100% rename from "blog/back/Java\344\275\223\347\263\273\344\270\255\344\270\200\344\272\233\346\225\260\345\255\227.md" rename to blog/back/java-number.md diff --git "a/blog/back/java/concurrency/1.java\345\244\232\347\272\277\347\250\213-\345\237\272\347\241\200.md" b/blog/back/java/concurrency/base.md similarity index 100% rename from "blog/back/java/concurrency/1.java\345\244\232\347\272\277\347\250\213-\345\237\272\347\241\200.md" rename to blog/back/java/concurrency/base.md diff --git "a/blog/back/java/concurrency/2.Java\345\244\232\347\272\277\347\250\213-JUC.md" b/blog/back/java/concurrency/juc.md similarity index 100% rename from "blog/back/java/concurrency/2.Java\345\244\232\347\272\277\347\250\213-JUC.md" rename to blog/back/java/concurrency/juc.md diff --git a/blog/back/java/jvm/1.buildJdk.md b/blog/back/java/jvm/build-jdk.md similarity index 99% rename from blog/back/java/jvm/1.buildJdk.md rename to blog/back/java/jvm/build-jdk.md index 2dc7b01..02fe140 100644 --- a/blog/back/java/jvm/1.buildJdk.md +++ b/blog/back/java/jvm/build-jdk.md @@ -1,5 +1,5 @@ --- -order: 1 +order: 2 date: 2020-09-11 --- # 构建JDK diff --git a/blog/back/java/jvm/jdkCommand.md b/blog/back/java/jvm/jdk-command.md similarity index 99% rename from blog/back/java/jvm/jdkCommand.md rename to blog/back/java/jvm/jdk-command.md index 9c8c971..9004af3 100644 --- a/blog/back/java/jvm/jdkCommand.md +++ b/blog/back/java/jvm/jdk-command.md @@ -1,5 +1,5 @@ --- -order: 2 +order: 3 date: 2021-12-02 --- # JDK命令 diff --git a/blog/back/java/jvm/0.note.md b/blog/back/java/jvm/note.md similarity index 99% rename from blog/back/java/jvm/0.note.md rename to blog/back/java/jvm/note.md index 04e8973..00140bc 100644 --- a/blog/back/java/jvm/0.note.md +++ b/blog/back/java/jvm/note.md @@ -1,7 +1,9 @@ --- date: 2020-09-11 +order: 1 --- > 记录Java虚拟机相关知识 + # 学习笔记 ## 类加载器 diff --git a/blog/back/message-queue/README.md b/blog/back/message-queue/README.md index d10656b..c80f39d 100644 --- a/blog/back/message-queue/README.md +++ b/blog/back/message-queue/README.md @@ -9,4 +9,6 @@ date: 2023-12-08 ## 目录 -- [Kafka](kafka) +- [Kafka](https://kafka.apache.org/) +- [RabbitMQ](https://www.rabbitmq.com/) +- [RocketMQ](https://rocketmq.apache.org/) diff --git a/blog/back/message-queue/Kafka.md b/blog/back/message-queue/kafka.md similarity index 100% rename from blog/back/message-queue/Kafka.md rename to blog/back/message-queue/kafka.md diff --git a/blog/back/mybatis/README.md b/blog/back/mybatis/README.md index 5347425..4638243 100644 --- a/blog/back/mybatis/README.md +++ b/blog/back/mybatis/README.md @@ -10,6 +10,6 @@ date: 2020-10-15 整理Mybatis的相关知识点 -- [Mapper自动刷新](Mapper自动刷新.md) +- [Mapper自动刷新](mapper-auto-refresh.md) - [Mybatis-Plus](mybatis-plus.md) - [其他](other.md) \ No newline at end of file diff --git "a/blog/back/mybatis/Mapper\350\207\252\345\212\250\345\210\267\346\226\260.md" b/blog/back/mybatis/mapper-auto-refresh.md similarity index 100% rename from "blog/back/mybatis/Mapper\350\207\252\345\212\250\345\210\267\346\226\260.md" rename to blog/back/mybatis/mapper-auto-refresh.md diff --git a/blog/back/Netty.md b/blog/back/netty.md.bak similarity index 84% rename from blog/back/Netty.md rename to blog/back/netty.md.bak index 77a7b95..1f52de9 100644 --- a/blog/back/Netty.md +++ b/blog/back/netty.md.bak @@ -6,7 +6,7 @@ category: tag: - 网络通信 - +timeline: false --- # Netty diff --git a/blog/back/spring-boot/README.md b/blog/back/spring-boot/README.md new file mode 100644 index 0000000..942cd82 --- /dev/null +++ b/blog/back/spring-boot/README.md @@ -0,0 +1,512 @@ +--- +category: + - 后端 + - SpringBoot +description: Figure the load sequence between load.path 、BOOT-INF/classes/ and BOOT-INF/lib/ by analyzing Spring Boot Loader - 分析Spring Boot Loader中相关目录中类加载顺序 +head: + - - meta + - name: keywords + content: SpringBootLoader,load sequence,BOOT-INF,loader.path,库加载顺序 +tag: + - Spring Boot + - Spring Boot Loader +date: 2024-12-30 +--- +# Spring Boot Loader + +> 当前版本为 3.2.x + +[SpringBoot文档-可执行的Jar文件](https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/executable-jar.html#appendix.executable-jar) + +[SpringBoot源码地址](https://github.com/spring-projects/spring-boot) + +[本文用于测试的Demo项目](https://github.com/zengsl/springboot-load-demo) + +## 目的 + +::: tip +微服务适合利用Docker镜像Cache特性 参考[SpringBoot 容器镜像](https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/container-images.html#container-images.dockerfiles) +::: + +起因:之前一直使用Docker部署SpringBoot Jar,但是实际运行过着中每次发版本上传镜像时间过久,因此考虑将lib分离出来单独部署,以此减小Jar的体积。但是分离之后发现项目中覆盖spring和sentinel的代码没有正确加载,导致覆盖失败。所以打算分析下SpringBoot Loader到底是如何加载类的。 + +通过[Loader源码](https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-loader/)运行一个由[SpringBoot插件](https://docs.spring.io/spring-boot/docs/3.2.12/maven-plugin/reference/htmlsingle/)生成标准的[可执行的Jar文件](https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/executable-jar.html#appendix.executable-jar): 包含BOOT-INF/classes、BOOT-INF/lib等内容。 + +观察类加载情况,了解BOOT-INF/classes、BOOT-INF/lib、load.path指定路径的加载顺序,用于分析class加载。 + +## 结论 + +按照`load.path(绝对路径顺序) -> BOOT-INF/classes/(绝对路径顺序)` -> `BOOT-INF/lib/(classpath.idx顺序)`形成列表,通过收集ClassLoader所需classpath、创建ClassLoader两个阶段做好类加载器初始化的准备工作 +。最终类加载寻找class/jar时会按照文件收集顺序优先的原则进行类加载,也就是源码中url收集顺序,参见下文。 + +**Jar匹配逻辑简述** + +```java {11} title="URLClassLoader查找class" +// URLClassLoader +protected Class findClass(final String name) + throws ClassNotFoundException +{ + final Class result; + try { + result = AccessController.doPrivileged( + new PrivilegedExceptionAction<>() { + public Class run() throws ClassNotFoundException { + String path = name.replace('.', '/').concat(".class"); + Resource res = ucp.getResource(path, false); + if (res != null) { + try { + return defineClass(name, res); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } catch (ClassFormatError e2) { + if (res.getDataError() != null) { + e2.addSuppressed(res.getDataError()); + } + throw e2; + } + } else { + return null; + } + } + }, acc); + } catch (java.security.PrivilegedActionException pae) { + throw (ClassNotFoundException) pae.getException(); + } + if (result == null) { + throw new ClassNotFoundException(name); + } + return result; +} +``` + + +```java title="URLClassPath寻找class" +// URLClassPath +public Resource getResource(String name, boolean check) { + if (DEBUG) { + System.err.println("URLClassPath.getResource(\"" + name + "\")"); + } + + Loader loader; + // 每一个url对应一个loader + for (int i = 0; (loader = getLoader(i)) != null; i++) { + Resource res = loader.getResource(name, check); + if (res != null) { + return res; + } + } + return null; +} +``` +![loadUrl](images/img_4.png) + +## 源码分析 + +### 环境准备 + +#### 可执行Jar + +创建一个SpringBoot项目,通过SpringBoot插件进行package。生成的jar将使用`PropertiesLauncher`加载 + +```xml title="pom.xml" + + ${project.artifactId} + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + false + false + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + ZIP + + + + + nothing + nothing + + + + + + + repackage + + + + + + +``` + +Jar内部结构: + +```text +example.jar + | + +-META-INF + | +-MANIFEST.MF + +-org + | +-springframework + | +-boot + | +-loader + | +- + +-BOOT-INF + +-classes + | +-mycompany + | +-project + | +-YourClasses.class + +-lib + +-dependency1.jar + +-dependency2.jar +``` + +![jar](images/img.png) + + +```text title="META-INF/MANIFEST.MF" +Manifest-Version: 1.0 +Created-By: Maven JAR Plugin 3.4.1 +Build-Jdk-Spec: 21 +Main-Class: org.springframework.boot.loader.launch.PropertiesLauncher +Start-Class: com.ruoyi.gateway.RuoYiGatewayApplication +Spring-Boot-Version: 3.2.8 +Spring-Boot-Classes: BOOT-INF/classes/ +Spring-Boot-Lib: BOOT-INF/lib/ +Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx +Spring-Boot-Layers-Index: BOOT-INF/layers.idx +``` + +#### 源码启动 + +复制PropertiesLauncherTests为PropertiesLauncherTests2,执行测试方法 + +大致流程如下: + +Loader将寻找并解析`META-INF/MANIFEST.MF`内的Main-Class属性,执行`main`方法。准备好classLoader和类路径等信息,最终启动`Start-Class`对应的实际项目类。 + +![PropertiesLauncher_main](images/PropertiesLauncher_main.svg) + +> 如果使用的测试项目比较复杂的话,通过Loader引导后在启动项目代码的过程中最终可能无法正常启动,会出现`NoClassDefFoundError`.分析**可能**的原因:当前源码启动是在SpringBoot项目路径下,和内部Spring项目的相关Jar包产生影响导致类加载顺序问题。 +> 即使内部项目最终无法正常启动也不影响此次试验的目的:了解BOOT-INF/classes、BOOT-INF/lib、load.path指定路径的加载顺序 + +```java + +@ExtendWith(OutputCaptureExtension.class) +@AssertFileChannelDataBlocksClosed +class PropertiesLauncherTests2 extends AbstractLauncherTests { + + private PropertiesLauncher launcher; + + private ClassLoader contextClassLoader; + + private CapturedOutput output; + + @BeforeEach + void setup(CapturedOutput capturedOutput) { + this.contextClassLoader = Thread.currentThread().getContextClassLoader(); + System.setProperty("loader.home", new File("/xxxx/target").getAbsolutePath()); + this.output = capturedOutput; + } + + @AfterEach + void close() throws Exception { + Thread.currentThread().setContextClassLoader(this.contextClassLoader); + System.clearProperty("loader.home"); + System.clearProperty("loader.path"); + System.clearProperty("loader.main"); + System.clearProperty("loader.config.name"); + System.clearProperty("loader.config.location"); + System.clearProperty("loader.system"); + System.clearProperty("loader.classLoader"); + if (this.launcher != null) { + this.launcher.close(); + } + } + + @Test + void testJarWithExternalLib() throws Exception { + System.setProperty("loader.path", "lib/"); +// System.setProperty("loader.debug", "true"); + // 准备好的标准的标准可执行jar + File explodedRoot = explode(new File("xxx.jar")); + this.launcher = new PropertiesLauncher(new ExplodedArchive(explodedRoot)); + this.launcher.launch(new String[0]); + waitFor("Hello World"); + } + + + @Test + void testJarWithInnerLib() throws Exception { +// System.setProperty("loader.debug", "true"); + // 准备好的标准的标准可执行jar + File explodedRoot = explode(new File("xxx.jar")); + this.launcher = new PropertiesLauncher(new ExplodedArchive(explodedRoot)); + this.launcher.launch(new String[0]); + waitFor("Hello World"); + } + + @Test + void testJarWithInnerAndExternalLib() throws Exception { + System.setProperty("loader.path", "lib/"); + File explodedRoot = explode(new File("xx.jar")); + this.launcher = new PropertiesLauncher(new ExplodedArchive(explodedRoot)); + this.launcher.launch(new String[0]); + } +} +``` + +补充:常规命令行启动 + +```shell +java -Dloader.path=lib/ -jar ruoyi-file-web.jar +#nohup java -verbose:class -Dloader.path=lib/ -jar ruoyi-gateway.jar > run.log 2>&1 & +``` + + +### 阅读源码 + +开启Debug日志,辅助观察设置-Dloader.debug=true或者临时修改下面代码。 + +```java {4,5} +// DebugLogger +private static final DebugLogger disabled; + static { +// disabled = Boolean.getBoolean(ENABLED_PROPERTY) ? null : new DisabledDebugLogger(); + disabled = null; + } +``` + +通过打印的日志可知: + +> 通过注释运行项目pom.xml中includes内容可以控制lib打包方式,重新package + +可执行Jar优先加载load.path指定的lib,然后是BOOT-INF/classes/,并未打印BOOT-INF/lib/相关信息,通过后续的观察可以发现BOOT-INF/lib内容是在创建类加载器时根据classpath.idx中的内容生成对应的url地址。 + +所以结论为上述资源路径加载顺序为:load.path -> BOOT-INF/classes/ -> BOOT-INF/lib/,详细过程如下: + + +- 源码启动入口 + +```java {6,7} +protected void launch(String[] args) throws Exception { + if (!isExploded()) { + Handlers.register(); + } + try { + // 获取类加载器 并设置寻找classpath和resource的url + // 一般来说getClassPathUrls()将先获取到load.path的内容,最后增加BOOT-INF/classes + ClassLoader classLoader = createClassLoader(getClassPathUrls()); + String jarMode = System.getProperty("jarmode"); + String mainClassName = hasLength(jarMode) ? JAR_MODE_RUNNER_CLASS_NAME : getMainClass(); + launch(classLoader, mainClassName, args); + } + catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } + + protected void launch(ClassLoader classLoader, String mainClassName, String[] args) throws Exception { + Thread.currentThread().setContextClassLoader(classLoader); + Class mainClass = Class.forName(mainClassName, false, classLoader); + Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); + mainMethod.setAccessible(true); + // 执行Start-Class对应的启动类,启动真正的项目启动器,引导工作结束。 + mainMethod.invoke(null, new Object[] { args }); + } +``` + + +- 创建类加载器核心代码 + +```java + +@Override +protected ClassLoader createClassLoader(Collection urls) throws Exception { + String loaderClassName = getProperty("loader.classLoader"); + // 根据classpath.idx补充classpath + if (this.classPathIndex != null) { + urls = new ArrayList<>(urls); + urls.addAll(this.classPathIndex.getUrls()); + } + if (loaderClassName == null) { + return super.createClassLoader(urls); + } + ClassLoader parent = getClass().getClassLoader(); + ClassLoader classLoader = new LaunchedClassLoader(false, urls.toArray(new URL[0]), parent); + debug.log("Classpath for custom loader: %s", urls); + classLoader = wrapWithCustomClassLoader(classLoader, loaderClassName); + debug.log("Using custom class loader: %s", loaderClassName); + return classLoader; +} + +private ClassLoader createClassLoader(URL[] urls) { + // ClassLoaders#AppClassLoader + ClassLoader parent = getClass().getClassLoader(); + return new LaunchedClassLoader(isExploded(), getArchive(), urls, parent); +} +// LaunchedClassLoader 继承关系,urls参数最终将交给JarUrlClassLoader和URLClassLoader +// LaunchedClassLoader -> JarUrlClassLoader -> URLClassLoader +``` + +![cl](images/img_1.png) + + +- 收集所有资源路径URL + + +getClassPathUrls() 将先后收集load.path 、BOOT-INF/classes最终返回一个URL集合。 + +BOOT-INF/lib是在类加载时处理 + +如果`load.path`所配置路径是相对路径则需要结合Home目录来计算最终路径。 Home目录默认值根据`${user.dir}`指定,可以用过`load.home`来修改。 + +```java PropertiesLauncher#getClassPathUrls() +// load.path设置为lib/ +@Override +protected Set getClassPathUrls() throws Exception { + Set urls = new LinkedHashSet<>(); + for (String path : getPaths()) { + path = cleanupPath(handleUrl(path)); + urls.addAll(getClassPathUrlsForPath(path)); + } + // 从根路径下获取classpath URL 当demo根路径为Jar的根路径,也就是exploded下的BOOT-INF/lib、BOOT-INF/classes、org 获取BOOT-INF/lib、BOOT-INF/classes中且不在于classpath.idx的内容 + // 当前demo最终加入BOOT-INF/classes + urls.addAll(getClassPathUrlsForRoot()); + debug.log("Using class path URLs %s", urls); + return urls; +} + +private Set getClassPathUrlsForPath(String path) throws Exception { + File file = (!isAbsolutePath(path)) ? new File(this.homeDirectory, path) : new File(path); + Set urls = new LinkedHashSet<>(); + // 非绝对路径 + if (!"/".equals(path)) { + // 是文件夹 lib/ + if (file.isDirectory()) { + try (ExplodedArchive explodedArchive = new ExplodedArchive(file)) { + debug.log("Adding classpath entries from directory %s", file); + // 加入当前路径 lib/对应的URI对象 + urls.add(file.toURI().toURL()); + // explodedArchive.getClassPathUrls(this::isArchive) 递归处理lib/下的jar包 和zip包 将调用ExplodedArchive#getClassPathUrls + urls.addAll(explodedArchive.getClassPathUrls(this::isArchive)); + } + } + } + if (!file.getPath().contains(NESTED_ARCHIVE_SEPARATOR) && isArchive(file.getName())) { + debug.log("Adding classpath entries from jar/zip archive %s", path); + urls.add(file.toURI().toURL()); + } + // 处理内嵌路径,会处理BOOT-INF等可执行Jar内部文件夹。如下图所示 + // 当前demo的返回值为0 + Set nested = getClassPathUrlsForNested(path); + if (!nested.isEmpty()) { + debug.log("Adding classpath entries from nested %s", path); + urls.addAll(nested); + } + return urls; +} + + +@Override +public Set getClassPathUrls(Predicate includeFilter, Predicate directorySearchFilter) + throws IOException { + Set urls = new LinkedHashSet<>(); + LinkedList files = new LinkedList<>(listFiles(this.rootDirectory)); + // 这里会处理所有的file对象,如果file是目录则会将其内部内容加入队列顶部继续重复处理。 + // 判断所有的file对象,匹配所有与load.path名字相同的目录 或者 load.path下面的所有archive(jar或者zip) + while (!files.isEmpty()) { + File file = files.poll(); + if (SKIPPED_NAMES.contains(file.getName())) { + continue; + } + String entryName = file.toURI().getPath().substring(this.rootUriPath.length()); + Entry entry = new FileArchiveEntry(entryName, file); + if (entry.isDirectory() && directorySearchFilter.test(entry)) { + files.addAll(0, listFiles(file)); + } + if (includeFilter.test(entry)) { + urls.add(file.toURI().toURL()); + } + } + return urls; +} + + +private boolean isArchive(Entry entry) { + return isArchive(entry.name()); +} + +private boolean isArchive(String name) { + name = name.toLowerCase(Locale.ENGLISH); + return name.endsWith(".jar") || name.endsWith(".zip"); +} + +private Set getClassPathUrlsForRoot() throws Exception { + debug.log("Adding classpath entries from root archive %s", this.archive); + // isIncludedOnClassPathAndNotIndexed 包含在classpath中且没有索引 : 是否在BOOT-INF/classes/或者BOOT-INF/lib/下 且classpath.idx不包含该内容 + // Archive.ALL_ENTRIES 所有archive都满足条件 + return this.archive.getClassPathUrls(this::isIncludedOnClassPathAndNotIndexed, Archive.ALL_ENTRIES); +} + +protected boolean isIncludedOnClassPathAndNotIndexed(Entry entry) { + if (!isIncludedOnClassPath(entry)) { + return false; + } + return (this.classPathIndex == null) || !this.classPathIndex.containsEntry(entry.name()); +} +``` + +![boot-inf](images/img_2.png) + + +- 类加载器LaunchedClassLoader 打条件断点`name.contains("SLF4JLogger")`观察加载过程。 + +通过下图的观察可以发现,最终是由`JarUrlClassLoader`加载,`JarUrlClassLoader`中会调用父类`URLClassLoader` ,加载的archive由`JarUrlClassLoader`和`URLClassLoader`构造方法中设置。 + +```java + +// LaunchedClassLoader#loadClass +@Override +protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.startsWith(JAR_MODE_PACKAGE_PREFIX) || name.equals(JAR_MODE_RUNNER_CLASS_NAME)) { + try { + Class result = loadClassInLaunchedClassLoader(name); + if (resolve) { + resolveClass(result); + } + return result; + } + catch (ClassNotFoundException ex) { + // Ignore + } + } + // 打条件断点进行观察 name.contains("SLF4JLogger") + return super.loadClass(name, resolve); +} + +``` + +![cl2](images/img_3.png) + diff --git a/blog/back/spring-boot/class-override.md b/blog/back/spring-boot/class-override.md new file mode 100644 index 0000000..5a797a7 --- /dev/null +++ b/blog/back/spring-boot/class-override.md @@ -0,0 +1,60 @@ +--- +category: + - 后端 + - SpringBoot +description: Analyze the class override in Spring Boot Project - 分析在SpringBoot项目中类覆盖的原理 +head: + - - meta + - name: keywords + content: classpath,class override,类覆盖,SpringBoot类覆盖 +tag: + - Spring Boot + - class override + - 类覆盖 + - classpath +date: 2025-01-10 +--- +# Class 覆盖 + +## 项目结构 + +一般的SpringBoot项目结构 + +```text title="可执行Jar" +example.jar +| ++-META-INF +| +-MANIFEST.MF ++-org +| +-springframework +| +-boot +| +-loader +| +- ++-BOOT-INF ++-classes +| +-mycompany +| +-project +| +-YourClasses.class ++-lib ++-dependency1.jar ++-dependency2.jar +``` + +## 覆盖操作 + +项目采用Maven多模块结构,应用模块会依赖一些common、util、base公用模块。当第三方包存在问题,无法通过升级版方式来修复时,我们通常会用最“简单有效”的方式进行处理——同名覆盖。 + +同名覆盖也就是在项目中创建与需要重写的第三方包内的class文件相同限定名(包名+类名),我们可能会在“应用模块”或者“公用模块”进行重写操作。 +重写类在构造方法或者初始化方法中进行内容打印,方便服务部署时观察覆盖是否成功,打印内容:“覆盖xxxx” + +例如: + +![demo](images/img_7.png) + +## 注意事项 +要保障正确覆盖class,就要保障类加载的过程中我们重写的覆盖类要优先于第三方提供的class + +“应用模块”或者“公用模块”两种常见重写情况: + +- “应用模块”一般来说重写之后的内容都属性classpath内,拥有最高优先级。但是如果启动jar时指定了-Dloader.path那么有可能被loader.path的内容覆盖 +- “公用模块”一般来说会和第三方包一样都放置在WEB-INF/lib或者BOOT-INF/lib内,也就是说和第三方包同级了。类加载的顺序取决于文件名顺序,所以需要保障“公用模块”排在前面 diff --git a/blog/back/spring-boot/images/PropertiesLauncher_main.png b/blog/back/spring-boot/images/PropertiesLauncher_main.png new file mode 100644 index 0000000..0e773db Binary files /dev/null and b/blog/back/spring-boot/images/PropertiesLauncher_main.png differ diff --git a/blog/back/spring-boot/images/PropertiesLauncher_main.svg b/blog/back/spring-boot/images/PropertiesLauncher_main.svg new file mode 100644 index 0000000..299d342 --- /dev/null +++ b/blog/back/spring-boot/images/PropertiesLauncher_main.svg @@ -0,0 +1,428 @@ + + +ActorPropertiesLauncherArchiveDebugLoggerLauncherClassPathIndexFileSystemPropertyUtilsExplodedArchiveHandlersLaunchedClassLoader1:main1.1:<<create>>1.1.1:create1.1.2:<<create>>1.1.2.1:initializeProperties1.1.2.1.1:getProperty1.1.2.1.2:getProperty1.1.2.1.3:getPropertyWithDefault1.1.2.1.4:getResource1.1.2.1.5:log1.1.2.1.6:log1.1.2.1.7:loadResource1.1.2.2:getClassPathIndex1.2:getArgs1.2.1:getProperty1.2.1.1:getProperty1.2.1.1.1:toCamelCase1.2.1.1.2:getProperty1.2.1.1.3:getResolvedProperty1.2.1.1.4:getResolvedProperty1.2.1.1.5:<<create>>1.2.1.1.6:getManifestValue1.2.1.1.7:getResolvedProperty1.2.1.1.8:getManifestValue1.2.1.1.9:getResolvedProperty1.2.1.1.10:resolvePlaceholders1.2.2:merge1.3:launch1.1.1.1:create1.1.1.1.1:create1.1.2.2.1:getClassPathIndexFileLocation1.1.2.2.2:loadIfPossible1.3.1:register1.3.2:createClassLoader1.3.2.1:createClassLoader1.3.2.1.1:<<create>>1.3.3:hasLength1.3.4:launch1.3.1.1:resetCachedUrlHandlers diff --git a/blog/back/spring-boot/images/image.png b/blog/back/spring-boot/images/image.png new file mode 100644 index 0000000..7ab8a8f Binary files /dev/null and b/blog/back/spring-boot/images/image.png differ diff --git a/blog/back/spring-boot/images/image5.png b/blog/back/spring-boot/images/image5.png new file mode 100644 index 0000000..5782c2c Binary files /dev/null and b/blog/back/spring-boot/images/image5.png differ diff --git a/blog/back/spring-boot/images/image6.png b/blog/back/spring-boot/images/image6.png new file mode 100644 index 0000000..40f1ac9 Binary files /dev/null and b/blog/back/spring-boot/images/image6.png differ diff --git a/blog/back/spring-boot/images/image7.png b/blog/back/spring-boot/images/image7.png new file mode 100644 index 0000000..0982f63 Binary files /dev/null and b/blog/back/spring-boot/images/image7.png differ diff --git a/blog/back/spring-boot/images/image8.png b/blog/back/spring-boot/images/image8.png new file mode 100644 index 0000000..303cd44 Binary files /dev/null and b/blog/back/spring-boot/images/image8.png differ diff --git a/blog/back/spring-boot/images/img.png b/blog/back/spring-boot/images/img.png new file mode 100644 index 0000000..ad849c2 Binary files /dev/null and b/blog/back/spring-boot/images/img.png differ diff --git a/blog/back/spring-boot/images/img_1.png b/blog/back/spring-boot/images/img_1.png new file mode 100644 index 0000000..fe66228 Binary files /dev/null and b/blog/back/spring-boot/images/img_1.png differ diff --git a/blog/back/spring-boot/images/img_2.png b/blog/back/spring-boot/images/img_2.png new file mode 100644 index 0000000..3fc35be Binary files /dev/null and b/blog/back/spring-boot/images/img_2.png differ diff --git a/blog/back/spring-boot/images/img_3.png b/blog/back/spring-boot/images/img_3.png new file mode 100644 index 0000000..e97ee85 Binary files /dev/null and b/blog/back/spring-boot/images/img_3.png differ diff --git a/blog/back/spring-boot/images/img_4.png b/blog/back/spring-boot/images/img_4.png new file mode 100644 index 0000000..835f1bf Binary files /dev/null and b/blog/back/spring-boot/images/img_4.png differ diff --git a/blog/back/spring-boot/images/img_5.png b/blog/back/spring-boot/images/img_5.png new file mode 100644 index 0000000..40aff88 Binary files /dev/null and b/blog/back/spring-boot/images/img_5.png differ diff --git a/blog/back/spring-boot/images/img_6.png b/blog/back/spring-boot/images/img_6.png new file mode 100644 index 0000000..b8eb69d Binary files /dev/null and b/blog/back/spring-boot/images/img_6.png differ diff --git a/blog/back/spring-boot/images/img_7.png b/blog/back/spring-boot/images/img_7.png new file mode 100644 index 0000000..d68e6a1 Binary files /dev/null and b/blog/back/spring-boot/images/img_7.png differ diff --git a/blog/back/spring-boot/layertool.md b/blog/back/spring-boot/layertool.md new file mode 100644 index 0000000..1fccdc1 --- /dev/null +++ b/blog/back/spring-boot/layertool.md @@ -0,0 +1,129 @@ +--- +category: + - 后端 + - SpringBoot +description: Analyze the SpringBoot layertools from the source code,figure out how to build images with it - 从源码分析Spring Boot分层工具,探讨如何使用它构建镜像,优化构建速度 +head: + - - meta + - name: keywords + content: SpringBoot,layertools,image,Docker分层构建,SpringBoot镜像分层构建,SpringBoot源码 +tag: + - Spring Boot + - Layer Tools + - Debug +date: 2025-01-07 +--- +# Spring Boot Layer Tools + +> 版本Boot 3.2.8 + +## 基本使用 + +> 构建Docker镜像时使用分层工具,可以提高[镜像构建效率](https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/container-images.html#container-images.efficient-images) + +```shell +java -Djarmode=layertools -jar my-app.jar +``` + +```shell +Usage: + java -Djarmode=layertools -jar my-app.jar + +Available commands: + list List layers from the jar that can be extracted + extract Extracts layers from the jar for image creation + help Help about any command +``` + +## 注意事项 + +配置spring-boot-maven-plugin的layers.xml时,采用如下内容: +```xml + + + + + org/springframework/boot/loader/** + + + + + + + + *:*:*SNAPSHOT + + + + + + + + + + application + + module-dependencies + third-dependencies + spring-boot-loader + snapshot-dependencies + + +``` + +定制化**`includeModuleDependencies`放入module-dependencies层时出现一个问题:有时候该文件夹下的BOOT-INF/lib不存在** + +### 准备 + +通过`java -Djarmode=layertools -jar demo.jar`开始调试源码: +- 通过Spring Boot Plugin打包 + +- 启动被调试项目 +```shell +java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 -Djarmode=layertools -jar demo.jar extract +``` + +- 下载源码,导入IDEA,配置远程调试设置调试端口8000并打断点,启动项目 + +通过demo.jar内部的MANIFEST文件可以知道启动类是`org.springframework.boot.loader.launch.JarLauncher` + + +### 源码观察 + + +通过一系列调用,`JarLauncher`会调用`JarModeRunner#main`;同时extract任务是由`ExtractCommand`执行,layers信息是在构造方法中创建,所以需要观察其创建过程中调用的`Layers.get(context)`方法。 + +```java +ExtractCommand(Context context) { + // Layers.get(context)获取层 + this(context, Layers.get(context)); +} + +ExtractCommand(Context context, Layers layers) { + super("extract", "Extracts layers from the jar for image creation", Options.of(DESTINATION_OPTION), + Parameters.of("[...]")); + this.context = context; + // 包含层信息 + this.layers = layers; +} +``` +![ExtractCommand](images/img_5.png) + +```java +// Layers.java +static Layers get(Context context) { + // 获取层信息 + IndexedLayers indexedLayers = IndexedLayers.get(context); + if (indexedLayers == null) { + throw new IllegalStateException("Failed to load layers.idx which is required by layertools"); + } + return indexedLayers; +} +``` + +![layers](images/img_6.png) + +最终可知extract的内容和jar内部layer.idx的内容有关,而layer.idx中定的module-dependencies层本身不存在BOOT-INF/lib,所以导致module-dependencies层内容为空。具体原因需要看jar打包时如何生成layer.idx diff --git a/blog/back/spring-boot/maven-plugin.md b/blog/back/spring-boot/maven-plugin.md new file mode 100644 index 0000000..ecb9008 --- /dev/null +++ b/blog/back/spring-boot/maven-plugin.md @@ -0,0 +1,633 @@ +--- +category: + - 后端 + - SpringBoot +description: Analyze the SpringBootMavenPlugin from the source code,include layers,classpath,image,-源码分析SpringBootMaven插件,分层镜像,类加载器,镜像 +head: + - - meta + - name: keywords + content: SpringBootMavenPlugin,layers,classpath,image,Spring Boot Maven插件,Spring Boot分层镜像,类加载器,镜像优化 +tag: + - Spring Boot + - Maven Plugin + - layers + - classpath + - Debug +date: 2025-01-07 +--- + +# Spring Boot Maven Plugin + +可执行Jar文件 [SpringBoot executable-jar](https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/executable-jar.html#appendix.executable-jar.nested-jars.index-files) + +部署方案:[Docker 镜像](/other/optimize/docker_image.md) + +## 索引文件 + +::: tip 官网文档 +Spring Boot Loader-compatible jar and war archives can include additional index files under the BOOT-INF/ directory. A classpath.idx file can be provided for both jars and wars, and it provides the ordering that jars should be added to the classpath. The layers.idx file can be used only for jars, and it allows a jar to be split into logical layers for Docker/OCI image creation. + +Index files follow a YAML compatible syntax so that they can be easily parsed by third-party tools. These files, however, are not parsed internally as YAML and they must be written in exactly the formats described below in order to be used. +::: + +Spring Boot Loader 兼容的 jar 和 war 归档文件可以在 BOOT-INF/ 目录下包含额外的索引文件。 + +classpath.idx 文件可同时用于 jar 和 war,它提供了将 jar 添加到 classpath 的顺序。 + +**layers.idx 文件只能用于 jar,它允许将 jar 分割成逻辑层,以便创建 Docker/OCI 镜像。** + +索引文件采用与 YAML 兼容的语法,因此第三方工具可以很容易地对其进行解析。不过,这些文件在内部不会被解析为 YAML,必须完全按照下面描述的格式编写才能使用。 + +## 定制layers + +::: tip 官方文档描述 +The layers order is important as it determines how likely previous layers can be cached when part of the application changes. The default order is dependencies, spring-boot-loader, snapshot-dependencies, application. Content that is least likely to change should be added first, followed by layers that are more likely to change. + +层的顺序很重要,因为它决定了当部分应用程序发生变化时,前面的层能被缓存的可能性有多大。默认顺序是依赖项、spring-boot-loader、快照依赖项、应用程序。应首先添加最不可能更改的内容,然后再添加较可能更改的层。 +::: + +从上文中索引文件描述中可知:层的作用对于提高服务部署效率很重要: + +- 生成layers.idx方便创建Docker/OCI 镜像:通过`spring-boot:build-image`生成镜像 + +- 分层Jar Docker镜像构建:通过[SpringBoot layertool](./layertool.md)按层解压jar文件,编写多阶段构建的[Dockerfile](https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/container-images.html#container-images.dockerfiles)充分利用docker cache 。因为这种方式是直接解压出来进行部署,所以和layers.idx没有直接关系了。 + +[packaging.layers.configuration](https://docs.spring.io/spring-boot/docs/3.2.12/maven-plugin/reference/htmlsingle/#packaging.layers.configuration) + +通过构建定制layers.xml文件,可以控制打包后的jar文件中的内容,从而实现更细粒度的控制。结合spring-boot-maven-plugin,可以实现在打包时将不同类型的文件放置在不同的层中,配合[SpringBoot Container Images](https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/container-images.html)从而实现更高效的部署和管理。 + + + + +### layers.xml使用 + +```xml + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + true + ${basedir}/src/main/resources/layers.xml + + + + + + +``` + + +```xml + + + + + org/springframework/boot/loader/** + + + + + + + + + *:*:*SNAPSHOT + + + + + com.ruoyi:ruoyi-*:* + + + + + + + + application + + module-dependencies + third-dependencies + spring-boot-loader + snapshot-dependencies + + + +``` + + +## layers.idx + +> layers.xml与上文一致 + +### 使用经验 + +- 当layers.xml中配置`includeModuleDependencies`标签时,需要整体项目一起打包,也就是直接对parent项目执行`clean + install` + +- 当layers.xml中不配置`includeModuleDependencies`标签时,通过``标签包含本地项目模块,可以单独对当前模块执行`clean + package` + +具体原因可以阅读下文 + +### 打包方式差异问题 + +clean + install全部项目(parent)后jar内部layer.idx文件内容如下: + +```text +- "application": + - "BOOT-INF/classes/" + - "BOOT-INF/classpath.idx" + - "BOOT-INF/layers.idx" + - "META-INF/" +- "module-dependencies": + - "BOOT-INF/lib/module1.jar" + - "BOOT-INF/lib/module2.jar" + - 省略其他包 + +- "third-dependencies": + - "BOOT-INF/lib/HdrHistogram-2.1.12.jar" + - "BOOT-INF/lib/LatencyUtils-2.0.3.jar" + - "BOOT-INF/lib/SparseBitSet-1.2.jar" + - 省略其他包 +- "spring-boot-loader": + - "org/" +- "snapshot-dependencies": + +``` + +clean + package当前模块后jar内部layer.idx文件内容如下: + +```text +- "application": + - "BOOT-INF/classes/" + - "BOOT-INF/classpath.idx" + - "BOOT-INF/layers.idx" + - "META-INF/" +- "module-dependencies": +- "third-dependencies": + - "BOOT-INF/lib/" +- "spring-boot-loader": + - "org/" +- "snapshot-dependencies": +``` + +产生差异代码: + +```java +// ArtifactsLibraries +private boolean isLocal(Artifact artifact) { + // 此处this.localProjects为核心,可以解释“打包方式差异问题”。当整体项目一起打包时,this.localProjects包含所有模块,所以能正确判断出本地模块;如果只单独打包当前模块无法判断 + for (MavenProject localProject : this.localProjects) { + // 判断是否是本地项目 + if (localProject.getArtifact().equals(artifact)) { + return true; + } + // 判断是否是附加artifact + for (Artifact attachedArtifact : localProject.getAttachedArtifacts()) { + if (attachedArtifact.equals(artifact)) { + return true; + } + } + } + return false; +} +``` + + +### 过程分析 + +- 核心类: + +CustomLayers#getLayer(Library library) + +CustomLayersProvider + +IncludeExcludeContentSelector#contains 用于处理include和exclude逻辑 + + +- 核心逻辑: + +判断包是否包含在当前层 + +```java +// IncludeExcludeContentSelector 打个条件断点 getLayer().name.equals("module-dependencies") && ((Library) item).name.startsWith("本地模块前缀") +public boolean contains(T item) { + return isIncluded(item) && !isExcluded(item); +} +// this.includes为其内部判断逻辑 +private boolean isIncluded(T item) { + if (this.includes.isEmpty()) { + return true; + } + for (ContentFilter include : this.includes) { + if (include.matches(item)) { + return true; + } + } + return false; +} + +``` + +当layers.xml中包含`includeModuleDependencies`时,设置一个`Library::isLocal`的判断逻辑,初始化代码为: + +```java +// CustomLayersProvider +private ContentSelector getLibrarySelector(Element element, + Function> filterFactory) { + Layer layer = new Layer(element.getAttribute("layer")); + List includes = getChildNodeTextContent(element, "include"); + List excludes = getChildNodeTextContent(element, "exclude"); + Element includeModuleDependencies = getChildElement(element, "includeModuleDependencies"); + Element excludeModuleDependencies = getChildElement(element, "excludeModuleDependencies"); + List> includeFilters = includes.stream() + .map(filterFactory) + .collect(Collectors.toCollection(ArrayList::new)); + if (includeModuleDependencies != null) { + // 初始化是否为“本地库的判断逻辑” + includeFilters.add(Library::isLocal); + } + List> excludeFilters = excludes.stream() + .map(filterFactory) + .collect(Collectors.toCollection(ArrayList::new)); + if (excludeModuleDependencies != null) { + excludeFilters.add(Library::isLocal); + } + return new IncludeExcludeContentSelector<>(layer, includeFilters, excludeFilters); +} + + +// Library +public boolean isLocal() { + return this.local; +} +``` + +因此判断是否为内部模块的逻辑就在`Library#local`中,那么观察其初始化逻辑: + +```java +// Packager +PackagedLibraries(Libraries libraries, boolean ensureReproducibleBuild) throws IOException { + this.libraries = (ensureReproducibleBuild) ? new TreeMap<>() : new LinkedHashMap<>(); + // 转处理库文件 + libraries.doWithLibraries((library) -> { + if (isZip(library::openStream)) { + // 添加库 + addLibrary(library); + } + }); + if (isLayered() && Packager.this.includeRelevantJarModeJars) { + addLibrary(JarModeLibrary.LAYER_TOOLS); + } + this.unpackHandler = new PackagedLibrariesUnpackHandler(); + this.libraryLookup = this::lookup; +} + +private void addLibrary(Library library) { + String location = getLayout().getLibraryLocation(library.getName(), library.getScope()); + if (location != null) { + String path = location + library.getName(); + Library existing = this.libraries.putIfAbsent(path, library); + Assert.state(existing == null, () -> "Duplicate library " + library.getName()); + } +} +``` + + + +```java +// ArtifactsLibraries +@Override +public void doWithLibraries(LibraryCallback callback) throws IOException { + Set duplicates = getDuplicates(this.artifacts); + for (Artifact artifact : this.artifacts) { + String name = getFileName(artifact); + File file = artifact.getFile(); + LibraryScope scope = SCOPES.get(artifact.getScope()); + if (scope == null || file == null) { + continue; + } + if (duplicates.contains(name)) { + this.log.debug("Duplicate found: " + name); + name = artifact.getGroupId() + "-" + name; + this.log.debug("Renamed to: " + name); + } + LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact); + boolean unpackRequired = isUnpackRequired(artifact); + // 是否为本地文件 + boolean local = isLocal(artifact); + boolean included = this.includedArtifacts.contains(artifact); + callback.library(new Library(name, file, scope, coordinates, unpackRequired, local, included)); + } +} + + +private boolean isLocal(Artifact artifact) { + // 此处this.localProjects为核心,可以解释“打包方式差异问题”。当整体项目一起打包时,this.localProjects包含所有模块,所以能正确判断出本地模块;如果只单独打包当前模块无法判断 + for (MavenProject localProject : this.localProjects) { + // 判断是否是本地项目 + if (localProject.getArtifact().equals(artifact)) { + return true; + } + // 判断是否是附加artifact + for (Artifact attachedArtifact : localProject.getAttachedArtifacts()) { + if (attachedArtifact.equals(artifact)) { + return true; + } + } + } + return false; +} + +// maven-core-3.9.4.jar +/** + * Returns a mutable list of the attached artifacts to this project. It is highly advised not + * to modify this list, but rather use the {@link MavenProjectHelper}. + *

+ * Note: This list will be made read-only in Maven 4.

+ * + * @return the attached artifacts of this project + */ +public List getAttachedArtifacts() { + if (attachedArtifacts == null) { + attachedArtifacts = new ArrayList<>(); + } + return attachedArtifacts; +} + +``` + +根据上述代码可知,本地库即为当前项目对象的attachedArtifacts属性内容,此属性与maven核心逻辑有关系,下面观察maven核心逻辑 + + +## classpath.idx + +这是一个类路径索引文件,它包含了在构建过程中使用的所有类和资源的列表。这个文件通常由Maven插件生成,并包含在构建的JAR文件中。 + +官方解释:类路径索引文件可在 BOOT-INF/classpath.idx 中提供。通常,该文件由 Spring Boot 的 Maven 和 Gradle 构建插件自动生成。它提供了一个 jar 名称(包括目录)列表,并按照顺序将它们添加到 classpath 中。由构建插件生成时,该 classpath 排序与构建系统在运行和测试应用程序时使用的顺序一致。每行必须以破折号空格("--")开始,名称必须使用双引号。 + +该文件控制了类加载器的行为,它决定了类加载器在加载类和资源时的顺序。这个文件对于Spring Boot应用程序来说非常重要,因为它可以确保应用程序在运行时能够正确地加载类和资源。 + +### 过程分析 + +通过调试插件运行过程,可以发现: +1. 在执行repackage方法时,会调用getLibraries方法,该方法会获取所有的库文件,并生成classpath.idx文件。相关代码如下: + + ```java {10} + // Packager + private void write(JarFile sourceJar, AbstractJarWriter writer, PackagedLibraries libraries) throws IOException { + if (isLayered()) { + writer.useLayers(this.layers, this.layersIndex); + } + writer.writeManifest(buildManifest(sourceJar)); + writeLoaderClasses(writer); + writer.writeEntries(sourceJar, getEntityTransformer(), libraries.getUnpackHandler(), + libraries.getLibraryLookup()); + Map writtenLibraries = libraries.write(writer); + writeNativeImageArgFile(writer, sourceJar, writtenLibraries); + if (isLayered()) { + writeLayerIndex(writer); + } + writeSignatureFileIfNecessary(writtenLibraries, writer); + } + + + Map write(AbstractJarWriter writer) throws IOException { + Map writtenLibraries = new LinkedHashMap<>(); + for (Entry entry : this.libraries.entrySet()) { + String path = entry.getKey(); + Library library = entry.getValue(); + if (library.isIncluded()) { + String location = path.substring(0, path.lastIndexOf('/') + 1); + writer.writeNestedLibrary(location, library); + writtenLibraries.put(path, library); + } + } + // 写入classpath.idx + writeClasspathIndexIfNecessary(writtenLibraries.keySet(), getLayout(), writer); + return writtenLibraries; + } + ``` + + +2. classpath.idx内容最初来自于maven生成的project对象中,相关代码如下: + + ``` java {8,20} + // RepackageMojo + private void repackage() throws MojoExecutionException { + Artifact source = getSourceArtifact(this.classifier); + File target = getTargetFile(this.finalName, this.classifier, this.outputDirectory); + // 处理layers.xml中includeModuleDependencies逻辑 + Repackager repackager = getRepackager(source.getFile()); + // 获取所有的库文件 + Libraries libraries = getLibraries(this.requiresUnpack); + try { + LaunchScript launchScript = getLaunchScript(); + repackager.repackage(target, libraries, launchScript, parseOutputTimestamp()); + } + catch (IOException ex) { + throw new MojoExecutionException(ex.getMessage(), ex); + } + updateArtifact(source, target, repackager.getBackupFile()); + } + + protected final Libraries getLibraries(Collection unpacks) throws MojoExecutionException { + Set artifacts = this.project.getArtifacts(); + Set includedArtifacts = filterDependencies(artifacts, getAdditionalFilters()); + return new ArtifactsLibraries(artifacts, includedArtifacts, this.session.getProjects(), unpacks, getLog()); + } + ``` + + ``` java + // MavenProject + public Set getArtifacts() { + if (artifacts == null) { + if (artifactFilter == null || resolvedArtifacts == null) { + artifacts = new LinkedHashSet<>(); + } else { + artifacts = new LinkedHashSet<>(resolvedArtifacts.size() * 2); + for (Artifact artifact : resolvedArtifacts) { + if (artifactFilter.include(artifact)) { + artifacts.add(artifact); + } + } + } + } + return artifacts; + } + ``` + +3. maven又是如何获取到这些库文件的,相关代码如下: + + + ![LifecycleDependencyResolver](images/image7.png) + ![LifecycleDependencyResolver2](images/image8.png) + + 从`toArtifacts`代码分析,maven在解析依赖时,会遍历所有的直接依赖,然后把它们的依赖分别进行递归调用`toArtifacts`方法: + + > jar的顺序和pom中依赖的顺序一致 + + ```java + // RepositoryUtils + public static void toArtifacts( + Collection artifacts, + Collection nodes, + List trail, + DependencyFilter filter) { + for (DependencyNode node : nodes) { + org.apache.maven.artifact.Artifact artifact = toArtifact(node.getDependency()); + + List nodeTrail = new ArrayList<>(trail.size() + 1); + nodeTrail.addAll(trail); + nodeTrail.add(artifact.getId()); + + if (filter == null || filter.accept(node, Collections.emptyList())) { + artifact.setDependencyTrail(nodeTrail); + artifacts.add(artifact); + } + + toArtifacts(artifacts, node.getChildren(), nodeTrail, filter); + } + } + + ``` + + +## 调试 + + +### 准备 + +- 下载插件源码,IDEA导入 + +``` shell +git clone https://github.com/spring-projects/spring-boot +# 切换需要调试的版本 +git checkout v3.2.8 +``` +- 准备好测试项目,配置需要测试的插件 ,如spring-boot-maven-plugin,配置参考上方内容 + +- 两个项目JDK和Maven版本保持一致 + +### 执行 + +- 打断点 + +在源码项目中通过搜索`@Mojo(name = "repackage"`找到对应的核心类,在`execute()`方法打上断点 + +![debug](images/image.png) + +- 在测试项目控制台中执行`${maven_home}/mvnDebug clean package -Dmaven.test.skip=true`开启调试模式,等待IDEA连接,复制控制台输出的端口号 + +```shell +mvnDebug clean package +``` + +![debug2](images/image5.png) + +- 配置远程调试 + +在源码项目新建远程调试并且启动,端口为上一步复制的端口号,等待将进入断点 + +![debug3](images/image6.png) + +## 构建OCI镜像 + +[SpringBoot build-image](https://docs.spring.io/spring-boot/docs/3.2.12/maven-plugin/reference/htmlsingle/#build-image) + +[SpringBoot build-image.example](https://docs.spring.io/spring-boot/docs/3.2.12/maven-plugin/reference/htmlsingle/#build-image.examples) + +### 使用方式 + +- 手动执行命令 `mvn springboot:build-image` +- `package` 阶段时自动创建映像 + +```xml + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-image-no-fork + + + + + + + + https://dockerpull.org/ + + example.com/library/${project.artifactId} + + + + + https://registry-1.docker.io + username + password + email + + + + + + + +``` + +相较于`build-image-no-fork`,`build-image`会保证一定是完成package操作状态(也就是会执行package的相关操作) + +如果利用SpringBoot插件构建镜像,那么`docker-compose.yml`可以直接使用镜像而不是`Dockerfile` + + +### 注意事项 + +构建镜像需要依赖一些镜像,比如`paketobuildpacks/builder-jammy-base:latest` + +这些镜像如果没有指定域名的话,默认的下载地址是`ImageName#DEFAULT_DOMAIN`中固定且无法修改——国内可能出现无法下载的情况(提前下载也无效),可尝试配置buildpack信息。 + +```java +public class ImageName { + private static final String DEFAULT_DOMAIN = "docker.io"; + //省略 + } + + +public class BuildRequest { + + static final String DEFAULT_BUILDER_IMAGE_NAME = "paketobuildpacks/builder-jammy-base:latest"; + + private static final ImageReference DEFAULT_BUILDER = ImageReference.of(DEFAULT_BUILDER_IMAGE_NAME); + + //省略 + } +``` \ No newline at end of file diff --git a/blog/back/SpringCloud/README.md b/blog/back/spring-cloud/README.md similarity index 100% rename from blog/back/SpringCloud/README.md rename to blog/back/spring-cloud/README.md diff --git a/blog/back/SpringCloud/egrant.md b/blog/back/spring-cloud/egrant.md.bak similarity index 100% rename from blog/back/SpringCloud/egrant.md rename to blog/back/spring-cloud/egrant.md.bak diff --git a/blog/back/SpringCloud/gateway.md b/blog/back/spring-cloud/gateway.md similarity index 100% rename from blog/back/SpringCloud/gateway.md rename to blog/back/spring-cloud/gateway.md diff --git a/blog/back/SpringCloud/images/img.png b/blog/back/spring-cloud/images/img.png similarity index 100% rename from blog/back/SpringCloud/images/img.png rename to blog/back/spring-cloud/images/img.png diff --git a/blog/back/SpringCloud/images/img_1.png b/blog/back/spring-cloud/images/img_1.png similarity index 100% rename from blog/back/SpringCloud/images/img_1.png rename to blog/back/spring-cloud/images/img_1.png diff --git a/blog/back/SpringCloud/images/img_10.png b/blog/back/spring-cloud/images/img_10.png similarity index 100% rename from blog/back/SpringCloud/images/img_10.png rename to blog/back/spring-cloud/images/img_10.png diff --git a/blog/back/SpringCloud/images/img_11.png b/blog/back/spring-cloud/images/img_11.png similarity index 100% rename from blog/back/SpringCloud/images/img_11.png rename to blog/back/spring-cloud/images/img_11.png diff --git a/blog/back/SpringCloud/images/img_12.png b/blog/back/spring-cloud/images/img_12.png similarity index 100% rename from blog/back/SpringCloud/images/img_12.png rename to blog/back/spring-cloud/images/img_12.png diff --git a/blog/back/SpringCloud/images/img_13.png b/blog/back/spring-cloud/images/img_13.png similarity index 100% rename from blog/back/SpringCloud/images/img_13.png rename to blog/back/spring-cloud/images/img_13.png diff --git a/blog/back/SpringCloud/images/img_14.png b/blog/back/spring-cloud/images/img_14.png similarity index 100% rename from blog/back/SpringCloud/images/img_14.png rename to blog/back/spring-cloud/images/img_14.png diff --git a/blog/back/SpringCloud/images/img_15.png b/blog/back/spring-cloud/images/img_15.png similarity index 100% rename from blog/back/SpringCloud/images/img_15.png rename to blog/back/spring-cloud/images/img_15.png diff --git a/blog/back/SpringCloud/images/img_16.png b/blog/back/spring-cloud/images/img_16.png similarity index 100% rename from blog/back/SpringCloud/images/img_16.png rename to blog/back/spring-cloud/images/img_16.png diff --git a/blog/back/SpringCloud/images/img_17.png b/blog/back/spring-cloud/images/img_17.png similarity index 100% rename from blog/back/SpringCloud/images/img_17.png rename to blog/back/spring-cloud/images/img_17.png diff --git a/blog/back/SpringCloud/images/img_18.png b/blog/back/spring-cloud/images/img_18.png similarity index 100% rename from blog/back/SpringCloud/images/img_18.png rename to blog/back/spring-cloud/images/img_18.png diff --git a/blog/back/SpringCloud/images/img_19.png b/blog/back/spring-cloud/images/img_19.png similarity index 100% rename from blog/back/SpringCloud/images/img_19.png rename to blog/back/spring-cloud/images/img_19.png diff --git a/blog/back/SpringCloud/images/img_2.png b/blog/back/spring-cloud/images/img_2.png similarity index 100% rename from blog/back/SpringCloud/images/img_2.png rename to blog/back/spring-cloud/images/img_2.png diff --git a/blog/back/SpringCloud/images/img_3.png b/blog/back/spring-cloud/images/img_3.png similarity index 100% rename from blog/back/SpringCloud/images/img_3.png rename to blog/back/spring-cloud/images/img_3.png diff --git a/blog/back/SpringCloud/images/img_4.png b/blog/back/spring-cloud/images/img_4.png similarity index 100% rename from blog/back/SpringCloud/images/img_4.png rename to blog/back/spring-cloud/images/img_4.png diff --git a/blog/back/SpringCloud/images/img_5.png b/blog/back/spring-cloud/images/img_5.png similarity index 100% rename from blog/back/SpringCloud/images/img_5.png rename to blog/back/spring-cloud/images/img_5.png diff --git a/blog/back/SpringCloud/images/img_6.png b/blog/back/spring-cloud/images/img_6.png similarity index 100% rename from blog/back/SpringCloud/images/img_6.png rename to blog/back/spring-cloud/images/img_6.png diff --git a/blog/back/SpringCloud/images/img_7.png b/blog/back/spring-cloud/images/img_7.png similarity index 100% rename from blog/back/SpringCloud/images/img_7.png rename to blog/back/spring-cloud/images/img_7.png diff --git a/blog/back/SpringCloud/images/img_8.png b/blog/back/spring-cloud/images/img_8.png similarity index 100% rename from blog/back/SpringCloud/images/img_8.png rename to blog/back/spring-cloud/images/img_8.png diff --git a/blog/back/SpringCloud/images/img_9.png b/blog/back/spring-cloud/images/img_9.png similarity index 100% rename from blog/back/SpringCloud/images/img_9.png rename to blog/back/spring-cloud/images/img_9.png diff --git a/blog/back/SpringCloud/run.md b/blog/back/spring-cloud/run.md similarity index 100% rename from blog/back/SpringCloud/run.md rename to blog/back/spring-cloud/run.md diff --git a/blog/back/SpringCloud/security.md b/blog/back/spring-cloud/security.md similarity index 100% rename from blog/back/SpringCloud/security.md rename to blog/back/spring-cloud/security.md diff --git a/blog/back/SpringCloud/selector.md b/blog/back/spring-cloud/selector.md similarity index 100% rename from blog/back/SpringCloud/selector.md rename to blog/back/spring-cloud/selector.md diff --git a/blog/back/SpringSecurity/README.md b/blog/back/spring-security/README.md similarity index 100% rename from blog/back/SpringSecurity/README.md rename to blog/back/spring-security/README.md diff --git a/blog/back/SpringSecurity/accessAuth.md b/blog/back/spring-security/access-auth.md similarity index 100% rename from blog/back/SpringSecurity/accessAuth.md rename to blog/back/spring-security/access-auth.md diff --git a/blog/back/SpringSecurity/anonymity.md b/blog/back/spring-security/anonymity.md similarity index 100% rename from blog/back/SpringSecurity/anonymity.md rename to blog/back/spring-security/anonymity.md diff --git a/blog/back/SpringSecurity/images/img.png b/blog/back/spring-security/images/img.png similarity index 100% rename from blog/back/SpringSecurity/images/img.png rename to blog/back/spring-security/images/img.png diff --git a/blog/back/SpringSecurity/images/img_1.png b/blog/back/spring-security/images/img_1.png similarity index 100% rename from blog/back/SpringSecurity/images/img_1.png rename to blog/back/spring-security/images/img_1.png diff --git a/blog/back/SpringSecurity/images/img_2.png b/blog/back/spring-security/images/img_2.png similarity index 100% rename from blog/back/SpringSecurity/images/img_2.png rename to blog/back/spring-security/images/img_2.png diff --git a/blog/back/SpringSecurity/images/img_3.png b/blog/back/spring-security/images/img_3.png similarity index 100% rename from blog/back/SpringSecurity/images/img_3.png rename to blog/back/spring-security/images/img_3.png diff --git a/blog/back/SpringSecurity/images/img_4.png b/blog/back/spring-security/images/img_4.png similarity index 100% rename from blog/back/SpringSecurity/images/img_4.png rename to blog/back/spring-security/images/img_4.png diff --git a/blog/back/SpringSecurity/images/img_5.png b/blog/back/spring-security/images/img_5.png similarity index 100% rename from blog/back/SpringSecurity/images/img_5.png rename to blog/back/spring-security/images/img_5.png diff --git a/blog/back/SpringSecurity/images/img_6.png b/blog/back/spring-security/images/img_6.png similarity index 100% rename from blog/back/SpringSecurity/images/img_6.png rename to blog/back/spring-security/images/img_6.png diff --git a/blog/back/SpringSecurity/images/img_7.png b/blog/back/spring-security/images/img_7.png similarity index 100% rename from blog/back/SpringSecurity/images/img_7.png rename to blog/back/spring-security/images/img_7.png diff --git a/blog/back/SpringSecurity/images/img_8.png b/blog/back/spring-security/images/img_8.png similarity index 100% rename from blog/back/SpringSecurity/images/img_8.png rename to blog/back/spring-security/images/img_8.png diff --git a/blog/back/SpringSecurity/images/img_9.png b/blog/back/spring-security/images/img_9.png similarity index 100% rename from blog/back/SpringSecurity/images/img_9.png rename to blog/back/spring-security/images/img_9.png diff --git a/blog/back/SpringSecurity/oauth2.md b/blog/back/spring-security/oauth2.md similarity index 100% rename from blog/back/SpringSecurity/oauth2.md rename to blog/back/spring-security/oauth2.md diff --git a/blog/back/SpringSecurity/servlet.md b/blog/back/spring-security/servlet.md similarity index 100% rename from blog/back/SpringSecurity/servlet.md rename to blog/back/spring-security/servlet.md diff --git a/blog/back/spring/2.aop.md b/blog/back/spring/aop.md similarity index 100% rename from blog/back/spring/2.aop.md rename to blog/back/spring/aop.md diff --git a/blog/back/spring/images/img_9.png b/blog/back/spring/images/img_9.png new file mode 100644 index 0000000..4190b51 Binary files /dev/null and b/blog/back/spring/images/img_9.png differ diff --git a/blog/back/spring/1.ioc.md b/blog/back/spring/ioc.md similarity index 100% rename from blog/back/spring/1.ioc.md rename to blog/back/spring/ioc.md diff --git a/blog/back/workflow/README.md b/blog/back/workflow/README.md index e3c0ce7..a8edab6 100644 --- a/blog/back/workflow/README.md +++ b/blog/back/workflow/README.md @@ -8,6 +8,9 @@ tag: date: 2021-08-22 --- # JBPM4 + +> 工作内容记录 + [[toc]] ## 数据库相关 diff --git a/blog/back/workflow/camunda.md b/blog/back/workflow/camunda.md new file mode 100644 index 0000000..a4f5a1c --- /dev/null +++ b/blog/back/workflow/camunda.md @@ -0,0 +1,246 @@ +--- +title: Camunda +tag: + - study + - BPMN + - Camunda +date: 2025-06-16 +--- +# Camunda + +> 针对Camunda7.23版本的记录,Camunda8主要针对云服务方向。下述涉及到的JAVA API操作,通常有对应的Rest API + +## 参考资料 + +- [get-started](https://docs.camunda.org/get-started/) +- [Camunda 7](https://docs.camunda.org/manual/7.23/) + +## 核心概念 + +### 流程定义 + +流程定义是流程结构的定义,定义了流程的节点、连线、变量、任务、监听器、属性等信息,适配BPMN2.0标准 + +#### 流程定义查询 + +```java +List processDefinitions = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey("invoice") + .orderByProcessDefinitionVersion() + .asc() + .list(); +``` + +#### 流程定义文件查询 + +```java +// 方法一 +BpmnModelInstance bpmnModelInstance = repositoryService.getBpmnModelInstance(processInstance.getProcessDefinitionId()); +String processDefinitionXml = Bpmn.convertToString(bpmnModelInstance); + +// 方法二 +select t.BYTES_ + from act_ge_bytearray t where t.DEPLOYMENT_ID_ = ? and t.NAME_ = ?; +``` + +#### Keys and Versions + +key 是流程定义的唯一标识符,version是流程定义的版本号,key和version组合唯一标识一个流程定义. key对应下列定义文件中的id属性。 + +```xml + + ... + +``` + +同一个流程多次部署时(key相同),流程引擎会为其定义不同的version + + +#### 挂起流程定义 + +RuntimeService Java API可以禁用一个流程定义,禁用的流程定义将无法实例化,但是已经启动的流程实例不受影响。 + +#### 相关代码 + +执行解析BPMN XML模型,获取流程定义 + +![parse-bpmn](images/parse-bpmn.png) + +由`BpmnParse`解析 + +![category-set](images/category-set.png) + + +act_re_procdef表中的CATEGORY_字段来源于targetNamespace + +### Process Instances 流程实例 + +流程实例流程定义的一个独立执行。流程实例与流程定义之间的关系就像面向对象编程中的对象和类一样。 + +> 原文:A process instance is an individual execution of a process definition. The relation of the process instance to the process definition is the same as the relation between Object and Class in Object Oriented Programming (the process instance playing the role of the object and the process definition playing the role of the class in this analogy). + +#### 开启流程实例 + +```java +ProcessInstance instance = runtimeService.startProcessInstanceByKey("invoice"); +``` + +带参数开启流程实例 + +```java +Map variables = new HashMap(); +variables.put("creditor", "Nice Pizza Inc."); +ProcessInstance instance = runtimeService.startProcessInstanceByKey("invoice", variables); +``` +进程变量可供进程实例中的所有任务(Task)使用,并在进程实例进入等待状态时自动持久化到数据库。 + + +**Start Process Instances via Tasklist 通过web界面开启流程实例** + +略过,一般很少直接使用web界面开启流程实例。 + + +#### Start a Process Instance at Any Set of Activities + +在任意活动集开启流程实例 + +```java +ProcessInstance instance = runtimeService.createProcessInstanceByKey("invoice") + .startBeforeActivity("SendInvoiceReceiptTask") + .setVariable("creditor", "Nice Pizza Inc.") + .startBeforeActivity("DeliverPizzaSubProcess") + .setVariableLocal("destination", "12 High Street") + .execute(); +``` + +通过`startProcessInstanceByKey` and `startProcessInstanceById`方法在默认的初始化活动开启流程实例,通常都是在流程定义的简单空白的开始事件。 + +通过流式API可以在流程实例的任意位置开启流程实例 + +```java +ProcessInstance instance = runtimeService.createProcessInstanceByKey("invoice") + .startBeforeActivity("SendInvoiceReceiptTask") + .setVariable("creditor", "Nice Pizza Inc.") + .startBeforeActivity("DeliverPizzaSubProcess") + .setVariableLocal("destination", "12 High Street") + .execute(); +``` + +流式构建器允许提交任意数量的所谓的实例化指令。在调用 execute 时,流程引擎会按照指定的顺序执行这些指令。在上面的示例中,引擎首先启动 SendInvoiceReceiptTask 任务并执行流程,直到达到等待状态,然后启动 DeliverPizzaTask 任务并执行同样的操作。执行完这两条指令后, execute 调用返回。 + +**Variables in Return 返回变量** + + +```java +ProcessInstanceWithVariables instance = runtimeService.createProcessInstanceByKey("invoice") + .startBeforeActivity("SendInvoiceReceiptTask") + .setVariable("creditor", "Nice Pizza Inc.") + .startBeforeActivity("DeliverPizzaSubProcess") + .setVariableLocal("destination", "12 High Street") + .executeWithVariablesInReturn(); +``` + +#### 查询流程实例 + +``` java +runtimeService.createProcessInstanceQuery() + .processDefinitionKey("invoice") + .variableValueEquals("creditor", "Nice Pizza Inc.") + .list(); +``` + + +#### 流程实例交互 + +在对特定流程实例(或流程实例列表)执行查询后,您可能想与之交互。与流程实例进行交互有多种可能性,其中最主要的有: + +- Triggering it (make it continue execution): + Through a Message Event +T hrough a Signal Event + +- 触发事件(程序继续执行) `Message Event`或者 `Signal Event` +- 取消实例: 通过`RuntimeService.deleteProcessInstance`方法 +- 开启/取消任意活动: [Process Instance Modification](https://docs.camunda.org/manual/7.23/user-guide/process-engine/process-instance-modification/) + +#### 挂起流程实例 + +通常当流程实例无法继续执行时,可以将其挂起。 + +例如:如果流程变量与预期不符,就可以暂停实例并安全地更改变量。 + +挂起的流程实例,其状态是不允许修改的。 + + +### Executions 执行 + +如果您的流程实例包含多个执行路径(例如在并行网关之后),您必须能够区分流程实例内当前激活的路径。在下面的示例中,接收付款和发送订单这两个用户任务可以同时处于活动状态。 + +![Parallel Gateway](images/image.png) + +**在内部,流程引擎会在流程实例内创建两个并发执行,每个并发执行路径一个**。例如,当进程引擎到达嵌入式子进程或多实例时,也会为作用域创建执行。 + +```java +runtimeService.createExecutionQuery() + .processInstanceId(someId) + .list(); +``` + +### Activity Instances 活动实例 + +活动实例概念与执行概念类似,但角度不同。执行可以被想象成在流程中移动的标记,而活动实例则代表活动(任务、子流程......)的单个实例。因此,活动实例的概念更注重状态。 + +活动实例也按照BPMN 2.0提供的范围结构形成一棵树。那些“在同一子流程层级上”的活动(即属于同一范围,包含在同一子流程内)它们的活动实例会在树中处于相同的层级。这里的“span a tree”可以理解为形成一棵树,而“on the same level of subprocess”指的是这些活动在同一个子流程中处于相同的位置或级别。 + +例如:活动实例用于[流程实例修改](https://docs.camunda.org/manual/7.23/user-guide/process-engine/process-instance-modification/)和[activity-instance-tree](https://docs.camunda.org/manual/7.23/webapps/cockpit/bpmn/process-instance-view/#activity-instance-tree) + + +活动实例查询 +```java +ActivityInstance rootActivityInstance = runtimeService.getActivityInstance(processInstance.getProcessInstanceId()); + +``` + +每个活动实例都有一个唯一的 ID。ID 是持久的,如果多次调用此方法,相同的活动实例将返回相同的活动实例 ID。(但是,可能会分配不同的执行,请参见下文) + +#### 与执行(Executions)的关系 + +流程引擎中的 "执行"(Execution)概念与 "活动实例"(activity instance)概念并不完全一致,因为执行树通常与 BPMN 中的活动/范围概念不一致。一般来说,执行和活动实例(ActivityInstances)之间有 n-1 的关系,也就是说,在给定的时间点上,一个活动实例可以链接到多个执行。此外,并不能保证启动某个活动实例的同一个执行也会结束该活动实例。流程引擎会对执行树的压缩进行一些内部优化,这可能会导致执行被重新排序和修剪。这可能会导致某个执行启动了某个活动实例,但另一个执行却结束了该活动实例的情况。另一种特殊情况是流程实例:如果流程实例正在执行流程定义范围以下的非范围活动(例如用户任务),那么它将同时被根活动实例和用户任务活动实例引用。 + + +### Jobs and Job Definitions 定时任务 + +``` java +// job查询 +managementService.createJobQuery() + .duedateHigherThan(someDate) + .list() + +// job定义查询 +managementService.createJobDefinitionQuery() + .processDefinitionKey("orderProcess") + .list() + + +// job挂起 +List jobDefinitions = managementService.createJobDefinitionQuery() + .processDefinitionKey("orderProcess") + .activityIdIn("processPayment") + .list(); + +for (JobDefinition jobDefinition : jobDefinitions) { + managementService.suspendJobDefinitionById(jobDefinition.getId(), true); +} +``` + + +## BPMN-JS + +BPMN 2.0 viewer and editor. + +通用:https://bpmn.io/toolkit/bpmn-js/ + +Camunda定制:https://github.com/camunda/camunda-bpmn-js + +camunda-bpmn-js@5.0.0版本开始,node必须大于等于20,需要注意兼容性 + +https://github.com/camunda/camunda-bpmn-js/blob/main/CHANGELOG.md \ No newline at end of file diff --git a/blog/back/workflow/images/category-set.png b/blog/back/workflow/images/category-set.png new file mode 100644 index 0000000..18e9a2a Binary files /dev/null and b/blog/back/workflow/images/category-set.png differ diff --git a/blog/back/workflow/images/image.png b/blog/back/workflow/images/image.png new file mode 100644 index 0000000..950e725 Binary files /dev/null and b/blog/back/workflow/images/image.png differ diff --git a/blog/back/workflow/images/parse-bpmn.png b/blog/back/workflow/images/parse-bpmn.png new file mode 100644 index 0000000..bdf89fe Binary files /dev/null and b/blog/back/workflow/images/parse-bpmn.png differ diff --git a/blog/chore/1brc.md b/blog/chore/1brc.md new file mode 100644 index 0000000..7224adf --- /dev/null +++ b/blog/chore/1brc.md @@ -0,0 +1,9 @@ +# 1BRC挑战学习 + +https://www.bilibili.com/video/BV1VbEMz3EBs/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=5b66fdaa6de7c1c724f35083d1ce217f + + +性能分析工具: +Performance + +Compiler explorer \ No newline at end of file diff --git a/blog/chore/cros.md b/blog/chore/cros.md new file mode 100644 index 0000000..80dd288 --- /dev/null +++ b/blog/chore/cros.md @@ -0,0 +1,104 @@ +--- +title: 跨域 +# order: 2 +tag: + - study +date: 2025-04-19 +--- + +# :o: 跨域 + + +> 学习笔记 + +## :heavy_check_mark:浏览器同源策略 + +浏览器同源策略(Same-origin policy)是浏览器提供的一个安全机制,它限制了不同源的文档、脚本、图像等资源被加载、执行等操作。所谓同源,指的是两个URL的协议、域名和端口都相同。 + +## ❓跨域会收到哪些限制 + +1. 限制Document访问 +2. 限制Cookie访问 +3. 限制Ajax获取数据 + +## :heavy_exclamation_mark:限制Ajax获取数据 + +跨域限制仅存在于浏览器端,服务端不存在跨域限制。 + +跨域请求实际上是已经发送给后端,并且后端也进行了响应,但浏览器会对Response进行拦截校验,并对跨域请求返回一个错误。 + +浏览器对Script、Link、Image等资源加载时的跨域请求限制不严格。 + + +### :one:CORS解决方案 + +> 自家服务器可使用 + +#### 请求分类 + +简单请求: +- 请求方法为:GET、HEAD、POST +- 请求头字段符合 CORS安全规范只要不修改请求头,一般都符合规范 +- 请求头Content-Type的值只能是以下三种: + - text/plain + - multipart/form-data + - application/x-www.form-urlencoded + +复杂请求:非简单请求都是复杂请求,将自动发送预检请求(preflight request) + + + +#### :no_entry: 预检请求 + +1. 发送时机:在实际跨域请求之前发出,由浏览器自动发起的。 +2. 主要作用:用于询问服务器是否允许跨域请求,如果允许,浏览器才会发送实际的跨域请求。 +3. 基本流程:先发起OPTIONS请求,如果通过预检,继续发起实际的跨域请求。 +4. 请求头:一个OPTIONS预检请求,通常会包括如下请求头: + - Origin:请求的来源,即请求的域名。 + - Access-Control-Request-Method:实际请求的HTTP方法,例如GET、POST等。 + - Access-Control-Request-Headers:实际请求的HTTP头部信息。 + + +Access-Control-Allow-Origin:允许跨域请求的来源,例如`*`表示允许所有来源,也可以指定具体的域名。 + +Access-Control-Allow-Methods:允许的HTTP方法,例如`GET`、`POST`等。 + +Access-Control-Allow-Headers:允许的HTTP头部信息,例如`Content-Type`、`Authorization`等。 + +Access-Control-Max-Age:预检请求的有效期,单位为秒。在有效期内,浏览器不会再次发起预检请求。 + +Access-Control-Allow-Credentials:是否允许发送Cookie,如果设置为`true`,则允许发送Cookie。 + +### :two:JSONP解决方案 + +> 目前很少用,JQuery有封装,axios明确说明不支持(不符合当前时代的产物) + +JSONP(JSON with Padding)是一种非官方的跨域解决方案,它利用了` + + + + \ No newline at end of file diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts deleted file mode 100644 index d8ac587..0000000 --- a/docs/.vuepress/config.ts +++ /dev/null @@ -1,42 +0,0 @@ -// const sidebarConf = require('./config/sidebarConf.js'); -import sidebarConf from './config/sidebarConf.js' -import navConf from './config/navConf.js' -import pluginsConf from'./config/pluginsConf.js' -import headConf from './config/headConf.js' -import {defaultTheme} from "vuepress"; -import mdKatex from 'markdown-it-katex' -import { getDirname, path } from '@vuepress/utils' - -// @ts-ignore -const __dirname = getDirname(import.meta.url) -export default { - title: 'coder Z', // 设置网站标题 - description: '泥瓦匠', - head: headConf, - smoothScroll: true, - theme: defaultTheme({ - logo: '/img/hero.png', - repo: 'zengsl/vuepress-blog', - navbar: navConf, - // 侧边栏数组 - // 所有页面会使用相同的侧边栏 - sidebar: sidebarConf, - docsRepo: 'https://github.com/zengsl/vuepress-blog', - docsBranch: 'master', - docsDir: 'docs', - editLinkPattern: ':repo/tree/:branch/:path', - }), - plugins: pluginsConf, -// 增加markdown配置 - markdown: { - lineNumbers: true, - extendsMarkdown: (md) => { - console.log('md',md) - md.use(mdKatex) - md.set({html: true}) - }, - }, - alias:{ - '@': path.resolve(__dirname, '../../components'), - } -} \ No newline at end of file diff --git a/docs/.vuepress/config/headConf.ts b/docs/.vuepress/config/headConf.ts deleted file mode 100644 index bb2ce93..0000000 --- a/docs/.vuepress/config/headConf.ts +++ /dev/null @@ -1,41 +0,0 @@ -export default [ - ['link', { - rel: 'icon', - href: '/favicon.ico' - }], - ['link', { - rel: 'manifest', - href: '/manifest.json' - }], - ['meta', { - name: 'theme-color', - content: '#3eaf7c' - }], - ['meta', { - name: 'apple-mobile-web-app-capable', - content: 'yes' - }], - ['meta', { - name: 'apple-mobile-web-app-status-bar-style', - content: 'black' - }], - ['link', { - rel: 'apple-touch-icon', - href: '/icons/apple-touch-icon-152x152.png' - }], - ['link', { - rel: 'mask-icon', - href: '/icons/safari-pinned-tab.svg', - color: '#3eaf7c' - }], - ['meta', { - name: 'msapplication-TileImage', - content: '/icons/msapplication-icon-144x144.png' - }], - ['meta', { - name: 'msapplication-TileColor', - content: '#000000' - }], - ['link', { rel: 'stylesheet', href: 'https://cdn.bootcdn.net/ajax/libs/KaTeX/0.13.13/katex.min.css' }], - ['link', { rel: "stylesheet", href: "https://cdn.bootcdn.net/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css" }] -] \ No newline at end of file diff --git a/docs/.vuepress/config/navConf.ts b/docs/.vuepress/config/navConf.ts deleted file mode 100644 index a112abe..0000000 --- a/docs/.vuepress/config/navConf.ts +++ /dev/null @@ -1,261 +0,0 @@ -export default [{ - text: 'Home', - link: '/' -}, - { - text: '前端', - children: [{ - text: 'Html', - link: '/front/html' - }, - { - text: 'Js', - link: '/front/js/' - }, - { - text: 'React', - link: '/front/react' - }, - { - text: 'Vue', - link: '/front/vue' - }, - { - text: 'Node', - link: '/front/node' - } - ] - }, - - { - text: '后端', - children: [{ - text: 'Java', - children: [{ - text: 'Java并发', - link: '/back/java/concurrency/' - }, - { - text: 'JVM', - link: '/back/java/jvm/' - } - ] - }, - { - text: 'Spring生态', - children: [{ - text: 'Spring', - link: '/back/spring/' - }, - { - text: 'Spring Security', - link: '/back/SpringSecurity/' - }, - { - text: 'Spring Cloud', - link: '/back/SpringCloud/' - } - ] - }, { - text: '任务调度', - children: [{ - text: 'Quartz', - link: '/back/job/quartz/' - }] - }, - { - text: 'Struts', - link: '/back/struts/' - - }, - { - text: 'Mybatis', - link: '/back/mybatis/' - - }, - { - text: '分布式', - children: [{ - text: '基本介绍', - link: '/back/distributed/' - }, - { - text: 'Zookeeper', - link: '/back/distributed/Zookeeper' - } - ] - }, { - text: '消息中间间', - - children: [{ - text: 'Kafka', - link: '/back/message-queue/kafka/' - }] - }, { - text: '工作流', - children: [{ - text: 'JBPM4', - link: '/back/workflow/' - }] - } - , - { - text: '算法', - link: '/back/algorithm/' - - }, - - { - text: 'Dubbo', - link: '/back/Dubbo/' - - }, - - { - text: 'Netty', - link: '/back/Netty/' - - } - - ] - }, - { - text: '数据库', - children: [ - { - text: 'MySql', - link: '/db/mysql/' - }, - { - text: 'Redis', - link: '/db/redis/' - }, - { - text: 'Oracle', - link: '/db/oracle/' - } - ] - }, - { - text: '网络', - children: [{ - text: 'TCP/IP', - link: '/network/tcpip/' - },{ - text: 'HTTPS', - link: '/network/https' - }] - }, - { - text: '运维', - - children: [{ - text: '操作系统', - children: [{ - text: 'Linux', - link: '/ops/os/linux/', - - }, { - text: 'Mac OS', - link: '/ops/os/mac/', - - }] - }, { - text: '服务器', - children: [{ - text: 'Apache', - link: '/ops/server/apache/' - }, - { - text: 'Nginx', - link: '/ops/server/nginx/' - }, - { - text: 'Tomcat', - link: '/ops/server/tomcat/' - }, - { - text: 'Weblogic', - link: '/ops/server/weblogic/' - } - ] - } - - ] - }, - { - text: '虚拟化', - - children: [{ - text: 'Docker', - link: '/virtualization/docker/' - }, { - text: 'K8S', - link: '/virtualization/k8s/' - } - ] - }, - { - text: '工具', - children: [{ - text: 'Typora', - link: '/tools/typora/1.installAndSetting' - }, { - text: 'Git', - link: '/tools/git/' - }, { - text: 'Sonar', - link: '/tools/sonar/' - }, { - text: '抓包', - link: '/tools/grab/' - }] - }, - { - text: '其他', - children: [{ - text: '性能调优', - link: '/other/performanceOptimize/' - }, - { - text: '调试技巧', - link: '/other/debugger/' - }, - { - text: '防重复请求', - link: '/other/repeatRequest' - }, - { - text: '批量插入', - link: '/other/batch' - }, - { - text: 'UReport2', - link: '/other/ureport' - }, - { - text: 'PDF', - link: '/other/pdf/' - }, { - text: 'Python', - link: '/python/' - }, { - text: 'Groovy', - link: '/groovy/' - }, { - text: 'RegExp', - link: '/other/regExp' - }, { - text: 'VuePress', - link: '/other/blog/' - }, { - text: 'Intellij plugin', - link: '/other/intellij/' - } - ] - }, - { - text: 'About', - link: '/about/aboutMe' - } -] diff --git a/docs/.vuepress/config/pluginsConf.ts b/docs/.vuepress/config/pluginsConf.ts deleted file mode 100644 index e31236d..0000000 --- a/docs/.vuepress/config/pluginsConf.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {pwaPlugin} from '@vuepress/plugin-pwa' -import {pwaPopupPlugin} from "@vuepress/plugin-pwa-popup"; -import {tocPlugin} from '@vuepress/plugin-toc' -import {registerComponentsPlugin} from "@vuepress/plugin-register-components"; -import {path} from "@vuepress/utils"; -import {gitPlugin} from "@vuepress/plugin-git"; - -export default [ - pwaPlugin(), - pwaPopupPlugin({ - // 配置项 - }), - tocPlugin({ - // 配置项 - }), - registerComponentsPlugin({ - componentsDir: path.resolve(__dirname, '../../../components'), - }), - gitPlugin({ - // 配置项 - contributors: false - }), -] \ No newline at end of file diff --git a/docs/.vuepress/config/pluginsConf.ts.bak b/docs/.vuepress/config/pluginsConf.ts.bak deleted file mode 100644 index 4821f77..0000000 --- a/docs/.vuepress/config/pluginsConf.ts.bak +++ /dev/null @@ -1,32 +0,0 @@ -import moment from 'moment' - -export default [ - ['@vuepress/medium-zoom', { - selector: 'img', - // medium-zoom options here - // See: https://github.com/francoischalifour/medium-zoom#options - options: { - margin: 16 - } - }], - ['@vuepress/last-updated', { - transformer: (timestamp, lang) => { - moment.locale("zh_cn") - return moment(timestamp).format("LLLL") - } - }], - ['@vuepress/back-to-top'], - ['@vuepress/active-header-links', { - sidebarLinkSelector: '.sidebar-link', - headerAnchorSelector: '.header-anchor' - }], - ['@vuepress/pwa', { - serviceWorker: true, - updatePopup: { - message: "内容有更新", - buttonText: "刷新" - } - }], - ['@vuepress/nprogress'], - ['@vuepress/blog'] -] \ No newline at end of file diff --git a/docs/.vuepress/config/sidebarConf.ts b/docs/.vuepress/config/sidebarConf.ts deleted file mode 100644 index 9d79303..0000000 --- a/docs/.vuepress/config/sidebarConf.ts +++ /dev/null @@ -1,510 +0,0 @@ -export default { - '/back/java/concurrency/': [{ - text: 'Java并发', - collapsable: false, - children: [{ - text: '介绍', - link: '/back/java/concurrency/' - }, - { - text: '线程池', - link: 'thread-pool' - }, - { - text: '基础', - link: '1.java多线程-基础' - }, - { - text: 'JUC', - link: '2.Java多线程-JUC' - } - ] - }], - '/back/java/jvm/': [{ - text: 'JVM', - collapsable: false, - children: [{ - text: '介绍', - link: '/back/java/jvm/' - }, - { - text: 'JDK工具', - link: 'jdkCommand' - }, - { - text: '编译JDK', - link: '1.buildJdk' - } - ] - }], - '/back/struts/': [{ - text: 'Struts', - collapsable: false, - sidebarDepth: 2, - children: [{ - text: '介绍', - link: '/back/struts/' - }, - { - text: '拦截器', - link: 'interceptor' - }, - { - text: '常见问题', - link: 'issues' - }, - { - text: '文件上传失败分析', - link: 'fileuploadFail' - } - ] - }], - '/back/spring/': [{ - text: 'Spring', - collapsable: false, - sidebarDepth: 2, - children: [{ - text: '概述', - link: '/back/spring/' - }, - { - text: '初始化', - link: 'init' - }, - { - text: 'Ioc', - link: '1.ioc' - }, - { - text: 'Aop', - link: '2.aop' - }, - { - text: 'Spring MVC', - link: 'springmvc' - } - ] - }], - '/back/SpringSecurity/': [{ - text: 'SpringSecurity', - collapsable: false, - sidebarDepth: 2, - children: [{ - text: '介绍', - link: '/back/SpringSecurity/' - }, - { - text: '资源访问权限', - link: 'accessAuth' - }, - { - text: '匿名权限', - link: 'anonymity' - }, - { - text: 'Servlet', - link: 'servlet' - }, - { - text: 'OAuth 2.0', - link: 'oauth2' - } - - ] - }], - - '/back/SpringCloud/': [{ - text: 'SpringCloud', - collapsable: false, - sidebarDepth: 2, - children: [{ - text: '介绍', - link: '/back/SpringCloud/' - }, - { - text: '启动过程', - link: 'run' - }, - { - text: 'Gateway', - link: 'gateway' - }, - { - text: 'Security', - link: 'security' - }, - { - text: 'egrantM', - link: 'egrant/' - } - ] - }], - '/back/job/quartz/': [{ - text: 'Quartz', - collapsable: false, - sidebarDepth: 2, - children: [{ - text: '介绍', - link: '/back/job/quartz/' - } - ] - }], - '/back/mybatis/': [{ - text: 'Mybatis', - collapsable: false, - children: [{ - text: '介绍', - link: '/back/mybatis/' - }, - { - text: 'Mybatis-Plus', - link: 'mybatis-plus' - }, { - text: 'Mapper自动刷新', - link: 'Mapper自动刷新' - }, - { - text: '其他', - link: 'other' - } - - ] - }], - '/back/distributed/': [{ - text: '分布式', - collapsable: false, - sidebarDepth: 2, - children: [{ - text: '介绍', - link: '/back/distributed/' - }, - { - text: '分布式事务', - link: 'distributedTransaction' - }, - { - text: '分布式锁', - link: 'distributedLocks' - }, - { - text: 'Zookeeper', - link: 'Zookeeper' - } - - ] - }], - '/ops/os/linux/': [{ - text: 'Linux', - collapsable: false, - children: [{ - text: '介绍', - link: '/ops/os/linux/' - }, - { - text: '安装配置', - link: '1.installAndSetting_vm' - }, - { - text: '常用命令', - link: '2.command' - }, - { - text: '系统性能', - link: 'sys_performance' - }, - { - text: 'shell脚本', - link: '3.shell' - }, - { - text: '磁盘扩容', - link: 'fileExtend' - } - ] - }], - '/ops/os/mac/': [{ - text: 'Mac OS', - collapsable: false, - children: [{ - text: '介绍', - link: '/ops/os/mac/' - }, - { - text: '安装', - link: '1.install' - }, - { - text: '配置', - link: '2.settings' - }, - { - text: '软件推荐', - link: '3.software' - }, - { - text: '快捷键', - link: '4.keys' - }, - { - text: '使用技巧', - link: '5.skill' - } - ] - }], - '/ops/server/nginx/': [{ - text: 'Nginx', - collapsable: false, - children: [{ - text: '前言', - link: '/ops/server/nginx/' - }, - { - text: '安装配置', - link: '1.nginx(windows)' - }, - { - text:'OpenResty', - link:'openresty' - } - ] - }], - '/ops/server/apache/': [{ - text: 'Apache', - collapsable: false, - children: [{ - text: '前言', - link: '/ops/server/apache/' - }, - { - text: '安装配置', - link: '1.installAndSetting' - } - ] - }], - '/ops/server/tomcat/': [{ - text: 'Tomcat', - collapsable: false, - children: [{ - text: '介绍', - link: '/ops/server/tomcat/' - },{ - text: '源码编译与启动', - link: 'tomcatSource' - },{ - text: '日志', - link: 'tomcatLogging' - }, - { - text: '常见问题', - link: 'problems' - } - ] - }], - '/tools/git/': [{ - text: 'Git', - collapsable: false, - children: [{ - text: '介绍', - link: '/tools/git/' - }, - { - text: '安装配置', - link: '1.installAndSetting' - }, - { - text: '部署', - link: '2.deploy' - }, - { - text: '常用命令', - link: '3.command' - } - ] - }], - '/tools/sonar/': [{ - text: 'Sonar', - collapsable: false, - children: [{ - text: '介绍', - link: '/tools/sonar/' - }, - { - text: '安装配置', - link: '1.installAndSetting' - } - ] - }], - '/tools/grab/': [{ - text: '抓包工具', - collapsable: false, - children: [{ - text: '介绍', - link: '/tools/grab/' - }, - { - text: 'Fiddler', - link: '/tools/grab/fiddler/use.md' - }, - { - text: 'Wireshark', - link: '/tools/grab/wireshark/use.md' - } - ] - }], - '/db/redis/': [{ - text: 'Redis', - collapsable: false, - sidebarDepth: 2, - children: [{ - text: '介绍', - link: '/db/redis/' - }, - { - text: '安装配置', - link: '1.installAndSetting' - }, - { - text: '常用命令', - link: '2.command' - } - ] - }], - '/db/oracle/': [{ - text: 'Oracle', - collapsable: false, - children: [{ - text: '介绍', - link: '/db/oracle/' - }, - { - text: '常用命令', - link: '1.command' - } - ] - }], - '/db/mysql/': [{ - text: 'MySql', - collapsable: false, - children: [{ - text: '介绍', - link: '/db/mysql/' - }, - { - text: '安装配置', - link: '1.install and setting' - } - ] - }], - '/other/debugger/': [{ - text: '调试技巧', - collapsable: false, - children: [{ - text: '介绍', - link: '/other/debugger/' - }, - { - text: '后端调试', - link: 'back_debug' - }, - { - text: '前端调试', - link: 'front_debug' - }, - { - text: '接口调试', - link: 'interface_debug' - } - ] - }], - '/other/performanceOptimize/': [{ - text: '性能调优', - collapsable: false, - children: [{ - text: '概述', - link: '/other/performanceOptimize/' - }, - { - text: '生产问题', - link: 'performance_optimize' - } - ] - }], - '/other/pdf/': [{ - text: 'PDF', - collapsable: false, - children: [{ - text: '介绍', - link: '/other/pdf/' - }, - { - text: 'PDF内容显示异常', - link: 'pdfContentError' - }, - { - text: 'PDF下载报错(字体)', - link: 'pdfFontError' - } - ] - }], - - '/front/js/': [{ - text: 'JavaScript', - collapsable: false, - children: [{ - text: '介绍', - link: '/front/js/' - }, - { - text: '基本概念', - link: '1.base' - }, - { - text: '对象', - link: '2.object' - }, - { - text: '原型', - link: '3.propotype' - }, - { - text: '闭包', - link: '4.closure' - }, - { - text: 'Screenshots', - link: 'Screenshots' - }, - { - text: '模块化', - link: 'module' - } - ] - }], - '/virtualization/k8s/': [{ - text: 'Kubernetes', - collapsable: true, - sidebarDepth: 2, - children: [{ - text: '介绍', - link: '/virtualization/k8s/' - }, - { - text: '安装', - link: 'install' - }, - { - text: 'kubernetes集群命令工具kubectl', - link: 'kubectl' - }, - { - text: 'Kubernetes集群YAML文件详解', - link: 'yaml' - }, - { - text: 'Kubernetes核心技术Pod', - link: 'pod' - } - ] - }] - -} \ No newline at end of file diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico deleted file mode 100644 index 291b8ff..0000000 Binary files a/docs/.vuepress/public/favicon.ico and /dev/null differ diff --git a/docs/.vuepress/public/icons/android-chrome-192x192.png b/docs/.vuepress/public/icons/android-chrome-192x192.png deleted file mode 100755 index b02aa64..0000000 Binary files a/docs/.vuepress/public/icons/android-chrome-192x192.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/android-chrome-512x512.png b/docs/.vuepress/public/icons/android-chrome-512x512.png deleted file mode 100755 index 06088b0..0000000 Binary files a/docs/.vuepress/public/icons/android-chrome-512x512.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/apple-touch-icon-120x120.png b/docs/.vuepress/public/icons/apple-touch-icon-120x120.png deleted file mode 100755 index 1427cf6..0000000 Binary files a/docs/.vuepress/public/icons/apple-touch-icon-120x120.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/apple-touch-icon-152x152.png b/docs/.vuepress/public/icons/apple-touch-icon-152x152.png deleted file mode 100755 index f24d454..0000000 Binary files a/docs/.vuepress/public/icons/apple-touch-icon-152x152.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/apple-touch-icon-180x180.png b/docs/.vuepress/public/icons/apple-touch-icon-180x180.png deleted file mode 100755 index 404e192..0000000 Binary files a/docs/.vuepress/public/icons/apple-touch-icon-180x180.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/apple-touch-icon-60x60.png b/docs/.vuepress/public/icons/apple-touch-icon-60x60.png deleted file mode 100755 index cf10a56..0000000 Binary files a/docs/.vuepress/public/icons/apple-touch-icon-60x60.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/apple-touch-icon-76x76.png b/docs/.vuepress/public/icons/apple-touch-icon-76x76.png deleted file mode 100755 index c500769..0000000 Binary files a/docs/.vuepress/public/icons/apple-touch-icon-76x76.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/apple-touch-icon.png b/docs/.vuepress/public/icons/apple-touch-icon.png deleted file mode 100755 index 03c0c5d..0000000 Binary files a/docs/.vuepress/public/icons/apple-touch-icon.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/favicon-16x16.png b/docs/.vuepress/public/icons/favicon-16x16.png deleted file mode 100755 index 42af009..0000000 Binary files a/docs/.vuepress/public/icons/favicon-16x16.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/favicon-32x32.png b/docs/.vuepress/public/icons/favicon-32x32.png deleted file mode 100755 index 46ca04d..0000000 Binary files a/docs/.vuepress/public/icons/favicon-32x32.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/msapplication-icon-144x144.png b/docs/.vuepress/public/icons/msapplication-icon-144x144.png deleted file mode 100644 index 7808237..0000000 Binary files a/docs/.vuepress/public/icons/msapplication-icon-144x144.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/mstile-150x150.png b/docs/.vuepress/public/icons/mstile-150x150.png deleted file mode 100755 index 3b37a43..0000000 Binary files a/docs/.vuepress/public/icons/mstile-150x150.png and /dev/null differ diff --git a/docs/.vuepress/public/icons/safari-pinned-tab.svg b/docs/.vuepress/public/icons/safari-pinned-tab.svg deleted file mode 100755 index 732afd8..0000000 --- a/docs/.vuepress/public/icons/safari-pinned-tab.svg +++ /dev/null @@ -1,149 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - diff --git a/docs/.vuepress/public/img/child.jpg b/docs/.vuepress/public/img/child.jpg deleted file mode 100755 index 4570976..0000000 Binary files a/docs/.vuepress/public/img/child.jpg and /dev/null differ diff --git a/docs/.vuepress/public/img/hero.png b/docs/.vuepress/public/img/hero.png deleted file mode 100644 index ac6beaf..0000000 Binary files a/docs/.vuepress/public/img/hero.png and /dev/null differ diff --git a/docs/.vuepress/public/manifest.json b/docs/.vuepress/public/manifest.json deleted file mode 100644 index 093961e..0000000 --- a/docs/.vuepress/public/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "专业的泥瓦匠", - "short_name": "泥瓦匠", - "icons": [ - { - "src": "/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "start_url": "/", - "display": "standalone", - "background_color": "#fff", - "theme_color": "#3eaf7c" -} \ No newline at end of file diff --git a/docs/.vuepress/public/xmind/JsModule.xmind b/docs/.vuepress/public/xmind/JsModule.xmind deleted file mode 100644 index a6154d1..0000000 Binary files a/docs/.vuepress/public/xmind/JsModule.xmind and /dev/null differ diff --git a/docs/.vuepress/public/xmind/WeblogicConsole.xmind b/docs/.vuepress/public/xmind/WeblogicConsole.xmind deleted file mode 100644 index 1d4948e..0000000 Binary files a/docs/.vuepress/public/xmind/WeblogicConsole.xmind and /dev/null differ diff --git a/docs/.vuepress/styles/palette.styl b/docs/.vuepress/styles/palette.styl deleted file mode 100644 index f0e0415..0000000 --- a/docs/.vuepress/styles/palette.styl +++ /dev/null @@ -1,8 +0,0 @@ -/**.theme-default-content:not(.custom) { - margin: 0 auto; - padding: 2rem 2.5rem; -} -**/ -// $contentWidth = 1200 - -// $nprogressColor = red \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index e835342..0000000 --- a/docs/README.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -home: true -heroImage: /img/child.jpg -#标题 -heroText: Free Coding -#Hero 副标题 -tagline: Day Day Coding,Day Day up -actionText: enter → -actionLink: /tools/typora/1.install and setting - -features: -- title: 简洁至上 - details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。 -- title: Vue驱动 - details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。 -- title: 高性能 - details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。 -footer: MIT Licensed | Copyright © 2018-present zzz ---- -::: warning -记录翻山的过程。。 -::: \ No newline at end of file diff --git a/docs/about/aboutMe.md b/docs/about/aboutMe.md deleted file mode 100644 index 5415d75..0000000 --- a/docs/about/aboutMe.md +++ /dev/null @@ -1,7 +0,0 @@ -# 个人简介 - -::: tip -我是一个代码的搬运工!!! -::: - -这是一个PWA测试 \ No newline at end of file diff --git "a/docs/back/C\350\257\255\350\250\200.md" "b/docs/back/C\350\257\255\350\250\200.md" deleted file mode 100644 index 7985cce..0000000 --- "a/docs/back/C\350\257\255\350\250\200.md" +++ /dev/null @@ -1,202 +0,0 @@ -# C语言 - - - -## 内存 - -size命令查看内存布局 - -- 代码段 :方法中的要执行的代码 -- 数据段 :已经初始化的静态变量 -- bss段:未初始化的静态变量 - -![image-20220108153610135](https://gitee.com/zengsl/picBed/raw/master/img/2022/01/20220108153610.png) - - - -### 堆栈区别 - -- 堆和其他区段一样,都是从低地址向高地址发展 - -- 栈则相反,是由高地址向低地址发展 - -### 常用方法 - -`malloc`、`free`、`calloc`、`realloc` - -### 内存池 - -频繁的调用`malloc`和`free`等操作会产生较多的内存碎片。 - -程序用一个单链表去维护一个内存池。 - - - -## 宏定义 - -预处理(文件包含、**宏定义**、条件编译)=》编译 - -宏定义只是做简单的替换 - -### 不带参数宏定义 - -- 宏命名通常约定大写 - -- 宏定义不是说明或语句,结尾不需要加分号 -- 作用域是从定义位置开始到真个程序结束 -- 可以用#undef来终止宏定义的作用域 -- 宏定义允许嵌套 - -```c -#define PI 3.14 - -// 支持作用范围 -#define PI 3.14 -// PI这个宏可以有效范围 -#undef PI - - -// 嵌套 -#define R 6371 -#define PI 3.14 -#define V PI * R * R * R * 4 / 3 -``` - -### 带参数宏定义 - -```c -#define MAX(x,y) (((x)>(y)) ? (x) : (y)) - -// #define MAX(x,y) (x>y ? x : y) 之所以上面要加括号就是为了防止运算符优先级的问题导致的问题 -``` - -### #和## - -- `#`和`##`是两个预处理运算符。 - -- 在带参数的宏定义中,`#`运算符后面应该跟一个参数,预处理器会把这个参数转换为一个字符串。 - - ```c - #define SQUAR(str) # str - ``` - -- `##`运算符被称为记号连接运算符,比如我们可以使用`##`运算符连接两个参数。 - - ```c - #define TOGETHER(x,y) x ## y - ``` - -### 可变参数 - - ```c - // ...代表可变参数 __VA_ARGS__是实际接收到的参数 可变参数可以传空参 - #define SHOWLIST(...) printf(# __VA_ARGS__) - ``` - - - -### 内联函数 inline - -使用 inline关键字去定义内联函数,在编译器会将使用函数的地方进行内联替换。但是会增加编译时间,现代编译器比较聪明,就算你不写inline,它也会自动将一些函数优化成内联函数。 - -## 结构体 - -### 定义 - -```c -// 声明 -struct XXX { - 数据类型 名称; - 。 - 。 -} [定义结构体变量]; - -// 使用 定义结构体变量 -struct XXX 变量名; -``` - - - -> 用sizeof查看结构体大小,可以发现编译器对结构体的成员变量会做内存对齐。32位机按照4字节对齐。调整C结构体的成员变量的顺序可以达到节省内存的目的。 - -### 结构体指针 - -- 结构体指针访问成员变量又两种方法: - - ```c - (*结构体指针).成员名 - - 结构体指针 -> 成员名 - ``` - - 函数传递结构体变量会导致程序开销变大,所以可以用结构体指针替代。 - -### 动态申请结构体 - -运用`malloc`函数在堆中分配内存,使用完需要进行free - -## 共用体 - -### 定义 - -```c -union 共用体名称 -{ - 共用体成员1; - 共用体成员2; - .... -}; -``` - -成员都是共用一个地址存储内容 - - - -## 关键字 - -### typedef - -给数据类型取别名 - -```c -// 将int取两个别名,一个是integer,一个是指针类型的PTRINT -typedef int integer,* PTRINT; - -// 指向 数组的指针别名 -typedef int (*PTR_TO_RUN)[3]; - -// 指向 返回值为int参数为void的函数的指针别名 -typedef int (*PTR_TO_RUN)(void); - -``` - -## 位域 - -```c -unsigned int a:1; -``` - - - -## 位操作 - -C语言并没有规定一个字节的尺寸,“可寻址的数据存储单位,其尺寸必须是可以容纳运行环境的基本字符集的任何成员”。 - -不同数据类型的具体大小没有规定,不同的平台下可能有不同的大小限制,一般在limit.h中有限制。 - -## 文件 - -打开关闭函数`fopen`、`fclose` - -### 文件读写 - -读取单个字符: - -fgetc、getc,fgetc是一个函数;而getc则是一个宏的实现。 - - - -读取字符串: - -fgets - diff --git a/docs/back/Dubbo.md b/docs/back/Dubbo.md deleted file mode 100644 index d7acc58..0000000 --- a/docs/back/Dubbo.md +++ /dev/null @@ -1,15 +0,0 @@ -# Dubbo - -待完善... - -SPI - - - -协议 - - - -过滤器 - -DefaultFuture \ No newline at end of file diff --git "a/docs/back/Java\344\275\223\347\263\273\344\270\255\344\270\200\344\272\233\346\225\260\345\255\227.md" "b/docs/back/Java\344\275\223\347\263\273\344\270\255\344\270\200\344\272\233\346\225\260\345\255\227.md" deleted file mode 100644 index 3144c88..0000000 --- "a/docs/back/Java\344\275\223\347\263\273\344\270\255\344\270\200\344\272\233\346\225\260\345\255\227.md" +++ /dev/null @@ -1,44 +0,0 @@ -# 计算机体系中一些数字 - -地址、指针的大小和操作系统位数有关:32位操作系统指针大小为32个bit,也就是4个字节。64位操作系统为8个字节。 - -## JVM - -### 压缩指针 - -对于32位机器,进程能使用的最大内存是4G。如果进程需要使用更多的内存,需要使用64位机器。 - -对于Java进程,在oop只有32位时,只能引用4G内存。因此,如果需要使用更大的堆内存,需要部署64位JVM。这样,oop为64位,可引用的堆内存就更大了。 - -在堆中,32位的对象引用占4个字节,而64位的对象引用占8个字节。也就是说,64位的对象引用大小是32位的2倍。 - -由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。 - -![image-20220107165913747](https://gitee.com/zengsl/picBed/raw/master/img/2022/01/20220107165919.png) - -### JMM - -#### 虚拟机栈 - -包括变量表、操作数栈、方法出口、动态链接。 - -变量表:基本数据类型和引用类型变量等在变量表中以变量槽(slot)的方式存储,其中64位的long和double会使用两个slot进行存储,而其他变量只使用一个slot存储。 - -## MySQL - -**页**,是InnoDB中数据管理的最小单位。当我们查询数据时,其是以页为单位,将磁盘中的数据加载到缓冲池中的。同理,更新数据也是以页为单位,将我们对数据的修改刷回磁盘。 - -Page是Innodb存储的最基本结构,也是Innodb磁盘管理的最小单位,与数据库相关的所有内容都存储在Page结构里。Page分为几种类型: - -> 1. `数据页(B-Tree Node)`, -> 2. `Undo页(Undo Log Page)`, -> 3. `系统页(System Page)`, -> 4. `事务数据页(Transaction System Page)` -> -> 每个数据页的大小为`16kb`,每个Page使用一个32位(一位表示的就是0或1)的int值来表示,正好对应Innodb最大64TB的存储容量(16kb * 2^32=64tib) - -mysql中的具体数据是存储在行中的,而行是存储在页中的,每页的默认大小为16k(大小可以通过配置文件修改),页的结构如下图所示 - -http://metronic.net.cn/metronic/show-46454.html - -## Kafka \ No newline at end of file diff --git a/docs/back/Netty.md b/docs/back/Netty.md deleted file mode 100644 index 9633ec3..0000000 --- a/docs/back/Netty.md +++ /dev/null @@ -1,3 +0,0 @@ -# Netty - -待完善... \ No newline at end of file diff --git a/docs/back/SpringBoot/README.md b/docs/back/SpringBoot/README.md deleted file mode 100644 index 4136fe5..0000000 --- a/docs/back/SpringBoot/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# 介绍 - diff --git a/docs/back/SpringCloud/README.md b/docs/back/SpringCloud/README.md deleted file mode 100644 index 7756f40..0000000 --- a/docs/back/SpringCloud/README.md +++ /dev/null @@ -1,181 +0,0 @@ -# 介绍 - -SpringCloud的学习笔记 - -2020-10-22 - -## 版本选择 - -Spring Boot - -[github](https://github.com/spring-projects/spring-boot) - -[官网](https://spring.io/projects/spring-boot) - -Boot官方推荐使用2.X以上的版本 - - -Spring Cloud - -[github](https://github.com/spring-projects/spring-cloud) - -[官网](https://spring.io/projects/spring-cloud) - -查找Spring Boot和Spring Cloud的版本依赖关系 - -- 从cloud的官网上查询 - -![版本依赖信息](images/img.png) - -- 更详细的查看方法 - -通过[查看信息地址](https://start.spring.io/actuator/info)获取json,将json格式化之后可以看到`spring-cloud`的版本信息,如下图: - -![cloud版本](images/img_1.png) - -~~~ json - "spring-cloud": { - "Finchley.M2": "Spring Boot >=2.0.0.M3 and <2.0.0.M5", - "Finchley.M3": "Spring Boot >=2.0.0.M5 and <=2.0.0.M5", - "Finchley.M4": "Spring Boot >=2.0.0.M6 and <=2.0.0.M6", - "Finchley.M5": "Spring Boot >=2.0.0.M7 and <=2.0.0.M7", - "Finchley.M6": "Spring Boot >=2.0.0.RC1 and <=2.0.0.RC1", - "Finchley.M7": "Spring Boot >=2.0.0.RC2 and <=2.0.0.RC2", - "Finchley.M9": "Spring Boot >=2.0.0.RELEASE and <=2.0.0.RELEASE", - "Finchley.RC1": "Spring Boot >=2.0.1.RELEASE and <2.0.2.RELEASE", - "Finchley.RC2": "Spring Boot >=2.0.2.RELEASE and <2.0.3.RELEASE", - "Finchley.SR4": "Spring Boot >=2.0.3.RELEASE and <2.0.999.BUILD-SNAPSHOT", - "Finchley.BUILD-SNAPSHOT": "Spring Boot >=2.0.999.BUILD-SNAPSHOT and <2.1.0.M3", - "Greenwich.M1": "Spring Boot >=2.1.0.M3 and <2.1.0.RELEASE", - "Greenwich.SR6": "Spring Boot >=2.1.0.RELEASE and <2.1.18.BUILD-SNAPSHOT", - "Greenwich.BUILD-SNAPSHOT": "Spring Boot >=2.1.18.BUILD-SNAPSHOT and <2.2.0.M4", - "Hoxton.SR8": "Spring Boot >=2.2.0.M4 and <2.3.5.BUILD-SNAPSHOT", - "Hoxton.BUILD-SNAPSHOT": "Spring Boot >=2.3.5.BUILD-SNAPSHOT and <2.4.0.M1", - "2020.0.0-M3": "Spring Boot >=2.4.0.M1 and <=2.4.0.M1", - "2020.0.0-M4": "Spring Boot >=2.4.0.M2 and <=2.4.0-M3", - "2020.0.0-SNAPSHOT": "Spring Boot >=2.4.0-M4" - } -~~~ - -- 更加精确的方法 - -查看对应cloud版本文档,看官方推荐 - -![入口](images/img_2.png) - -![boot版本](images/img_3.png) - - - -## 学习文档 - -### Spring Cloud - -[Hoxton.SR8版本官方资料](https://docs.spring.io/spring-cloud/docs/Hoxton.SR8/reference/html/) - -[国人翻译中文文档](https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md) - - -### Spring Boot - -[官方资料](https://docs.spring.io/spring-boot/docs/current/reference/html/) - - - - -## 服务发现 - -nacos、eureka - -### nacos - -集成了ribbon - - -spring-cloud-starter-alibaba-nacos-discovery集成ribbon - -ribbon : 客户端需要给`RestTemplate`实例添加`@LoadBalanced`注解,开启`@LoadBalanced`与`Ribbon`的集成 - - -~~~ java -@SpringBootApplication -@EnableDiscoveryClient -public class NacosConsumerApplication { - - @LoadBalanced - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - public static void main(String[] args) { - SpringApplication.run(NacosConsumerApplication.class, args); - } - - @RestController - public class TestController { - - private final RestTemplate restTemplate; - - @Autowired - public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;} - - @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) - public String echo(@PathVariable String str) { - return restTemplate.getForObject("http://service-provider/echo/" + str, String.class); - } - } -} - -~~~ - -## 网关 - -gateway 、Consul - - -### GateWay - -集成了hystrix - - -## 负载均衡 - -ribbon Fegin OpenFegin - -ribbon需要配合`@LoadBalanced`注解和`RestTemplate`实例 - -Fegin在ribbon上进行了改造,直接在接口上添加注解,无需`RestTemplate`实例 - -OpenFegin在Fegin上进行了改造,增加了spring mvc等的支持 - -## 熔断 - -hystrix 和 Sentinel - - -### hystrix - - - - -### SpringCloud 对熔断集成 - -1. 启动类增加`@EnableCircuitBreaker`注解 - -2. 根据`spring.cloud.circuit.breaker.enabled`配置判断是否开启,默认为true - -3. 从`"META-INF/spring.factories"`中寻找`org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker`的实现,这里只能是一个实现 - - 比如:`org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration` - - 在`HystrixCircuitBreakerConfiguration`中比较重要的一点就是创建了一个切面 - - ~~~ java - @Bean - public HystrixCommandAspect hystrixCommandAspect() { - return new HystrixCommandAspect(); - } - ~~~ - - 从HystrixCommandAspect中可以发现,其会监听`@HystrixCommand`和`@HystrixCollapser`两个注解,其中`@HystrixCommand`用于配置熔断的回调操作 \ No newline at end of file diff --git a/docs/back/SpringCloud/egrant/README.md b/docs/back/SpringCloud/egrant/README.md deleted file mode 100644 index 3561b35..0000000 --- a/docs/back/SpringCloud/egrant/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# 仪器设备架构 - -## 启动 - -- EgrantApplication通过SpringApplicationBuilder实现定制SpringApplication - -- 通过`StartUpParamServiceImpl`来实现,一些默认的启动参数设置 - -默认加载:egrant.yml和egrant-{profile}.yml,以及初始化nacos - - -## 网关 - -GateWay->WebFlux : HandlerFunction - -https://cloud.tencent.com/developer/article/1488120 \ No newline at end of file diff --git a/docs/back/SpringCloud/gateway.md b/docs/back/SpringCloud/gateway.md deleted file mode 100644 index a0588bf..0000000 --- a/docs/back/SpringCloud/gateway.md +++ /dev/null @@ -1,7 +0,0 @@ -# 网关 - -[2.1.5文档](https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.5.RELEASE/single/spring-cloud-gateway.html) - -GateWay->WebFlux : HandlerFunction - -https://cloud.tencent.com/developer/article/1488120 \ No newline at end of file diff --git a/docs/back/SpringCloud/images/img.png b/docs/back/SpringCloud/images/img.png deleted file mode 100644 index 53d75f3..0000000 Binary files a/docs/back/SpringCloud/images/img.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_1.png b/docs/back/SpringCloud/images/img_1.png deleted file mode 100644 index 53d75f3..0000000 Binary files a/docs/back/SpringCloud/images/img_1.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_10.png b/docs/back/SpringCloud/images/img_10.png deleted file mode 100644 index 6a08cb8..0000000 Binary files a/docs/back/SpringCloud/images/img_10.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_11.png b/docs/back/SpringCloud/images/img_11.png deleted file mode 100644 index 25eefe9..0000000 Binary files a/docs/back/SpringCloud/images/img_11.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_12.png b/docs/back/SpringCloud/images/img_12.png deleted file mode 100644 index 46efd20..0000000 Binary files a/docs/back/SpringCloud/images/img_12.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_13.png b/docs/back/SpringCloud/images/img_13.png deleted file mode 100644 index abc4918..0000000 Binary files a/docs/back/SpringCloud/images/img_13.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_14.png b/docs/back/SpringCloud/images/img_14.png deleted file mode 100644 index a3d9827..0000000 Binary files a/docs/back/SpringCloud/images/img_14.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_15.png b/docs/back/SpringCloud/images/img_15.png deleted file mode 100644 index 40d0446..0000000 Binary files a/docs/back/SpringCloud/images/img_15.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_16.png b/docs/back/SpringCloud/images/img_16.png deleted file mode 100644 index 213f390..0000000 Binary files a/docs/back/SpringCloud/images/img_16.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_17.png b/docs/back/SpringCloud/images/img_17.png deleted file mode 100644 index c51c227..0000000 Binary files a/docs/back/SpringCloud/images/img_17.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_18.png b/docs/back/SpringCloud/images/img_18.png deleted file mode 100644 index b415dd7..0000000 Binary files a/docs/back/SpringCloud/images/img_18.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_2.png b/docs/back/SpringCloud/images/img_2.png deleted file mode 100644 index 59fec41..0000000 Binary files a/docs/back/SpringCloud/images/img_2.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_3.png b/docs/back/SpringCloud/images/img_3.png deleted file mode 100644 index 532ee82..0000000 Binary files a/docs/back/SpringCloud/images/img_3.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_4.png b/docs/back/SpringCloud/images/img_4.png deleted file mode 100644 index ca06722..0000000 Binary files a/docs/back/SpringCloud/images/img_4.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_5.png b/docs/back/SpringCloud/images/img_5.png deleted file mode 100644 index cac92fb..0000000 Binary files a/docs/back/SpringCloud/images/img_5.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_6.png b/docs/back/SpringCloud/images/img_6.png deleted file mode 100644 index abccbdc..0000000 Binary files a/docs/back/SpringCloud/images/img_6.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_7.png b/docs/back/SpringCloud/images/img_7.png deleted file mode 100644 index 9adfc83..0000000 Binary files a/docs/back/SpringCloud/images/img_7.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_8.png b/docs/back/SpringCloud/images/img_8.png deleted file mode 100644 index dc24705..0000000 Binary files a/docs/back/SpringCloud/images/img_8.png and /dev/null differ diff --git a/docs/back/SpringCloud/images/img_9.png b/docs/back/SpringCloud/images/img_9.png deleted file mode 100644 index 0b47ff5..0000000 Binary files a/docs/back/SpringCloud/images/img_9.png and /dev/null differ diff --git a/docs/back/SpringCloud/run.md b/docs/back/SpringCloud/run.md deleted file mode 100644 index 4bf762a..0000000 --- a/docs/back/SpringCloud/run.md +++ /dev/null @@ -1,691 +0,0 @@ - -# 启动过程 - -> 基于版本 spring-boot:2.1.14.RELEAES,启动gateway项目进行观察 - -## 介绍 - -以下是常见的boot项目启动入口,比较核心的点就在于注解`@SpringBootApplication`和`SpringApplication.run` - -~~~ java -@SpringBootApplication -public class SpringBoot2018 { - - public static void main(String[] args) { - SpringApplication.run(SpringBoot2018.class, args); - } -} -~~~ - - - -`SpringApplication.run(String... args)`结构 - -~~~ java - -public ConfigurableApplicationContext run(String... args) { - // 计时器 - StopWatch stopWatch = new StopWatch(); - // 计时开始 - stopWatch.start(); - ConfigurableApplicationContext context = null; - Collection exceptionReporters = new ArrayList<>(); - // 设置java.awt.headless模式为true,Headless模式是在缺少显示屏、键盘或者鼠标时的系统配置。 - configureHeadlessProperty(); - // ## 获取运行监听器 - SpringApplicationRunListeners listeners = getRunListeners(args); - - // 发布ApplicationStartingEvent事件 - listeners.starting(); - try { - // 处理参数 - ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); - - // 准备环境 - ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); - - // 配置忽略bean信息 spring.beaninfo.ignore为true - configureIgnoreBeanInfo(environment); - - Banner printedBanner = printBanner(environment); - - // 创建应用上下文,默认org.springframework.context.annotation.AnnotationConfigApplicationContext - context = createApplicationContext(); - - // 获取异常报告器 - exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, - new Class[] { ConfigurableApplicationContext.class }, context); - - // 准备上下文,将相关信息和上下文绑定 - prepareContext(context, environment, listeners, applicationArguments, printedBanner); - - // 刷新上下文,这是重点,与spring初始化的核心过程类似 - refreshContext(context); - - // 刷新之后的动作,暂无实现 - afterRefresh(context, applicationArguments); - - // 计时结束 - stopWatch.stop(); - if (this.logStartupInfo) { - new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); - } - // 发布ApplicationStartedEvent事件,触发相关监听器 - listeners.started(context); - - // 调用运行器 - callRunners(context, applicationArguments); - } - catch (Throwable ex) { - handleRunFailure(context, ex, exceptionReporters, listeners); - throw new IllegalStateException(ex); - } - - try { - listeners.running(context); - } - catch (Throwable ex) { - handleRunFailure(context, ex, exceptionReporters, null); - throw new IllegalStateException(ex); - } - return context; - } -~~~ - - -::: tip -这里需要说明以下,次方法会被调用两次,一次是执行main方法时调用,另一次是在执行“准备环境”`prepareEnvironment`方法中会触发`BootstrapApplicationListener`,该方法又重新调用了run。 - -所以在执行`prepareEnvironment`时触发`BootstrapApplicationListener`时,会优先执行监听器触发的run方法调用,等执行完成之后,再继续执行之后的代码。所以`prepareEnvironment`之前的方法调用是main方法触发,`BootstrapApplicationListener`后触发,后面的方法则相反。 - -这里在分析run方法的过程中各个阶段时,并没有都针对两次运行进行分析,没特殊说明都默认为main方法调用 - -`BootstrapApplicationListener`会根据environment.getPropertySources().contains("bootstrap")判断是否执行过,不会存在递归触发的情况。 -::: - - -## 运行监听器 - - -### 获取运行监听器 - -~~~ java -private SpringApplicationRunListeners getRunListeners(String[] args) { - Class[] types = new Class[] { SpringApplication.class, String[].class }; - return new SpringApplicationRunListeners(logger, - getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); - } - -private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) { - ClassLoader classLoader = getClassLoader(); - // Use names and ensure unique to protect against duplicates - Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); - List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); - AnnotationAwareOrderComparator.sort(instances); - return instances; - } -~~~ - - -通过`SpringFactoriesLoader`去加载`org.springframework.boot.SpringApplicationRunListener` - -~~~ java -public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) { - String factoryClassName = factoryClass.getName(); - return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); - } - -private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { - MultiValueMap result = cache.get(classLoader); - if (result != null) { - return result; - } - - try { - // 从META-INF/spring.factories中查询 - Enumeration urls = (classLoader != null ? - classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : - ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); - result = new LinkedMultiValueMap<>(); - while (urls.hasMoreElements()) { - URL url = urls.nextElement(); - UrlResource resource = new UrlResource(url); - Properties properties = PropertiesLoaderUtils.loadProperties(resource); - for (Map.Entry entry : properties.entrySet()) { - String factoryClassName = ((String) entry.getKey()).trim(); - for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { - result.add(factoryClassName, factoryName.trim()); - } - } - } - cache.put(classLoader, result); - return result; - } - catch (IOException ex) { - throw new IllegalArgumentException("Unable to load factories from location [" + - FACTORIES_RESOURCE_LOCATION + "]", ex); - } - } -~~~ - -- 首先调用`loadSpringFactories(classLoader)`,通过classLoader从**META-INF/spring.factories**加载内容(如下图)。将其他中的内容存放到`LinkedMultiValueMap`对象中,等号左边的factoryClassName为key,等号右边的多值存放到LinkedList作为value。最后将结果存放到一个缓存对象`cache`中,key为当前的classloader,避免重复加载提升性能。 - -![META-INF/spring.factories](images/img_4.png) - -![classLoader](images/img_5.png) - -- 从上一点中计算出的结果中通过key`org.springframework.boot.SpringApplicationRunListener`获取所有的**Spring应用运行监听器**,然后排序存入SpringApplicationRunListeners中 - - - - -### 开启监听器 - -通过`SpringApplicationRunListeners`的`starting()`方法批量触发监听器 - -当前拥有的监听器为`EventPublishingRunListener` - -![SpringApplicationRunListeners](images/img_6.png) - - - -## 发布启动事件 - -ApplicationStartingEvent - -发布`ApplicationStartingEvent`事件,触发相关监听器(调用onApplicationEvent方法) - - -通过`EventPublishingRunListener`来组播`org.springframework.boot.context.event.ApplicationStartingEvent`事件 - -~~~ java -// EventPublishingRunListener -@Override - public void starting() { - this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); - } - -@Override - public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { - ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); - Executor executor = getTaskExecutor(); - // 先获取ApplicationStartingEvent监听器然后执行事件 - for (ApplicationListener listener : getApplicationListeners(event, type)) { - if (executor != null) { - executor.execute(() -> invokeListener(listener, event)); - } - else { - invokeListener(listener, event); - } - } - } - -// getApplicationListeners调用retrieveApplicationListeners -~~~ - -`retrieveApplicationListeners`方法获取所有监听`ApplicationStartingEvent`事件的监听器 - -1. 先通过`this.defaultRetriever.applicationListeners`获取默认的监听器listener - -2. 筛选出支持`ApplicationStartingEvent`事件的监听器,加入监听器集合 - -![监听器1](images/img_7.png) - -main与`BootstrapApplicationListener`执行时监听器是一致的。 - -3. 如果listenerBeans存在,则根据beanName创建监听器,加入监听器集合 - -this.defaultRetriever初始化? - - - -## 准备环境 - -~~~ java -private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, - ApplicationArguments applicationArguments) { - // Create and configure the environment - ConfigurableEnvironment environment = getOrCreateEnvironment(); - - // 设置转化服务,设置命令行参数,设置激活profile - configureEnvironment(environment, applicationArguments.getSourceArgs()); - - // 这应该是让配置类可以获取环境信息 - ConfigurationPropertySources.attach(environment); - - // 触发监听器方法(一旦环境准备好,但在ApplicationContext创建之前调用),发布`ApplicationEnvironmentPreparedEvent`事件 - listeners.environmentPrepared(environment); - - // 将环境信息绑定至SpringApplication - bindToSpringApplication(environment); - if (!this.isCustomEnvironment) { - environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, - deduceEnvironmentClass()); - } - ConfigurationPropertySources.attach(environment); - return environment; -} -~~~ - -当前所有监听`ApplicationEnvironmentPreparedEvent`事件的监听器 - -![ApplicationEnvironmentPreparedEvent监听器](images/img_8.png) - - -图中第一个监听器`BootstrapApplicationListener`需要在这里说明以下,该监听器被触发之后会再次执行`SpringApplication`的run方法,所以该监听器会优先于main方法进行调用创建上下文方法。 - -所以在`prepareEnvironment`之后的方法调用 - -## 创建应用上下文 - -`context = createApplicationContext();` - -~~~java -protected ConfigurableApplicationContext createApplicationContext() { - Class contextClass = this.applicationContextClass; - if (contextClass == null) { - try { - switch (this.webApplicationType) { - case SERVLET: - contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); - break; - case REACTIVE: - contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); - break; - default: - contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); - } - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException( - "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", - ex); - } - } - return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); - } -~~~ - -::: tip -由于[环境准备](#准备环境)中会触发`SpringApplicationRunListeners`的`environmentPrepared`方法,内部会触发所有监听了ApplicationEnvironmentPreparedEvent事件的监听器的`onApplicationEvent`方法。 - -listeners中包含`BootstrapApplicationListener`,该监听器会再次触发`SpringApplication`的run方法,所以该监听器会优先于main方法进行调用创建上下文方法。 - -::: - -此时如果没有特殊设置 - -`BootstrapApplicationListener`上下文默认创建为`org.springframework.context.annotation.AnnotationConfigApplicationContext` - -main上下文默认创建为`org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext` - -就启动过程而言,后续过程[准备上下文](#准备上下文)、[刷新上下文](#刷新上下文)、[刷新上下文后置动作](#刷新上下文后置动作)、[发布已启动事件](#发布已启动事件)、[调用运行器](#调用运行器)都需针对这两类上下文进行操作。 - -## 异常报告器 - -`BootstrapApplicationListener`触发 - -![异常报告器](images/img_9.png) - -main方法触发 - -![异常报告器2](images/img_10.png) - -## 准备上下文 - -prepareContext,准备上下文,将相关信息和上下文绑定 - -~~~ java -private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, - SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { - // 上下文绑定环境 - context.setEnvironment(environment); - // 应用上下文后置处理 - postProcessApplicationContext(context); - - // 在初始化完成之前,将所有的初始化器应用于上下文 - applyInitializers(context); - - // 发布ApplicationContextInitializedEvent事件 - listeners.contextPrepared(context); - if (this.logStartupInfo) { - logStartupInfo(context.getParent() == null); - logStartupProfileInfo(context); - } - - // Add boot specific singleton beans - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - beanFactory.registerSingleton("springApplicationArguments", applicationArguments); - if (printedBanner != null) { - beanFactory.registerSingleton("springBootBanner", printedBanner); - } - if (beanFactory instanceof DefaultListableBeanFactory) { - ((DefaultListableBeanFactory) beanFactory) - .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); - } - - // Load the sources 载入所有来源 - Set sources = getAllSources(); - Assert.notEmpty(sources, "Sources must not be empty"); - load(context, sources.toArray(new Object[0])); - - // 监听器载入上下文 - listeners.contextLoaded(context); -} -~~~ - -### 上下文后置处理 - -~~~ java - -protected void postProcessApplicationContext(ConfigurableApplicationContext context) { - if (this.beanNameGenerator != null) { - // 注册单例的beanName生成器 - context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, - this.beanNameGenerator); - } - if (this.resourceLoader != null) { - // 设置 - if (context instanceof GenericApplicationContext) { - ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader); - } - if (context instanceof DefaultResourceLoader) { - ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader()); - } - } - if (this.addConversionService) { - context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); - } -} -~~~ - -替`AnnotationConfigApplicationContext`设置转换服务 - -### 应用初始化器Initializers - -完成注册bean工厂后置处理器的工作 - - -从META-INF/spring.factories中寻找`ApplicationContextInitializer.class` - -也由可能通过其他方式,如: - -`BootstrapApplicationListener.AncestorInitializer`和`BootstrapApplicationListener.DelegatingEnvironmentDecryptApplicationInitializer`是由事件`BootstrapApplicationListener`直接传递增加 - -`PropertySourceBootstrapConfiguration`、`EnvironmentDecryptApplicationInitializer`是由事件`BootstrapApplicationListener`从上下文中根据类型`ApplicationContextInitializer.class`获取 - -`BootstrapApplicationListener`先通过`SpringApplicationBuilder`的run方法,创建SpringApplication并执行相关初始化操作,执行完之后在监听器后续代码还会添加Initializer,包括:`BootstrapApplicationListener.AncestorInitializer`、`PropertySourceBootstrapConfiguration`、`EnvironmentDecryptApplicationInitializer`、`BootstrapApplicationListener.DelegatingEnvironmentDecryptApplicationInitializer` - -创建时机: - -- SpringApplication创建时 - -- 事件 - -~~~ java -protected void applyInitializers(ConfigurableApplicationContext context) { - for (ApplicationContextInitializer initializer : getInitializers()) { - Class requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), - ApplicationContextInitializer.class); - Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); - initializer.initialize(context); - } -} -~~~ - -BootStrap调用执行时 - -![应用初始化器](images/img_11.png) - -main调用执行时 - -![应用初始化器2](images/img_12.png) - -### 发布事件 - -发布ApplicationContextInitializedEvent事件 - -### 载入sources - -获取所有sources -~~~ java -public Set getAllSources() { - Set allSources = new LinkedHashSet<>(); - if (!CollectionUtils.isEmpty(this.primarySources)) { - allSources.addAll(this.primarySources); - } - if (!CollectionUtils.isEmpty(this.sources)) { - allSources.addAll(this.sources); - } - return Collections.unmodifiableSet(allSources); -} -~~~ - -当上下文为`AnnotationConfigApplicationContext`时,this.primarySources = `org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration` - -将sources载入上下文 - -根据sources和上下文获取bean定义加载器 - -载入的过程会调用AnnotatedBeanDefinitionReader的register方法,将sources的内容都注册进去 - -注册的过程后面再讨论 TODO - -~~~ java -protected void load(ApplicationContext context, Object[] sources) { - if (logger.isDebugEnabled()) { - logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); - } - // 这里需要根据sources和上下文获取bean定义加载器 - BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources); - if (this.beanNameGenerator != null) { - loader.setBeanNameGenerator(this.beanNameGenerator); - } - if (this.resourceLoader != null) { - loader.setResourceLoader(this.resourceLoader); - } - if (this.environment != null) { - loader.setEnvironment(this.environment); - } - loader.load(); -} -~~~ - -### 监听器载入上下文 - -~~~ java -public void contextLoaded(ConfigurableApplicationContext context) { - for (ApplicationListener listener : this.application.getListeners()) { - // 如果实现了ApplicationContextAware接口,则设置上下文 - if (listener instanceof ApplicationContextAware) { - ((ApplicationContextAware) listener).setApplicationContext(context); - } - // 上下文注册监听器 - context.addApplicationListener(listener); - } - // 触发应用准备完成事件 - this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context)); -} - -~~~ - -主要做三件事情 - -1. 如果实现了`ApplicationContextAware`接口,则设置上下文 - -2. 上下文注册监听器该监听器 - -3. 触发应用准备完成事件`ApplicationPreparedEvent` - - -![监听器](images/img_13.png) - -## 刷新上下文 - -初始化过程的核心,与spring初始化过程类似,调用上下文对象的refresh()方法 - -~~~ java -private void refreshContext(ConfigurableApplicationContext context) { - // 调用上下文对象的refresh()方法,该方法实际上在抽象类AbstractApplicationContext中 - refresh(context); - if (this.registerShutdownHook) { - try { - context.registerShutdownHook(); - } - catch (AccessControlException ex) { - // Not allowed in some environments. - } - } -} - -// AbstractApplicationContext -@Override -public void refresh() throws BeansException, IllegalStateException { - synchronized (this.startupShutdownMonitor) { - // Prepare this context for refreshing. 为上下文刷新做准备 - prepareRefresh(); - - // Tell the subclass to refresh the internal bean factory.告诉子类去刷新内部bean工厂,bean工厂为DefaultListableBeanFactory - // 与Spring初始化不同,这里并没有做实质性的工作,而Spring在该步骤进行了BeanDefinition的加载 - ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - - // Prepare the bean factory for use in this context. 为当前上下文准备好bean工厂 - prepareBeanFactory(beanFactory); - - try { - // Allows post-processing of the bean factory in context subclasses. - // 运行上下文子类中bean工厂的后置处理,默认为空实现。AnnotationConfigApplicationContext未重写此类。 - postProcessBeanFactory(beanFactory); - - // Invoke factory processors registered as beans in the context. - // 调用在上下文中注册为Bean的工厂处理器,执行工厂后置处理器 - // 比如BootstrapImportSelectorConfiguration,执行@Import({BootstrapImportSelector.class}) - invokeBeanFactoryPostProcessors(beanFactory); - - // Register bean processors that intercept bean creation. - // 注册bean后置处理器 - registerBeanPostProcessors(beanFactory); - - // Initialize message source for this context. - // 初始化MessageSource - initMessageSource(); - - // Initialize event multicaster for this context. - // 初始化事件多播器 - initApplicationEventMulticaster(); - - // Initialize other special beans in specific context subclasses. - onRefresh(); - - // Check for listener beans and register them. - registerListeners(); - - // Instantiate all remaining (non-lazy-init) singletons. - finishBeanFactoryInitialization(beanFactory); - - // Last step: publish corresponding event. - finishRefresh(); - } - - catch (BeansException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Exception encountered during context initialization - " + - "cancelling refresh attempt: " + ex); - } - - // Destroy already created singletons to avoid dangling resources. - destroyBeans(); - - // Reset 'active' flag. - cancelRefresh(ex); - - // Propagate exception to caller. - throw ex; - } - - finally { - // Reset common introspection caches in Spring's core, since we - // might not ever need metadata for singleton beans anymore... - resetCommonCaches(); - } - } -} -~~~ - -### 执行工厂后置处理器 - -`invokeBeanFactoryPostProcessors(beanFactory);` - -上下文`AnnotationConfigApplicationContext`对应的后置处理器为 - -![工厂bean后置处理器](images/img_14.png) - -1. 首先分别对这几个bean工厂后置处理器进行一些操作 - -![操作](images/img_15.png) - -2. 因为需要先调用实现了`PriorityOrdered`接口的`BeanDefinitionRegistryPostProcessors`,找到对应的bean进行排序后执行,然后统计到处理器数组 - -`org.springframework.context.annotation.ConfigurationClassPostProcessor` - -![处理器](images/img_16.png) - -调用方法为`invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);` - -3. 与上一点类似,寻找对应的 实现了`Ordered`接口的`BeanDefinitionRegistryPostProcessors` - -4. 最后执行其他的BeanDefinitionRegistryPostProcessors,直到没有发现新的位置 - -5. 通过`invokeBeanFactoryPostProcessors`执行所有后置处理器的回调,也就是调用他们的postProcessBeanFactory - -![后置处理器的回调](images/img_17.png) - - - -后置处理器是由初始化器Initializers来完成,也可能是通过其他方式,如监听器ConfigFileApplicationListener - -## 刷新上下文后置动作 - -暂无实现 - -## 发布已启动事件 - -发布`ApplicationStartedEvent`事件,触发相关监听器 - -相关监听器的获取也是通过`EventPublishingRunListener`,详情请参考[发布启动事件](#发布启动事件) - -## 调用运行器 - -~~~ java -private void callRunners(ApplicationContext context, ApplicationArguments args) { - List runners = new ArrayList<>(); - runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); - runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); - AnnotationAwareOrderComparator.sort(runners); - for (Object runner : new LinkedHashSet<>(runners)) { - if (runner instanceof ApplicationRunner) { - callRunner((ApplicationRunner) runner, args); - } - if (runner instanceof CommandLineRunner) { - callRunner((CommandLineRunner) runner, args); - } - } -} -~~~ - -获取所有的`ApplicationRunner`和`CommandLineRunner`,排序之后调用run方法。 - - - - - - - - -`BootstrapApplicationListener` - -注册bean - -BootstrapImportSelectorConfiguration - -运行已经注册的工厂处理器 \ No newline at end of file diff --git a/docs/back/SpringCloud/security.md b/docs/back/SpringCloud/security.md deleted file mode 100644 index 99f983e..0000000 --- a/docs/back/SpringCloud/security.md +++ /dev/null @@ -1,85 +0,0 @@ -# 安全 - -> 基于spring-security-web-5.1.10 - - - - - - - - - - - - -[SpringBoot Oauth2文档](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-security-oauth2) - -[官方整合gateway和oauth2的demo](https://github.com/spring-cloud-samples/sample-gateway-oauth2login/blob/master/gateway/src/main/java/sample/GatewayApplication.java) - -[oauth2文档](https://projects.spring.io/spring-security-oauth/docs/Home.html) - -[oauth2开发指南](https://projects.spring.io/spring-security-oauth/docs/oauth2.html) - -[SpringCloudSecurity中文文档](https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_security) - -[SpringCloudSecurity官方文档](https://docs.spring.io/spring-cloud-security/docs/2.2.4.RELEASE/reference/html/) - -### Token Relay - -gateway作为Oauth2的Token Relay,使用`TokenRelayGatewayFilterFactory`实现 - -App.java - -~~~ java -@Autowired -private TokenRelayGatewayFilterFactory filterFactory; - -@Bean -public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("resource", r -> r.path("/resource") - .filters(f -> f.filter(filterFactory.apply())) - .uri("http://localhost:9000")) - .build(); -} -~~~ - - -或 - -application.yaml - -~~~ java -spring: - cloud: - gateway: - routes: - - id: resource - uri: http://localhost:9000 - predicates: - - Path=/resource - filters: - - TokenRelay= -~~~ - - - -## 执行过程 - -`org.springframework.security.web.FilterChainProxy`包含以下过滤器 - -![FilterChainProxy](images/img_18.png) - -- WebAsyncManagerIntegrationFilter - -通过使用SecurityContextCallableProcessingInterceptor.beforeConcurrentHandling(org.springframework.web.context.request.NativeWebRequest,Callable)将SecurityContext填充在Callable上,从而在SecurityContext和Spring Web的WebAsyncManager之间提供集成 - - - - -`TokenEndpoint` - - - -`AuthorizationEndpoint` \ No newline at end of file diff --git a/docs/back/SpringCloud/selector.md b/docs/back/SpringCloud/selector.md deleted file mode 100644 index 1f498c7..0000000 --- a/docs/back/SpringCloud/selector.md +++ /dev/null @@ -1,16 +0,0 @@ -# 选择器 - -BootstrapImportSelector - -![BootstrapImportSelector](https://gitee.com/zengsl/picBed/raw/master/img/20201217164314.png) - - -~~~ java -@Override -public String[] selectImports(AnnotationMetadata annotationMetadata) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - // Use names and ensure unique to protect against duplicates - List names = new ArrayList<>(SpringFactoriesLoader - .loadFactoryNames(BootstrapConfiguration.class, classLoader)); - -~~~ \ No newline at end of file diff --git a/docs/back/SpringSecurity/README.md b/docs/back/SpringSecurity/README.md deleted file mode 100644 index 435b3dd..0000000 --- a/docs/back/SpringSecurity/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# 介绍 - -> 针对spring3.x以xml方式集成security - -配置文件 - -~~~ xml - - - - - - - - -~~~ - - -## 过滤器初始化 - -Security的过滤器是在项目启动时进行BeanDefinition加载的过程中,由`HttpSecurityBeanDefinitionParser`的parse方法完成。 - -createFilterChain方法会获取三种过滤器,然后根据OrderComparator进行排序 - -~~~ java -public BeanDefinition parse(Element element, ParserContext pc) { - CompositeComponentDefinition compositeDef = - new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element)); - pc.pushContainingComponent(compositeDef); - - registerFilterChainProxyIfNecessary(pc, pc.extractSource(element)); - - // Obtain the filter chains and add the new chain to it - BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS); - List filterChains = (List) - listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue(); - // 初始化过滤器 - filterChains.add(createFilterChain(element, pc)); - - pc.popAndRegisterContainingComponent(); - return null; -} - - -private BeanReference createFilterChain(Element element, ParserContext pc) { - - 省略 - - List unorderedFilterChain = new ArrayList(); - - unorderedFilterChain.addAll(httpBldr.getFilters()); - unorderedFilterChain.addAll(authBldr.getFilters()); - unorderedFilterChain.addAll(buildCustomFilterList(element, pc)); - - Collections.sort(unorderedFilterChain, new OrderComparator()); - checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element)); - - // The list of filter beans - List filterChain = new ManagedList(); - - for (OrderDecorator od : unorderedFilterChain) { - filterChain.add(od.bean); - } - - return createSecurityFilterChainBean(element, pc, filterChain); -} - - - -~~~ \ No newline at end of file diff --git a/docs/back/SpringSecurity/accessAuth.md b/docs/back/SpringSecurity/accessAuth.md deleted file mode 100644 index b9c985e..0000000 --- a/docs/back/SpringSecurity/accessAuth.md +++ /dev/null @@ -1,222 +0,0 @@ -# 资源访问权限 - -## 配置 - -当用户访问了受保护的资源,spring会抛出一个AuthenticationException,会触发AuthenticationEntryPoint的commence方法 - -1. 配置在web.xml中的org.springframework.web.filter.DelegatingFilterProxy是一个过滤器的代理链FilterChainProxy,里面包含多个过滤器。 - -~~~ xml - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - springSecurityFilterChain - /* - -~~~ - -2. FilterChainProxy中会调用资源访问过滤器(实现该接口FilterInvocationSecurityMetadataSource)来校验当前用户是否有访问当前资源的权限。 - -3. springsecurity的配置文件中进行配置 - -~~~ xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -~~~ - - - -## 拦截过程 - -FilterChainProxy中包含以下过滤器![1568702032466](images/img.png) - -,其中有两个FilterSecurityInterceptor过滤器,两者securityMetadataSource的内容不同,其中一个是我们自己实现的类(在上文springsecurity配置中有说明) - -![1568702126843](images/img_1.png) - -当在执行FilterSecurityInterceptor的时候会执行一个父类(AbstractSecurityInterceptor)的前置方法,前置方法的主要授权拦截逻辑如下 - -1. 该前置方法其实就是调用当前`FilterSecurityInterceptor的securityMetadataSource中的Collection getAttributes(Object object)`方法,该方法会获取所有菜单的资源权限中是否包含当前请求,如果包含那么就返回当前请求对应的授权名称(比如授权表sys_authorite表中的值) - -2. 获取当前用户的授权 UserDetailServiceImpl调用authorityManager.obtainGrantedAuthorities(userId, role, null);(AuthorityManagerImpl) - -3. 使用accessDecisionManager进行授权裁决 - - - accessDecisionManager(AffirmativeBased)的decide方法会对该请求资源所需的所有权限进行轮询使用RoleVoter角色投票器进行投票判断权限。 - - - 诺该当前用户的授权中没有包含请求资源所需的权限,RoleVoter会返回ACCESS_DENIED(-1) - - - accessDecisionManager判断完所有授权之后,如果当前请求资源所需权限包含了 当前用户授权中没有的授权则抛出异常**AccessDeniedException** - -4. AbstractSecurityInterceptor捕获异常,**发布AuthorizationFailureEvent事件**,并且**将异常向上抛出**。 - -5. 因为过滤器ExceptionTranslationFilter在FilterSecurityInterceptor之前(可以从上图看出),所以抛出异常之后ExceptionTranslationFilter将进行捕获,捕获到了一个访问拒绝的异常。 - - - 在catch方法中进行逻辑判断,最终调用handleSpringSecurityException方法,该方法会针对异常类型和对应的执行操作进行判断。 - - - 某种情况(其他情况在下方代码描述有说明)会调用**sendStartAuthentication处理授权问题**,**该方法中会调用所有的authenticationEntryPoint的实现类**(下面对该类进行解释)。 - -~~~ java -public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - FilterInvocation fi = new FilterInvocation(request, response, chain); - invoke(fi); -} - -public void invoke(FilterInvocation fi) throws IOException, ServletException { - if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) - && observeOncePerRequest) { - // filter already applied to this request and user wants us to observe - // once-per-request handling, so don't re-do security checking - fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); - } else { - // first time this request being called, so perform security checking - if (fi.getRequest() != null) { - fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); - } - // 调用父类的前置方法 - InterceptorStatusToken token = super.beforeInvocation(fi); - - fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); - - super.afterInvocation(token, null); - } -} - -// AbstractSecurityInterceptor中的前置方法(缩略代码) -protected InterceptorStatusToken beforeInvocation(Object object) { - -// 父类前置方法中有以下逻辑,这里的obtainSecurityMetadataSource()的内容其实是 FilterSecurityInterceptor中的securityMetadataSource - Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object); - Authentication authenticated = authenticateIfRequired(); - // Attempt authorization - try { - this.accessDecisionManager.decide(authenticated, object, attributes); - } - catch (AccessDeniedException accessDeniedException) { - publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)) - throw accessDeniedException; - } - - ...... -} -~~~ - - -ExceptionTranslationFilter - -~~~ java -try { - chain.doFilter(request, response); - logger.debug("Chain processed normally"); -} -catch (IOException ex) { - throw ex; -} -catch (Exception ex) { - // Try to extract a SpringSecurityException from the stacktrace - Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); - RuntimeException ase = (AuthenticationException) - throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); - - if (ase == null) { - ase = (AccessDeniedException)throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); - } - if (ase != null) { - handleSpringSecurityException(request, response, chain, ase); - } else { - // Rethrow ServletExceptions and RuntimeExceptions as-is - if (ex instanceof ServletException) { - throw (ServletException) ex; - } - else if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - - // Wrap other Exceptions. This shouldn't actually happen - // as we've already covered all the possibilities for doFilter - throw new RuntimeException(ex); - } -} - -// 异常判断处理方法 -private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, - RuntimeException exception) throws IOException, ServletException { - if (exception instanceof AuthenticationException) { - logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception); - // 其他异常处理 - sendStartAuthentication(request, response, chain, (AuthenticationException) exception); - } - else if (exception instanceof AccessDeniedException) { - - if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) { - logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point", - exception); - // 拥有匿名权限的处理逻辑(当前我们调试的情况) - sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException( - "Full authentication is required to access this resource")); - } - else { - logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception); - // 未拥有匿名权限的处理逻辑,调用访问拒绝处理器 - accessDeniedHandler.handle(request, response, (AccessDeniedException) exception); - } - } -} - -protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, - AuthenticationException reason) throws ServletException, IOException { - // SEC-112: Clear the SecurityContextHolder's Authentication, as the - // existing Authentication is no longer considered valid - SecurityContextHolder.getContext().setAuthentication(null); - requestCache.saveRequest(request, response); - logger.debug("Calling Authentication entry point."); - authenticationEntryPoint.commence(request, response, reason); -} -~~~ - - - -**AuthenticationEntryPoint接口** - -当授权判断失败,被认定当前访问者无法访问当前资源的时候会触发AuthenticationEntryPoint的所有实现类。 - -作用: - 控制ajax授权访问,超时限制调用。 - -实现AuthenticationEntryPoint在commence判断是否是ajax请求,同时将该请求访问去掉匿名权限。 - diff --git a/docs/back/SpringSecurity/anonymity.md b/docs/back/SpringSecurity/anonymity.md deleted file mode 100644 index c2915c7..0000000 --- a/docs/back/SpringSecurity/anonymity.md +++ /dev/null @@ -1,16 +0,0 @@ -# 匿名权限 - -## 匿名权限配置 - -- 定义匿名权限名称 - -``A_ANONYMOUS为权限名 - - -- 资源与匿名权限做好关联 - -如果未登录属于匿名权限,那么`AnonymousAuthenticationFilter`会为当前用户设置匿名权限的上下文,访问资源时会根据当前资源url查找其对应的权限,然后将拿到的资源对应的权限和匿名用户上下文中的权限名进行匹配。如果命中则表示有权限。 - -所以当前资源必须配置有匿名权限`A_ANONYMOUS` - - diff --git a/docs/back/SpringSecurity/images/img.png b/docs/back/SpringSecurity/images/img.png deleted file mode 100644 index 86aa451..0000000 Binary files a/docs/back/SpringSecurity/images/img.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_1.png b/docs/back/SpringSecurity/images/img_1.png deleted file mode 100644 index 41ba0d1..0000000 Binary files a/docs/back/SpringSecurity/images/img_1.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_2.png b/docs/back/SpringSecurity/images/img_2.png deleted file mode 100644 index 0c7dd3f..0000000 Binary files a/docs/back/SpringSecurity/images/img_2.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_3.png b/docs/back/SpringSecurity/images/img_3.png deleted file mode 100644 index 86baa3b..0000000 Binary files a/docs/back/SpringSecurity/images/img_3.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_4.png b/docs/back/SpringSecurity/images/img_4.png deleted file mode 100644 index e41f22a..0000000 Binary files a/docs/back/SpringSecurity/images/img_4.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_5.png b/docs/back/SpringSecurity/images/img_5.png deleted file mode 100644 index 2330a4b..0000000 Binary files a/docs/back/SpringSecurity/images/img_5.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_6.png b/docs/back/SpringSecurity/images/img_6.png deleted file mode 100644 index 93efe91..0000000 Binary files a/docs/back/SpringSecurity/images/img_6.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_7.png b/docs/back/SpringSecurity/images/img_7.png deleted file mode 100644 index b4124b9..0000000 Binary files a/docs/back/SpringSecurity/images/img_7.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_8.png b/docs/back/SpringSecurity/images/img_8.png deleted file mode 100644 index 9fcb817..0000000 Binary files a/docs/back/SpringSecurity/images/img_8.png and /dev/null differ diff --git a/docs/back/SpringSecurity/images/img_9.png b/docs/back/SpringSecurity/images/img_9.png deleted file mode 100644 index bd5f34d..0000000 Binary files a/docs/back/SpringSecurity/images/img_9.png and /dev/null differ diff --git a/docs/back/SpringSecurity/oauth2.md b/docs/back/SpringSecurity/oauth2.md deleted file mode 100644 index 2b7c7ec..0000000 --- a/docs/back/SpringSecurity/oauth2.md +++ /dev/null @@ -1,400 +0,0 @@ - -# OAuth 2.0开发人员指南 - -> 对spring-security-oauth2的翻译 -> 日期:2020-12-23 -> spring-security-oauth2:2.3.5 - -[spring-security-oauth2原文](https://projects.spring.io/spring-security-oauth/docs/oauth2.html) - - -## 弃用通知 - -不建议使用Spring Security OAuth项目。 Spring Security提供了最新的OAuth 2.0支持。有关更多详细信息,请参见[《 OAuth 2.0迁移指南》](https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide)。 - -## 介绍 - -这是OAuth 2.0支持的用户指南。对于OAuth 1.0,一切都不同,因此请[参阅其用户指南](https://projects.spring.io/spring-security-oauth/docs/oauth1.html)。 - -本用户指南分为两部分,第一部分用于OAuth 2.0提供程序,第二部分用于OAuth 2.0客户端。对于提供者和客户而言,示例代码的最佳来源是[集成测试](https://github.com/spring-projects/spring-security-oauth/tree/master/tests)和[示例应用程序](https://github.com/spring-projects/spring-security-oauth/tree/master/samples/oauth2)。 - -## OAuth 2.0提供程序 - -OAuth 2.0提供程序机制负责公开受OAuth 2.0保护的资源。该配置涉及建立OAuth 2.0客户端,这些客户端可以独立或代表用户访问其受保护的资源。提供者通过管理和验证用于访问受保护资源的OAuth 2.0令牌来做到这一点。如果适用,提供者还必须为用户提供一个界面,以确认可以授予客户端访问受保护资源的权限(即确认页面)。 - -## OAuth 2.0提供程序实现 - -OAuth 2.0中的提供者角色实际上是在授权服务和资源服务之间分配的,尽管它们有时驻留在同一应用程序中,但使用Spring Security OAuth,您可以选择将它们拆分到两个应用程序中,并具有多个共享同一个授权服务的资源服务 - -拥有**授权服务器**和**资源服务器** - -对令牌的请求由Spring MVC控制器端点处理(endpoints),对受保护资源的访问由标准Spring Security请求过滤器处理。 - -为了实现OAuth 2.0授权服务器,Spring Security过滤器链中需要以下端点: - - - `AuthorizationEndpoint` 用于服务授权请求. 默认URL: /oauth/authorize. - - - `TokenEndpoint` 用于服务访问令牌的请求. 默认URL: /oauth/token. - -为了实现OAuth 2.0资源服务器,需要以下过滤器: - - - `OAuth2AuthenticationProcessingFilter` 用于在给定经过身份验证的访问令牌的情况下为请求加载身份验证. - -对于所有OAuth 2.0提供程序功能,可使用特殊的Spring OAuth **@Configuration适配器**简化配置。 - -还有一个用于OAuth配置的XML名称空间,该模式位于[https://www.springframework.org/schema/security/spring-security-oauth2.xsd](https://www.springframework.org/schema/security/spring-security-oauth2.xsd)。命名空间为`http://www.springframework.org/schema/security/oauth2` - -## 授权服务器配置 - -配置授权服务器时,必须考虑客户端用于从最终用户获取访问令牌的授予类型(例如,authorization code授权码, user credentials, refresh token)。服务器的配置用于提供客户端明细服务和令牌服务的实现,并全局启用或禁用该机制的某些方面。但是请注意,可以为每个客户端专门配置权限,使其能够使用某些授权机制和访问授权。即仅仅因为您的提供程序配置为支持"client credentials"授予类型,并不意味着授权特定的客户端使用该授予类型。 - -`@EnableAuthorizationServer`注解和实现`AuthorizationServerConfigurer`的任何`@Bean`(具有空方法的便捷适配器实现)一起用于配置OAuth 2.0授权服务器机制,。 - -::: tip -AuthorizationServerConfigurerAdapter 是上面所提到的一个具有空方法的便捷适配器实现 -::: - -以下功能委托给由Spring创建并传递到`AuthorizationServerConfigurer`中的单独的配置器: - -- `ClientDetailsS​​erviceConfigurer`:定义客户端明细服务的配置器configurer。可以初始化客户端明细,或者您可以仅引用现有存储的。 - -- `AuthorizationServerSecurityConfigurer`:定义令牌端点(token endpoint)上的安全约束。 - -- `AuthorizationServerEndpointsConfigurer`:定义授权和令牌端点(token endpoint)以及令牌服务。 - -提供者配置的一个重要方面是将授权代码提供给OAuth客户端的方式(在授权代码授权中)。 OAuth客户端通过将最终用户定向到可以在其中输入凭据的授权页面来获得授权码,从而导致从提供者授权服务器重定向回带有授权码的OAuth客户端。 OAuth 2规范中对此进行了详细说明。 - -在XML中,有一个``元素,该元素以类似的方式用于配置OAuth 2.0授权服务器 - -### 配置客户端明细(Client Details) - -`ClientDetailsS​​erviceConfigurer`(来自`AuthorizationServerConfigurer`的回调)可用于定义in-memory或JDBC的客户端明细服务实现。 - -客户端的重要属性: - -- clientId: (必填) 客户端id - -- secret: (受信任的客户端必填)客户端密钥(如果有) (required for trusted clients) the client secret, if any. `TODO` - -- scope: 客户端被限制范围,不设置或者为空则表示不受范围限制 - -- authorizedGrantTypes: 授权客户端使用的授权类型。默认值为空 - -- authorities: 授予客户端的权限(常规的Spring Security权限) - -可以通过直接访问基础存储(例如,对于`JdbcClientDetailsS​​ervice`使用数据库表)或通过`ClientDetailsManager`接口(`ClientDetailsS​​ervice`的两个实现也都实现了)来在运行的应用程序中更新客户端详细信息。 - -::: tip -并没有找到`ClientDetailsManager`这个接口 - -`ClientDetailsS​​ervice`有`InMemoryClientDetailsService`和`JdbcClientDetailsService`两个实现,分别对应内存存储和数据库存储 -::: - -注意:JDBC服务的schema未随库一起打包(因为您可能想在实践​​中使用太多变化),但是有一个示例可以从github的[测试代码](https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql)开始 - -上述测试代码如下: - -~~~ sql --- used in tests that use HSQL -create table oauth_client_details ( - client_id VARCHAR(256) PRIMARY KEY, - resource_ids VARCHAR(256), - client_secret VARCHAR(256), - scope VARCHAR(256), - authorized_grant_types VARCHAR(256), - web_server_redirect_uri VARCHAR(256), - authorities VARCHAR(256), - access_token_validity INTEGER, - refresh_token_validity INTEGER, - additional_information VARCHAR(4096), - autoapprove VARCHAR(256) -); - -create table oauth_client_token ( - token_id VARCHAR(256), - token LONGVARBINARY, - authentication_id VARCHAR(256) PRIMARY KEY, - user_name VARCHAR(256), - client_id VARCHAR(256) -); - -create table oauth_access_token ( - token_id VARCHAR(256), - token LONGVARBINARY, - authentication_id VARCHAR(256) PRIMARY KEY, - user_name VARCHAR(256), - client_id VARCHAR(256), - authentication LONGVARBINARY, - refresh_token VARCHAR(256) -); - -create table oauth_refresh_token ( - token_id VARCHAR(256), - token LONGVARBINARY, - authentication LONGVARBINARY -); - -create table oauth_code ( - code VARCHAR(256), authentication LONGVARBINARY -); - -create table oauth_approvals ( - userId VARCHAR(256), - clientId VARCHAR(256), - scope VARCHAR(256), - status VARCHAR(10), - expiresAt TIMESTAMP, - lastModifiedAt TIMESTAMP -); - - --- customized oauth_client_details table -create table ClientDetails ( - appId VARCHAR(256) PRIMARY KEY, - resourceIds VARCHAR(256), - appSecret VARCHAR(256), - scope VARCHAR(256), - grantTypes VARCHAR(256), - redirectUrl VARCHAR(256), - authorities VARCHAR(256), - access_token_validity INTEGER, - refresh_token_validity INTEGER, - additionalInformation VARCHAR(4096), - autoApproveScopes VARCHAR(256) -); -~~~ - - -### 管理令牌(Managing Tokens) - -`AuthorizationServerTokenServices`接口定义管理OAuth 2.0令牌所需的操作。请注意以下几点: - -- 创建访问令牌后,必须存储身份验证,以便接受访问令牌的资源以后可以引用它 - -- 访问令牌用于加载用于授权其创建的身份验证 - -在创建`AuthorizationServerTokenServices`实现时,您可能要考虑使用DefaultTokenServices,它可以插入许多策略来更改访问令牌的格式和存储。默认情况下,除了将其委派给`TokenStore`的令牌的持久性之外,它通过随机值创建令牌并处理所有事务,。默认存储是[内存中的实现](https://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/store/InMemoryTokenStore.html),但还有其他一些实现。这是对其中每个都有一些讨论的一个描述 - -- 默认的`InMemoryTokenStore`非常适合单台服务器(即低流量,并且在发生故障时不与备份服务器进行热交换)。大多数项目都可以从此处开始,并且可以在开发模式下以这种方式运行,从而轻松启动没有依赖性的服务器。 - -- `JdbcTokenStore`是[JDBC实现版本](https://projects.spring.io/spring-security-oauth/docs/JdbcTokenStore),它将令牌数据存储在关系数据库中。如果可以在服务器之间共享数据库,请使用JDBC版本;如果只有一个,则可以扩展同一服务器的实例;如果有多个组件,则可以使用授权和资源服务器。要使用`JdbcTokenStore`,需要在类路径上使用“ spring-jdbc”。 - -- 存储的`JSON Web TOKEN(JWT)`版本将有关授权的所有数据编码到令牌本身中(因此根本没有后端存储,这是一个很大的优势)。一个缺点是您不能轻易地撤销访问令牌,因此授予它们的有效期通常很短,并且撤销是在刷新令牌处进行的。另一个缺点是,如果您在令牌中存储了大量用户凭证信息,则令牌会变得很大。从不保留任何数据的意义上讲,`JwtTokenStore`并不是真正的“存储”,但它在翻译`DefaultTokenServices`中的令牌值和身份验证信息之间起着相同的作用。 - -注意:JDBC服务的schema未随库一起打包(因为您可能想在实践​​中使用太多变化),但是有一个示例可以从github的[测试代码](https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql)开始。确保使用`@EnableTransactionManagement`来防止在创建令牌时客户端应用程序在争用同一行之间发生冲突。还要注意,示例schema具有显式的PRIMARY KEY声明-在并发环境中,这些声明也是必需的。 - -::: tip - -以上管理令牌功能由spring-security-oauth2框架提供 - -`DefaultTokenServices`是提供的一个默认`AuthorizationServerTokenServices`接口实现 - -`TokenStore`接口是用来实现令牌持久化,spring-security-oauth2框架提供了`JdbcTokenStore`,`RedisTokenStore`,`JwtTokenStore`,`InMemoryTokenStore`和`JwkTokenStore`五种实现方式 - -::: - -### JWT令牌(JWT Tokens) - -如果要使用JWT令牌的话,需要在授权服务器中使用`JwtTokenStore`。资源服务器还需要解码令牌,因此`JwtTokenStore`依赖于`JwtAccessTokenConverter`,并且授权服务器和资源服务器都需要相同的实现。 - -**默认情况下,令牌已签名**,资源服务器还必须能够验证签名,因此它要么需要与授权服务器相同的对称(签名)密钥(共享密钥或对称密钥)(shared secret, or symmetric key),要么需要与授权服务器中(公私钥或非对称密钥)的私钥(签名密钥)匹配的公钥(认证密钥)。 - -授权服务器在`/oauth/token_key`端点上公开公钥(如果有),默认情况下使用访问规则“denyAll()”来保护。您可以通过将标准SpEL表达式注入`AuthorizationServerSecurityConfigurer`中来打开它(例如,“permitAll()”因为它是公钥而已) - -要使用`JwtTokenStore`,您需要在类路径上使用**“spring-security-jwt”**(您可以在与Spring OAuth相同的github存储库中找到它,但发布周期不同) - -::: tip - -`JwtTokenStore`和`JwtAccessTokenConverter`是两个token存储比较重要的对象 - -`AuthorizationServerSecurityConfigurer`是来自`AuthorizationServerConfigurer`的回调 - -要使用jwt的话,需要加入spring-security-jwt依赖 - -::: - -### 授权类型(Grant Types) - -授权类型是由`AuthorizationEndpoint`提供,并且可以通过`AuthorizationServerEndpointsConfigurer`(来自`AuthorizationServerConfigurer`的回调)进行配置。默认情况下除了密码之外,所有的授权类型都支持 (有关如何打开它的详细信息,请参见下文)。以下属性影响授权类型: - -- `authenticationManager`: 通过注入`AuthenticationManager`来打开密码授权 - -- `userDetailsService`: 如果你注入一个`UserDetailsService`实现或者无论用任何方式进行全局配置这个实现(例如:在`GlobalAuthenticationConfigurerAdapter`中配置),刷新令牌授权(refresh token grant)将包含对用户详细信息的检查,以确保帐户仍然处于活动状态 - -- `authorizationCodeServices`: 定义针对授权码授权的授权码服务(`AuthorizationCodeServices`的实例) - -- `implicitGrantService`: 隐式授权(imlpicit grant)期间管理状态 - -- `tokenGranter`: `TokenGranter` (完全控制授权并忽略上述其他属性) - -在XML中,授权类型作为`authorization-server`的子元素包含在内。 - -### 配置端点访问地址(Configuring the Endpoint URLs) - -`AuthorizationServerEndpointsConfigurer`(来自`AuthorizationServerConfigurer`的回调)有一个方法`pathMapping()`,有两个参数: - -- defaultPath,默认端点(endpoint)URL路径(由框架实现) - -- customPath,所需的自定义路径(以"/"开头) - -框架提供的URL路径有: - -- `/oauth/authorize`授权端点,存在于`AuthorizationEndpoint` - -- `/oauth/token`令牌端点,存在于`TokenEndpoint` - -- `/oauth/confirm_access` 用户在此发起授权批准(这个应该是返回授权请求页面的请求),存在于`WhitelabelApprovalEndpoint` - -- `/oauth/error`用于在授权服务器中呈现错误,存在于`WhitelabelErrorEndpoint` - -- `/oauth/check_token`资源服务器中用来解码访问令牌,存在于`CheckTokenEndpoint` - -- `/oauth/token_key` 如果使用JWT令牌,将公开用于令牌验证的公钥,存在于`TokenKeyEndpoint` - -需要注意的是授权端点`/oauth/authorize`(或其映射的替代方法)应使用**Spring Security**进行保护,以便只有经过身份验证的用户才能访问 - -例如使用一个标准的**Spring Security** WebSecurityConfigurer: - -~~~ java -@Override -protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests().antMatchers("/login").permitAll().and() - // default protection for all resources (including /oauth/authorize) - .authorizeRequests() - .anyRequest().hasRole("USER") - // ... more configuration, e.g. for form login -} -~~~ - -注意:如果您的授权服务器也是资源服务器,那么还有另一个优先级较低控制API资源的安全过滤器链。因为请求都被访问令牌保护,你需要使得它们都路径不被由那些主要面向用户(user-facing)过滤器链匹配。因此,请务必包括一个请求匹配器,该匹配器仅从上面的`WebSecurityConfigurer`中选取非API资源 - -令牌端点`/oauth/token`默认是受保护的,由Spring OAuth的配置中通过支持客户端密钥(client secret)的HTTP Basic authentication,这在XML配置中是无法进行设置的(所以它应该被明确的保护) - -在 XML中``元素具有一些属性,可以通过相似的方式用来更改默认端点URL。`/check_token`端点必须显式启用(使用`check-token-enabled`属性) - -::: tip - -令牌端点保护,简单点说就是在访问`/oauth/token`时,需要在HTTP请求头中增加authentication,在此存放客户端的信息。客户端信息会在`BasicAuthenticationFilter`中获取(如下图)。 - -`BasicAuthenticationFilter`详细信息可以查看该类的API文档,这里对Authorization的格式做一个简单描述: - -Authorization内容由一个身份验证方案`Basic`和一个Base64加密的`username:password`token组合而成 - -如果用户"Aladdin",密码"open sesame",`Aladdin:open sesame`经过Base64编码之后得到的token为`QWxhZGRpbjpvcGVuIHNlc2FtZQ==` - -那么Authorization请求头内容应该为`Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` - -::: - -![Authentication](images/img_9.png) - -## 自定义界面(Customizing the UI) - -### 强制使用SSL(Enforcing SSL) - -## 自定义错误处理(Customizing the Error Handling) - -## 映射用户角色到权限范围(Mapping User Roles to Scopes) - - -## 资源服务器配置(Resource Server Configuration) - -资源服务器(可以与授权服务器或单独的应用程序相同)提供受OAuth2令牌保护的资源。Spring OAuth提供实现此保护的Spring Security认证过滤器,你可以在`@Configuration`配置类上增加`@EnableResourceServer`注解开启此功能,并且如果需要的话可以使用`ResourceServerConfigurer`接口来进行配置。可以配置以下功能: - -- `tokenServices`: 定义了令牌服务的bean,`ResourceServerTokenServices`的实例 - -- `resourceId`: 资源的id(可选,但建议进行设置,并将由身份验证服务器验证) - -- 其他资源服务器的扩展点(例如:用于从输入请求中抽取令牌的`tokenExtractor`) - -- 保护资源的请求匹配器(默认是保护资源服务器的全部路径) - -- 保护资源的访问规则,默认是简单身份认证(plain "authenticated") - -- 其他被Spring Security的HttpSecurity配置器所允许的定制化 - -`@EnableResourceServer`注解自动添加了一个`OAuth2AuthenticationProcessingFilter`类型的过滤器到Spring Security的过滤器链 - -在XML中的``元素中,将id属性配置为一个servlet过滤器的bean id,过滤就可以手动的被加入到标准的Spring Security的过滤器链中 - -`ResourceServerTokenServices`是与授权服务器契约的另一半。如果资源服务器和授权服务器是在同一个应用中,同时使用的是`DefaultTokenServices`,那么就不必考虑过多,因为实现了所有所需的接口,并且自动保持了一致。如果资源服务器是分离的应用,那么你必须确保与授权服务器的功能相匹配还需要提供一个知道如何正确解码令牌的`ResourceServerTokenServices`。 -与授权服务器一样,您通常可以使用`DefaultTokenServices`,并且选项大多通过`TokenStore`表示(后端存储或本地编码)。另一种选择是`RemoteTokenServices`,它是一个Spring OAuth功能(不是规范的一部分),允许资源服务器通过授权服务器上的HTTP资源解码令牌(`/oauth/check_token`)。 -如果资源服务器中没有大量流量,`RemoteTokenServices`将非常方便(所有的请求都会使用授权服务器进行验证),或者你可以缓存保存token验证的结果。要使用`/oauth/check_token`端点,你需要在`AuthorizationServerSecurityConfigurer`中修改访问规则(默认"denyAll()")来公开它,如下面一个例子: - -~~~ java -@Override -public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { - oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess( - "hasAuthority('ROLE_TRUSTED_CLIENT')"); -} -~~~ - -在这个例子中同时配置来`/oauth/check_token`和`/oauth/token_key`两个端点(因此,受信任的资源可以获取JWT验证的公钥)。这两个端点都是使用客户端凭证(client credentials)通过HTTP Basic authentication来进行保护的。 - -### 配置OAuth-Aware表达式处理器(Configuring An OAuth-Aware Expression Handler) - -您可能希望利用Spring Security[基于表达式的访问控制](https://docs.spring.io/spring-security/site/docs/3.2.5.RELEASE/reference/htmlsingle/#el-access)。默认情况下,表达式处理程序将在`@EnableResourceServer`中注册 -表达式包括#oauth2.clientHasRole、#oauth2.clientHasAnyRole 和 #oath2.denyClient,它们可用于根据oauth客户端的角色提供访问(有关综合列表,请参阅`OAuth2SecurityExpressionMethods`) - -## OAuth 2.0 客户端(OAuth 2.0 Client) - -OAuth 2.0客户端机制负责访问其他服务器的OAuth 2.0受保护资源。配置涉及建立用户可能有权访问的相关受保护资源。客户端可能还需要为用户提供存储授权代码和访问令牌的机制 - -### 受保护资源配置(Protected Resource Configuration) - -受保护资源(或者“远程资源”)可以使用`OAuth2ProtectedResourceDetails`类型的bean定义进行定义。一个受保护资源由以下属性: - -- `id`:资源id,id仅由客户端用于查找资源;它从未在 Oauth 协议中使用过。它也被用作beanID 。 - -- `clientId`:OAuth客户端Id。这是OAuth提供程序标识客户端的ID - -- `clientSecret`:与资源关联的密钥。默认情况下,没有密钥为空 - -- `accessTokenUri`: 提供获取令牌的OAuth提供程序端点地址 - -- `scope`:指定对资源的访问范围的字符串的逗号分隔列表。默认情况下,不会指定任何范围。 - -- `clientAuthenticationScheme`:客户端用于对访问令牌终结点进行身份验证的方案。建议的值:"http_basic"和"form"。默认值:"http_basic"。参见[OAuth 2规范的第 2.1 节](https://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-2.1) - -不同的授予类型具有不同的`OAuth2ProtectedResourceDetails`的具体实现(例如,`ClientCredentialsResource`是针对客户端凭证"client_credentials"的授权类型)。对于需要用户授权的授予类型,还有一个属性: - -- `userAuthorizationUri`:如果用户需要授权访问资源,用户将被重定向到该用户。请注意,这并不总是是必需的,具体取决于支持哪些OAuth 2配置文件。 - -在 XML 中,元素可用于创建`OAuth2ProtectedResourceDetails`类型的bean。它具有与上述所有属性匹配的属性。 - -### 客户端配置 - -对于OAuth 2.0客户端,使用`@EnableOAuth2Client`简化了配置。做了两件事情: - -- 创建过滤器bean(使用ID`oauth2ClientContextFilter`)来存储当前请求和上下文。在请求期间需要进行身份验证时,它会管理对OAuth身份验证uri的重定向 - -- 在请求范围内创建类型为`AccessTokenRequest`的bean。这可以通过授权码(或隐藏式)授予客户端来防止与单个用户相关的状态发生碰撞 - -过滤器必须连接到应用程序中(例如,使用Servlet初始化器或`web.xml`为具有相同名称的`DelegatingFilterProxy`配置) - -`AccessTokenRequest`可以在`OAuth2RestTemplate` 中像这样使用: - -~~~ java -@Autowired -private OAuth2ClientContext oauth2Context; - -@Bean -public OAuth2RestTemplate sparklrRestTemplate() { - return new OAuth2RestTemplate(sparklr(), oauth2Context); -} -~~~ - -OAuth2ClientContext放置在会话范围内,以保持不同用户的状态分开。如果没有这些,您必须自己在服务器上管理等效的数据结构,将传入请求映射到用户,并关联每个用户与`OAuth2ClientContext`的单独实例。 - -在XML中,有一个``具有 id 属性的元素 - 这是Servlet`过滤器`的bean ID,必须像`@Configuration`中的情况一样映射到`DelegatingFilterProxy`(同名)。 - -### 访问受保护资源(Accessing Protected Resources) - -### 在客户端中保留令牌(Persisting Tokens in a Client) - -## 外部OAuth2提供程序客户端的自定义 - -Customizations for Clients of External OAuth2 Providers - - diff --git a/docs/back/SpringSecurity/servlet.md b/docs/back/SpringSecurity/servlet.md deleted file mode 100644 index cb8d20a..0000000 --- a/docs/back/SpringSecurity/servlet.md +++ /dev/null @@ -1,324 +0,0 @@ - -# Servlet应用 -> [spring-security文档](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/) -> 调试环境为spring-boot:2.1.4 + spring-security:5.1.10 + spring-security-oauth2:2.3.5的项目环境 - -## SpringBoot自动配置 - -Spring Boot **自动**做以下事情: - -- 开启Spring Security的默认配置,创建名为`springSecurityFilterChain`的过滤器,这个过滤器负责你的应用中所有的安全(保护应用程序 URL、验证提交的用户名和密码、重定向到表单中的日志等)。 - -- 使用用户名`user`和在控制台打印出的随机生成的密码来创建一个`UserDetailsService`接口的实现bean - -- 注册一个名字为`springSecurityFilterChain`的过滤器,用来处理Sevlet容器中的所有请求 - - -Spring Boot 的配置不多,但是做的事情很多。功能摘要如下: - -- 与应用程序的任何交互都需要经过身份验证的用户 - -- 生成默认登录表单 - -- 允许用户使用用户名和记录到控制台的密码,通过基于表单的身份验证进行身份验证 - -- 使用BCrypt保护密码存储 - -- 允许用户注销 - -- [CSRF攻击](https://en.wikipedia.org/wiki/Cross-site_request_forgery)防御(CSRF attack prevention) - -- [会话固定](https://en.wikipedia.org/wiki/Session_fixation)保护(Session Fixation protection) - -- Security Header集成 - - - [HTTP Strict Transport Security](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)用于安全请求 - - - [X-Content-Type-Options](https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx)集成 - - - 缓存控制(可稍后由应用程序覆盖,以允许缓存静态资源) - - - [X-XSS-Protection](https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/dd565647(v=vs.85)?redirectedfrom=MSDN)集成 - - - X-Frame-Options集成帮助防止[点击劫持](https://en.wikipedia.org/wiki/Clickjacking) - -- 与以下Servlet API方法集成: - - - HttpServletRequest#getRemoteUser() - - - HttpServletRequest.html#getUserPrincipal() - - - HttpServletRequest.html#isUserInRole(java.lang.String) - - - HttpServletRequest.html#login(java.lang.String, java.lang.String) - - - HttpServletRequest.html#logout() - -## Servlet Security:全局 - -介绍基于Servlet的应用中的Spring Security高级架构,是基于[`Authentication`](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-authentication), [`Authorization`](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-authorization), [`Protection Against Exploits`](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-exploits)的参考部分构建的高级架构 - -### 过滤器回顾 - -### DelegatingFilterProxy - -### FilterChainProxy - -### SecurityFilterChain - -`FilterChainProxy`会使用`SecurityFilterChain`来决定哪些Spring Security过滤器需要执行 - -简单来说`SecurityFilterChain`就是包含了一系列不同Spring Security过滤器 - -可能会有多个`SecurityFilterChain`,根据当前请求的URL来进行匹配,匹配到就将此`SecurityFilterChain`交给`FilterChainProxy`放入过滤器链中进行执行,从而会忽略掉其他的`SecurityFilterChain`。 - -**规则:优先第一个匹配到的过滤器链**,具体代码见下图: - -![过滤链匹配](images/img_2.png) - -调试代码可以发现:当前有三个安全过滤器链,均是DefaultSecurityFilterChain的实例 - -前两个过滤器链分别针对oauth2授权服务和资源服务(项目集成了Spring-Security-oauth2),第三个则为Spring Security原本的过滤器链服务 - -![过滤链截图](images/img_3.png) - -对这三个安全过滤器链做一个简单的描述: - -- 授权服务配置生成 - -匹配规则:匹配请求`/oauth/token`,`/oauth/token_key`,`/oauth/check_token` - -过滤器: - -![过滤器](images/img_4.png) - -- 资源服务器配置生成 - -匹配规则:`NotOAuthRequestMatcher` - -过滤器: - -![过滤器](images/img_5.png) - -- WebSecurityConfigurer生成 - -匹配规则:所有请求 - -过滤器: - -![过滤器](images/img_6.png) - -#### 安全过滤器链集合的初始化过程 - -安全过滤器链集合是在项目启动的时候在配置类`WebSecurityConfiguration`的springSecurityFilterChain()方法中进行初始化的,上文有说到的自动装配会自动创建一个名为`springSecurityFilterChain`的过滤器就是由这个方法创建。 -该方法内调用`WebSecurity`实例的build()方法进行过滤的构建。 - -`WebSecurity`实例中存在一个securityFilterChainBuilders属性,该属性用来存储过滤器链集合。 - -安全过滤链集合的初始化过程: - -1. `WebSecurity`实例执行build()方法过程中,调用内部方法doBuild() - -2. doBuild()内部调用this.init(),此内部会在`WebSecurity`的securityFilterChainBuilders集合中生成三个过滤器链的builder类(HttpSecurity),该对象有两个关键的属性filters和configurers。filters中有一个默认过滤器`WebAsyncManagerIntegrationFilter`,configurers有多个安全配置类 - -这里大概的逻辑就是获取到`SecurityConfigurer`集合,然后遍历配置类调用init方法。从调试结果可以看到有三个配置类: - -- `org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration` - -- `org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration` - -- `com.iris.auth.config.SecurityConfiguration` - 项目中对`WebSecurityConfigurer`接口对实现(实际上是继承适配器`WebSecurityConfigurerAdapter`) - -![SecurityConfigurers](images/img_7.png) - -~~~ java -// WebSecurity -private void init() throws Exception { - Collection> configurers = getConfigurers(); - - for (SecurityConfigurer configurer : configurers) { - configurer.init((B) this); - } - - for (SecurityConfigurer configurer : configurersAddedInInitializing) { - configurer.init((B) this); - } -} -~~~ -3. this.init()执行完之后会继续调用performBuild(),在此内部会完成所有的过滤器链集合的初始化工作,大致逻辑如下: - -- 如果有需要忽略的请求,则设置一个默认的过滤器链`DefaultSecurityFilterChain` - -- 根据第2点中生成的securityFilterChainBuilders遍历调用build方法生成对应的过滤器链,实际上会遍历HttpSecurity的configurers内容调用对应configure方法添加过滤器,从整体上来说是一个递归调用的过程。 - -~~~ java -protected Filter performBuild() throws Exception { - 略... - int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size(); - List securityFilterChains = new ArrayList<>( - chainSize); - for (RequestMatcher ignoredRequest : ignoredRequests) { - securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); - } - for (SecurityBuilder securityFilterChainBuilder : securityFilterChainBuilders) { - securityFilterChains.add(securityFilterChainBuilder.build()); - } - FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); - if (httpFirewall != null) { - filterChainProxy.setFirewall(httpFirewall); - } - filterChainProxy.afterPropertiesSet(); - - Filter result = filterChainProxy; - 略... - postBuildAction.run(); - return result; -} -~~~ - -> 这三个安全配置类也是在调用相关的BeanPostProcess对`WebSecurityConfiguration`初始化的过程中,初始化`setFilterChainProxySecurityConfigurer`方法的时候进行插入的 - -::: tip -AuthorizationServerSecurityConfiguration 和 ResourceServerConfiguration分别是Oatuh2.0认证协议中的"授权服务器安全配置"和"资源服务器安全配置" - -简单来说安全过滤器链的初始化过程和安全配置类有关系,整个过程中`SecurityBuilder`的实现类们起着至关重要的作用 - -::: - -总结: - -- 安全过滤器链的初始化的整个入口在`WebSecurityConfiguration`中,通过`springSecurityFilterChain()`将获取到的安全配置对象`WebSecurityConfiguration`进行构建(该类中会初始化系统中配置的其他安全配置对象)。 - -- `WebSecurityConfiguration`构建的过程中会对其所包含的安全过滤器对象进行构建,所包含的安全过滤器对象可能还可能包含其他的子安全配置对象,不断递归完成所有构建过程 - -- 对于每一个安全配置对象初始化子安全配置对象和过滤器的过程都在其对应的init方法中,一般是在其获取`SecurityBuilder`的过程中。对于每一个安全配置的build方法调用,大致就是重复去调用子安全配置对象的init方法。 - -下面是AuthorizationServer配置类的相关代码 - -![安全配置对象](images/img_8.png) - - -configure(http)方法会调用我们实现`AuthorizationServerConfigurer`的对应方法 - - -### 安全过滤器(Security Filters) - -安全过滤器是由`SecurityFilterChain`API插入`FilterChainProxy`。过滤器的顺序很重要!通常不需要知道 Spring Security筛选器的顺序。然而,有时知道排序是有好处的。 - -以下是Spring Security排序的完整列表: - -- ChannelProcessingFilter - -- WebAsyncManagerIntegrationFilter - -- SecurityContextPersistenceFilter - -- HeaderWriterFilter - -- CorsFilter - -- CsrfFilter - -- LogoutFilter - -- OAuth2AuthorizationRequestRedirectFilter - -- Saml2WebSsoAuthenticationRequestFilter - -- X509AuthenticationFilter - -- AbstractPreAuthenticatedProcessingFilter - -- CasAuthenticationFilter - -- OAuth2LoginAuthenticationFilter - -- Saml2WebSsoAuthenticationFilter - -- [UsernamePasswordAuthenticationFilter](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-authentication-usernamepasswordauthenticationfilter) - -- OpenIDAuthenticationFilter - -- DefaultLoginPageGeneratingFilter - -- DefaultLogoutPageGeneratingFilter - -- ConcurrentSessionFilter - -- [DigestAuthenticationFilter](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-authentication-digest) - -- BearerTokenAuthenticationFilter - -- [BasicAuthenticationFilter](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-authentication-basic) - -- RequestCacheAwareFilter - -- SecurityContextHolderAwareRequestFilter - -- JaasApiIntegrationFilter - -- RememberMeAuthenticationFilter - -- AnonymousAuthenticationFilter - -- OAuth2AuthorizationCodeGrantFilter - -- SessionManagementFilter - -- [ExceptionTranslationFilter](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-exceptiontranslationfilter) - -- [FilterSecurityInterceptor](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-authorization-filtersecurityinterceptor) - -- SwitchUserFilter - -### 处理安全异常 - -`ExceptionTranslationFilter`过滤器允许将`AccessDeniedException`和`AuthenticationException`转换为HTTP响应 - -`ExceptionTranslationFilter`作为Security Filters的一部分插入FilterChainProxy - -## 认证(Authentication) - -Spring Security为身份验证提供了全面支持。本节讨论: - -### 架构组件 - -本节介绍Spring Security在Servlet身份验证中使用的主要体系结构组件。如果您需要解释这些部分如何结合的具体流程,请查看[认证机制](#认证机制)的特定章节 - -- SecurityContextHolder - Spring Security用来存储[已认证用户](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#authentication)的详细信息的地方 - -- SecurityContext - 从SecurityContextHolder中获取,包含来当前已认证用户的Authentication(认证信息) - -- Authentication - 认证信息,可以是`AuthenticationManager`的输入,以提供用户为身份验证提供的凭据,也可以提供来自`SecurityContext`的当前用户 - -- GrantedAuthority - `Authentication`授予主体的权力(即角色、范围等) - -- AuthenticationManager - 定义Spring Security过滤器如何执行[身份验证](https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#authentication)的API。 - -- ProviderManager - `AuthenticationManager`的最常见实现 - -- AuthenticationProvider - `ProviderManager`用于执行特定类型的身份验证 - -- Request Credentials with AuthenticationEntryPoint - 用于从客户端请求凭据(即重定向到登录页、发送 WWW 身份验证响应等) - -- AbstractAuthenticationProcessingFilter - 用于身份验证的基本筛选器。这还提供了高级身份验证流的好主意,以及部分如何协同工作 - -### 认证机制 - -- Username and Password - how to authenticate with a username/password - -- OAuth 2.0 Login - OAuth 2.0 Log In with OpenID Connect and non-standard OAuth 2.0 Login (i.e. GitHub) - -- SAML 2.0 Login - SAML 2.0 Log In - -- Central Authentication Server (CAS) - Central Authentication Server (CAS) Support - -- Remember Me - How to remember a user past session expiration - -- JAAS Authentication - Authenticate with JAAS - -- OpenID - OpenID Authentication (not to be confused with OpenID Connect) - -- Pre-Authentication Scenarios - Authenticate with an external mechanism such as SiteMinder or Java EE security but still use Spring Security for authorization and protection against common exploits. - -- X509 Authentication - X509 Authentication \ No newline at end of file diff --git a/docs/back/algorithm/README.md b/docs/back/algorithm/README.md deleted file mode 100644 index d5c70d4..0000000 --- a/docs/back/algorithm/README.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar: auto ---- -# 算法 - -## 公式定理 - -### Master公式 - -master公式:也叫主定理。它提供了一种通过渐近符号表示递推关系式的方法。 - -应用Master定理可以很简便的求解递归方程。 - -$$ -T [n] = a*T[n/b] + O (N^d) -$$ -①当dlog(b,a)时,时间复杂度为O(n^d) - -### HyperLogLog - -https://www.yuque.com/abser/aboutme/nfx0a4 \ No newline at end of file diff --git a/docs/back/architect.md b/docs/back/architect.md deleted file mode 100644 index a2dfc84..0000000 --- a/docs/back/architect.md +++ /dev/null @@ -1,55 +0,0 @@ -## 分布式理论 - -### 热点数据 - -热点数据隔离: - -- 热点散列(阿里黑科技) -- 热点库 -- 多级缓存 - - - -数据异构(canal) - - - -缓存穿透、缓存击穿、缓存雪崩 - -热点缓存淘汰策略、多级缓存、布隆过滤器、Null值key - - - -### 数据高可用 - -主从、主备 - -冷(兜底方案)、热备份 - -**Canal中间件** - - - -### 消息中间件 - -Kafka - -RabbitMQ、RocketMQ - - - -### 其他 - -线上预警、业务埋点、系统水位监控系统 - -QPS预估 - - - -### 性能测试指标 - -RT响应时间 - -QPS每秒访问 - -并发数 diff --git a/docs/back/distributed/README.md b/docs/back/distributed/README.md deleted file mode 100644 index 2aaa038..0000000 --- a/docs/back/distributed/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# 概述 - -记录分布式知识 \ No newline at end of file diff --git a/docs/back/distributed/Zookeeper.md b/docs/back/distributed/Zookeeper.md deleted file mode 100644 index 20dd4fe..0000000 --- a/docs/back/distributed/Zookeeper.md +++ /dev/null @@ -1,277 +0,0 @@ -# Zookeeper - -> 版本 3.5.7 - -基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就会将负责通知已经在Zookeeper上注册的那些观察者作出相应的反应。 - -Zookeeper = 文件系统 + 通知机制 - -**一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。** - -Zookeeper保证的是CAP理论中的**CP** - -1. **Zookeeper**不能保证每次服务请求的可用性。(注:在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要 - -重新请求才能获得结果)。所以说,Zookeeper不能保证服务可用性。 - -2. 进行**Leader**选举时集群都是不可用。 - -## 基本安装 - -### 准备 - -1. [下载](https://zookeeper.apache.org/releases.html)安装包,如apache-zookeeper-3.5.7-bin.tar.gz - -> 集群配置文件使用xync分发https://www.bilibili.com/video/BV1Qp4y1n7EN?p=28 - -2. 解压安装包 - -```shell - # 解压 - tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/ - - # 修改名称 - mv /opt/apache-zookeeper-3.5.7-bin /opt/zookeeper-3.5.7 -``` - -### 配置修改 - -1. 进入/opt/zookeeper-3.5.7/conf目录,执行` mv zoo_sample.cfg zoo.cfg` -2. 修改配置文件` vim zoo.cfg`配置dataDir内容为`dataDir=/opt/zookeeper-3.5.7/zkData` - -3. 创建zkData文件夹 - -### 操作 - -- 启动` bin/zkServer.sh start` -- 查看状态` bin/zkServer.sh status` -- 启动客户端` bin/zkServer.sh stop` - - - -在/etc/profile中增加zk的环境变量 - -```shell -export ZK_HOME=/opt/zookeeper-3.5.7 - -export PATH=${PATH}:${ZK_HOME}/bin -``` - - - -### 服务脚本 - -可设置zookeeper开机启动 - -``` shell -cd /etc/rc.d/init.d - -touch zookeeper - -chmod 777 zookeeper - -vim zookeeper - -# 设置开机启动 -chkconfig zookeeper on - -# 添加和验证 -chkconfig --add zookeeper -chkconfig --list zookeeper -``` -脚本内容如下: - -``` shell -#!/bin/bash - -#chkconfig:2345 20 90 -#description:zookeeper -#processname:zookeeper -export JAVA_HOME=/opt/jdk/jdk1.8.0_321 -export JRE_HOME=${JAVA_HOME}/jre -export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib -export PATH=${PATH}:${JAVA_HOME}/bin:${JRE_HOME}/bin - -case $1 in - "start") /opt/zookeeper-3.5.7/bin/zkServer.sh start;; - "stop") /opt/zookeeper-3.5.7/bin/zkServer.sh stop;; - "status") /opt/zookeeper-3.5.7/bin/zkServer.sh status;; - "restart") /opt/zookeeper-3.5.7/bin/zkServer.sh restart;; - *) echo "require start|stop|status|restart" -esac - -``` - - - -## 集群安装 - -准备三台机器,这里使用之前配置k8s使用的三台虚拟机。 - -| 机器名 | IP | -| ------------ | --------------- | -| k8s-master01 | 192.168.143.129 | -| k8s-node01 | 192.168.143.130 | -| K8s-node02 | 192.168.143.131 | - -::: tip - -在准备好机器之后,先修改各自的hostname并修改所有机器的/etc/hosts文件 - -::: - -### 安装 - -安装过程与基本安装一致,按照相同的步骤在三台机器上重复执行即可。 - - - -### 集群配置 - -#### 配置服务器编号 - -在zkData文件夹下创建一个**myid**文文件,分别修改内容为0、1、2 - -![image-20220205155033332](https://gitee.com/zengsl/picBed/raw/master/img/2022/02/20220205155038.png) - -#### 配置zoo.cfg文件 - -增加集群配置 - -``` -server.0=k8s-master01:2888:3888 -server.1=k8s-node01:2888:3888 -server.2=k8s-node02:2888:3888 -``` - -配置解释 - -```shell -server.A=B:C:D -``` - -A是一个数字,表示这个是第几号服务器,对应myid文件中的内容。 - -B是服务器地址 - -C是这个服务器 Follower 与集群中的 Leader 服务器交换信息的端口; - -D 是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 - -Leader,而这个端口就是用来执行选举时服务器相互通信的端口。 - - - -### 集群操作 - -- 分别启动 Zookeeper - -``` shell -bin/zkServer.sh start -``` - -- 查看状态 - -```shell - bin/zkServer.sh status -``` - - - -### 集群操作脚本 - -1. 在/home/用户名/bin目录下面创建脚本 - -![image-20220205155954878](https://gitee.com/zengsl/picBed/raw/master/img/2022/02/20220205155955.png) - -脚本内容如下: - -``` shell -#!/bin/bash -case $1 in -"start"){ -for i in k8s-master01 k8s-node01 k8s-node02 -do -echo ---------- zookeeper $i 启动 ------------ -ssh $i "/opt/zookeeper-3.5.7/bin/zkServer.sh -start" -done -};; -"stop"){ -for i in k8s-master01 k8s-node01 k8s-node02 -do -echo ---------- zookeeper $i 停止 ------------ -ssh $i "/opt/zookeeper-3.5.7/bin/zkServer.sh -stop" -done -};; -"status"){ -for i in k8s-master01 k8s-node01 k8s-node02 -do -echo ---------- zookeeper $i 状态 ------------ -ssh $i "/opt/zookeeper-3.5.7/bin/zkServer.sh -status" -done -};; -esac -``` - -2. 赋权 - -``` shell - chmod u+x zk.sh -``` - -3. Zookeeper集群启动脚本 - -``` shell -zk.sh start -``` - -4. Zookeeper集群停止脚本 - -```shell -zk.sh stop -``` - - - -## 客户端命令行操作 - -### 基本操作 - -| 命令 | 功能描述 | -| --------- | ------------------------------------------------------------ | -| help | 显示所有操作命令 | -| ls path | 使用 ls 命令来查看当前 znode 的子节点 [可监听] -w 监听子节点变化 -s 附加次级信息 | -| create | 创建节点 -s 有序列 -e 临时(重启或者超时消失) | -| get path | 获得节点的值 -w 监听节点内容变化 -s 附加次级信息 | -| set | 设置节点的具体值 | -| stat | 查看节点状态 | -| delete | 删除节点 | -| deleteall | 递归删除节点 | - -启动客户端 - -``` shell -bin/zkCli.sh -server k8s-master01:2181 -``` - -显示所有操作命令 - -```shell -help -``` - - - -## 源码阅读 - -Paxos算法 - -ZAB协议 - -### 服务端启动 - -代码入口可以从zkServer.sh脚本中发现是`QuorumPeerMain`类的main方法。 - diff --git a/docs/back/distributed/distributedLocks.md b/docs/back/distributed/distributedLocks.md deleted file mode 100644 index 11c5442..0000000 --- a/docs/back/distributed/distributedLocks.md +++ /dev/null @@ -1,6 +0,0 @@ -# 分布式锁 - - -## Redlock实现 - -[分布式锁](https://www.jianshu.com/p/7e47a4503b87) \ No newline at end of file diff --git a/docs/back/distributed/distributedTransaction.md b/docs/back/distributed/distributedTransaction.md deleted file mode 100644 index f959b9a..0000000 --- a/docs/back/distributed/distributedTransaction.md +++ /dev/null @@ -1,68 +0,0 @@ -# 分布式事务 - -## 2PC 两阶段提交 - -## 2PC应用之XA - -XA是X/Open组织提出的,定义了事务管理器与资源管理器之间通信的接口协议;XA协议由数据库实现,目前支持XA协议的数据库有Oracle、MySql、BD2等; - -一个数据库实现XA协议之后,它就可以作为作为一个资源管理器参与到分布式事务中; - - -## 2PC应用之TCC - -## 柔性事务 - -单数据库事务完全遵循ACID规范,属于刚性事务,分布式事务要完全遵循ACID规范比较困难, 分布式事务属于柔性事务,满足BASE理论; - - -BASE描述: BA(Basic Availability 基本业务可用性)、S(Soft state 柔性状态)、E(Eventual consistency 最终一致性); - -柔性事务对ACID的支持: - -1、原子性: - -严格遵循; - -2、一致性: - -事务完成后的一致性严格遵循,事务中的一致性可适当放宽; - -3、隔离性: - -并行事务间不可影响;事务中间结果可见性允许安全放宽; - -4、持久性: - -严格遵循; - -为了可用性、性能的需要,柔性事务降低了一致性(C)与隔离性(I) 的要求,即“基本可用,最终一致”; - - -### 柔性事务的分类 - -柔性事务分为:两阶段型、补偿型、异步确保型、最大努力通知型; - -1、两阶段型 - -就是分布式事务两阶段提交,对应技术上的XA、JTA/JTS,这是分布式环境下事务处理的典型模式。 - -2、补偿型 - -TCC型事务(Try/Confirm/Cancel)可以归为补偿型;TCC思路是:尽早释放锁;在Try成功的情况下,如果事务要回滚,Cancel将作为一个补偿机制,回滚Try操作; - -TCC各操作事务本地化,且尽早提交 (放弃两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为; - -TCC是将资源层的两阶段提交协议转换到业务层,成为业务模型中的一部分; - -3、异步确保型 - -将一些同步阻塞的事务操作变为异步的操作,避免对数据库事务的争用;比如消息事务机制; - -4、最大努力通知型 - -通过通知服务器(消息通知)进行,允许失败,有补充机制; - -### 所有引用 - -- [分布式事务:分布式事务原理概述](https://yq.aliyun.com/articles/608863) \ No newline at end of file diff --git a/docs/back/hibernate/README.md b/docs/back/hibernate/README.md deleted file mode 100644 index 24ed99c..0000000 --- a/docs/back/hibernate/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# Hibernate知识点 - - - - - -## 结果转换器(Map) - -使用hibernate时通过设置ResultTransformer为`Transformers.ALIAS_TO_ENTITY_MAP`将查询出来的行记录转换为HashMap存储 - -```java -String selectAddressSql = "select t.area_no as code ,t.name ,t.parent_no as parent_code ,t.type as levels from const_area t order by t.seq_no"; - list = session.createSQLQuery(selectAddressSql).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list(); -``` - -先看下`Transformers.ALIAS_TO_ENTITY_MAP`实现的核心代码 - -``` java -// AliasToEntityMapResultTransformer中转换的核心代码 -@Override - public Object transformTuple(Object[] tuple, String[] aliases) { - Map result = new HashMap(tuple.length); - for ( int i=0; i types, List aliases) throws SQLException { - if (this.alias == null) { - // 从元数据中获取到别名,这里的元数据也就是之前提到的JDBC驱动中获取ResultSet时创建的 - // 这行代码最终就是从statement中获取到accessors中对应的COLUMN_NAME - this.alias = metadata.getColumnName(this.position); - } else if (this.position < 0) { - this.position = metadata.resolveColumnPosition(this.alias); - } - - if (this.type == null) { - this.type = metadata.getHibernateType(this.position); - } - - types.add(this.type); - aliases.add(this.alias); - } -``` - diff --git "a/docs/back/java/concurrency/1.java\345\244\232\347\272\277\347\250\213-\345\237\272\347\241\200.md" "b/docs/back/java/concurrency/1.java\345\244\232\347\272\277\347\250\213-\345\237\272\347\241\200.md" deleted file mode 100644 index 804c407..0000000 --- "a/docs/back/java/concurrency/1.java\345\244\232\347\272\277\347\250\213-\345\237\272\347\241\200.md" +++ /dev/null @@ -1,228 +0,0 @@ -# java多线程-基础 -## 一、简介 - -​ **什么是并发?** - -## 二、java实现方式 - -### 1、继承Thread类 - -~~~ java -class TestThread implements Runnable { - @Override - public void run() { - for (int i = 0; i < 100; i++) { - if (i % 2 == 0) { - System.out.println(Thread.currentThread().getName() + " :" + i); - } - } - } -} -// 调用 -TestThread myThread = new TestThread(); -Thread t1 = new Thread(myThread); -t1.start(); -~~~ - - - -### 2、实现Runnable接口 - -~~~ java -class Window1 implements Runnable { - private static int ticket = 100; - @Override - public void run() { - while (true) { - if (ticket > 0) { - System.out.println(Thread.currentThread().getName() + ":卖票, 票号为:" + ticket); - ticket--; - } else { - break; - } - } - } -} -// 调用 -Window1 window1 = new Window1(); -Thread t1 = new Thread(window1); -t1.setName("线程1"); -t1.start(); -~~~ - - - -### 3、实现Callable接口 - -~~~ java -class NumThread implements Callable { - @Override - public Object call() throws Exception { - int sum = 0; - for (int i = 1; i <= 100; i++) { - if (i % 2 == 0) { - System.out.println(i); - sum += i; - } - } - return sum; - } -} -// 调用 -NumThread numThread = new NumThread(); -// 创建FutureTask来接收线程 -FutureTask futureTask = new FutureTask(numThread); -// 开启线程 -new Thread(futureTask).start(); -try { - // 获取返回值 - Object sum = futureTask.get(); - System.out.println("总和为:" + sum); -} catch (InterruptedException e) { - e.printStackTrace(); -} catch (ExecutionException e) { - e.printStackTrace(); -} -~~~ - -### 4、线程池 - -~~~ java -// Executors.newFixedThreadPool 除该方法之外其他创建线程的方法 -// 比如:newFixedThreadPool、newScheduledThreadPool等。 -ExecutorService service = Executors.newFixedThreadPool(10); -service.execute(new NumThread2()); -service.execute(new NumThread3()); -// service.submit(); // 适用于有返回值的 实现Callable接口的线程 -service.shutdown(); -~~~ - - - -​ **3、4是在JDK5.0开始后新增对线程创建方式** - -## 三、线程通信 - -使用方法wait()、notify()和notifyAll() -wait():执行方法 当前线程进入阻塞状态,释放同步监视器 -notify():一旦试行次方法 就会唤醒被wait的线程,如果有多个被wait就唤醒优先级高对线程。 -notifyAll():唤醒所有被wait对线程 - -1. **wait()、notify()、notifyAll(),此通信方法必须使用在同步代码块或者同步方法中。如果是使用lock的方式则有其他通信方法。** - -2. **wait()、notify()、notifyAll(),此三个方法对调用者必须是同步代码块或者同步方法中同步代码监视器。 否则会出现java.lang.IllegalMonitorStateException异常** - - ~~~ java - class Number implements Runnable { - - private int number = 1; - private Object obj = new Object(); - - @Override - public void run() { - while (true) { - synchronized (obj) { - obj.notify(); - if (number <= 100) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName() + ":" + number); - number++; - try { - // 阻塞线程 - obj.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } else { - break; - } - } - } - } - } - ~~~ - - - -sleep 和 wait的异同? -相同点:一旦执行方法,都可以使得当前线程进入阻塞状态。 -不同点: - -- 两个方法声明对位置不同:Thread类中声明的sleep,Object类中声明wait -- 调用的要求不同:sleep可以在任何对场景调用,wait必须使用在同步代码块中,由同步代码监视器Monitor来调用。 -- 关于是否释放同步监视器:如果两个方法都是使用在同步代码块或者同步方法中,sleep不会释放。 - -经典问题:生产者消费者问题 - - - -### 四、JDK5.0后新增对线程创建方式 - -1、实现Callable接口 - -1.1. 创建一个实现Callable对实现类 -1.2. 实现call方法 -1.3. 创建Callable接口实现类的对象 -1.4. 将此Callable接口实现类的对象作为参数穿肚到FutureTask构造器中 -1.5. 将FutureTask对象作为参数放入Thread构造器参数,创建Thread对象,并且调用start() -1.6. 获取返回值futureTask.get(); - -~~~ java -public class ThreadNew { - public static void main(String[] args) { - NumThread numThread = new NumThread(); - FutureTask futureTask = new FutureTask(numThread); - new Thread(futureTask).start(); - try { - Object sum = futureTask.get(); - System.out.println("总和为:" + sum); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } -} - -class NumThread implements Callable { - @Override - public Object call() throws Exception { - int sum = 0; - for (int i = 1; i <= 100; i++) { - if (i % 2 == 0) { - System.out.println(i); - sum += i; - } - } - return sum; - } -} -~~~ - -与使用Runnable接口相比,Callable功能更强大 - -- 可以有返回值 -- 可以抛出异常 -- 支持泛型对返回值 -- 需要借助FutureTask类,比如获取返回结果 - -2、使用线程池 - -~~~ java -ExecutorService service = Executors.newFixedThreadPool(10); -service.execute(new NumThread2()); -service.execute(new NumThread3()); -// service.submit(); // 适用于有返回值的 实现Callable接口的线程 -service.shutdown(); -~~~ - -优点: - -- 提高响应速度(节约创建线程的时间,提前创建好) -- 降低资源消耗(重复利用线程池中的线程,避免每次都创建) -- 便于线程的统一管理 - diff --git "a/docs/back/java/concurrency/2.Java\345\244\232\347\272\277\347\250\213-JUC.md" "b/docs/back/java/concurrency/2.Java\345\244\232\347\272\277\347\250\213-JUC.md" deleted file mode 100644 index e4a0ff2..0000000 --- "a/docs/back/java/concurrency/2.Java\345\244\232\347\272\277\347\250\213-JUC.md" +++ /dev/null @@ -1,77 +0,0 @@ -# Java多线程-JUC -## 线程通信 - -ReentrantLock - -~~~ java -Lock lock = new ReentrantLock(); -Condition condition1 = lock.newCondition(); -lock.lock(); -lock.unlock(); -condition1.await(); -condition1.signal(); - -~~~ - - - - - -## 读写锁 - -``` -ReentrantReadWriteLock -``` - -乐观锁 - -悲观锁 - -读写分离增强效率 - -ReadWriteLock - -写写/读写 需要“互斥” - -读读 不需要“互斥” - -## 线程八锁 - -~~~ java - /** - * 1、两个普通方法,两个线程,标准打印,打印? one two - * 2、新增Thead.sleep给getOne(),打印? one two2 - * 3、新增getThree(),打印? three one two - * 4、两个普通方法,两个Number对象一个调用number1,一个调用number2 打印? two one - * 5、getOne 静态同步方法 打印? two one - * 6、getOne getTwo 静态同步方法 打印? one two - * 7、getOne静态 getTwo 非态同步方法 两个Number对象 打印? two one - * 8、getOne getTwo 静态同步方法 两个Number对象 打印? one two - * - * 线程八锁关键点: - * ①非静态方法的锁默认为this,静态方法的锁默认为对应的Class的实例 - * ②某一个时刻内,只能有一个线程持有锁,无论有几个方法。 - */ -~~~ - - - - - -## ForkJoinPool 分支/合并框架 1.7引入 - -采用"工作窃取"模式 - -``` -java8 对forkJoin进行了改造 -``` - -~~~ java -Long sum = LongStream.rangeClosed(0L, 100000000L).parallel().reduce(0L, Long::sum); -System.out.println(sum); -~~~ - - - - - diff --git a/docs/back/java/concurrency/README.md b/docs/back/java/concurrency/README.md deleted file mode 100644 index 77e1f98..0000000 --- a/docs/back/java/concurrency/README.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: 首页 ---- - -## 介绍 -记录java并发编程学习过程中的相关知识点 - -进程:操作系统中分配资源的最小单位,不同进程的运行是独立的、资源是不共享的。 - -线程:属于进程,是操作系统能够进行任务调度和执行的基本单位的最小单位。拥有所在进程的资源。 - - -## 锁的概念梳理 - -共享锁 - - -无锁 - - -自旋锁、适应性自旋锁 - - -锁粗化、锁消除 - -# AQS - -头节点为什么要设置为一个空节点 - -waitStatus - - -## 指针小结 - -取消操作大致如下: - -1. 将thread清除 - -2. 寻找前面非取消的节点,作为前驱节点 - -3. 将前节点的next指针指向当前节点的next节点(next指针切换) - - -获取锁: - -1. - - - - -释放锁: - - - - -## 参考 - -《Java并发编程艺术》 - -[从ReentrantLock的实现看AQS的原理及应用](https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html) - -[不可不说的Java“锁”事](https://tech.meituan.com/2018/11/15/java-lock.html) - - -[深入分析Synchronized原理(阿里面试题)](https://www.cnblogs.com/aspirant/p/11470858.html) \ No newline at end of file diff --git a/docs/back/java/concurrency/thread-pool.md b/docs/back/java/concurrency/thread-pool.md deleted file mode 100644 index d92dec4..0000000 --- a/docs/back/java/concurrency/thread-pool.md +++ /dev/null @@ -1,16 +0,0 @@ -# 线程池 - -[Java线程池实现原理及其在美团业务中的实践](https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html) - - -核心线程数 - -最大线程数 - -任务队列数 - - -## 任务调度 - -## 任务缓冲 - diff --git a/docs/back/java/jvm/1.buildJdk.md b/docs/back/java/jvm/1.buildJdk.md deleted file mode 100644 index 580902e..0000000 --- a/docs/back/java/jvm/1.buildJdk.md +++ /dev/null @@ -1,552 +0,0 @@ -# 构建JDK - -> 先对Github中的文档做一些翻译 2021/11/26 - -## 源码下载 - -[Github](https://github.com/openjdk/jdk) - - -## 硬件环境要求 - -JDK是一个庞大的项目,编译的过程需要很长的时间,所以需要一台性能不错的机器。 - -**强烈建议**在构建中使用 SSD 磁盘,因为磁盘速度是构建性能的限制因素之一我们强烈建议在构建中使用 SSD 磁盘,因为磁盘速度是构建性能的限制因素之一。 - -## 操作系统要求 - -JDK主线项目支持Linux、macOS、AIX和Windows。对于其他系统的支持,在单独的"port"项目中。 - -### macOS - -Apple正在使用一种非常激进的方案来推动操作系统更新,并将这些更新与所需的Xcode更新相结合。不幸的是,这使得像JDK这样的项目很难跟上运行macOS的不断更新的计算机的步伐。请参阅‎[‎有关Apple Xcode‎](https://github.com/openjdk/jdk/blob/master/doc/building.md#apple-xcode)‎的一些策略来处理此问题的部分‎. - -标准的macOS环境包含了构建所需的基础工具,但是对于外部库,建议使用包管理器。JDK在示例中使用[homebrew](https://brew.sh/),但是你可以自行选择你想要用的管理器(或没有)。 - - - -## Native编译器(工具链)要求 - -JDK存在大量的需要编译之后才能在目标平台上运行的native代码。理论上,工具链和操作系统应该是独立因素,但是从实践上来看在目标操作系统和工具链之间或多或少。 - -**操作系统工具链版本** - -Linux gcc 10.2.0 macOS Apple Xcode 10.1 (using clang 10.0.0) Windows Microsoft Visual Studio 2019 update 16.7.2 - -所有编译器都应该能够编译为C99语言标准,因为源代码中使用了一些C99功能。Microsoft Visual Studio不完全支持C99,因此在实践中,共享代码仅限于使用它支持的C99功能。 - -### gcc - -最少需要5.0版本。更老的版本会在`configure`时产生警告无法工作。 - -目前的JDK至少需要gcc版本是10.2。 - -通常来说,两者之间的任何版本都可以使用。 - -### clang - -最少需要3.5版本。`configure`无法接受更老的版本。 - -在Linux要使用clang去替换gcc,使用`--with-toolchain-type=clang` - -### Apple Xcode - -最少需要Xcode8,安装方式: - -```shell -xcode-select --install -``` - -建议在更新Xcode时保留旧版本的Xcode以构建JDK。 [blog page](http://iosdevelopertips.com/xcode/install-multiple-versions-of-xcode.html)提供了管理多版本Xcode的一些好的建议。 - -运行`configure`前使用`xcode-select -s或者`使用`--with-toolchain-path`去指定使用的Xcode版本。例如:`configure --with-toolchain-path=/Applications/Xcode8.app/Contents/Developer/usr/bin` - -如果最近更新了OS 或者/和 Xcode版本, JDK无法被编译, 请查看 [Problems with the Build Environment](https://github.com/openjdk/jdk/blob/master/doc/building.md#problems-with-the-build-environment), 和 [Getting Help](https://github.com/openjdk/jdk/blob/master/doc/building.md#getting-help) 寻找一些解决办法。 - -### Microsoft Visual Studio - -### IBM XL C/C++ - -## ‎引导 JDK 要求‎ - -矛盾的是,构建JDK需要一个预先存在的JDK。这成为“引导JDK”。但是引导JDK不必是直接从OpenJDK社区中提供的源代码构建的JDK。如果要将JDK移植到新平台,则很可能已经存在存在另一个可用于引导JDK到用于改平台的JDK。 - -经验法则是,用于构建JDK主要版本‎*‎N‎*‎的引导JDK应该是主要版本‎*‎N-1‎*‎的JDK,因此对于构建JDK 9,JDK 8适合作为引导JDK。但是,JDK 应该能够"构建自身",因此当前 JDK 源代码的最新构建是一种可接受的替代方案。如果您遵循‎*‎N-1‎*‎规则,请确保您拥有最新的更新版本,因为 JDK 8 GA 可能无法在所有平台上构建 JDK 9。‎ - -‎在发布周期的早期,版本‎*‎N-1‎*‎可能尚未发布。在这种情况下,首选的引导 JDK 将是版本‎*‎N-2,‎*‎直到版本‎*‎N-1‎*‎可用。 - -如果没有自动的检测到引导JDK,或者选中了错误的JDK,可以使用`--with-boot-jdk`去指定要使用的JDK。 - -## 外部库要求‎ - -‎不同的平台需要不同的外部库。通常,库不是可选的 - 也就是说,它们是必需的或不使用的。‎ - -如果所需的库没有被`configure`检测到,你需要提供他们的路径。`configure`有两种参数的方式去指定外部库:`--with-=`或者`--with--include= --with--lib=`。第一种方式更近简单,但要求包含文件和库文件驻留在此目录下的默认层次结构中。作为备选,第二种方式允许分别指定包含目录和库目录。 - -### FreeType - -在任何平台上都不需要来自 FreeType Project 的 FreeType2。在基于 unix 的平台上,如果配置构建构件将引用系统安装的库,而不是绑定 JDK 自己的副本,则会出现异常。 - -- macOS,运行`brew install freetype` - -- 基于apt的Linux,运行`sudo apt-get install libfreetype6-dev` - -- 基于rpm的Linux,运行`sudo yum install freetype-devel` -- Alpine Linux,运行`sudo apk add freetype-dev` - -如果`configure`没有自动检测到FreeType,可以通过`--with-freetype-include=`和`--with-freetype-lib=`来指定。 - -### CUPS - -所有平台都需要通用 UNIX 打印系统头文件,Windows 除外。通常这些文件是由操作系统提供的。 - -- 要在基于 apt 的 Linux 上安装,请尝试运行 `sudo apt-get install libcups2-dev`。 -- 要在基于 rpm 的 Linux 上安装,请尝试运行 `sudo yum install cups-devel`。 -- 要在 Alpine Linux 上安装,请尝试运行`sudo apk add cups-dev`。 - -如果 `configure` 没有正确定位 CUPS 文件,则使用 `--with-CUPS=`。 - -### X11 - -Linux 上需要某些 x11库和包含文件。 - -- 要在基于 apt 的 Linux 上安装,试着运行 sudo apt-get install libx11-dev libxext-dev libxrender-dev libxrandr-dev libxtst-dev libxt-dev。 - -- 要在基于 rpm 的 Linux 上安装,请尝试运行 sudo yum install libXtst-devel libXt-devel libXrender-devel libXrandr-devel libXi-devel。 -- 要在 Alpine Linux 上安装,请尝试运行 sudo apk add libx11-dev libxext-dev libxrender-dev libxrandr-dev libxtst-dev libxt-dev。 - -如果 `configure` 没有正确定位 x11文件,则使用 `--with-x=` 。 - -### ALSA - -在 Linux 上需要高级 Linux 声音架构,至少需要 ALSA 的0.9.1版本。 - -- 要在基于 apt 的 Linux 上安装,请尝试运行 sudo apt-get install libasound2-dev。 -- 要在基于 rpm 的 Linux 上安装,请尝试运行 sudo yum install alsa-lib-devel。 -- 要在 Alpine Linux 上安装,请尝试运行 sudo apk add alsa-lib-dev。 - -如果 configure 没有正确定位 ALSA 文件,则使用 -- with-ALSA = < path > 。 - -### libffi Liffi - -在构建 Hotspot 的零版本时,需要使用可移植外部函数接口库 libffi。 - -- 要在基于 apt 的 Linux 上安装,请尝试运行 sudo apt-get install libffi-dev。 -- 要在基于 rpm 的 Linux 上安装,请尝试运行 sudo yum install libffi-devel。 -- 要在 Alpine Linux 上安装,请尝试运行 sudo apk add libffi-dev。 - -如果 configure 没有正确定位 libffi 文件,则使用 -- with-libffi = < path > 。 - -## 构建工具需求 - -### Autoconf 自动配置文件夹 - -JDK 在所有平台上都需要 Autoconf,至少需要2.69版本。 - - - -- 要在基于 apt 的 Linux 上安装,请尝试运行 sudo apt-get install autoconf。 -- 要在基于 rpm 的 Linux 上安装,请尝试运行 sudo yum install autoconf。 -- 要在 Alpine Linux 上安装,请尝试运行 sudo apk add autoconf。 -- 要在 macOS 上安装,请尝试运行 `brew install autoconf`。 -- To install on Windows, try running `/setup-x86_64 -q -P autoconf`. -- 要在 Windows 上安装,请尝试运行 cygwin setup/setup-x86 _ 64-q-p autoconf 的 < path。 - -如果 configure 在定位你的 AUTOCONF 安装时遇到了问题,你可以使用 AUTOCONF 环境变量指定它,像这样: - -``` -AUTOCONF= configure ... -``` - -### GNU Make - -JDK 需要 gnumake,不支持其他 Make 风格。 - -必须使用 GNU Make 的至少版本3.81。对于支持 GNU Make 4.0或更高版本的发行版,我们强烈推荐它。GNU Make 4.0包含了处理并行构建(由 -- with-output-sync 支持)以及速度和稳定性改进的有用功能。 - -请注意,configure 将定位并验证 make 的正常运行版本,并将该 make 二进制文件的路径存储在配置中。如果在命令行上使用 make 启动构建,您将使用 PATH 中的第一个 make 版本,而不必使用存储在配置中的版本。这个初始 make 将被用作“ bootstrap make”,在第二个阶段,configure 定位的 make 将被调用。通常情况下,这不会带来任何问题,但是如果您的路径中有一个非常老的 Make,或者一个非 gnu Make,这可能会导致问题。 - -如果您想覆盖 configure 找到的默认 MAKE,请使用 MAKE configure 变量,例如 configure MAKE =/opt/gnu/MAKE。 - -### GNU Bash - -JDK 需要 gnubash,不支持其他 shell。 - -必须至少使用 GNU Bash 的3.2版本。 - -## Running Configure 运行配置 - -要构建 JDK,您需要一个“配置”,它包含存储构建输出的目录,以及关于平台、特定构建机器和影响构建方式的选择的信息。 - -这个配置是由配置脚本创建的,配置脚本的基本调用如下: - -``` -bash configure [options] -``` - -这将创建包含配置的输出目录,并为生成结果设置一个区域。这个目录通常看起来像 build/linux-x64-server-release,但实际名称取决于特定的配置。(也可以直接设置,请参阅使用多种配置)。这个目录在这个文档中被称为 $BUILD。 - - - -`configure` 将尝试找出您正在运行的系统以及所有必要的构建组件都在哪里。如果您已经安装了构建的所有先决条件,那么它应该可以找到所有的内容。如果它不能自动检测到任何组件,它将退出并通知您有关问题。 - -一些命令行示例: - -- 使用 `c:\freetype-i586`中的 Freetype2为 Windows 创建一个32位版本: - - ``` - bash configure --with-freetype=/cygdrive/c/freetype-i586 --with-target-bits=32 - ``` - -- 在启用服务器 JVM 和 DTrace 的情况下创建调试版本: - - ``` - bash configure --enable-debug --with-jvm-variants=server --enable-dtrace - ``` - -### Common Configure Arguments 常见的配置参数 - -下面是一些最常见和最重要的配置参数。 - -要获得所有可用配置参数的最新信息,请运行: - -```shell -bash configure --help -``` - -(注意,这个帮助文本还包括常规的 autoconf 选项,比如 -- dvidir,它与 JDK 无关。要只列出特定于 jdk 的特性,可以使用 bashconfigure -- help = short。) - -#### 配置裁剪生成的参数 - -- -- enable-debug -- 将调试级别设置为 fastdebug (这是 -- with-debug-level = fastdebug 的缩写) -- -- with-debug-level = < level >-设置调试级别,可以是发布、快速调试、缓慢调试或优化。默认是释放。优化的版本带有附加的 Hotspot 调试代码。 -- -- with-native-debug-symbols = < method >-指定是否以及如何生成本机调试符号。可用的方法有无、内部、外部、压缩。默认行为取决于平台。有关详细信息,请参阅本机调试符号。 -- -- with-version-string = < string >-指定此构建将被标识为的版本字符串。 -- -- with-version-< part > = < value >-a group of options,其中 < part > 可以是 pre,opt,build,major,minor,security 或 patch 的任意选项。使用这些选项仅从默认值修改版本字符串的相应部分,或者修改 -- with-version-string 提供的值。 -- -- with-jvm-variant = < variant > [ ,< variant > ... ]-构建 Hotspot 的指定变体(或变体)。有效的变体有: 服务器、客户机、最小、核心、零、自定义。请注意,并非所有变量都可以在单个构建中组合。 -- -- enable-JVM-feature-< feature > 或 -- disable-JVM-feature-< feature->-Include (or exclude) < feature > 作为 Hotspot 中的 JVM 特性。还可以指定要启用的特性列表,用空格或逗号分隔,如 -- with-jvm-features = < feature > [ ,< feature > ... ]。如果你用-作为前缀 < feature > ,它将被禁用。这些选项将修改您正在构建的 JVM 变体的默认特性列表。对于自定义 JVM 变体,默认列表为空。可以使用 bashconfigure -- help 找到有效的 JVM 特性的完整列表。 -- -- with-target-bits = < bits >-创建一个适合在 < bits > 平台上运行的目标二进制文件。使用它在64位构建平台上创建32位输出,而不是进行完整的交叉编译。(这就是所谓的简化版本。) - -在 Linux、 BSD 和 AIX 上,可以覆盖 Java 默认搜索运行时/JNI 库的地方。在为系统 JNI 库存在一个特殊的共享目录的情况下,这是很有用的。通过设置 java.library.path 属性,可以在运行时重写此设置。 - -- -- with-jni-libpath = < path >-在搜索运行库时使用指定的路径作为默认路径。 - -#### Configure Arguments for Native Compilation 配置本机编译的参数 - -- -- with-devkit = < path >-使用此 devkit 来编译器、工具和资源 -- -- with-sysroot = < path >-使用此目录作为 sysroot -- -- with-extra-path = < path > [ ; < path > ]-在搜索所有类型的二进制文件时,将这些目录前置到默认路径 -- -- with-toolchain-path = < path > [ ; < path > ]-在搜索工具链二进制文件(编译器等)时预置这些目录 -- -- with-extra-cflags = < flags >-在编译 JDK c 文件时附加这些标志 -- -- with-extra-cxxflags = < flags >-在编译 JDK c + + 文件时附加这些标志 -- -- with-extra-ldflags = < flags >-在链接 JDK 库时附加这些标志 - -#### Configure Arguments for External Dependencies 为外部依赖项配置参数 - -- `--with-boot-jdk=` - Set the path to the [Boot JDK](https://github.com/openjdk/jdk/blob/master/doc/building.md#boot-jdk-requirements) -- `--with-freetype=` - Set the path to [FreeType](https://github.com/openjdk/jdk/blob/master/doc/building.md#freetype) -- -- with-FreeType = < path >-设置路径为 FreeType -- -- with-CUPS = < path >-将路径设置为 CUPS -- -- with-x = < path >-设置路径为 X11 -- `--with-alsa=` - Set the path to [ALSA](https://github.com/openjdk/jdk/blob/master/doc/building.md#alsa) -- -- with-libffi = < path >-设置路径为 libffi -- -- with-JTReg = < path >-Set the path to JTReg. 参见运行测试 - -JDK 存储库中包括 JDK 使用的某些第三方库(libjpeg、 giflib、 libpng、 lcms 和 zlib)。JDK 构建的默认行为是使用包含的(“捆绑”) libjpeg、 giflib、 libpng 和 lcms 版本。对于 zlib,系统 lib (如果存在)除了 Windows 和 AIX 上使用。但是,捆绑的库可能会被外部版本所取代。为此,在这些参数中将 system 指定为 < source > 选项。(缺省值是捆绑的)。 - -- `--with-libjpeg=` - Use the specified source for libjpeg -- `--with-giflib=` - Use the specified source for giflib -- `--with-libpng=` - Use the specified source for libpng -- `--with-lcms=` - Use the specified source for lcms -- `--with-zlib=` - Use the specified source for zlib -- 在 Linux 上,可以选择 c + + 运行时的静态或动态链接。默认是静态链接,如果找不到静态库,则使用动态链接作为回退。 - -- `--with-stdc + + lib = `-使用指定的方法(静态、动态或默认)链接 c + + 运行时。 - -### Configure Control Variables 配置控制变量 - -It is possible to control certain aspects of `configure` by overriding the value of `configure` variables, either on the command line or in the environment. - -可以通过在命令行或环境中重写 configure 变量的值来控制配置的某些方面。 - -Normally, this is **not recommended**. If used improperly, it can lead to a broken configuration. Unless you're well versed in the build system, this is hard to use properly. Therefore, `configure` will print a warning if this is detected. - -通常情况下,这是不推荐的。如果使用不当,可能导致配置中断。除非你精通构建系统,否则很难正确使用它。因此,如果检测到这一点,configure 将打印一个警告。 - -However, there are a few `configure` variables, known as *control variables* that are supposed to be overridden on the command line. These are variables that describe the location of tools needed by the build, like `MAKE` or `GREP`. If any such variable is specified, `configure` will use that value instead of trying to autodetect the tool. For instance, `bash configure MAKE=/opt/gnumake4.0/bin/make`. - -但是,有一些配置变量(称为控制变量)应该在命令行上重写。这些变量描述构建所需的工具的位置,如 MAKE 或 GREP。如果指定了任何此类变量,configure 将使用该值,而不是尝试自动检测工具。例如,bash 配置 MAKE =/opt/gnumake4.0/bin/MAKE。 - -If a configure argument exists, use that instead, e.g. use `--with-jtreg` instead of setting `JTREGEXE`. - -如果存在 configure 参数,请使用它,例如使用 -- with-jtreg 而不是设置 JTREGEXE。 - -Also note that, despite what autoconf claims, setting `CFLAGS` will not accomplish anything. Instead use `--with-extra-cflags` (and similar for `cxxflags` and `ldflags`). - -还要注意,不管 autoconf 声称什么,设置 CFLAGS 将不会完成任何事情。而是使用 -- with-extra-cflags (类似于 cxxflags 和 ldflags)。 - - - -## 本机编译JDK8 - -> MacOS 12.0.1 (21A559) -> -> Xcode Version 13.1 (13A1030d) - - - -每个版本的JDK编译可能会有区别,具体参考对应版本中的文档。 - - - -```shell -# 环境准备 -xcode-select --install -brew install freetype -brew install autoconf - -# 编译 -bash configure --with-target-bits=64 --with-debug-level=slowdebug --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home 2>&1 | tee configure_mac_x64.log - -bash configure --with-tools-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr CC=clang CXX=clang++ --with-target-bits=64 --with-debug-level=slowdebug --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home 2>&1 | tee configure_mac_x64.log - -bash configure --enable-unlimited-crypto CC=clang CXX=clang++ --with-target-bits=64 --with-debug-level=slowdebug --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home 2>&1 | tee configure_mac_x64.log -``` - ---enable-unlimited-crypto 开启无限制加密策略。这个不加的话,到后面编译成功,执行 java -version 的时候会提示: - -```shell -# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again -``` - - - -bash configure CC=clang CXX=clang++ --with-target-bits=64 --with-debug-level=slowdebug --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home 2>&1 | tee configure_mac_x64.log - -`configure`成功 - -![image-20211126151610558](https://gitee.com/zengsl/picBed/raw/master/img/2021/11/20211126151610.png) - -```shell -make all -``` - - - -### 问题 - -#### configure报错 - -找不到gcc编译器,实际上是安装了的: - -``` sh -configure: error: GCC compiler is required. Try setting --with-tools-dir. -configure exiting with result code 1 -``` - -打开文件搜索“GCC compiler is required”,注释掉错误打印。 - - - -![image-20211126150618835](https://gitee.com/zengsl/picBed/raw/master/img/2021/11/20211126150618.png) - - - -#### make 报错 - -```sh -clang: error: include path for libstdc++ headers not found; pass '-stdlib=libc++' on the command line to use the libc++ standard library instead [-Werror,-Wstdlibcxx-not-found] -``` - -原因:高版本Xcode不带lstdc++库,但是hotspot还是需要。 - -参考[quantum6/xcode-missing-libstdcpp (github.com)](https://github.com/quantum6/xcode-missing-libstdcpp) 解决 - -由于本地已经有c++目录,直接执行install会报错,所以将脚本调整一下,让下一层文件夹4.2.1做链接 - -``` sh -set -e - -sudo ln -s \ - $PWD/include/c++/4.2.1 \ - /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/ - -sudo ln -s \ - $PWD/lib/libstdc++.6.0.9.tbd \ - $PWD/lib/libstdc++.6.tbd \ - $PWD/lib/libstdc++.tbd \ - /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/ - -``` - - - - - -参考: - -[1].[深夜里安静的编译一个OpenJDK8,坑太多-云海天教程 (yht7.com)](https://www.yht7.com/news/98196) - -[2].https://www.cnblogs.com/dwtfukgv/p/14727290.html - - - - - -## 本机编译JDK12 - -源码下载https://download.java.net/openjdk/jdk12/ri/openjdk-12+32_src.zip - - - - - -#### 环境说明 - -| 工具 | 版本 | -| -------- | ---------------------------------------------- | -| 操作系统 | MacOS 12.0.1 (21A559) | -| XCode | Xcode Version 13.1 (13A1030d) | -| clang | Apple clang version 13.0.0 (clang-1300.0.29.3) | -| clang++ | Apple clang version 13.0.0 (clang-1300.0.29.3) | - - - -```shell -bash configure --enable-unlimited-crypto --with-target-bits=64 --with-debug-level=slowdebug --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk-11.0.8.jdk/Contents/Home 2>&1 | tee configure_mac_x64.log -``` - -#bash configure --enable-debug --with-jvm-variants=server - - - -错误一 - -```shell -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/src/hotspot/share/runtime/sharedRuntime.cpp:2873:85: error: expression does not compute the number of elements in this array; element type is 'double', not 'relocInfo' [-Werror,-Wsizeof-array-div] - buffer.insts()->initialize_shared_locs((relocInfo*)locs_buf, sizeof(locs_buf) / sizeof(relocInfo)); - ~~~~~~~~ ^ -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/src/hotspot/share/runtime/sharedRuntime.cpp:2872:14: note: array 'locs_buf' declared here - double locs_buf[20]; - ^ -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/src/hotspot/share/runtime/sharedRuntime.cpp:2873:85: note: place parentheses around the 'sizeof(relocInfo)' expression to silence this warning - buffer.insts()->initialize_shared_locs((relocInfo*)locs_buf, sizeof(locs_buf) / sizeof(relocInfo)); - ^ -1 error generated. - -``` - -```c++ -buffer.insts()->initialize_shared_locs((relocInfo*)locs_buf, sizeof(locs_buf) / sizeof(relocInfo)); -替换为 -buffer.insts()->initialize_shared_locs((relocInfo*)locs_buf, (sizeof(locs_buf) / sizeof(relocInfo))); - - -``` - -错误二 - -```shell -In file included from /Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/test/hotspot/gtest/logging/logTestFixture.cpp:27: -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/test/hotspot/gtest/logging/logTestUtils.inline.hpp:34:70: error: suspicious concatenation of string literals in an array initialization; did you mean to separate the elements with a comma? [-Werror,-Wstring-concatenation] - "=", "+", " ", "+=", "+=*", "*+", " +", "**", "++", ".", ",", ",," ",+", - ^ - , -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/test/hotspot/gtest/logging/logTestUtils.inline.hpp:34:65: note: place parentheses around the string literal to silence warning - "=", "+", " ", "+=", "+=*", "*+", " +", "**", "++", ".", ",", ",," ",+", - ^ -1 error generated. - -``` - -补一个逗号 - -![image-20211209102153300](https://gitee.com/zengsl/picBed/raw/master/img/2021/12/20211209102158.png) - - - -错误三 - -```shell -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/test/hotspot/gtest/logging/test_logFileOutput.cpp:83:5: error: suspicious concatenation of string literals in an array initialization; did you mean to separate the elements with a comma? [-Werror,-Wstring-concatenation] - "filecount=9999999999999999999999999" - ^ -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/test/hotspot/gtest/logging/test_logFileOutput.cpp:82:5: note: place parentheses around the string literal to silence warning - "filesize=9999999999999999999999999" - ^ -1 error generated. - -``` - -补一个逗号 - -![image-20211209102434695](https://gitee.com/zengsl/picBed/raw/master/img/2021/12/20211209102554.png) - -错误四 - -``` shell -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/test/hotspot/gtest/classfile/test_symbolTable.cpp:62:6: error: explicitly assigning value of variable of type 'TempNewSymbol' to itself [-Werror,-Wself-assign-overloaded] - s1 = s1; // self assignment - ~~ ^ ~~ -1 error generated. - -``` - -![image-20211209103012137](https://gitee.com/zengsl/picBed/raw/master/img/2021/12/20211209103012.png) - - - -错误五 - -```shell -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/src/hotspot/share/runtime/arguments.cpp:1452:35: error: result of comparison against a string literal is unspecified (use an explicit string comparison function instead) [-Werror,-Wstring-compare] - if (old_java_vendor_url_bug != DEFAULT_VENDOR_URL_BUG) { - ^ ~~~~~~~~~~~~~~~~~~~~~~ -1 error generated. - -``` - -```c++ -// if (old_java_vendor_url_bug != DEFAULT_VENDOR_URL_BUG) { 替换 - if (strcmp(old_java_vendor_url_bug, DEFAULT_VENDOR_URL_BUG) != 0) { -``` - - - -错误六 - -```shell -/Users/zengsl/Project/study/openjdk-compile/openjdk-12+32/src/java.base/unix/native/libjava/ProcessImpl_md.c:362:17: error: 'vfork' is deprecated: Use posix_spawn or fork [-Werror,-Wdeprecated-declarations] - resultPid = vfork(); - ^ -/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/usr/include/unistd.h:604:1: note: 'vfork' has been explicitly marked deprecated here -__deprecated_msg("Use posix_spawn or fork") -^ -/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/usr/include/sys/cdefs.h:208:48: note: expanded from macro '__deprecated_msg' - #define __deprecated_msg(_msg) __attribute__((__deprecated__(_msg))) - ^ -1 error generated. - -``` - -不知道咋解决。。[官方issue](https://bugs.openjdk.java.net/browse/JDK-8274293)有人提此问题 - - - -### 本机编译JDK17 - -openjdk17https://download.java.net/openjdk/jdk17/ri/openjdk-17+35_src.zip - -[openjdk17下载地址](http://jdk.java.net/java-se-ri/17) - -```sh -bash configure --enable-unlimited-crypto --with-target-bits=64 --with-debug-level=slowdebug --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk-16.0.1.jdk/Contents/Home 2>&1 | tee configure_mac_x64.log -``` - -错误一 - -``` -error: invalid integral value '16-DMAC_OS_X_VERSION_MIN_REQUIRED=10120' in '-mstack-alignment=16-DMAC_OS_X_VERSION_MIN_REQUIRED=10120' -``` - -[[JDK-8272700\] [macos] Build failure with Xcode 13.0 after JDK-8264848 - Java Bug System](https://bugs.openjdk.java.net/browse/JDK-8272700) diff --git a/docs/back/java/jvm/README.md b/docs/back/java/jvm/README.md deleted file mode 100644 index ff7dbfa..0000000 --- a/docs/back/java/jvm/README.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -sidebarDepth: 2 ---- -> 记录Java虚拟机相关知识 - -## 类加载器 - -`ClassLoader`和`Class.forName`的区别 - -`ClassLoader`默认不会初始化类,只是加载到内存中 - -`Class.forName`默认会初始化类,可以通过参数设置 - -`Class.forName`使用的是**调用类的加载器**,通过`ClassLoader.getClassLoader(Reflection.getCallerClass())`来获取 - -## 多线程 - -[jls-17.7](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7) - -[synchronized的JVM底层实现](https://blog.csdn.net/niuwei22007/article/details/51433669) - -[synchronized](https://www.cnblogs.com/aspirant/p/11470858.html) - -### Synchronized关键字 - -- 字节码层面包括: - -同步块`monitorenter`和`monitorexit`两个指令,同步方法有`ACC_SYNCHRONIZED`修饰 - -monitorenter, monitorexit的指令解析是通过 InterpreterRuntime.cpp中的两个方法实现。monitor锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的 - -- 源码层面有以下几个概念: - -`ObjectMonitor` - -Contention List:所有请求锁的线程将被首先放置到该竞争队列 - -Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List - -Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set - -OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck - -Owner:获得锁的线程称为Owner - -!Owner:释放锁的线程 - -#### 锁粗化 - -扩大锁范围,将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁 - -#### 锁消除 - -这个跟后端编译优化中的逃逸分析有关系。 - -#### 锁膨胀 - -何为对象头? - - -- 无锁 - -- 偏向锁 - -偏向锁在JDK1.6之后是默认启用的,但是在应用启动几秒钟之后才激活,可以用参数进行设置,也可以关闭偏向锁。 - -当一个对象已经计算过一致性哈希码(未重写hashCode方法,指Obejct::hashCode)之后,它就再也无法进入偏向锁状态了;而当一个对象当前正处于偏向锁状态,又收到需要计算其一致性哈希码的请求时,它的偏向状态会被立即撤销,并且锁会膨胀为重量级锁。 - - -- 轻量级锁 - -Lock Record:displace hdr、owner - -- 重量级锁 - - - -#### volatile和synchronized的区别 - -截止JDK8, Java里只有`volatile`变量是能实现禁止指令重排的。 - -`volatile`能保证可见性、有序性 - -`synchronized`能保证可见性、有序性、原子性 - -关于有序性,两者针对的角度不同,`synchronized`是指的块与块之间看起来是原子操作,块与块之间有序可见。volatile 是在底层通过内存屏障防止指令重排的,变量前后之间的指令与指令之间有序可见 - -### volatile关键字 - -[深度解析volatile—底层实现](https://www.jianshu.com/p/2643c9ea1b82) - -**保证了内存可见性和禁止指令重排序** - -MESI:CPU缓存一致性协议 - -X86架构(TSO平台)下只存在StoreLoad问题,通过lock指令来解决(之所以没用mfence,是因为性能更好)。为什么只有StoreLoad的问题?因为intel采用了Store Buffer,它是一个CPU内部缓冲区,MESI无法保证CPU的内部缓冲区的可见性。用lock指令会强制Store Buffer写回缓存。 - -编码层面:volatile - -字节码层面:ACC_VOLATILE - -JVM层面:StoreStore,StoreLoad,LoadStore,LoadLoad - -StoreLoad的实现用到了lock指令(intel) - -CPU层面:sfence、lfence、mfence - -long和double的非原子性协定 - - diff --git a/docs/back/java/jvm/jdkCommand.md b/docs/back/java/jvm/jdkCommand.md deleted file mode 100644 index e7bbc01..0000000 --- a/docs/back/java/jvm/jdkCommand.md +++ /dev/null @@ -1,207 +0,0 @@ ---- -sidebarDepth: 2 ---- - -## JDK内置调优工具 - -### JPS - -虚拟机进程状况工具 - -```shell -jps -usage: jps [-help] - jps [-q] [-mlvV] [] - -Definitions: - : [:] -``` - -### jmap - -Java内存映像工具 - -```shell -# 查看帮助信息 -jmap -help -Usage: - jmap -clstats - to connect to running process and print class loader statistics - jmap -finalizerinfo - to connect to running process and print information on objects awaiting finalization - jmap -histo[:live] - to connect to running process and print histogram of java object heap - if the "live" suboption is specified, only count live objects - jmap -dump: - to connect to running process and dump java heap - jmap -? -h --help - to print this help message - - dump-options: - live dump only live objects; if not specified, - all objects in the heap are dumped. - format=b binary format - file= dump heap to - - Example: jmap -dump:live,format=b,file=heap.bin - -# dump堆内存 -jmap -dump:live,format=b,file=heap.dump -``` - -除了使用jmap命令,还可以通过以下方式: - -> 1、使用 jconsole 选项通过 HotSpotDiagnosticMXBean 从运行时获得堆转储(生成dump文件)、 -> -> 2、虚拟机启动时如果指定了 -XX:+HeapDumpOnOutOfMemoryError 选项, 则在抛出 OutOfMemoryError 时, 会自动执行堆转储。 -> -> 3、使用 hprof 命令 - - - -### jstack - -jstack主要用来查看某个Java进程内的线程堆栈信息 - -```shell -# 查看帮助信息 -jstack -help -Options: - -l long listing. Prints additional information about locks 会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况 - -e extended listing. Prints additional information about threads - -? -h --help -help to print this help message -``` - -### jstat - -JVM统计监测工具 - -```shell -# 查看帮助信息 -jstat -help -Usage: jstat --help|-options - jstat -