@Autowired hay scope.Tổng quan & IoC/DI
Spring cốt lõi là một IoC container: thay vì tự new và nối dependency, bạn khai báo và để container tạo + wiring. Hiểu rõ điều này là hiểu toàn bộ Spring.
IoC và DI là gì — khác nhau ra sao
| Khái niệm | Ý nghĩa | Vai trò trong Spring |
|---|---|---|
| IoC | Đảo ngược quyền kiểm soát: bạn không tự tạo object, giao cho framework/container | Container quản lý vòng đời + wiring của bean |
| DI | Một dạng của IoC: dependency được tiêm vào thay vì tự tìm/khởi tạo | Constructor/setter/field injection |
| Bean | Object do Spring container quản lý | Khởi tạo 1 lần, có scope và lifecycle |
Lợi ích: giảm coupling, dễ test (thay mock), quản lý scope/lifecycle, và là nền để AOP (@Transactional, @Async) hoạt động qua proxy.
Ba trụ cột cần nhớ
- Container — BeanFactory / ApplicationContext: nơi chứa và quản lý bean
- DI — cơ chế nối các bean lại với nhau
- AOP — tách cross-cutting concern (log, tx, security) khỏi business code
Luồng gọi điển hình: AppConfig → container khởi tạo → nạp @Component/@Bean → inject → sẵn sàng dùng.
Module chính của Spring Framework
| Module | Vai trò |
|---|---|
| spring-core | Nền tảng, util, type conversion |
| spring-beans | BeanFactory, định nghĩa bean |
| spring-context | ApplicationContext, event, SpEL |
| spring-aop | AOP proxy |
| spring-expression | SpEL |
| spring-test | Test context, mock |
“Spring Core” ở đây gồm core + beans + context + aop. Web, Data, Security là các module khác (file riêng).
Spring Framework 6 — điểm khác
- Yêu cầu Java 17+
- Namespace chuyển từ
javax.*sangjakarta.* @PostConstruct/@PreDestroylấy từjakarta.annotation, không cònjavax.annotation- Hỗ trợ AOT + GraalVM native image (build-time processing)
- Observation API + Micrometer cho tracing/metrics (Spring Boot 3)
@MockBeandeprecate → dùng@MockitoBean(Spring Boot 3.4+)
Nếu dự án cũ dùng javax.annotation.PostConstruct, nâng lên Spring 6 sẽ không chạy init callback — lỗi phổ biến khi migrate.
Bốn câu mở đầu hay gặp
- Spring là gì, IoC và DI khác nhau thế nào?
- Bean là gì, làm sao khai báo?
- BeanFactory và ApplicationContext khác gì?
- Constructor, setter và field injection nên dùng loại nào?
Gợi ý: trả lời ngắn gọn rồi dẫn sang một ví dụ bạn đã dùng thật — điều này tạo ấn tượng hơn kể tên.
Container
Trái tim của Spring. Nắm BeanFactory vs ApplicationContext và cách khởi tạo context là trả lời được cả loạt câu hỏi nền tảng.
BeanFactory vs ApplicationContext
| BeanFactory | ApplicationContext | |
|---|---|---|
| Khởi tạo singleton | Lazy — chỉ tạo khi getBean() | Eager — tạo hết singleton lúc khởi động |
| BeanPostProcessor | Phải đăng ký thủ công | Tự động đăng ký + kích hoạt |
| i18n / resource | Không | MessageSource, Resource loader |
| Event | Không | ApplicationEventPublisher |
| Web context | Không | Có (WebApplicationContext) |
ApplicationContext extends BeanFactory và là lựa chọn chuẩn. Eager init giúp phát hiện lỗi cấu hình (thiếu bean, cycle) ngay lúc start thay vì lúc chạy.
Khởi tạo context
Trong Spring Boot, bạn hiếm khi tự new context — SpringApplication.run() làm việc đó, tạo ServletWebServerApplicationContext.
Context hierarchy
| Thành phần | Vai trò |
|---|---|
| Root context | Bean dùng chung: service, repository, datasource |
| Child context | Mỗi DispatcherServlet một context con (controller…) |
- Context con nhìn thấy bean của context cha
- Context cha không thấy bean của con
- Spring Boot thường chỉ dùng một context duy nhất
getBean và lỗi tương ứng
Gọi getBean() trực tiếp là “anti-pattern” trong app code — nên dùng DI. Nó chỉ hợp lúc bootstrap, test, hoặc truy cập bean động.
Bean registry — chuyện bên trong
- Mỗi bean có một
BeanDefinition(class, scope, lazy, init/destroy, dependencies…) - Bean được lưu trong
DefaultListableBeanFactory singletonObjects(level-1 cache) giữ instance đã khởi tạo xong- Bean name mặc định: tên method (
@Bean) hoặc tên class viết thường chữ đầu (@Component)
Định nghĩa bean
Có hai cách khai báo bean: stereotype annotation trên class, và @Bean trong @Configuration. Biết khi nào dùng cái nào.
Stereotype annotation
| Annotation | Dùng cho |
|---|---|
| @Component | Component chung |
| @Service | Tầng nghiệp vụ (ngữ nghĩa rõ ràng) |
| @Repository | DAO; dịch lỗi persistence → DataAccessException |
| @Controller / @RestController | Web (file Boot & Web) |
Ba annotation kia đều được meta-annotated bằng @Component — về chức năng như nhau, khác ở ngữ nghĩa và xử lý riêng.
@Component vs @Bean
| @Component | @Bean | |
|---|---|---|
| Vị trí | Trên class | Trên method trong config |
| Phát hiện | Tự động qua component scan | Khai báo tường minh |
| Phù hợp | Class do bạn viết | Class thư viện bên ngoài, hoặc cần tuỳ biến lúc tạo |
Class của bên thứ ba (DataSource, ObjectMapper, RestTemplate) không thể gắn @Component → phải dùng @Bean.
Java config mẫu
Tham số của method @Bean được Spring tự inject (theo type/name như bình thường). Không cần viết @Autowired trên method @Bean.
@Configuration full vs lite mode
Full mode: class @Configuration bị CGLIB subclass để chặn lời gọi @Bean chéo, đảm bảo trả về cùng singleton.
@ComponentScan
- Mặc định quét từ package của class config trở xuống
@SpringBootApplicationđã bọc sẵn@ComponentScan- Quét quá rộng → chậm start; quét hẹp → thiếu bean
Tên bean, alias và đụng độ
- Tên mặc định: class
CreditCardPayment→creditCardPayment - Hai bean cùng tên → bean định nghĩa sau ghi đè (hoặc lỗi nếu
allow-bean-definition-overriding=false) - Hai bean cùng type mà không có
@Primary/@Qualifier→ lỗi lúc inject
Dependency Injection
Ba kiểu injection và cách Spring phân giải @Autowired khi có nhiều ứng viên — phần được hỏi nhiều nhất sau khái niệm IoC.
Ba kiểu injection
| Kiểu | Ưu | Nhược |
|---|---|---|
| Constructor | Immutable, dependency bắt buộc rõ ràng, dễ test | Constructor dài khi nhiều dependency |
| Setter | Tùy chọn, thay đổi sau init | Không immutable, dễ sót dependency |
| Field | Gọn | Khó test, cần reflection, không final, dễ NPE |
@Autowired phân giải thế nào
- Tìm bean theo type
- Nhiều ứng viên → khớp theo name (tên field/tham số)
- Vẫn mơ hồ → dùng
@Primaryhoặc@Qualifier - Không có bean →
NoSuchBeanDefinitionException(trừ khirequired=false)
Spring chọn @Primary khi không có @Qualifier; nếu có @Qualifier thì qualifier thắng primary.
@Qualifier và @Primary
@Qualifier nhận tên bean (mặc định là tên class viết thường chữ đầu). Có thể tạo qualifier tùy biến bằng @Qualifier trên annotation khác.
Optional, collection và lazy
Muốn tiêm theo thứ tự, đánh @Order(n) trên từng bean — Spring sắp xếp collection theo đó.
Vì sao constructor injection tốt hơn
- Field có thể
final→ immutable, an toàn - Dependency bắt buộc hiện rõ trong chữ ký
- Test bằng
new OrderService(mock)không cần Spring - Lỗi thiếu dependency nổ ngay lúc khởi động
- Tránh reflection và NPE của field injection
Field injection khiến class khó kiểm thử độc lập và che giấu sự phình to dependency. Nếu constructor quá dài, đó là tín hiệu nên tách class.
@Resource và @Inject (JSR)
| @Autowired | Spring, theo type rồi name |
| @Resource | JSR-250, theo name trước (có thể chỉ định) |
| @Inject | JSR-330, theo type; kèm @Named thay cho qualifier |
Trong dự án Spring, cứ dùng @Autowired + constructor cho nhất quán; JSR annotation chỉ khi cần chuẩn chung với CDI.
Bean scope
Scope quyết định vòng đời của bean. Câu hỏi kinh điển: prototype tiêm vào singleton thì tạo mấy lần?
Các scope
| Scope | Hành vi |
|---|---|
| singleton | Mặc định — 1 instance cho cả container |
| prototype | Mỗi lần yêu cầu/inject tạo instance mới |
| request | 1 instance cho mỗi HTTP request |
| session | 1 instance cho mỗi HTTP session |
| application | 1 instance cho mỗi ServletContext |
| websocket | 1 instance cho mỗi WebSocket session |
Bẫy: prototype tiêm vào singleton
Dependency được inject một lần duy nhất lúc singleton khởi tạo, nên prototype không được tạo mới cho mỗi lần dùng. Đây là nhầm lẫn phổ biến nhất về scope.
Web scope
Web scope cần proxy vì singleton (controller) tham chiếu tới bean ngắn hạn. Nếu không có proxy sẽ lỗi ngay lúc khởi tạo.
Singleton có thread-safe không?
- Singleton là 1 instance chia sẻ cho mọi thread
- Spring không tự đồng bộ — thread-safety là trách nhiệm của bạn
- Nguyên tắc: bean singleton nên stateless (không giữ state mutable)
- Có state → dùng scope ngắn hơn hoặc tự đồng bộ/immutable
Giữ private int counter; rồi tăng trong method của bean singleton là race condition điển hình.
Vòng đời bean
Thứ tự: constructor → inject → @PostConstruct → sẵn sàng → @PreDestroy. BeanPostProcessor là “phép màu” đằng sau mọi thứ.
Sơ đồ vòng đời
Vì BeanPostProcessor chạy cho mọi bean, nó là nền tảng của @Autowired, @Transactional, proxy AOP. Prototype bean không nhận @PreDestroy (container không giữ reference).
Callback khởi tạo / hủy
Thứ tự chi tiết
- Bean được instatiate (constructor)
- Inject dependencies (field/setter)
- Gọi các Aware interface (BeanNameAware, ApplicationContextAware…)
BeanPostProcessor.postProcessBeforeInitialization@PostConstruct/afterPropertiesSet()BeanPostProcessor.postProcessAfterInitialization- Bean sẵn sàng; khi shutdown →
@PreDestroy/destroy()
Không gọi logic phụ thuộc dependency chưa inject trong constructor — để vào @PostConstruct.
BeanPostProcessor
Nếu muốn xử lý định nghĩa bean (trước khi instance tồn tại), dùng BeanFactoryPostProcessor — ví dụ PropertySourcesPlaceholderConfigurer xử lý ${...}.
Khi nào callback không chạy
- Prototype bean:
@PreDestroykhông được gọi tự động - Dùng
@Bean(initMethod=...)+@Componentcó cả hai → chạy cả hai - Nhầm package
javax.annotation(cũ) → callback bị bỏ qua âm thầm - Bean lười (
@Lazy) chỉ chạy init khi được dùng lần đầu
Cấu hình & properties
Ba cách lấy cấu hình: @Value (đơn giản), @ConfigurationProperties (gõ mạnh, có nhóm), và SpEL. Kèm theo @Profile và @Conditional.
@Value và SpEL
${...} là property placeholder, #{...} là SpEL. Trộn được: #{'${app.env}' + '!'}.
@ConfigurationProperties (gõ mạnh)
Ưu tiên @ConfigurationProperties hơn nhiều @Value rải rác: gõ mạnh, có nhóm, validate được bằng Bean Validation, và relaxed binding (app.my-host ↔ myHost).
Thứ tự ưu tiên properties
- Command line arguments (
--app.name=x) SPRING_APPLICATION_JSON- Java System properties
- OS environment variables
application-{profile}.ymlapplication.yml@PropertySource/ giá trị mặc định
(Đầy đủ nhất khi dùng Spring Boot; bản chất là các PropertySource xếp chồng, nguồn trên ghi đè nguồn dưới.)
@Profile
Kích hoạt: spring.profiles.active=dev hoặc --spring.profiles.active=dev; trong test dùng @ActiveProfiles("dev").
@Conditional
Spring Boot mở rộng: @ConditionalOnProperty, @ConditionalOnClass, @ConditionalOnMissingBean — nền tảng của auto-configuration.
Environment & PropertySource
Environment= tậpPropertySource+ active profilesPropertySource: file yml/properties, system, env vars…- Tiêm
Environment envrồienv.getProperty("app.name") @PropertySource("classpath:custom.properties")thêm nguồn riêng
Circular dependency
A cần B, B cần A. Spring có thể “chữa” một phần nhờ 3-level cache, nhưng tốt nhất là đừng tạo ra nó.
Vấn đề
Vòng phụ thuộc qua constructor gần như không thể giải — Spring ném BeanCurrentlyInCreationException. Vòng qua setter/field có thể được “vá” nhờ tham chiếu sớm.
Spring xử lý bằng 3-level cache
| Cache | Chứa gì |
|---|---|
| Level 1 — singletonObjects | Bean đã khởi tạo xong |
| Level 2 — earlySingletonObjects | Tham chiếu sớm (bean đang tạo) |
| Level 3 — singletonFactories | ObjectFactory để tạo tham chiếu sớm |
Khi B cần A mà A chưa xong, Spring đưa tham chiếu sớm của A (level 3 → level 2) vào B. Vì vậy setter/field cycle mới tồn tại được.
Cách xử lý
Nên tránh
- Cycle là mùi thiết kế: hai class dính chặt vào nhau
- Spring Boot 2.6+ cấm circular reference mặc định
- Giải pháp đúng là cấu trúc lại: tách responsibility, đưa logic chung ra bean thứ ba
@Lazychỉ là phương án tình thế, không phải kiến trúc
Bật lại bằng spring.main.allow-circular-references=true là che giấu vấn đề, đừng làm trong code mới.
Proxy & AOP
@Transactional, @Async, @Cacheable đều hoạt động nhờ proxy AOP. Hiểu proxy và bẫy self-invocation là hiểu vì sao chúng “đôi khi không chạy”.
Thuật ngữ AOP
| Aspect | Mô-đun chứa cross-cutting concern |
| Advice | Hành động (before/after/around) |
| Pointcut | Biểu thức chọn các join point |
| Join point | Điểm thực thi (method call…) |
| Target | Object bị áp dụng |
| Proxy | Object thay thế, bọc lời gọi |
| Weaving | Ghép aspect vào code |
Các loại advice
@Around mạnh nhất (đo thời gian, retry, cache) nhưng phải gọi pjp.proceed() và trả về đúng giá trị, nếu không method gốc không chạy.
Pointcut thường dùng
| execution(* c.a.s.*.*(..)) | mọi method public của service |
| within(c.a.s..*) | mọi type trong package service |
| @annotation(Transactional) | method có annotation đó |
| bean(*Service) | bean có tên kết thúc “Service” |
| args(String, ..) | tham số đầu là String |
execution là linh hoạt nhất: return-type class.method(arguments); .. = bất kỳ tham số, * = bất kỳ tên.
JDK dynamic proxy vs CGLIB
| JDK dynamic proxy | CGLIB | |
|---|---|---|
| Cơ chế | java.lang.reflect.Proxy theo interface | Sinh class con bằng bytecode |
| Yêu cầu | Bean phải có interface | Class không được final; method không final/private |
| Khi chọn | proxyTargetClass=false (mặc định @EnableAspectJAutoProxy) | proxyTargetClass=true; Spring Boot bật sẵn |
Spring Boot đặt proxyTargetClass=true mặc định nên hầu hết bean được proxy bằng CGLIB — vì vậy gọi qua interface hay class đều “bắt” được advice.
Bẫy self-invocation — kinh điển
Proxy chỉ can thiệp khi lời gọi từ bên ngoài đi vào. Gọi this.method() là gọi thẳng đối tượng thật, bỏ qua mọi advice.
@Transactional nhìn từ AOP
@Transactionalđược xử lý bởiTransactionInterceptor— một Advice- Mở tx trước method, commit/rollback sau — tất cả qua proxy
- Vì vậy self-invocation, method
private, gọi trênnewđều mất transaction - Chi tiết propagation/isolation/rollback → xem file Spring Data JPA & Hibernate
Events
Dùng event để nới lỏng coupling: publish từ nơi xảy ra, listen ở nơi quan tâm — không cần biết nhau.
Publish & listen
Không cần implement ApplicationListener nữa — @EventListener là đủ. Một listener có thể nhận nhiều kiểu event.
@TransactionalEventListener
AFTER_COMMIT— phổ biến nhất (gửi mail, đẩy queue sau khi lưu xong)AFTER_ROLLBACK,BEFORE_COMMIT,AFTER_COMPLETION- Nếu không có transaction, event bị bỏ qua (fallbackExecution=true để vẫn chạy)
Đồng bộ hay bất đồng bộ
- Mặc định: đồng bộ — cùng thread, cùng transaction với nơi publish
- Bất đồng bộ: thêm
@Async+@EnableAsync - Async listener chạy thread khác → không còn nằm trong transaction gốc
- Thứ tự nhiều listener cùng event:
@Order
Khi nào nên dùng event
- Mở rộng hành vi mà không sửa code gốc (audit, thông báo, thống kê)
- Nới lỏng coupling giữa các module
- Tránh dùng cho luồng chính: event làm logic khó theo dõi, khó debug
- Không lạm dụng async event cho dữ liệu cần nhất quán tức thời
Testing
Spring Test cung cấp context cho test và cache context để chạy nhanh. Biết cái này để viết test gọn và không bị “test chậm”.
Setup context cho test
Dùng JUnit 5 + spring-test. @ContextConfiguration chỉ load đúng config bạn khai báo — nhanh hơn full context.
Mock bean
@MockBean bị deprecate từ Spring Boot 3.4 → dùng @MockitoBean. Mỗi mock khác nhau tạo một context cache khác nhau, nên đặt mock nhất quán giữa các test.
Context caching
- Spring cache context theo key: config class + profiles + properties…
- Các test dùng cùng key → dùng lại context → nhanh hơn nhiều
- Test làm “bẩn” context (đổi bean, đổi state) →
@DirtiesContextđể reset - Context chỉ tạo lại khi key khác nhau
Đây là lý do nên giữ cấu hình test ổn định: càng ít biến thể context, test càng nhanh.
Tùy biến cho riêng test
Core vs Boot testing
| @ContextConfiguration | Chỉ load config chỉ định — Spring Core |
| @SpringBootTest | Load full application context — Boot (file riêng) |
| @WebMvcTest | Chỉ tầng web + MockMvc — Boot & Web |
Trong dự án Boot thực tế, bạn sẽ dùng @SpringBootTest là chủ yếu; mục này để phân biệt ranh giới khái niệm.
Bẫy & checklist
Tổng hợp lỗi mà người phỏng vấn hay cài vào để kiểm tra bạn có thực sự hiểu cơ chế proxy và scope hay không.
Mười bẫy kinh điển
| Bẫy | Chuyện xảy ra | Xử lý |
|---|---|---|
| Prototype trong singleton | Prototype chỉ tạo 1 lần | proxyMode hoặc ObjectProvider |
| Self-invocation | this.txMethod() mất @Transactional | Tách class / self-injection |
| Field injection | Khó test, không final, dễ NPE | Constructor injection |
| Nhiều bean cùng type | NoUniqueBeanDefinitionException | @Primary / @Qualifier |
javax.annotation | @PostConstruct không chạy trên Spring 6 | Chuyển sang jakarta.annotation |
| @Configuration lite | Gọi @Bean chéo tạo nhiều instance | Dùng @Configuration full mode |
| Singleton có state | Race condition giữa các thread | Bean stateless / immutable |
| Constructor cycle | BeanCurrentlyInCreationException | Refactor, @Lazy |
| Async thiếu @EnableAsync | @Async vẫn chạy đồng bộ | Thêm @EnableAsync |
Gọi getBean() trong service | Bỏ qua DI, code khó test | Tiêm qua constructor |
Nên
Dùng constructor injection, field final.
Bean singleton để stateless.
@Configuration cho config, @Component cho class của bạn.
@ConfigurationProperties thay nhiều @Value rải rác.
Tách class khi constructor quá 5–6 tham số.
Dùng @Qualifier/@Primary khi nhiều implementation.
Đặt logic khởi tạo vào @PostConstruct, không phải constructor.
Tách cross-cutting concern bằng AOP thay vì copy-paste.
Giữ cấu hình test ổn định để tận dụng context cache.
Tránh
Field injection (trừ legacy code).
Circular dependency qua constructor.
Prototype tiêm trực tiếp vào singleton mà không proxy.
Gọi this.@Transactional() nội bộ.
Bean singleton giữ state mutable.
Bắt NoUniqueBeanDefinitionException bằng cách đoán tên.
@Lazy để “chữa” mọi cycle thay vì refactor.
Gọi ctx.getBean() trong business code.
Quét component scan quá rộng.
Dùng @Value cho object phức tạp (nên @ConfigurationProperties).
Checklist tự đánh giá
| Mức | Bạn cần trả lời được |
|---|---|
| Junior | IoC/DI là gì, bean là gì, @Component vs @Bean, constructor vs field injection, @Autowired/@Qualifier, default scope |
| Middle | BeanFactory vs ApplicationContext, vòng đời bean, BeanPostProcessor, prototype trap, @Configuration full/lite, circular dependency, proxy JDK/CGLIB + self-invocation |
| Senior | Thiết kế context, @Conditional/@Profile chiến lược, AOP pointcut tinh chỉnh, event + transaction boundary, chiến lược testing & context cache, trade-off khi chọn giải pháp |
Hỏi nhanh đáp gọn
Tự trả lời trước khi mở. Câu nào phải mở mới nhớ, đánh dấu lại mục tương ứng ở trên.
IoC (Inversion of Control) là gì?
Đảo ngược quyền kiểm soát: thay vì code tự new và quản lý object, bạn giao việc đó cho framework/container. Spring container tạo object (bean), nối dependency, quản lý vòng đời — code chỉ khai báo và nhận.
DI khác IoC thế nào?
IoC là nguyên tắc chung (đừng tự tạo object). DI là một cách hiện thực của IoC: dependency được truyền vào từ ngoài (constructor/setter/field) thay vì tự tìm hay khởi tạo bên trong.
BeanFactory và ApplicationContext khác gì?
BeanFactory là interface nền, lazy (tạo bean khi getBean), ít tính năng. ApplicationContext extends nó, eager-init singleton lúc start, tự kích hoạt BeanPostProcessor, có i18n, event, resource loading, web context. Dùng ApplicationContext là chuẩn.
Bean là gì, khai báo thế nào?
Bean là object do Spring container quản lý. Khai báo bằng stereotype annotation (@Component/@Service/@Repository) cho class của bạn, hoặc @Bean trong @Configuration cho class thư viện ngoài.
@Component khác @Bean ở đâu?
@Component đặt trên class, được component scan tự phát hiện. @Bean đặt trên method trong config, khai báo tường minh — dùng khi class không phải của bạn hoặc cần logic tạo phức tạp.
Constructor, setter hay field injection?
Ưu tiên constructor: field có thể final, dependency bắt buộc rõ ràng, dễ test bằng new. Setter cho dependency tùy chọn. Field injection gọn nhưng khó test, không final, dễ NPE — tránh dùng.
@Autowired phân giải thế nào khi nhiều bean cùng type?
Theo thứ tự: type → nếu nhiều thì khớp theo tên field/param → nếu vẫn mơ hồ dùng @Primary (ưu tiên mặc định) hoặc @Qualifier (chọn tường minh, thắng primary). Không có bean → NoSuchBeanDefinitionException.
@Primary và @Qualifier khác gì?
@Primary đánh dấu bean ưu tiên khi nhiều ứng viên cùng type, áp dụng tự động khi không có chỉ định. @Qualifier("tên") chọn đúng bean theo tên tại điểm inject và ghi đè @Primary.
@Service, @Repository khác @Component thế nào?
Cả ba đều được meta-annotate bằng @Component, chức năng như nhau. Khác ở ngữ nghĩa và một xử lý riêng: @Repository dịch lỗi persistence thành DataAccessException; @Service/@Controller chủ yếu để phân tầng rõ ràng.
Scope mặc định là gì, prototype trap ra sao?
Mặc định singleton (1 instance/container). Prototype tạo mới mỗi lần yêu cầu, nhưng nếu tiêm prototype vào singleton thì chỉ tạo 1 lần khi singleton init. Fix bằng proxyMode hoặc ObjectProvider.
Vòng đời bean gồm những bước nào?
Constructor → inject dependency → Aware callbacks → BeanPostProcessor.before → @PostConstruct → BeanPostProcessor.after → sẵn sàng → khi shutdown @PreDestroy. Prototype không nhận @PreDestroy.
@PostConstruct vs InitializingBean vs init-method?
@PostConstruct (jakarta.annotation) là cách chuẩn, không coupling Spring. InitializingBean.afterPropertiesSet() và @Bean(initMethod=...) tương đương nhưng gắn chặt vào Spring/XML. Ưu tiên @PostConstruct.
@Configuration full mode và lite mode khác gì?
Full mode (class có @Configuration) bị CGLIB subclass nên lời gọi @Bean chéo trả về cùng instance singleton. Lite mode (@Component/method @Bean) không proxy, gọi chéo tạo instance mới.
Circular dependency xử lý thế nào?
Constructor cycle → BeanCurrentlyInCreationException. Setter/field cycle có thể vá nhờ 3-level cache (tham chiếu sớm). Nhưng cycle là mùi thiết kế: nên refactor, hoặc dùng @Lazy/ObjectProvider tạm thời. Boot 2.6+ cấm mặc định.
Proxy cơ chế: JDK vs CGLIB, self-invocation là gì?
Spring AOP tạo proxy: JDK dynamic proxy theo interface, hoặc CGLIB subclass (không proxy final/private). Self-invocation là khi gọi this.method() bên trong class — lời gọi không qua proxy nên advice/@Transactional mất tác dụng.
AOP dùng khi nào, @Around khác @Before?
AOP cho cross-cutting concern: log, đo thời gian, transaction, security. @Before chỉ chạy trước method; @Around bao quanh, kiểm soát proceed() (đo thời gian, retry, cache) — mạnh nhất nhưng phải gọi proceed().
@Transactional hoạt động qua cái gì?
Qua proxy AOP (TransactionInterceptor): mở transaction trước method, commit/rollback sau. Vì vậy self-invocation, method private, hoặc gọi trên new đều không có transaction. Chi tiết propagation/isolation xem file JPA/Hibernate.
@Value và @ConfigurationProperties khác gì?
@Value("${...}") đọc từng giá trị, đơn giản nhưng rời rạc. @ConfigurationProperties(prefix=...) gom nhóm vào class/record gõ mạnh, có relaxed binding và validate được — ưu tiên cho cụm cấu hình.
@Profile và @Conditional dùng để làm gì?
@Profile("dev") kích hoạt bean theo profile đang chạy (spring.profiles.active). @Conditional tổng quát hơn, quyết định bằng điều kiện tự viết; Boot mở rộng thành @ConditionalOnProperty/@ConditionalOnClass…
Singleton bean có thread-safe không?
Không tự động. Singleton là 1 instance chia sẻ mọi thread; Spring không đồng bộ giúp bạn. Vì vậy singleton nên stateless; nếu có state phải tự đồng bộ, dùng immutable, hoặc chuyển scope ngắn hơn.
BeanPostProcessor vs BeanFactoryPostProcessor?
BeanPostProcessor chạy sau khi instance tồn tại, quanh bước khởi tạo của mọi bean — nền của @Autowired, proxy. BeanFactoryPostProcessor chạy trước, sửa bean definition (ví dụ xử lý ${...}).
Events dùng thế nào, @TransactionalEventListener?
Publish bằng ApplicationEventPublisher.publishEvent, lắng nghe bằng @EventListener. @TransactionalEventListener(AFTER_COMMIT) chỉ chạy sau khi transaction commit — hợp cho gửi mail/đẩy queue sau khi lưu dữ liệu.
Context caching trong test là gì?
Spring Test cache context theo key (config + profiles + properties). Các test cùng key dùng lại context thay vì tạo mới → chạy nhanh. Test làm bẩn context thì đánh @DirtiesContext để tạo lại.
@MockBean và @MockitoBean khác gì?
Cùng mục đích: thay bean thật bằng mock trong context test. @MockBean bị deprecate từ Spring Boot 3.4 → chuyển sang @MockitoBean. Đổi mock làm đổi key context cache.
Spring 6 thay đổi gì quan trọng?
Yêu cầu Java 17+, namespace chuyển javax.* → jakarta.* (nên @PostConstruct lấy từ jakarta.annotation), hỗ trợ AOT/GraalVM native, quan sát bằng Micrometer. Migrate nhầm package là lỗi phổ biến.
Luyện trắc nghiệm
12 câu đúng kiểu bẫy phỏng vấn. Chọn đáp án để xem giải thích ngay — điểm và kỷ lục được lưu trên máy bạn.