Câu hỏi phỏng vấn Spring Boot Auto-Configuration
Bộ đề này harvest từ module Spring Boot & Auto-Configuration của khoá Spring Core — mỗi câu link bài học đào sâu cơ chế, không dừng ở đáp án thuộc lòng.
01
@SpringBootApplication gồm những annotation nào, mỗi cái làm gì?
JuniorĐây là composite của ba annotation:
@SpringBootConfiguration(chính là@Configuration— đánh dấu class này là nguồn bean definition),@EnableAutoConfiguration(bật cơ chế autoconfig) và@ComponentScan(quét@Component/@Service/@Repositorytừ package hiện tại xuống). Chỉ@EnableAutoConfigurationlà thứ nghe có vẻ "magic", mà bản thân nó cũng chỉ là@Import(AutoConfigurationImportSelector.class)cộng@AutoConfigurationPackage. Ba annotation cộng lại vừa đủ đểSpringApplication.run()dựng một container đầy đủ. Hệ quả thực tế hay bị hỏi tiếp: vì package của class chứa annotation được lấy làm gốc scan, đặt classmainở sub-package sẽ khiến mọi bean nằm ngoài sub-package đó không được đăng ký.Đọc thuộc ba cái tên nhưng không nói được@EnableAutoConfigurationthực chất chỉ import một selector — và không biết vị trí package của classmainquyết định phạm vi component scan.02
Spring Boot có phải một framework mới không? Nó khác Spring Framework ở đâu?
JuniorKhông phải framework mới — Spring Boot là lớp đóng gói nằm trên Spring Framework, gồm năm trụ cột: starter dependency (BOM curate version của hơn 200 thư viện), auto-configuration, embedded server, production-ready feature (Actuator, externalized config) và build plugin đóng gói executable fat jar. Nó ra đời 2014 để xoá 60-90 phút boilerplate mỗi project Spring 4 —
web.xml,applicationContext.xml, Tomcat cài rời,log4j.xml. Bằng chứng rõ nhất cho "chỉ là lớp bọc":SpringApplication.run()chạy 14 bước và bước 12 chính làrefresh()của Spring core, Boot chỉ wrap thêm 11 bước trước cùng 2 bước sau. Vì thế bỏ Boot không có nghĩa viết lại app: code Spring core vẫn chạy, chỉ mất phần default và đóng gói.Trả lời "Boot là Spring bản mới, dễ dùng hơn" mà không nêu được Boot chỉ đóng gói Spring core — bước 12 củaSpringApplication.run()vẫn làrefresh()quen thuộc.03
Spring Boot starter thực chất là gì? Nó có chứa code không?
JuniorStarter là một jar RỖNG CLASS — giải nén ra không có file
.classnào, thứ duy nhất có ý nghĩa làpom.xmlbên trong. Vai trò duy nhất của nó là liệt kê transitive dependency cho một tính năng, để bạn khai một dòng thay vì mười lăm dòng:spring-boot-starter-webkéo theo khoảng 30 jar gồm Tomcat embedded, Jackson, Spring MVC và logging stack. Version không nằm trong starter mà đến từ BOM, nên pom bên trong starter không khai version cho bất kỳ dependency nào. Điểm hay bị nhầm: starter là cơ chế Maven (gom dependency), còn auto-configuration là cơ chế Spring (tạo bean) — hai thứ độc lập, có cái này mà không có cái kia là chuyện bình thường.Nói "starter tự cấu hình giúp mình" — starter chỉ kéo jar về classpath, còn việc tạo bean là do autoconfig đọc classpath đó rồi quyết định.04
spring-boot-starter-parent khác spring-boot-dependencies thế nào?
Juniorspring-boot-dependencieslà BOM thuần — một pom chỉ chứadependencyManagementđịnh nghĩa version cho hơn 200 thư viện, không kéo dependency nào vào classpath.spring-boot-starter-parentkế thừa BOM đó rồi cộng thêm Java version mặc định, UTF-8 encoding, plugin default (Surefire, Failsafe) và resource filtering — khai một dòng parent là xong. Khi project đã có corporate parent thì buộc phải dùng cách thứ hai, vì Maven chỉ cho phép một parent: import BOM bằngscope=importcùngtype=pomtrongdependencyManagement. Đánh đổi là mất phần Java version và plugin default, phải tự cấu hình lấy.Không giải thích được vì sao BOM thắng thuật toán nearest-wins của Maven — version trongdependencyManagementđược đọc TRƯỚC khi Maven resolve conflict trong cây transitive, nên nó luôn thắng.05
Họ annotation @ConditionalOn* dùng để làm gì? Đặt nhiều cái cùng lúc thì sao?
JuniorBoot có 12 annotation
@ConditionalOn*, mỗi cái hỏi một câu về môi trường runtime:@ConditionalOnClass/@ConditionalOnMissingClasshỏi classpath,@ConditionalOnBean/@ConditionalOnMissingBeanhỏi context,@ConditionalOnPropertyvà@ConditionalOnExpressionhỏi cấu hình, số còn lại hỏi web context, resource, Java version, cloud platform và JNDI. Đặt nhiều annotation trên cùng một class là AND logic — fail một điều kiện thì cả autoconfig bị skip, không có chuyện pass một phần.@ConditionalOnPropertycó thêmmatchIfMissing = trueđể bật tính năng theo mặc định và cho user opt-out bằng property. Cả family này xây trên@Conditionalcủa Spring core, Boot chỉ đóng gói sẵn 12 câu hỏi hay dùng nhất.Kể tên annotation mà không nói được chúng kết hợp theo AND — và không biếtmatchIfMissinglà thứ quyết định tính năng bật hay tắt khi property vắng mặt.06
Vì sao chỉ cần khai một @Bean là config của mình thắng autoconfig?
JuniorĐó là back-off pattern của
@ConditionalOnMissingBean: autoconfig khai bean mặc định nhưng kiểm tra trước xem context đã có bean cùng kiểu chưa, có rồi thì nhường. Nhờ vậy user override chỉ bằng một@Beanbình thường — không cần annotation đặc biệt, không cần@Primary, không đụng gì tới source của Boot. Đây chính là ranh giới giữa "opinionated" và "lock-in": Boot có default cho phần lớn use case nhưng cơ chế override là tính năng thiết kế chứ không phải hack. So với việc exclude cả autoconfig, back-off chính xác hơn hẳn: chỉ nhường ĐÚNG một bean, phần còn lại của autoconfig class vẫn giữ nguyên.Nhầm@ConditionalOnMissingBeanvới@Primary—@Primaryvẫn tạo cả hai bean rồi mới chọn cái ưu tiên inject, còn@ConditionalOnMissingBeankhông tạo bean thừa ngay từ đầu.Học sâu hơn
@ConditionalOn* family + back-off pattern + autoconfig orderingSpring Boot & Auto-configuration · ~13 phútCustom Starter — tự viết starter nội bộ và quản lý supply chainSpring Boot & Auto-configuration · ~14 phútSpring Boot là gì — tại sao ra đời và 5 trụ cộtSpring Boot & Auto-configuration · ~12 phút07
Muốn đổi embedded Tomcat sang Jetty thì làm gì, có phải sửa code không?
JuniorExclude
spring-boot-starter-tomcatkhỏispring-boot-starter-webrồi thêmspring-boot-starter-jetty— không sửa một dòng Java nào. Lý do không cần sửa code: auto-configuration đọc classpath, thấyJettyServletWebServerFactorythay choTomcatServletWebServerFactorynên tạo embedded server tương ứng, còn business code chỉ nói chuyện qua Servlet API. Pattern y hệt áp cho logging — excludespring-boot-starter-loggingrồi thêmspring-boot-starter-log4j2. Bẫy đi kèm: exclude mà quên thêm replacement thì Maven build vẫn pass, nhưng lúcSpringApplication.run()chạy Boot không tìm thấyServletWebServerFactoryvà némNoSuchBeanDefinitionException.Trả lời chung chung "đổi dependency là xong" mà không nêu được vì sao code không đổi (autoconfig detect classpath) lẫn hậu quả khi exclude thiếu replacement.08
Spring Boot tìm và load các auto-configuration class bằng cách nào?
Mid@EnableAutoConfigurationchỉ là@Import(AutoConfigurationImportSelector.class)— selector đó mới làm việc thật. Nó gọiImportCandidates.load()để đọc fileMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importstừ MỌI jar trên classpath qua cơ chếclasspath*:scan; mỗi dòng trong file là một fully-qualified class name, bản Boot 3.4 có 143 dòng. Quy trình gồm ba bước gọn: load danh sách, bỏ class user exclude, rồi filter qua@ConditionalOn*. Danh sách còn lại đượcConfigurationClassPostProcessorparse như@Configurationclass và register thànhBeanDefinition— tới đây mới chỉ có metadata, object thật ra đời ở pha instantiate sau đó. Từ Boot 2.7, file imports này thay chospring.factoriescũ.Nói "Boot quét classpath rồi tự tạo bean" mà không biết danh sách autoconfig đến từ một file text nằm trong từng jar — đó cũng là lý do starter bên thứ ba đóng góp được autoconfig mà không cần sửa core Boot.09
Vì sao AutoConfigurationImportSelector phải là DeferredImportSelector?
MidDeferredImportSelectorkhiếnselectImports()bị đẩy vào nhóm chạy SAU khi mọi@Configurationcủa app đã parse xong, khác vớiImportSelectorthường chạy cùng lượt. Thứ tự này là điều kiện sống còn của back-off:@ConditionalOnMissingBeanchỉ cho kết quả đúng nếu autoconfig được evaluate sau khi bean của user đã nằm trongbeanDefinitionMap. Nếu dùngImportSelectorthường, thứ tự giữa autoconfig và user config không xác định — autoconfig có thể chạy trước rồi tạo bean default dù user đã khai. Hệ quả tích cực khác:@ConditionalOnBean(DataSource.class)trong autoconfig nhìn thấy đượcDataSourcedo user khai trong@Configurationbình thường.Chỉ nói "autoconfig chạy sau" mà không gọi tênDeferredImportSelector, và không nối được nó với lý do@ConditionalOnMissingBeanhoạt động đúng.10
Vì sao Boot dùng ASM bytecode reader thay vì Class.forName() khi kiểm tra @ConditionalOnClass?
MidBước filter cần đọc
@ConditionalOnClasscủa từng candidate, và Boot đọc bằng ASM bytecode reader chứ không load class. Ba lý do: load class sẽ chạy static initializer nên gây side effect ngoài ý muốn; nếu class trong annotation value vắng mặt thìClass.forName()némClassNotFoundExceptionthay vì skip êm; và nạp 143 class cùng dependency transitive vào metaspace là lãng phí cho thứ phần lớn sẽ không dùng. ASM chỉ parse annotation table trong bytecode để lấy chuỗi tên class, rồi kiểm tra tồn tại bằngClassLoader.getResource(...)— không load, không init. Còn một lớp tối ưu nữa:META-INF/spring-autoconfigure-metadata.propertiessinh lúc build chứa condition pre-compute, loại bớt candidate trước khi cần đụng tới bytecode.Nghĩ Boot phải load hết 143 autoconfig rồi mới lọc — thực tế phần lớn candidate bị loại từ file metadata hoặc từ ASM, chưa bao giờ đi quaClass.forName().11
Thứ tự giữa các auto-configuration được quyết định thế nào, và vì sao cần thứ tự?
MidCần thứ tự vì
@ConditionalOnMissingBeanphụ thuộc vào bean đã có tại thời điểm evaluate — autoconfig đến trước sẽ đăng ký bean, autoconfig đến sau phải nhường; không có ordering thì kết quả là bug không tái hiện được. Khai bằng@AutoConfigureBefore/@AutoConfigureAfter, hoặc attributebefore/aftercủa@AutoConfigurationtừ Boot 2.7, hoặc@AutoConfigureOrderkhi chỉ cần đẩy rất sớm hay rất muộn. Chuỗi kinh điển làDataSourceAutoConfigurationrồi tớiHibernateJpaAutoConfigurationrồiJpaRepositoriesAutoConfiguration, mỗi bước xây trên bean của bước trước. Cơ chế bên dưới không phải ép chạy tuần tự:AutoConfigurationSortergom mọi quan hệ before/after thành DAG rồi topological sort ra danh sách an toàn — khaiaftermột class không tồn tại thì quan hệ đó bị bỏ qua, còn quan hệ vòng làm Boot némIllegalStateExceptionlúc startup.Đặt@AutoConfigureAfterlên một@Configurationthường — annotation này chỉ có nghĩa trên class@AutoConfiguration, ở chỗ khác Spring bỏ qua hoàn toàn mà không báo gì.12
Auto-config không kích hoạt, bean không được tạo — bạn debug thế nào?
MidĐừng đoán: bật
/actuator/conditionsquamanagement.endpoints.web.exposure.include, hoặc chạyjava -jar app.jar --debugđể in condition evaluation report ra console. Ba mục cần đọc làpositiveMatches(autoconfig đã register kèm lý do pass),negativeMatches(bị skip kèm lý do fail, ví dụ không tìm thấy classRedisTemplate) vàexclusions(bị loại qua thuộc tính exclude). ĐọcnegativeMatchescho biết chính xác condition nào fail nên fix đúng gốc: thiếu jar thì soimvn dependency:tree, thiếu property thì bổ sung cấu hình. Nếu autoconfig không xuất hiện trong report luôn thì vấn đề nằm sớm hơn — fileAutoConfiguration.importschưa đúng nên class chưa bao giờ vào danh sách candidate.Xử lý theo kiểu thử-sai (thêm bớt dependency, exclude bừa autoconfig) thay vì đọcnegativeMatches— toàn bộ dữ liệu đó đã được Spring ghi sẵn vàoConditionEvaluationReportlúc evaluate.13
Viết một starter nội bộ cho team thì cấu trúc gồm những gì?
MidStarter chuẩn gồm hai module:
X-spring-boot-autoconfigurechứa@AutoConfigurationclass cùng@ConfigurationProperties, cònX-spring-boot-starterlà jar rỗng chỉ pull autoconfigure và runtime lib. Tách hai vì module autoconfigure khai runtime lib dạngoptional=true(không transitive) cho khớp với@ConditionalOnClass, còn starter pull tường minh cả hai để consumer chỉ cần khai một dependency. Bắt buộc phải có fileMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsliệt kê class autoconfig — thiếu nó thì annotation đầy đủ tới đâu Boot cũng không biết class tồn tại, và đây là bug im lặng phổ biến nhất. Về đặt tên, starter chính chủ dùngspring-boot-starter-Xcòn third-party hay nội bộ đảo lại thànhX-spring-boot-starterđể không gây nhầm ai là người maintain.Viết đủ@AutoConfigurationrồi tin là "Boot tự quét package của tôi" — Boot chỉ đọc danh sách trong file imports; verify bằngjar tf acme-starter.jar | grep AutoConfiguration.imports.14
Có mấy cách tắt một auto-configuration, và nên chọn cách nào?
MidCó hai mức khác nhau:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)(hoặcexcludeNamekhi chỉ có tên chuỗi) loại HẲN autoconfig class khỏi danh sách candidate, còn khai một@Beancùng kiểu chỉ khiến autoconfig back-off đúng bean đó. Exclude thô hơn nhiều: bạn mất luôn phần còn lại của autoconfig class — binding@ConfigurationProperties, các bean phụ, listener đi kèm. Vì vậy mặc định nên override bằng bean, chỉ exclude khi thật sự muốn autoconfig đó biến mất hoàn toàn. Kiểm chứng bằng/actuator/conditions: class bị exclude nằm ở phầnexclusions, khác hẳnnegativeMatchesvốn là do condition fail.Exclude cả autoconfig chỉ để đổi một bean, rồi ngạc nhiên vì mất luôn property binding và các bean phụ mà autoconfig đó vẫn đang cung cấp.15
Vì sao autoconfig hay viết @ConditionalOnClass(name = "...") thay vì class literal?
MidVì module autoconfigure phải COMPILE được ngay cả khi thư viện đó không có trên classpath của chính nó. Dùng class literal thì trình biên dịch cần phân giải được class, không có là compile fail — trớ trêu ở chỗ ý định ban đầu chỉ là "chỉ chạy khi class này có mặt". Chuỗi tên thì không tạo ràng buộc compile nào; lúc runtime Boot dùng
ClassLoader.getResource(...)kiểm tra file class có tồn tại hay không rồi mới quyết định.@ConditionalOnMissingBeancũng có thuộc tínhtypenhận chuỗi vì đúng lý do đó — chỉ dùng class literal khi class chắc chắn luôn có mặt, ví dụ class của chính module autoconfigure.Cho rằng dùng chuỗi là "code bẩn" — đây là ràng buộc kỹ thuật: class literal trong annotation tạo compile dependency, còn autoconfig phải build được khi thư viện đích vắng mặt.
Topic kế cùng track: Câu hỏi phỏng vấn JPA & bài toán N+1 →
Trả lời trôi chảy bắt đầu từ hiểu cơ chế
Mỗi câu ở trên đều có bài học đứng sau. Học tuần tự cả khoá Spring Core & Boot để không chỉ trả lời được, mà giải thích được vì sao.