├── .github └── workflows │ └── deploy-docs.yml ├── .gitignore ├── DirectoryV3.xml ├── README.md ├── package.json ├── pnpm-lock.yaml └── src ├── .vuepress ├── client.ts ├── config.ts ├── config │ └── docSearchLocales.ts ├── path │ ├── navbar.ts │ └── sidebar │ │ ├── index.ts │ │ ├── installation-guide.ts │ │ ├── java-features.ts │ │ ├── jvm.ts │ │ └── spring-framework.ts ├── public │ ├── CNAME │ ├── assets │ │ └── images │ │ │ └── cover3.jpg │ ├── favicon.ico │ ├── images │ │ ├── personal │ │ │ └── geekyspace.png │ │ └── system │ │ │ ├── geeky.png │ │ │ ├── geeky_en.png │ │ │ └── geeky_zh.png │ └── special │ │ └── wedding-chenzhuo.html ├── styles │ ├── config.scss │ ├── index.scss │ └── palette.scss └── theme.ts ├── README.md ├── about-me.md ├── article.md └── md ├── docker ├── install.md ├── mirror-acceleration.md ├── overview.md └── top20-commands.md ├── idea-tips └── activation.md ├── installation-guide ├── README.md ├── base-tools │ └── Homebrew.md ├── dev-env │ ├── java │ │ └── SDKMAN.md │ └── nodejs │ │ ├── Corepack.md │ │ ├── nrm.md │ │ └── nvm.md └── os │ └── windows-office-activation.md ├── java-features ├── Java10 │ └── jep286-local-variable-type-inference.md ├── Java11 │ └── jep320-remove-JavaEE-CORBA.md ├── Java14 │ └── jep361-switch-expressions.md ├── Java15 │ ├── jep371-hidden-classes.md │ └── jep378-text-blocks.md ├── Java16 │ ├── jep394-pattern-matching-for-instanceof.md │ └── jep395-records.md ├── Java17 │ ├── jep406-pattern-matching-for-switch-preview.md │ └── jep409-sealed-classes.md ├── Java18 │ ├── jep400-utf8-by-default.md │ ├── jep408-simple-web-server.md │ └── jep413-code-snippets-in-api-documentation.md ├── Java19 │ └── java19-new-features-summary.md ├── Java20 │ └── java20-new-features-summary.md ├── Java21 │ ├── jep430-string-templates.md │ ├── jep431-sequenced-collections.md │ ├── jep439-generational-zgc.md │ ├── jep440-record-partterns.md │ ├── jep441-pattern-matching-for-switch.md │ └── jep444-virtual-threads.md ├── Java9 │ ├── jep222-jshell.md │ └── jep269-convenience-factory-methods-for-collections.md └── README.md ├── java-juc └── README.md ├── jvm ├── README.md ├── part1 │ ├── compile_jdk.md │ └── overview.md ├── part2 │ ├── heap-object-flow.md │ ├── runtime-data-areas.md │ └── visual-tools │ │ └── visualvm.md └── part3 │ ├── bytecode-instructions-set.md │ ├── class-file-structure.md │ └── class-loading-mechanism.md ├── spring-boot ├── README.md └── quickstart.md ├── spring-data-jpa ├── README.md └── jetbrains │ └── getting-started.md ├── spring-framework ├── core │ ├── README.md │ ├── beans-definition.md │ ├── beans-lifecycle.md │ ├── beans-scope.md │ ├── child-bean-definitions.md │ ├── dependencies │ │ ├── README.md │ │ ├── factory-autowire.md │ │ ├── factory-collaborators.md │ │ ├── factory-dependson.md │ │ ├── factory-lazy-init.md │ │ ├── factory-method-injection.md │ │ └── factory-properties-detailed.md │ └── ioc-container.md └── overview │ ├── README.md │ └── quickstart.md └── template └── blog template.md /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | 2 | name: 部署文档 3 | 4 | on: 5 | push: 6 | branches: 7 | # 确保这是你正在使用的分支名称 8 | - master 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | deploy-gh-pages: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | # 如果你文档需要 Git 子模块,取消注释下一行 22 | # submodules: true 23 | 24 | - name: 安装 pnpm 25 | uses: pnpm/action-setup@v2 26 | with: 27 | run_install: true 28 | version: 8 29 | 30 | 31 | - name: 设置 Node.js 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: 20 35 | cache: pnpm 36 | 37 | 38 | - name: 构建文档 39 | env: 40 | NODE_OPTIONS: --max_old_space_size=8192 41 | run: |- 42 | pnpm run docs:build 43 | > src/.vuepress/dist/.nojekyll 44 | 45 | - name: 部署文档 46 | uses: JamesIves/github-pages-deploy-action@v4 47 | with: 48 | # 这是文档部署到的分支名称 49 | branch: gh-pages 50 | folder: src/.vuepress/dist 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /**/.obsidian/ 3 | node_modules/ 4 | src/.vuepress/.cache/ 5 | src/.vuepress/.temp/ 6 | src/.vuepress/dist/ 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /DirectoryV3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 在线访问地址 2 | 3 | https://www.geekyspace.cn/ 4 | 5 | ## 博客效果图展示: 6 | 7 | image 8 | 9 | image 10 | 11 | image 12 | 13 | image 14 | 15 | image 16 | 17 | image 18 | 19 | ## 项目介绍 20 | 21 | 本项目是一个基于VuePress的个人博客项目,主要用于记录个人的学习和工作经验,以及一些技术分享。 22 | 23 | ## 代码仓库地址 24 | 25 | https://github.com/joeljhou/geekyspace/ 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuepress-theme-hope-template", 3 | "version": "2.0.0", 4 | "description": "A project of vuepress-theme-hope", 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "docs:build": "vuepress-vite build src", 9 | "docs:clean-dev": "vuepress-vite dev src --clean-cache", 10 | "docs:dev": "vuepress-vite dev src", 11 | "docs:dedupe": "pnpm dedupe", 12 | "docs:update-package": "pnpm dlx vp-update" 13 | }, 14 | "devDependencies": { 15 | "@vuepress/bundler-vite": "2.0.0-rc.9", 16 | "@vuepress/plugin-copyright": "2.0.0-rc.21", 17 | "@vuepress/plugin-docsearch": "2.0.0-rc.21", 18 | "@vuepress/plugin-feed": "2.0.0-rc.21", 19 | "@vuepress/plugin-google-analytics": "2.0.0-rc.21", 20 | "@vuepress/plugin-redirect": "2.0.0-rc.21", 21 | "mermaid": "^11.4.1", 22 | "vue": "^3.4.27", 23 | "vuepress": "2.0.0-rc.9", 24 | "vuepress-plugin-comment2": "2.0.0-rc.30", 25 | "vuepress-theme-hope": "2.0.0-rc.32" 26 | }, 27 | "packageManager": "pnpm@10.6.3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6" 28 | } 29 | -------------------------------------------------------------------------------- /src/.vuepress/client.ts: -------------------------------------------------------------------------------- 1 | // client.ts ---> 客户端配置文件 2 | // @ts-ignore 3 | import {defineGiscusConfig} from 'vuepress-plugin-comment2/client' 4 | // @ts-ignore 5 | import {defineClientConfig} from "vuepress/client"; 6 | 7 | /** 8 | * // 设置评论插件Giscus客户端选项:https://giscus.app/zh-CN 9 | */ 10 | defineGiscusConfig({ 11 | repo: 'joeljhou/geekyspace', 12 | repoId: 'R_kgDOK4fo4g', 13 | category: 'Announcements', 14 | categoryId: 'DIC_kwDOK4fo4s4Cd4Y9', 15 | mapping: 'title' 16 | }) 17 | 18 | export default defineClientConfig({ 19 | // 解决百度统计单页面应用无法统计的问题 20 | enhance({app, router, siteData}) { 21 | router.beforeEach((to, from, next) => { 22 | // @ts-ignore 23 | if (typeof _hmt != "undefined") { 24 | if (to.path && to.fullPath !== from.fullPath) { 25 | // @ts-ignore 26 | _hmt.push(["_trackPageview", to.fullPath]); 27 | } 28 | } 29 | 30 | next(); 31 | }); 32 | }, 33 | setup() { 34 | }, 35 | rootComponents: [], 36 | }) 37 | -------------------------------------------------------------------------------- /src/.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | // config.ts ---> 配置文件 2 | import {defineUserConfig} from "vuepress"; 3 | import {redirectPlugin} from '@vuepress/plugin-redirect'; 4 | import {docsearchPlugin} from "@vuepress/plugin-docsearch"; 5 | import {googleAnalyticsPlugin} from '@vuepress/plugin-google-analytics'; 6 | import {commentPlugin} from "vuepress-plugin-comment2"; 7 | import {docSearchLocales} from "./config/docSearchLocales"; 8 | 9 | import theme from "./theme.js"; 10 | 11 | export default defineUserConfig({ 12 | base: "/", 13 | lang: "zh-CN", 14 | 15 | theme, 16 | 17 | plugins: [ 18 | // 设置重定向 19 | redirectPlugin({ 20 | config: {}, 21 | }), 22 | // 搜索插件 23 | docsearchPlugin({ 24 | appId: "PTKSWUU4JQ", 25 | apiKey: "8cf4dc036ad5f140f40d1d97e178b0b4", 26 | indexName: "geekyspace", 27 | locales: {"/": docSearchLocales}, 28 | injectStyles: true 29 | }), 30 | // 设置谷歌分析 31 | googleAnalyticsPlugin({ 32 | id: "G-3L19EZ1HH8", 33 | debug: false, 34 | }), 35 | // 设置评论插件 36 | commentPlugin({ 37 | // 插件选项:Artalk | Giscus | Waline | Twikoo 38 | provider: "Giscus", 39 | }), 40 | ], 41 | 42 | // Enable it with pwa 43 | // shouldPrefetch: false, 44 | 45 | head: [ 46 | // 添加百度统计代码 47 | [ 48 | "script", 49 | {}, 50 | ` 51 | var _hmt = _hmt || []; 52 | (function() { 53 | var hm = document.createElement("script"); 54 | hm.src = "https://hm.baidu.com/hm.js?c3b455c45c9c9b349e7d28e7e13e950f"; 55 | var s = document.getElementsByTagName("script")[0]; 56 | s.parentNode.insertBefore(hm, s); 57 | })(); 58 | ` 59 | ], 60 | // 添加Google AdSense广告位 61 | [ 62 | "script", 63 | { 64 | async: true, 65 | src: "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3218450089809357", 66 | crossorigin: "anonymous" 67 | } 68 | ] 69 | ], 70 | 71 | }); 72 | -------------------------------------------------------------------------------- /src/.vuepress/config/docSearchLocales.ts: -------------------------------------------------------------------------------- 1 | export const docSearchLocales = { 2 | placeholder: "搜索geekyspace.cn的文档", 3 | translations: { 4 | button: { 5 | buttonText: "", 6 | buttonAriaLabel: "", 7 | }, 8 | modal: { 9 | searchBox: { 10 | resetButtonTitle: "清除查询条件", 11 | resetButtonAriaLabel: "清除查询条件", 12 | cancelButtonText: "取消", 13 | cancelButtonAriaLabel: "取消", 14 | }, 15 | startScreen: { 16 | recentSearchesTitle: "搜索历史", 17 | noRecentSearchesText: "最近没有搜索", 18 | saveRecentSearchButtonTitle: "保存至搜索历史", 19 | removeRecentSearchButtonTitle: "从搜索历史中移除", 20 | favoriteSearchesTitle: "收藏", 21 | removeFavoriteSearchButtonTitle: "从收藏中移除", 22 | }, 23 | errorScreen: { 24 | titleText: "无法获取结果", 25 | helpText: "你可能需要检查你的网络连接", 26 | }, 27 | footer: { 28 | selectText: "选择", 29 | navigateText: "导航", 30 | closeText: "关闭", 31 | searchByText: "搜索", 32 | }, 33 | noResultsScreen: { 34 | noResultsText: "无法找到相关结果", 35 | suggestedQueryText: "你可以尝试查询", 36 | reportMissingResultsText: "你认为该查询应该有结果?", 37 | reportMissingResultsLinkText: "点击反馈", 38 | }, 39 | }, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/.vuepress/path/navbar.ts: -------------------------------------------------------------------------------- 1 | import {navbar} from "vuepress-theme-hope"; 2 | 3 | export default navbar([ 4 | {text: "首页", icon: "home", link: "/"}, 5 | {text: "时间轴", icon: "list", link: "timeline/"}, 6 | { 7 | text: "Java", icon: "java", children: [ 8 | { 9 | text: "Java基础", children:[ 10 | {text: "Java新版本特性", icon: "java", link: "md/java-features/"}, 11 | {text: "多线程与并发编程", icon: "thread", link: "md/java-juc/"}, 12 | ], 13 | }, 14 | { 15 | text: "Java进阶", children:[ 16 | {text: "深入理解Java虚拟机", icon: "jvm-xx", link: "md/jvm/"}, 17 | ], 18 | } 19 | ] 20 | }, 21 | { 22 | text: "Spring", icon: "spring", children: [ 23 | {text: "Spring 框架", icon: "spring", link: "md/spring-framework/core/"}, 24 | // {text: "Spring Boot 教程", icon: "spring", link: "md/spring-boot/"}, 25 | {text: "Spring Data JPA", icon: "spring", link: "md/spring-data-jpa/jetbrains/getting-started"}, 26 | ] 27 | }, 28 | {text: "安装大全", icon: "launch", link: "md/installation-guide/"}, 29 | { 30 | text: "玩转IDEA", icon: "intellij-idea", prefix: "md/idea-tips/", children: [ 31 | { 32 | text: "IDEA 教程", 33 | icon: "intellij-idea", 34 | link: "https://www.jetbrains.com/help/idea/getting-started.html" 35 | }, 36 | {text: "正版激活码", icon: "intellij-idea", link: "activation"}, 37 | ] 38 | }, 39 | {text: "文库汇总", icon: "article", link: "article.html"}, 40 | ]); 41 | -------------------------------------------------------------------------------- /src/.vuepress/path/sidebar/index.ts: -------------------------------------------------------------------------------- 1 | import {sidebar} from "vuepress-theme-hope"; 2 | 3 | import {javaFeatures} from "./java-features.js"; 4 | import {jvm} from "./jvm.js"; 5 | import {springFramework} from "./spring-framework"; 6 | import {installationGuide} from "./installation-guide"; 7 | 8 | export default sidebar({ 9 | "/md/java-features/": javaFeatures, // Java新特性 10 | "/md/jvm/": jvm, // 深入理解Java虚拟机 11 | "/md/spring-framework/": springFramework, // Spring框架 12 | // "/md/spring-boot/": [ // SpringBoot框架 13 | // {text: "总目录", prefix: "/md/spring-boot/", link: "/md/spring-boot/"}, 14 | // { 15 | // text: "快速入门", children: [ 16 | // {text: "Spring Boot 入门", link: "quickstart"} 17 | // ] 18 | // }, 19 | // ], 20 | "/md/spring-data-jpa/": [ 21 | {text: "总目录", prefix: "/md/spring-data-jpa/", link: "/md/spring-data-jpa/"}, 22 | {text: "快速入门", prefix: "/md/jetbrains/", link: "jetbrains/getting-started"}, 23 | ], 24 | "/md/installation-guide/": installationGuide, 25 | "/md/docker/": [ 26 | {text: "概述", prefix: "/md/docker/overview", link: "/md/docker/overview"}, 27 | {text: "安装指南", prefix: "/md/docker/install", link: "/md/docker/install"}, 28 | {text: "镜像加速器", prefix: "/md/docker/mirror-acceleration", link: "/md/docker/mirror-acceleration"}, 29 | {text: "Top20常用命令", prefix: "/md/docker/top20-commands", link: "/md/docker/top20-commands"}, 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /src/.vuepress/path/sidebar/installation-guide.ts: -------------------------------------------------------------------------------- 1 | import {arraySidebar} from "vuepress-theme-hope"; 2 | 3 | export const installationGuide = arraySidebar([ 4 | {text: "开发者安装大全", prefix: "/md/installation-guide/", link: "/md/installation-guide/"}, 5 | { 6 | text: "操作系统", prefix: "os/", children: [ 7 | {text: "Windows、Office激活", link: "windows-office-activation"}, 8 | ], 9 | }, 10 | { 11 | text: "常用工具", prefix: "base-tools/", children: [ 12 | {text: "Homebrew", link: "Homebrew"}, 13 | ], 14 | }, 15 | { 16 | text: "开发环境", prefix: "dev-env/", children: [ 17 | { 18 | text: "Java", prefix: "java/", link: "java/", 19 | collapsible: false, 20 | children: [ 21 | {text: "SDKMAN", link: "SDKMAN"}, 22 | ] 23 | }, 24 | { 25 | text: "Nodejs", prefix: "nodejs/", link: "nodejs/", 26 | collapsible: false, 27 | children: [ 28 | {text: "nvm", link: "nvm"}, 29 | {text: "nrm", link: "nrm"}, 30 | {text: "Corepack", link: "Corepack"}, 31 | ] 32 | }, 33 | ], 34 | }, 35 | ]); -------------------------------------------------------------------------------- /src/.vuepress/path/sidebar/java-features.ts: -------------------------------------------------------------------------------- 1 | import {arraySidebar} from "vuepress-theme-hope"; 2 | 3 | export const javaFeatures = arraySidebar([ 4 | {text: "总目录", prefix: "/md/java-features/", link: "/md/java-features/",}, 5 | { 6 | text: "Java 21", prefix: "Java21/", link: "Java21/", 7 | children: [ 8 | {text: "字符串模版(Preview)", link: "jep430-string-templates"}, 9 | {text: "有序集合", link: "jep431-sequenced-collections"}, 10 | {text: "分代ZGC", link: "jep439-generational-zgc"}, 11 | {text: "记录模式", link: "jep440-record-partterns"}, 12 | {text: "switch模式匹配", link: "jep441-pattern-matching-for-switch"}, 13 | {text: "虚拟线程", link: "jep444-virtual-threads"}, 14 | ], 15 | }, 16 | { 17 | text: "Java 20", prefix: "Java20/", link: "Java20/", 18 | children: [ 19 | {text: "新特性总结", link: "java20-new-features-summary"}, 20 | ], 21 | }, 22 | { 23 | text: "Java 19", prefix: "Java19/", link: "Java19/", 24 | children: [ 25 | {text: "新特性总结", link: "java19-new-features-summary"}, 26 | ], 27 | }, 28 | { 29 | text: "Java 18", prefix: "Java18/", link: "Java18/", 30 | children: [ 31 | {text: "指定UTF-8为默认字符集", link: "jep400-utf8-by-default"}, 32 | {text: "简单Web服务器", link: "jep408-simple-web-server"}, 33 | {text: "新增@snipppet标签", link: "jep413-code-snippets-in-api-documentation"}, 34 | ], 35 | }, 36 | { 37 | text: "Java 17", prefix: "Java17/", link: "Java17/", 38 | children: [ 39 | {text: "switch模式匹配(Preview)", link: "jep406-pattern-matching-for-switch-preview"}, 40 | {text: "sealed类", link: "jep409-sealed-classes"}, 41 | ], 42 | }, 43 | { 44 | text: "Java 16", prefix: "Java16/", link: "Java16/", 45 | children: [ 46 | {text: "instanceof模式匹配", link: "jep394-pattern-matching-for-instanceof"}, 47 | {text: "record类", link: "jep395-records"}, 48 | ], 49 | }, 50 | { 51 | text: "Java 15", prefix: "Java15/", link: "Java15/", 52 | children: [ 53 | {text: "隐藏类(Hidden Classes)", link: "jep371-hidden-classes"}, 54 | {text: "文本块(Text Blocks)", link: "jep378-text-blocks"}, 55 | ], 56 | }, 57 | { 58 | text: "Java 14", prefix: "Java14/", link: "Java14/", 59 | children: [ 60 | {text: "switch表达式增强", link: "jep361-switch-expressions"}, 61 | ], 62 | }, 63 | { 64 | text: "Java 11", prefix: "Java11/", link: "Java11/", 65 | children: [ 66 | {text: "移除JavaEE和CORBA模块", link: "jep320-remove-JavaEE-CORBA"}, 67 | ], 68 | }, 69 | { 70 | text: "Java 10", prefix: "Java10/", link: "Java10/", 71 | children: [ 72 | {text: "局部变量类型推断", link: "jep286-local-variable-type-inference"}, 73 | ], 74 | }, 75 | { 76 | text: "Java 9", prefix: "Java9/", link: "Java9/", 77 | children: [ 78 | {text: "交互式编程环境JShell", link: "jep222-jshell"}, 79 | {text: "不可变集合的快捷创建方法", link: "jep269-convenience-factory-methods-for-collections"}, 80 | ], 81 | } 82 | ] 83 | ); 84 | -------------------------------------------------------------------------------- /src/.vuepress/path/sidebar/jvm.ts: -------------------------------------------------------------------------------- 1 | import {arraySidebar} from "vuepress-theme-hope"; 2 | 3 | export const jvm = arraySidebar([ 4 | {text: "总目录", prefix: "/md/jvm/", link: "/md/jvm/",}, 5 | { 6 | text: "走近Java", prefix: "part1/", link: "part1/", 7 | children: [ 8 | {text: "JVM概述", link: "overview"}, 9 | {text: "编译JDK", link: "compile_jdk"}, 10 | ], 11 | }, 12 | { 13 | text: "自动内存管理", prefix: "part2/", link: "part2/", 14 | children: [ 15 | {text: "运行时数据区", link: "runtime-data-areas"}, 16 | {text: "对象分配过程", link: "heap-object-flow"}, 17 | { 18 | text: "基础故障处理工具", prefix: "basic-tools/", link: "basic-tools/", 19 | collapsible: true, 20 | children: [ 21 | // {text: "依赖注入", link: "factory-collaborators"} 22 | ] 23 | }, 24 | { 25 | text: "可视化故障处理工具", prefix: "visual-tools/", link: "visual-tools/", 26 | collapsible: true, 27 | children: [ 28 | {text: "VisualVM介绍", link: "visualvm"} 29 | ] 30 | }, 31 | ], 32 | }, 33 | { 34 | text: "虚拟机执行子系统", prefix: "part3/", link: "part3/", 35 | children: [ 36 | {text: "类文件结构", link: "class-file-structure"}, 37 | {text: "字节码指令集", link: "bytecode-instructions-set"}, 38 | {text: "类加载机制", link: "class-loading-mechanism"}, 39 | ], 40 | }, 41 | { 42 | text: "程序编译与代码优化", prefix: "part4/", link: "part4/", 43 | children: [], 44 | }, 45 | { 46 | text: "高效并发", prefix: "part5/", link: "part5/", 47 | children: [], 48 | }, 49 | 50 | ] 51 | ); 52 | -------------------------------------------------------------------------------- /src/.vuepress/path/sidebar/spring-framework.ts: -------------------------------------------------------------------------------- 1 | import {arraySidebar} from "vuepress-theme-hope"; 2 | 3 | export const springFramework = arraySidebar([ 4 | { 5 | text: "概述", collapsible: true, prefix: "overview/", link: "overview/", 6 | children: [{text: "快速开始", link: "quickstart"}] 7 | }, 8 | { 9 | text: "核心技术", collapsible: true, prefix: "core/", link: "core/", 10 | children: [ 11 | {text: "IoC容器", link: "ioc-container"}, 12 | {text: "Bean定义", link: "beans-definition"}, 13 | { 14 | text: "依赖", prefix: "dependencies/", link: "dependencies/", 15 | collapsible: true, 16 | children: [ 17 | {text: "依赖注入", link: "factory-collaborators"}, 18 | {text: "依赖和配置详解", link: "factory-properties-detailed"}, 19 | {text: "使用depends-on", link: "factory-dependson"}, 20 | {text: "懒加载Bean", link: "factory-lazy-init"}, 21 | {text: "自动装配协作者", link: "factory-autowire"}, 22 | {text: "方法注入", link: "factory-method-injection"}, 23 | ] 24 | }, 25 | {text: "Bean作用域", link: "beans-scope"}, 26 | {text: "Bean生命周期", link: "beans-lifecycle"}, 27 | {text: "Bean定义继承", link: "child-bean-definitions"}, 28 | ] 29 | }, 30 | ] 31 | ); 32 | -------------------------------------------------------------------------------- /src/.vuepress/public/CNAME: -------------------------------------------------------------------------------- 1 | www.geekyspace.cn 2 | -------------------------------------------------------------------------------- /src/.vuepress/public/assets/images/cover3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeljhou/geekyspace/4b9e48fd630904868c122817e90ce78bd9803fe1/src/.vuepress/public/assets/images/cover3.jpg -------------------------------------------------------------------------------- /src/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeljhou/geekyspace/4b9e48fd630904868c122817e90ce78bd9803fe1/src/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /src/.vuepress/public/images/personal/geekyspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeljhou/geekyspace/4b9e48fd630904868c122817e90ce78bd9803fe1/src/.vuepress/public/images/personal/geekyspace.png -------------------------------------------------------------------------------- /src/.vuepress/public/images/system/geeky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeljhou/geekyspace/4b9e48fd630904868c122817e90ce78bd9803fe1/src/.vuepress/public/images/system/geeky.png -------------------------------------------------------------------------------- /src/.vuepress/public/images/system/geeky_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeljhou/geekyspace/4b9e48fd630904868c122817e90ce78bd9803fe1/src/.vuepress/public/images/system/geeky_en.png -------------------------------------------------------------------------------- /src/.vuepress/public/images/system/geeky_zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeljhou/geekyspace/4b9e48fd630904868c122817e90ce78bd9803fe1/src/.vuepress/public/images/system/geeky_zh.png -------------------------------------------------------------------------------- /src/.vuepress/public/special/wedding-chenzhuo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 新婚快乐 7 | 45 | 46 | 47 | 48 |
49 |

🎉🎊 囍 新婚快乐 囍 🎊🎉

50 |

亲爱的 卓哥

51 |

今天是你人生中最重要的日子之一,
从懵懂少年到如今的新郎官,
见证你的成长,真的由衷替你高兴!🎉

52 |

愿你与 伟娜 携手同行,相知相守,
往后余生,岁月温柔,爱情长存 💕💕

53 |

💖 祝你们百年好合,白头偕老!💖

54 | 55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/.vuepress/styles/config.scss: -------------------------------------------------------------------------------- 1 | // 你可以在这里更改配置 https://theme-hope.vuejs.press/zh/config/style.html 2 | /* Content Class */ 3 | $content-class: ".theme-hope-content"; 4 | 5 | /* responsive breakpoints */ 6 | 7 | // wide screen 8 | $pc: 1440px !default; 9 | 10 | // desktop 11 | $laptop: 1280px !default; 12 | 13 | // narrow desktop / iPad 14 | $pad: 959px !default; 15 | 16 | // wide mobile 17 | $tablet: 719px !default; 18 | 19 | // narrow mobile 20 | $mobile: 419px !default; 21 | 22 | /* Color list */ 23 | $colors: #cf1322, #fa541c, #f39c12, #2ecc71, #25a55b, #10a5a5, #096dd9, #aa6fe9, 24 | #eb2f96 !default; 25 | 26 | /* Code Block */ 27 | // only available with shiki highlighter 28 | $code-color: ( 29 | light: #383a42, 30 | dark: #abb2bf, 31 | ) !default; 32 | $code-bg-color: ( 33 | light: #ecf4fa, 34 | dark: #282c34, 35 | ) !default; -------------------------------------------------------------------------------- /src/.vuepress/styles/index.scss: -------------------------------------------------------------------------------- 1 | // fix:解决新版Chrome字体加粗 2 | body { 3 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 4 | -webkit-font-smoothing: antialiased; 5 | -moz-osx-font-smoothing: grayscale; 6 | font-size: 16px; 7 | } 8 | 9 | // 导航栏 10 | #navbar { 11 | border-bottom: 1px solid var(--border-color); 12 | 13 | // 去掉父级标题“文本转换为大写字母” 14 | .dropdown-subtitle { 15 | text-transform: none; 16 | } 17 | 18 | } 19 | 20 | // 侧边栏 21 | #sidebar { 22 | border-right: 1px solid var(--border-color); 23 | font-size: 0.86em; 24 | scrollbar-color: var(--border-color); 25 | //padding-inline-start: 0; 26 | 27 | // 父级目录字体加粗 28 | .vp-sidebar-title { 29 | font-weight: 700; 30 | } 31 | } 32 | 33 | // 设置博客上边距 34 | .blog-page-wrapper { 35 | margin-top: 20px; 36 | } 37 | 38 | /** 39 | * ========== Markdown 样式 ========== 40 | */ 41 | // 修改 > 块引用 样式 42 | html[data-theme] blockquote { 43 | border-left: 4px solid var(--theme-color-dark); 44 | padding: 10px 15px; 45 | color: var(--text-color); 46 | background-color: var(--theme-color-mask); 47 | } 48 | 49 | // 修改 `标记` 样式 50 | html[data-theme] mark { 51 | border-radius: 2px; 52 | padding: 2px 4px; 53 | margin: 0 2px; 54 | font-weight: 500; 55 | color: var(--text-color); 56 | background-color: var(--theme-color-mask); 57 | } 58 | 59 | // 修改 “表格” 样式 60 | table { 61 | // 默认情况下,“表格” 宽度不会占满整个屏幕,这里设置为 100% 62 | display: inline-table; 63 | //width: 100%; 64 | border-radius: 1px; 65 | margin: 0; 66 | 67 | th { 68 | text-align: left; 69 | color: #fff; 70 | background-color: var(--theme-color-dark); 71 | } 72 | 73 | tr:nth-child(odd) { 74 | background-color: var(--theme-color-mask); 75 | } 76 | 77 | // 鼠标悬停时改变行颜色 78 | tr:hover { 79 | color: var(--theme-color); 80 | 81 | html[data-theme=light] & { 82 | background-color: #ebebeb; 83 | } 84 | 85 | html[data-theme=dark] & { 86 | background-color: #30363d; 87 | } 88 | } 89 | 90 | // 去除“表格”的边框,看起来更加美观 91 | thead, tbody, tfoot, tr, td, th { 92 | border-width: 0 0 1px 0; 93 | } 94 | } 95 | 96 | // 视觉优化 “夜间模式” 97 | html[data-theme=dark] { 98 | --bg-color: #22272e; /*页面背景颜色*/ 99 | --navbar-bg-color: #22272e; /*导航栏背景颜色*/ 100 | --sidebar-bg-color: #22272e; /*侧边栏背景颜色*/ 101 | --bg-color-back: #22272e; /*首页页背景颜色*/ 102 | --bg-color-float: #2b313a; /*首页卡片背景颜色*/ 103 | 104 | --text-color: #e6edf3; /*页面字体颜色*/ 105 | --grey3: #e6edf3; /*首页项目卡片字体颜色*/ 106 | --dark-grey: #e6edf3; /*图标文字颜色*/ 107 | --light-grey: #e6edf3; /*图标文字颜色*/ 108 | } 109 | 110 | html[data-theme=light] { 111 | //--dark-grey: #2c3e50; /*图标文字颜色*/ 112 | --light-grey: #2c3e50; /*图标文字颜色*/ 113 | } 114 | 115 | 116 | // 夜间模式下降低图片亮度 117 | html[data-theme="dark"] img { 118 | filter: brightness(0.7); /* 适当降低亮度 */ 119 | transition: filter 0.3s ease-in-out; 120 | } 121 | 122 | // 将博主信息移动至文章列表的左侧 123 | @import "vuepress-theme-hope/presets/left-blog-info.scss"; -------------------------------------------------------------------------------- /src/.vuepress/styles/palette.scss: -------------------------------------------------------------------------------- 1 | // 设置默认主题色 2 | $theme-color: #3eaf7c; 3 | 4 | // 主题色选择器 5 | $theme-colors: #2196f3, #a266f3, #e04c8a, #fb9b5f; 6 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | # 博客主页配置:https://theme-hope.vuejs.press/zh/config/frontmatter/blog-home.html 3 | home: true 4 | layout: BlogHome 5 | hero: false 6 | title: 最新发布 7 | icon: home 8 | projects: 9 | - icon: launch 10 | name: 开发者安装大全 11 | desc: 好用工具、开发环境、中间件配置等安装指南 12 | link: /md/installation-guide/ 13 | 14 | - icon: java 15 | name: Java新特性 16 | desc: 从Java 8开始所有新特性解读 17 | link: /md/java-features/ 18 | 19 | - icon: intellij-idea 20 | name: IDEA激活 21 | desc: 了解IDEA更多牛x功能、推荐很有意思的主题和插件 22 | link: /md/idea-tips/activation 23 | 24 | - icon: spring 25 | name: Spring实战 26 | desc: 深入理解Spring框架 27 | link: /md/spring-framework/core/ 28 | 29 | - icon: mysql 30 | name: MySQL教程 31 | desc: MySQL必知必会、高性能架构、排错指南 32 | link: /mysql/ 33 | 34 | - icon: aigc 35 | name: AIGC新时代 36 | desc: 提供生成式人工智能的入门和进阶教程 37 | link: /aigc/ 38 | 39 | - icon: cloud-service 40 | name: 云原生技术 41 | desc: 分享云原生架构的最佳实践和使用指南 42 | link: /cloud-native/ 43 | 44 | - icon: smart-contracts 45 | name: 区块链智能合约 46 | desc: 提供区块链技术和智能合约开发的指南 47 | link: /blockchain/ 48 | 49 | footer: © 2023 - 至今 www.geekyspace.cn 保留所有权利 50 | copyright: Copyright © 2024 流浪码客 51 | --- 52 | -------------------------------------------------------------------------------- /src/about-me.md: -------------------------------------------------------------------------------- 1 | --- 2 | # 信息 Frontmatter 配置 3 | title: 个人简介 4 | description: 一个专注于技术分享的博客网站 5 | icon: circle-info 6 | # cover: /assets/images/cover3.jpg 7 | author: 流浪码客 8 | isOriginal: false # 是否原创文章 9 | date: 1998-12-14 10 | category: 程序人生 11 | tag: 程序人生 12 | # 布局 Frontmatter 配置 13 | sidebar: false # 禁用侧边栏 14 | --- 15 | 16 | # 个人简介 | 一个专注于技术分享的博客网站 17 | 18 | 欢迎访问我的博客网站 [www.geekyspace.cn](www.geekyspace.cn),与我一起探索科技的未来! 19 | 20 | 本博客维护人周宇,在Java和相关技术领域拥有多年的专业经验。 21 | 此前,我在深圳南山 [Liquido](https://www.liquido.com/) 工作,这是一家专注于金融领域, 22 | 提供拉美地区[支付解决方案](https://mp.weixin.qq.com/s/UzPsaVvXqrvW8T8PDPnhZw)的公司。 23 | 24 | **贴上“程序员”标签** 25 | 26 | 1. 个人兴趣:我从小就对工科方向的工作非常感兴趣,喜欢倒腾电子产品,研究它们的原理,拆了又装。 27 | 2. 家庭影响:我有一个“伢伢”,比我大10多岁,他学的就是计算机,赚钱也多。 28 | 他毕业后在一家电商公司工作,后来被京东收购,之后去了高德,又被阿里收购。在它的影响下,我最终成为了一名程序员。 29 | 30 | **程序员的价值体现** 31 | 32 | 做程序员是幸运的,世界的本质是价值交换。 33 | 作为程序员,我最大的成就感在于能否真正给别人带来价值。如果可以,那泰裤辣。 34 | 大环境没有那么差(给自己信心),这是程序员的时代!!! 35 | 36 | * 人生没有白走的路,每一步都算数(我要坚持坚持坚持) 37 | 38 | **我的程序员格言** 39 | 40 | 1. 编码原则:🥇 代码简洁高效,优雅永不过时 ✨ 41 | 2. 架构核心:🔧 掌握系统控制权,确保可扩展性和灵活性 42 | 3. 技术理念:🌐 技术广度是技术深度的副产品 43 | 4. 职业信仰:🚀 当坚持成为一种热爱,极致成为一种精神,那么成功就是一种必然 44 | 5. 长期主义:⏳ 注重长远发展,坚持不懈地追求卓越 45 | 46 | -------------------------------------------------------------------------------- /src/article.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | icon: home 4 | title: 文库汇总 5 | 6 | # 指定首页背景头像 7 | heroImage: /images/personal/geekyspace.png 8 | heroImageDark: /images/personal/geekyspace.png 9 | # 指定首页背景图片 10 | #bgImage: /assets/bg/wallhaven-8586my.png 11 | #bgImageDark: /assets/bg/wallhaven-o5762l.png 12 | bgImageStyle: 13 | background-attachment: fixed 14 | heroText: 文库汇总 15 | tagline: 极客空间,一个专注于技术分享的博客网站✨ 16 | actions: 17 | - text: 首页 18 | icon: home 19 | link: / 20 | type: primary 21 | - text: 文章 22 | icon: article 23 | link: /article/ 24 | 25 | features: 26 | - title: RabbitMQ 27 | icon: rabbitmq 28 | # icon: https://avatars.githubusercontent.com/u/96669?s=48&v=4 29 | details: 高并发实战-RabbitMQ消息队列 30 | link: https://github.com/joeljhou/RabbitMQ 31 | 32 | - title: JVM 33 | icon: javaJvm-xx 34 | details: 深入理解Java虚拟机 35 | link: /md/jvm/ 36 | 37 | - title: Docker 38 | icon: docker 39 | details: Docker容器化最佳实践 40 | link: /md/docker/overview 41 | 42 | - title: K8S 43 | icon: k8s 44 | details: Kubernetes云原生时代 45 | 46 | - title: MySQL 47 | icon: mysql 48 | # icon: https://labs.mysql.com/common/logos/mysql-logo.svg 49 | details: MySQL原理与SQL调优 50 | 51 | - title: Redis 52 | icon: redis 53 | #icon: https://redis.io/wp-content/uploads/2024/04/Logotype.svg 54 | details: Redis缓存高可用实战 55 | 56 | - title: Elasticsearch 57 | icon: elasticsearch 58 | #icon: https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt36f2da8d650732a0/5d0823c3d8ff351753cbc99f/logo-elasticsearch-32-color.svg 59 | details: Elasticsearch搜索引擎 60 | 61 | - title: Google Istio 62 | icon: istio_bypass_mode_s 63 | details: Google Istio服务网格 64 | 65 | copyright: false 66 | footer: "© 2023 - 至今 www.geekyspace.cn 保留所有权利" 67 | --- 68 | -------------------------------------------------------------------------------- /src/md/docker/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Docker安装 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-11-15 12 | category: Docker 13 | tag: 14 | - docker 15 | --- 16 | 17 | # Docker安装&配置 18 | 19 | 您可以在多个平台上下载并安装 Docker。请参阅以下部分并选择最适合您的安装路径。 20 | 21 | > [适用于Mac的Docker桌面](https://docs.docker.com/desktop/setup/install/mac-install/) 22 | 23 | > [适用于Windows的Docker桌面](https://docs.docker.com/desktop/setup/install/windows-install/) 24 | 25 | > [适用于Linux的Docker桌面](https://docs.docker.com/desktop/setup/install/linux/) 26 | 27 | **验证是否安装成功:** 28 | 29 | ```shell 30 | $ docker version # 查看版本 31 | $ docker info # 查看信息 32 | ``` 33 | 34 | **运行测试镜像** 35 | 36 | ```shell 37 | $ docker run hello-world 38 | $ docker run -it ubuntu bash 39 | ``` 40 | 41 | 可能会遇到如下网络错误,这个时候就需要配置[镜像源加速器](/md/docker/mirror-acceleration.html) 42 | 43 | `docker: error pulling image configuration: download failed after attempts=6: EOF.` -------------------------------------------------------------------------------- /src/md/docker/mirror-acceleration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Docker镜像加速器 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-11-16 12 | category: Docker 13 | tag: 14 | - docker 15 | --- 16 | 17 | # Docker镜像加速器 18 | 19 | 国内从`Docker Hub`拉取镜像有时会遇到困难,此时可以配置镜像加速器。 20 | 21 | ## 阿里云镜像加速器 22 | 23 | > [阿里云镜像加速器](https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors),有针对`Ubuntu`,`CentOS`,`Mac`,`Windows`的操作文档 24 | 25 | ![阿里云镜像加速器](http://img.geekyspace.cn/pictures/2024/202411270006564.png) 26 | 27 | ## Linux 28 | 29 | 对于使用`Ubuntu`或`CentOS`的系统,请在`/etc/docker/daemon.json`文件中写入如下内容(如果文件不存 30 | 在请新建该文件) 31 | 32 | ```shell 33 | sudo mkdir -p /etc/docker 34 | sudo tee /etc/docker/daemon.json <<-'EOF' 35 | { 36 | "registry-mirrors": ["https://onnxqmp4.mirror.aliyuncs.com"] 37 | } 38 | EOF 39 | 40 | # 之后重新启动服务 41 | sudo systemctl daemon-reload 42 | sudo systemctl restart docker 43 | ``` 44 | 45 | ## Windows 46 | 47 | 对于使用Windows的系统,在`docker desktop`右上角**齿轮**图标,打开配置窗口后选择`Docker Engine`。
48 | 编辑JSON串,填写加速器地址: 49 | 50 | Windows: `%USERPROFILE%\.docker\daemon.json` 51 | 52 | ![Windows配置镜像加速](http://img.geekyspace.cn/pictures/2024/202411160051539.png) 53 | 54 | **完整的`daemon.json`配置示例:** 55 | 56 | ```shell 57 | { 58 | "builder": { 59 | "gc": { 60 | "defaultKeepStorage": "20GB", 61 | "enabled": true 62 | } 63 | }, 64 | "experimental": false, 65 | "registry-mirrors": [ 66 | "https://onnxqmp4.mirror.aliyuncs.com", 67 | "https://docker.hpcloud.cloud", 68 | "https://docker.m.daocloud.io", 69 | "https://docker.1panel.live", 70 | "http://mirrors.ustc.edu.cn", 71 | "https://docker.chenby.cn", 72 | "https://docker.ckyl.me", 73 | "http://mirror.azure.cn", 74 | "https://hub.rat.dev" 75 | ] 76 | } 77 | ``` 78 | 79 | ## MacOS 80 | 81 | ![MacOS配置Docker镜像加速器](http://img.geekyspace.cn/pictures/2024/202412041859454.png) 82 | -------------------------------------------------------------------------------- /src/md/docker/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Docker概述 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-11-15 12 | category: Docker 13 | tag: 14 | - docker 15 | --- 16 | 17 | # Docker概述 18 | 19 | ## 什么是Docker? 20 | 21 | `Docker`是`dotCloud`团队在2013年发布的开源项目。 使用`Go`语言开发,是一个轻量级的虚拟机容器解决方案。 22 | 23 | ## 为什么使用Docker? 24 | 25 | Docker跟传统虚拟机相比,具有以下优势: 26 | 27 | * 更高效的利用系统资源 28 | * 更快速的启动速度 29 | * 一致的运行环境 30 | * 持续交付和部署 31 | * 更轻松的迁移 32 | * 更轻松的维护和扩展 33 | 34 | **`Docker` VS `传统虚拟机`** 35 | 36 | | 特性 | 容器 | 虚拟机 | 37 | |-------|-----------|--------| 38 | | 启动 | 秒级 | 分钟级 | 39 | | 硬盘使用 | 一般为 MB | 一般为 GB | 40 | | 性能 | 接近原生 | 弱于 | 41 | | 系统支持量 | 单机支持上千个容器 | 一般几十个 | 42 | 43 | ## 基本概念 44 | 45 | * **镜像(Image)**:Docker镜像是一个只读的模板,包含了运行容器所需的所有文件。 46 | * 本质是==文件系统== 47 | * 基于`Union FS`设计,**分层存储**,可以叠加 48 | * **容器(Container)**:Docker容器是可独立运行的一个或一组应用,是Docker镜像的运行实例。 49 | * 实质是==进程== 50 | * 拥有自己的`root`文件系统,网络配置,进程空间等 51 | * 最佳实践:文件写入操作使用**数据卷(Volume)**,避免文件写入到容器中 52 | * **仓库(Repository)**:Docker Registry是一个集中存储、分发镜像服务。 53 | * [Docker Hub](https://hub.docker.com/) 官方镜像仓库 54 | * [Google Container Registry](https://cloud.google.com/artifact-registry/docs?hl=zh-cn) K8s 镜像仓库 55 | * [Amazon ECR](https://aws.amazon.com/cn/ecr/) AWS 镜像仓库 56 | * [VMWare Harbor](https://github.com/goharbor/harbor) 和 [Sonatype Nexus](https://www.sonatype.com/docker) 57 | 三方软件实现了Docker Registry API 58 | 59 | ## Docker执行流程 60 | 61 | * 客户端发指令 → 守护进程接收指令 → 检查镜像(本地/Docker Hub) → 创建容器 → 启动并运行。 62 | 63 | ![Docker执行流程](http://img.geekyspace.cn/pictures/2024/202411152359021.png) 64 | 65 | ## Docker架构 66 | 67 | Docker 的架构设计基于 **客户端-服务器模型**,主要包含以下核心组件: 68 | 69 | ![Docker架构](http://img.geekyspace.cn/pictures/2024/202411262315598.webp) 70 | 71 | 1. **Client(客户端)** :用户与Docker交互的界面,通过命令行或API发送指令给Docker daemon。 72 | * `docker run`:创建并启动一个容器。 73 | * `docker build`:根据Dockerfile构建一个新的镜像。 74 | * `docker pull`:从注册中心拉取一个镜像。 75 | * `docker push`:将本地镜像推送到注册中心。 76 | 2. **Docker daemon(守护进程)** :Docker引擎的核心,负责监听、处理客户端的指令,并管理Docker对象(镜像、容器等)。 77 | * `镜像管理`:管理本地镜像,包括存储、加载和删除。 78 | * `容器管理`:创建、启动、停止、删除容器。 79 | * `网络管理`:为容器提供网络接口。 80 | * `存储管理`:管理容器的数据卷。 81 | 3. **Images(镜像)** :Docker镜像是一个只读的模板,包含了运行容器所需的所有文件 82 | * `分层结构`:镜像由多层组成,每一层代表一个构建步骤。 83 | * `只读`:镜像是只读的,保证了镜像内容的不可变性。 84 | 4. **Containers(容器)** :Docker容器是镜像的运行实例,是应用程序的运行环境。 85 | * `隔离性`:每个容器都有独立的文件系统、网络配置和进程空间。 86 | * `可复用性`:基于同一个镜像可以创建多个容器实例。 87 | 5. **Registry(注册中心)** :用于存储和分发Docker镜像的仓库。 88 | * `集中存储`:将镜像存储在集中式的仓库中,方便管理和共享。 89 | * `版本控制`:支持镜像的版本管理。 -------------------------------------------------------------------------------- /src/md/docker/top20-commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Top20常用命令 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-11-23 12 | category: Docker 13 | tag: 14 | - docker 15 | --- 16 | 17 | # Top20常用命令 18 | 19 | 作为一款领先的容器化工具,Docker 提供了强大的功能,让开发者和运维人员能够快速构建、部署和管理应用。 20 | 在这篇文章中,我们将介绍 20 条最常用的 Docker 命令,并结合详细说明,帮助大家轻松掌握 Docker 的基本操作。 21 | 22 | * [1. Docker version](#docker-version) 23 | * [2. Docker search](#docker-search) 24 | * [3. Docker pull](#docker-pull) 25 | * [4. Docker run](#docker-run) 26 | * [5. Docker ps](#docker-ps) 27 | * [6. Docker stop](#docker-stop) 28 | * [7. Docker restart](#docker-restart) 29 | * [8. Docker kill](#docker-kill) 30 | * [9. Docker exec](#docker-exec) 31 | * [10. Docker login](#docker-login) 32 | * [11. Docker commit](#docker-commit) 33 | * [12. Docker push](#docker-push) 34 | * [13. Docker network](#docker-network) 35 | * [14. Docker history](#docker-history) 36 | * [15. Docker rmi](#docker-rmi) 37 | * [16. Docker ps -a](#docker-ps--a) 38 | * [17. Docker copy](#docker-copy) 39 | * [18. Docker logs](#docker-logs) 40 | * [19. Docker volume](#docker-volume) 41 | * [20. Docker logout](#docker-logout) 42 | 43 | **相关文档** 44 | 45 | * [Docker 命令参考文档](https://docs.docker.com/engine/reference/commandline/cli/) 46 | * [Dockerfile 镜像构建参考文档](https://docs.docker.com/engine/reference/builder/?spm=5176.8351553.0.0.4ef81991wFvDZm) 47 | 48 | ## Docker version 49 | 50 | 用途:显示 Docker 的版本信息。 51 | 52 | ```bash 53 | docker version 54 | ``` 55 | 56 | ![docker version](http://img.geekyspace.cn/pictures/2024/202411231416702.png) 57 | 58 | ## Docker search 59 | 60 | 用途: 用于搜索 Docker Hub 上的镜像。 61 | 62 | ```shell 63 | docker search nginx 64 | ``` 65 | 66 | ![docker search nginx](http://img.geekyspace.cn/pictures/2024/202411231425169.png) 67 | 68 | ## Docker pull 69 | 70 | 用途:从 Docker Hub 下载镜像。 71 | 72 | ```shell 73 | docker pull nginx 74 | ``` 75 | 76 | ![docker pull nginx](http://img.geekyspace.cn/pictures/2024/202411231427300.png) 77 | 78 | ## Docker run 79 | 80 | 用途:运行一个镜像,并创建一个容器。 81 | 82 | ```shell 83 | docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD=root -p 3307:3306 mysql:8.0 84 | ``` 85 | 86 | 参数解释: 87 | 88 | * `-d`:后台运行容器,并返回 Shell。 89 | * `-e MYSQL_ROOT_PASSWORD=root`:设置环境变量,用于设置 MySQL 的 root 用户密码。 90 | * `-p 3306:3306`:将主机的端口 3306 映射到容器的端口 3306。 91 | * `mysql:8.0`:要下载的镜像的名称。 92 | 93 | ![docker run mysql](http://img.geekyspace.cn/pictures/2024/202411231444798.png) 94 | 95 | ## Docker ps 96 | 97 | 用途:列出当前正在运行的容器。 98 | 99 | ```shell 100 | docker ps 101 | ``` 102 | 103 | ## Docker stop 104 | 105 | 用途:停止一个正在运行的容器。 106 | 107 | ```shell 108 | docker stop 109 | ``` 110 | 111 | ## Docker restart 112 | 113 | 用途:重启一个容器。 114 | 115 | ```shell 116 | docker restart 117 | ``` 118 | 119 | ## Docker kill 120 | 121 | 用途:强制停止一个容器。 122 | 123 | ```shell 124 | docker kill 125 | ``` 126 | 127 | ## Docker exec 128 | 129 | 用途:在运行中的容器中执行命令。 130 | 131 | ```shell 132 | docker exec -it bash 133 | ``` 134 | 135 | 参数解释: 136 | 137 | * `-it`:保持 STDIN 打开并允许使用键盘输入。 138 | * ``:要进入的容器的ID。 139 | * `bash`:要执行的命令。 140 | 141 | ![docker exec -it bash](http://img.geekyspace.cn/pictures/2024/202411231506412.png) 142 | 143 | ## Docker login 144 | 145 | 用途:登录 Docker Hub,用于推送私有镜像。 146 | 147 | ```shell 148 | docker login 149 | ``` 150 | 151 | ![docker login](http://img.geekyspace.cn/pictures/2024/202411231511727.png) 152 | 153 | ## Docker commit 154 | 155 | 用途:创建一个镜像,该镜像包含一个容器的当前状态。 156 | 157 | ```shell 158 | docker commit 159 | ``` 160 | 161 | 参数解释: 162 | 163 | * ``:要提交为镜像的容器的ID。 164 | * ``:新镜像的名称。 165 | 166 | ## Docker push 167 | 168 | 用途:将镜像推送到 Docker Hub。 169 | 170 | ```shell 171 | docker push 172 | ``` 173 | 174 | ## Docker network 175 | 176 | 用途:管理 Docker 网络。 177 | 178 | ```shell 179 | docker network create 180 | docker network inspect 181 | docker network rm 182 | docker network connect 183 | docker network disconnect 184 | ``` 185 | 186 | 参数解释: 187 | * ``:要创建、查看、删除或连接的 Docker 网络的名称。 188 | * ``:要连接到或断开连接的网络的容器的ID。 189 | 190 | ## Docker history 191 | 192 | 用途:显示镜像的创建历史。 193 | 194 | ```shell 195 | docker history 196 | ``` 197 | 198 | 参数解释: 199 | * ``:要查看其创建历史的镜像的名称。 200 | 201 | ## Docker rmi 202 | 203 | 用途:删除镜像。 204 | 205 | ```shell 206 | docker rmi 207 | ``` 208 | 209 | ## Docker ps -a 210 | 211 | 用途:列出所有容器,包括已停止的容器。 212 | 213 | ```shell 214 | docker ps -a 215 | ``` 216 | 217 | ![docker ps -a](http://img.geekyspace.cn/pictures/2024/202411231500573.png) 218 | 219 | ## Docker copy 220 | 221 | 用途:复制文件或文件夹到容器。 222 | 223 | ```shell 224 | docker cp : 225 | ``` 226 | 227 | 参数解释: 228 | * ``:主机上的文件或文件夹的路径。 229 | * ``:要复制文件的容器的ID。 230 | * ``:容器内的目标路径。 231 | 232 | ## Docker logs 233 | 234 | 用途:查看容器的日志。 235 | 236 | ```shell 237 | docker logs 238 | ``` 239 | 240 | 参数解释: 241 | 242 | * ``:要查看其日志的容器的ID。 243 | 244 | ## Docker volume 245 | 246 | 用途:管理 Docker 卷。 247 | 248 | ```shell 249 | docker volume create 250 | docker volume inspect 251 | docker volume rm 252 | docker run -v :/data nginx 253 | ``` 254 | 255 | 参数解释: 256 | * ``:要创建、查看或删除的卷的名称。 257 | * `nginx`:要运行的镜像名称。 258 | * `/data`:要挂载到容器内的卷的挂载点。 259 | 260 | ## Docker logout 261 | 262 | 用途:注销 Docker Hub。 263 | 264 | ```shell 265 | docker logout 266 | ``` 267 | 268 | -------------------------------------------------------------------------------- /src/md/idea-tips/activation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2025最新IntelliJ IDEA专业版稳定正版激活码 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: true 11 | date: 2024-04-17 12 | category: 开发工具 13 | tag: 14 | - IDEA 15 | --- 16 | 17 | # 2025最新IntelliJ IDEA专业版稳定正版激活码 18 | 19 | ## 适用于Mac系统 20 | 21 | > 仅支持官网专业版2020-2024的激活。不支持社区版 toolbox下载的IDEA。 22 | 23 | ### 1️⃣ 第一步:下载激活文件 24 | 25 | [激活文件获取](https://h5.m.goofish.com/item?id=785250929165) 26 | 27 | ### 2️⃣ 第二步:打开激活文件 `macjihuo.zip`,解压缩 28 | 29 | ### 3️⃣ 第三步:"macjihuo"文件夹上右键,新建终端窗口 30 | 31 | ![macjihuo](http://img.geekyspace.cn/pictures/2024/202404171725739.png) 32 | 33 | ### 4️⃣ 第三步:运行脚本,输入对应软件的命令-回车(看下图) 34 | 35 | ==运行命令前:先打开你要激活的软件。然后关闭软件,最后输入命令激活软件哦。== 36 | 37 | ```shell 38 | 如果你要激活idea: 则输入 sh idea.sh 39 | 如果你要激活pycharm: 则输入 sh pycharm.sh 40 | 如果你要激活datagrip: 则输入 sh datagrip.sh 41 | 如果你要激活clion: 则输入 sh clion.sh 42 | 如果你要激活goland: 则输入 sh goland.sh 43 | 如果你要激活webstorm: 则输入 sh webstorm.sh 44 | 如果你要激活phpstorm: 则输入 sh phpstorm.sh 45 | 如果你要激活dataspell:则输入 sh dataspell.sh 46 | 如果你要激活rider: 则输入 sh rider.sh 47 | ``` 48 | 49 | ![Success](http://img.geekyspace.cn/pictures/2024/202404171714345.png) 50 | 51 | ## 适用于Windows系统 52 | 53 | > 仅支持官网专业版2020-2024的激活。不支持社区版 toolbox下载的 54 | 55 | ### 1️⃣ 第一步:下载激活文件 56 | 57 | [激活文件获取](https://h5.m.goofish.com/item?id=785250929165) 58 | 59 | ### 2️⃣ 第二步:解压下载的压缩包 60 | 61 | ### 3️⃣ 第三步:打开解压的“`激活码-Win系统`”文件夹, 62 | 63 | 双击要激活软件的vbs 64 | 65 | ![Double-click](http://img.geekyspace.cn/pictures/2024/202404171718775.png) 66 | 67 | **♦♦比如:** 68 | 69 | * 要激活pycharm,就运行 pycharm激活.vbs 70 | * 要激活IDEA,就运行 IDEA激活.vbs 71 | 72 | 等待出现 如下 `Success` 提示,再次打开软件即可正常使用! 73 | 74 | ![Success](http://img.geekyspace.cn/pictures/2024/202404171718903.png) 75 | -------------------------------------------------------------------------------- /src/md/installation-guide/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 开发者安装大全 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-04-23 12 | category: 软件安装 13 | tag: 14 | --- 15 | 16 | # 开发者安装大全 17 | 18 | 该专栏主要整理与汇总开发者常用的编程环境、中间件等工具的安装,以指导开发者快速搭建自己的需要的开发环境。 19 | -------------------------------------------------------------------------------- /src/md/installation-guide/base-tools/Homebrew.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: macOS必备包管理工具—Homebrew 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2025-03-15 12 | category: 包管理器 13 | tags: 14 | - Homebrew 15 | --- 16 | 17 | # macOS必备包管理工具—Homebrew 18 | 19 | 主要官方来源:[Homebrew官网](https://brew.sh/zh-cn/) | [Github](https://github.com/homebrew) 20 | 21 | > Homebrew 是 macOS 和 Linux 上的包管理器,可以帮助用户轻松安装和管理各种软件包。 22 | 23 | ## 安装Homebrew 24 | 25 | ### 官网脚本 26 | 27 | ![安装Homebrew](http://img.geekyspace.cn/pictures/2025/20250312024222153.png) 28 | 29 | **安装 Homebrew** 30 | 31 | 将以下命令粘贴至终端: 32 | ```shell 33 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 34 | ``` 35 | 运行此命令将安装最新版本的Homebrew,自动处理MacOS环境中`Xcode Command Tools`的安装。 36 | - **`git`**(用于版本控制,拉取 Homebrew 软件包) 37 | - **`gcc` / `clang`**(用于编译部分软件包) 38 | - **`make`**(用于构建和安装依赖) 39 | 40 | 安装完成后,按照终端提示,添加 Homebrew 环境环境变量到 `.zprofile` 或 `.bash_profile`。 41 | ```shell 42 | echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile 43 | source ~/.zprofile 44 | ``` 45 | 46 | **卸载 Homebrew** 47 | ```shell 48 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)" 49 | ``` 50 | 51 | **验证 Homebrew 安装** 52 | 安装完成后,可以运行以下命令来检查 Homebrew 是否正确安装: 53 | ```shell 54 | brew --version 55 | ``` 56 | 57 | ### 国内脚本 58 | 59 | 由于国内网络访问 GitHub 可能较慢,可以使用国内的 Homebrew 安装脚本,加快安装速度。 60 | **安装步骤** 61 | 1. 访问 [HomebrewCN](https://gitee.com/cunkai/HomebrewCN)。 62 | 2. 按照说明执行相应的安装命令。 63 | 64 | ### .pkg包(macOS) 65 | 66 | 如果您使用的是 macOS,并希望通过图形化方式安装 Homebrew,可以使用官方提供的 `.pkg` 安装器。 67 | **安装步骤:** 68 | 1. 访问 [Homebrew 最新 GitHub 发行版](https://github.com/Homebrew/brew/releases/latest)。 69 | 2. 下载 `.pkg` 文件。 70 | 3. 双击安装并按照提示完成安装。 71 | 72 | ## 使用指南 73 | 74 | 以下是一些常见的 Homebrew 命令: 75 | 76 | | 操作 | 命令 | 77 | | ---------------- | ----------------------------------- | 78 | | 安装命令行软件包 | `brew install ` | 79 | | 安装图形界面软件 | `brew install --cask ` | 80 | | 搜索软件 | `brew search ` | 81 | | 卸载命令行软件包 | `brew uninstall ` | 82 | | 卸载图形界面软件 | `brew uninstall --cask ` | 83 | | 更新 Homebrew 本身 | `brew update` | 84 | | 更新所有已安装软件 | `brew upgrade` | 85 | | 更新具体软件 | `brew upgrade ` | 86 | | 显示已安装的软件 | `brew list` | 87 | | 查看软件信息 | `brew info ` | 88 | | 查看需要更新的软件 | `brew outdated` | 89 | | 清理不再使用的旧版本 | `brew cleanup` | 90 | | 显示软件安装路径 | `brew --prefix ` | 91 | | 测试软件是否正常运行 | `brew test ` | 92 | | 检查 Homebrew 运行状态 | `brew doctor` | 93 | | 列出所有可用的 cask | `brew list --cask` | 94 | | 列出所有依赖项 | `brew deps ` | 95 | | 显示详细依赖树 | `brew deps --tree ` | 96 | | 查看软件安装日志 | `brew log ` | 97 | ## 默认安装路径 98 | 99 | 在 Apple M1 (M4) 芯片的 Mac 上,Homebrew 默认安装路径有所不同。对于 M1 芯片,Homebrew 会安装在 `/opt/homebrew` 目录下,而不是像 Intel 芯片那样安装在 `/usr/local`。 100 | 101 | - **Homebrew** 安装路径:`/opt/homebrew` 102 | - **Homebrew 安装的软件** 默认路径:`/opt/homebrew/Cellar/` 103 | - **软链接** 默认路径:`/opt/homebrew/bin`(软链接会创建在这个目录中,以便更方便地在命令行中调用安装的软件) 104 | 105 | 通过以下命令确认安装路径: 106 | ```shell 107 | which brew 108 | ``` 109 | 检查某个软件包的安装路径,可以使用 `brew list <软件名>`。 110 | -------------------------------------------------------------------------------- /src/md/installation-guide/dev-env/java/SDKMAN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java生态版本管理神器—SDKMAN 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: true 11 | date: 2025-03-15 12 | category: 包管理器 13 | tags: 14 | - SDKMAN 15 | --- 16 | # Java生态版本管理神器—SDKMAN 17 | 18 | 主要官方来源:[SDKMAN官网](https://sdkman.io/) | [Github](https://github.com/sdkman) 19 | 20 | > SDKMAN! 是一个轻量级、支持多平台,开源的,用于管理多个 SDK(如 `Java`、`Kotlin`、`Gradle`、`Maven` 等)的工具。 21 | 22 | ## 安装SDKMAN 23 | 24 | **安装 SDKMAN!** 25 | * 只需启动一个新终端并输入: 26 | ```shell 27 | curl -s "https://get.sdkman.io" | bash 28 | ``` 29 | **初始化并配置环境变量** 30 | * 按照屏幕上的说明完成安装。然后,打开一个新终端或在同一 shell 中运行以下命令: 31 | ```shell 32 | source "$HOME/.sdkman/bin/sdkman-init.sh" 33 | ``` 34 | 最后,运行以下代码片段来确认安装成功: 35 | ```shell 36 | sdk version 37 | ``` 38 | 您应该看到包含最新脚本和本机版本的输出: 39 | ```shell 40 | SDKMAN! 41 | script: 5.19.0 42 | native: 0.6.0 (macos aarch64) 43 | ``` 44 | 45 | ## 使用指南 46 | 47 | 以下是基于 SDKMAN! 文档整理的 **Java SDK 管理命令及详细解释**,按照使用流程排列: 48 | ### 查看Java版本 49 | ```shell 50 | sdk list java 51 | ``` 52 | 查看所有 [Java 发行版](https://sdkman.io/jdks)(如 Zulu、AdoptOpenJDK、Amazon Corretto 等)及版本列表。 53 | ![sdk list java](http://img.geekyspace.cn/pictures/2025/20250313021115034.png) 54 | * **关键字段**:`Identifier` 是安装时使用的唯一标识符(如 `21.0.6-tem`)。 55 | 56 | ### JDK的安装与卸载 57 | 58 | **安装指定版本的 Java** 59 | ```shell 60 | sdk install java $Identifier 61 | ``` 62 | * 如果不输入`$Identifier`的话,会自动安装最新的稳定版本。 63 | * Eclipse Temurin 作为默认 JDK,因为它被广泛认为是 OpenJDK 发行版的事实标准。 64 | 65 | 66 | **卸载已安装的 Java 版本** 67 | ```shell 68 | sdk uninstall java $Identifier 69 | ``` 70 | ### JDK版本管理 71 | 72 | **切换 Java 版本(当前会话有效)** 73 | ```shell 74 | sdk use java $Identifier 75 | ``` 76 | 77 | **设置全局默认 Java 版本** 78 | ```shell 79 | sdk default java $Identifier 80 | ``` 81 | 82 | **查看当前 Java 版本** 83 | ```shell 84 | sdk current java 85 | ``` 86 | -------------------------------------------------------------------------------- /src/md/installation-guide/dev-env/nodejs/Corepack.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Corepack核心包管理工具 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2025-03-15 12 | category: 包管理器 13 | tag: 14 | - npm 15 | - pnpm 16 | - yarn 17 | --- 18 | 19 | # Corepack核心包管理工具 20 | 21 | 主要官方来源:[Nodejs官网文档](https://nodejs.org/api/corepack.html) | [Github](https://github.com/nodejs/corepack) 22 | 23 | > Corepack 作为一个内置于 Node.js 工具,为开发者解决了包管理器(`Yarn`、`npm` 和 `pnpm` )版本不一致和兼容性问题。 24 | 25 | ## 安装Corepack 26 | 从 Node.js 16.x 开始,Corepack 已经内置在 Node.js 中,所以只要安装 Node.js,就可以直接使用 Corepack。如果需要手动安装,可以参考 [Github 官方文档](https://github.com/nodejs/corepack?tab=readme-ov-file#manual-installs)。 27 | **验证安装** 28 | ```shell 29 | corepack -v 30 | ``` 31 | **Corepack更新** 32 | * 用于解决 [Corepack 中的签名过时](https://github.com/nodejs/corepack/issues/612) 问题 33 | ```shell 34 | npm install --global corepack@latest 35 | ``` 36 | ## 启用/禁用 Corepack 37 | * [Corepack](https://github.com/nodejs/corepack) 默认是实验性工具,需要手动启用。 38 | ```shell 39 | # 启用 40 | corepack enable 41 | ``` 42 | 启用后,它会自动为你管理所需的包管理器版本。 43 | ```shell 44 | # 禁用 45 | corepack disable 46 | ``` 47 | ## 管理包管理器的版本 48 | Corepack 使得你能够轻松管理不同版本的包管理器。比如你可以为项目指定特定版本的 `npm`、`Yarn` 或 `pnpm`。 49 | ### 安装[pnpm](https://pnpm.io/zh/) 50 | 51 | > 快速的,节省磁盘空间的包管理工具。pnpm 比 npm 快 2 倍。 52 | 53 | **安装并启用:** 54 | ```shell 55 | corepack prepare pnpm@latest --activate 56 | ``` 57 | **验证安装:** 58 | ```shell 59 | pnpm --version 60 | ``` 61 | 62 | **固定项目使用的版本:** 63 | ```shell 64 | corepack use pnpm@latest-10 65 | ``` 66 | 这会将 `pnpm` 的版本信息添加到 `package.json` 中的 `packageManager` 字段。 67 | ### 安装[Yarn](https://yarnpkg.com/) 68 | 69 | [Github](https://github.com/yarnpkg/yarn) 70 | 71 | > Yarn 是 Facebook 开发的一个快速、安全、稳定的包管理器,特别适用于大型项目。 72 | 73 | **安装并启用:** 74 | * 稳定版本:1.22.22 75 | ```shell 76 | corepack prepare yarn@1.22.22 --activate 77 | ``` 78 | **验证安装:** 79 | ```shell 80 | yarn --version 81 | ``` 82 | **固定项目使用的版本:** 83 | ```shell 84 | corepack use yarn@1.22.22 85 | ``` 86 | 在 `package.json` 中,会看到类似以下内容: 87 | ```shell 88 | { 89 | "packageManager": "yarn@1.22.22" 90 | } 91 | ``` 92 | 这样,Corepack 会根据该配置自动管理 Yarn 的版本。 -------------------------------------------------------------------------------- /src/md/installation-guide/dev-env/nodejs/nrm.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: npm源切换加速利器—nrm 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2025-03-17 12 | category: 包管理器 13 | tags: 14 | - nrm 15 | - 镜像 16 | --- 17 | # npm源切换加速利器—nrm 18 | 19 | 主要官方来源:[npmjs-nrm](https://www.npmjs.com/package/nrm) 20 | 21 | > `nrm`可以帮助您轻松快速地切换不同的 npm 注册表。它支持 `npm` 、 `cnpm` 、 `taobao` 、 `yarn` 、 `tencent` 、 `npmMirror`和`huawei` 。 22 | 23 | ## 安装nrm 24 | ```shell 25 | # npm 安装 26 | npm install -g nrm 27 | ``` 28 | 安装成功后,使用`nrm --version`查看命令,验证是否安装正常。 29 | 30 | ## 使用指南 31 | ### nrm ls 32 | 查看源列表 33 | ```shell 34 | $ nrm ls 35 | 36 | * npm ---------- https://registry.npmjs.org/ 37 | yarn --------- https://registry.yarnpkg.com/ 38 | tencent ------ https://mirrors.tencent.com/npm/ 39 | cnpm --------- https://r.cnpmjs.org/ 40 | taobao ------- https://registry.npmmirror.com/ 41 | npmMirror ---- https://skimdb.npmjs.com/registry/ 42 | huawei ------- https://repo.huaweicloud.com/repository/npm/ 43 | ``` 44 | ### nrm use 45 | 切换镜像 46 | ```shell 47 | $ nrm use taobao 48 | 49 | SUCCESS The registry has been changed to 'taobao'. 50 | ``` 51 | ### nrm current 52 | 查看当前源 53 | ```shell 54 | $ nrm current 55 | 56 | You are using taobao registry. 57 | ``` 58 | ### nrm test 59 | 测试所有源的响应时间 60 | ```shell 61 | $ nrm test 62 |   npm ---------- 823 ms 63 |   yarn --------- 799 ms 64 |   tencent ------ 964 ms 65 |   cnpm --------- 1882 ms 66 | * taobao ------- 184 ms 67 |   npmMirror ---- 871 ms 68 |   huawei ------- 845 ms 69 | ``` 70 | ### nrm add 71 | 添加镜像源 72 | ```shell 73 | $ nrm add <源名> <源URL> 74 | ``` 75 | ### nrm del 76 | 删除镜像源 77 | ```shell 78 | nrm del <源名> 79 | ``` 80 | 81 | ## 统一切换npm,pnpm,yarn镜像源 82 | 使用 `nrm` 切换镜像源时,通常会修改 `.npmrc` 配置文件,这会同时影响 `npm` 和 `pnpm`。 83 | ``` 84 | nrm use huawei 85 | ``` 86 | * 验证 `npm` 镜像源: 87 | ```shell 88 | npm config get registry 89 | ``` 90 | * 验证 `pnpm` 镜像源: 91 | ```shell 92 | pnpm config get registry 93 | ``` 94 | 95 | 为了确保`nrm use`命令, `yarn` 也同步更新镜像源,在 `~/.zshrc`中中添加以下内容: 96 | ```shell 97 | nrm() { 98 | if [ "$1" = "use" ] && [ -n "$2" ]; then 99 | command nrm use "$2" 100 | yarn config set registry $(npm config get registry) 101 | echo "Yarn registry updated to $(yarn config get registry)" 102 | else 103 | command nrm "$@" 104 | fi 105 | } 106 | ``` 107 | 执行`source ~/.zshrc`后,验证 `yarn` 镜像源: 108 | ```shell 109 | yarn config get registry 110 | ``` 111 | -------------------------------------------------------------------------------- /src/md/installation-guide/dev-env/nodejs/nvm.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Node.js版本管理神器—nvm 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2025-03-15 12 | category: 包管理器 13 | tags: 14 | - nvm 15 | --- 16 | 17 | # Node.js版本管理神器—nvm 18 | 19 | 主要官方来源:[nvm Github](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating) 20 | 21 | > `nvm`是 [node.js](https://nodejs.org/zh-cn) 的版本管理器,允许您通过命令行快速安装和使用不同版本的 node。 22 | 23 | ## 安装nvm 24 | 25 | 手动下载并运行 [安装脚本](https://github.com/nvm-sh/nvm/blob/v0.40.2/install.sh),或使用**命令行安装**(`cURL` 或 `Wget`): 26 | ```shell 27 | # 使用 curl 安装 28 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash 29 | 30 | # 使用 wget 安装 31 | wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash 32 | ``` 33 | 34 | 脚本会自动**配置环境变量**(`~/.zshrc`下): 35 | ``` 36 | export NVM_DIR="$HOME/.nvm" 37 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # 加载 nvm 38 | [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # 加载 nvm bash_completion 39 | ``` 40 | * 自行修改后,需执行 `source ~/.zshrc` 41 | 42 | **验证安装** 43 | ```shell 44 | nvm --version 45 | ``` 46 | 47 | > 注意:[nvm](https://github.com/nvm-sh/nvm) 的维护者明确表示 `brew` 不是官方推荐的安装方式。 48 | 49 | ## 使用指南 50 | 51 | ### 查看所有可用版本 52 | 53 | ```shell 54 | nvm ls-remote --lts # 查看所有长期支持 (LTS) 版本 55 | nvm ls-remote # 查看所有可用版本 56 | ``` 57 | ### 安装 58 | ```shell 59 | nvm install # 安装指定版本(如 nvm install 20) 60 | nvm install --lts # 安装最新的 LTS 版本 61 | nvm install node # 安装最新的稳定版本 62 | ``` 63 | ### 切换版本 64 | ```shell 65 | nvm use # 切换到指定版本(如 nvm use 16) 66 | nvm use default # 切换到默认版本 67 | ``` 68 | ### 设置默认版本 69 | ```shell 70 | nvm alias default # 设置默认版本(如 nvm alias default 18) 71 | ``` 72 | ### 列出已安装的版本 73 | ```shell 74 | nvm list # 列出本地已安装的 Node.js 版本 75 | nvm ls # 同上,显示已安装的版本 76 | ``` 77 | ### 当前版本 78 | ```shell 79 | node -v # 显示当前 Node.js 版本 80 | nvm current # 显示当前 NVM 管理的 Node.js 版本 81 | ``` 82 | ### 卸载 83 | ```shell 84 | nvm uninstall # 卸载指定版本(如 nvm uninstall 16) 85 | ``` 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/md/installation-guide/os/windows-office-activation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Windows、Office激活密钥,脚本,程序 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-04-23 12 | category: 激活 13 | tag: 14 | - Windows 15 | - Office 16 | --- 17 | 18 | # Windows、Office激活密钥,脚本,程序 19 | 20 | ## ① 微软正版秘钥(官方) 21 | 22 | ### 01.Windows系统激活 23 | 24 | 1. `Win+R` 打开终端,输入命令 25 | 26 | ```shell 27 | slui 3 28 | ``` 29 | 30 | 2. 打开“系统激活”窗口,点击“更改”按钮 31 | 32 | ![更改产品密钥](http://img.geekyspace.cn/pictures/2024/202404232040995.png) 33 | 34 | 3. 输入“秘钥”,点击“下一页” 进行 “激活” !获取 [微软Windows官方正版秘钥](https://learn.microsoft.com/zh-cn/windows-server/get-started/kms-client-activation-keys) 35 | 36 | ![输入产品密钥](http://img.geekyspace.cn/pictures/2024/202404232318292.png) 37 | 38 | 4. “激活”成功 39 | 40 | ![Windows官方密钥激活](http://img.geekyspace.cn/pictures/2024/202404232327475.png) 41 | 42 | ### 02.Office软件激活 43 | 44 | 1. **打开Office应用程序:** 启动`Word`、`Excel`或`PowerPoint`等 45 | 2. **访问文件菜单:** 在应用程序中,点击左上角的"文件"菜单 46 | 3. **点击账户:** 在文件菜单中,选择"账户" 47 | 4. **更改产品密钥:** 在账户页面,找到并点击"更改产品密钥" 48 | 5. **输入产品密钥:** 在弹出的对话框中输入你的25位密钥 !获取 [微软Office官方正版秘钥](https://learn.microsoft.com/zh-cn/deployoffice/vlactivation/gvlks) 49 | 6. **激活Office:** 输入密钥后,点击"继续"或"激活产品" 50 | 51 | ## ② 激活脚本(原理) 52 | 53 | > 一键激活脚本:[https://wwi.lanzoup.com/b0foh27ne](https://wwi.lanzoup.com/b0foh27ne), 密码:`gkjb` 54 | 55 | ![脚本-持续更新](http://img.geekyspace.cn/pictures/2024/202404231900072.png) 56 | 57 | ### 01.脚本激活命令 58 | 59 | **将密钥写入系统,实现激活** 60 | 61 | ```shell 62 | slmgr /ipk XXXXX-XXXXX-XXXXX-XXXXX-XXXXX 63 | ``` 64 | 65 | 如果你有新的零售产品密钥,将 `X` 替换为实际的密钥,运行以上命令即可激活 66 | 67 | ### 02.脚本激活网站推荐 68 | 69 | [KMS一键激活windows/office网站](https://kms.cx/) 70 | 71 | ![KMS一键激活windows/office网站](http://img.geekyspace.cn/pictures/2024/202404240116136.png) 72 | 73 | ## ③ 激活程序(推荐) 74 | 75 | > 获取 [一键激活程序](https://m.tb.cn/h.g0ZtnKz?tk=OoXyWIKR74J) ,提供多种激活方式 76 | 77 | ![程序-持续更新](http://img.geekyspace.cn/pictures/2024/202404231900370.png) 78 | 79 | ::: note 注意:使用一键激活程序,需先关闭电脑相关的杀毒程序 80 | 81 | * [关闭 Windows 安全中心中的Defender 防病毒保护](https://support.microsoft.com/zh-cn/windows/%E5%85%B3%E9%97%AD-windows-%E5%AE%89%E5%85%A8%E4%B8%AD%E5%BF%83%E4%B8%AD%E7%9A%84defender-%E9%98%B2%E7%97%85%E6%AF%92%E4%BF%9D%E6%8A%A4-99e6004f-c54c-8509-773c-a4d776b77960) 82 | * [360安全卫士设置白名单](http://www.fastaux.com/index.php?c=show&id=54) 83 | * [360杀毒软件怎么添加文件白名单](https://www.eyunsou.com/360sd/gongnneg/bmd/) 84 | 85 | ::: 86 | 87 | ### 01.数字激活工具 HWID_KMS38_CN_62 88 | 89 | **概述** 90 | 91 | `HWID_KMS38_CN_62.exe` 是一种**数字激活工具**,主要用于激活 Microsoft Windows 操作系统。它采用 HWID 技术,生成硬件标识符并将其发送到 92 | Microsoft 的激活服务器进行验证,实现系统长期激活。 93 | 94 | **激活步骤** 95 | 96 | 1. 鼠标右键点击“打开”,进入程序中 97 | 98 | ![正在启动](http://img.geekyspace.cn/pictures/2024/202404232343700.png) 99 | 100 | 2. 进入程序界面后,工作模式选择“==安装数字密钥==(HWID)”,点击“开始” 101 | 102 | ![数字密钥激活](http://img.geekyspace.cn/pictures/2024/202404232345572.png) 103 | 104 | 3. 完成后执行[一键激活脚本](#一键激活脚本)中的`06.查看是否永久激活.bat`验证激活状态 105 | 106 | ![计算机已永久激活](http://img.geekyspace.cn/pictures/2024/202404232347627.png) 107 | 108 | ### 02.KMS激活工具 DragonKMS 109 | 110 | 互联网论坛上的知名激活工具,也是我个人常用的激活工具,常用于激活Windows和Office全家桶,通过联网KMS激活 111 | 112 | ![MicroKMS神龙版](http://img.geekyspace.cn/pictures/2024/202404232356474.png) 113 | 114 | ### 03.开源激活工具 HEU_KMS_Activator 115 | 116 | 仅供研究激活原理使用,在“智能激活”中点击“开始”,即按照最佳激活方式"数字许可证/KMS38/OEM/KMS"依次激活,直到激活成功 117 | 118 | ![智能激活(即数字许可证/KMS38/OEM/KMS依次激活)](http://img.geekyspace.cn/pictures/2024/202404240021458.png) 119 | 120 | 等待一会,激活成功后提示 121 | 122 | ![Success](http://img.geekyspace.cn/pictures/2024/202404240027800.png) 123 | 124 | ### 04.小马激活工具 KMS10 125 | 126 | 老牌激活工具,文件大小仅1M,我以前还用来激活Win7,可以想象年代的久远。支持离线激活,永久有效,无需考虑激活时限。office版本为retail的用户请自行转换版本到volume,即channel 127 | switch。 128 | 129 | ![小马激活工具](http://img.geekyspace.cn/pictures/2024/202404240037901.png) 130 | 131 | ### 05.大神制作 W10DigitalActivation 132 | 133 | 大神Ratiborus制作的,它还有开发激活Office的 **KMSAuto++** 工具,以及可定制化安装Office的 **Office2013-2021 C2R Install** 134 | 。可以关注它的[Twitter](https://twitter.com/ratiborus58)。 135 | 136 | ![WIN0-HWID数字永久激活](http://img.geekyspace.cn/pictures/2024/202404240055203.png) 137 | 138 | ![永久激活](http://img.geekyspace.cn/pictures/2024/202404240057039.png) 139 | 140 | ### 06.云萌激活工具 CMWTAT_Digital 141 | 142 | 云萌 Windows 10 激活工具,专门用于数字权利激活的软件。本软件是一款使用CSharp编写的 Windows 10 数字权利激活工具 143 | 144 | ![云萌 Windows 10 激活工具](http://img.geekyspace.cn/pictures/2024/202404240106825.png) 145 | -------------------------------------------------------------------------------- /src/md/java-features/Java10/jep286-local-variable-type-inference.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 10 新特性:局部变量类型推断 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | date: 2023-12-23 10 | sticky: false 11 | star: false 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 286 16 | --- 17 | 18 | # Java 10 新特性:局部变量类型推断 19 | 20 | Java 10 引入了一项新的语言特性,即**局部变量类型推断**(Local-Variable Type Inference), 21 | 它允许在局部变量声明时,根据变量的初始值,推断出变量的数据类型。 22 | 23 | ## 语法 24 | 25 | 局部变量类型推断的语法非常简单,只需要将 `var` 关键字作为局部变量的类型即可。 26 | 27 | ```java 28 | var list = new ArrayList(); // 自动推断 ArrayList 29 | var stream = list.stream(); // 自动推断 Stream 30 | ``` 31 | 32 | ## 示例 33 | 34 | 相比传统的方式和 Java 7 的钻石操作符(Diamond Operator),Java 10 的局部变量类型推断使得代码更加精炼: 35 | 36 | ```java 37 | // 传统方式(等号两边都需要) 38 | List list = new ArrayList(); 39 | 40 | // Java7的钻石操作符(Diamond Operator)(只需要在左边申明类型即可) 41 | List list = new ArrayList<>(); 42 | 43 | // Java10的局部变量类型推断(类型在等号右边决定) 44 | var list = new ArrayList(); 45 | ``` 46 | 47 | 在使用 var 进行局部变量类型推断时,需要注意以下几点: 48 | 1. 必须在声明的同时进行初始化 49 | 2. 仅限于局部变量,不能用于定义成员变量、方法参数和返回类型 50 | 3. 每次只能定义一个变量,不能复合声明多个变量 51 | 52 | 通过使用局部变量类型推断,我们能够在不失代码可读性的前提下,减少了冗余的类型声明,使得代码更加简洁清晰。 53 | 这一特性尤其在Lambda表达式、集合初始化等场景下表现突出,提高了代码的书写效率。 54 | 在实际项目中,合理运用局部变量类型推断,将有助于代码的维护和阅读。 55 | -------------------------------------------------------------------------------- /src/md/java-features/Java11/jep320-remove-JavaEE-CORBA.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 11 新特性 :移除JavaEE和CORBA模块以及JavaFX 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-24 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 320 16 | --- 17 | 18 | # Java 11 新特性 :移除JavaEE和CORBA模块以及JavaFX 19 | 20 | Java 11 中移除了 `Java EE` 和 `CORBA` 模块,同时 `JavaFX` 也被剥离,但仍可作为独立模块使用。 21 | 22 | ## Java9 弃用过程 23 | 24 | 在 Java 9 中,`Java EE` 和 `CORBA` 模块被标记为 @Deprecated,为开发者提供了适应期。 25 | 26 | ## Java11 彻底删除 27 | 28 | Java 11 完全删除了以下九个模块: 29 | 30 | * java.xml.ws(包含 JAX-WS、SAAJ 和 Web 服务元数据) 31 | * java.xml.bind(JAXB) 32 | * java.activation(JAF) 33 | * java.xml.ws.annotation(常用注解) 34 | * java.corba(CORBA) 35 | * java.transaction(JTA) 36 | * java.se.ee (以上6个模块的聚合模块) 37 | * jdk.xml.ws (JAX-WS 工具) 38 | * jdk.xml.bind (JAXB 工具) 39 | 40 | 删除后的影响: 41 | 42 | * 源代码从 OpenJDK 存储库中删除 43 | * 在 JDK 运行时映像中将不包含这些类 44 | * 相关工具将不再可用: 45 | * wsgen and wsimport (来自 jdk.xml.ws) 46 | * schemagen and xjc (来自 jdk.xml.bind) 47 | * idlj, orbd, servertool, and tnamesrv (来自 java.corba) 48 | * JNDI CosNaming 提供者 (来自 java.corba) 将不再可用 49 | * 不再有命令行标志能够启用它们,就像 JDK 9 上的 --add-modules 一样 50 | 51 | ## JavaFX 移除 52 | 53 | `JavaFX` 在 Java 11 中被移除,但仍可以作为独立模块使用。 54 | 开发者需要额外的配置和依赖,以在项目中继续使用 JavaFX 技术。 55 | -------------------------------------------------------------------------------- /src/md/java-features/Java14/jep361-switch-expressions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 14 新特性:switch表达式增强 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-25 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 361 16 | --- 17 | 18 | # Java 14 新特性:switch表达式增强 19 | 20 | Java 14(JEP 361)引入了 switch 表达式的新特性,其中包括了 "**箭头标签**(`case ... ->`)" 和 `yield` 语句的增强, 21 | 同时支持 `Lambda` 语法,使得代码更加灵活、简洁,并为未来的**模式匹配**(JEP 305)特性做好了准备。 22 | 23 | ## 传统的switch语句 24 | 25 | 首先,让我们回顾一下传统的switch语句,它们在处理多个条件时可能显得有些冗长: 26 | 27 | ```java 28 | switch (day) { 29 | case MONDAY: 30 | case FRIDAY: 31 | case SUNDAY: 32 | System.out.println(6); 33 | break; 34 | case TUESDAY: 35 | System.out.println(7); 36 | break; 37 | case THURSDAY: 38 | case SATURDAY: 39 | System.out.println(8); 40 | break; 41 | case WEDNESDAY: 42 | System.out.println(9); 43 | break; 44 | } 45 | ``` 46 | 47 | 传统的 switch 语句存在以下问题: 48 | 49 | 1. 设计受到C和C++等低级语言的影响,且默认支持fall through语义 50 | 2. 过多的`break`语句使得代码显得冗长 51 | 52 | ## switch表达式增强 53 | 54 | ### 箭头标签(case L ->) 55 | 56 | 1. 引入了一种新的开关标签"`case L ->`",用于表示只有一个分支的情况 57 | 2. 允许每种情况下有多个常量,用逗号分隔 58 | 3. 标签右侧的代码仅限于表达式、块或抛出异常throw语句 59 | 60 | ```java 61 | switch (day) { 62 | case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); 63 | case TUESDAY -> System.out.println(7); 64 | case THURSDAY, SATURDAY -> System.out.println(8); 65 | case WEDNESDAY -> System.out.println(9); 66 | } 67 | ``` 68 | 69 | ### 局部变量独立作用域 70 | 71 | 在 Java 14 中,允许在每个分支中声明局部变量,避免块中变量命名冲突和误用。 72 | 73 | ``` 74 | switch (day) { 75 | case MONDAY: 76 | case TUESDAY: 77 | int temp = ... // 'temp'的作用域延续到 } 78 | break; 79 | case WEDNESDAY: 80 | case THURSDAY: 81 | int temp2 = ... // 不能将此变量命名为'temp' 82 | break; 83 | default: 84 | int temp3 = ... // 不能将此变量命名为'temp' 85 | } 86 | ``` 87 | 88 | ### switch表达式 89 | 90 | Switch 表达式被引入,允许将 `switch` 语句用作表达式,通过 `Lambda` 语法,根据输入值返回不同的结果。 91 | 92 | ```java 93 | // 根据输入值`k`的不同,返回不同的字符串,并通过`System.out.println`打印结果 94 | static void howMany(int k) { 95 | System.out.println( 96 | switch (k) { 97 | case 1 -> "one"; 98 | case 2 -> "two"; 99 | default -> "many"; 100 | } 101 | ); 102 | } 103 | ``` 104 | 105 | Switch表达式的常见形式如下: 106 | 107 | ```java 108 | T result = switch (arg) { 109 | case L1 -> e1; 110 | case L2 -> e2; 111 | default -> e3; 112 | }; 113 | ``` 114 | 115 | > Switch表达式是多态表达式(poly expression)。 116 | > 117 | > 多态性是指在编译时不确定具体类型,而在运行时确定类型的特性。 118 | 119 | ### yield语句返回值 120 | 121 | 允许在switch表达式中使用`yield`语句,而不是使用`break`语句,用于返回一个值,结束switch表达式的执行。 122 | 123 | ```java 124 | int numLetters = switch (day) { 125 | case MONDAY, FRIDAY, SUNDAY -> 6; 126 | case TUESDAY -> 7; 127 | case THURSDAY, SATURDAY -> 8; 128 | case WEDNESDAY -> { 129 | int temp = performComplexCalculation(); // 在这里进行一些复杂的计算 130 | yield temp; // 使用yield返回计算结果 131 | } 132 | }; 133 | ``` 134 | 135 | ## 发展脉络 136 | 137 | 追溯JEP 361的发展历程:从JDK 12预览版(JEP 325)到JDK 13预览版(JEP 354), 138 | 虽然部分功能在早期版本中已经出现,但建议在 JDK 14 及以后的版本中使用,以获得更好的稳定性和支持。 139 | -------------------------------------------------------------------------------- /src/md/java-features/Java15/jep371-hidden-classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 15 新特性:隐藏类(Hidden Classes) 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-26 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 371 16 | --- 17 | 18 | # Java 15 新特性:隐藏类(Hidden Classes) 19 | 20 | **隐藏类**(Hidden Classes) 提供了一种在运行时生成类的机制,在编译时未知,并且不能直接在源代码中引用, 21 | 需要通过反射间接使用它们,隐藏类是为框架设计的,具有以下特性: 22 | 23 | * **动态生成内部类**:隐藏类天生为框架设计,在运行时生成内部类 24 | * **反射访问限制**:隐藏类只能通过反射访问,不能直接被其他类的字节码访问 25 | * **独立加载和卸载**:隐藏类可以独立于其他类加载和卸载 26 | * **框架扩展性**:适用于需要在运行时生成类的框架,提高语言的灵活性和效率 27 | 28 | ## 原理 29 | 30 | 31 | ## 框架中应用 32 | 33 | 34 | [https://bugs.openjdk.org/browse/JDK-8220607](https://bugs.openjdk.org/browse/JDK-8220607) 35 | -------------------------------------------------------------------------------- /src/md/java-features/Java15/jep378-text-blocks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 15 新特性:文本块(Text Blocks) 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-27 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 378 16 | --- 17 | 18 | # Java 15 新特性:文本块(Text Blocks) 19 | 20 | Java 15(JEP 378)引入了**文本块**(Text Blocks)这一新特性,旨在简化多行字符串的表示,提高代码可读性,并减少在字符串中使用转义符的需求。 21 | 文本块通过引入三个双引号的**胖分隔符**(`"""`)来实现,同时支持转义序列,为开发人员提供更直观、易读的字符串处理方式。 22 | 23 | ## 快速上手 24 | 25 | **HTML示例** 26 | 27 | ```java 28 | // 使用“一维”字符串文字 29 | String html = "\n" + 30 | " \n" + 31 | "

Hello, world

\n" + 32 | " \n" + 33 | "\n"; 34 | 35 | // 使用“二维”文本块 36 | String html = """ 37 | 38 | 39 |

Hello, world

40 | 41 | 42 | """; 43 | ``` 44 | 45 | **SQL示例** 46 | 47 | ```sql 48 | // 使用“一维”字符串文字 49 | String query = "SELECT \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TB\"\n" + 50 | "WHERE \"CITY\" = 'INDIANAPOLIS'\n" + 51 | "ORDER BY \"EMP_ID\", \"LAST_NAME\";\n"; 52 | 53 | // 使用“二维”文本块 54 | String query = """ 55 | SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB" 56 | WHERE "CITY" = 'INDIANAPOLIS' 57 | ORDER BY "EMP_ID", "LAST_NAME"; 58 | """; 59 | ``` 60 | 61 | **Polyglot语言示例** 62 | 63 | ```polyglot 64 | // 使用“一维”字符串文字 65 | ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); 66 | Object obj = engine.eval("function hello() {\n" + 67 | " print('\"Hello, world\"');\n" + 68 | "}\n" + 69 | "\n" + 70 | "hello();\n"); 71 | 72 | // 使用“二维”文本块 73 | ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); 74 | Object obj = engine.eval("function hello() {\n" + 75 | " print('\"Hello, world\"');\n" + 76 | "}\n" + 77 | "\n" + 78 | "hello();\n"); 79 | ``` 80 | 81 | ## 编译时处理 82 | 83 | 文本块是String类型的常量表达式,类似于字符串字面量。然而,与字符串字面值不同,文本块的内容在编译时经历三个步骤的处理:==行终止符的规范化==、==附带白色空间的移除==和==解释转义序列==: 84 | 85 | 1. 转换内容的行终止符 86 | * 行终止符从CR(\u000D)和CRLF(\u000D\u000A)规范化为`LF(\u000A)` 87 | 2. 删除内容周围附带的白色空间(用于匹配Java源代码的缩进) 88 | 3. 解释内容中的转义序列,执行解释作为最后一步开发人员可以编写转义序列,如\n,而不会被前面的步骤修改或删除 89 | 90 | 处理后的内容以`CONSTANT_String_info`形式记录在**类文件的常量池**中,运行时,文本块被计算为String的实例。 91 | 92 | ## 新增转义序列 93 | 94 | 为了更精细地控制==换行符==和==空格==的处理,引入了两个新的转义序列:\ 和 \s。 95 | 96 | ### 换行符 \ 97 | 98 | ```java 99 | // 传统方式 100 | String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " + 101 | "elit, sed do eiusmod tempor incididunt ut labore " + 102 | "et dolore magna aliqua."; 103 | 104 | // 使用 \ 105 | String text = """ 106 | Lorem ipsum dolor sit amet, consectetur adipiscing \ 107 | elit, sed do eiusmod tempor incididunt ut labore \ 108 | et dolore magna aliqua.\ 109 | """; 110 | ``` 111 | 112 | > Tips: 因为字符和传统字符串不允许嵌入换行符,所以\ 转义序列只适用于文本块 113 | 114 | ### 单个空格 \s 115 | 116 | 新的 \s 转义序列简单地转换为单个空格(\u0020) 117 | 118 | ```java 119 | // 使用 \s 保持固定长度 120 | String colors = """ 121 | red \s 122 | green\s 123 | blue \s 124 | """; 125 | ``` 126 | 127 | 转义序列直到去除无意义的空格后才被解释,\s 可以作为栅栏,防止尾随空格被去除。 128 | 在这个示例中,每行末尾使用 \s 可以确保每行长度恰好为六个字符。 129 | 130 | ## 文本块连接 131 | 132 | 文本块的连接是引入的一个方便的特性,使得字符串的拼接变得更加简洁。 133 | 在连接时,相邻的文本块将自动合并,无需显式使用加号连接操作符。 134 | 135 | ```java 136 | // 字符串和文本块连接 137 | String code = "public void print(Object o) {" + 138 | """ 139 | System.out.println(Objects.toString(o)); 140 | } 141 | """; 142 | 143 | // 相邻的文本块将自动合并,无需显式使用加号连接操作符 144 | String code = """ 145 | public void print(Object o) { 146 | """ 147 | """ 148 | System.out.println(Objects.toString(o)); 149 | } 150 | """; 151 | ``` 152 | 153 | 在上述示例中,两个相邻的文本块会自动连接,形成一个整体的字符串。 154 | 这种自动连接的特性让代码更加清晰,减少了冗余的拼接操作。 155 | 156 | ## 文本块新方法 157 | 158 | 文本块引入了一些新方法,以便更方便地处理多行字符串: 159 | 160 | - `String::stripIndent()`:去除多行字符串的前导空格 161 | - `String::translateEscapes()`:转义多行字符串中的转义字符 162 | - `String::formatted()`:在文本块中使用占位符进行值替换 163 | - `String::lines():`:将多行字符串拆分为行的流,方便逐行处理。 164 | -------------------------------------------------------------------------------- /src/md/java-features/Java16/jep394-pattern-matching-for-instanceof.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 16 新特性:instanceof 模式匹配 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-28 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 394 16 | --- 17 | 18 | # Java 16 新特性:instanceof 模式匹配 19 | 20 | Java 16 引入了`instanceof`**模式匹配**的增强语法,用于更简便地判断对象是否是某个类的实例并进行相应的**局部类型转换**。 21 | 22 | ## instanceof 基础用法 23 | 24 | ```java 25 | if (obj instanceof String) { 26 | String someString = (String) obj; // 强制类型转换 27 | // ... 28 | } 29 | ``` 30 | 31 | 这个**强制转换**通常是在 `instanceof` 检查之后 的第一件事,所以为什么不围绕它优化一下语法呢? 32 | 33 | ## instanceof 增强用法 34 | 35 | ```java 36 | if (obj instanceof String someString) { 37 | // ... 38 | } 39 | // 这里 someString 超出了作用域 40 | ``` 41 | 42 | 1. 若 `instanceof` 检查成功,将自动将变量转换为指定类型 43 | 2. 定义的变量实质上是一个**局部变量**,只在if语句的范围内可见 44 | 45 | ## 常见用法建议 46 | 47 | 不仅如此!使用模式匹配,我们可以更灵活地应用条件测试。 48 | 49 | * 利用已定义的 obj,在不需要额外嵌套的情况下判断字符串是否以“Awesome”开头 50 | 51 | ```java 52 | // 以前 53 | return (someObject instanceof String) && ((String) someObject).startsWith("Awesome"); 54 | 55 | // 现在 56 | return someObject instanceof String someString && someString.startsWith("Awesome"); 57 | ``` 58 | 59 | * 甚至在 equals 方法中,代码会更加简洁 60 | 61 | ```java 62 | // 以前 63 | public boolean equals(Object obj) { 64 | if (obj instanceof Integer) { 65 | return value == ((Integer) obj).intValue(); 66 | } 67 | return false; 68 | } 69 | 70 | // 现在 71 | public boolean equals(Object obj) { 72 | return (obj instanceof Integer i) && value == i.intValue(); 73 | } 74 | ``` 75 | 76 | 多么不同!现在代码简洁而直观。 77 | 78 | ## 发展脉络 79 | 80 | 该功能经历了2个预览版本(JDK 14中的JEP 305、JDK 15中的JEP 375),最终定稿于JDK 16中的JEP 394。 81 | -------------------------------------------------------------------------------- /src/md/java-features/Java16/jep395-records.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 16 新特性:record类 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-29 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 395 16 | --- 17 | 18 | # Java 16 新特性:record类 19 | 20 | Java 16 引入的**记录类**(Records Classes)是一种用于简化不可变数据管理的特殊类。 21 | 它通过紧凑的语法提供了对不可变数据的支持,并自动生成常见的方法, 22 | 如`equals()`、`hashCode()`和`toString()`等,从而减少了开发者的样板代码。 23 | 24 | ## 传统Java Bean问题 25 | 26 | 在处理不可变数据时,传统的Java Bean存在“==繁琐==”和“==冗长==”的问题。 27 | 为了管理少量值的不可变数据,开发者需要编写大量低价值、重复且容易出错的代码, 28 | 包括`构造函数`、`访问器(getter/setter)`、`equals`、`hashCode`、`toString`等。 29 | 30 | 例如,用于携带x和y坐标的类通常会演变成以下繁琐形式: 31 | 32 | ```java 33 | class Point { 34 | private final int x; 35 | private final int y; 36 | 37 | Point(int x, int y) { 38 | this.x = x; 39 | this.y = y; 40 | } 41 | 42 | int x() { return x; } 43 | int y() { return y; } 44 | 45 | public boolean equals(Object o) { 46 | if (!(o instanceof Point)) return false; 47 | Point other = (Point) o; 48 | return other.x == x && other.y == y; 49 | } 50 | 51 | public int hashCode() { 52 | return Objects.hash(x, y); 53 | } 54 | 55 | public String toString() { 56 | return String.format("Point[x=%d, y=%d]", x, y); 57 | } 58 | } 59 | ``` 60 | 61 | ## 引入record类 62 | 63 | **语法如下**:使用 `record`==关键字==,指定==类名称==为 Point,定义==参数列表== x 和 y 作为组件 64 | 65 | ```java 66 | record Point(int x, int y) { } 67 | ``` 68 | 69 | record申明的类,具备这些特点: 70 | 71 | 1. 它是一个`final`类 72 | 2. 自动实现`equals`、`hashCode`、`toString`函数 73 | 3. 成员变量均为`public`属性 74 | 75 | 所以,对于之前写的Point类,它等价于一个这样的类: 76 | 77 | ```java 78 | // Record类声明,使用record关键字,名称为Point,带有两个参数x和y 79 | public final class Point { 80 | // 1. 自动生成成员变量(fields) 81 | final int x; 82 | final int y; 83 | 84 | // 2. 自动生成构造函数(constructor) 85 | public Point( int x, int y){ 86 | this.x = x; 87 | this.y = y; 88 | } 89 | 90 | // 3. 自动生成的访问方法 91 | public int x () { 92 | return x; 93 | } 94 | 95 | public int y () { 96 | return y; 97 | } 98 | 99 | // 4. 自动生成equals和hashCode方法 100 | @Override 101 | public boolean equals (Object obj){ 102 | // 实现相等性比较的逻辑 103 | } 104 | 105 | @Override 106 | public int hashCode () { 107 | // 生成哈希码的逻辑 108 | } 109 | 110 | // 5. 自动生成toString方法 111 | @Override 112 | public String toString () { 113 | return "Point{" + 114 | "x=" + x + 115 | ", y=" + y + 116 | '}'; 117 | } 118 | } 119 | ``` 120 | 121 | 通过使用record类,你可以更专注于业务逻辑而不是样板代码,提高了代码的可读性和可维护性。 122 | 123 | ## 显示声明紧凑构造函数 124 | 125 | 未显式声明构造函数时,系统会自动生成包含所有成员变量的隐式构造函数。 126 | 当显式声明紧凑构造函数可以==省略形式参数列表==、编译后在构造函数的末尾==自动分配==给相应的形式参数(this.x = x;)。 127 | 128 | * **验证参数**的紧凑构造函数 129 | 130 | ```java 131 | record Book(String title, String author, int pageCount) { 132 | Book { 133 | if (pageCount <= 0) { 134 | throw new IllegalArgumentException("页数必须大于零."); 135 | } 136 | } 137 | } 138 | ``` 139 | 140 | * **规范**的紧凑构造函数 141 | 142 | ```java 143 | // 记录类 Rational 表示有理数,包含分子(num)和分母(denom)两个成员变量 144 | record Rational(int num, int denom) { 145 | Rational { 146 | int gcd = gcd(num, denom); // 计算最大公约数 147 | num /= gcd; // 将分子除以最大公约数 148 | denom /= gcd; // 将分母除以最大公约数 149 | } 150 | } 151 | ``` 152 | 153 | 这个声明等同于传统的构造函数形式: 154 | 155 | ```java 156 | record Rational(int num, int denom) { 157 | Rational(int num, int demon) { 158 | // 逻辑代码 159 | int gcd = gcd(num, denom); 160 | num /= gcd; 161 | denom /= gcd; 162 | // 赋值代码 163 | this.num = num; 164 | this.denom = denom; 165 | } 166 | } 167 | ``` 168 | 169 | * 记录类**语义一致性** 170 | 171 | 例如,考虑以下方式声明的记录类R: 172 | 173 | ```java 174 | record R(T1 c1, ..., Tn cn){ } 175 | ``` 176 | 177 | 如果通过以下方式复制R的实例r1: 178 | 179 | ```java 180 | R r2 = new R(r1.c1(), r1.c2(), ..., r1.cn()); 181 | ``` 182 | 183 | 则假设 r1 不是空引用,表达式 r1.equals(r2) 总是为 true。 184 | 这是因为记录类的隐式声明的 equals 方法保证了相同字段值的两个记录实例相等。 185 | 186 | > Tips: 程序员需要谨慎确保显式声明的方法维持语义一致性 187 | 188 | * 避免不良风格的代码 189 | 190 | ```java 191 | record SmallPoint(int x, int y) { 192 | public int x() { return this.x < 100 ? this.x : 100; } 193 | public int y() { return this.y < 100 ? this.y : 100; } 194 | } 195 | ``` 196 | 197 | 这是一个不良风格的代码,违反了语义一致性的原则,访问器`x()`,`y()`方法调整类实例的状态; 198 | **改进建议**, 如果需要限制坐标值的范围,应该在构造函数或其他明确的位置处理。 199 | 200 | ```java 201 | record SmallPoint(int x, int y) { 202 | // 紧凑构造函数中限制坐标值的范围 203 | public SmallPoint { 204 | this.x = Math.min(x, 100); 205 | this.y = Math.min(y, 100); 206 | } 207 | } 208 | ``` 209 | 210 | ## 发展脉络 211 | 212 | `record` 类最初在JDK 14预览版(JEP 359)提出,随后在JDK 15预览版(JEP 384)再次推出。 213 | 最终,它在JDK 16中(JEP 395)正式发布,成为Java语言的一项重要特性。 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /src/md/java-features/Java17/jep406-pattern-matching-for-switch-preview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 17 新特性:switch模式匹配(Preview) 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-30 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 406 16 | --- 17 | 18 | # Java 17 新特性:switch模式匹配(Preview) 19 | 20 | 当case标签可以有模式时,有如下四个主要的设计问题,我们一一来看: 21 | 22 | 1. 增强类型检查 23 | 2. switch表达式和语句的完整性 24 | 3. 模式变量声明的作用域 25 | 4. 处理null 26 | 27 | ## 模式匹配设计 28 | 29 | ### 增强类型检查 30 | 31 | 通过扩展`switch`模式匹配的`case`标签,现在支持除了**原始数据类型**(`char`、`byte`、`short` 或 `int`)之外, 32 | 相应的**包装类**(`Character`、`Byte`、`Short` 或 `Integer`)、`String` 以及`Enum`类型等**任何引用类型**。 33 | 34 | ```java 35 | record Point(int i, int j) {} 36 | enum Color { RED, GREEN, BLUE; } 37 | 38 | static void typeTester(Object o) { 39 | switch (o) { 40 | case null -> System.out.println("null"); 41 | case String s -> System.out.println("String"); 42 | case Color c -> System.out.println("Enum,颜色具有 " + Color.values().length + " 个值"); 43 | case Point p -> System.out.println("Record Class: " + p.toString()); 44 | case int[] ia -> System.out.println("Array,长度为" + ia.length); 45 | default -> System.out.println("其他情况"); 46 | } 47 | } 48 | ``` 49 | 50 | > **注意⚠️:要避免模式标签支配**(编译异常) 51 | > 52 | > 如果一个模式标签在switch块中被先前的模式标签支配, 或者存在多个全匹配的标签(default 和 total类型模式), 则会产生编译时错误。 53 | 54 | * 例1: 模式 `case CharSequence cs` 支配 `case String s` ,因为 String 是 CharSequence 的子类 55 | * 例2: 总模式的情况,如 `case p` 支配 `case null` 模式,因为总模式匹配所有值,包括null 56 | * 例3: 模式 `case p` 支配 `case p && e` ,因为满足第一个模式的值也满足第二个模式 57 | * 例4: 模式 `case String s` 支配了带条件的模式 `case String s && s.length() > 0` 58 | 59 | ```java 60 | switch(o) { 61 | case CharSequence cs -> 62 | System.out.println("一个长度为" + cs.length() + "的序列"); 63 | case String s -> // 编译错误 - 模式被前一个模式支配 64 | System.out.println("一个字符串:" + s); 65 | default -> { 66 | break; 67 | } 68 | } 69 | ``` 70 | 71 | ### switch表达式和语句的完整性 72 | 73 | 通常情况下,通过添加`default`标签,可以确保`switch`块的完整性。 74 | 75 | ```java 76 | static void printType(Object o) { 77 | switch (o) { 78 | case String s -> System.out.println("String"); 79 | case Integer i -> System.out.println("Integer"); 80 | default -> System.out.println("Other"); 81 | } 82 | } 83 | ``` 84 | 85 | 如果switch表达式的类型是**密封类**([JEP 409](https://openjdk.org/jeps/409)), 86 | 则类型覆盖检查会考虑密封类的permits子句,以确保switch块的完整性。 87 | 88 | 以下是一个密封接口Animal的示例,包括Dog和Cat两个允许的子类: 89 | 90 | ```java 91 | sealed interface Animal permits Dog, Cat {} 92 | class Dog implements Animal {} 93 | class Cat implements Animal {} 94 | 95 | static String getSound(Animal animal) { 96 | return switch (animal) { 97 | case Dog d -> "Woof!"; 98 | case Cat c -> "Meow!"; 99 | // no default needed! 100 | }; 101 | } 102 | ``` 103 | 104 | 在这种情况下,由于编译器知道只有Dog和Cat是可能的类型,所以可以不需要`default`标签。 105 | 同样,对于枚举类,每个常量都有一个子句,也不需要default标签。 106 | 107 | ### 模式变量声明的作用域 108 | 109 | `instanceof`([JEP 394](https://openjdk.org/jeps/394))进行**模式匹配**, 110 | **模式变量**的作用域限定在`匹配的条件表达式`和相应的`then`块中。 如果匹配失败,模式变量在`else`块中不可见。 111 | 112 | ```java 113 | static void test(Object o) { 114 | if ((o instanceof String s) && s.length() > 3) { 115 | System.out.println(s); 116 | } else { 117 | System.out.println("Not a string"); 118 | } 119 | } 120 | ``` 121 | 122 | `switch`语句的`case`标签进行**模式匹配**,有以下两条规则: 123 | 124 | 1. `->`形式:作用域包括箭头右侧的表达式、块或 throw 语句 125 | ```java 126 | static void test(Object o) { 127 | switch (o) { 128 | case Character c -> { 129 | if (c.charValue() == 7) { 130 | System.out.println("Ding!"); 131 | } 132 | System.out.println("Character"); 133 | } 134 | case Integer i -> 135 | throw new IllegalStateException("Invalid Integer argument of value " + i.intValue()); 136 | default -> { 137 | break; 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | 2. `:`形式,则其作用域包括语句组的块语句,直到遇到下一个`switch`标签或其他控制流语句 144 | 145 | ```java 146 | static void test(Object o) { 147 | switch (o) { 148 | case Character c: 149 | if (c.charValue() == 7) { 150 | System.out.print("Ding "); 151 | } 152 | if (c.charValue() == 9) { 153 | System.out.print("Tab "); 154 | } 155 | System.out.println("character"); 156 | default: 157 | System.out.println(); 158 | } 159 | } 160 | ``` 161 | 162 | ### 处理null 163 | 164 | 引入新的`null`标签,用于明确处理选择表达式为`null`的情况 165 | 166 | ```java 167 | // test(null) 不再抛出NullPointerException,而是打印 "null!" 168 | static void test(Object o) { 169 | switch (o) { 170 | case null -> System.out.println("null!"); 171 | case String s -> System.out.println("String"); 172 | default -> System.out.println("Something else"); 173 | } 174 | } 175 | ``` 176 | 177 | 由空标签产生的新标签形式, JDK 16中,`switch`块支持两种风格, 178 | 179 | 1. `:` 形式,允许`fallthrough`,多个标签通常写为`case l1: case l2:` 180 | 2. `->`形式,不允许`fallthrough`,多个标签写为`case l1, l2->` 181 | 182 | ```java 183 | // 处理 null 和 String 标签,使用 : 形式 184 | switch (o) { 185 | case null: case String s: 186 | System.out.println("String, including null"); 187 | break; 188 | // 更多的 cases... 189 | 190 | } 191 | 192 | // 结合 null case 和 default 标签,使用 -> 形式 193 | switch (o) { 194 | // 更多的 cases... 195 | case null, default -> 196 | System.out.println("The rest (including null)"); 197 | } 198 | ``` 199 | 200 | ## 保护模式和括号模式 201 | 202 | 为了增强代码的可读性并避免歧义,引入了两种新的模式匹配技术: 203 | 204 | * 保护模式 (guarded patterns),允许在模式匹配成功后添加一个布尔表达式 205 | * 括号模式 (parenthesized patterns),将模式放在括号中,避免歧义,控制顺序 206 | 207 | 在成功匹配模式后,我们经常会进一步测试匹配结果。这会导致代码变得繁琐,例如: 208 | 209 | ```java 210 | static void test(Object o) { 211 | switch (o) { 212 | case String s: 213 | if (s.length() == 1) { ... } 214 | else { ... } 215 | break; 216 | ... 217 | } 218 | } 219 | ``` 220 | 221 | 使用**保护模式**,写成`p && e`改进上面的代码,使其更加简洁 222 | 223 | ```java 224 | static void test(Object o) { 225 | switch (o) { 226 | case String s && s.length() == 1 -> ... 227 | case String s -> ... 228 | } 229 | } 230 | ``` 231 | 232 | > JDK 17中还加入了**括号模式**,以避免解析歧义。支持括号内写入`(p)` 其中p是一个模式。在JDK 21中,括号模式被移除。 233 | 234 | ## 启用预览功能 235 | 236 | Preview阶段的功能并不是默认开启的,需要在编译和运行时启用。 237 | 238 | ```shell 239 | java --enable-preview --source 17 PatternMatching.java 240 | ``` 241 | -------------------------------------------------------------------------------- /src/md/java-features/Java17/jep409-sealed-classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 17 新特性:sealed类 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-31 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 409 16 | --- 17 | 18 | # Java 17 新特性:sealed类 19 | 20 | Java 17 中引入了**密封类**(Sealed Classes),它是一种限制的类和接口, 21 | 可以控制哪些类继承或实现它,保证在编译时就能够确定类的继承关系,提高代码的可读性和可维护性。 22 | 23 | ## 密封类语法 24 | 25 | 密封类的声明使用关键字 `sealed`,并通过 `permits` 关键字声明允许继承或实现的类。 26 | 27 | ```java 28 | // 密封类 29 | public abstract sealed class 类名 extends 父类名 permits 子类名1, 子类名2, ... { 30 | // 类的成员 31 | } 32 | 33 | // 密封接口 34 | public sealed interface 接口名 extends 父接口名 permits 子类名1, 子类名2, ... { 35 | // 接口的成员 36 | } 37 | ``` 38 | 39 | 密封类对其允许的子类施加了三个约束: 40 | 41 | 1. 密封类及其允许的子类必须属于同一个模块或同一包(对于未命名模块) 42 | 2. 每个允许的子类必须直接扩展密封类 43 | 3. 每个允许的子类必须使用修饰符描述其继承关系: 44 | * `final`:表示该类不能被继承(记录类隐式声明为 `final`) 45 | * `sealed`:表示该类可以被继承,但只能被允许的子类继承 46 | * `non-sealed`:表示该类可以被继承,且可以被任意类继承 47 | 48 | ## 历史限制继承手段 49 | 50 | 对于继承能力的限制,Java 语言已经提供了以下几种手段: 51 | 52 | 1. `final`修饰类,这样类就无法被继承了 53 | 2. 构造函数声明为`private`或`package-private`,则只能在同一类或同一包中创建该类的子类 54 | 55 | ## 发展脉络 56 | 57 | 该功能经历了2个预览版本(JDK 15中的JEP 360、JDK 16中的JEP 397),最终定稿于JDK 17中的JEP 409。 58 | -------------------------------------------------------------------------------- /src/md/java-features/Java18/jep400-utf8-by-default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 18 新特性:指定UTF-8为默认字符集 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-01 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 400 16 | --- 17 | 18 | # Java 18 新特性:指定UTF-8为默认字符集 19 | 20 | 在Java 18中,将`UTF-8`指定为标准Java API 的默认字符集, 21 | 以提高Java程序在不同实现、操作系统、区域设置和配置下的一致性。 22 | 23 | ## 目标 24 | 25 | * 使Java程序在依赖默认字符集的代码上更加可预测和**可移植** 26 | * 明确标准Java API在何处使用默认字符集 27 | * 在标准Java API中统一使用UTF-8,除了控制台I/O 28 | 29 | 尽管这项工作可能会发现新的便利方法可能会使现有的API更易于使用,但这一更改并不是要弃用或删除依赖默认字符集的标准Java API。 30 | 31 | ## 动机 32 | 33 | 默认情况下,Java API 会根据**运行时环境**(操作系统、用户的区域设置等)选择默认字符集。 34 | 为了提高 Java API 的一致性并降低潜在的兼容性问题,我们建议将所有 Java API 统一使用 `UTF-8` 作为默认字符集。 35 | 尽管这一变更可能对迁移到 JDK 18 的程序产生兼容性影响,但我们提供了一个 `COMPAT` 选项,允许恢复到之前的行为,即默认字符集取决于环境。 36 | 37 | ## 描述 38 | 39 | ### 兼容性危害示例 40 | 41 | 在MacOS上以`UTF-8`编码的日语文本文件在Windows上以美英或日语区域设置读取时被损坏 42 | 43 | ```java 44 | java.io.FileReader(“hello.txt”) -> “こんにちは” (macOS) 45 | java.io.FileReader(“hello.txt”) -> “ã?“ã‚“ã?«ã?¡ã? ” (Windows (en-US)) 46 | java.io.FileReader(“hello.txt”) -> “縺ォ縺。縺ッ” (Windows (ja-JP) 47 | ``` 48 | 49 | ### 查询默认字符集 50 | 51 | 通过方法 `java.nio.charset.Charset.defaultCharset()` 可以获取默认字符集。 52 | 53 | 另外,使用以下命令可以快速查看当前 JDK 的默认字符集: 54 | 55 | ```java 56 | java -XshowSettings:properties -version 2>&1 | grep file.encoding 57 | ``` 58 | 59 | 如果想在所有 Java 版本上获取从环境中确定的字符集,可以使用以下代码: 60 | 61 | ```java 62 | // 获取native.encoding系统属性(在Java 18及更高版本上赋值) 63 | String encoding = System.getProperty("native.encoding"); 64 | // 使用三元运算符选择字符集,如果encoding不为null,则使用指定字符集,否则使用默认字符集 65 | Charset cs = (encoding != null) ? Charset.forName(encoding) : Charset.defaultCharset(); 66 | // 使用指定字符集创建 FileReader 对象,打开名为 "file.txt" 的文件 67 | var reader = new FileReader("file.txt", cs); 68 | ``` 69 | 70 | ### 兼容使用默认字符集API(迁移) 71 | 72 | 多个标准 Java API 使用默认字符集,包括: 73 | 74 | * 在 java.io 包中,InputStreamReader、FileReader、OutputStreamWriter、FileWriter 和 PrintStream 75 | 提供了构造函数,用于创建使用默认字符集进行编码或解码的读取器、写入器和打印流 76 | * 在 java.util 包中,Formatter 和 Scanner 提供了构造函数,使用默认字符集进行操作 77 | * 在 java.net 包中,URLEncoder 和 URLDecoder 提供了使用默认字符集的已弃用方法 78 | 79 | 我们将更新所有使用 Charset.defaultCharset() 进行交叉引用的标准 Java API 的规范。 80 | 这些 API 包括上述提到的 API,但不包括 System.out 和 System.err,它们的字符集将由 Console.charset() 指定。 81 | 82 | ### file.encoding 和 native.encoding 系统属性 83 | 84 | `file.encoding` 是 Java 虚拟机的系统属性,用于指定默认的字符编码 85 | 86 | ```shell 87 | java -Dfile.encoding=COMPAT # COMPAT 模式, 默认字符集取决于环境 88 | java -Dfile.encoding=UTF-8 # UTF-8 模式, 默认字符集为UTF-8 89 | ``` 90 | 91 | `native.encoding` 在Java 17 中引入,该属性提供了底层主机环境的字符编码名称 92 | 93 | Java内部使用了三个字符集相关的系统属性,它们目前未指定且不受支持。这里简要记录一下: 94 | 95 | 1. `sun.stdout.encoding` 96 | 2. `sun.stderr.encoding` 97 | 3. `sun.jnu.encoding`: 98 | 99 | > Tips:对于JDK(8-17):强烈建议开发人员使用`java -Dfile.encoding=UTF-8`指定默认字符集为UTF-8启动程序 100 | 101 | ### 源文件编码 102 | Java语言允许源代码使用`UTF-16`编码方式表达`Unicode`字符,并且这不受默认字符集UTF-8的影响。 103 | 但是,`javac`编译器会受到影响,因为它需要将源代码转换为平台默认的字符集,除非通过`-encoding`选项进行配置 104 | 105 | 如果源文件以非UTF-8编码保存并在较早的JDK上进行编译,然后在JDK 18或更高版本上重新编译,可能会导致问题。 106 | 例如,如果非UTF-8源文件中的字符串文字包含非ASCII字符,则在JDK 18或更高版本中,除非使用`-encoding`选项,否则这些文字可能会被`javac`错误解释。 107 | 108 | 在使用UTF-8作为默认字符集的JDK上编译之前,强烈建议开发人员通过在当前JDK(8-17)上使用javac -encoding UTF-8 ... 进行编译来检查字符集问题。 109 | 另外,喜欢以非UTF-8编码保存源文件的开发人员可以通过将JDK 17及更高版本上的`-encoding`选项设置为`native.encoding`系统属性的值,防止javac假定UTF-8。 110 | 111 | ### 旧版默认字符集(US-ASCII) 112 | 113 | 在JDK 17及更早版本中,名称`default`会被识别为`US-ASCII`字符集的别名。 114 | 115 | 在JDK 18中,默认字符集`UTF-8`,保留`default`作为`US-ASCII`的别名将会非常混乱,于是重新定义`default`不再是`US-ASCII`的别名。 116 | 117 | Java程序使用枚举常量StandardCharsets.US_ASCII来明确其开发人员意图,而不是向Charset.forName(...)传递字符串。 118 | 119 | 因此,在JDK 18中,`Charset.forName("default")`将抛出 UnsupportedCharsetException。 120 | 这将为开发人员提供检测到这种惯用法并迁移到US-ASCII或Charset.defaultCharset()结果的机会。 121 | -------------------------------------------------------------------------------- /src/md/java-features/Java18/jep408-simple-web-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 18 新特性:简单Web服务器 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-02 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 408 16 | --- 17 | 18 | # Java 18 新特性:简单Web服务器 19 | 20 | Java 18 引入了**简单Web服务**器(Simple Web Server),一个专为教育或非正式任务设计的最小HTTP服务器,为单个目录层次结构提供服务。 21 | 基于JDK中的`com.sun.net.httpserver`包实现,旨在简化服务器的创建和请求处理过程。 22 | 23 | 主要特点: 24 | 25 | * 不能替代成熟的商业服务器,如`Jetty`、`Nginx` 和 `Apache Tomcat`等 26 | * 不提供身份验证、访问控制或加密等安全功能 27 | * 仅支持HTTP/1.1,不支持HTTPS 28 | * 仅支持GET、HEAD请求,否则返回 501 Not Implemented 或 405 Not Allowed 29 | 30 | ## 命令行工具 31 | 32 | 为了开始使用简单Web服务器,您需要准备一个`index.html`文件,并执行以下步骤: 33 | 34 | 1. 打开终端。 35 | 2. 输入命令:`jwebserver`。 36 | 37 | ```shell 38 | $ jwebserver 39 | ``` 40 | 41 | 默认情况下,服务器将绑定到本地回环地址,并在端口8000上提供服务。看到类似以下的输出: 42 | 43 | ```shell 44 | Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::". 45 | Serving /cwd and its subdirectories on 127.0.0.1 port 8000 46 | URL: http://127.0.0.1:8000/ 47 | ``` 48 | 49 | 尝试访问一下 `http://127.0.0.1:8000/` ,就可以获得之前准备的HTML内容了。 50 | 51 | ### 支持的命令行选项 52 | 53 | ```shell 54 | 选项: 55 | -h 或 -? 或 --help 56 | 打印帮助信息并退出. 57 | 58 | -b addr 或 --bind-address addr 59 | 指定绑定的地址。默认:127.0.0.1或::1(回环地址)。要使用所有接口,请使用 -b 0.0.0.0 或 -b ::. 60 | 61 | -d dir 或 --directory dir 62 | 指定要提供服务的目录。默认:当前目录. 63 | 64 | -o level 或 --output level 65 | 指定输出格式。none | info | verbose。默认:info. 66 | 67 | -p port 或 --port port 68 | 指定要监听的端口。默认:8000. 69 | 70 | -version 或 --version 71 | 打印版本信息并退出。 72 | 73 | 要停止服务器,请按 Ctrl + C. 74 | ``` 75 | 76 | ## API编程方式 77 | 78 | 尽管命令行工具提供了便利,但为了更灵活地定制处理程序的行为,与现有代码集成,提高代码的可读性和可维护性,我们引入了新的API。 79 | 80 | > 新的API中引入了三个新的类是`SimpleFileServer`、`HttpHandlers`和`Request`, 81 | > 每个类都构建在`com.sun.net.httpserver`包中的现有类和接口上:`HttpServer`、`HttpHandler`、`Filter`和`HttpExchange`。 82 | 83 | ### 简单文件服务器(SimpleFileServer) 84 | 85 | `SimpleFileServer`支持创建文件服务器、文件服务器处理程序和输出过滤器。 86 | 87 | ```java 88 | package com.sun.net.httpserver; 89 | 90 | public final class SimpleFileServer { 91 | // 创建文件服务器实例 92 | public static 1 createFileServer( 93 | InetSocketAddress addr, Path rootDirectory, OutputLevel outputLevel) {...} 94 | 95 | // 创建文件服务器处理程序 96 | public static HttpHandler createFileHandler( 97 | Path rootDirectory) {...} 98 | 99 | // 创建输出过滤器 100 | public static Filter createOutputFilter( 101 | OutputStream out, OutputLevel outputLevel) {...} 102 | ... 103 | } 104 | ``` 105 | 106 | 有了这个类,在`jshell`中只需几行代码,就可以启动一个最小但定制的**文件服务器**: 107 | 108 | ```java 109 | jshell> var server = SimpleFileServer.createFileServer(new InetSocketAddress(8080), 110 | ...> Path.of("/some/path"), SimpleFileServer.OutputLevel.INFO); 111 | jshell> server.start() 112 | ``` 113 | 114 | 相当于命令行模式的: 115 | 116 | ```shell 117 | jwebserver -p 8080 -d /some/path -o info 118 | ``` 119 | 120 | ### 自定义处理程序和过滤器 121 | 122 | 将自定义的**文件服务器处理程序**添加到现有服务器: 123 | 124 | ```java 125 | jshell> var handler = SimpleFileServer.createFileHandler(Path.of("/some/path")); 126 | jshell> var server = HttpServer.create(new InetSocketAddress(8080), 127 | ...> 10, "/store/", new SomePutHandler()); 128 | jshell> server.createContext("/browse/", handler); 129 | jshell> server.start(); 130 | ``` 131 | 132 | 将自定义的**输出过滤器**在创建过程中添加到服务器: 133 | 134 | ```java 135 | jshell> var filter = SimpleFileServer.createOutputFilter(System.out, 136 | ...> OutputLevel.INFO); 137 | jshell> var server = HttpServer.create(new InetSocketAddress(8080), 138 | ...> 10, "/store/", new SomePutHandler(), filter); 139 | jshell> server.start(); 140 | ``` 141 | 142 | 两个例子是由`HttpServer`和`HttpsServer`类中的新重载`create`方法启用的: 143 | 144 | ```java 145 | public static HttpServer create(InetSocketAddress addr, 146 | int backlog, 147 | String root, 148 | HttpHandler handler, 149 | Filter... filters) throws IOException {...} 150 | ``` 151 | 152 | ### 增强的请求处理(HttpHandlers) 153 | 154 | 简单Web服务器的核心功能是**处理程序**。为了与现有代码兼容,我们引入了`HttpHandlers`类, 155 | 提供两个静态方法用于==创建==和==自定义处理程序==,还有`Filter`类中的新方法用于适配请求: 156 | 157 | ```java 158 | package com.sun.net.httpserver; 159 | 160 | public final class HttpHandlers { 161 | 162 | // handleOrElse方法补充条件处理程序 163 | public static HttpHandler handleOrElse(Predicate handlerTest, 164 | HttpHandler handler, 165 | HttpHandler fallbackHandler) {...} 166 | 167 | // of方法创建具有预设响应状态的处理程序 168 | public static HttpHandler of(int statusCode, Headers headers, String body) {...} 169 | {...} 170 | } 171 | ``` 172 | 173 | ```java 174 | public abstract class Filter { 175 | // adaptRequest方法获取预处理过滤器,用于在处理请求之前检查和调整请求的某些属性 176 | public static Filter adaptRequest( 177 | String description, UnaryOperator requestOperator) {...} 178 | {...} 179 | } 180 | ``` 181 | 182 | 这些方法的用例包括基于请求方法委托交换,创建总是返回特定响应的“canned response”处理程序,或向所有传入请求添加标头。 183 | 184 | ### 请求(Request) 185 | 186 | 现有API中,使用HttpExchange类来表达HTTP==请求-响应对==,描述了请求-响应交换的完整可变状态。 187 | 然而,并非所有这状态对于处理程序的定制和适配都是必要的。 188 | 因此,我们引入了更简单的Request接口,提供==请求==的不可变状态的有限视图。 189 | 190 | ```java 191 | public interface Request { 192 | URI getRequestURI(); // 获取请求的URI 193 | String getRequestMethod(); // 获取请求的方法 194 | Headers getRequestHeaders(); // 获取请求的标头 195 | // 用于修改请求头部信息 196 | default Request with(String headerName, List headerValues) 197 | {...} 198 | } 199 | ``` 200 | 201 | 这使得可以直接定制现有的处理程序,例如: 202 | 203 | ```java 204 | // 创建一个处理程序,根据请求方法选择对应的处理器,如果请求方法为 PUT,则使用 SomePutHandler,否则使用 SomeHandler 205 | var h = HttpHandlers.handleOrElse(r -> 206 | r.getRequestMethod().equals("PUT"), new SomePutHandler(), new SomeHandler()); 207 | // 创建一个过滤器,用于修改请求头部信息,在请求中添加名为 "Foo" 的头部,值为 "Bar" 208 | var f = Filter.adaptRequest("Add Foo header", r -> r.with("Foo", List.of("Bar"))); 209 | // 创建一个 HTTP 服务器,并指定端口为 8080,最大连接数为 10,根路径为 "/",处理程序为 h,过滤器为 f 210 | var s = HttpServer.create(new InetSocketAddress(8080), 10, "/", h, f); 211 | // 启动服务器 212 | s.start(); 213 | ``` 214 | 215 | ## 替代品 216 | 217 | **命令行工具**的替代方案: 218 | 219 | * 最初,使用 `java -m jdk.httpserver` 命令运行 Simple Web Server,没有专门的命令行工具 220 | * 为了提高便利性和可访问性,我们引入了一个专门的工具 `jwebserver` 221 | * 实际上 `jwebserver` 在后台使用了 `java -m ...` 命令 222 | 223 | **API编程方式**替代方案: 224 | 225 | * 新的 `DelegatingHandler` 类:捆绑定制方法在一个单独的类中,但由于引入了新类型并未添加更多功能,我们放弃了这个选项。 226 | * 将 `HttpHandler` 作为服务:将 `HttpHandler` 转换为服务,并提供内部文件服务器处理程序实现。然而,这种方法对于我们要提供的功能来说过于复杂。 227 | * 使用**过滤器**而不是 `HttpHandler`:仅使用过滤器来处理请求,但这样做不符合直觉,并且过滤器的方法会更难找到。 228 | -------------------------------------------------------------------------------- /src/md/java-features/Java18/jep413-code-snippets-in-api-documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 18 新特性:新增@snipppet标签 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-03 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 413 16 | --- 17 | 18 | # Java 18 新特性:新增@snipppet标签 19 | 20 | Java 18 引入了`@snippet`标签,用于在API文档中嵌入代码片段,以便更好地展示API的使用方法。 21 | 22 | 主要特点: 23 | 24 | * 有效性检查,代码包含语法错误时,会出现错误提示 25 | * 启用现代样式,例如语法高亮显示,以及名称与声明的自动链接 26 | * 为创建和编辑代码段提供更好的IDE支持 27 | 28 | ## 存在的@code标签 29 | 30 | 用于单独的小段代码, 当代码片段复杂时, 使用复合模式的文档注释,如下所示: 31 | 32 | ```java 33 | *
{@code
34 |  *     源代码行1
35 |  *     ...
36 |  *     源代码行n
37 |  * }
38 | ``` 39 | 40 | ## 引入@snippet标签 41 | 42 | 解决了`@code`标签的不足,允许在API文档中直接嵌入代码片段,以便更好地展示API的使用方法。 43 | 44 | ```java 45 | /** 46 | * 以下代码显示了如何使用 {@code Optional.isPresent}: 47 | * {@snippet : 48 | * if (v.isPresent()) { 49 | * System.out.println("v: " + v.get()); 50 | * } 51 | * } 52 | */ 53 | ``` 54 | 55 | 作为外部片段导入 56 | 57 | ```java 58 | /** 59 | * 以下代码显示了如何使用 {@code Optional.isPresent}: 60 | * {@snippet file="ShowOptional.java" region="example"} 61 | */ 62 | ``` 63 | 64 | 其中`ShowOptional.java`是一个包含以下内容的文件: 65 | 66 | ```java 67 | public class ShowOptional { 68 | void show(Optional v) { 69 | // @start region="example" 70 | if (v.isPresent()) { 71 | System.out.println("v: " + v.get()); 72 | } 73 | // @end 74 | } 75 | } 76 | ``` 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/md/java-features/Java19/java19-new-features-summary.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java19 新特性总结 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-04 12 | category: Java Features 13 | tag: 14 | - java 15 | --- 16 | 17 | # Java 19 新特性总结 18 | 19 | 该版本推出的均为孵化与预览功能,所以这里不做单独的详细解读,大部分内容均放在Java 21中介绍。 20 | 21 | * 422: Linux/RISC-V Port:RISC-V是一个基于精简指令集(RISC)原则的开源指令集架构(ISA),这个JEP的主旨则是移植JDK到RISC-V上。 22 | 23 | 以下预览特性在Java 21中正式定稿: 24 | 25 | * [405: Record Patterns (Preview):终稿见 440: Record Patterns](/java-features/Java21/jep440-record-partterns) 26 | * [425: Virtual Threads (Preview):终稿见 444: Virtual Threads](/java-features/Java21/jep444-virtual-threads) 27 | * [427: Pattern Matching for switch (Third Preview):终稿见 441: Pattern Matching for switch](/java-features/Java21/jep441-pattern-matching-for-switch) 28 | 29 | 以下内容在Java 21中继续迭代: 30 | 31 | * 424: Foreign Function & Memory API (Preview) 32 | * 426: Vector API (Fourth Incubator) 33 | * 428: Structured Concurrency (Incubator) 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/md/java-features/Java20/java20-new-features-summary.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java20 新特性总结 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-05 12 | category: Java Features 13 | tag: 14 | - java 15 | --- 16 | 17 | # Java 20 新特性总结 18 | 19 | 该版本推出的均为孵化与预览功能,所以这里不做单独的详细解读,大部分内容均放在Java 21中介绍。 20 | 21 | 以下内容在Java 21中正式定稿,可根据链接查看终稿内容: 22 | 23 | * [432: Record Patterns (Second Preview):终稿见440: Record Patterns](/java-features/Java21/jep440-record-partterns) 24 | * [433: Pattern Matching for switch (Fourth Preview):终稿见441: Pattern Matching for switch](/java-features/Java21/jep441-pattern-matching-for-switch) 25 | * [436: Virtual Threads (Second Preview):终稿见444: Virtual Threads](/java-features/Java21/jep444-virtual-threads) 26 | 27 | 以下内容在Java 21中继续迭代: 28 | 29 | * 429: Scoped Values (Incubator) 30 | * 434: Foreign Function & Memory API (Second Preview) 31 | * 437: Structured Concurrency (Second Incubator) 32 | * 438: Vector API (Fifth Incubator) 33 | 34 | -------------------------------------------------------------------------------- /src/md/java-features/Java21/jep430-string-templates.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 21 新特性:字符串模版(Preview) 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-06 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 430 16 | --- 17 | 18 | # Java 21 新特性:String Templates(字符串模版) 19 | 20 | Java 21 中引入了**字符串模版**(String Templates),它是一种新的字符串字面量,用于更简洁地构建字符串。 21 | 22 | ## 字符串组合的机制 23 | 24 | 在之前,Java 提供了几种字符串组合的机制,但不幸的是,它们都存在一些缺点 25 | 26 | 1. 使用 `+` 操作符, 代码难以阅读 27 | ```java 28 | String s = x + " plus " + y + " equals " + (x + y); 29 | ``` 30 | 2. 使用 `StringBuilder` 和 `StringBuffer`,代码冗长 31 | ```java 32 | String s = new StringBuilder() 33 | .append(x) 34 | .append(“plus“) 35 | .append(y) 36 | .append(“equals“) 37 | .append(x + y) 38 | .println(); 39 | ``` 40 | 3. 使用 `String::format` 和 `String::formatted`,容易出现参数数量和类型不匹配的问题 41 | ```java 42 | String s = String.format("%2$d plus %1$d equals %3$d", x, y, x + y); 43 | String t = "%2$d plus %1$d equals %3$d".formatted(x, y, x + y); 44 | ``` 45 | 4. 使用 `java.text.MessageFormat` 格式化消息,语法复杂对一些人来说可能不太熟悉 46 | ```java 47 | MessageFormat mf = new MessageFormat("{0} plus {1} equals {2}"); 48 | String s = mf.format(x, y, x + y); 49 | ``` 50 | 51 | 下面,我们将学习Java 21中的字符串模版,以及它的使用方法。 52 | 53 | ## 模版表达式(插值) 54 | 55 | 在Java 21中处理字符串的新方法称为:`Template Expressions`,即:**模版表达式**。 56 | 57 | * 优点:模版表达式可以执行**字符串插值**,插值不仅比串联更方便,而且在阅读代码时也更清晰 58 | * 缺点:但插值是危险的,尤其是对于SQL语句,因为它可能导致注入攻击 59 | 60 | ```java 61 | String name = "Joan"; 62 | String info = STR."My name is \{name}"; 63 | assert info.equals("My name is Joan"); // true 64 | ``` 65 | 66 | 上述代码中的第2行就是一个模版表达式,其中主要包含三个部分: 67 | 68 | 1. 模板处理器`STR`; 69 | 2. 一个`.`字符,类似于方法调用 70 | 3. 包含嵌入表达式(`\{name}`)的模版 71 | 72 | 运行时,计算模板表达式,模板处理器将模板中的文本与嵌入表达式的值组合在一起,以产生结果。 73 | 74 | ## STR模版处理器 75 | 76 | > STR模板处理器用于将模板中的每个==嵌入表达式==替换成==表达式的(字符串)值==来执行字符串插值 77 | 78 | * STR是一个`public static final`字段,它会自动导入到每个Java源文件中 79 | 80 | 使用STR模板处理器的模板表达式示例。符号 `|` 后显示前一条语句的值,类似于`jshell`。 81 | 82 | ```java 83 | // 嵌入式表达式可以是字符串 84 | String firstName = "Bill"; 85 | String lastName = "Duck"; 86 | String fullName = STR."\{firstName} \{lastName}"; 87 | | "Bill Duck" 88 | String sortName = STR."\{lastName}, \{firstName}"; 89 | | "Duck, Bill" 90 | 91 | // 嵌入式表达式可以执行算术运算 92 | int x = 10, y = 20; 93 | String s = STR."\{x} + \{y} = \{x + y}"; 94 | | "10 + 20 = 30" 95 | 96 | // 嵌入式表达式可以调用方法和访问字段 97 | String s = STR."You have a \{getOfferType()} waiting for you!"; 98 | | "You have a gift waiting for you!" 99 | String t = STR."Access at \{req.date} \{req.time} from \{req.ipAddress}"; 100 | | "Access at 2022-03-25 15:34 from 8.8.8.8" 101 | ``` 102 | 103 | * 为了帮助重构,嵌入式表达式中可以使用双引号字符,而无需将它们转义为`"` 104 | 105 | ```java 106 | String filePath = "tmp.dat"; 107 | File file = new File(filePath); 108 | String old = "The file " + filePath + " " + (file.exists() ? "does" : "does not") + " exist"; 109 | String msg = STR."The file \{filePath} \{file.exists() ? "does" : "does not"} exist"; 110 | | "The file tmp.dat does exist" 或 "The file tmp.dat does not exist" 111 | ``` 112 | 113 | * 为了提高可读性,在源文件中,嵌入式表达式可以跨越多行而不会引入新的换行符 114 | 115 | ```java 116 | String time = STR."The time is \{ 117 | // java.time.format包非常有用 118 | DateTimeFormatter 119 | .ofPattern("HH:mm:ss") 120 | .format(LocalTime.now()) 121 | } right now"; 122 | // "The time is 12:34:56 right now" 123 | ``` 124 | 125 | * 字符串模板表达式中嵌入表达式的数量没有限制 126 | 127 | ```java 128 | // 嵌入式表达式可以是后缀递增表达式 129 | int index = 0; 130 | String data = STR."\{index++}, \{index++}, \{index++}, \{index++}"; 131 | // "0, 1, 2, 3" 132 | ``` 133 | 134 | * 任何Java表达式都可以用作嵌入式表达式,甚至是模板表达式。例如: 135 | 136 | ```java 137 | // 嵌入式表达式是(嵌套的)模板表达式 138 | String[] fruit = { "apples", "oranges", "peaches" }; 139 | String s = STR."\{fruit[0]}, \{STR."\{fruit[1]}, \{fruit[2]}"}"; 140 | // "apples, oranges, peaches" 141 | ``` 142 | 143 | * 在这里,模板表达式 `STR."\{fruit[1]}, \{fruit[2]}"` 嵌入到另一个模板表达式的模板中。 144 | 由于存在大量的 `"` `,` `\` 和 `{ }` 字符,这段代码很难阅读,因此最好将其格式化为: 145 | 146 | ```java 147 | String s = STR."\{fruit[0]}, \{ 148 | STR."\{fruit[1]}, \{fruit[2]}" 149 | }"; 150 | ``` 151 | 152 | ## 多行模板表达式 153 | 154 | 模板表达式的模板可以跨越多行源代码,类似于Java 15中的[文本块](/java-features/Java15/jep378-text-blocks)的语法。 155 | 开发者可以用它来方便的组织`html`、`json`、`xml`等字符串内容,比如下面这样: 156 | 157 | ```java 158 | // 多行模板表达式示例:HTML文档 159 | String title = "My Web Page"; 160 | String text = "Hello, world"; 161 | String html = STR.""" 162 | 163 | 164 | \{title} 165 | 166 | 167 |

\{text}

168 | 169 | 170 | """; 171 | | 输出结果: 172 | | """ 173 | | 174 | | 175 | | My Web Page 176 | | 177 | | 178 | |

Hello, world

179 | | 180 | | 181 | | """ 182 | 183 | // 多行模板表达式示例:JSON文档 184 | String name = "Joan Smith"; 185 | String phone = "555-123-4567"; 186 | String address = "1 Maple Drive, Anytown"; 187 | String json = STR.""" 188 | { 189 | "name": "\{name}", 190 | "phone": "\{phone}", 191 | "address": "\{address}" 192 | } 193 | """; 194 | | 输出结果: 195 | | """ 196 | | { 197 | | "name": "Joan Smith", 198 | | "phone": "555-123-4567", 199 | | "address": "1 Maple Drive, Anytown" 200 | | } 201 | | """ 202 | 203 | record Rectangle(String name, double width, double height) { 204 | double area() { 205 | return width * height; 206 | } 207 | } 208 | Rectangle[] zone = new Rectangle[] { 209 | new Rectangle("Alfa", 17.8, 31.4), 210 | new Rectangle("Bravo", 9.6, 12.4), 211 | new Rectangle("Charlie", 7.1, 11.23), 212 | }; 213 | // 多行模板表达式示例:表格 214 | String table = STR.""" 215 | Description Width Height Area 216 | \{zone[0].name} \{zone[0].width} \{zone[0].height} \{zone[0].area()} 217 | \{zone[1].name} \{zone[1].width} \{zone[1].height} \{zone[1].area()} 218 | \{zone[2].name} \{zone[2].width} \{zone[2].height} \{zone[2].area()} 219 | Total \{zone[0].area() + zone[1].area() + zone[2].area()} 220 | """; 221 | | 输出结果: 222 | | """ 223 | | Description Width Height Area 224 | | Alfa 17.8 31.4 558.92 225 | | Bravo 9.6 12.4 119.03999999999999 226 | | Charlie 7.1 11.23 79.733 227 | | Total 757.693 228 | | """ 229 | ``` 230 | 231 | ## FMT模板处理器 232 | 233 | 除了STR模版处理器之外,Java中还提供了另外一个模版处理器:FMT。 234 | FMT与STR相似之处在于它执行插值,但还提供了==格式化处理==能力。 235 | 236 | * 格式说明符与`java.util.Formatter`中定义的格式说明符相同 237 | 238 | ```java 239 | record Rectangle(String name, double width, double height) { 240 | double area() { 241 | return width * height; 242 | } 243 | } 244 | Rectangle[] zone = new Rectangle[] { 245 | new Rectangle("Alfa", 17.8, 31.4), 246 | new Rectangle("Bravo", 9.6, 12.4), 247 | new Rectangle("Charlie", 7.1, 11.23), 248 | }; 249 | // 多行模板表达式示例:表格 250 | String table = FMT.""" 251 | Description Width Height Area 252 | %-12s\{zone[0].name} %7.2f\{zone[0].width} %7.2f\{zone[0].height} %7.2f\{zone[0].area()} 253 | %-12s\{zone[1].name} %7.2f\{zone[1].width} %7.2f\{zone[1].height} %7.2f\{zone[1].area()} 254 | %-12s\{zone[2].name} %7.2f\{zone[2].width} %7.2f\{zone[2].height} %7.2f\{zone[2].area()} 255 | \{" ".repeat(28)} Total %7.2f\{zone[0].area() + zone[1].area() + zone[2].area()} 256 | """; 257 | | 输出结果: 258 | | """ 259 | | Description Width Height Area 260 | | Alfa 17.80 31.40 558.92 261 | | Bravo 9.60 12.40 119.04 262 | | Charlie 7.10 11.23 79.73 263 | | Total 757.69 264 | | """ 265 | ``` 266 | -------------------------------------------------------------------------------- /src/md/java-features/Java21/jep431-sequenced-collections.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 21 新特性:有序集合 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-07 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 431 16 | --- 17 | 18 | # Java 21 新特性:有序集合(Sequenced Collections) 19 | 20 | 在JDK 21中,**有序集合**(Sequenced Collections)引入了新的接口和方法来简化集合处理。 21 | 22 | > 此增强功能旨在解决访问Java中各种集合类型的第一个和最后一个元素需要非统一且麻烦处理场景 23 | 24 | `Sequenced Collections` 引入如下 3 个新接口,用于处理顺序`List`、`Set`和`Map`, 25 | 并将它们整合到现有的集合类型中。这些新接口中的方法都具有默认实现。 26 | 27 | 1. SequencedCollection 28 | 2. SequencedSet 29 | 3. SequencedMap 30 | 31 | ## SequencedCollection 32 | 33 | 提供了在集合两端添加、检索和移除元素的方法,沿着`reversed()`方法提供了该集合的逆序视图。 34 | 35 | ```java 36 | interface SequencedCollection extends Collection { 37 | // 新方法:返回反转后的序列化集合 38 | SequencedCollection reversed(); 39 | // 以下方法是从Deque提升的,支持在两端添加、获取和删除元素 40 | void addFirst(E); 41 | void addLast(E); 42 | E getFirst(); 43 | E getLast(); 44 | E removeFirst(); 45 | E removeLast(); 46 | } 47 | ``` 48 | 49 | * 新的`reversed()`方法提供了原始集合的反向视图,对原始集合的任何修改都可以在视图中看到 50 | * 如果允许,对视图的修改将写入原始集合 51 | * 逆序视图使得不同的序列类型可以在两个方向上处理元素 52 | * 如:增强for循环、显式iterator()循环、forEach()、stream()、parallelStream() 和 toArray() 53 | 54 | 例如,从`LinkedHashSet`获得逆序流以前很难,现在很简单 55 | 56 | ```java 57 | linkedHashSet.stream().sorted(Comparator.reverseOrder()) // 获取逆序流非常困难 58 | linkedHashSet.reversed().stream() // 现在:直接使用 reversed() 方法获取逆序流 59 | ``` 60 | 61 | > `reversed()` 方法本质上是 `NavigableSet::descendingSet`的重命名,并升级为 SequencedCollection 62 | 63 | ## SequencedSet 64 | 65 | sequenced set 是一个不包含重复元素的 SequencedCollection,区别是`SequencedSet.reversed()`的返回类型是SequencedSet。 66 | 67 | ```java 68 | interface SequencedSet extends Set, SequencedCollection { 69 | // 重写父接口的 reversed() 方法 70 | SequencedSet reversed(); 71 | } 72 | ``` 73 | 74 | **SortedSet** 75 | 76 | * Java集合框架中`SortedSet`接口,表示一个有序集合 77 | * 不能支持显式定位操作,因为是基于元素之间的相对比较来确定它们位置的,而不是基于顺序插入 78 | * 如果尝试使用`addFirst(E)`或`addLast(E)`方法,会抛出`UnsupportedOperationException`异常 79 | 80 | **SequencedSet** 81 | 82 | * `SequencedSet`接口扩展了`SortedSet` 83 | * 其中`addFirst(E)`和`addLast(E)` 方法对于集合(如`LinkedHashSet`)具有特殊情况语义: 84 | * 如果元素已经存在于集合中,则将其移动到适当的位置 85 | * 这弥补了LinkedHashSet中的一个长期缺陷,即无法重新定位元素 86 | 87 | ## SequencedMap 88 | 89 | sequenced map 是一个映射,其键具有已定义的顺序, 90 | 它不实现SequencedCollection,而是提供了自己的方法,这些方法将访问顺序应用于映射条目,而不是单个元素。 91 | 92 | ```java 93 | interface SequencedMap extends Map { 94 | // 新方法 95 | SequencedMap reversed(); // 返回一个反转的映射 96 | SequencedSet sequencedKeySet(); // 返回键的序列化集合 97 | SequencedCollection sequencedValues(); // 返回值的序列化集合 98 | SequencedSet> sequencedEntrySet();// 返回条目的序列化集合 99 | V putFirst(K key, V value); // 将键值对放在映射的第一个位置 100 | V putLast(K key, V value); // 将键值对放在映射的最后一个位置 101 | // 从 NavigableMap 提升的方法,支持在映射的两端获取和移除条目 102 | Entry firstEntry(); // 返回映射的第一个条目 103 | Entry lastEntry(); // 返回映射的最后一个条目 104 | Entry pollFirstEntry(); // 移除并返回映射的第一个条目 105 | Entry pollLastEntry(); // 移除并返回映射的最后一个条目 106 | } 107 | ``` 108 | 109 | 新的`put*(K, V)`方法具有特殊的含义,类似于 SequencedSet 中对应的`add*(E)`方法: 110 | 111 | * 对于 LinkedHashMap 映射,如果已存在相同键的条目,该方法将重新定位该条目 112 | * 对于 SortedMap 映射,这些方法会抛出 `UnsupportedOperationException` 异常 113 | 114 | ## 改造 115 | 116 | 上述定义的三个新接口完美地适应了现有的集合类型层次结构(点击放大): 117 | 118 | ![Sequenced-Collections](http://img.geekyspace.cn/pictures/2024/SequencedCollectionDiagram20220216.png) 119 | 120 | 具体而言,我们对现有的类和接口进行以下调整: 121 | 122 | * List 现在将 SequencedCollection 定义为其直接超级接口 123 | * Deque 现在将 SequencedCollection 定义为其直接超级接口 124 | * LinkedHashSet 另外实现了 SequencedSet 125 | * SortedSet 现在将 SequencedSet 定义为其直接超级接口 126 | * LinkedHashMap 另外实现了 SequencedMap 127 | * SortedMap 现在将 SequencedMap 定义为其直接超级接口 128 | 129 | 我们在适当的位置为`reversed()`方法定义协变覆盖。 130 | 例如: `List::reversed` 被覆盖为返回 List 类型的值,而不是 `SequencedCollection` 类型的值。 131 | 132 | 我们还向 Collections 实用类添加了新方法,用于创建三种新类型的不可修改包装: 133 | 134 | * Collections.unmodifiableSequencedCollection(sequencedCollection) 135 | * Collections.unmodifiableSequencedSet(sequencedSet) 136 | * Collections.unmodifiableSequencedMap(sequencedMap) 137 | 138 | ## 第一个和最后一个元素的访问 139 | 140 | 引入顺序接口的动机是对获取集合的第一个和最后一个元素的简单方法的长期未决需求。 141 | 142 | 目前,在Java 21之前,JDK API调用访问第一个和最后一个元素的一些示例: 143 | 144 | | 访问位置 | List | Deque | SortedSet | 145 | |--------|-------------------------|------------------|-------------| 146 | | 第一个元素 | list.get(0) | deque.getFirst() | set.first() | 147 | | 最后一个元素 | list.get(list.size()-1) | deque.getLast() | set.last() | 148 | 149 | 可以看到,一个简单的操作,在不同的集合中需要不同的编写方式,非常麻烦! 150 | 但在JDK 21之后,访问第一个和最后一个元素就方法多了: 151 | 152 | 对于`List`, `Deque`, `Set`这些有序的集合,访问方法变得统一起来: 153 | 154 | * 第一个元素:`collection.getFirst()` 155 | * 最后一个元素:`collection.getLast()` 156 | -------------------------------------------------------------------------------- /src/md/java-features/Java21/jep439-generational-zgc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 21 新特性:分代ZGC 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-08 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 439 16 | --- 17 | 18 | # Java 21 新特性:分代ZGC(Generational ZGC) 19 | 20 | Java以其垃圾回收机制而闻名。这是它的主要优势之一,但也可能是许多头疼的根源。 21 | 22 | * Java 11([JEP 333](https://openjdk.org/jeps/333))中引入了一个可扩展的低延迟垃圾收集器,称为ZGC 23 | * Java 15([JEP 377](https://openjdk.org/jeps/377))中 ZGC 可用于生产 24 | * 现在,随着Java 21的出现,它已经发展成为一种分代GC([JEP 439](https://openjdk.org/jeps/439)) 25 | 26 | ## 垃圾收集(Garbage Collection) 27 | 28 | 在Java中,垃圾收集器负责释放堆内存,堆内存是存储Java对象的地方。 29 | 这有助于防止内存泄漏并确保有效的资源使用,否则,程序会抛出`OutOfMemoryError`异常。 30 | 31 | “[垃圾收集](https://wiki.c2.com/?GarbageCollection)”的概念本质上是**自动内存管理**, 这可能导致如下潜在的错误: 32 | 33 | 1. 需要时间来清理和重新排列内存,引入了运行时开销,超出了程序员的控制。 34 | 2. GC运行的实际点通常是不确定的,对于高吞吐量内存消耗大的应用,可能会长时间的“**GC暂停**” 35 | 3. 讽刺的是,GC的非确定性也是它的优点之一,我们不必担心内存是何时或如何释放的,它将自动发生 36 | 37 | 有三种主要的自动内存管理技术: 38 | 39 | 1. 引用计数([ReferenceCounting](https://wiki.c2.com/?ReferenceCounting)) 40 | 2. 标记和清除([MarkAndSweep](https://wiki.c2.com/?MarkAndSweep)) 41 | 3. 复制([StopAndCopy](https://wiki.c2.com/?StopAndCopy)) 42 | 43 | ## 不同语言如何管理内存 44 | 45 | * **C/C++**:手动管理内存,程序员负责分配和释放内存 46 | * **Objective-C 和 Swift**:引入了自动引用计数(ARC),但仍然需要手动释放内存 47 | * **Rust**:使用[仿射类型系统](https://en.wikipedia.org/wiki/Substructural_type_system#Affine_type_systems) 48 | 而不是GC,引入了所有权和借用,编译器在编译时检查内存安全性 49 | * **Kotlin**:与Java类似,但引入了`Kotlin/Native`,允许手动内存管理 50 | * **Java、[Python](https://devguide.python.org/internals/garbage-collector/) 51 | 、Go、[C#](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals) 52 | 、JavaScript**:自动内存管理,垃圾收集器负责释放内存 53 | 54 | ## HotSpot JVM垃圾收集器 55 | 56 | 内存管理有许多不同的方法,并且没有“最好”的方法。 57 | 即使在一种语言/运行时中,也可以有不止一种垃圾收集方法,JVM就是一个很好的例子。 58 | 59 | 与单一的GC不同,[HotSpot JVM](https://docs.oracle.com/en/java/javase/11/gctuning/available-collectors.html)有5个GC可供选择: 60 | 61 | * Garbage-First Collector(G1)(Java 9后的默认选项) 62 | * Serial Collector 63 | * Parallel Collector 64 | * ~~Concurrent Mark Sweep (CMS) Collector(Java 9中已弃用)~~ 65 | * Shenandoah GC(Java 12+) 66 | * Z Garbage Collector(Java 15中可用于生产) 67 | 68 | 此外,不要忘记还有其他的JDK实现! 69 | 70 | * [Eclipse OpenJ9](https://eclipse.dev/openj9/) 使用具有多个收集策略的分代并发GC 71 | * [GraalVM](https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/MemoryManagement/) 72 | 有 Epsilon GC,它是一个 No-Op GC,完全不进行内存清理 73 | 74 | ## 如何选择JVM GC 75 | 76 | 许多语言只提供了一种垃圾收集方法,而Java之所以提供多种GC选项,取决于您的应用程序对于“全局停顿”事件和总体暂停时间的容忍程度。 77 | 78 | GC算法主要关注三个指标: 79 | 80 | 1. **吞吐量**:应用程序的运行时间与GC时间的比率 81 | 2. **延迟**:GC暂停时间 82 | 3. **内存占用**:GC对堆内存的使用 83 | 84 | 与许多问题一样,您无法为所有这些问题进行优化,因此每个GC都需要在它们之间找到平衡点。以下是一些场景及其匹配的GC作为起点: 85 | 86 | | 垃圾收集器 | 场景 | 87 | |-------------|------------------------------------------------| 88 | | Serial | 小数据集 (最大~100 MB)
资源有限 (例如单核)
暂停时间短 | 89 | | Parallel | 多核系统上的峰值性能
非常适合高计算负载
暂停时间 > 1秒是可以接受的 | 90 | | G1
CMS | 响应时间 > 吞吐量
堆内存较大
暂停时间 < 1秒 | 91 | | Shenandoah | 尽量减少暂停时间
可预测的延迟 | 92 | | ZGC | 响应时间是高优先级的,和/或
非常大的堆内存 | 93 | | Epsilon GC | 性能测试和故障排除 | 94 | 95 | 每种方法都有自己的优点和缺点,这在很大程度上取决于应用程序的需求和可用资源。 96 | 97 | ## 分代ZGC 98 | 99 | Java 11引入的实验性功能**ZGC**是一种**非分代**的垃圾收集方法。 100 | 尽管如此,它在**GC暂停**时间方面仍然带来了显著改进,至少在资源足够的情况下,可以比并发线程消耗内存更快地回收内存。 101 | 缺点是,它将所有对象存储在一起,而不考虑年龄,因此每个周期都会收集所有对象。 102 | 103 | [generational hypothesis](https://www.memorymanagement.org/glossary/g.html#generational.hypothesis) 104 | 观察到==年轻对象==比==年长对象==更有可能“**早逝**”,于是产生了分代假设。 105 | 106 | Java 21 中,**分代 ZGC** 将堆分为两个逻辑代:一个用于最近分配的对象,另一个用于长期存活对象。 107 | GC 可以专注于更频繁地收集**年轻**(最近分配)且更有**前途**(可能长期存在)的对象,而不会增加GC暂停时间,将其保持在 1 毫秒以下。 108 | 109 | **分代ZGC**与非分代ZGC相比的关键优势: 110 | 111 | * 减少分配停滞的风险 112 | * 降低堆内存开销要求 113 | * 减少垃圾回收CPU开销 114 | 115 | 此外,目标是在保留非分代方法已有优势的基础上实现这些优势: 116 | 117 | * 暂停时间保持在在 1 毫秒以下 118 | * 支持多达数万TB的堆大小 119 | * 最少的手动配置 120 | 121 | 为了保持最后一点,新的GC不需要手动配置代的大小、GC使用的线程数,或对象在年轻代中停留的时间。 122 | 123 | ## 如何使用 JVM GC 124 | 125 | 在Java 21中,分代ZGC是默认的垃圾收集器。为了顺利过渡,分代ZGC将与非分代ZGC一起提供,您可以通过以下方式进行配置: 126 | 127 | ```shell 128 | # 启用ZGC(默认为非分代) 129 | $ java -XX:+UseZGC 130 | 131 | # 使用分代ZGC 132 | $ java -XX:+UseZGC -XX:+ZGenerational 133 | ``` 134 | 135 | 如果您需要关闭分代ZGC,可以通过将加号(+)替换为减号(-)来实现: 136 | 137 | ```shell 138 | # 不使用分代ZGC 139 | $ java -XX:+UseZGC -XX:-ZGenerational 140 | ``` 141 | 142 | 计划在更晚的版本中完全删除非分代ZGC。 143 | -------------------------------------------------------------------------------- /src/md/java-features/Java21/jep440-record-partterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 21 新特性:记录模式 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-09 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 440 16 | --- 17 | 18 | # Java 21 新特性:记录模式(Record Patterns) 19 | 20 | Java 21 中的**记录模式**(Record Patterns)是对模式匹配的扩展,它允许在模式匹配中使用**记录**(Records)类型。 21 | 同时,记录模式还支持嵌套,可以实现更复杂的数据查询和处理。 22 | 23 | ## 仅仅是类型匹配 24 | 25 | 到目前为止,Java中的模式匹配主要局限于匹配类型:[instanceof类型匹配](/java-features/Java16/jep394-pattern-matching-for-instanceof) 26 | 27 | ```java 28 | // Java 16 之前 29 | if (obj instanceof String) { 30 | String str = (String) obj; 31 | System.out.println(str); 32 | } 33 | 34 | // JAVA 16+ 35 | if (obj instanceof String str) { 36 | System.out.println(str); 37 | } 38 | ``` 39 | 40 | Java 41 | 21扩展了这个概念,使其可用于switch语句和表达式: [switch的模式匹配](/java-features/Java21/jep441-pattern-matching-for-switch) 42 | 43 | ```java 44 | // JAVA 21之前 45 | static String asStringValue(Object anyValue) { 46 | String result = null; 47 | if (anyValue instanceof String str) { 48 | result = str; 49 | } else if (anyValue instanceof BigDecimal bd) { 50 | result = bd.toEngineeringString(); 51 | } else if (anyValue instance Integer i) { 52 | result = Integer.toString(i); 53 | } else { 54 | result = "n/a"; 55 | } 56 | return result; 57 | } 58 | 59 | // JAVA 21+ 60 | static String asStringValue(Object anyValue) { 61 | return switch (anyValue) { 62 | case String str -> str; 63 | case BigDecimal bd -> bd.toEngineeringString(); 64 | case Integer i -> Integer.toString(i); 65 | default -> "n/a"; 66 | }; 67 | } 68 | ``` 69 | 70 | 代码比之前更加简洁,同时也更加易读。但是,这种模式匹配仍然局限于类型匹配。 71 | 72 | ## record模式 73 | 74 | 当我们将模式匹配与记录类型结合使用时,我们称之为**记录模式**。这意味着我们可以在模式匹配中使用记录类型,以及记录类型的属性。 75 | 76 | ```java 77 | record Point(int x, int y) {} 78 | 79 | // Java 16 之前 80 | static void printSum(Object obj) { 81 | if (obj instanceof Point p) { 82 | int x = p.x(); 83 | int y = p.y(); 84 | System.out.println(x+y); 85 | } 86 | } 87 | 88 | // JAVA 21+ 89 | static void printSum(Object obj) { 90 | if (obj instanceof Point(int x, int y)) { 91 | System.out.println(x+y); 92 | } 93 | } 94 | ``` 95 | 96 | 其中`Point(int x, int y)`就是记录模式,它匹配`Point`类型的对象,将记录的实例(obj)分解到它的组件(`x`和`y`)。 97 | 98 | ## 嵌套record的解构 99 | 100 | 假设我们设计了一个记录,表示一个矩形,其中包含左上角和右下角的颜色点。 101 | 如果我们想要获取左上角点的颜色,我们可以这样写: 102 | 103 | ```java 104 | record Point(int x, int y) {} 105 | enum Color { RED, GREEN, BLUE } 106 | record ColoredPoint(Point p, Color c) {} 107 | record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} 108 | 109 | Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1), 110 | new ColoredPoint(new Point(x2, y2), c2)); 111 | 112 | // Java 16 之前 113 | static void printUpperLeftColoredPoint(Rectangle r) { 114 | if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { 115 | System.out.println(ul.c()); 116 | } 117 | } 118 | 119 | // JAVA 21+ 120 | static void printUpperLeftColoredPoint(Rectangle r) { 121 | if (r instanceof Rectangle(ColoredPoint(Point ul, Color c), ColoredPoint lr)) { 122 | System.out.println(c); 123 | } 124 | } 125 | ``` 126 | 127 | 嵌套模式允许我们使用与将其组合的代码一样清晰简洁的代码来拆解聚合。 128 | 129 | ## 发展脉络 130 | 131 | 该功能最初作为预览功能在Java 19(JEP 405)中首次亮相,随后经过Java 20(JEP 432)的迭代,最终在Java 21中定稿(JEP 440)。 132 | 此功能与模式匹配的switch语句(JEP 441)共同演进,并且它们之间存在相当大的互动。 133 | -------------------------------------------------------------------------------- /src/md/java-features/Java21/jep441-pattern-matching-for-switch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 21 新特性:switch模式匹配 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-10 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 441 16 | --- 17 | 18 | # Java 21 新特性:switch模式匹配 19 | 20 | Java 21 引入了 switch 模式匹配功能,它增强了 switch 语句的功能,允许使用更简洁的语法来执行类型检查和数据提取。 21 | 该功能与[记录模式(JEP 440)](/java-features/Java21/jep440-record-partterns)共同发展,并与之有相当大的互动。 22 | 23 | 24 | ## switch + instanceof 25 | 26 | 与if条件中的`instanceof`一样,`switch case`现在可以对其值进行类型检查,并创建一个case作用域变量: 27 | 28 | ```java 29 | static String asStringValue(Object anyValue) { 30 | return switch (anyValue) { 31 | case String str -> str; 32 | case JSONObject json -> json.toCompactString(); 33 | case BigDecimal bd -> bd.toEngineeringString(); 34 | case Integer i -> Integer.toString(i); 35 | case LocalDate ld -> ld.format(DateTimeFormatter.ISO_LOCAL_DATE); 36 | default -> "n/a"; 37 | }; 38 | } 39 | ``` 40 | 41 | ## switch + null 42 | 43 | ```java 44 | static String asStringValue(Object anyValue) { 45 | return switch (anyValue) { 46 | case null -> "n/a"; 47 | case String str -> str; 48 | ... 49 | }; 50 | } 51 | ``` 52 | 53 | ## switch + enum 54 | 55 | ```java 56 | sealed interface CardClassification permits Suit, Tarot {} 57 | public enum Suit implements CardClassification { CLUBS, DIAMONDS, HEARTS, SPADES } 58 | final class Tarot implements CardClassification {} 59 | 60 | static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) { 61 | switch (c) { 62 | case CLUBS -> System.out.println("梅花"); 63 | case DIAMONDS -> System.out.println("方块"); 64 | case HEARTS -> System.out.println("红桃"); 65 | case SPADES -> System.out.println("黑桃"); 66 | case Tarot t -> System.out.println("塔罗牌"); 67 | default -> System.out.println("未知的卡片类型"); 68 | } 69 | } 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /src/md/java-features/Java21/jep444-virtual-threads.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 21 新特性:虚拟线程 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-01-11 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 444 16 | --- 17 | 18 | # Java 21 新特性:虚拟线程(Virtual Threads) 19 | 20 | Java 21 引入了**虚拟线程**(Virtual Threads)功能,类似于Go语言中的`Goroutines`。 21 | 虚拟线程是一种轻量级的线程,它可以极大地减少了编写、维护和管理高吞吐量并发应用程序所需的工作量。 22 | 23 | Java平台目前为止有两种类型的线程:**传统线程**,也称为==平台线程==,和**虚拟线程**。 24 | 25 | ## 平台线程 26 | 27 | 在引入虚拟线程之前,我们所使用的线程`java.lang.Thread`是由所谓的平台线程支持的。 28 | 29 | 这些线程通常是 1:1 映射到操作系统线程的,因此它们是重量级的,创建和销毁线程的开销很大。 30 | 且每个请求都需要一个独立的线程,这会导致线程资源的快速耗尽,从而限制了应用程序的可伸缩性。 31 | 32 | ### 创建平台线程 33 | 34 | ```java 35 | Thread thread = new Thread(() -> { 36 | // 由平台线程执行的代码 37 | }).start(); 38 | ``` 39 | 40 | 随着[Project Loom](https://openjdk.org/projects/loom/)简化了新的并发方法,它还提供了一种新的方法来创建平台支持的线程: 41 | 42 | ```java 43 | Thread thread = Thread.ofPlatform() 44 | .start(runnable); 45 | 46 | // 或者 47 | Thread thread = Thread.ofPlatform(). 48 | .daemon() 49 | .name("platform-thread") 50 | .unstarted(runnable); 51 | ``` 52 | 53 | ## 虚拟线程 54 | 55 | 虚拟线程是JDK提供的**轻量级线程**实现,可以在同一个OS线程上运行许多虚拟线程。 56 | 虚拟线程为平台线程提供了一种更有效的替代方案,允许开发人员以显著降低的开销处理大量任务。 57 | 这些线程提供了与现有Java代码的兼容性和无缝迁移路径,从而从增强的性能和资源利用率中获益。 58 | 59 | 许多语言中都有某种形式的轻量级线程: 60 | 61 | * Go语言的[Goroutines](https://go.dev/tour/concurrency/1) 62 | * Erlang的[Erlang Processes](https://www.erlang.org/docs/23/efficiency_guide/processes.html) 63 | * Haskell的[Haskell Threads](https://wiki.haskell.org/Lightweight_concurrency) 64 | * 等等 65 | 66 | ### 创建虚拟线程 67 | 68 | 1. 使用`Thread.startVirtualThread()`方法创建虚拟线程: 69 | 70 | ```java 71 | // 使用静态构建器方法 72 | Thread.startVirtualThread(() -> { 73 | // 由虚拟线程执行的代码 74 | }); 75 | ``` 76 | 77 | 也可以使用`Thread.ofVirtual()`来创建,这里还可以设置一些属性,比如:线程名称等。具体如下代码: 78 | 79 | ```java 80 | Thread virtualThread = Thread.ofVirtual() 81 | .name("virtual-thread") 82 | .start(runnable); 83 | ``` 84 | 85 | 2. 使用`ExecutorService`创建虚拟线程: 86 | 87 | 从Java 5开始,就推荐开发人员使用`ExecutorServices`而不是直接使用`Thread`类了。 88 | 现在,Java 21中引入了使用虚拟线程,所以也有了新的ExecutorService来适配,看看下面的例子: 89 | 90 | ```java 91 | try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { 92 | IntStream.range(0, 10_000).forEach(i -> { 93 | executor.submit(() -> { 94 | Thread.sleep(Duration.ofSeconds(1)); 95 | return i; 96 | }); 97 | }); 98 | } // executor.close() 被隐式调用, 然后 waits 99 | ``` 100 | 101 | 3.使用`ThreadFactory`创建虚拟线程: 102 | 103 | 开发者还可以创建一个生成虚拟线程的工厂来管理,具体看下面的例子例子: 104 | 105 | ```java 106 | ThreadFactory virtualThreadFactory = Thread.ofVirtual() 107 | .name("virtual-thread", 0) 108 | .factory(); 109 | 110 | Thread factoryThread = virtualThreadFactory.newThread(() -> { 111 | // 由虚拟线程执行的代码 112 | }); 113 | factoryThread.start(); 114 | ``` 115 | 116 | 这段代码创建了一个虚拟线程工厂,每个虚拟线程都会以`virtual-thread`为前缀、以数字结尾(从0开始累加)的名称。 117 | 118 | ## 虚拟线程如何工作 119 | 120 | 虚拟线程是一个新的轻量级`java.lang.Thread`变体,由JVM的[Project Loom](https://openjdk.org/projects/loom/)项目实现的。 121 | 它使用了一种称为`Continuation`的技术,不受操作系统的管理或调度。相反,JVM负责调度。 122 | 123 | ![Java中虚拟线程的结构](https://img.geekyspace.cn/pictures/2024/202403141847457.jpg) 124 | 125 | 应用程序实例化虚拟线程,而 JVM 分配计算资源来处理它们。 126 | 与传统线程相对比,传统线程直接映射到操作系统(OS)进程。 127 | 传统线程中,应用程序代码负责提供和释放 OS 资源。 128 | 而虚拟线程中,应用程序实例化虚拟线程,从而表达并发需求。 129 | 但实际上是 JVM 从操作系统获取并释放资源。 130 | 131 | ![JVM/OS线程管理](https://img.geekyspace.cn/pictures/2024/202403141846557.webp) 132 | 133 | 所需的平台线程在 FIFO 工作窃取 134 | [ForkJoinPool](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ForkJoinPool.html) 135 | 中进行管理,默认情况下使用所有可用处理器, 136 | 但可以通过调整系统属性 `jdk.virtualThreadScheduler.parallelism` 来根据您的需求进行修改。 137 | 您熟悉的 `ForkJoinPool` 和其他功能(如并行流)使用的公共池的主要区别在于,公共池以 LIFO 模式运行。 138 | 139 | -------------------------------------------------------------------------------- /src/md/java-features/Java9/jep222-jshell.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 9 新特性:交互式编程环境JShell 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-21 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 222 16 | --- 17 | 18 | # Java 9 新特性:交互式编程环境JShell 19 | 20 | JShell 是 Java 9 引入的一个**交互式编程环境**,它是 Java 编程语言的 REPL(Read-Eval-Print Loop)实现。 21 | REPL 是一种编程环境,允许用户输入表达式并立即看到结果,而无需事先编写和编译完整的程序。 22 | JShell 的目标是提供一个轻量级、灵活且易于使用的工具,使得 Java 开发者能够更直观地编写和测试代码。 23 | 24 | ## JShell快速入门 25 | 26 | ### 启动JShell 27 | 28 | 打开终端,然后执行命令:`jshell`,执行效果如下: 29 | 30 | ```java 31 | ➜ ~ jshell 32 | | 欢迎使用 JShell -- 版本 9 33 | | 要大致了解该版本, 请键入: /help intro 34 | 35 | jshell> 36 | ``` 37 | 38 | ### 帮助介绍 /help intro 39 | 40 | 执行 `/help intro` 命令以获取有关 JShell 工具的简要介绍,**intro** 是主题,提供了关于 jshell 工具的核心概念和使用方法的信息。 41 | 42 | ``` 43 | jshell> /help intro 44 | | 45 | | intro 46 | | ===== 47 | | 48 | | 使用 jshell 工具可以执行 Java 代码,从而立即获取结果。 49 | | 您可以输入 Java 定义(变量、方法、类等等),例如:int x = 8 50 | | 或 Java 表达式,例如:x + x 51 | | 或 Java 语句或导入。 52 | | 这些小块的 Java 代码称为“片段”。 53 | | 54 | | 这些 jshell 工具命令还可以让您了解和 55 | | 控制您正在执行的操作,例如:/list 56 | | 57 | | 有关命令的列表,请执行:/help 58 | 59 | jshell> 60 | ``` 61 | 62 | ### 定义变量、方法、类 63 | 64 | ```java 65 | // 定义变量 66 | jshell> int x = 8 67 | x ==> 8 68 | 69 | // 定义方法 70 | jshell> int square(int num) { 71 | ...> return num * num; 72 | ...> } 73 | | 已创建 方法 square(int) 74 | 75 | // 定义类 76 | jshell> public class Message{ 77 | ...> private String msg; 78 | ...> public Message(String msg){ 79 | ...> this.msg = msg; 80 | ...> } 81 | ...> public String getMessage(){ 82 | ...> return msg; 83 | ...> } 84 | ...> } 85 | | 已创建 类 Message 86 | 87 | jshell> 88 | ``` 89 | 90 | ### 执行表达式、调用方法 91 | 92 | ```java 93 | // 执行 Java 表达式 94 | jshell> x + x 95 | $4 ==> 16 96 | 97 | // 调用方法 98 | jshell> square(5) 99 | $5 ==> 25 100 | 101 | 102 | // 创建类实例并调用方法 103 | jshell> Message messageObj = new Message("Hello, JShell!"); 104 | messageObj ==> Message@6d4b1c02 105 | 106 | jshell> messageObj.getMessage() 107 | 108 | $7 ==> "Hello, JShell!" 109 | ``` 110 | 111 | ## 查看定义的变量:/vars 112 | 113 | ```java 114 | jshell> /vars 115 | | int x = 8 116 | | int $4 = 16 117 | | int $5 = 25 118 | | Message messageObj = Message@6d4b1c02 119 | | String $7 = "Hello, JShell!" 120 | ``` 121 | 122 | ## 查看定义的方法:/methods 123 | 124 | ```java 125 | jshell> /methods 126 | | int square(int) 127 | ``` 128 | 129 | ## 查看定义的类:/types 130 | 131 | ```java 132 | jshell> /types 133 | | class Message 134 | 135 | ``` 136 | 137 | ## 列出输入源条目:/list 138 | 139 | 执行后,可以看到之前在`jshell`中输入的内容清单: 140 | 141 | ```java 142 | jshell> /list 143 | 144 | 1 : int x = 8; 145 | 2 : int square(int num) { 146 | return num * num; 147 | } 148 | 3 : public class Message{ 149 | private String msg; 150 | public Message(String msg){ 151 | this.msg = msg; 152 | } 153 | public String getMessage(){ 154 | return msg; 155 | } 156 | } 157 | 4 : x + x 158 | 5 : square(5) 159 | 6 : Message messageObj = new Message("Hello, JShell!"); 160 | 7 : messageObj.getMessage() 161 | 162 | jshell> 163 | ``` 164 | 165 | ## 编辑源条目:/edit 166 | 167 | 上面通过`/list`列出了输入的条目信息,下面试试通过`/edit`编辑下,比如: 168 | 169 | ```java 170 | jshell> /edit 2 171 | ``` 172 | 173 | 这将打开编辑器,修改先前定义的 `square` 方法。 174 | 175 | 修改完成后,点击 **accept** 即可 176 | 177 | ## 删除源条目:/drop 178 | 179 | 使用 `/drop` 命令可以删除之前输入的源代码块。可以通过指定**名称**或 **ID** 删除特定的源代码块。例如: 180 | 181 | ```java 182 | jshell> /drop Message 183 | | 已删除 类 Message 184 | ``` 185 | 186 | 这将删除之前定义的 `Message` 类。 187 | 188 | ## 保存文件:/save 189 | 190 | 通过 `/save` 命令,您可以将 JShell 中的源代码保存到文件中,以便将其保留或与他人共享。例如: 191 | 192 | ```java 193 | jshell> /save myCode.java 194 | ``` 195 | 196 | 这将把当前所有的源代码保存到一个名为 `myCode.java` 的文件中。 197 | 198 | ## 打开文件:/open 199 | 200 | 使用 `/open` 命令可以将文件的内容导入到 JShell 中,以便重新使用或修改。例如: 201 | 202 | ```java 203 | jshell> /open myCode.java 204 | ``` 205 | 206 | 这将导入之前保存的 `myCode.java` 文件中的源代码。 207 | 208 | ## 重置jshell:/reset 209 | 210 | 使用 `/reset` 命令可以清空 JShell 的状态,包括所有定义的变量、方法和类。例如: 211 | 212 | ```java 213 | jshell> /reset 214 | | 正在重置状态 215 | ``` 216 | 217 | 这将重置 JShell 并清除所有之前定义的内容。 218 | 219 | ## 查看引入的包:/imports 220 | 221 | 使用 `/imports` 命令可以查看当前已经导入的包。这对于确保您在 JShell 中能够访问所需的类和方法非常有用。例如: 222 | 223 | ```java 224 | jshell> /imports 225 | | import java.util.* 226 | ``` 227 | 228 | 这表明已经导入了 `java.util` 包。 229 | 230 | ## 退出jshell:/exit 231 | 232 | 使用 `/exit` 命令可以退出 JShell。如果需要,在命令后可以添加一个整数表达式片段作为退出代码。例如: 233 | 234 | ```java 235 | jshell> /exit 0 236 | ``` 237 | 238 | 这将以退出代码 0 退出 JShell。 239 | 240 | ## 查看命令:/help 241 | 242 | 最后,使用 `/help` 命令可以随时查看 JShell 的帮助信息,了解各种命令和主题的使用方法。例如: 243 | 244 | ```java 245 | jshell> /help 246 | | 键入 Java 语言表达式, 语句或声明。 247 | | 或者键入以下命令之一: 248 | | /list [<名称或 id>|-all|-start] 249 | | 列出您键入的源 250 | | /edit <名称或 id> 251 | | 编辑源条目 252 | | /drop <名称或 id> 253 | | 删除源条目 254 | | /save [-all|-history|-start] <文件> 255 | | 将片段源保存到文件 256 | | /open 257 | | 打开文件作为源输入 258 | | /vars [<名称或 id>|-all|-start] 259 | | 列出已声明变量及其值 260 | | /methods [<名称或 id>|-all|-start] 261 | | 列出已声明方法及其签名 262 | | /types [<名称或 id>|-all|-start] 263 | | 列出类型声明 264 | | /imports 265 | | 列出导入的项 266 | | /exit [] 267 | | 退出 jshell 工具 268 | | /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ... 269 | | 查看或更改评估上下文 270 | | /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]... 271 | | 重置 jshell 工具 272 | | /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]... 273 | | 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore) 274 | | /history [-all] 275 | | 您键入的内容的历史记录 276 | | /help [|] 277 | | 获取有关使用 jshell 工具的信息 278 | | /set editor|start|feedback|mode|prompt|truncation|format ... 279 | | 设置配置信息 280 | | /? [|] 281 | | 获取有关使用 jshell 工具的信息 282 | | /! 283 | | 重新运行上一个片段 -- 请参阅 /help rerun 284 | | / 285 | | 按 ID 或 ID 范围重新运行片段 -- 参见 /help rerun 286 | | /- 287 | | 重新运行以前的第 n 个片段 -- 请参阅 /help rerun 288 | | 289 | | 有关详细信息, 请键入 '/help', 后跟 290 | | 命令或主题的名称。 291 | | 例如 '/help /list' 或 '/help intro'。主题: 292 | | 293 | | intro 294 | | jshell 工具的简介 295 | | keys 296 | | 类似 readline 的输入编辑的说明 297 | | id 298 | | 片段 ID 以及如何使用它们的说明 299 | | shortcuts 300 | | 片段和命令输入提示, 信息访问以及 301 | | 自动代码生成的按键说明 302 | | context 303 | | /env /reload 和 /reset 的评估上下文选项的说明 304 | | rerun 305 | | 重新评估以前输入片段的方法的说明 306 | 307 | jshell> 308 | ``` 309 | 310 | 这将显示 JShell 的主要帮助信息。 311 | -------------------------------------------------------------------------------- /src/md/java-features/Java9/jep269-convenience-factory-methods-for-collections.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 9 新特性:不可变集合的快捷创建方法 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2023-12-22 12 | category: Java Features 13 | tag: 14 | - java 15 | order: 266 16 | --- 17 | 18 | # Java 9 新特性:不可变集合的快捷创建方法 19 | 20 | Java 9 引入了一项令人期待的新特性,即**集合的便利工厂方法**(Convenience Factory Methods for Collections),旨在使不可变集合的创建更加简单和便捷。 21 | 在此之前,我们通常使用构造方法来初始化集合,而Java 9为我们提供了一些全新的静态工厂方法,使得创建不可变集合的过程更为优雅。 22 | 23 | ## Java 9的集合创建方式 24 | 25 | Java 9引入了一些便利的工厂方法,使得创建和初始化集合对象变得更加简洁和方便。 26 | 这些改进包括List.of()、Set.of()和Map.of()等方法,用于创建不可变的集合对象。 27 | 28 | ```java 29 | // 创建不可变列表 30 | List immutableList = List.of("item1", "item2", "item3"); 31 | 32 | // 创建不可变集合 33 | Set immutableSet = Set.of("item1", "item2", "item3"); 34 | 35 | // 创建不可变映射 36 | Map immutableMap = Map.of("a", 1, "b", 2, "c", 3); 37 | ``` 38 | 39 | 这样一行代码就完成了整个集合的创建和初始化过程,使得代码更加简洁、清晰,并且具有更高的可读性。 40 | 41 | ## Java 8的集合创建方式 42 | 43 | Java 8引入了Lambda表达式和流式操作,这使得集合的初始化过程变得更加流畅和具有函数式编程的特性。 44 | 45 | ```java 46 | // 创建不可变列表 47 | List immutableList = Collections.unmodifiableList( 48 | Stream.of("item1", "item2", "item3").collect(Collectors.toList()) 49 | ); 50 | 51 | // 创建不可变集合 52 | Set immutableSet = Collections.unmodifiableSet( 53 | Stream.of("item1", "item2", "item3").collect(Collectors.toSet()) 54 | ); 55 | 56 | // 创建不可变映射 57 | Map immutableMap = Collections.unmodifiableMap( 58 | Stream.of(new String[][]{{"a", "1"}, {"b", "2"}, {"c", "3"}}) 59 | .collect(Collectors.toMap(data -> data[0], data -> Integer.parseInt(data[1]))) 60 | ); 61 | ``` 62 | 63 | 虽然相较于传统方式,Java 8的写法更为紧凑,但仍显得略显繁琐。 64 | 65 | ## 传统的集合创建方式 66 | 67 | ```java 68 | // 创建不可变列表 69 | List traditionalList = new ArrayList<>(); 70 | traditionalList.add("item1"); 71 | traditionalList.add("item2"); 72 | traditionalList.add("item3"); 73 | traditionalList = Collections.unmodifiableList(traditionalList); 74 | 75 | // 创建不可变集合 76 | Set traditionalSet = new HashSet<>(); 77 | traditionalSet.add("item1"); 78 | traditionalSet.add("item2"); 79 | traditionalSet.add("item3"); 80 | traditionalSet = Collections.unmodifiableSet(traditionalSet); 81 | 82 | // 创建不可变映射 83 | Map traditionalMap = new HashMap<>(); 84 | traditionalMap.put("a", 1); 85 | traditionalMap.put("b", 2); 86 | traditionalMap.put("c", 3); 87 | traditionalMap = Collections.unmodifiableMap(traditionalMap); 88 | ``` 89 | 90 | 这种方式繁琐且不够直观,给代码的可读性和编写效率带来了一定的挑战。 91 | 92 | ## List.of() vs. Arrays.asList() 93 | 94 | * **可变性:**`List.of` 创建的是不可变集合,`Arrays.asList` 是可变集合 95 | 96 | ```java 97 | // List.of 创建建的列表是不可变的 98 | List immutableList = List.of(1, 2, 3); 99 | // 无法添加、删除或修改元素,以下操作会导致 UnsupportedOperationException 100 | immutableList.add(4); 101 | immutableList.set(0, 0); 102 | 103 | // Arrays.asList() 创建的列表是可变的 104 | List mutableList = Arrays.asList(1, 2, 3); 105 | // 可以使用 add()、set() 方法修改元素,但不允许改变列表的大小 106 | mutableList.add(4); 107 | mutableList.set(0, 0); 108 | ``` 109 | 110 | * **null元素:**`List.of` 不允许包含 null 元素,`Arrays.asList` 允许包含 null 元素,但不推荐 111 | 112 | ```java 113 | List listWithNull = Arrays.asList(1, null, 3); 114 | ``` 115 | 116 | * **底层数据结构:**`List.of `使用不可变数据结构,`Arrays.asList`底层使用数组,对列表修改将反映在原始数组上 117 | 118 | ```java 119 | List immutableList = List.of(1, 2, 3); 120 | List mutableList = Arrays.asList(1, 2, 3); 121 | ``` 122 | -------------------------------------------------------------------------------- /src/md/jvm/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 深入理解Java虚拟机 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: true # 置顶特定文章 10 | star: true # 收藏star标记 11 | date: 2024-08-08 12 | category: JVM 13 | tag: 14 | - jvm 15 | --- 16 | 17 | # 深入理解Java虚拟机目录 18 | 19 | ## 前言(Preface) 20 | 21 | ## 致谢(Acknowledgements) 22 | 23 | ## 第一部分 走近Java(Part 1: Approaching Java) 24 | 25 | - **第1章 走近Java(Chapter 1: Approaching Java)** 26 | - 1.1 概述(Overview) 27 | - 1.2 Java技术体系(Java Technology System) 28 | - 1.3 Java发展史(History of Java) 29 | - [1.4 Java虚拟机家族(Java Virtual Machine Family)](part1/overview.html#java虚拟机家族) 30 | - 1.5 展望Java技术的未来(Future of Java Technology) 31 | - [1.6 实战:自己编译JDK(Practical: Compiling JDK)](part1/compile_jdk) 32 | 33 | ## 第二部分 自动内存管理(Part 2: Automatic Memory Management) 34 | 35 | - **第2章 Java内存区域与内存溢出异常(Chapter 2: Java Memory Areas and OutOfMemoryError)** 36 | - 2.1 概述(Overview) 37 | - [2.2 运行时数据区域(Runtime Data Areas)](part2/runtime-data-areas) 38 | - [2.3 HotSpot虚拟机对象探秘(HotSpot Virtual Machine Object Exploration)](part2/heap-object-flow) 39 | - 2.4 实战:OutOfMemoryError异常(Practical: OutOfMemoryError Exception) 40 | 41 | - **第3章 垃圾收集器与内存分配策略(Chapter 3: Garbage Collectors and Memory Allocation Strategies)** 42 | - 3.1 概述(Overview) 43 | - 3.2 对象已死吗?(Is the Object Dead?) 44 | - 3.3 垃圾收集算法(Garbage Collection Algorithms) 45 | - 3.4 HotSpot的算法实现(HotSpot Algorithm Implementation) 46 | - 3.5 垃圾收集器(Garbage Collectors) 47 | 48 | - **第4章 虚拟机性能监控、故障处理工具(Chapter 4: JVM Performance Monitoring and Troubleshooting Tools)** 49 | - 4.1 概述(Overview) 50 | - 4.2 JConsole介绍(Introduction to JConsole) 51 | - [4.3 VisualVM介绍(Introduction to VisualVM)](part2/visual-tools/visualvm.md) 52 | - 4.4 其他工具(Other Tools) 53 | 54 | - **第5章 调优案例分析与实战(Chapter 5: Optimization Case Analysis and Practices)** 55 | - 5.1 概述(Overview) 56 | - 5.2 实战案例分析(Practical Case Analysis) 57 | - 5.3 调优实践(Optimization Practices) 58 | 59 | ## 第三部分 虚拟机执行子系统(Part 3: JVM Execution Subsystem) 60 | 61 | - **第6章 类文件结构(Chapter 6: Class File Structure)** 62 | - 6.1 概述(Overview) 63 | - [6.2 无关性的基石(The Cornerstone of Independence)](part3/class-file-structure.html#跨平台的基石) 64 | - [6.3 Class类文件的结构(Structure of Class File)](part3/class-file-structure.html#class类文件结构-理论) 65 | - [6.4 字节码指令简介(Introduction to Bytecode Instructions)](part3/bytecode-instructions-set) 66 | - 6.5 公有设计,私有实现(Public Design, Private Implementation) 67 | - 6.6 Class文件结构的发展(Development of Class File Structure) 68 | 69 | - **第7章 虚拟机类加载机制(Chapter 7: JVM Class Loading Mechanism)** 70 | - 7.1 概述(Overview) 71 | - 7.2 类加载的时机(Timing of Class Loading) 72 | - [7.3 类加载的过程(Class Loading Process)](part3/class-loading-mechanism.html#类加载的过程) 73 | - [7.4 类加载器(Class Loaders)](part3/class-loading-mechanism.html#类加载器) 74 | - 7.5 Java模块化系统(Java Modular System) 75 | 76 | - **第8章 虚拟机字节码执行引擎(Chapter 8: JVM Bytecode Execution Engine)** 77 | - 8.1 概述(Overview) 78 | - 8.2 运行时栈帧结构(Runtime Stack Frame Structure) 79 | - 8.3 方法调用与返回(Method Invocation and Return) 80 | 81 | - **第9章 类加载及执行子系统的案例与实战(Chapter 9: Case Studies and Practices of Class Loading and Execution Subsystem)** 82 | - 9.1 概述(Overview) 83 | - 9.2 实战案例分析(Practical Case Analysis) 84 | - 9.3 调优实践(Optimization Practices) 85 | 86 | ## 第四部分 程序编译与代码优化(Part 4: Program Compilation and Code Optimization) 87 | 88 | - **第10章 前端编译与优化(Chapter 10: Front-end Compilation and Optimization)** 89 | - 10.1 概述(Overview) 90 | - 10.2 语法与语义分析(Syntax and Semantic Analysis) 91 | - 10.3 字节码生成(Bytecode Generation) 92 | 93 | - **第11章 后端编译与优化(Chapter 11: Back-end Compilation and Optimization)** 94 | - 11.1 概述(Overview) 95 | - 11.2 即时编译器(Just-in-time Compiler) 96 | - 11.3 编译优化(Compilation Optimization) 97 | 98 | ## 第五部分 高效并发(Part 5: Efficient Concurrency) 99 | 100 | - **第12章 Java内存模型与线程(Chapter 12: Java Memory Model and Threads)** 101 | - 12.1 概述(Overview) 102 | - 12.2 Java内存模型(Java Memory Model) 103 | - 12.3 线程与线程池(Threads and Thread Pools) 104 | 105 | - **第13章 线程安全与锁优化(Chapter 13: Thread Safety and Lock Optimization)** 106 | - 13.1 概述(Overview) 107 | - 13.2 线程安全(Thread Safety) 108 | - 13.3 锁优化(Lock Optimization) 109 | 110 | ## 附录(Appendices) 111 | 112 | - **附录A 在Windows系统下编译OpenJDK 6(Appendix A: Compiling OpenJDK 6 on Windows)** 113 | - **附录B 展望Java技术的未来(2013年版)(Appendix B: Outlook of Java Technology's Future (2013 Edition))** 114 | - **附录C 虚拟机字节码指令表(Appendix C: JVM Bytecode Instruction Table)** 115 | - C.1 指令集概述(Overview of Instruction Set) 116 | - C.2 常用指令详解(Detailed Explanation of Common Instructions) 117 | 118 | - **附录D 对象查询语言(OQL)简介(Appendix D: Introduction to Object Query Language (OQL))** 119 | - D.1 概述(Overview) 120 | - D.2 OQL语法(OQL Syntax) 121 | - D.3 使用案例(Usage Cases) 122 | 123 | - **附录E JDK历史版本轨迹(Appendix E: Historical Versions of the JDK)** 124 | - E.1 概述(Overview) 125 | - E.2 版本列表(Version List) 126 | -------------------------------------------------------------------------------- /src/md/jvm/part1/compile_jdk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 实战编译JDK 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-07-26 12 | category: JVM 13 | tag: 14 | - jvm 15 | order: 1.6 16 | --- 17 | 18 | # 实战编译JDK 19 | 20 | 想要窥探Java虚拟机内部的实现原理,最直接的路径就是编译自己的JDK。 21 | 尽管网络上有不少开源JDK实现,但OpenJDK无疑是最广泛使用的,我们将选择OpenJDK进行编译实战。 22 | 23 | ## 获取源码 24 | 25 | * 版本选择OpenJDK 12,下载地址 [https://hg.openjdk.org/jdk/jdk12](https://hg.openjdk.org/jdk/jdk12) 26 | * 点击“browse”链接,然后选择对应的压缩包(zip、gz)进行下载 27 | 28 | ![OpenJDK源码下载](https://img.geekyspace.cn/pictures/2024/202407260406861.png) 29 | 30 | ## 系统需求 31 | 32 | 建议在Linux或MacOS上构建OpenJDK,构建工具链和依赖项比起Windows或Solaris平台要容易许多。 33 | 34 | * 认真阅读一遍源码中的`doc/building.html`文档 35 | * 确保源码和依赖项不要放在包含中文的目录里面 36 | 37 | ## 构建编译环境 38 | 39 | ## 进行编译 40 | 41 | ## 在IDE工具中进行源码调试 42 | 43 | -------------------------------------------------------------------------------- /src/md/jvm/part1/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java虚拟机概述 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-07-19 12 | category: JVM 13 | tag: 14 | - jvm 15 | order: 1.4 16 | --- 17 | 18 | # Java虚拟机概述 19 | 20 | > **Java虚拟机**(Java Virtual Machine,简称JVM)是运行所有Java程序的虚拟计算机,是Java平台的核心实现。 21 | > 它提供了一种独立于底层硬件和操作系统的运行环境,使Java程序能够在任何安装了JVM的系统上执行。 22 | > JVM通过将Java字节码(.class文件)转换为机器码来实现跨平台运行,这一特性被称为“Write Once, Run Anywhere”。 23 | 24 | ## 跨平台开发的通用平台 25 | 26 | 随着发展,JVM不再是Java独享的Moment,越来越多的语言开始在JVM上运行,使JVM逐渐演变成一个**跨平台开发的通用平台**。 27 | 28 | ![jvm-class](https://img.geekyspace.cn/pictures/2024/image-20240620020158368.png) 29 | 30 | * JVM本质上只关心`.class`的字节码文件,而不关心源代码是用什么语言编写的。 31 | * 通过Oracle TCK(Technology Compatibility Kit)测试,就是合格的Java虚拟机。 32 | 33 | ## Java虚拟机家族 34 | 35 | **虚拟机始祖:Sun Classic/Exact VM(Sun/Oracle公司)** 36 | 37 | * **Classic VM:** 38 | * 1996年1月23日,Sun发布JDK 1.0,正式商用,最早的Java虚拟机实现 39 | * 直到JDK 1.4,才完全退出商用虚拟机的历史舞台 40 | * ==纯解释器==,可外挂即时编译器(JIT),**缺点**是只能二选一 41 | * **Exact VM:** 42 | * 在JDK 1.2时,在Solaris平台发布,是Classic VM的改进版 43 | * 因准确式内存管理(Exact Memory Management)而得名,是垃圾收集时准确判断堆上数据的前提 44 | * 它的编译执行系统已经具备现代高性能虚拟机雏形 45 | * 如热点探测、两级即时编译器、==编译器与解释器混合工作==模式等 46 | * **缺点**是只能在Solaris平台上运行 47 | 48 | **武林盟主:HotSpot VM(Sun/Oracle公司)** 49 | 50 | * **HotSpot VM** 51 | * 最初由Longview Technologies公司开发,后被Sun公司收购 52 | * 从JDK 1.3至今(2024),HotSpot VM成为默认虚拟机,目前使用最广泛 53 | * HotSpot VM集成了Sun以上两款虚拟机优点(准确式内存管理,热点代码探测技术...) 54 | * **优点**是同时支持解释执行和即时编译执行,在响应速度和执行速度上取得平衡 55 | * Oracle收购Sun以后,建立HotRockit项目,把BEA JRocki优秀特性融合到HotSpot之中 56 | * 2014年JDK 8时期,HotSpot移除掉[永久代](/md/jvm/part2/runtime-data-areas.html#方法区),吸收了JRockit的Java Mission Control监控工具等功能 57 | 58 | **小家碧玉:Mobile/Embedded VM(Sun/Oracle公司)** 59 | 60 | 专门为移动设备和嵌入式设备设计的Java虚拟机(JavaME) 61 | 62 | * **KVM(Kilobyte Virtual Machine)**: 63 | * 用于早期的移动设备,但智能手机市场已被Android和iOS主导 64 | * **CDC(Connected Device Configuration)**: 65 | * 用于功能更强的嵌入式设备,但面临自家Java SE Embedded(eJDK)的竞争 66 | * 由于Java SE的普及,CDC市场快速萎缩,Oracle基本砍掉了CDC-HI项目,将其划归Java SE Embedded 67 | 68 | **天下第二:BEA JRockit/IBM J9 VM** 69 | 70 | * **BEA JRockit**: 71 | * 最初由BEA Systems开发,后被Oracle收购,永远停留在R28(JDK 6版JRockit代号) 72 | * ==专注于服务器==硬件和服务端应用场景,不关注启动速度,不包含解释器实现 73 | * 以其出色的垃圾收集器和性能和诊断工具(如Java Mission Control)著称 74 | * **IBM J9 VM**: 75 | * 全称“IBM Technology for Java Virtual Machine”,简称IT4J,但普遍称为J9 76 | * 作为通用型JVM,其市场定位接近HotSpot,主要优势在IBM产品上 77 | * 由IBM开发,2017年开源为[OpenJ9](https://www.eclipse.org/openj9/),现由Eclipse基金会维护 78 | * 模块化设计优于HotSpot 79 | * 核心组件库(包括垃圾收集器、即时编译器、诊断监控子系统等)构成了IBM OMR项目 80 | * 可以在其他语言平台如Ruby、Python中快速组装成相应的功能 81 | 82 | **软硬合璧:BEA Liquid VM/Azul VM(Zing)** 83 | 84 | * **BEA Liquid VM**: 85 | * 也被称为JRockit VE(Virtual Edition,VE),专为BEA WebLogic实时运行环境设计 86 | * BEA公司开发的JRockit虚拟机虚拟化版本,可直接运行在自家Hypervisor系统上 87 | * 不需要操作系统支持,自身实现了必要的操作系统功能(如线程调度、文件系统、网络支持等) 88 | * 直接控制硬件,避免内核态/用户态切换,最大限度发挥硬件性能,提升Java程序执行效率 89 | * 随着JRockit虚拟机终止开发,Liquid VM项目也停止了 90 | 91 | * **Azul VM**: 92 | * 适用于Azul Systems专有硬件Vega产品线,在HotSpot基础改进 93 | * 采用PGC和C4收集器,停顿时间可控,每个Azul VM实例可管理数十个CPU和数百GB内存 94 | 95 | * **Zing VM**: 96 | * 2010年起,Azul公司重心转向软件,发布Zing虚拟机,基于HotSpot某旧版代码分支 97 | * 低延迟,配备PGC和C4垃圾收集器 98 | * 支持TB级别Java堆内存,暂停时间不超过10毫秒 99 | * HotSpot直到JDK 11和JDK 12的ZGC和Shenandoah收集器才达到类似目标,但效果仍不及C4 100 | * Zing的ReadyNow(快速预热、启动)! 101 | * 利用之前收集的性能监控数据,使虚拟机在启动后快速达到高性能水平 102 | * 减少从解释执行到即时编译的等待时间 103 | * 易于监控(ZVision/ZVRobot工具) 104 | * 方便用户监控JVM运行状态,包括代码热点、对象分配监控、锁竞争监控等 105 | 106 | **挑战者:Apache Harmony/Google Android Dalvik VM** 107 | 108 | * **Apache Harmony**: 109 | * 一个开源的Java SE实现项目,旨在提供兼容Java SE的JVM及类库。虽然项目已停止,但其代码和理念影响深远 110 | * Apache软件基金会开源项目,兼容JDK 5和JDK 6,提供自己的虚拟机和Java类库API。 111 | * 没有通过TCK认证,无法正式称为“Java虚拟机” 112 | * 曾对Java生态系统构成巨大挑战,导致Apache基金会退出JCP组织 113 | * 随Sun公司开源OpenJDK,Harmony项目的优势逐渐减弱 114 | * 主要贡献(如Java类库代码)被吸纳进IBM JDK 7和Google Android SDK 115 | 116 | 117 | * **Google Android Dalvik VM**: 118 | * Android平台核心虚拟机,名字来源于冰岛的小渔村Dalvik 119 | * 非Java虚拟机,使用寄存器架构,不直接执行Java Class文件,而是执行DEX文件 120 | * 通过Class文件转化为DEX文件,支持Java语法和API,推动Android迅速发展 121 | * Android 2.2引入即时编译器,提升性能 122 | * Android 4.4开始引入提前编译(Ahead of Time Compilation,AOT)的ART虚拟机 123 | * Android 5.0开始ART全面替代Dalvik虚拟机 124 | 125 | **没有成功,但并非失败:Microsoft JVM及其他** 126 | 127 | * **Microsoft JVM**: 128 | * 微软开发的Java虚拟机,曾用于早期的Windows平台。但由于与Sun的法律纠纷,微软最终停止了其开发 129 | * 为支持Internet Explorer 3中的Java Applets,开发Microsoft JVM,仅限Windows平台 130 | * 被认为是当时Windows系统下性能最好的Java虚拟机,1997年和1998年连续获得《PC Magazine》“编辑选择奖” 131 | * 1997年被Sun公司控告侵犯商标、不正当竞争,最终微软赔偿2000万美元,并承诺停止开发和逐步移除其Java虚拟机 132 | * 虽然微软的Java虚拟机未能长期发展,但其短暂的成功对当时Java的推广起到了积极作用。 133 | 134 | **百家争鸣** 135 | 136 | * KVM:为小型设备设计的轻量级Java虚拟机 137 | * Java Card VM:支持智能卡和小型嵌入式设备的Java虚拟机 138 | * Squawk VM:针对嵌入式系统和传感器网络的Java虚拟机 139 | * JavaInJava:用Java自身编写的Java虚拟机 140 | * Maxine VM:由Java编写、用于研究和实验的Java虚拟机 141 | * Jikes RVM: IBM开源的高性能研究虚拟机 142 | * IKVM.NET:在.NET平台上运行Java代码的虚拟机 143 | * JamVM:[http://jamvm.sourceforge.net/](http://jamvm.sourceforge.net/) 144 | * CacaoVM:[http://www.cacaovm.org/](http://www.cacaovm.org/) 145 | * SableVM:[http://www.sablevm.org/](http://www.sablevm.org/) 146 | * Kaffe:[http://www.kaffe.org/](http://www.kaffe.org/) 147 | * Jelatine JVM:[http://jelatine.sourceforge.net/](http://jelatine.sourceforge.net/) 148 | * NanoVM:[http://www.harbaum.org/till/nanovm/index.shtml](http://www.harbaum.org/till/nanovm/index.shtml) 149 | * MRP:[https://github.com/codehaus/mrp](https://github.com/codehaus/mrp) 150 | * Moxie JVM:[http://moxie.sourceforge.net/](http://moxie.sourceforge.net/) 151 | 152 | ## Java虚拟机架构 153 | 154 | 理解总体知识点在全局上与知识体系之间的对应关系 155 | 156 | ![Java虚拟机架构](https://img.geekyspace.cn/pictures/2024/0082zybply1gc6fz21n8kj30u00wpn5v.jpg) 157 | 158 | 从整体上看,JVM 由三个不同的组件组成: 159 | 160 | 1. **类加载子系统(Class Loader SubSystem)**:主要负责将类`.class`加载到内存中 161 | 2. **运行时数据区(Runtime Data Area)**:管理JVM运行时所需的数据结构 162 | 3. **执行引擎(Execution Engine)**:负责执行字节码指令,将其转换为机器代码,供机器理解 163 | 164 | ![JVM三大组件](https://img.geekyspace.cn/pictures/2024/image-39.png) 165 | 166 | --- 167 | 168 | ### 类加载子系统 169 | 170 | 第一个组件 ==**类加载过程**== 分为三个阶段:加载、链接和初始化。 171 | 172 | **1、加载(Loading)**:将类的字节码文件加载到内存中,并生成 JVM 运行时的类表示 173 | 174 | * **启动类加载器(Bootstrap Class Loader):** 175 | * 负责加载Java的核心类库,如`java.lang`、`java.net`、`java.util`、`java.io`等 176 | * 这些类库位于`$JAVA_HOME/jre/lib`目录中,例如`rt.jar` 177 | * **扩展类加载器(Extension Class Loader):** 178 | * Bootstrap类加载器的子类,同时也是Application类加载器的父类。 179 | * 它负责加载位于`$JAVA_HOME/jre/lib/ext`目录中的Java标准库的扩展 180 | * **系统类加载器(Application Class Loader):** 181 | * Extension类加载器的子类,加载位于类路径上的类文件,默认情况下,类路径为应用程序的目录 182 | * 可通过添加命令行选项`-classpath`或`-cp`来修改类路径 183 | 184 | **2、链接(Linking)**:将类的二进制数据合并到JVM的运行时状态中,分为以下三个步骤: 185 | 186 | - **验证(Verify)**:通过一组约束或规则检查`.class`文件的正确性,验证失败抛出`VerifyException` 187 | - **准备(Prepare)**:为类或接口的静态字段分配内,存并设置默认初始值 188 | - **解析(Resolve)**:将符号引用替换为常量池中存在的直接引用 189 | 190 | **3、初始化(Initialization)**:执行类的静态初始化块和静态变量的初始化 191 | 192 | ![类加载三个阶段](https://img.geekyspace.cn/pictures/2024/image-40.png) 193 | 194 | --- 195 | 196 | ### 运行时数据区 197 | 198 | 第二个组件 ==**运行时数据区域**== 内含有五个数据区域: 199 | 200 | * *线程共享区*: 201 | - **方法区**(Method Area):存储已加载的类信息、常量池、静态变量和即时编译后的代码 202 | - **堆(Heap Area)**:存储对象实例和数组(即`new`出来的对象) 203 | - GC管理的主要区域,分为新生代和老年代 204 | * *线程隔离区*: 205 | - **虚拟机栈(Virtual Machine Stack)**:存储局部变量、操作数栈(用于计算)、栈帧和方法调用信息 206 | - **程序计数器(PC寄存器,Program Counter Register)**:存储当前线程执行的字节码指令的地址,以便线程切换时能恢复 207 | - **本地方法栈(Native Method Stack)**:用于执行本地方法(即 JNI 调用的C/C++代码) 208 | 209 | ![运行时数据区](https://img.geekyspace.cn/pictures/2024/image-32.png) 210 | 211 | --- 212 | 213 | ### 执行引擎 214 | 215 | 第三个组件 ==**执行引擎**== 包含解释器、编译器和垃圾回收区: 216 | 217 | * **解释器(Interpreter)**:**逐行**解释执行字节码指令,速度较慢,但实现简单 218 | * **即时编译器(JIT Compiler,Just-In-Time Compiler)**:将**热点代码**(经常执行的字节码)编译成机器码,以提高执行效率 219 | * **垃圾回收器(Garbage Collector)**:自动管理和回收堆中的无用对象,防止内存泄漏 220 | 221 | ![执行引擎](https://img.geekyspace.cn/pictures/2024/image-33.png) 222 | 223 | 这些部分共同组成了JVM的核心功能,使得Java程序可以跨平台运行,并且具有良好的性能和安全性 224 | 225 | * 参考:[Siben Nayak—《面向初学者的Java虚拟机架构》](https://www.freecodecamp.org/news/jvm-tutorial-java-virtual-machine-architecture-explained-for-beginners/) 226 | -------------------------------------------------------------------------------- /src/md/jvm/part2/heap-object-flow.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 堆中对象分配、布局和访问的全过程 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-08-10 12 | category: JVM 13 | tag: 14 | - jvm 15 | order: 2.3 16 | --- 17 | 18 | # 堆中对象分配、布局和访问的全过程 19 | 20 | > 本文将深入探讨HotSpot虚拟机中Java堆中对象分配、布局和访问的全过程。 21 | 22 | ## 对象的创建 23 | 24 | 在Java中,创建对象通常使用`new`关键字,而在JVM中,创建对象的过程如下: 25 | 26 | **1、类加载检查** 27 | 28 | JVM首先检查`new`指令所引用的类是否已加载、连接和初始化。 29 | 如果没有,会先执行[类加载过程](/md/jvm/part3/class-loading-mechanism.html#类加载的过程)。 30 | 31 | **2、分配内存** 32 | 33 | JVM为新对象分配内存,其大小在类加载完成时确认,**内存分配的方式:** 34 | 35 | 1. **指针碰撞:** 适用于规整的堆内存,将内存分为已使用和未使用两部分,通过移动指针分配内存。 36 | 2. **空闲列表:** 适用于不规整的堆内存,JVM维护一个空闲列表,从中找到合适的内存块进行分配。 37 | 38 | 使用压缩整理功能的收集器(如Serial、ParNew)通常采用指针碰撞分配,而基于标记-清除(Sweep)算法的CMS收集器使用空闲列表分配。 39 | 40 | **线程安全的内存分配方式:** 41 | 42 | * **同步处理:** 通过同步操作或CAS加重试机制确保原子性。 43 | * **线程本地分配缓冲(TLAB):** 每个线程在Java堆中预先分配一小块内存,分配时在`TLAB`中进行, 44 | 只有`TLAB`耗尽分配新的`TLAB`时才才需同步。 45 | * 通过`-XX:+UseTLAB`参数来启用`TLAB`。 46 | 47 | **3、初始化零值(不包括对象头)** 48 | 49 | JVM将分配的内存空间(不包括对象头)初始化为零值,确保对象字段在未显式初始化时可直接使用。 50 | 51 | **4、设置对象头** 52 | 53 | JVM设置对象头,包括: 54 | 55 | * **Mark Word:** 存储对象的哈希码(调用`Object::hashCode()`时计算)、GC分代年龄、锁信息等。 56 | * **类元数据指针:** 指向对象所属类的元数据,确定对象是哪个类的实例。 57 | 58 | **5、执行``方法** 59 | 60 | 至此,在JVM层面,一个新的对象已经产生了。 61 | 但从Java程序视角,对象创建才刚刚开始,所有的字段都还为零值。 62 | 只有构造方法``执行后,对象才按照程序员的意图完成初始化,成为一个真正可用的对象。 63 | 64 | ## 对象的内存布局 65 | 66 | ## 对象的访问定位 67 | 68 | -------------------------------------------------------------------------------- /src/md/jvm/part2/runtime-data-areas.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 运行时数据区 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-08-10 12 | category: JVM 13 | tag: 14 | - jvm 15 | order: 2.2 16 | --- 17 | 18 | # 运行时数据区 19 | 20 | > **运行时数据区**是指在运行程序时存储数据的内存区域。分为程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区五个部分。 21 | 22 | ![Java虚拟机运行时数据区](https://img.geekyspace.cn/pictures/2024/202408102247073.png) 23 | 24 | * **线程私有:** 25 | * **程序计数器** - 存储线程执行位置 26 | * **虚拟机栈** - 存储Java方法调用与执行过程的数据 27 | * **本地方法栈** - 存储本地方法的执行数据 28 | * **线程共享:** 29 | * **堆** - 主要存储对象 30 | * **方法区** - 存储类/方法/字段等定义(元)数据 31 | * **运行时常量区** - 保存常量static数据 32 | 33 | ## 程序计数器 34 | 35 | > **程序计数器**是线程私有的,用于记录当前程序执行的字节码指定位置。 36 | 37 | **知识点:** 38 | 39 | 1. 线程私有 40 | 2. 不会被垃圾回收 41 | 3. 访问速度最快(JVM内存区域中) 42 | 4. 占用内存少,不会出现`OutOfMemoryError` 43 | 5. 执行Java方法时,记录的是字节码指令地址 44 | 6. 执行Native方法时,记录为未定义(`undefined`) 45 | 46 | **思考以下问题,加强理解:** 47 | 48 | 1. 程序计数器如何保证线程能够准确地恢复到之前的执行位置? 49 | 2. 字节码执行与程序计数器的关系? 50 | 51 | ## 虚拟机栈 52 | 53 | > **虚拟机栈**是线程私有的内存区域,其生命周期与线程相同。 54 | > 它描述了**方法执行的内存模型**。当方法被执行时,JVM会为该方法同步创建一个==栈帧(Stack Frame)==。 55 | 56 | **知识点:** 57 | 58 | 1. 线程私有 59 | 2. 不会被垃圾回收 60 | 3. 访问速度仅次于程序计数器 61 | 4. 栈大小可设置,限制深度: 62 | * 推荐固定大小设置(`-Xss 数值[k|m|g]`),达到上限,抛出`StackOverflowError` 63 | * 动态扩展,可用内存不足时,抛出`OutOfMemoryError` 64 | 65 | **栈帧的内部结构:** 66 | 67 | ![栈帧的概念结构](http://img.geekyspace.cn/pictures/2025/0082zybply1gc8tjehg8bj318m0lbtbu.jpg) 68 | 69 | * **局部变量表:** 用于存储方法中的局部变量和参数。 70 | * **操作数栈:** 后进先出(LIFO)结构,用于方法执行时存储执行指令产生中间结果。 71 | * **动态链接:** 指在方法调用时,将符号引用转换为直接引用的过程。 72 | * **方法返回地址:** 指方法调用后返回位置的地址。 73 | 74 | ## 本地方法栈 75 | 76 | > **本地方法栈**是线程私有,与虚拟机栈功能相似。其中虚拟机栈为Java方法(字节码)服务,本地方法栈则为Native方法服务。 77 | 78 | * HotSpot虚拟机把虚拟机栈和本地方法栈合二为一。 79 | * 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出`StackOverflowError`和`OutOfMemoryError`异常。 80 | 81 | ## Java堆 82 | 83 | > **Java堆**是虚拟机管理的内存中最大的一块,线程共享,并在虚拟机启动时创建。 84 | > 它的唯一目的是存放对象实例,几乎所有的对象实例以及数组都在堆上分配。 85 | 86 | **堆内存模型:** 87 | 88 | ![堆内存模型](https://img.geekyspace.cn/pictures/2024/202407172004168.png) 89 | 90 | 现代垃圾收集器采用分代收集理论进行设计,因此堆内存被划分为多个区域,包括: 91 | 92 | * **新生代:** 93 | * 存放生命周期较短的对象 94 | * 通常由Eden区和两个Survivor区(被称为from/to或s0/s1)组成,默认比例是`8:1:1` 95 | * 填满时触发`Minor GC`(小型垃圾回收) 96 | * 采用复制算法,将存活的对象复制到Survivor区,然后清理Eden区和使用过的Survivor区。 97 | * **老年代:** 98 | * 存放生命周期较长,或多次垃圾收集后任然存活的对象 99 | * 填满时触发`Major GC`或`Full GC`,耗时严重 100 | * 使用的垃圾收集算法通常是标记-清除算法或标记-整理算法。 101 | * **永久代(PermGen):** 102 | * 存放Class元数据,包括类结构、方法、字段信息等 103 | * 属于“堆”的一部分,无法扩展时会抛出`OutOfMemoryError`异常 104 | * 通过命令`-Xms`设置初始堆大小,`-Xmx`设定最大堆大小 105 | * 从JDK8开始,被元空间(Metaspace)取代,称为“非堆”,使用的是本地内存 106 | 107 | [DigitalOcean——Java (JVM) 内存模型 - Java 中的内存管理](https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java) 108 | 109 | ## 方法区 110 | 111 | > **方法区**是JVM规范中的一个逻辑区域,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。 112 | 113 | * 在Java7的时候,方法区被称为“**永久代**(PermGen)”。 114 | * 从Java8开始,方法区的实现被改为**元空间**(Metaspace),元空间使用的是本地内存,而不是像永久代那样在JVM的堆内存中分配。 115 | 116 | ## 运行时常量池 117 | 118 | > **运行时常量池**是方法区的一部分,用于存放编译期生成的各种字面量与符号引用,支持在运行时动态添加新的常量。 119 | 120 | * **字面量:** 表示固定的数据值,如整数、浮点数、字符串等常量。 121 | * **符号引用:** 一组符号,用于描述所引用的目标,包括类和接口的全限定名、字段和方法的名称。 122 | 123 | **知识点:** 124 | 125 | 1. 具备动态性,如`String.intern()`方法将字符串对象添加到运行时常量池中。 126 | 2. 会产生`OutOfMemoryError`异常 127 | 128 | **思考以下问题,加强理解:** 129 | 130 | 1. Class常量池与运行时常量池的关系? 131 | 132 | ## 直接内存 133 | 134 | **直接内存**并不是虚拟机运行时数据区的一部分,也未在《Java虚拟机规范》中明确定义。 135 | 然而,由于其频繁使用且可能导致`OutOfMemoryError`异常,值得在此进行讨论。 136 | 137 | **关键点:** 138 | - **NIO的引入:** JDK 1.4引入了NIO(New Input/Output)类,通过通道(Channel)和缓冲区(Buffer)实现了一种新的I/O方式。它使用本地(Native)函数库直接分配堆外内存,并通过在Java堆中的`DirectByteBuffer`对象进行引用和操作。 139 | - **性能优势:** 这种方法能够显著提高性能,因为它避免了在Java堆和本地堆之间的数据复制,从而加快了I/O操作。 140 | - **内存限制:** 虽然直接内存的分配不受Java堆大小的限制,但仍受到本机总内存(包括物理内存、SWAP分区或分页文件)大小和处理器寻址空间的限制。 141 | - **配置问题:** 在配置虚拟机参数(如`-Xmx`)时,管理员通常会根据实际物理内存来设置Java堆的大小,但可能忽略直接内存的占用。如果各个内存区域的总和超过了物理内存限制,可能在动态扩展时导致`OutOfMemoryError`异常。 -------------------------------------------------------------------------------- /src/md/jvm/part2/visual-tools/visualvm.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VisualVM介绍 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2025-03-06 12 | category: JVM 13 | tag: 14 | - jvm 15 | order: 4.3 16 | --- 17 | 18 | # VisualVM介绍 19 | 20 | > 官网地址: https://visualvm.github.io/ 21 | 22 | * IDEA 集成:[VisualVM Launcher](https://plugins.jetbrains.com/plugin/7115-visualvm-launcher/versions) 23 | 24 | * Github开源翻译助手:[VVM-Translator](https://github.com/zedoCN/VVM-Translator) 25 | 26 | * 安装插件VisualVM GC 27 | 28 | -------------------------------------------------------------------------------- /src/md/jvm/part3/bytecode-instructions-set.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 字节码指令集 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-07-26 12 | category: JVM 13 | tag: 14 | - jvm 15 | order: 6.4 16 | --- 17 | 18 | # 字节码指令集 19 | 20 | > 字节码指令集是Java虚拟机(JVM)能理解和执行的低级指令集合。具体保存在Java类文件(`.class`)的方法区描述中。 21 | 22 | 总数不超过256个,由*操作码**操作数* 组成。 23 | 24 | - **操作码(`Opcode`):** 一个字节长度的数字,代表某种特定操作 25 | - **操作数(`Operands`):** 跟随操作码之后的零至多个参数,用于该操作所需的数据 26 | 27 | 由于JVM采用面向操作数栈而不是面向寄存器的架构,大多数指令都不包含操作数,只有一个操作码,指令参数存放在操作数栈中。 28 | 29 | ## 操作码助记符 30 | 31 | 数据类型相关的字节码指令,包含特定的**操作码助记符**: 32 | 33 | | 数据类型 | 操作码助记符 | 34 | |-----------|--------| 35 | | int | `i` | 36 | | long | `l` | 37 | | short | `s` | 38 | | byte | `b` | 39 | | char | `c` | 40 | | float | `f` | 41 | | double | `d` | 42 | | reference | `a` | 43 | 44 | 也有一些指令没有明确的类型字符: 45 | 46 | | 指令 | 描述 | 47 | |---------------|-----------------| 48 | | `arraylength` | 操作数为数组类型对象 | 49 | | `goto` | 无条件跳转指令,与数据类型无关 | 50 | 51 | 由于操作码长度只有一个字节,如果每种类型的指令都支持所有数据类型,指令数量将超出范围。 52 | 因此,Java虚拟机的指令集设计成非完全独立的(“Not Orthogonal”)。 53 | 即并非每种数据类型和每一种操作都有对应的指令。 54 | 55 | ## 操作码表 56 | 57 | 使用数据类型对应的操作码助记符替换操作码`opcode`列指令模板中的`T`,得到具体的字节码指令。 58 | 59 | 参考下表**Java虚拟机指令集所支持的数据类型**。 60 | 61 | | opcode | byte | short | int | long | float | double | char | reference | 62 | |---------------|--------|---------|-----------|---------|---------|---------|---------|-----------| 63 | | **Tpush** | bipush | sipush | | | | | | | 64 | | **Tconst** | | | iconst | lconst | fconst | dconst | | aconst | 65 | | **Tload** | | | iload | lload | fload | dload | | aload | 66 | | **Tstore** | | | istore | lstore | fstore | dstore | | astore | 67 | | **Tinc** | | | iinc | | | | | | 68 | | **Taload** | | baload | saload | iaload | laload | faload | daload | caload | aaload | 69 | | **Tastore** | | bastore | sastore | iastore | lastore | fastore | dastore | castore | aastore | 70 | | **Tadd** | | | iadd | ladd | fadd | dadd | | | 71 | | **Tsub** | | | isub | lsub | fsub | dsub | | | 72 | | **Tmul** | | | imul | lmul | fmul | dmul | | | 73 | | **Tdiv** | | | idiv | ldiv | fdiv | ddiv | | | 74 | | **Trem** | | | irem | lrem | frem | drem | | | 75 | | **Tneg** | | | ineg | lneg | fneg | dneg | | | 76 | | **Tshl** | | | ishl | lshl | | | | | 77 | | **Tshr** | | | ishr | lshr | | | | | 78 | | **Tushr** | | | iushr | lushr | | | | | 79 | | **Tand** | | | iand | land | | | | | 80 | | **Tor** | | | ior | lor | | | | | 81 | | **Txor** | | | ixor | lxor | | | | | 82 | | **i2T** | i2b | i2s | i2i | i2l | i2f | i2d | | | 83 | | **l2T** | | | l2i | l2l | l2f | l2d | | | 84 | | **f2T** | | | f2i | f2l | f2f | f2d | | | 85 | | **d2T** | | | d2i | d2l | d2f | d2d | | | 86 | | **Tcmp** | | | icmp | lcmp | | | | | 87 | | **Tcmpl** | | | | | fcmpl | dcmpl | | | 88 | | **Tcmpg** | | | | | fcmpg | dcmpg | | | 89 | | **if_TcmpOP** | | | if_icmpOP | | | | | if_acmpOP | 90 | | **Treturn** | | | ireturn | lreturn | freturn | dreturn | | areturn | 91 | 92 | 从表中看来,大部分指令不支持`byte`、`char`和`short`类型,`boolean`类型更是没有任何指令支持。 93 | 94 | * 编译器会在编译期或运行期将`byte`和`short`类型数据带符号扩展(Sign-Extend)为`int`类型 95 | * 将`boolean`和`char`类型数据零位扩展(Zero-Extend)为`int`类型 96 | * 在处理这些类型的数组时,也会转换为使用`int`类型的字节码指令 97 | 98 | 因此,大多数对`boolean`、`byte`、`short`和`char`类型数据的操作,实际上都是使用`int`类型进行的。 99 | 100 | ## 字节码指令分类 101 | 102 | ### 加载和存储指令 103 | 104 | > 加载和存储指令用于在栈帧中的局部变量表和操作数栈之间传输数据。 105 | 106 | - **将局部变量加载到操作数栈** 107 | * 整数加载指令:`iload`、`iload_` 108 | * 长整型加载指令:`lload`、`lload_` 109 | * 浮点型加载指令:`fload`、`fload_` 110 | * 双精度浮点型加载指令:`dload`、`dload_` 111 | * 引用类型加载指令:`aload`、`aload_` 112 | - **将数值从操作数栈存储到局部变量表** 113 | * 整数存储指令:`istore`、`istore_` 114 | * 长整型存储指令:`lstore`、`lstore_` 115 | * 浮点型存储指令:`fstore`、`fstore_` 116 | * 双精度浮点型存储指令:`dstore`、`dstore_` 117 | * 引用类型存储指令:`astore`、`astore_` 118 | - **将常量加载到操作数栈** 119 | * 字节常量加载指令:`bipush` 120 | * 短整型常量加载指令:`sipush` 121 | * 常量池加载指令:`ldc`、`ldc_w`、`ldc2_w` 122 | * 空常量加载指令:`aconst_null` 123 | * 整数常量加载指令:`iconst_m1`、`iconst_` 124 | * 长整型常量加载指令:`lconst_` 125 | * 浮点型常量加载指令:`fconst_` 126 | * 双精度浮点型常量加载指令:`dconst_` 127 | - **扩充局部变量表访问索引的指令** 128 | * 扩展索引指令:`wide` 129 | 130 | 加载和存储指令主要用于操作数栈和局部变量表之间的数据传输。 131 | 此外,一些指令(如访问对象字段或数组元素的指令)也会涉及操作数栈的数据传输。 132 | 133 | ### 运算指令 134 | 135 | > 算术指令用于对两个操作数栈上的值进行特定运算,并将结果重新存入到操作栈顶。 136 | 137 | - **算术指令列表:** 138 | * 加法指令:`iadd`、`ladd`、`fadd`、`dadd` 139 | * 减法指令:`isub`、`lsub`、`fsub`、`dsub` 140 | * 乘法指令:`imul`、`lmul`、`fmul`、`dmul` 141 | * 除法指令:`idiv`、`ldiv`、`fdiv`、`ddiv` 142 | * 求余指令:`irem`、`lrem`、`frem`、`drem` 143 | * 取反指令:`ineg`、`lneg`、`fneg`、`dneg` 144 | * 位移指令:`ishl`、`ishr`、`iushr`、`lshl`、`lshr`、`lushr` 145 | * 按位或指令:`ior`、`lor` 146 | * 按位与指令:`iand`、`land` 147 | * 按位异或指令:`ixor`、`lxor` 148 | * 局部变量自增指令:`iinc` 149 | * 比较指令:`dcmpg`、`dcmpl`、`fcmpg`、`fcmpl`、`lcmp` 150 | 151 | ### 类型转换指令 152 | 153 | > 类型转换指令可以将两种不同的数值类型相互转换。 154 | > 用于实现用户代码中的显式类型转换操作,或处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。 155 | 156 | - **宽化类型转换:** 即小范围类型向大范围类型的安全转换 157 | * `int`类型到`long`、`float`或者`double`类型 158 | * `long`类型到`float`、`double`类型 159 | * `float`类型到`double`类型 160 | - **窄化类型转换:** 与“宽化”相对,需显式指令,可能导致正负号变化和精度丢失 161 | * `i2b`、`i2c`、`i2s`、`l2i`、`f2i`、`f2l`、`d2i`、`d2l`、`d2f` 162 | 163 | ### 对象创建与访问指令 164 | 165 | 虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。 166 | 对象创建后,可以通过对象访问指令来获取对象实例或数组中的字段或者数组元素。 167 | 168 | - **对象创建指令** 169 | * 创建类实例:`new` 170 | * 创建数组:`newarray`、`anewarray`、`multianewarray` 171 | - **对象访问指令** 172 | * 访问字段:`getfield`、`putfield`、`getstatic`、`putstatic` 173 | * 访问数组元素:`baload`、`caload`、`saload`、`iaload`、`laload`、`faload`、`daload`、`aaload` 174 | * 存储数组元素:`bastore`、`castore`、`sastore`、`iastore`、`fastore`、`dastore`、`aastore` 175 | * 数组操作:`arraylength` 176 | * 类型检查和转换:`instanceof`、`checkcast` 177 | 178 | ### 操作数栈管理指令 179 | 180 | 如同操作一个普通数据结构中的堆栈那样,Java虚拟机提供了一些用于直接操作操作数栈的指令,包括: 181 | 182 | - **操作数栈管理指令** 183 | * 出栈:`pop`、`pop2` 184 | * 复制栈顶元素:`dup`、`dup2`、`dup_x1`、`dup2_x1`、`dup_x2`、`dup2_x2` 185 | * 互换栈顶两个元素:`swap` 186 | 187 | ### 控制转移指令 188 | 189 | > 控制转移指令用于在程序执行过程中有条件或无条件地跳转到其他指令位置,修改程序计数器(PC)的值。 190 | 191 | - **条件分支** 192 | * `ifeq`、`iflt`、`ifle`、`ifne`、`ifgt`、`ifge` 193 | * `ifnull`、`ifnonnull` 194 | * `if_icmpeq`、`if_icmpne`、`if_icmplt`、`if_icmpgt`、`if_icmple`、`if_icmpge` 195 | * `if_acmpeq`、`if_acmpne` 196 | - **复合条件分支** 197 | * `tableswitch` — 使用表的方式处理范围内的分支 198 | * `lookupswitch` — 使用查找表的方式处理分支 199 | - **无条件分支** 200 | - `goto`、`goto_w` — 无条件跳转到指定位置 201 | - `jsr`、`jsr_w` — 跳转到子程序并保存返回地址 202 | - `ret` — 从子程序返回 203 | 204 | 所有比较最终都转为`int`类型,Java虚拟机提供了丰富的 `int` 类型条件分支指令: 205 | 206 | * `boolean`、`byte`、`char`和`short`直接使用`int`类型指令 207 | * `long`、`float` 和 `double`先用对应比较指令,再转换为`int`进行条件分支 208 | 209 | ### 方法调用和返回指令 210 | 211 | - **方法调用(分派、执行过程)** 分为以下五种指令,用于不同类型的方法调用: 212 | * `invokevirtual`:调用对象的实例方法,依据对象的实际类型进行分派(虚方法分派),最常见 213 | * `invokeinterface`:调用接口方法,运行时搜索实现了接口的方法 214 | * `invokespecial`:调用需要特殊处理的实例方法,如实例初始化方法、私有方法和父类方法 215 | * `invokestatic`:调用类静态方法(static方法) 216 | * `invokedynamic`:运行时动态解析和调用方法,分派逻辑由用户定义 217 | - **方法返回指令** 根据返回值的类型区分,包括: 218 | * `ireturn`:返回 `boolean`、`byte`、`char`、`short` 和 `int` 类型的值 219 | * `lreturn`:返回 `long` 类型的值 220 | * `freturn`:返回 `float` 类型的值 221 | * `dreturn`:返回 `double` 类型的值 222 | * `areturn`:返回引用类型的值 223 | * `return`:用于声明为 `void` 的方法、实例初始化方法、类初始化方法 224 | 225 | ### 异常处理指令 226 | 227 | 在Java程序中,**显式抛出异常**(`throw` 语句)由`athrow`指令实现。 228 | 229 | 除了显式抛出异常,在JVM指令检测到异常状况时,会自动抛出**运行时异常**。 230 | 例如,在整数运算中,当除数为零时,虚拟机会在`idiv`或`ldiv`指令中抛出`ArithmeticException`异常。 231 | 232 | 处理异常(`catch` 语句)在Java虚拟机中不是通过字节码指令实现的,而是采用**异常表**来完成。 233 | 234 | ### 同步指令 235 | 236 | Java虚拟机支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用**管程**(Monitor,通常称为“锁”)来实现的。 237 | 238 | **方法级同步** 239 | 240 | 方法级同步是隐式的,通过方法调用和返回操作实现。 241 | 虚拟机从方法常量池中的方法表结构中的 `ACC_SYNCHRONIZED` 访问标志来判断方法是否被声明为同步方法。同步方法的执行过程如下: 242 | 243 | 1. 方法调用时,检查 `ACC_SYNCHRONIZED` 标志。 244 | 2. 如果设置了该标志,执行线程需先成功持有管程,然后才能执行方法。 245 | 3. 方法执行完成(无论正常还是异常)后,释放管程。 246 | 4. 在方法执行期间,持有管程的线程独占管程,其他线程无法获取同一个管程。 247 | 248 | **指令序列同步** 249 | 250 | 同步一段指令集序列由 `synchronized` 语句块表示。Java虚拟机的指令集中有 `monitorenter` 和 `monitorexit` 两条指令来支持 `synchronized` 关键字的语义。以下是一个示例代码及其编译后的字节码序列: 251 | 252 | ```java 253 | void onlyMe(Foo f) { 254 | synchronized(f) { 255 | doSomething(); 256 | } 257 | } 258 | ``` 259 | 260 | 编译后的字节码序列: 261 | 262 | ```shell 263 | Method void onlyMe(Foo) 264 | 0 aload_1 // 将对象f入栈 265 | 1 dup // 复制栈顶元素(即f的引用) 266 | 2 astore_2 // 将栈顶元素存储到局部变量表变量槽 2中 267 | 3 monitorenter // 以栈顶元素(即f)作为锁,开始同步 268 | 4 aload_0 // 将局部变量槽 0(即this指针)的元素入栈 269 | 5 invokevirtual #5 // 调用doSomething()方法 270 | 8 aload_2 // 将局部变量槽 2的元素(即f)入栈 271 | 9 monitorexit // 退出同步 272 | 10 goto 18 // 方法正常结束,跳转到18返回 273 | 13 astore_3 // 异常路径起始,见下面异常表的Target 13 274 | 14 aload_2 // 将局部变量槽 2的元素(即f)入栈 275 | 15 monitorexit // 退出同步 276 | 16 aload_3 // 将局部变量槽 3的元素(即异常对象)入栈 277 | 17 athrow // 把异常对象重新抛出给onlyMe()方法的调用者 278 | 18 return // 方法正常返回 279 | 280 | Exception table: 281 | From To Target Type 282 | 4 10 13 any 283 | 13 16 13 any 284 | ``` 285 | 286 | 编译器必须确保无论方法通过何种方式完成,方法中调用过的每条`monitorenter`指令都有其对应的`monitorexit`指令,无论是正常结束还是异常结束。 287 | 为了保证在方法异常完成时`monitorenter`和`monitorexit`指令依然正确配对执行,编译器会自动生成一个异常处理程序,用于执行`monitorexit`指令。 288 | -------------------------------------------------------------------------------- /src/md/jvm/part3/class-loading-mechanism.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 类加载机制 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-07-20 12 | category: JVM 13 | tag: 14 | - jvm 15 | order: 7.3 16 | --- 17 | 18 | # 类加载机制 19 | 20 | ## 类的生命周期 21 | 22 | > 类的生命周期将会经历**加载** (Loading)、**验证**(Verification)、**准备**(Preparation)、**解析**(Resolution)、**初始化**(Initialization)、**使用**(Using)和**卸载**(Unloading)七个阶段 23 | 24 | * 其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,按部就班地开始。 25 | * 解析阶段不一定,某些情况下可以在初始化之后,以支持Java的运行时绑定(动态绑定)特性。 26 | 27 | ![类的生命周期](https://img.geekyspace.cn/pictures/2024/202407260441081.png) 28 | 29 | * 注:并非所有的类都会经历完整的生命周期,有些类可能在某些阶段就结束其在JVM中的生涯。 30 | 31 | ## Class实例何时被创建 32 | 33 | 在Java虚拟机(JVM)规范中,类的**加载**过程是由JVM自行决定的,但**初始化**过程则必须严格按照规范执行。 34 | (在初始化之前,类的加载、验证、准备阶段必然已经完成)。 35 | 36 | ### 主动引用(六种情况) 37 | 38 | > 在Java虚拟机规范中,“有且只有”以下六种情况会触发类的初始化,称为对一个类的**主动引用**: 39 | 40 | 1. 创建对象实例、访问静态字段(非常量)、调用静态方法 41 | * 即遇到`new`、`getstatic`、`putstatic`或`invokestatic`这 4 种字节码指令。 42 | 2. **反射调用** 使用`java.lang.reflect`对类型(类和接口)进行反射调用 43 | 3. 初始化子类时,先初始化父类 44 | 4. **启动主类**,JVM启动时,先初始化包含`main()`方法的主类。 45 | 5. **JDK 1.7的动态语言支持** 46 | * [周志明-解析 JDK 7 的动态类型语言支持](https://www.infoq.cn/article/jdk-dynamically-typed-language/) 47 | * JDK 1.7 引入`java.lang.invoke.MethodHandle` 动态语言支持,解析静态字段/方法或构造方法时 48 | * 即解析的句柄属于`REF_getStatic`、`REF_putStatic`、`REF_invokeStatic`或`REF_newInvokeSpecial`四种情况时,会触发目标类初始化。 49 | 6. **接口默认方法**初始化规则 50 | * JDK 1.8 新增`default`默认方法,若实现类初始化,则需要先初始化该接口。 51 | 52 | ### 被动引用(不触发) 53 | 54 | > 除了以上六种场景外,所有其他引用类的方式都不会触发初始化,称为**被动引用**。 55 | 56 | * **例1:通过子类引用父类的静态字段,不会导致子类初始化,只有父类会被初始化** 57 | * 子类是否加载和验证,取决于虚拟机的具体实现。 58 | * 在HotSpot虚拟机(JDK 1.8 亲测)中,使用`-XX:+TraceClassLoading`观察到此操作会导致子类加载。 59 | * **例2:通过数组定义来引用类,不会触发此类的初始化** 60 | * 例如,`MyClass[] sca = new MyClass[10];`,不会初始化`MyClass`类 61 | * 但这段代码触发了另一个名为`[L包名.MyClass`的类的初始化阶段。它是由虚拟机自动生成的、继承自`java.lang.Object` 62 | 的子类,由字节码指令`newarray`触发。这个类表示`MyClass`的一维数组,包含数组应有的属性和方法(如`public`的`length` 63 | 属性和`clone()`方法)。 64 | * Java语言对数组的访问比C/C++更安全,因为这个类包装了数组元素的访问,C/C++中直接翻译为对数组指针的移动。 65 | 在Java语言里,发生数组越界时会抛出`java.lang.ArrayIndexOutOfBoundsException`异常,避免非法内存访问。 66 | * **例3:引用常量不会触发定义常量的类的初始化** 67 | * 因为常量在编译阶段就会被存入调用类的常量池中。 68 | 69 | 70 | ### 接口和类“加载与初始化”的差异 71 | 72 | **相同点** 73 | 74 | * 类 & 接口都经历:加载 → 验证 → 准备 → 解析 → **初始化(`()`)** 75 | 76 | **差异** 77 | 78 | * 接口不能使用`static{}`语句块,但编译器仍会生成`()`方法用于初始化静态变量。 79 | * 类在初始化时,其父类必须先初始化。 80 | * 接口在初始化时不要求父接口初始化,只有在真正使用到父接口的常量等成员时才会触发其初始化。 81 | 82 | ## 类加载的过程 83 | 84 | Java类加载过程主要分为**加载**、**连接**(验证、准备、解析)、**初始化**三个阶段。 85 | 86 | ### 加载(Loading) 87 | 88 | > 1. 通过类的全限定名**读取类的二进制字节流**。 89 | > 2. 并将字节流所代表的 静态存储结构转化为**方法区**的运行时数据结构。 90 | > 3. 在**堆**内存中生成`java.lang.Class`对象,作为方法区这个类的各种数据的的访问入口。 91 | 92 | * **类文件来源:** 93 | * 通常包括本地文件系统、压缩文件(如JAR、WAR)、网络、数据库、加密文件(防止反编译)、运行时动态生成,以及由其他文件生成(如JSP应用生成的Class文件)。 94 | * **数组类加载:** 95 | * 数组类本身不通过类加载器创建,而是由Java虚拟机直接在内存中构建出来。但数组类的元素类型最终还是靠类加载器来完成加载。 96 | 97 | image-20250302011909381 98 | 99 | ### 连接(Linking) 100 | 101 | 分为三个阶段: 102 | 103 | * **验证**(Verify):确保字节码符合虚拟机要求 104 | * **准备**(Prepare):为字段赋予初始值 105 | * **解析**(Resolve):符号引用转换为直接引用 106 | 107 | > **验证**(Verify)是连接阶段的第一阶段,目的是确保字节码的正确性和安全性,包括文件格式验证、元数据验证、字节码验证和符号引用验证四个阶段。 108 | 109 | 验证阶段大致上会完成下面四个阶段的检验动作: 110 | 111 | 1. **文件格式验证:** 检查字节流是否符合Class文件的格式规范。 112 | 2. **元数据验证:** 对类的元数据信息进行语义校验,确保其符合Java语言的语法和语意规则。 113 | 3. **字节码验证:** 通过数据流分析和控制流分析,确保字节码指令的合法性和逻辑正确性。 114 | 4. **符号引用验证:** 在解析阶段之前,检查符号引用是否能被正确解析。 115 | 116 | ![验证阶段](http://img.geekyspace.cn/pictures/2025/image-20250302022044718.png) 117 | 118 | > **准备**(Prepare)阶段为类中的静态变量分配内存并设置初始值。 119 | 120 | 静态变量的内存分配发生在**方法区**中。 121 | 122 | * 在 JDK 7及之前,HotSpot使用**永久代**(PerGen)来实现方法区,存放在堆内存中。 123 | * 在 JDK 8及之后,永久代被移除,取而代之的是**元空间**(Metaspace),存放在操作系统本地内存中。 124 | 125 | 准备阶段 126 | 127 | > **解析**(Prepare)阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。 128 | 129 | * **符号引用(Symbolic References):** 字符串形式表示的对目标的逻辑引用 130 | * **直接引用(Direct References):** 是直接定位到目标内存位置的指针、偏移量或句柄。 131 | 132 | 解析动作主要针对类或接口字段类方法接口方法方法类型方法句柄调用点限定符这 7 类符号引用进行。 133 | 134 | ### 初始化(Initialization) 135 | 136 | > 类加载过程的最后一个阶段,负责执行类构造器方法 ``。 137 | 138 | * **自动生成:** 由javac编译自动生成``,是所有静态变量赋值和静态代码块的集合。 139 | * **非法前向引用:** 静态语句块中只能访问定义在其之前的变量。 140 | * 父类的 `` 方法会先于子类的 `` 方法执行。 141 | * 如果一个类没有静态语句块和静态变量的赋值操作,那么编译器可能不会生成 `` 方法。 142 | * `+TraceClassLoading` 查看类加载过程 143 | * 多线程环境下,类的初始化可能会出现并发问题,JVM会保证``方法的线程安全执行。 144 | 145 | ## 类加载器 146 | 147 | > 加载阶段“通过一个类的全限定名来获取描述该类的二进制字节流”,这个动作的代码被称为“类加载器”(Class Loader)。 148 | 149 | ### 类与类加载器 150 | 151 | 一个类在JVM中由其完全限定名和对应的类加载器共同确定唯一性。 152 | 即使两个类具有相同的完全限定名,由不同的类加载器加载的,JVM也会将它们视为两个“不相等”的类。 153 | 154 | * “不相等” 包括Class对象的`equals`方法,`isAssignableFrom()`方法,`isInstance()`方法的返回结果。 155 | 156 | **不同的类加载器对instanceof关键字运算的结果的影响** 157 | 158 | ```java 159 | /** 160 | * 类加载器与instanceof关键字演示 161 | * 162 | * @author zzm 163 | */ 164 | public class ClassLoaderTest { 165 | public static void main(String[] args) throws Exception { 166 | // 创建一个自定义的类加载器 167 | ClassLoader myLoader = new ClassLoader() { 168 | @Override 169 | public Class loadClass(String name) throws ClassNotFoundException { 170 | try { 171 | // 获取类名对应的文件名(去除包名,只保留类名) 172 | String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; 173 | // 从当前类路径中加载Class文件 174 | InputStream is = getClass().getResourceAsStream(fileName); 175 | if (is == null) { 176 | // 如果找不到文件,则调用父类加载器加载 177 | return super.loadClass(name); 178 | } 179 | byte[] b = new byte[is.available()]; 180 | is.read(b); 181 | return defineClass(name, b, 0, b.length); 182 | } catch (IOException e) { 183 | throw new ClassNotFoundException(name); 184 | } 185 | } 186 | }; 187 | 188 | Object obj = myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance(); 189 | System.out.println(obj.getClass()); 190 | System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest); 191 | } 192 | } 193 | ``` 194 | 195 | 运行结果: 196 | 197 | ```shell 198 | class org.fenixsoft.classloading.ClassLoaderTest 199 | false 200 | ``` 201 | 202 | ### 双亲委派模型 203 | 204 | 双亲委派模型是Java类加载机制的一种设计模式 ,用于确保类的唯一性和安全性。 205 | 它规定,类加载器在接收到类加载请求时,首先将该请求委派给父类加载器处理。 206 | 如果父类加载器无法完成加载,子类加载器才会尝试加载该类。 207 | 208 | ![类加载器双亲委派模型(JDK 8及之前)](https://img.geekyspace.cn/pictures/2024/202408100244787.png) 209 | 210 | * 从Java虚拟机的角度,类加载器分为两类: 211 | * 启动类加载器(Bootstrap ClassLoader):这是Java虚拟机的一部分,使用`C++`实现,负责加载核心类库,如`rt.jar`中的类。这个类加载器不可被Java程序直接引用。 212 | * 其他类加载器:这些是由Java实现的类加载器,继承自`java.lang.ClassLoader`,并且独立存在于虚拟机之外。 213 | * 从Java开发人员的角度,可以细分为三层类加载器: 214 | * **启动类加载器**(Bootstrap ClassLoader):加载`\lib`目录中的核心类库。 215 | * **扩展类加载器**(Extension ClassLoader):加载`\lib\ext`目录或通过java.ext.dirs指定路径中的扩展类库。 216 | * **应用程序类加载器**(Application ClassLoader):加载用户类路径(ClassPath)上的所有类库,通常是程序的默认类加载器。 217 | 218 | **双亲委派模型的实现** 219 | 220 | ```java 221 | protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 222 | // 首先,检查请求的类是否已经被加载过了 223 | Class c = findLoadedClass(name); 224 | 225 | if (c == null) { // 如果该类还未被加载 226 | try { 227 | if (parent != null) { // 如果存在父类加载器 228 | c = parent.loadClass(name, false); // 让父类加载器尝试加载该类 229 | } else { 230 | c = findBootstrapClassOrNull(name); // 如果没有父类加载器,尝试用引导类加载器加载 231 | } 232 | } catch (ClassNotFoundException e) { 233 | // 如果父类加载器抛出 ClassNotFoundException 234 | // 说明父类加载器无法完成加载请求,继续在本加载器中寻找 235 | } 236 | 237 | if (c == null) { 238 | // 在父类加载器无法加载时 239 | // 调用本加载器的 findClass 方法来加载类 240 | c = findClass(name); 241 | } 242 | } 243 | 244 | if (resolve) { 245 | resolveClass(c); 246 | } 247 | 248 | return c; // 返回加载的类 249 | } 250 | ``` 251 | 252 | ### 破坏双亲委派模型 253 | 254 | 双亲委派模型并不是强制性约束的模型,直到Java模块化出现为止,出现过3次较大规模“被破坏”的情况。 255 | 256 | **第 1 次被破坏:** 257 | 258 | 在JDK 1.2之前,由于没有双亲委派模型的约束,开发者可以直接覆盖 `loadClass()` 方法。 259 | 为了兼容现有代码并引导开发者遵循双亲委派模型,JDK 1.2引入了 `findClass()` 260 | 方法,建议开发者重写该方法而非直接覆盖 `loadClass()`。 261 | 262 | * **loadClass():** 双亲委派模型的实现,父类加载异常时,调用 `findClass()` 方法进行加载。 263 | * **findClass():** 自定义类加载器具体实现。 264 | 265 | --- 266 | 267 | **第 2 次被破坏:** 268 | 269 | 由于基础类型有时需调用用户代码,如JNDI(Java命名和目录接口)加载SPI(服务提供者接口)代码,这打破了双亲委派模型的层次结构来逆向使用类加载器。 270 | 271 | * Java引入一个不太优雅的设计:线程上下文类加载器,来实现SPI加载。当SPI的服务提供者多于一个的时候,代码就只能根据具体提供者的类型来硬编码判断。 272 | * 为了消除这种极不优雅的实现方式,在JDK 6时,引入了 `java.util.ServiceLoader`,以 `META-INF/services` 273 | 中的配置信息,辅以责任链模式,提供了更合理的SPI加载方式。 274 | 275 | --- 276 | 277 | **第 3 次被破坏:** 278 | 279 | 由于用户对程序动态性的追求,如代码热替换(Hot Swap)和模块热部署(Hot Deployment),导致双亲委派模型在OSGi中再次“被破坏”。 280 | 这种“动态性”在大型系统或企业级软件中尤其重要,因为频繁重启生产系统可能会被视为生产事故。 281 | 282 | * **OSGi中的类加载器机制:** 283 | * 每个OSGi模块(Bundle)都有一个独立的类加载器。更换 Bundle 时,同时替换其类加载器,实现代码热替换。 284 | * **类加载顺序:** 285 | * 以 `java.*` 开头的类由父类加载器加载。 286 | * 委派列表内的类由父类加载器加载。 287 | * Import 列表中的类由 Export 该类的 Bundle 加载器加载。 288 | * 当前 Bundle 的 ClassPath 中的类由自己的类加载器加载。 289 | * Fragment Bundle 中的类由对应的类加载器加载。 290 | * Dynamic Import 列表中的类由对应 Bundle 的类加载器加载。 291 | * 若以上均失败,则类加载失败。 -------------------------------------------------------------------------------- /src/md/spring-boot/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spring Boot 入门教程 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-03-15 12 | category: Spring Boot 13 | tag: 14 | - spring boot 15 | --- 16 | 17 | # Spring Boot 教程 18 | 19 | -------------------------------------------------------------------------------- /src/md/spring-boot/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SpringBoot 快速入门 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-03-15 12 | category: Spring Boot 13 | tag: 14 | - spring boot 15 | order: 1 16 | --- 17 | 18 | # SpringBoot 快速入门 19 | -------------------------------------------------------------------------------- /src/md/spring-data-jpa/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spring Data JPA 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: 2024-04-04 12 | category: Spring Data 13 | tag: 14 | - spring data jpa 15 | --- 16 | 17 | # Spring Data JPA 18 | 19 | ## 目录 20 | 21 | * [Spring Data JPA 快速入门](/spring-data-jpa/jetbrains/getting-started) 22 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spring 核心技术 3 | icon: spring 4 | author: 流浪码客 5 | isOriginal: true 6 | date: 2024-03-18 7 | category: Spring 8 | tag: Spring Framework 9 | --- 10 | 11 | # Spring 核心技术 12 | 13 | ## 核心技术 14 | 15 | Spring 框架的核心技术主要包括: 16 | 17 | * **依赖注入(dependency injection),也称为控制反转(IoC)** 18 | * 事件(events) 19 | * 资源(resources) 20 | * 国际化(i18n) 21 | * 数据验证(validation) 22 | * 数据绑定(data binding) 23 | * 类型转换(type conversion) 24 | * SpEL(Spring Expression Language) 25 | * **面向切面编程(AOP)** 26 | 27 | ## Spring IoC容器和Bean简介 28 | 29 | **Spring IoC(控制反转)也被称为依赖注入(DI)** 30 | 31 | 它是一个过程,对象仅通过构造参数、工厂方法参数或在**对象实例**被构造函数或工厂方法返回后,在其上设置的属性来定义它们的依赖关系。 32 | 在IoC容器创建Bean时,它会自动注入这些依赖项。 不再需要通`直接构造依赖项`或使用`服务定位器模式`等方式来管理对象的实例化或位置, 33 | 而是交由IoC容器来管理,因此称为**控制反转**。 34 | 35 | [org.springframework.beans](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/beans/factory/BeanFactory.html) 36 | 和 [org.springframework.context](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/ApplicationContext.html) 37 | 包是Spring Framework的IoC容器的基础。 38 | [BeanFactory](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/beans/factory/BeanFactory.html) 39 | 接口提供了一种高级配置机制,能够管理任何类型的`object`对象。 40 | [ApplicationContext](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/ApplicationContext.html) 41 | 是 BeanFactory 的一个子接口。它增加了: 42 | 43 | * 与Spring的AOP特性更好的集成 44 | * `Message resource`消息资源处理(用于国际化) 45 | * `Event publication`事件发布 46 | * 应用层特定的上下文,例如Web应用程序的`WebApplicationContext` 47 | 48 | 简而言之,`BeanFactory` 提供了配置框架和基本功能,`ApplicationContext` 添加了更多企业特定的功能。 49 | `ApplicationContext` 是 `BeanFactory` 的一个超集。 在特别要求轻量级应用程序的情况下,可以考虑使用`BeanFactory`。 50 | 51 | 想要了解 BeanFactory 52 | 参阅 [BeanFactory API](https://docs.spring.io/spring-framework/reference/core/beans/beanfactory.html) 。 53 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/beans-definition.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bean 定义(Definition) 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-03-18 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # Bean 定义(Definition) 11 | 12 | ## 概述 13 | 14 | > 构建应用程序主干并由Spring IoC 容器管理的对象称为 Bean。 15 | > 16 | > 在容器中,Bean的定义表示为`org.springframework.beans.factory.config.BeanDefinition`对象。 17 | 18 | **BeanDefinition包含以下元数据:** 19 | 20 | * **全路径类名**:通常,被定义为Bean的实现类 21 | * **行为配置元素**:说明了Bean在容器中的行为方式,例如作用域scope、生命周期回调等 22 | * **依赖关系**:描述Bean与其他Bean之间的依赖关系,包括依赖注入,依赖查找等 23 | * 其他配置信息:如:管理连接池的Bean可以配置pool的大小限制,使用的连接数量等 24 | 25 | 该元数据转换为组成每个Bean定义的一组属性。 下表介绍了这些属性: 26 | 27 | | 属性 | 描述 | 28 | |--------------------------|--------------| 29 | | Class | Bean的全限定类名 | 30 | | Name | Bean的名称 | 31 | | Scope | Bean的作用域 | 32 | | Constructor arguments | Bean的构造函数参数 | 33 | | Properties | Bean的属性 | 34 | | Autowiring mode | Bean的自动装配模式 | 35 | | Lazy initialization mode | Bean的延迟初始化模式 | 36 | | Initialization method | Bean的初始化方法 | 37 | | Destruction method | Bean的销毁方法 | 38 | 39 | ## 命名Beans 40 | 41 | 在Spring IoC容器中,每个Bean都必须有一个**唯一的标识符**(identifier),如果需要一个以上的标识符,多余的标识符可以被视为**别名**。 42 | 43 | **基于XML的配置元数据** 44 | 45 | | 属性/元素 | 描述 | 46 | |---------|----------------------------------------| 47 | | `id` | Bean的唯一标识符;默认采取小写字母开头的驼峰命名法 | 48 | | `name` | Bean的别名,可以有多个别名;用逗号(`,`)、分号(`;`)或空格分隔 | 49 | | `alias` | 与name作用相同,都是用于指定Bean的别名(Spring 5.0中废弃) | 50 | | `ref` | 引用其他Bean | 51 | 52 | 建议为每个Bean提供一个唯一的`id`属性,以便使用`ref`属性引用该Bean。 53 | 不提供Bean名称的动机与使用[内部Bean](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html#beans-inner-beans) 54 | 和[自动装配协作者](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-autowire.html)有关。 55 | 56 | **使用`Introspector`生成默认Bean名称** 57 | 58 | 在classpath中的组件扫描,Spring会自动为未命名的组件按照`java.beans.Introspector`的规则生成一个默认的bean名称 59 | 60 | 默认将类名的转为==小写字母开头的驼峰命名法==;如`com.example.MyBean`类的Bean名称是`myBean`。 61 | 特殊的,如果类名的第一个和第二个字符都是大写字母,则 Spring 会保留原始的大小写;如`URL`类的默认Bean名称还是`URL` 62 | 63 | > 如果你使用**Java配置**,`@Bean` 注解可以被用来提供别名。 64 | > 参阅 [使用@Bean注释](https://docs.spring.io/spring-framework/reference/core/beans/java/bean-annotation.html)。 65 | 66 | ## 实例化Bean 67 | 68 | Bean定义(definition)本质上是创建一个或多个对象的“配方”。 69 | 容器在被询问时查看指定名称的Bean的“配方”,并使用该Bean定义所封装的元数据来创建(或获取)一个对象。 70 | 71 | **使用XML配置元数据实例化Bean** 72 | 73 | 如果使用基于XML的配置元数据,可以在``元素中的`class`属性中指定要实例化对象的类型。 74 | 这个`class`属性通常是必需的,用于定义内部`BeanDefinition`对象实例的`Class`属性。 75 | 对于一些例外情况,参阅 [使用实例工厂方法实例化](#使用实例工厂方法实例化) 76 | 以及 [Bean定义的继承](https://docs.spring.io/spring-framework/reference/core/beans/child-bean-definitions.html)。 77 | 78 | **使用`Class`属性** 79 | 80 | 1. 通过反射调用构造函数创建Bean 81 | * 这种方式类似于Java中的`new`操作符,容器通过反射调用构造函数来创建Bean 82 | ```xml 83 | 84 | ``` 85 | 2. 通过静态工厂方法创建Bean 86 | * 这种方式不太常见,容器会调用一个类上的`static`工厂方法来创建Bean 87 | ```xml 88 | 89 | ``` 90 | 91 | **嵌套类的Bean定义** 92 | 93 | > 如果要为嵌套类配置Bean定义,可以使用嵌套类的==二进制名称==或==源名称==,通过美元符号 (`$`) 或点 (`.`) 分隔。 94 | 95 | 假设有一个名为`OuterClass`的外围类,其中包含一个名为`InnerClass`的嵌套类 96 | 97 | * 二进制名称:`com.example.OuterClass$InnerClass` 98 | * 源名称:`com.example.OuterClass.InnerClass` 99 | 100 | ### 使用构造函数实例化 101 | 102 | 通过构造函数方法创建`Bean`时,所有普通的类都可以被`Spring`使用并与之兼容 103 | 104 | * 被开发的类不需要实现任何特定的接口,或以特定的方式进行编码。只需指定`Bean`类就足够了 105 | * 根据你对该特定`Bean`使用的`IoC`类型,可能需要一个==默认(空)构造函数== 106 | 107 | 通过基于XML的配置元数据,你可以按以下方式指定你的bean类。 108 | 109 | ```xml 110 | 111 | 112 | 113 | 114 | ``` 115 | 116 | 关于向==构造函数传递参数==和==对象被构造后设置对象实例属性==的机制的详细信息,请参阅 [依赖注入](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html)。 117 | 118 | ### 使用静态工厂方法实例化 119 | 120 | 定义一个静态工厂方法创建Bean时,使用`class`属性指定工厂类,使用`factory-method`属性指定工厂方法。 121 | 这种Bean定义的一个用途是在遗留代码中调用`static`工厂,做到==无侵入性==。 122 | 123 | 示例Bean定义,它使用静态工厂方法创建Bean: 124 | 125 | ```xml 126 | 129 | ``` 130 | 131 | 下面的例子展示了一个与Bean定义(definition)一起工作的类: 132 | 133 | ```java 134 | public class ClientService { 135 | private static ClientService clientService = new ClientService(); 136 | private ClientService() {} 137 | 138 | public static ClientService createInstance() { 139 | return clientService; 140 | } 141 | } 142 | ``` 143 | 144 | 关于向==工厂方法提供(可选)参数==以及==从工厂返回对象后设置对象实例属性==的机制,参阅 [依赖和配置详解](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html)。 145 | 146 | ### 使用实例工厂方法实例化 147 | 148 | 使用**实例工厂方法**进行的实例化,与[使用静态工厂方法实例化](#使用静态工厂方法实例化)类似; 149 | 通过从容器中调用现有`Bean`的非静态方法来创建一个新的`Bean`。 150 | 要使用这种机制,请将`class`属性留空,`factory-bean`属性指定工厂Bean,`factory-method`属性指定工厂方法。 151 | 152 | ```xml 153 | 154 | 155 | 156 | 157 | 158 | 159 | 162 | 163 | 166 | ``` 167 | 168 | 以下示例显示了相应的类: 169 | 170 | ```java 171 | public class DefaultServiceLocator { 172 | 173 | private static ClientService clientService = new ClientServiceImpl(); 174 | 175 | private static AccountService accountService = new AccountServiceImpl(); 176 | 177 | public ClientService createClientServiceInstance() { 178 | return clientService; 179 | } 180 | 181 | public AccountService createAccountServiceInstance() { 182 | return accountService; 183 | } 184 | } 185 | ``` 186 | 187 | 这种方法表明,工厂Bean本身可以通过依赖注入(DI)进行管理和配置。参阅 [依赖和配置详解](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html)。 188 | 189 | ::: note 190 | 在 Spring 文档中,“`factory bean`”是指Spring容器中配置的Bean,它通过[实例](#使用实例工厂方法实例化) 191 | 或[静态](#使用静态工厂方法实例化)工厂方法创建对象。 相比之下,`FactoryBean`(注意大小写)是Spring特定 192 | [FactoryBean](https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html#beans-factory-extension-factorybean) 193 | 接口的实现,它允许自定义实例化逻辑。 194 | ::: 195 | 196 | ### 确定Bean的运行时类型 197 | 198 | 在 Spring 应用程序中,要确定特定`Bean`的==运行时类型==可能会有些复杂。 199 | 这是因为`Bean`的类型可能受到多种因素的影响,包括但不限于以下几点: 200 | 201 | 1. **Bean 元数据定义** 202 | * Bean元数据定义中指定的`class`类只是一个初始的类引用 203 | 2. **静态工厂方法和 FactoryBean** 204 | * 与静态工厂方法或者FactoryBean的实现结合使用,可能会导致Bean的实际类型与`class`属性指定的类型不同 205 | 3. **实例工厂方法** 206 | * 与实例工厂方法结合使用,根本不会使用`class`属性指定的类型,而是通过指定的`factory-bean`名称来解决 207 | 4. **AOP 代理** 208 | * AOP代理可能会使用基于接口代理包装Bean实例,目标Bean的实际类型公开有限(只有其实现的的代理接口) 209 | 210 | 考虑到上述所有情况,要了解某个特定Bean的实际运行时类型。 211 | 推荐的方法是使用`BeanFactory`接口的`getType()`方法, 并返回与`BeanFactory.getBean()`调用返回的对象类型相同的类型。 212 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/child-bean-definitions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bean定义继承 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-04-10 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # Bean定义继承 11 | 12 | 一个Bean定义可以包含大量的配置信息,包括构造函数参数、属性值以及容器特定的信息,比如初始化方法、静态工厂方法名称等等。 13 | 一个子Bean定义会从父定义中继承配置数据。子定义可以根据需要覆盖一些值或添加其他值。 14 | 使用父子Bean定义可以节省大量的输入工作。实际上,这是一种模板化的形式。 15 | 16 | 如果你以编程方式使用`ApplicationContext`接口,子Bean定义由`ChildBeanDefinition`类表示。 17 | 大多数用户不会在这个层面上直接操作它们。相反,他们会在诸如`ClassPathXmlApplicationContext`之类的类中以声明性方式配置Bean定义。 18 | 当你使用基于XML的配置元数据时,可以通过使用`parent`属性指定父Bean来表示子Bean定义,将父Bean作为此属性的值。以下示例展示了如何这样做: 19 | 20 | ```xml 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | ``` 34 | 35 | 子Bean定义如果未指定Bean类,则会使用父定义中的Bean类,但也可以覆盖它。 36 | 在后一种情况下,子Bean类必须与父Bean类兼容(即,它必须接受父Bean的属性值)。 37 | 38 | 子Bean定义从父Bean继承作用域(Scope)、构造函数参数值、属性值和方法重写,并有添加新值的选项。 39 | 你指定的任何作用域、初始化方法、销毁(destroy)方法或静态(static)工厂方法设置都会覆盖相应的父设置。 40 | 41 | 剩余的设置始终来自于子Bean定义:依赖(depends on)、自动注入(autowire)模式、依赖检查、singleton以及懒加载(lazy init)。 42 | 43 | 在上面的示例中,通过使用`abstract`属性显式地将父Bean定义标记为抽象。 44 | 如果父定义没有指定类,则需要显式地将父Bean定义标记为抽象,如下面的示例所示: 45 | 46 | ```xml 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | ``` 60 | 61 | 父Bean定义不能单独实例化,因为它是不完整的,并且也明确标记为`abstract`。 62 | 当一个定义是`abstract`的时候,它只能作为纯模板Bean定义使用,用作子定义的父定义。 63 | 尝试单独使用这样的`abstract`父Bean,通过将其作为另一个Bean的`ref`属性引用或者显式地使用`getBean()`方法调用父Bean的ID,都会返回错误。 64 | 同样,容器的内部`preInstantiateSingletons()`方法会忽略那些被定义为抽象的Bean定义。 65 | 66 | ::: note 67 | `ApplicationContext` 默认预设了所有的单例Bean。因此,重要的是(至少对于单例Bean来说), 68 | 如果你有一个(父)Bean定义,你打算只作为模板使用,并且这个定义指定了一个类,你必须确保将`abstract`属性设置为 69 | true,否则应用上下文将尝试预实化`abstract` Bean。 70 | ::: 71 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/dependencies/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 依赖(Dependencies) 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-03-26 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # 依赖(Dependencies) 11 | 12 | 企业应用程序通常不是由单个对象(或`Spring`术语中的`Bean`)构成的。 13 | 即使是最简单的应用程序,也是由多个对象共同协作来呈现给最终用户一个连贯的应用体验。 14 | 15 | 下一节将解释如何从定义独立的Bean开始,逐步实现一个完整的应用程序。 16 | 在这个应用程序中,各个对象将相互协作,实现一个共同的目标。 17 | 18 | ## 章节摘要 19 | 20 | * [依赖注入](./factory-collaborators.md) 21 | * [依赖和配置详解](./factory-properties-detailed.md) 22 | * [使用depends-on](./factory-dependson.md) 23 | * [懒加载Bean](./factory-lazy-init.md) 24 | * [自动装配协作者](./factory-autowire.md) 25 | * [方法注入](./factory-method-injection.md) 26 | 27 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/dependencies/factory-autowire.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 自动装配协作者 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-03-26 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # 自动装配协作者(Autowiring Collaborators) 11 | 12 | Spring容器可以自动装配协作Bean之间的关系。 13 | 你可以通过检查`ApplicationContext`的内容,让Spring自动为你的Bean解析协作对象(其他Bean)。 14 | 15 | **自动装配的优势** 16 | 17 | * **减少手动配置**:自动装配可以显著减少对手动指定属性或构造方法参数的需求。 18 | [其他机制](https://docs.spring.io/spring-framework/reference/core/beans/child-bean-definitions.html) 19 | ,如bean模板,在这方面也是非常有价值的。 20 | * **动态更新配置**:随着项目的发展,对象可能会增加新的依赖。自动装配能够适应这种变化,自动满足新的依赖关系,而无需手动更新配置。 21 | 这一点在项目的迭代开发过程中尤为有用。同时,当项目稳定下来后,开发者仍然可以选择切换到显式装配,以获得更精确的控制。 22 | 23 | **四种自动装配模式** 24 | 25 | 在使用XML配置时, 26 | (参阅 [依赖注入](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html)), 27 | 可以通过``元素的`autowire`属性来定义Bean的自动装配模式。 28 | Spring提供了四种自动装配模式,允许你为每个Bean单独指定使用哪种模式。以下是这四种模式的详细描述: 29 | 30 | | 模式 | 描述 | 31 | |-------------|----------------------------------------------------------------------------------------------------------------------------------------------| 32 | | no | **默认模式**:
不执行自动装配。开发者需要使用`ref`标签显式声明依赖关系。 | 33 | | byName | **按名称自动装配**:
容器会寻找一个属性名称或setter方法名称相匹配的Bean定义将其注入。
例如:Bean的属性名为`master`(也就是说,它有一个`setMaster(..)`方法),Spring会寻找名为`master`的Bean定义来注入。 | 34 | | byType | **按类型自动装配**:
容器会寻找与属性类型相匹配的Bean定义将其注入。如果存在多个相同类型的Bean,将抛出异常。 | 35 | | constructor | **按构造函数参数类型自动装配**:
容器会寻找与构造函数参数类型相匹配的Bean定义将其注入。如果没有匹配的Bean定义,将抛出异常。这种模式确保了Bean在创建时其必需的依赖就已经被满足。 | 36 | 37 | 利用`byType`或`constructor`自动装配模式,你可以装配数组(`array`)和泛型集合(`collection`)。 38 | 在这种模式下,Spring容器会自动寻找所有与所需类型相匹配的Bean,并将其注入到定义的数组或集合中,从而满足配置的依赖关系。 39 | 此外,当预期的键(key)类型为`String`时,还可以自动装配一个强类型化的`Map`实例。 40 | 在这个`Map`中,键(key)将是Bean的名称,而值(value)则是所有匹配期望类型的Bean实例。 41 | 42 | > 这样的自动装配`Map`实例,为我们提供了一种便捷的方式来管理和访问一组具有相同类型的Bean,特别是当我们需要根据名称快速查找特定的Bean时。 43 | 44 | ## 自动装配的局限和缺点 45 | 46 | 自动装配在项目中保持一致性时效果最佳。如果自动装配没有被普遍采用,而只有少数Bean定义使用它,可能会引起开发人员的困惑。 47 | 以下是考虑自动装配时需要注意的限制和缺点: 48 | 49 | 1. **显式依赖优先**:当存在显式依赖(`property`或`constructor-arg`)时,自动装配将被忽略 50 | 2. **不适用于基本类型**:自动装配不适用于基本类型,如`int`、`long`、`String`等,因为它们没有Bean名称 51 | 3. **精确性问题**:自动装配不如显式注入精确,因为它依赖于类型匹配,而不是Bean的名称或其他限定符,这可能导致意外的结果 52 | 4. **文档化缺失**:自动装配的依赖关系可能不会自动包含在生成的文档中,因此开发者需要额外的文档或注释来解释Bean之间的依赖关系 53 | 5. **多重匹配问题**:当存在多个Bean定义与自动装配的属性类型匹配时,Spring将抛出异常 54 | 6. **歧义性**:当存在多个Bean定义与自动装配的属性类型匹配时,Spring将无法确定哪个Bean是首选的,就会抛出异常 55 | 56 | 你有以下几种方法来解决**多重匹配问题和歧义性**: 57 | 58 | * 放弃自动装配,使用显式装配 59 | * 通过将Bean定义的`autowire-candidate`属性设置为`false`来排除Bean 60 | * 通过将Bean定义的`primary`属性设置为`true`来指定首选Bean 61 | * 实现更细粒度的控制,可以使用基于注解的配置方式, 62 | 参阅 [基于注解的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html) 63 | 64 | ## 从自动装配中排除Bean 65 | 66 | 在每个Bean定义中,你可以设置`autowire-candidate`属性来控制Bean是否标记为自动装配候选项。 67 | 68 | * 默认情况下,`autowire-candidate`属性的值为`true`,表示Bean是自动装配的候选项 69 | * 属性为`false`时,Bean将被排除在自动装配之外,这一设置对注解式配置 70 | (如[@Autowired](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/autowired.html))同样有效 71 | 72 | ::: note 73 | **注意⚠️**:`autowire-candidate`属性仅影响基于类型的自动装配模式。 74 | 对于按名称的显示引用,`autowire-candidate`属性不起作用。 只要名称匹配,它仍然可以被注入。 75 | ::: 76 | 77 | 你还可以根据`byName`的模式匹配来限制自动装配候选项。 78 | 顶层的``元素接受一个或多个模式,并将其放在 `default-autowire-candidates` 属性中,以逗号分隔。 79 | 例如,要将自动装配候选项限制为任何**名称以Repository结尾的Bean**,提供值为`*Repository`。 80 | 81 | ```xml 82 | 83 | 84 | 85 | ``` 86 | 87 | Bean定义的`autowire-candidate`属性显式设置为 true 的值始终具有优先权。 88 | 对于这种Bean,不适用于模式匹配规则。 89 | 90 | 使用这些技术,你可以精确地控制自动装配的行为,避免不期望的依赖注入,确保Bean的自动装配符合你的设计意图。 91 | 92 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/dependencies/factory-dependson.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用depends-on 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-03-26 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # 使用depends-on 11 | 12 | 如果一个Bean是另一个Bean的依赖项,这意味着一个Bean被设置为另一个Bean的属性。 13 | 可以通过[元素](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-dependson.html#beans-ref-element) 14 | 来实现这一点。 然而,有时Bean之间的依赖关系并不那么直接。 15 | 16 | 举个例子:当一个类中的静态初始化器需要被触发时,比如数据库驱动程序的注册。 17 | `depends-on`属性可以强制容器在初始化`beanOne` Bean之前先初始化指定的`manager` Bean。 18 | 以下示例使用`depends-on`属性来表达对单个Bean的依赖: 19 | 20 | ```xml 21 | 22 | 23 | ``` 24 | 25 | 要表达对多个Bean的依赖,可以将多个Bean名称作为`depends-on`属性的值提供(通过逗号、空格和分号进行分隔): 26 | 27 | ```xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | ``` 35 | 36 | ::: note 37 | `depends-on`属性不仅可以指定初始化时的依赖关系,而且在 38 | [单例](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-singleton) 39 | Bean的情况下,还可以指定相应的销毁时依赖关系。 40 | `depends-on`指定的依赖Bean会在给定Bean本身被销毁之前被首先销毁。因此,`depends-on`也可以控制关闭顺序。 41 | ::: 42 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/dependencies/factory-lazy-init.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 懒加载(Lazy Initialization)Bean 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-03-26 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # 懒加载(Lazy Initialization)Bean 11 | 12 | 在Spring框架中,`ApplicationContext`的默认行为是在启动过程中立即创建并配置所有的单例Bean。 13 | 这种做法有利于及时发现配置错误或环境问题,避免了错误在应用运行一段时间后才暴露。 14 | 然而,如果需要改变这一行为,可以通过设置Bean定义为懒加载(lazy-initialized)来实现。 15 | 这样一来,Bean的实例化将被推迟到第一次实际请求该Bean时进行,而不是在应用启动时完成,从而提供了更大的灵活性和控制。 16 | 17 | 在XML中,通过``元素上的`lazy-init`属性来控制这种行为,如下例所示: 18 | 19 | ```xml 20 | 21 | 22 | 23 | 24 | ``` 25 | 26 | 当上述配置被`ApplicationContext`加载启动时,`lazy` Bean不会立即预实例化,而`not.lazy` Bean则会被急切地预实例化。 27 | 28 | 然而,当一个懒加载的Bean作为另一个未标记为懒加载单例Bean的依赖项时,`ApplicationContext`会在启动时创建这个懒加载的Bean, 29 | 因为它必须满足单例Bean的依赖关系。懒加载的Bean会被注入到一个未标记为懒加载的单例Bean中。 30 | 31 | 你还可以通过在``元素上使用`default-lazy-init`属性来控制容器级别的懒加载初始化,如下例所示: 32 | 33 | ```xml 34 | 35 | 36 | 37 | ``` 38 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/dependencies/factory-method-injection.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 方法注入 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-03-26 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # 方法注入 11 | 12 | 大多数应用场景中,容器中的大多数Bean都是[单例(singleton)](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-singleton) 13 | 的。 14 | 当一个单例(singleton)Bean需要与原型(prototype)Bean协作时,传统的注入方式可能不再适用。 15 | 这是因为单例Bean在整个应用生命周期内只创建一次,而原型Bean每次请求时都会创建一个新的实例。 16 | 17 | 一种解决方案是放弃一些控制反转(inversion of control)。 18 | 通过实现[ApplicationContextAware](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-aware) 19 | 接口使Bean A意识到Spring IoC容器 ,并 20 | [使用容器](https://docs.spring.io/spring-framework/reference/core/beans/basics.html#beans-factory-client) 21 | 进行`getBean("B")`调用,每次Bean A需要时请求(新建`new`)Bean B实例。 以下示例展示了这种方法: 22 | 23 | ```java 24 | package fiona.apple; 25 | 26 | // 导入 Spring 框架相关类 27 | import org.springframework.beans.BeansException; 28 | import org.springframework.context.ApplicationContext; 29 | import org.springframework.context.ApplicationContextAware; 30 | 31 | /** 32 | * 使用一个有状态的 Command-style 类来执行一些处理的类。 33 | */ 34 | public class CommandManager implements ApplicationContextAware { 35 | 36 | private ApplicationContext applicationContext; 37 | 38 | public Object process(Map commandState) { 39 | // 获取一个适当的 Command 的新实例 40 | Command command = createCommand(); 41 | // 设置 Command 的状态 42 | command.setState(commandState); 43 | return command.execute(); 44 | } 45 | 46 | protected Command createCommand() { 47 | // 注意 Spring API 的依赖! 48 | return this.applicationContext.getBean("command", Command.class); 49 | } 50 | 51 | public void setApplicationContext( 52 | ApplicationContext applicationContext) throws BeansException { 53 | this.applicationContext = applicationContext; 54 | } 55 | } 56 | ``` 57 | 58 | 上述方法并不理想,因为业务代码意识到并与Spring框架耦合。 59 | 方法注入(Method Injection)是Spring IoC容器的一种高级特性,可以干净地处理这种用例。 60 | 61 | > 你可以在[这篇博客文章](https://spring.io/blog/2004/08/06/method-injection/)中阅读更多关于方法注入的动机。 62 | 63 | ## 查找方法依赖注入 64 | 65 | ## 任意方法替换 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/md/spring-framework/core/ioc-container.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: IoC 容器(Container) 3 | author: 流浪码客 4 | isOriginal: true 5 | date: 2024-03-18 6 | category: Spring 7 | tag: Spring Framework 8 | --- 9 | 10 | # IoC 容器(Container) 11 | 12 | ## 概述 13 | 14 | > Spring IoC(控制反转)容器是Spring框架的核心。 15 | > `org.springframework.context.ApplicationContext`接口代表Spring IoC容器,负责实例化、配置和组装`Bean`。 16 | 17 | Spring提供了几个 ApplicationContext 接口的实现,在独立应用程序中,最常用的是: 18 | 19 | * [ClassPathXmlApplicationContext](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/support/ClassPathXmlApplicationContext.html) 20 | * [FileSystemXmlApplicationContext](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/support/FileSystemXmlApplicationContext.html) 21 | * 等等 22 | 23 | **支持以XML、Java注释或Java代码作为配置元数据的格式** 24 | 25 | 虽然`XML`一直是定义配置元数据的传统格式, 但你可以通过提供少量的`XML`配置来指定容器使用`Java注解`或`Java代码`作为元数据格式。 26 | 以声明式方式启用对这些元数据格式的支持,从而更灵活地定义应用程序的配置信息。 27 | 28 | **为Web应用程序提供方便的ApplicationContext实例化** 29 | 30 | 在大多数应用场景中,无需手动编写代码来实例化**Spring IoC**容器; 31 | 例如:在Web应用场景中,通常只需要在应用程序的`web.xml`文件中编写 8 32 | 行(或更多)[模板式的Web描述符](https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html#context-create) 33 | 即可初始化`ApplicationContext` 34 | 35 | **解析Spring框架的工作原理:==应用程序类==与==配置元数据==的整合** 36 | 37 | 下图表展示了Spring框架的工作原理高层视图。通过将你的应用程序类与配置元数据结合起来, 38 | 一旦`ApplicationContext`被创建和初始化,你就获得了一个完全配置且可执行的系统或应用程序。 39 | 40 | ![Spring IoC容器](https://img.geekyspace.cn/pictures/2024/202403181756387.png) 41 | 42 | ## 配置元数据 43 | 44 | 如上图所示,Spring IoC容器消费配置元数据。 45 | 这种配置元数据代表了你作为一个应用开发者,如何告诉Spring容器在你的应用中实例化、配置和组装对象。 46 | 47 | ::: note 48 | **注意⚠️**:Spring IoC容器本身与实际配置元数据的编写格式完全解耦。 49 | 如今,许多开发者选择使用[基于Java的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/java.html) 50 | 来构建他们的Spring应用程序。 51 | ::: 52 | 53 | 有关在`Spring`容器中使用其他形式的元数据信息,参阅: 54 | 55 | * [基于XML的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html) 56 | * [基于注解的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html)(Spring 57 | 2.5开始支持) 58 | * [基于Java的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/java.html)(Spring 59 | 3.0开始支持;参阅 [@Configuration](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/Configuration.html), [@Bean](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/Bean.html), 60 | [@Import](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/Import.html), 61 | 和 [@DependsOn](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/DependsOn.html) 62 | 注解) 63 | 64 | Spring的配置包含至少一个,通常是多个``元素。容器必须管理这些定义的bean。 65 | 66 | * XML配置:将这些Bean配置为顶层 `` 元素内的 `` 元素 67 | * Java配置:将这些Bean配置为`@Configuration`类中的`@Bean`注解的方法 68 | 69 | 这些Bean的定义对应于构成应用程序的实际对象, 70 | 如服务层对象,持久层对象(Dao),表示层对象(Web控制器),基础设施对象(JPA EntityManagerFactory),JMS队列等。 71 | 通常,人们不会在容器中配置细粒度的`domain`对象,因为创建和加载`domain`对象的通常是`repository`和`service`层逻辑的责任。 72 | 73 | 下面的例子显示了基于XML的配置元数据的基本结构: 74 | 75 | ```xml 76 | 77 | 81 | 82 | (1) (2) 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ``` 94 | 95 | 1. `id`属性是一个字符串,用于==唯一标识==Bean 96 | 2. `class`属性是一个字符串,用于指定Bean的==完整类名==(包括包名) 97 | 98 | `id`属性的值可以用来指代其他Bean的`ref`属性,从而实现Bean之间的依赖关系。 99 | 参阅 [依赖](https://docs.spring.io/spring-framework/reference/core/beans/dependencies.html)。 100 | 101 | ## 实例化容器 102 | 103 | 提供给`ApplicationContext` 104 | 构造函数的一条或多条路径是==资源字符串==,它让容器从各种外部资源(如本地文件系统、Java `CLASSPATH` 105 | 等)加载配置元数据。 106 | 107 | ```java 108 | ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); 109 | ``` 110 | 111 | ::: note 112 | 了解更多关于[资源加载](https://docs.spring.io/spring-framework/reference/core/resources.html)的信息; 113 | 它提供了一种简单的方法,可以从`URI`语法中定义的位置读取`InputStream`。 特别是,`Resource`路径被用来构建应用程序上下文, 如 114 | [Application Context和资源路径](https://docs.spring.io/spring-framework/reference/core/resources.html#resources-app-ctx) 115 | 中所述。 116 | ::: 117 | 118 | 以下示例显示了**服务层对象**`services.xml` 配置文件: 119 | 120 | ```xml 121 | 122 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | ``` 138 | 139 | 以下示例显示**数据访问对象**(data access object)`daos.xml` 文件: 140 | 141 | ```xml 142 | 143 | 147 | 148 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | ``` 161 | 162 | 在前面的示例中,服务层由 `PetStoreServiceImpl` 类和两个类型为 `JpaAccountDao` 和 `JpaItemDao` 的数据访问对象组成(基于JPA对象-关系映射标准)。 163 | 164 | * `property name` 元素指的是`JavaBean`属性的名称 165 | * `ref` 元素指的是引用另一个`Bean`定义的名称 166 | 167 | `id` 和 `ref`元素之间的这种联系,表达了协作对象之间的依赖关系。 168 | 有关配置对象依赖项的详细信息,参阅 [依赖](https://docs.spring.io/spring-framework/reference/core/beans/dependencies.html)。 169 | 170 | ## 使用容器 171 | 172 | `ApplicationContext`是一个高级工厂的接口,能够维护不同Bean及其依赖关系的注册表。 173 | 通过使用方法 `T getBean(String name, Class requiredType)`,你可以检索到Bean的实例。 174 | 175 | `ApplicationContext`可以让你读取Bean定义(definition)并访问它们,如下例所示。 176 | 177 | ```java 178 | // 创建和配置Bean 179 | ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); 180 | 181 | // 检索配置的实例 182 | PetStoreService service = context.getBean("petStore", PetStoreService.class); 183 | 184 | // 使用配置的实例 185 | List userList = service.getUsernameList(); 186 | ``` 187 | 188 | **不直接依赖于Spring的API** 189 | 190 | > 理想情况下,应用程序代码不应该直接依赖于Spring的API,而是通过元数据(如自动装配`@Autowired`注解)声明对特定Bean的依赖。 191 | 192 | 虽然 ApplicationContext 接口提供了一些检索 Bean 的方法,如 getBean() 等,但在设计上,应该避免直接依赖这些方法。 193 | 194 | 例如,Spring与Web框架的集成为各种**Web框架组件**(如Controller控制器和JSF管理的Bean)提供了依赖注入的能力, 195 | 使得你可以通过元数据(如`@Autowired`注解)声明对特定`Bean`的依赖,而不必直接调用`getBean()`等方法。 196 | 这样可以使代码更加模块化、可维护性更高。 197 | -------------------------------------------------------------------------------- /src/md/spring-framework/overview/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spring Framework 概述 3 | icon: spring 4 | author: 流浪码客 5 | isOriginal: true 6 | date: 2024-03-15 7 | category: Spring 8 | tag: Spring Framework 9 | star: true 10 | sticky: true 11 | --- 12 | 13 | # Spring Framework 概述 14 | 15 | ## Spring Framework 简介 16 | 17 | Spring是一个开源、轻量级、依赖注入(DI)容器和框架,用于构建Java企业应用程序。 18 | 19 | * Spring 官网:[spring.io](https://docs.spring.io/spring-framework/reference/) 20 | * Spring 中文网:[springdoc.cn](https://springdoc.cn/spring/) 21 | 22 | ## 为什么使用Spring? 23 | 24 | > 官网解释:[why-spring](https://spring.io/why-spring) 25 | > 26 | > Spring让Java编程变得更快、更容易、更安全。 27 | > Spring对速度、简单性和生产力的关注使其成为世界上最受欢迎的Java框架。 28 | 29 | 我们使用了许多Spring框架提供的工具,并受益于许多==开箱即用==的解决方案, 30 | 无需担心编写大量额外的代码,因此这确实为我们节省了时间和精力。 31 | 32 | ## 核心思想 33 | 34 | Spring的核心思想是 **控制反转(IOC)** 和 **面向切面编程(AOP)**。 35 | 36 | **控制反转(IoC)** 37 | 38 | 控制反转是一种设计模式,它将对象的创建和对象之间的依赖关系的管理交给了Spring IoC容器。 39 | 在传统的开发模式中,对象的创建和对象之间的依赖关系的管理都是由程序员来完成的。 40 | 41 | **面向切面编程(AOP)** 42 | 43 | 面向切面编程是一种编程范式,它将程序的业务逻辑和系统级服务(如日志,事务,安全等)分开,通过横切关注点的方式来解耦。 44 | 在传统的开发模式中,业务逻辑和系统级服务是混在一起的,这样会导致代码的复杂性增加。 45 | 46 | ## 版本支持 47 | 48 | 我们建议尽可能从Maven Central升级到最新的**Spring Framework 6.0.x / 5.3.x** 版本 49 | 50 | 在Spring Framework 6.0中, Spring需要Java 17+。 51 | 52 | * 6.2.x (2024年11月) - 下一个功能分支 53 | * 6.1.x (2023年11月) - 即将推出的功能分支 54 | * 6.0.x (2022年11月) - 主要生产线,基于JDK 17和Jakarta EE 9 55 | * 5.3.x - 第五代最终功能分支,长期支持,支持JDK 8、11、17和Java EE 8 56 | * 4.3.x - EOL (2020年12月31日),不再提供维护和安全补丁 57 | * 3.2.x - EOL (2016年12月31日),不再提供维护和安全补丁 58 | 59 | 您可以在 [spring.io#support](https://spring.io/projects/spring-framework#support)上找到有关官方支持日期的更多信息。 60 | 61 | ## 入门指南 62 | 63 | 使用[Spring Boot](https://spring.io/projects/spring-boot)来快速创建生产就绪的Spring应用程序 64 | 65 | * 您可以通过[start.spring.io](start.spring.io)生成基本项目 66 | * 或者遵循"[入门指南](https://spring.io/guides)" 67 | 之一,例如"[开始构建RESTful Web服务](https://spring.io/guides/gs/rest-service/)" 68 | -------------------------------------------------------------------------------- /src/md/spring-framework/overview/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spring Framework 快速开始 3 | icon: spring 4 | author: 流浪码客 5 | isOriginal: true 6 | date: 2024-03-15 7 | category: Spring 8 | tag: Spring Framework 9 | --- 10 | 11 | # Spring Framework 快速开始 12 | 13 | > **IntelliJ IDEA** 提供的专用项目向导,您能够快速创建Spring应用程序,这也是创建Spring应用程序的最佳方式之一。 14 | 15 | 参考🚀 :[Jetbrains 创建第一个Spring应用程序](https://www.jetbrains.com/help/idea/your-first-spring-application.html) 16 | 17 | 在本教程中,您将学习如何公开HTTP端点并将其映射到一个方法,当用户通过Web浏览器访问时,该方法会向用户返回问候信息。 18 | 19 | ## 创建 Spring Boot项目 20 | 21 | 1. 在主菜单中,转到 **文件(File) | 新建(New) | 项目(Project)** 22 | 2. 在 **新建项目(New Project)** 对话框中,选择 `Spring Initializr` 23 | 3. 指定项目的名称 ==spring-boot-tutorial==,单击 **下一步(Next)** 继续 24 | 25 | ![Spring Initializr in the New Project wizard](https://img.geekyspace.cn/pictures/2024/spring-new-project-initializr.png) 26 | 27 | 4. 在**Web**组下选择**Spring Web**依赖项,然后单击 **创建(Create)** 生成并设置项目 28 | 29 | ![Spring Dependencies in the New Project wizard](https://img.geekyspace.cn/pictures/2024/spring-new-project-dependencies.png) 30 | 31 | ## 添加 sayHello() 方法 32 | 33 | `Spring Initializr` 会创建一个带有 `main()` 方法的类来启动你的 Spring 应用程序。 34 | 35 | 1. 转到 "**导航(Navigate) | 文件(File )**",搜索`SpringBootTutorialApplication.java`文件并打开 36 | 37 | ![Using Go To File to open SpringBootTutorialApplication.java](https://img.geekyspace.cn/pictures/2024/spring-boot-tutorial-gotofile.png) 38 | 39 | 2. 添加 `sayHello()` 方法,并包含所有必要的注解和导入: 40 | 41 | ```java 42 | package com.example.springboottutorial; 43 | 44 | import org.springframework.boot.SpringApplication; 45 | import org.springframework.boot.autoconfigure.SpringBootApplication; 46 | import org.springframework.web.bind.annotation.GetMapping; 47 | import org.springframework.web.bind.annotation.RequestParam; 48 | import org.springframework.web.bind.annotation.RestController; 49 | 50 | @SpringBootApplication 51 | @RestController 52 | public class SpringBootTutorialApplication { 53 | 54 | public static void main(String[] args) { 55 | SpringApplication.run(SpringBootTutorialApplication.class, args); 56 | } 57 | 58 | @GetMapping("/hello") 59 | public String sayHello(@RequestParam(value = "myName", defaultValue = "World") String name) { 60 | return String.format("Hello %s!", name); 61 | } 62 | 63 | } 64 | ``` 65 | 66 | `sayHello()`方法接受一个名字参数,并返回与参数值组合的`Hello`单词。其余的工作由添加Spring注解来处理: 67 | 68 | * `@RestController`注解将`SpringBootTutorialApplication`类标记为请求处理程序(==REST控制器==)。 69 | * `@GetMapping("/hello")`注解将`sayHello()`方法==映射==到GET请求的`/hello`路径。 70 | *` @RequestParam`注解将名字**方法参数**==映射==到`myName` Web请求参数。如果您在Web请求中未提供`myName` 71 | 参数,则默认为`World`。 72 | 73 | ## 运行Spring应用程序 74 | 75 | **IntelliJ IDEA** 创建了一个` Spring Boot` 运行配置,您可以使用它来运行新 `Spring` 应用程序。 76 | 77 | * 如果选择了运行配置,请按 `Shift` `F10`键。 78 | * 还可以点击 `SpringBootTutorialApplication.java` 文件` main()`方法旁边的▶️图标运行。 79 | 80 | 默认情况下,IntelliJ IDEA 会在运行工具窗口中显示你正在运行的 Spring Boot 应用程序。 81 | 82 | ![The Run tool window with a running Spring Boot application](https://img.geekyspace.cn/pictures/2024/spring-boot-demo-run-console_dark.png) 83 | 84 | 控制台选项卡显示 Spring 日志消息的输出。 85 | 默认情况下,内置的 Apache Tomcat 服务器正在监听端口 8080。 86 | 打开你的网络浏览器并访问 [http://localhost:8080/hello](http://localhost:8080/hello)。 87 | 如果你操作正确,你应该会看到你的应用程序以 Hello World! 回应。 88 | 89 | ![Spring Boot Hello World response in the browser](https://img.geekyspace.cn/pictures/2024/spring-boot-web-browser-hello-world.png) 90 | 91 | 这是默认的响应。你可以在你的网络请求中提供一个参数,让应用程序知道如何适当地问候你。 92 | 例如,尝试访问 [http://localhost:8080/hello?myName=Human](http://localhost:8080/hello?myName=Human)。 93 | 94 | ## 添加主页 95 | 96 | 创建的 Spring Boot 应用程序在 `/hello` 路径下有一个端点可用。 97 | 然而,如果你在 [http://localhost:8080/](http://localhost:8080/) 中打开你的应用程序的根上下文,你会收到错误,因为没有定义根资源。 98 | 让我们添加一个静态 HTML 首页,其中包含指向你端点的链接。 99 | 100 | 1. 请在 `/src/main/resources/static/` 下创建 `index.html` 文件。 101 | 2. 你可以修改默认模板或者使用以下 HTML 代码替换它: 102 | 103 | ```html 104 | 105 | 106 | 107 | 您的第一个 Spring 应用程序 108 | 109 | 110 | 111 |

问候全世界!

112 | 113 | 114 |
115 | 116 | 117 | 118 |
119 |
120 | 121 | 122 | ``` 123 | 124 | 3.在运行工具窗口中,点击 "**重新运行**" 按钮,或按下 `Shift` + `F10`。 125 | 现在你的应用程序将会在 [http://localhost:8080/](http://localhost:8080/) 上作为根资源提供 `index.html` 页面。 126 | 127 | ![image-20240316015405566](https://img.geekyspace.cn/pictures/2024/image-20240316015405566.png) 128 | 129 | ## 下一个教程 130 | 131 | 这个简单的应用程序演示了如何开始使用 Spring。 要了解 IntelliJ IDEA 如何帮助你编写代码并在运行时管理应用程序, 132 | 请参考下一个教程,该教程重点介绍更高级的 [Spring 支持功能](https://www.jetbrains.com/help/idea/spring-support-tutorial.html)。 133 | -------------------------------------------------------------------------------- /src/md/template/blog template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{title}} 3 | shortTitle: 4 | description: 5 | icon: 6 | cover: 7 | author: 流浪码客 8 | isOriginal: true 9 | sticky: false 10 | star: false 11 | date: {{date}} 12 | category: 分类 13 | tags: 14 | - 标签 15 | --- 16 | # {{title}} 17 | 18 | ## {{title}} 19 | 20 | --------------------------------------------------------------------------------