Spring Core - Annotation-Based Configuration and Component Scanning
Explain and use Annotation-based Configuration
Annotation-based configuration allows you to configure your Spring application using annotations instead of traditional XML files. This approach improves readability, reduces boilerplate, and leverages Java's type-safety.
1. Define Components
@Service
public class MyService {
public String getMessage() {
return "Hello, Spring!";
}
}
@Controller
public class MyController {
@Autowired
private MyService myService;
public void showMessage() {
System.out.println(myService.getMessage());
}
}
2. Configure Application
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}
3. Bootstrap the Application
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyController controller = context.getBean(MyController.class);
controller.showMessage();
}
}
Discuss Best Practices for Configuration choices
Prefer Java-Based Configuration Over XML
- Use @Configuration classes instead of XML for cleaner, type-safe, and IDE-friendly configurations.
- XML can still be used for legacy systems or specific integration scenarios.
Leverage @ComponentScan for Component Discovery
- Use @ComponentScan to automatically discover beans annotated with @Component, @Service, @Repository, or @Controller.
- Restrict the scan to specific packages for better performance and clarity.
Use @ConfigurationProperties for Externalized Configuration
- Group related properties using @ConfigurationProperties instead of scattering @Value annotations.
- This approach improves readability and enables validation.
Enable Profiles for Environment-Specific Configurations
- Use Spring Profiles (@Profile) to handle different environments (e.g., dev, prod, test).
- Define environment-specific properties in files like application-dev.properties and application-prod.properties.
Avoid Hardcoding Values
- Always externalize configuration using application.properties, application.yml, or environment variables.
- Use the @Value or @ConfigurationProperties annotations to inject values.
Use Factory Methods for Complex Bean Initialization
- Define beans with complex initialization logic using @Bean factory methods in @Configuration classes.
- Avoid writing initialization logic directly in constructors.
Ensure Proper Dependency Management
- Use @Autowired for dependencies but prefer constructor injection for mandatory dependencies (supports immutability and easier testing).
- Use @Lazy to delay bean initialization when needed.
Break Configuration into Multiple Classes
- Split configuration into smaller, logically grouped classes (e.g., database configuration, security configuration) for better maintainability.
- Use @Import to combine them when necessary.
Document Custom Configuration Classes
- Add comments to custom configuration classes to explain their purpose and usage.
Avoid Overuse of @Value
- For complex or reusable configurations, prefer @ConfigurationProperties over multiple @Value annotations.
Secure Sensitive Configuration
- Never store sensitive information (e.g., passwords, API keys) directly in property files.
- Use tools like Spring Cloud Config, Vault, or environment variables for secure storage.
Optimize for Testing
- Create dedicated configurations for tests (e.g., @TestConfiguration) to isolate testing environments.
- Use in-memory databases like H2 for testing instead of production databases.
Use Defaults in Properties
- Provide sensible defaults in property files to avoid application crashes when certain values are missing.
Use Spring Boot Starters
- Rely on Spring Boot starters for common configurations (e.g., database, web, security) to reduce boilerplate.
Monitor Bean Scope Usage
- Use the appropriate scope (singleton, prototype, request, etc.) for each bean.
- Default to singleton unless a different scope is explicitly required.
Enable Lazy Initialization for Unused Beans
- Configure lazy initialization globally (spring.main.lazy-initialization=true) or on a per-bean basis (@Lazy).
Validate Configuration Properties
- Use property validation with @Validated and constraints like @NotNull or @Min for @ConfigurationProperties.
Use Conditional Annotations
- Use @Conditional or @ConditionalOnProperty to load beans conditionally based on configuration or environment.
Minimize Global Configuration in Spring Boot
- Use application-specific property prefixes (e.g., myapp.feature.enabled) to avoid conflicts with other modules.
Leverage IDE Tools
- Use IDEs like IntelliJ IDEA or Eclipse for auto-completion and validation of Spring configurations.
Use @PostConstruct and @PreDestroy
@PostConstruct is used for performing initialization tasks after the bean has been created and dependencies injected.
@PreDestroy is used for performing cleanup tasks before the bean is destroyed.
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class MyService {
// Constructor and properties
private String name;
public MyService() {
System.out.println("MyService constructor is called");
}
@PostConstruct
public void init() {
this.name = "Spring Bean";
System.out.println("MyService bean initialized, name set to: " + name);
}
@PreDestroy
public void cleanup() {
System.out.println("MyService bean is being destroyed");
}
public void doSomething() {
System.out.println("Doing something in MyService...");
}
}
// Output when the application context is started
MyService constructor is called
MyService bean initialized, name set to: Spring Bean
// Output when the application context is closed (bean destroyed):
MyService bean is being destroyed
Order of Execution:
- @PostConstruct is executed immediately after the bean is fully initialized (dependencies injected and all setup complete).
- @PreDestroy is executed just before the bean is destroyed (during the container shutdown).
Limitations in Spring Boot:
- If you're using Spring Boot, @PostConstruct and @PreDestroy methods will work as expected, but make sure that the beans are managed by the Spring container (annotated with @Component or similar).
Alternative to @PostConstruct and @PreDestroy:
- If using Spring’s @Configuration classes, you can use @Bean methods with initMethod and destroyMethod attributes for initialization and destruction instead.
Exception Handling:
- If an exception is thrown in the method annotated with @PostConstruct or @PreDestroy, it will be propagated and may cause issues during bean initialization or destruction. Ensure that such methods handle exceptions properly.
Explain and use “Stereotype” Annotations
Spring provides several stereotype annotations that are specialized versions of @Component. These annotations are used to define beans in specific layers of the application, helping to clarify their roles. They also enable better semantics for different types of Spring beans (e.g., service layer, data access layer, presentation layer).
@Component
- Generic bean annotation: Marks a class as a Spring-managed bean. It is the most general annotation for defining beans in the application context.
- Use case: When the class does not fall into a more specific role like @Service, @Repository, or @Controller.
@Component
public class MyComponent {
public void doSomething() {
System.out.println("Doing something in MyComponent");
}
}
@Service
- Service layer annotation: Specialization of @Component for service classes that hold business logic.
- Use case: Typically used to define beans that provide business services or perform tasks such as calculations, transformations, etc.
@Service
public class MyService {
public String processData(String data) {
return "Processed: " + data;
}
}
@Repository
- Persistence layer annotation: Specialization of @Component for DAO (Data Access Object) classes that interact with a database or other data sources.
- Use case: Typically used for classes that encapsulate data access logic (e.g., database interactions via Hibernate, JPA, or JDBC).
@Repository
public class MyRepository {
public void saveData(String data) {
System.out.println("Data saved: " + data);
}
}
@Controller
- Web layer annotation: Specialization of @Component for Spring MVC controllers that handle HTTP requests.
- Use case: Typically used in web applications to mark a class that acts as a controller for incoming web requests, such as in RESTful APIs or MVC controllers.
@Controller
public class MyController {
@RequestMapping("/greet")
public String greet() {
return "Hello, Spring!";
}
}
@RestController
- RESTful web service controller: A convenience annotation combining @Controller and @ResponseBody. It simplifies the development of REST APIs by eliminating the need to annotate each method with @ResponseBody.
- Use case: Typically used for creating REST API endpoints in a Spring application.
@RestController
public class MyRestController {
@GetMapping("/api/greet")
public String greet() {
return "Hello from RESTful API!";
}
}
Why Use Stereotype Annotations?
- Clearer Semantics: These annotations give a clear indication of the role of the class within the application (service, repository, controller, etc.), improving readability and maintainability.
- Better Component Scanning: Spring can automatically detect and register these beans when @ComponentScan is used, making it easier to manage and organize your application.
- Improved Testing: Stereotype annotations help in writing focused tests for each layer of the application.
