Spring core interview questions: IoC, Dependency Injection, AOP, beans, transactions and MVC.
30 questions
IoC means the framework controls the flow and lifecycle of objects instead of the application. DI is the technique Spring uses to achieve it: the container builds objects (beans) and injects their dependencies rather than each object creating its own.
With DI you write against interfaces, and the container wires concrete implementations - e.g. a field-based @Autowired, constructor injection, or setter injection. Benefits: loose coupling, easier testing (mock dependencies) and easier swapping of implementations.
Stateless services should be singletons; stateful beans or expensive per-call objects may need prototype scope.
All four register a class as a Spring bean via component scanning; they differ semantically:
AOP extracts cross-cutting concerns (logging, transactions, security) into aspects. Key terms: join point (where an aspect runs, e.g. a method execution), pointcut (expression selecting join points), advice (the action: @Before, @After, @Around), target object and proxy.
Spring AOP is proxy-based: it wraps the target bean (JDK dynamic proxy or CGLIB) and intercepts calls that match pointcuts. It only works on Spring-managed beans and on method calls through the proxy.
@Transactional wraps a method in a transaction that commits on success and rolls back on RuntimeException (checked exceptions do not roll back unless configured).
Propagation decides what happens when a transactional method calls another:
Note: self-invocation bypasses the proxy, so annotating a method called from within the same class has no effect.
BeanFactory is the low-level container that provides basic DI. ApplicationContext extends it and adds enterprise features: message i18n, event publishing, AOP integration, web-aware contexts and earlier detection of configuration errors.
In modern Spring you always use ApplicationContext (e.g. AnnotationConfigApplicationContext); BeanFactory is mostly of historical interest for embedding in other frameworks in a minimal way.
Three styles: constructor injection (dependencies passed via constructor - recommended, makes beans immutable and guarantees they exist), setter injection (setters called after construction - allows reconfiguration), and field injection with @Autowired on fields (concise but hard to test and hides dependencies). Modern guidance: prefer constructor injection.
A bean is any object managed by the Spring IoC container - its lifecycle is controlled by the container. Beans are defined with XML +beans/bean+ entries, Java configuration (@Configuration + @Bean methods), or component scanning annotations (@Component/@Service/@Repository/@Controller).
By default singleton beans are eager: they are created when the context starts. With @Lazy (or the lazy-init property), a bean is created only when first requested/injected. Lazy init reduces startup time but can defer errors and break circular dependency resolution. Use it for optional or heavy beans.
Constructor injection of mutually dependent singletons fails with BeanCurrentlyInCreationException. Options: use setter/field injection for one side (Spring resolves prototype-ish via lazy proxies), mark one dependency @Lazy so a proxy is injected, or refactor to remove the cycle (extract an interface or an event). Prefer redesigning the code.
@Autowired performs dependency injection by type first; if multiple beans of the same type exist, it falls back to the bean name and finally to @Qualifier to disambiguate. Use @Qualifier("name") when you have several implementations of one interface.
@Primary marks one bean as the default when several candidates exist.
The container is the part of Spring that creates beans, wires dependencies and manages their lifecycles. The two interfaces are BeanFactory (lazy, minimal container) and ApplicationContext (a superset adding messages, events, resource loading and eager singleton initialization). In practice you always use ApplicationContext.
AOP works through a proxy wrapping the target bean, so calls that enter via the proxy get intercepted. A call from one method of a class to another method of the same class uses this and bypasses the proxy - so @Transactional/@Async/pointcut advice silently does not run. Fixes: inject a self-proxy (ObjectProvider, AopContext.currentProxy) or split the classes.
SpEL is an expression language evaluated at runtime: #{systemProperties['user.home']}, @Value("#{myBean.method()}"), or conditions like #{'prod'.equals(someProp)}. It is used in annotations (@Value, @PreAuthorize), XML and V/SQL query definitions. Different from externalized property placeholders ${...} which simply read properties.
Spring MVC implements the MVC pattern for web apps. DispatcherServlet is the front controller: every HTTP request arrives at it, it resolves the handler mapping → calls the controller method → applies interceptors → resolves the view (ViewResolver) or writes the body → returns the response.
@RequestMapping("/path") maps a URL (optionally restricted by HTTP method with method = RequestMethod.GET). The shortcuts @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping combine the path with a single HTTP method, improving readability.
Besides @ExceptionHandler methods, @ControllerAdvice can add data to all models with @ModelAttribute methods, register @InitBinder methods for form binding customization, and help implement message converters. It lets cross-cutting controller behavior live in one class.
@Transactional marks a method/class so Spring's transaction interceptor starts a transaction before it and commits/rolls back after. Rebounds: transactional proxies wrap the bean, rollback happens by default on unchecked (RuntimeException) exceptions - checked exceptions do not trigger rollback unless declared in the rollbackFor attribute.
Propagation defines what happens when a transactional method is called within another. Common modes: REQUIRED (default - join an existing transaction or create one), REQUIRES_NEW (always suspend the outer transaction and create a new one), MANDATORY (must already be in a transaction), NOT_SUPPORTED (run without a transaction), NEVER (error if a transaction exists).
Isolation controls how transactions see each other: READ_UNCOMMITTED (dirty reads possible), READ_COMMITTED (only committed data, but non-repeatable reads), REPEATABLE_READ (rows lock so reads are consistent), SERIALIZABLE (highest isolation, full locking, lowest concurrency). Set via @Transactional(isolation = Isolation.SERIALIZABLE).
Spring Data JPA lets you define repository interfaces; Spring generates the implementation at runtime. Declaring a method like List<User> findByEmailAndActive(String email, boolean active) is automatically translated into a query by the method name, or annotated with @Query.
Repository methods accept a Pageable: userRepository.findAll(PageRequest.of(page, size, Sort.by("name"))), returning a Page with content, total elements and total pages. Derived queries can also take Pageable, e.g. findByRole(String role, Pageable pageable).
JdbcTemplate is a thin utility over JDBC: you write the SQL and map rows manually, giving full control and performance. Spring Data JPA manages entity mapping, caching, lazy loading and queries generated from method names or JPQL - faster to build, at the cost of less SQL control.
Application events decouple components. A class extends ApplicationEvent (or you use a plain object since Spring 4.2), the producer calls applicationEventPublisher.publishEvent(new OrderCreated(order)), and listeners react with:
@EventListener
public void on(OrderCreated e) { ... }By default events are synchronous in the same thread; combine with @Async for asynchronous processing.
Both are stereotypes that register beans. @Repository additionally enables Spring's persistence exception translation: exceptions thrown by a persistence provider (JPA/Hibernate) are translated into Spring's DataAccessException hierarchy, regardless of which provider you use.
Annotate beans or configurations with @Profile("dev"), then activate profiles with spring.profiles.active (or the SPRING_PROFILES_ACTIVE env var). In tests @ActiveProfiles("test") selects profiles. Beans whose profile is not active are simply not created.
A BeanPostProcessor hooks into the bean lifecycle of every bean (before/after initialization), letting you modify or proxy beans. A BeanFactoryPostProcessor runs before any bean is instantiated and can modify bean definitions themselves, even before they are used.
@Configuration classes are processed by CGLIB and enforce singleton semantics: @Bean methods are intercepted so each bean is created once even if called repeatedly. @Component classes are not enhanced, so a @Bean method inside one may return a new instance every call. Use @Configuration for bean definitions, @Component for your own service classes.
Use plain JUnit with mocks to test one unit: construct the service with Mockito mocks of its dependencies. For integration, use @SpringBootTest with context, @MockBean to replace collaborators, and @DirtiesContext where needed. Keep domain logic tests free of Spring for fast feedback.