CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

82

Quality

82%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Validation failed for skills in this tile
One or more skills have errors that need to be fixed before they can move to Implementation and Discovery review.
Overview
Quality
Evals
Security
Files

examples.mdplugins/developer-kit-java/skills/spring-boot-dependency-injection/references/

Spring Boot Dependency Injection - Examples

Comprehensive examples demonstrating dependency injection patterns, from basic to advanced scenarios.

Example 1: Constructor Injection (Recommended)

The preferred pattern for mandatory dependencies.

// With Lombok @RequiredArgsConstructor (RECOMMENDED)
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    private final PasswordEncoder passwordEncoder;

    public User registerUser(CreateUserRequest request) {
        log.info("Registering user: {}", request.getEmail());
        
        User user = User.builder()
            .email(request.getEmail())
            .name(request.getName())
            .password(passwordEncoder.encode(request.getPassword()))
            .build();
        
        User saved = userRepository.save(user);
        emailService.sendWelcomeEmail(saved.getEmail());
        
        return saved;
    }
}

// Without Lombok (Explicit)
@Service
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    private final PasswordEncoder passwordEncoder;

    public UserService(UserRepository userRepository,
                      EmailService emailService,
                      PasswordEncoder passwordEncoder) {
        this.userRepository = Objects.requireNonNull(userRepository);
        this.emailService = Objects.requireNonNull(emailService);
        this.passwordEncoder = Objects.requireNonNull(passwordEncoder);
    }

    public User registerUser(CreateUserRequest request) {
        // Implementation
    }
}

Test (Easy - No Spring Needed)

@Test
void shouldRegisterUserAndSendEmail() {
    // Arrange - Create mocks manually
    UserRepository mockRepository = mock(UserRepository.class);
    EmailService mockEmailService = mock(EmailService.class);
    PasswordEncoder mockEncoder = mock(PasswordEncoder.class);
    
    UserService service = new UserService(mockRepository, mockEmailService, mockEncoder);
    
    User user = User.builder().email("test@example.com").build();
    when(mockRepository.save(any())).thenReturn(user);
    when(mockEncoder.encode("password")).thenReturn("encoded");

    // Act
    User result = service.registerUser(new CreateUserRequest("test@example.com", "Test", "password"));

    // Assert
    assertThat(result.getEmail()).isEqualTo("test@example.com");
    verify(mockEmailService).sendWelcomeEmail("test@example.com");
}

Example 2: Setter Injection for Optional Dependencies

Use setter injection ONLY for optional dependencies with sensible defaults.

@Service
public class ReportService {
    private final ReportRepository reportRepository;
    private EmailService emailService;  // Optional
    private CacheService cacheService;   // Optional

    // Constructor for mandatory dependency
    public ReportService(ReportRepository reportRepository) {
        this.reportRepository = Objects.requireNonNull(reportRepository);
    }

    // Setters for optional dependencies
    @Autowired(required = false)
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }

    @Autowired(required = false)
    public void setCacheService(CacheService cacheService) {
        this.cacheService = cacheService;
    }

    public Report generateReport(ReportRequest request) {
        Report report = reportRepository.create(request.getTitle());

        // Use optional services if available
        if (emailService != null) {
            emailService.sendReport(report);
        }

        if (cacheService != null) {
            cacheService.cache(report);
        }

        return report;
    }
}

Example 3: Configuration with Multiple Bean Definitions

@Configuration
public class AppConfig {

    // Bean 1: Database
    @Bean
    public DataSource dataSource(
            @Value("${spring.datasource.url}") String url,
            @Value("${spring.datasource.username}") String username,
            @Value("${spring.datasource.password}") String password) {
        
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);
        config.setMaximumPoolSize(20);
        
        return new HikariDataSource(config);
    }

    // Bean 2: Transaction Manager (depends on DataSource)
    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }

    // Bean 3: Repository (depends on DataSource via JPA)
    @Bean
    public UserRepository userRepository(UserJpaRepository jpaRepository) {
        return new UserRepositoryAdapter(jpaRepository);
    }

    // Bean 4: Service (depends on Repository)
    @Bean
    public UserService userService(UserRepository repository) {
        return new UserService(repository);
    }
}

Example 4: Resolving Ambiguities with @Qualifier

@Configuration
public class DataSourceConfig {
    
    @Bean(name = "primaryDB")
    public DataSource primaryDataSource() {
        return new HikariDataSource();
    }

    @Bean(name = "secondaryDB")
    public DataSource secondaryDataSource() {
        return new HikariDataSource();
    }
}

@Service
public class MultiDatabaseService {
    private final DataSource primaryDataSource;
    private final DataSource secondaryDataSource;

    // Using @Qualifier to resolve ambiguity
    public MultiDatabaseService(
            @Qualifier("primaryDB") DataSource primary,
            @Qualifier("secondaryDB") DataSource secondary) {
        this.primaryDataSource = primary;
        this.secondaryDataSource = secondary;
    }

    public void performOperation() {
        // Use primary for writes
        executeUpdate(primaryDataSource);
        
        // Use secondary for reads
        executeQuery(secondaryDataSource);
    }
}

// Alternative: Using @Primary
@Configuration
public class PrimaryDataSourceConfig {
    
    @Bean
    @Primary  // This bean is preferred when multiple exist
    public DataSource primaryDataSource() {
        return new HikariDataSource();
    }

    @Bean
    public DataSource secondaryDataSource() {
        return new HikariDataSource();
    }
}

Example 5: Conditional Bean Registration

@Configuration
public class OptionalFeatureConfig {

    // Only create if feature is enabled
    @Bean
    @ConditionalOnProperty(name = "feature.notifications.enabled", havingValue = "true")
    public NotificationService notificationService() {
        return new EmailNotificationService();
    }

    // Fallback if no other bean exists
    @Bean
    @ConditionalOnMissingBean(NotificationService.class)
    public NotificationService defaultNotificationService() {
        return new NoOpNotificationService();
    }

    // Only create if class is on classpath
    @Bean
    @ConditionalOnClass(RedisTemplate.class)
    public CacheService cacheService() {
        return new RedisCacheService();
    }
}

@Service
public class OrderService {
    private final NotificationService notificationService;

    public OrderService(NotificationService notificationService) {
        this.notificationService = notificationService;  // Works regardless of implementation
    }

    public void createOrder(Order order) {
        // Always works, but behavior depends on enabled features
        notificationService.sendConfirmation(order);
    }
}

Example 6: Profiles and Environment-Specific Configuration

@Configuration
@Profile("production")
public class ProductionConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://prod-db:5432/production");
        config.setMaximumPoolSize(30);
        config.setMaxLifetime(1800000);  // 30 minutes
        return new HikariDataSource(config);
    }

    @Bean
    public SecurityService securityService() {
        return new StrictSecurityService();
    }
}

@Configuration
@Profile("test")
public class TestConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:test-data.sql")
            .build();
    }

    @Bean
    public SecurityService securityService() {
        return new PermissiveSecurityService();
    }
}

@Configuration
@Profile("development")
public class DevelopmentConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost:5432/dev");
        return new HikariDataSource(config);
    }

    @Bean
    public SecurityService securityService() {
        return new DebugSecurityService();
    }
}

Usage:

export SPRING_PROFILES_ACTIVE=production
# or in application.properties:
# spring.profiles.active=production

Example 7: Lazy Initialization

@Configuration
public class ExpensiveResourceConfig {

    @Bean
    @Lazy  // Created only when first accessed
    public ExpensiveService expensiveService() {
        System.out.println("ExpensiveService initialized (lazy)");
        return new ExpensiveService();
    }

    @Bean
    public NormalService normalService(ExpensiveService expensive) {
        // ExpensiveService not created yet
        return new NormalService(expensive);  // Lazy proxy injected here
    }
}

@SpringBootTest
class LazyInitializationTest {
    @Test
    void shouldInitializeExpensiveServiceLazy() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ExpensiveResourceConfig.class);
        
        // ExpensiveService not initialized yet
        assertThat(context.getBean(NormalService.class)).isNotNull();
        
        // Now ExpensiveService is initialized
        ExpensiveService service = context.getBean(ExpensiveService.class);
        assertThat(service).isNotNull();
    }
}

Example 8: Circular Dependency Resolution with Events

// ❌ BAD - Circular dependency
@Service
public class UserService {
    private final OrderService orderService;
    
    public UserService(OrderService orderService) {
        this.orderService = orderService;  // Circular!
    }
}

@Service
public class OrderService {
    private final UserService userService;
    
    public OrderService(UserService userService) {
        this.userService = userService;  // Circular!
    }
}

// ✅ GOOD - Use events to decouple
public class UserRegisteredEvent extends ApplicationEvent {
    private final String userId;
    
    public UserRegisteredEvent(Object source, String userId) {
        super(source);
        this.userId = userId;
    }
}

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final ApplicationEventPublisher eventPublisher;

    public User registerUser(CreateUserRequest request) {
        User user = userRepository.save(User.create(request));
        eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId()));
        return user;
    }
}

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;

    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        // Create welcome order when user registers
        Order welcomeOrder = Order.createWelcomeOrder(event.getUserId());
        orderRepository.save(welcomeOrder);
    }
}

Example 9: Component Scanning

@Configuration
@ComponentScan(basePackages = {
    "com.example.users",
    "com.example.products",
    "com.example.orders"
})
public class AppConfig {
}

// Alternative: Exclude packages
@Configuration
@ComponentScan(basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX,
        pattern = "com\\.example\\.internal\\..*"))
public class AppConfig {
}

// Auto-discovered by Spring Boot
@SpringBootApplication  // Implies @ComponentScan("package.of.main.class")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Example 10: Testing with Constructor Injection

// ❌ Service with field injection (hard to test)
@Service
public class BadUserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

@Test
void testBadService() {
    // Must use Spring to test this
    UserService service = new BadUserService();
    // Can't inject mocks without reflection or Spring
}

// ✅ Service with constructor injection (easy to test)
@Service
@RequiredArgsConstructor
public class GoodUserService {
    private final UserRepository userRepository;
    
    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

@Test
void testGoodService() {
    // Can test directly without Spring
    UserRepository mockRepository = mock(UserRepository.class);
    UserService service = new GoodUserService(mockRepository);
    
    User mockUser = new User(1L, "Test");
    when(mockRepository.findById(1L)).thenReturn(Optional.of(mockUser));
    
    User result = service.getUser(1L);
    assertThat(result.getName()).isEqualTo("Test");
}

// Integration test
@SpringBootTest
@ActiveProfiles("test")
class UserServiceIntegrationTest {
    @Autowired
    private UserService userService;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void shouldFetchUserFromDatabase() {
        User user = User.create("test@example.com");
        userRepository.save(user);
        
        User retrieved = userService.getUser(user.getId());
        assertThat(retrieved.getEmail()).isEqualTo("test@example.com");
    }
}

These examples cover constructor injection (recommended), setter injection (optional dependencies), configuration, testing patterns, and common best practices for dependency injection in Spring Boot.

plugins

developer-kit-java

skills

README.md

tile.json