[{"data":1,"prerenderedAt":2541},["ShallowReactive",2],{"\u002Fposts\u002F7dc99e0":3,"surround-\u002Fposts\u002F7dc99e0":2535},{"id":4,"title":5,"body":6,"categories":2511,"date":2513,"description":2514,"draft":2515,"extension":2516,"image":2517,"meta":2518,"navigation":351,"path":2520,"permalink":2520,"published":2521,"readingTime":2522,"recommend":194,"references":2521,"seo":2527,"sitemap":2528,"stem":2529,"tags":2530,"type":2533,"updated":2513,"__hash__":2534},"content\u002Fposts\u002F2026\u002F从零手写一个 Spring Boot Starter：深入理解自动装配原理.md","从零手写一个 Spring Boot Starter：深入理解自动装配原理",{"type":7,"value":8,"toc":2500},"minimark",[9,24,27,36,41,44,115,118,122,125,239,242,245,835,839,846,852,863,866,869,919,923,926,929,936,939,946,951,954,957,1457,1461,1464,1467,1732,1736,1739,1742,2399,2402,2405,2475,2478,2496],[10,11,12,21],"blockquote",{},[13,14,15,16,20],"p",{},"做 SpringBoot 项目开发的同学都知道，平时项目中会用到很多第三方的 starter 依赖，比如 MyBatisPlus 的依赖，",[17,18,19],"code",{"code":19},"mybatis-plus-spring-boot3-starter","。有没有好奇过这个依赖是怎么开发出来的，为什么引入后就可以使用很多依赖里的功能？",[13,22,23],{},"本文将手把手带你写一个输出一个类似 MyBatisPlus 启动后控制台输出一个 Banner 的 SpringBootStarter",[13,25,26],{},"本次开发的源码已同步GitHub仓库",[28,29],"link-card",{"className":30,"icon":33,"link":34,"title":35},[31,32],"gradient-card","active","https:\u002F\u002Fgithub.githubassets.com\u002Ffavicons\u002Ffavicon.svg","https:\u002F\u002Fgithub.com\u002Fdhbxs\u002FBannerStarter","BannerStarter",[37,38,40],"h2",{"id":39},"场景启动器-starter","场景启动器 Starter",[13,42,43],{},"一个最简单的场景启动器一般包含已下几个部分",[45,46,47,63],"table",{},[48,49,50],"thead",{},[51,52,53,57,60],"tr",{},[54,55,56],"th",{},"组件",[54,58,59],{},"扮演角色",[54,61,62],{},"功能描述",[64,65,66,78,89,104],"tbody",{},[51,67,68,72,75],{},[69,70,71],"td",{},"BannerService",[69,73,74],{},"业务组件",[69,76,77],{},"●执行输出 Banner 的业务逻辑功能",[51,79,80,83,86],{},[69,81,82],{},"BannerProperties",[69,84,85],{},"属性组件",[69,87,88],{},"●封装配置文件属性值，读取配置文件中是否开启输出 Banner 的功能",[51,90,91,94,97],{},[69,92,93],{},"BannerAutoConfiguration",[69,95,96],{},"自动配置类",[69,98,99,100,103],{},"●主要任务：注册属性组件 ",[101,102],"br",{},"  ●次要任务：确保业务组件放入IoC容器，调用方可以直接注入业务组件并调用组件执行逻辑",[51,105,106,109,112],{},[69,107,108],{},"AutoConfiguration.imports",[69,110,111],{},"声明自动配置类",[69,113,114],{},"让 SpringBoot 指导要加载哪个自动配置类",[37,116,117],{"id":117},"自顶向下开发",[119,120,121],"h3",{"id":121},"项目依赖",[13,123,124],{},"SpringBoot 官方的启动器一般 artifactId 为 spring-boot-starter-xxx，例如：",[126,127,132],"pre",{"className":128,"code":129,"language":130,"meta":131,"style":131},"language-xml shiki shiki-themes catppuccin-latte one-dark-pro","\u003Cdependency>\n    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n    \u003CartifactId>spring-boot-starter-web\u003C\u002FartifactId>\n    \u003Cversion>4.0.5\u003C\u002Fversion>\n    \u003Cscope>compile\u003C\u002Fscope>\n\u003C\u002Fdependency>\n","xml","",[17,133,134,150,173,192,211,230],{"__ignoreMap":131},[135,136,139,143,147],"span",{"class":137,"line":138},"line",1,[135,140,142],{"class":141},"sxizN","\u003C",[135,144,146],{"class":145},"sGF2L","dependency",[135,148,149],{"class":141},">\n",[135,151,153,156,159,162,166,169,171],{"class":137,"line":152},2,[135,154,155],{"class":141},"    \u003C",[135,157,158],{"class":145},"groupId",[135,160,161],{"class":141},">",[135,163,165],{"class":164},"sa2x1","org.springframework.boot",[135,167,168],{"class":141},"\u003C\u002F",[135,170,158],{"class":145},[135,172,149],{"class":141},[135,174,176,178,181,183,186,188,190],{"class":137,"line":175},3,[135,177,155],{"class":141},[135,179,180],{"class":145},"artifactId",[135,182,161],{"class":141},[135,184,185],{"class":164},"spring-boot-starter-web",[135,187,168],{"class":141},[135,189,180],{"class":145},[135,191,149],{"class":141},[135,193,195,197,200,202,205,207,209],{"class":137,"line":194},4,[135,196,155],{"class":141},[135,198,199],{"class":145},"version",[135,201,161],{"class":141},[135,203,204],{"class":164},"4.0.5",[135,206,168],{"class":141},[135,208,199],{"class":145},[135,210,149],{"class":141},[135,212,214,216,219,221,224,226,228],{"class":137,"line":213},5,[135,215,155],{"class":141},[135,217,218],{"class":145},"scope",[135,220,161],{"class":141},[135,222,223],{"class":164},"compile",[135,225,168],{"class":141},[135,227,218],{"class":145},[135,229,149],{"class":141},[135,231,233,235,237],{"class":137,"line":232},6,[135,234,168],{"class":141},[135,236,146],{"class":145},[135,238,149],{"class":141},[13,240,241],{},"如果是三方的 starter 的话，通常 artifactId 为 xxx-spring-boot-starter",[13,243,244],{},"所以我们的项目 artifactId 命名为 banner-spring-boot-starter",[126,246,249],{"className":128,"code":247,"filename":248,"language":130,"meta":131,"style":131},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003Cproject xmlns=\"http:\u002F\u002Fmaven.apache.org\u002FPOM\u002F4.0.0\"\n         xmlns:xsi=\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance\"\n         xsi:schemaLocation=\"http:\u002F\u002Fmaven.apache.org\u002FPOM\u002F4.0.0 http:\u002F\u002Fmaven.apache.org\u002Fxsd\u002Fmaven-4.0.0.xsd\">\n    \u003CmodelVersion>4.0.0\u003C\u002FmodelVersion>\n\n    \u003CgroupId>top.dhbxs.demo\u003C\u002FgroupId>\n    \u003CartifactId>banner-spring-boot-starter\u003C\u002FartifactId>\n    \u003Cversion>1.0-SNAPSHOT\u003C\u002Fversion>\n\n    \u003Cproperties>\n        \u003Cmaven.compiler.source>21\u003C\u002Fmaven.compiler.source>\n        \u003Cmaven.compiler.target>21\u003C\u002Fmaven.compiler.target>\n        \u003Cproject.build.sourceEncoding>UTF-8\u003C\u002Fproject.build.sourceEncoding>\n    \u003C\u002Fproperties>\n\n    \u003Cdependencies>\n        \u003C!-- springboot 自动配置 -->\n        \u003Cdependency>\n            \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n            \u003CartifactId>spring-boot-autoconfigure\u003C\u002FartifactId>\n            \u003Cversion>3.5.13\u003C\u002Fversion>\n        \u003C\u002Fdependency>\n\n        \u003C!-- springboot 配置处理器 -->\n        \u003Cdependency>\n            \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n            \u003CartifactId>spring-boot-configuration-processor\u003C\u002FartifactId>\n            \u003Cversion>2.0.4.RELEASE\u003C\u002Fversion>\n            \u003C!-- 添加optional属性，不传递此依赖，此依赖只在编译时期生成配置元数据 -->\n            \u003Coptional>true\u003C\u002Foptional>\n        \u003C\u002Fdependency>\n    \u003C\u002Fdependencies>\n\n\n    \u003Cbuild>\n        \u003Cplugins>\n            \u003Cplugin>\n                \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n                \u003CartifactId>spring-boot-maven-plugin\u003C\u002FartifactId>\n                \u003Cversion>3.5.13\u003C\u002Fversion>\n            \u003C\u002Fplugin>\n        \u003C\u002Fplugins>\n    \u003C\u002Fbuild>\n\n\u003C\u002Fproject>\n","pom.xml",[17,250,251,280,295,312,329,347,353,371,389,407,412,422,442,460,479,489,494,504,511,520,538,556,574,584,589,595,604,621,639,657,663,682,691,700,705,710,720,730,740,758,776,793,803,812,821,826],{"__ignoreMap":131},[135,252,253,256,258,262,265,269,272,274,277],{"class":137,"line":138},[135,254,255],{"class":141},"\u003C?",[135,257,130],{"class":145},[135,259,261],{"class":260},"sK5Ct"," version",[135,263,264],{"class":164},"=",[135,266,268],{"class":267},"sw_MA","\"1.0\"",[135,270,271],{"class":260}," encoding",[135,273,264],{"class":164},[135,275,276],{"class":267},"\"UTF-8\"",[135,278,279],{"class":141},"?>\n",[135,281,282,284,287,290,292],{"class":137,"line":152},[135,283,142],{"class":141},[135,285,286],{"class":145},"project",[135,288,289],{"class":260}," xmlns",[135,291,264],{"class":164},[135,293,294],{"class":267},"\"http:\u002F\u002Fmaven.apache.org\u002FPOM\u002F4.0.0\"\n",[135,296,297,300,304,307,309],{"class":137,"line":175},[135,298,299],{"class":260},"         xmlns",[135,301,303],{"class":302},"sGpC2",":",[135,305,306],{"class":260},"xsi",[135,308,264],{"class":164},[135,310,311],{"class":267},"\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance\"\n",[135,313,314,317,319,322,324,327],{"class":137,"line":194},[135,315,316],{"class":260},"         xsi",[135,318,303],{"class":302},[135,320,321],{"class":260},"schemaLocation",[135,323,264],{"class":164},[135,325,326],{"class":267},"\"http:\u002F\u002Fmaven.apache.org\u002FPOM\u002F4.0.0 http:\u002F\u002Fmaven.apache.org\u002Fxsd\u002Fmaven-4.0.0.xsd\"",[135,328,149],{"class":141},[135,330,331,333,336,338,341,343,345],{"class":137,"line":213},[135,332,155],{"class":141},[135,334,335],{"class":145},"modelVersion",[135,337,161],{"class":141},[135,339,340],{"class":164},"4.0.0",[135,342,168],{"class":141},[135,344,335],{"class":145},[135,346,149],{"class":141},[135,348,349],{"class":137,"line":232},[135,350,352],{"emptyLinePlaceholder":351},true,"\n",[135,354,356,358,360,362,365,367,369],{"class":137,"line":355},7,[135,357,155],{"class":141},[135,359,158],{"class":145},[135,361,161],{"class":141},[135,363,364],{"class":164},"top.dhbxs.demo",[135,366,168],{"class":141},[135,368,158],{"class":145},[135,370,149],{"class":141},[135,372,374,376,378,380,383,385,387],{"class":137,"line":373},8,[135,375,155],{"class":141},[135,377,180],{"class":145},[135,379,161],{"class":141},[135,381,382],{"class":164},"banner-spring-boot-starter",[135,384,168],{"class":141},[135,386,180],{"class":145},[135,388,149],{"class":141},[135,390,392,394,396,398,401,403,405],{"class":137,"line":391},9,[135,393,155],{"class":141},[135,395,199],{"class":145},[135,397,161],{"class":141},[135,399,400],{"class":164},"1.0-SNAPSHOT",[135,402,168],{"class":141},[135,404,199],{"class":145},[135,406,149],{"class":141},[135,408,410],{"class":137,"line":409},10,[135,411,352],{"emptyLinePlaceholder":351},[135,413,415,417,420],{"class":137,"line":414},11,[135,416,155],{"class":141},[135,418,419],{"class":145},"properties",[135,421,149],{"class":141},[135,423,425,428,431,433,436,438,440],{"class":137,"line":424},12,[135,426,427],{"class":141},"        \u003C",[135,429,430],{"class":145},"maven.compiler.source",[135,432,161],{"class":141},[135,434,435],{"class":164},"21",[135,437,168],{"class":141},[135,439,430],{"class":145},[135,441,149],{"class":141},[135,443,445,447,450,452,454,456,458],{"class":137,"line":444},13,[135,446,427],{"class":141},[135,448,449],{"class":145},"maven.compiler.target",[135,451,161],{"class":141},[135,453,435],{"class":164},[135,455,168],{"class":141},[135,457,449],{"class":145},[135,459,149],{"class":141},[135,461,463,465,468,470,473,475,477],{"class":137,"line":462},14,[135,464,427],{"class":141},[135,466,467],{"class":145},"project.build.sourceEncoding",[135,469,161],{"class":141},[135,471,472],{"class":164},"UTF-8",[135,474,168],{"class":141},[135,476,467],{"class":145},[135,478,149],{"class":141},[135,480,482,485,487],{"class":137,"line":481},15,[135,483,484],{"class":141},"    \u003C\u002F",[135,486,419],{"class":145},[135,488,149],{"class":141},[135,490,492],{"class":137,"line":491},16,[135,493,352],{"emptyLinePlaceholder":351},[135,495,497,499,502],{"class":137,"line":496},17,[135,498,155],{"class":141},[135,500,501],{"class":145},"dependencies",[135,503,149],{"class":141},[135,505,507],{"class":137,"line":506},18,[135,508,510],{"class":509},"skYY2","        \u003C!-- springboot 自动配置 -->\n",[135,512,514,516,518],{"class":137,"line":513},19,[135,515,427],{"class":141},[135,517,146],{"class":145},[135,519,149],{"class":141},[135,521,523,526,528,530,532,534,536],{"class":137,"line":522},20,[135,524,525],{"class":141},"            \u003C",[135,527,158],{"class":145},[135,529,161],{"class":141},[135,531,165],{"class":164},[135,533,168],{"class":141},[135,535,158],{"class":145},[135,537,149],{"class":141},[135,539,541,543,545,547,550,552,554],{"class":137,"line":540},21,[135,542,525],{"class":141},[135,544,180],{"class":145},[135,546,161],{"class":141},[135,548,549],{"class":164},"spring-boot-autoconfigure",[135,551,168],{"class":141},[135,553,180],{"class":145},[135,555,149],{"class":141},[135,557,559,561,563,565,568,570,572],{"class":137,"line":558},22,[135,560,525],{"class":141},[135,562,199],{"class":145},[135,564,161],{"class":141},[135,566,567],{"class":164},"3.5.13",[135,569,168],{"class":141},[135,571,199],{"class":145},[135,573,149],{"class":141},[135,575,577,580,582],{"class":137,"line":576},23,[135,578,579],{"class":141},"        \u003C\u002F",[135,581,146],{"class":145},[135,583,149],{"class":141},[135,585,587],{"class":137,"line":586},24,[135,588,352],{"emptyLinePlaceholder":351},[135,590,592],{"class":137,"line":591},25,[135,593,594],{"class":509},"        \u003C!-- springboot 配置处理器 -->\n",[135,596,598,600,602],{"class":137,"line":597},26,[135,599,427],{"class":141},[135,601,146],{"class":145},[135,603,149],{"class":141},[135,605,607,609,611,613,615,617,619],{"class":137,"line":606},27,[135,608,525],{"class":141},[135,610,158],{"class":145},[135,612,161],{"class":141},[135,614,165],{"class":164},[135,616,168],{"class":141},[135,618,158],{"class":145},[135,620,149],{"class":141},[135,622,624,626,628,630,633,635,637],{"class":137,"line":623},28,[135,625,525],{"class":141},[135,627,180],{"class":145},[135,629,161],{"class":141},[135,631,632],{"class":164},"spring-boot-configuration-processor",[135,634,168],{"class":141},[135,636,180],{"class":145},[135,638,149],{"class":141},[135,640,642,644,646,648,651,653,655],{"class":137,"line":641},29,[135,643,525],{"class":141},[135,645,199],{"class":145},[135,647,161],{"class":141},[135,649,650],{"class":164},"2.0.4.RELEASE",[135,652,168],{"class":141},[135,654,199],{"class":145},[135,656,149],{"class":141},[135,658,660],{"class":137,"line":659},30,[135,661,662],{"class":509},"            \u003C!-- 添加optional属性，不传递此依赖，此依赖只在编译时期生成配置元数据 -->\n",[135,664,666,668,671,673,676,678,680],{"class":137,"line":665},31,[135,667,525],{"class":141},[135,669,670],{"class":145},"optional",[135,672,161],{"class":141},[135,674,675],{"class":164},"true",[135,677,168],{"class":141},[135,679,670],{"class":145},[135,681,149],{"class":141},[135,683,685,687,689],{"class":137,"line":684},32,[135,686,579],{"class":141},[135,688,146],{"class":145},[135,690,149],{"class":141},[135,692,694,696,698],{"class":137,"line":693},33,[135,695,484],{"class":141},[135,697,501],{"class":145},[135,699,149],{"class":141},[135,701,703],{"class":137,"line":702},34,[135,704,352],{"emptyLinePlaceholder":351},[135,706,708],{"class":137,"line":707},35,[135,709,352],{"emptyLinePlaceholder":351},[135,711,713,715,718],{"class":137,"line":712},36,[135,714,155],{"class":141},[135,716,717],{"class":145},"build",[135,719,149],{"class":141},[135,721,723,725,728],{"class":137,"line":722},37,[135,724,427],{"class":141},[135,726,727],{"class":145},"plugins",[135,729,149],{"class":141},[135,731,733,735,738],{"class":137,"line":732},38,[135,734,525],{"class":141},[135,736,737],{"class":145},"plugin",[135,739,149],{"class":141},[135,741,743,746,748,750,752,754,756],{"class":137,"line":742},39,[135,744,745],{"class":141},"                \u003C",[135,747,158],{"class":145},[135,749,161],{"class":141},[135,751,165],{"class":164},[135,753,168],{"class":141},[135,755,158],{"class":145},[135,757,149],{"class":141},[135,759,761,763,765,767,770,772,774],{"class":137,"line":760},40,[135,762,745],{"class":141},[135,764,180],{"class":145},[135,766,161],{"class":141},[135,768,769],{"class":164},"spring-boot-maven-plugin",[135,771,168],{"class":141},[135,773,180],{"class":145},[135,775,149],{"class":141},[135,777,779,781,783,785,787,789,791],{"class":137,"line":778},41,[135,780,745],{"class":141},[135,782,199],{"class":145},[135,784,161],{"class":141},[135,786,567],{"class":164},[135,788,168],{"class":141},[135,790,199],{"class":145},[135,792,149],{"class":141},[135,794,796,799,801],{"class":137,"line":795},42,[135,797,798],{"class":141},"            \u003C\u002F",[135,800,737],{"class":145},[135,802,149],{"class":141},[135,804,806,808,810],{"class":137,"line":805},43,[135,807,579],{"class":141},[135,809,727],{"class":145},[135,811,149],{"class":141},[135,813,815,817,819],{"class":137,"line":814},44,[135,816,484],{"class":141},[135,818,717],{"class":145},[135,820,149],{"class":141},[135,822,824],{"class":137,"line":823},45,[135,825,352],{"emptyLinePlaceholder":351},[135,827,829,831,833],{"class":137,"line":828},46,[135,830,168],{"class":141},[135,832,286],{"class":145},[135,834,149],{"class":141},[119,836,838],{"id":837},"autoconfigurationimports-文件","AutoConfiguration.imports 文件",[13,840,841,842,845],{},"首先要有一个 ",[17,843,844],{"code":844},"org.springframework.boot.autoconfigure.AutoConfiguration.imports"," 文件用于声明自动配置类，这个文件的文件名不能改，SpringBoot 启动时会解析这个文件，然后自动加载里面配置好的类，SpringBoot 官方的 starter 有 142 个自动配置类。如果项目引入了我们当前开发的这个 starter，那 SpringBoot 启动时就会加载自身 142 个再加上我们配置好的类。",[13,847,848,849,851],{},"在 resources 目录下新建 META-INF\u002Fspring 文件夹，然后在这个文件夹下新建名为 ",[17,850,844],{"code":844}," 的文件，内容如下：",[126,853,857],{"className":854,"code":855,"language":856,"meta":131,"style":131},"language-plain shiki shiki-themes catppuccin-latte one-dark-pro","top.dhbxs.demo.config.BannerAutoConfiguration\n","plain",[17,858,859],{"__ignoreMap":131},[135,860,861],{"class":137,"line":138},[135,862,855],{},[13,864,865],{},"这里的内容就是我们 BannerStarter 里的自动装配类的 全限定类名。",[13,867,868],{},"然后顺手在 resources 目录下新建个 dhbxs 的文件夹，存放我们要在控制台打印的 banner 字符，命名为 banner.txt",[126,870,872],{"className":854,"code":871,"language":856,"meta":131,"style":131},"      ██   ██        ██\n     ░██  ░██       ░██\n     ░██  ░██       ░██        ██   ██    ██████\n  ██████  ░██████   ░██████   ░░██ ██    ██░░░░\n ██░░░██  ░██░░░██  ░██░░░██   ░░███    ░░█████\n░██  ░██  ░██  ░██  ░██  ░██    ██░██    ░░░░░██\n░░██████  ░██  ░██  ░██████    ██ ░░██   ██████\n ░░░░░░   ░░   ░░   ░░░░░     ░░   ░░   ░░░░░░\n:: My Framework ::  (v1.0.0)\n",[17,873,874,879,884,889,894,899,904,909,914],{"__ignoreMap":131},[135,875,876],{"class":137,"line":138},[135,877,878],{},"      ██   ██        ██\n",[135,880,881],{"class":137,"line":152},[135,882,883],{},"     ░██  ░██       ░██\n",[135,885,886],{"class":137,"line":175},[135,887,888],{},"     ░██  ░██       ░██        ██   ██    ██████\n",[135,890,891],{"class":137,"line":194},[135,892,893],{},"  ██████  ░██████   ░██████   ░░██ ██    ██░░░░\n",[135,895,896],{"class":137,"line":213},[135,897,898],{}," ██░░░██  ░██░░░██  ░██░░░██   ░░███    ░░█████\n",[135,900,901],{"class":137,"line":232},[135,902,903],{},"░██  ░██  ░██  ░██  ░██  ░██    ██░██    ░░░░░██\n",[135,905,906],{"class":137,"line":355},[135,907,908],{},"░░██████  ░██  ░██  ░██████    ██ ░░██   ██████\n",[135,910,911],{"class":137,"line":373},[135,912,913],{}," ░░░░░░   ░░   ░░   ░░░░░     ░░   ░░   ░░░░░░\n",[135,915,916],{"class":137,"line":391},[135,917,918],{},":: My Framework ::  (v1.0.0)\n",[119,920,922],{"id":921},"bannerautoconfiguration-类","BannerAutoConfiguration 类",[13,924,925],{},"在 java 目录下，新建 config 包，在该包里新建 BannerAutoConfiguration.java 类。",[13,927,928],{},"这个类是 starter 自动装配执行的第一个类，类上方有两个比较特殊的注解：",[930,931,932],"ol",{},[933,934,935],"li",{},"@ConditionalOnClass(BannerService.class)",[13,937,938],{},"这个注解主要是在执行 BannerAutoConfiguration 类之前，检查类路径下有没有 BannerService 这个类，如果有，则把当前 BannerAutoConfiguration 这个类加入 IoC 容器。",[13,940,941,942,945],{},"主要是防止在使用我们框架的项目中，如果没有完整下载到我们的 starter，依赖缺失导致报 ",[17,943,944],{"code":944},"ClassNotFoundException","异常，如果依赖缺失，也就不进行自动装配了。",[930,947,948],{"start":152},[933,949,950],{},"@EnableConfigurationProperties(BannerProperties.class)",[13,952,953],{},"这个注解主要是加载属性组件，读取用户在配置文件中配置的数据，把 BannerProperties 也注册为 SpringBean",[13,955,956],{},"该类中把业务逻辑类 BannerService 也 new 出来，然后创建为一个 SpringBean，方便框架调用方直接可以注入该 Bean 调用业务逻辑。",[126,958,963],{"className":959,"code":960,"filename":961,"language":962,"meta":131,"style":131},"language-java shiki shiki-themes catppuccin-latte one-dark-pro","package top.dhbxs.demo.config;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport top.dhbxs.demo.properties.BannerProperties;\nimport top.dhbxs.demo.service.BannerService;\n\n\u002F**\n * Banner 自动配置类\n *\n * @author dhbxs\n * @since 2026\u002F4\u002F9\n *\u002F\n@Configuration\n\u002F\u002F 如果当前类路径下有 BannerService 这个类，就把当前类加入 IoC 容器\n@ConditionalOnClass(BannerService.class)\n\u002F\u002F 注册属性组件，SpringBoot 会读取用户在配置文件中指定的值，注入属性组件\n@EnableConfigurationProperties(BannerProperties.class)\npublic class BannerAutoConfiguration {\n\n    private final BannerProperties bannerProperties;\n\n    public BannerAutoConfiguration(BannerProperties bannerProperties) {\n        this.bannerProperties = bannerProperties;\n    }\n\n    @Bean\n    \u002F\u002F 条件注入 当 IoC 容器中没有这个 bean 时，才把返回对象注入到 IoC 容器中，如果有，则不注入\n    @ConditionalOnMissingBean\n    public BannerService bannerService() {\n        \u002F\u002F 创建BannerService时，把用户配置的是否启用也传进去\n        return new BannerService(bannerProperties.getEnable());\n    }\n}\n","BannerAutoConfiguration.java","java",[17,964,965,995,999,1036,1065,1095,1121,1146,1170,1195,1199,1204,1209,1214,1226,1236,1241,1251,1256,1276,1281,1297,1312,1316,1334,1338,1358,1377,1382,1386,1394,1399,1406,1421,1426,1448,1452],{"__ignoreMap":131},[135,966,967,971,974,978,981,983,986,988,991],{"class":137,"line":138},[135,968,970],{"class":969},"sSWcl","package",[135,972,973],{"class":969}," top",[135,975,977],{"class":976},"sHUla",".",[135,979,980],{"class":969},"dhbxs",[135,982,977],{"class":976},[135,984,985],{"class":969},"demo",[135,987,977],{"class":976},[135,989,990],{"class":969},"config",[135,992,994],{"class":993},"sgT6j",";\n",[135,996,997],{"class":137,"line":152},[135,998,352],{"emptyLinePlaceholder":351},[135,1000,1001,1004,1008,1011,1014,1016,1019,1021,1024,1026,1029,1031,1034],{"class":137,"line":175},[135,1002,1003],{"class":969},"import",[135,1005,1007],{"class":1006},"szf8G"," org",[135,1009,977],{"class":1010},"sGc2n",[135,1012,1013],{"class":1006},"springframework",[135,1015,977],{"class":1010},[135,1017,1018],{"class":1006},"boot",[135,1020,977],{"class":1010},[135,1022,1023],{"class":1006},"autoconfigure",[135,1025,977],{"class":1010},[135,1027,1028],{"class":1006},"condition",[135,1030,977],{"class":1010},[135,1032,1033],{"class":1006},"ConditionalOnClass",[135,1035,994],{"class":993},[135,1037,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060,1063],{"class":137,"line":194},[135,1039,1003],{"class":969},[135,1041,1007],{"class":1006},[135,1043,977],{"class":1010},[135,1045,1013],{"class":1006},[135,1047,977],{"class":1010},[135,1049,1018],{"class":1006},[135,1051,977],{"class":1010},[135,1053,1023],{"class":1006},[135,1055,977],{"class":1010},[135,1057,1028],{"class":1006},[135,1059,977],{"class":1010},[135,1061,1062],{"class":1006},"ConditionalOnMissingBean",[135,1064,994],{"class":993},[135,1066,1067,1069,1071,1073,1075,1077,1079,1081,1084,1086,1088,1090,1093],{"class":137,"line":213},[135,1068,1003],{"class":969},[135,1070,1007],{"class":1006},[135,1072,977],{"class":1010},[135,1074,1013],{"class":1006},[135,1076,977],{"class":1010},[135,1078,1018],{"class":1006},[135,1080,977],{"class":1010},[135,1082,1083],{"class":1006},"context",[135,1085,977],{"class":1010},[135,1087,419],{"class":1006},[135,1089,977],{"class":1010},[135,1091,1092],{"class":1006},"EnableConfigurationProperties",[135,1094,994],{"class":993},[135,1096,1097,1099,1101,1103,1105,1107,1109,1111,1114,1116,1119],{"class":137,"line":232},[135,1098,1003],{"class":969},[135,1100,1007],{"class":1006},[135,1102,977],{"class":1010},[135,1104,1013],{"class":1006},[135,1106,977],{"class":1010},[135,1108,1083],{"class":1006},[135,1110,977],{"class":1010},[135,1112,1113],{"class":1006},"annotation",[135,1115,977],{"class":1010},[135,1117,1118],{"class":1006},"Bean",[135,1120,994],{"class":993},[135,1122,1123,1125,1127,1129,1131,1133,1135,1137,1139,1141,1144],{"class":137,"line":355},[135,1124,1003],{"class":969},[135,1126,1007],{"class":1006},[135,1128,977],{"class":1010},[135,1130,1013],{"class":1006},[135,1132,977],{"class":1010},[135,1134,1083],{"class":1006},[135,1136,977],{"class":1010},[135,1138,1113],{"class":1006},[135,1140,977],{"class":1010},[135,1142,1143],{"class":1006},"Configuration",[135,1145,994],{"class":993},[135,1147,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168],{"class":137,"line":373},[135,1149,1003],{"class":969},[135,1151,973],{"class":1006},[135,1153,977],{"class":1010},[135,1155,980],{"class":1006},[135,1157,977],{"class":1010},[135,1159,985],{"class":1006},[135,1161,977],{"class":1010},[135,1163,419],{"class":1006},[135,1165,977],{"class":1010},[135,1167,82],{"class":1006},[135,1169,994],{"class":993},[135,1171,1172,1174,1176,1178,1180,1182,1184,1186,1189,1191,1193],{"class":137,"line":391},[135,1173,1003],{"class":969},[135,1175,973],{"class":1006},[135,1177,977],{"class":1010},[135,1179,980],{"class":1006},[135,1181,977],{"class":1010},[135,1183,985],{"class":1006},[135,1185,977],{"class":1010},[135,1187,1188],{"class":1006},"service",[135,1190,977],{"class":1010},[135,1192,71],{"class":1006},[135,1194,994],{"class":993},[135,1196,1197],{"class":137,"line":409},[135,1198,352],{"emptyLinePlaceholder":351},[135,1200,1201],{"class":137,"line":414},[135,1202,1203],{"class":509},"\u002F**\n",[135,1205,1206],{"class":137,"line":424},[135,1207,1208],{"class":509}," * Banner 自动配置类\n",[135,1210,1211],{"class":137,"line":444},[135,1212,1213],{"class":509}," *\n",[135,1215,1216,1219,1223],{"class":137,"line":462},[135,1217,1218],{"class":509}," * ",[135,1220,1222],{"class":1221},"szsGz","@author",[135,1224,1225],{"class":509}," dhbxs\n",[135,1227,1228,1230,1233],{"class":137,"line":481},[135,1229,1218],{"class":509},[135,1231,1232],{"class":1221},"@since",[135,1234,1235],{"class":509}," 2026\u002F4\u002F9\n",[135,1237,1238],{"class":137,"line":491},[135,1239,1240],{"class":509}," *\u002F\n",[135,1242,1243,1247],{"class":137,"line":496},[135,1244,1246],{"class":1245},"slb6Y","@",[135,1248,1250],{"class":1249},"saiVi","Configuration\n",[135,1252,1253],{"class":137,"line":506},[135,1254,1255],{"class":509},"\u002F\u002F 如果当前类路径下有 BannerService 这个类，就把当前类加入 IoC 容器\n",[135,1257,1258,1260,1262,1266,1268,1270,1273],{"class":137,"line":513},[135,1259,1246],{"class":1245},[135,1261,1033],{"class":1249},[135,1263,1265],{"class":1264},"syBBb","(",[135,1267,71],{"class":1006},[135,1269,977],{"class":993},[135,1271,1272],{"class":1006},"class",[135,1274,1275],{"class":1264},")\n",[135,1277,1278],{"class":137,"line":522},[135,1279,1280],{"class":509},"\u002F\u002F 注册属性组件，SpringBoot 会读取用户在配置文件中指定的值，注入属性组件\n",[135,1282,1283,1285,1287,1289,1291,1293,1295],{"class":137,"line":540},[135,1284,1246],{"class":1245},[135,1286,1092],{"class":1249},[135,1288,1265],{"class":1264},[135,1290,82],{"class":1006},[135,1292,977],{"class":993},[135,1294,1272],{"class":1006},[135,1296,1275],{"class":1264},[135,1298,1299,1302,1305,1309],{"class":137,"line":558},[135,1300,1301],{"class":969},"public",[135,1303,1304],{"class":969}," class",[135,1306,1308],{"class":1307},"sIkkJ"," BannerAutoConfiguration",[135,1310,1311],{"class":993}," {\n",[135,1313,1314],{"class":137,"line":576},[135,1315,352],{"emptyLinePlaceholder":351},[135,1317,1318,1321,1324,1328,1332],{"class":137,"line":586},[135,1319,1320],{"class":969},"    private",[135,1322,1323],{"class":969}," final",[135,1325,1327],{"class":1326},"sPahJ"," BannerProperties",[135,1329,1331],{"class":1330},"sIGPt"," bannerProperties",[135,1333,994],{"class":993},[135,1335,1336],{"class":137,"line":591},[135,1337,352],{"emptyLinePlaceholder":351},[135,1339,1340,1343,1346,1348,1350,1353,1356],{"class":137,"line":597},[135,1341,1342],{"class":969},"    public",[135,1344,1308],{"class":1345},"seVD2",[135,1347,1265],{"class":993},[135,1349,82],{"class":1326},[135,1351,1331],{"class":1352},"sddMY",[135,1354,1355],{"class":993},")",[135,1357,1311],{"class":993},[135,1359,1360,1364,1366,1369,1373,1375],{"class":137,"line":606},[135,1361,1363],{"class":1362},"sQee8","        this",[135,1365,977],{"class":993},[135,1367,1368],{"class":1006},"bannerProperties",[135,1370,1372],{"class":1371},"sqgB4"," =",[135,1374,1331],{"class":164},[135,1376,994],{"class":993},[135,1378,1379],{"class":137,"line":623},[135,1380,1381],{"class":993},"    }\n",[135,1383,1384],{"class":137,"line":641},[135,1385,352],{"emptyLinePlaceholder":351},[135,1387,1388,1391],{"class":137,"line":659},[135,1389,1390],{"class":1245},"    @",[135,1392,1393],{"class":1249},"Bean\n",[135,1395,1396],{"class":137,"line":665},[135,1397,1398],{"class":509},"    \u002F\u002F 条件注入 当 IoC 容器中没有这个 bean 时，才把返回对象注入到 IoC 容器中，如果有，则不注入\n",[135,1400,1401,1403],{"class":137,"line":684},[135,1402,1390],{"class":1245},[135,1404,1405],{"class":1249},"ConditionalOnMissingBean\n",[135,1407,1408,1410,1413,1416,1419],{"class":137,"line":693},[135,1409,1342],{"class":969},[135,1411,1412],{"class":1326}," BannerService",[135,1414,1415],{"class":1345}," bannerService",[135,1417,1418],{"class":993},"()",[135,1420,1311],{"class":993},[135,1422,1423],{"class":137,"line":702},[135,1424,1425],{"class":509},"        \u002F\u002F 创建BannerService时，把用户配置的是否启用也传进去\n",[135,1427,1428,1431,1434,1436,1438,1440,1442,1445],{"class":137,"line":707},[135,1429,1430],{"class":969},"        return",[135,1432,1433],{"class":969}," new",[135,1435,1412],{"class":1345},[135,1437,1265],{"class":993},[135,1439,1368],{"class":1006},[135,1441,977],{"class":993},[135,1443,1444],{"class":1345},"getEnable",[135,1446,1447],{"class":993},"());\n",[135,1449,1450],{"class":137,"line":712},[135,1451,1381],{"class":993},[135,1453,1454],{"class":137,"line":722},[135,1455,1456],{"class":993},"}\n",[119,1458,1460],{"id":1459},"bannerproperties-类","BannerProperties 类",[13,1462,1463],{},"这个类就是和我们平时自己写的从 application.yml 中读取配置信息的类一样，使用@ConfigurationProperties(prefix = \"banner\") 注解定义已 banner 开头的配置属性",[13,1465,1466],{},"新建 properties 包，放在该包下",[126,1468,1471],{"className":959,"code":1469,"filename":1470,"language":962,"meta":131,"style":131},"package top.dhbxs.demo.properties;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n\u002F**\n * Banner 是否启用配置类\n *\n * @author dhbxs\n * @since 2026\u002F4\u002F9\n *\u002F\n@Component\n@ConfigurationProperties(prefix = \"banner\")\npublic class BannerProperties {\n    \u002F**\n     * 是否启用 Banner\n     *\u002F\n    private Boolean enable = true;\n\n    public Boolean getEnable() {\n        return enable;\n    }\n\n    public void setEnable(Boolean enable) {\n        this.enable = enable;\n    }\n}\n","BannerProperties.java",[17,1472,1473,1493,1497,1526,1548,1552,1556,1561,1565,1573,1581,1585,1592,1611,1621,1626,1631,1636,1654,1658,1671,1680,1684,1688,1709,1724,1728],{"__ignoreMap":131},[135,1474,1475,1477,1479,1481,1483,1485,1487,1489,1491],{"class":137,"line":138},[135,1476,970],{"class":969},[135,1478,973],{"class":969},[135,1480,977],{"class":976},[135,1482,980],{"class":969},[135,1484,977],{"class":976},[135,1486,985],{"class":969},[135,1488,977],{"class":976},[135,1490,419],{"class":969},[135,1492,994],{"class":993},[135,1494,1495],{"class":137,"line":152},[135,1496,352],{"emptyLinePlaceholder":351},[135,1498,1499,1501,1503,1505,1507,1509,1511,1513,1515,1517,1519,1521,1524],{"class":137,"line":175},[135,1500,1003],{"class":969},[135,1502,1007],{"class":1006},[135,1504,977],{"class":1010},[135,1506,1013],{"class":1006},[135,1508,977],{"class":1010},[135,1510,1018],{"class":1006},[135,1512,977],{"class":1010},[135,1514,1083],{"class":1006},[135,1516,977],{"class":1010},[135,1518,419],{"class":1006},[135,1520,977],{"class":1010},[135,1522,1523],{"class":1006},"ConfigurationProperties",[135,1525,994],{"class":993},[135,1527,1528,1530,1532,1534,1536,1538,1541,1543,1546],{"class":137,"line":194},[135,1529,1003],{"class":969},[135,1531,1007],{"class":1006},[135,1533,977],{"class":1010},[135,1535,1013],{"class":1006},[135,1537,977],{"class":1010},[135,1539,1540],{"class":1006},"stereotype",[135,1542,977],{"class":1010},[135,1544,1545],{"class":1006},"Component",[135,1547,994],{"class":993},[135,1549,1550],{"class":137,"line":213},[135,1551,352],{"emptyLinePlaceholder":351},[135,1553,1554],{"class":137,"line":232},[135,1555,1203],{"class":509},[135,1557,1558],{"class":137,"line":355},[135,1559,1560],{"class":509}," * Banner 是否启用配置类\n",[135,1562,1563],{"class":137,"line":373},[135,1564,1213],{"class":509},[135,1566,1567,1569,1571],{"class":137,"line":391},[135,1568,1218],{"class":509},[135,1570,1222],{"class":1221},[135,1572,1225],{"class":509},[135,1574,1575,1577,1579],{"class":137,"line":409},[135,1576,1218],{"class":509},[135,1578,1232],{"class":1221},[135,1580,1235],{"class":509},[135,1582,1583],{"class":137,"line":414},[135,1584,1240],{"class":509},[135,1586,1587,1589],{"class":137,"line":424},[135,1588,1246],{"class":1245},[135,1590,1591],{"class":1249},"Component\n",[135,1593,1594,1596,1598,1600,1604,1606,1609],{"class":137,"line":444},[135,1595,1246],{"class":1245},[135,1597,1523],{"class":1249},[135,1599,1265],{"class":1264},[135,1601,1603],{"class":1602},"sJ4Hi","prefix",[135,1605,1372],{"class":1371},[135,1607,1608],{"class":267}," \"banner\"",[135,1610,1275],{"class":1264},[135,1612,1613,1615,1617,1619],{"class":137,"line":462},[135,1614,1301],{"class":969},[135,1616,1304],{"class":969},[135,1618,1327],{"class":1307},[135,1620,1311],{"class":993},[135,1622,1623],{"class":137,"line":481},[135,1624,1625],{"class":509},"    \u002F**\n",[135,1627,1628],{"class":137,"line":491},[135,1629,1630],{"class":509},"     * 是否启用 Banner\n",[135,1632,1633],{"class":137,"line":496},[135,1634,1635],{"class":509},"     *\u002F\n",[135,1637,1638,1640,1643,1646,1648,1652],{"class":137,"line":506},[135,1639,1320],{"class":969},[135,1641,1642],{"class":1326}," Boolean",[135,1644,1645],{"class":1330}," enable ",[135,1647,264],{"class":1371},[135,1649,1651],{"class":1650},"sFiMs"," true",[135,1653,994],{"class":993},[135,1655,1656],{"class":137,"line":513},[135,1657,352],{"emptyLinePlaceholder":351},[135,1659,1660,1662,1664,1667,1669],{"class":137,"line":522},[135,1661,1342],{"class":969},[135,1663,1642],{"class":1326},[135,1665,1666],{"class":1345}," getEnable",[135,1668,1418],{"class":993},[135,1670,1311],{"class":993},[135,1672,1673,1675,1678],{"class":137,"line":540},[135,1674,1430],{"class":969},[135,1676,1677],{"class":164}," enable",[135,1679,994],{"class":993},[135,1681,1682],{"class":137,"line":558},[135,1683,1381],{"class":993},[135,1685,1686],{"class":137,"line":576},[135,1687,352],{"emptyLinePlaceholder":351},[135,1689,1690,1692,1695,1698,1700,1703,1705,1707],{"class":137,"line":586},[135,1691,1342],{"class":969},[135,1693,1694],{"class":969}," void",[135,1696,1697],{"class":1345}," setEnable",[135,1699,1265],{"class":993},[135,1701,1702],{"class":1326},"Boolean",[135,1704,1677],{"class":1352},[135,1706,1355],{"class":993},[135,1708,1311],{"class":993},[135,1710,1711,1713,1715,1718,1720,1722],{"class":137,"line":591},[135,1712,1363],{"class":1362},[135,1714,977],{"class":993},[135,1716,1717],{"class":1006},"enable",[135,1719,1372],{"class":1371},[135,1721,1677],{"class":164},[135,1723,994],{"class":993},[135,1725,1726],{"class":137,"line":597},[135,1727,1381],{"class":993},[135,1729,1730],{"class":137,"line":606},[135,1731,1456],{"class":993},[119,1733,1735],{"id":1734},"bannerservice-类","BannerService 类",[13,1737,1738],{},"这个类是我们打印 Banner 的核心业务逻辑类，在构造器中调用该类中的方法实现在启动 SpringBoot 项目时，自动打印 Banner。",[13,1740,1741],{},"主要实现读取我们定义的 dhbxs\u002Fbanner.txt 文件，然后将里面的内容打印到控制台。",[126,1743,1746],{"className":959,"code":1744,"filename":1745,"language":962,"meta":131,"style":131},"package top.dhbxs.demo.service;\n\nimport java.io.BufferedReader;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.stream.Collectors;\n\n\u002F**\n * 打印 Banner 业务类\n *\n * @author dhbxs\n * @since 2026\u002F4\u002F9\n *\u002F\npublic class BannerService {\n\n    \u002F\u002F 是否启用\n    private final Boolean enable;\n\n    public BannerService(Boolean enable) {\n        this.enable = enable;\n        this.print();\n    }\n\n    \u002F**\n     * 从classpath读取banner.txt并打印到控制台\n     *\n     * @param bannerPath classpath下的路径，如 \"banner.txt\" 或 \"static\u002Fbanner.txt\"\n     *\u002F\n    public void print(String bannerPath) {\n        try (InputStream is = BannerService.class.getClassLoader().getResourceAsStream(bannerPath)) {\n            if (is == null) {\n                System.out.println(\"Banner not found: \" + bannerPath);\n                return;\n            }\n            String banner = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))\n                    .lines()\n                    .collect(Collectors.joining(\"\\n\"));\n            System.out.println(banner);\n        } catch (Exception e) {\n            System.out.println(\"Failed to print banner: \" + e.getMessage());\n        }\n    }\n\n    \u002F**\n     * 使用默认路径 banner.txt\n     *\u002F\n    public void print() {\n        if (Boolean.TRUE.equals(this.enable)) {\n            print(\"dhbxs\u002Fbanner.txt\");\n        }\n    }\n}\n","BannerService.java",[17,1747,1748,1768,1772,1791,1808,1825,1848,1871,1875,1879,1884,1888,1896,1904,1908,1918,1922,1927,1939,1943,1959,1973,1985,1989,1993,1997,2002,2007,2021,2025,2045,2087,2107,2135,2142,2147,2189,2200,2230,2250,2270,2298,2303,2307,2311,2315,2320,2325,2338,2371,2384,2389,2394],{"__ignoreMap":131},[135,1749,1750,1752,1754,1756,1758,1760,1762,1764,1766],{"class":137,"line":138},[135,1751,970],{"class":969},[135,1753,973],{"class":969},[135,1755,977],{"class":976},[135,1757,980],{"class":969},[135,1759,977],{"class":976},[135,1761,985],{"class":969},[135,1763,977],{"class":976},[135,1765,1188],{"class":969},[135,1767,994],{"class":993},[135,1769,1770],{"class":137,"line":152},[135,1771,352],{"emptyLinePlaceholder":351},[135,1773,1774,1776,1779,1781,1784,1786,1789],{"class":137,"line":175},[135,1775,1003],{"class":969},[135,1777,1778],{"class":1006}," java",[135,1780,977],{"class":1010},[135,1782,1783],{"class":1006},"io",[135,1785,977],{"class":1010},[135,1787,1788],{"class":1006},"BufferedReader",[135,1790,994],{"class":993},[135,1792,1793,1795,1797,1799,1801,1803,1806],{"class":137,"line":194},[135,1794,1003],{"class":969},[135,1796,1778],{"class":1006},[135,1798,977],{"class":1010},[135,1800,1783],{"class":1006},[135,1802,977],{"class":1010},[135,1804,1805],{"class":1006},"InputStream",[135,1807,994],{"class":993},[135,1809,1810,1812,1814,1816,1818,1820,1823],{"class":137,"line":213},[135,1811,1003],{"class":969},[135,1813,1778],{"class":1006},[135,1815,977],{"class":1010},[135,1817,1783],{"class":1006},[135,1819,977],{"class":1010},[135,1821,1822],{"class":1006},"InputStreamReader",[135,1824,994],{"class":993},[135,1826,1827,1829,1831,1833,1836,1838,1841,1843,1846],{"class":137,"line":232},[135,1828,1003],{"class":969},[135,1830,1778],{"class":1006},[135,1832,977],{"class":1010},[135,1834,1835],{"class":1006},"nio",[135,1837,977],{"class":1010},[135,1839,1840],{"class":1006},"charset",[135,1842,977],{"class":1010},[135,1844,1845],{"class":1006},"StandardCharsets",[135,1847,994],{"class":993},[135,1849,1850,1852,1854,1856,1859,1861,1864,1866,1869],{"class":137,"line":355},[135,1851,1003],{"class":969},[135,1853,1778],{"class":1006},[135,1855,977],{"class":1010},[135,1857,1858],{"class":1006},"util",[135,1860,977],{"class":1010},[135,1862,1863],{"class":1006},"stream",[135,1865,977],{"class":1010},[135,1867,1868],{"class":1006},"Collectors",[135,1870,994],{"class":993},[135,1872,1873],{"class":137,"line":373},[135,1874,352],{"emptyLinePlaceholder":351},[135,1876,1877],{"class":137,"line":391},[135,1878,1203],{"class":509},[135,1880,1881],{"class":137,"line":409},[135,1882,1883],{"class":509}," * 打印 Banner 业务类\n",[135,1885,1886],{"class":137,"line":414},[135,1887,1213],{"class":509},[135,1889,1890,1892,1894],{"class":137,"line":424},[135,1891,1218],{"class":509},[135,1893,1222],{"class":1221},[135,1895,1225],{"class":509},[135,1897,1898,1900,1902],{"class":137,"line":444},[135,1899,1218],{"class":509},[135,1901,1232],{"class":1221},[135,1903,1235],{"class":509},[135,1905,1906],{"class":137,"line":462},[135,1907,1240],{"class":509},[135,1909,1910,1912,1914,1916],{"class":137,"line":481},[135,1911,1301],{"class":969},[135,1913,1304],{"class":969},[135,1915,1412],{"class":1307},[135,1917,1311],{"class":993},[135,1919,1920],{"class":137,"line":491},[135,1921,352],{"emptyLinePlaceholder":351},[135,1923,1924],{"class":137,"line":496},[135,1925,1926],{"class":509},"    \u002F\u002F 是否启用\n",[135,1928,1929,1931,1933,1935,1937],{"class":137,"line":506},[135,1930,1320],{"class":969},[135,1932,1323],{"class":969},[135,1934,1642],{"class":1326},[135,1936,1677],{"class":1330},[135,1938,994],{"class":993},[135,1940,1941],{"class":137,"line":513},[135,1942,352],{"emptyLinePlaceholder":351},[135,1944,1945,1947,1949,1951,1953,1955,1957],{"class":137,"line":522},[135,1946,1342],{"class":969},[135,1948,1412],{"class":1345},[135,1950,1265],{"class":993},[135,1952,1702],{"class":1326},[135,1954,1677],{"class":1352},[135,1956,1355],{"class":993},[135,1958,1311],{"class":993},[135,1960,1961,1963,1965,1967,1969,1971],{"class":137,"line":540},[135,1962,1363],{"class":1362},[135,1964,977],{"class":993},[135,1966,1717],{"class":1006},[135,1968,1372],{"class":1371},[135,1970,1677],{"class":164},[135,1972,994],{"class":993},[135,1974,1975,1977,1979,1982],{"class":137,"line":558},[135,1976,1363],{"class":1362},[135,1978,977],{"class":993},[135,1980,1981],{"class":1345},"print",[135,1983,1984],{"class":993},"();\n",[135,1986,1987],{"class":137,"line":576},[135,1988,1381],{"class":993},[135,1990,1991],{"class":137,"line":586},[135,1992,352],{"emptyLinePlaceholder":351},[135,1994,1995],{"class":137,"line":591},[135,1996,1625],{"class":509},[135,1998,1999],{"class":137,"line":597},[135,2000,2001],{"class":509},"     * 从classpath读取banner.txt并打印到控制台\n",[135,2003,2004],{"class":137,"line":606},[135,2005,2006],{"class":509},"     *\n",[135,2008,2009,2012,2015,2018],{"class":137,"line":623},[135,2010,2011],{"class":509},"     * ",[135,2013,2014],{"class":1221},"@param",[135,2016,2017],{"class":1352}," bannerPath",[135,2019,2020],{"class":509}," classpath下的路径，如 \"banner.txt\" 或 \"static\u002Fbanner.txt\"\n",[135,2022,2023],{"class":137,"line":641},[135,2024,1635],{"class":509},[135,2026,2027,2029,2031,2034,2036,2039,2041,2043],{"class":137,"line":659},[135,2028,1342],{"class":969},[135,2030,1694],{"class":969},[135,2032,2033],{"class":1345}," print",[135,2035,1265],{"class":993},[135,2037,2038],{"class":1326},"String",[135,2040,2017],{"class":1352},[135,2042,1355],{"class":993},[135,2044,1311],{"class":993},[135,2046,2047,2050,2053,2055,2058,2060,2062,2064,2066,2068,2071,2074,2077,2079,2082,2085],{"class":137,"line":665},[135,2048,2049],{"class":969},"        try",[135,2051,2052],{"class":993}," (",[135,2054,1805],{"class":1326},[135,2056,2057],{"class":1330}," is",[135,2059,1372],{"class":1371},[135,2061,1412],{"class":1006},[135,2063,977],{"class":993},[135,2065,1272],{"class":1006},[135,2067,977],{"class":993},[135,2069,2070],{"class":1345},"getClassLoader",[135,2072,2073],{"class":993},"().",[135,2075,2076],{"class":1345},"getResourceAsStream",[135,2078,1265],{"class":993},[135,2080,2081],{"class":164},"bannerPath",[135,2083,2084],{"class":993},"))",[135,2086,1311],{"class":993},[135,2088,2089,2092,2094,2097,2100,2103,2105],{"class":137,"line":684},[135,2090,2091],{"class":969},"            if",[135,2093,2052],{"class":993},[135,2095,2096],{"class":164},"is ",[135,2098,2099],{"class":1371},"==",[135,2101,2102],{"class":1650}," null",[135,2104,1355],{"class":993},[135,2106,1311],{"class":993},[135,2108,2109,2112,2114,2117,2119,2122,2124,2127,2130,2132],{"class":137,"line":693},[135,2110,2111],{"class":1006},"                System",[135,2113,977],{"class":993},[135,2115,2116],{"class":1006},"out",[135,2118,977],{"class":993},[135,2120,2121],{"class":1345},"println",[135,2123,1265],{"class":993},[135,2125,2126],{"class":267},"\"Banner not found: \"",[135,2128,2129],{"class":1371}," +",[135,2131,2017],{"class":164},[135,2133,2134],{"class":993},");\n",[135,2136,2137,2140],{"class":137,"line":702},[135,2138,2139],{"class":969},"                return",[135,2141,994],{"class":993},[135,2143,2144],{"class":137,"line":707},[135,2145,2146],{"class":993},"            }\n",[135,2148,2149,2152,2155,2157,2159,2162,2164,2167,2170,2172,2175,2178,2181,2183,2186],{"class":137,"line":712},[135,2150,2151],{"class":1326},"            String",[135,2153,2154],{"class":1330}," banner",[135,2156,1372],{"class":1371},[135,2158,1433],{"class":969},[135,2160,2161],{"class":1345}," BufferedReader",[135,2163,1265],{"class":993},[135,2165,2166],{"class":969},"new",[135,2168,2169],{"class":1345}," InputStreamReader",[135,2171,1265],{"class":993},[135,2173,2174],{"class":164},"is",[135,2176,2177],{"class":993},",",[135,2179,2180],{"class":1006}," StandardCharsets",[135,2182,977],{"class":993},[135,2184,2185],{"class":1006},"UTF_8",[135,2187,2188],{"class":993},"))\n",[135,2190,2191,2194,2197],{"class":137,"line":722},[135,2192,2193],{"class":993},"                    .",[135,2195,2196],{"class":1345},"lines",[135,2198,2199],{"class":993},"()\n",[135,2201,2202,2204,2207,2209,2211,2213,2216,2218,2221,2225,2227],{"class":137,"line":732},[135,2203,2193],{"class":993},[135,2205,2206],{"class":1345},"collect",[135,2208,1265],{"class":993},[135,2210,1868],{"class":1006},[135,2212,977],{"class":993},[135,2214,2215],{"class":1345},"joining",[135,2217,1265],{"class":993},[135,2219,2220],{"class":267},"\"",[135,2222,2224],{"class":2223},"sFWNk","\\n",[135,2226,2220],{"class":267},[135,2228,2229],{"class":993},"));\n",[135,2231,2232,2235,2237,2239,2241,2243,2245,2248],{"class":137,"line":742},[135,2233,2234],{"class":1006},"            System",[135,2236,977],{"class":993},[135,2238,2116],{"class":1006},[135,2240,977],{"class":993},[135,2242,2121],{"class":1345},[135,2244,1265],{"class":993},[135,2246,2247],{"class":164},"banner",[135,2249,2134],{"class":993},[135,2251,2252,2255,2258,2260,2263,2266,2268],{"class":137,"line":760},[135,2253,2254],{"class":993},"        }",[135,2256,2257],{"class":969}," catch",[135,2259,2052],{"class":993},[135,2261,2262],{"class":1326},"Exception",[135,2264,2265],{"class":1352}," e",[135,2267,1355],{"class":993},[135,2269,1311],{"class":993},[135,2271,2272,2274,2276,2278,2280,2282,2284,2287,2289,2291,2293,2296],{"class":137,"line":778},[135,2273,2234],{"class":1006},[135,2275,977],{"class":993},[135,2277,2116],{"class":1006},[135,2279,977],{"class":993},[135,2281,2121],{"class":1345},[135,2283,1265],{"class":993},[135,2285,2286],{"class":267},"\"Failed to print banner: \"",[135,2288,2129],{"class":1371},[135,2290,2265],{"class":1006},[135,2292,977],{"class":993},[135,2294,2295],{"class":1345},"getMessage",[135,2297,1447],{"class":993},[135,2299,2300],{"class":137,"line":795},[135,2301,2302],{"class":993},"        }\n",[135,2304,2305],{"class":137,"line":805},[135,2306,1381],{"class":993},[135,2308,2309],{"class":137,"line":814},[135,2310,352],{"emptyLinePlaceholder":351},[135,2312,2313],{"class":137,"line":823},[135,2314,1625],{"class":509},[135,2316,2317],{"class":137,"line":828},[135,2318,2319],{"class":509},"     * 使用默认路径 banner.txt\n",[135,2321,2323],{"class":137,"line":2322},47,[135,2324,1635],{"class":509},[135,2326,2328,2330,2332,2334,2336],{"class":137,"line":2327},48,[135,2329,1342],{"class":969},[135,2331,1694],{"class":969},[135,2333,2033],{"class":1345},[135,2335,1418],{"class":993},[135,2337,1311],{"class":993},[135,2339,2341,2344,2346,2348,2350,2353,2355,2358,2360,2363,2365,2367,2369],{"class":137,"line":2340},49,[135,2342,2343],{"class":969},"        if",[135,2345,2052],{"class":993},[135,2347,1702],{"class":1006},[135,2349,977],{"class":993},[135,2351,2352],{"class":1006},"TRUE",[135,2354,977],{"class":993},[135,2356,2357],{"class":1345},"equals",[135,2359,1265],{"class":993},[135,2361,2362],{"class":1362},"this",[135,2364,977],{"class":993},[135,2366,1717],{"class":1006},[135,2368,2084],{"class":993},[135,2370,1311],{"class":993},[135,2372,2374,2377,2379,2382],{"class":137,"line":2373},50,[135,2375,2376],{"class":1345},"            print",[135,2378,1265],{"class":993},[135,2380,2381],{"class":267},"\"dhbxs\u002Fbanner.txt\"",[135,2383,2134],{"class":993},[135,2385,2387],{"class":137,"line":2386},51,[135,2388,2302],{"class":993},[135,2390,2392],{"class":137,"line":2391},52,[135,2393,1381],{"class":993},[135,2395,2397],{"class":137,"line":2396},53,[135,2398,1456],{"class":993},[37,2400,2401],{"id":2401},"打包并给其他项目使用",[13,2403,2404],{},"使用 Maven package 将项目打包后，可以用 install 命令安装到本地 Maven 仓库，然后其他项目中使用 Maven 坐标即可将依赖引入项目",[126,2406,2408],{"className":128,"code":2407,"filename":248,"language":130,"meta":131,"style":131},"\u003Cdependency>\n  \u003CgroupId>top.dhbxs.demo\u003C\u002FgroupId>\n  \u003CartifactId>banner-spring-boot-starter\u003C\u002FartifactId>\n  \u003Cversion>1.0-SNAPSHOT\u003C\u002Fversion>\n\u003C\u002Fdependency>\n",[17,2409,2410,2418,2435,2451,2467],{"__ignoreMap":131},[135,2411,2412,2414,2416],{"class":137,"line":138},[135,2413,142],{"class":141},[135,2415,146],{"class":145},[135,2417,149],{"class":141},[135,2419,2420,2423,2425,2427,2429,2431,2433],{"class":137,"line":152},[135,2421,2422],{"class":141},"  \u003C",[135,2424,158],{"class":145},[135,2426,161],{"class":141},[135,2428,364],{"class":164},[135,2430,168],{"class":141},[135,2432,158],{"class":145},[135,2434,149],{"class":141},[135,2436,2437,2439,2441,2443,2445,2447,2449],{"class":137,"line":175},[135,2438,2422],{"class":141},[135,2440,180],{"class":145},[135,2442,161],{"class":141},[135,2444,382],{"class":164},[135,2446,168],{"class":141},[135,2448,180],{"class":145},[135,2450,149],{"class":141},[135,2452,2453,2455,2457,2459,2461,2463,2465],{"class":137,"line":194},[135,2454,2422],{"class":141},[135,2456,199],{"class":145},[135,2458,161],{"class":141},[135,2460,400],{"class":164},[135,2462,168],{"class":141},[135,2464,199],{"class":145},[135,2466,149],{"class":141},[135,2468,2469,2471,2473],{"class":137,"line":213},[135,2470,168],{"class":141},[135,2472,146],{"class":145},[135,2474,149],{"class":141},[13,2476,2477],{},"如果一切正常，启动 SpringBoot 项目后应该可以看到 SpringBoot 的官方 Banner，然后便可以看到我们 banner-spring-boot-starter 依赖所打印的 Banner，并且可以在 application.yml 中灵活配置是否打印 Banner",[126,2479,2484],{"className":2480,"code":2481,"filename":2482,"language":2483,"meta":131,"style":131},"language-yaml shiki shiki-themes catppuccin-latte one-dark-pro","banner:\n  enable: true\n","application.yml","yaml",[17,2485,2486,2491],{"__ignoreMap":131},[135,2487,2488],{"class":137,"line":138},[135,2489,2490],{},"banner:\n",[135,2492,2493],{"class":137,"line":152},[135,2494,2495],{},"  enable: true\n",[2497,2498,2499],"style",{},"html pre.shiki code .sxizN, html code.shiki .sxizN{--shiki-default:#179299;--shiki-dark:#ABB2BF}html pre.shiki code .sGF2L, html code.shiki .sGF2L{--shiki-default:#1E66F5;--shiki-dark:#E06C75}html pre.shiki code .sa2x1, html code.shiki .sa2x1{--shiki-default:#4C4F69;--shiki-dark:#ABB2BF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sK5Ct, html code.shiki .sK5Ct{--shiki-default:#DF8E1D;--shiki-dark:#D19A66}html pre.shiki code .sw_MA, html code.shiki .sw_MA{--shiki-default:#40A02B;--shiki-dark:#98C379}html pre.shiki code .sGpC2, html code.shiki .sGpC2{--shiki-default:#7C7F93;--shiki-dark:#D19A66}html pre.shiki code .skYY2, html code.shiki .skYY2{--shiki-default:#7C7F93;--shiki-default-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic}html pre.shiki code .sSWcl, html code.shiki .sSWcl{--shiki-default:#8839EF;--shiki-dark:#C678DD}html pre.shiki code .sHUla, html code.shiki .sHUla{--shiki-default:#7C7F93;--shiki-dark:#C678DD}html pre.shiki code .sgT6j, html code.shiki .sgT6j{--shiki-default:#7C7F93;--shiki-dark:#ABB2BF}html pre.shiki code .szf8G, html code.shiki .szf8G{--shiki-default:#4C4F69;--shiki-dark:#E5C07B}html pre.shiki code .sGc2n, html code.shiki .sGc2n{--shiki-default:#7C7F93;--shiki-dark:#E5C07B}html pre.shiki code .szsGz, html code.shiki .szsGz{--shiki-default:#8839EF;--shiki-default-font-style:inherit;--shiki-dark:#C678DD;--shiki-dark-font-style:italic}html pre.shiki code .slb6Y, html code.shiki .slb6Y{--shiki-default:#FE640B;--shiki-dark:#ABB2BF}html pre.shiki code .saiVi, html code.shiki .saiVi{--shiki-default:#FE640B;--shiki-dark:#E5C07B}html pre.shiki code .syBBb, html code.shiki .syBBb{--shiki-default:#7C7F93;--shiki-dark:#E06C75}html pre.shiki code .sIkkJ, html code.shiki .sIkkJ{--shiki-default:#DF8E1D;--shiki-default-font-style:italic;--shiki-dark:#E5C07B;--shiki-dark-font-style:inherit}html pre.shiki code .sPahJ, html code.shiki .sPahJ{--shiki-default:#8839EF;--shiki-dark:#E5C07B}html pre.shiki code .sIGPt, html code.shiki .sIGPt{--shiki-default:#4C4F69;--shiki-dark:#E06C75}html pre.shiki code .seVD2, html code.shiki .seVD2{--shiki-default:#1E66F5;--shiki-default-font-style:italic;--shiki-dark:#61AFEF;--shiki-dark-font-style:inherit}html pre.shiki code .sddMY, html code.shiki .sddMY{--shiki-default:#E64553;--shiki-default-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic}html pre.shiki code .sQee8, html code.shiki .sQee8{--shiki-default:#D20F39;--shiki-dark:#E5C07B}html pre.shiki code .sqgB4, html code.shiki .sqgB4{--shiki-default:#179299;--shiki-dark:#56B6C2}html pre.shiki code .sJ4Hi, html code.shiki .sJ4Hi{--shiki-default:#4C4F69;--shiki-dark:#D19A66}html pre.shiki code .sFiMs, html code.shiki .sFiMs{--shiki-default:#D20F39;--shiki-dark:#D19A66}html pre.shiki code .sFWNk, html code.shiki .sFWNk{--shiki-default:#EA76CB;--shiki-dark:#56B6C2}",{"title":131,"searchDepth":194,"depth":194,"links":2501},[2502,2503,2510],{"id":39,"depth":152,"text":40},{"id":117,"depth":152,"text":117,"children":2504},[2505,2506,2507,2508,2509],{"id":121,"depth":175,"text":121},{"id":837,"depth":175,"text":838},{"id":921,"depth":175,"text":922},{"id":1459,"depth":175,"text":1460},{"id":1734,"depth":175,"text":1735},{"id":2401,"depth":152,"text":2401},[2512],"开发","2026-04-09 07:13:41","一篇关于如何开发自定义 Spring Boot Starter 的技术教程。文章以开发一个控制台 Banner 输出组件为例，手把手讲解了 Spring Boot 自动装配机制的实现原理和完整开发流程。文章通过实战案例揭示了 MyBatis-Plus 等第三方 Starter 背后的实现原理，帮助开发者理解 Spring Boot 的扩展机制。",false,"md","https:\u002F\u002Ffile.dhbxs.top\u002F2026\u002F04\u002Fcsoexczb.webp",{"slots":2519},{},"\u002Fposts\u002F7dc99e0",null,{"text":2523,"minutes":2524,"time":2525,"words":2526},"8 min read",7.525,451500,1505,{"title":5,"description":2514},{"loc":2520},"posts\u002F2026\u002F从零手写一个 Spring Boot Starter：深入理解自动装配原理",[2531,2532],"SpringBoot","Java","tech","SHvEBZFhNRjYZoIwXmgkBHkuBorSfdL1RzsoyOSdNIY",[2536,2521],{"title":2537,"path":2538,"stem":2539,"date":2540,"type":2533,"children":-1},"LangChain4j工具调用中断引发JSON格式报错的问题调查","\u002Fposts\u002F4c27c0e","posts\u002F2025\u002FLangChain4j工具调用中断引发JSON格式报错的问题调查","2025-11-01 16:37:52",1775740823478]