Spring Boot වල Conditional Beans හැදීම: @Conditional සහ Condition Interface එකට ගැඹුරු මඟපෙන්වීමක් SC Guide

Spring Boot වල Conditional Beans හැදීම: @Conditional සහ Condition Interface එකට ගැඹුරු මඟපෙන්වීමක් SC Guide

කොහොමද යාලුවනේ, ඔයාලා හැමෝම හොඳින් ඉන්නවා කියලා හිතනවා! අද අපි කතා කරන්න යන්නේ Spring Boot Applications හදනකොට අපිට ගොඩක් වැදගත් වෙන, වැඩේ තව ටිකක් smart කරන "Conditional Beans" කියන සංකල්පය ගැන. අපි හැමෝම දන්නවා Spring Framework එකේ dependency injection සහ inversion of control කියන ඒවා කොච්චර වටිනවද කියලා. හැබැයි සමහර වෙලාවට අපිට අවශ්‍ය වෙන්නේ යම්කිසි කොන්දේසියක් සපුරනවා නම් විතරක් Bean එකක් initialize වෙන්න. ඒ කියන්නේ, "මේ condition එක හරි නම් විතරයි මගේ මේ service එක load වෙන්න ඕනේ" වගේ අදහසක්. සාමාන්‍යයෙන් අපේ application එකක configuration එක environment එකෙන් environment එකට වෙනස් වෙන්න පුළුවන්. උදාහරණයක් විදිහට, development environment එකේදී mock services පාවිච්චි කරනවා වෙන්න පුළුවන්, production එකේදී real services පාවිච්චි කරනවා වෙන්න පුළුවන්. නැත්නම්, යම්කිසි feature එකක් enable කරලා තියෙනවා නම් විතරක් ඒකට අදාල Beans load වෙන්න ඕනේ කියලා අපිට ඕනේ වෙන්න පුළුවන්. මේ වගේ අවස්ථාවලදී අපිට "Conditional Beans" කියන සංකල්පය හරිම ප්‍රයෝජනවත් වෙනවා.

අද අපි බලමු කොහොමද Spring Boot වල මේ Conditional Beans effectively භාවිතා කරන්නේ කියලා. විශේෂයෙන්ම, @Conditional annotation එක සහ Condition interface එක හරහා අපේම Custom Conditions හදාගන්නේ කොහොමද කියලා අපි සරල උදාහරණ එක්ක පැහැදිලිව කතා කරමු. අපි Environment Properties පාවිච්චි කරලා කොහොමද Beans create කරන්නේ කියන එක practical exercise එකක් විදිහට කරලා බලමු. එහෙනම්, අපි පටන් ගමු නේද?

Conditional Beans කියන්නේ මොනවාද?

සරලවම කිව්වොත්, Conditional Bean එකක් කියන්නේ Spring IoC Container එක විසින් initialize කරන්නේ, යම්කිසි කොන්දේසියක් (condition) සත්‍ය නම් විතරයි. මේකෙන් අපිට පුළුවන් අපේ application එකේ startup time එක අඩු කරගන්න, අනවශ්‍ය Beans load වීම වළක්වාගන්න, සහ environment එකට අදාලව configurations වෙනස් කරගන්න. හිතන්නකෝ ඔයාලා "කෝපි කඩේක" (coffee shop) owner කෙනෙක් කියලා. සාමාන්‍යයෙන් කෝපි කඩේක, කෝපි හදන්න කෝපි ඇට, කිරි, සීනි වගේ දේවල් ඕනේ වෙනවා. හැබැයි ඔයාලා Ice Coffee විකුණන්නේ, "Ice Machine" එක වැඩ කරනවා නම් විතරයි. ඒ කියන්නේ, Ice Machine එක වැඩ කරන්නේ නැත්නම්, ඔයාලා Ice Coffee හදන්න ඕනේ නැහැ, මොකද බැරි නිසා. මේක හරියටම Conditional Beans වගේ තමයි. "Ice Machine එක වැඩ කරනවා" කියන එක condition එකක්. ඒ condition එක true නම් විතරයි "Ice Coffee making process" කියන Bean එක load වෙන්නේ. ඒක ඇත්තටම අපේ application එකේ efficiency එක වැඩි කරනවා. විශේෂයෙන් Microservices Architecture එකේදී මේක හරිම වැදගත්.

@Conditional Annotation එක ගැන

Spring Framework එකේදී Conditional Beans හදන්න අපිට @Conditional annotation එක භාවිතා කරන්න පුළුවන්. මේ annotation එක @Configuration class එකක් මත, @Bean method එකක් මත, නැත්නම් වෙනත් annotation එකක් මත පවා භාවිතා කරන්න පුළුවන්. මේකෙන් වෙන්නේ, අපි දීලා තියෙන Condition class එකේ matches() method එක true return කරනවා නම් විතරයි අදාල Bean එක create වෙන්නේ.

@Conditional annotation එකට Condition interfaces implement කරපු class එකක reference එකක් parameter එකක් විදිහට දෙන්න ඕනේ. Spring Framework එකේ අපිට මේ වගේ pre-built conditions ගොඩක් තියෙනවා. උදාහරණයක් විදිහට:

  • @ConditionalOnProperty: යම්කිසි property එකක value එකක් මත පදනම් වෙලා.
  • @ConditionalOnMissingBean: යම්කිසි Bean එකක් නැත්නම් විතරක් create කරන්න.
  • @ConditionalOnClass: යම්කිසි class එකක් classpath එකේ තියෙනවා නම් විතරක් create කරන්න.
  • @ConditionalOnWebApplication: application එක web application එකක් නම් විතරක්.

මේවා Spring Boot auto-configuration එකේදී ගොඩක් වෙලාවට භාවිතා වෙනවා. හැබැයි, අපිට අපේම custom logic එකක් මත පදනම් වෙලා Bean එකක් create කරන්න අවශ්‍ය නම්, අපිට Condition interface එක implement කරලා අපේම Condition class එකක් හදාගන්න පුළුවන්. බලමු කොහොමද ඒක කරන්නේ කියලා.

සරල උදාහරණයක් විදිහට, අපි මේ වගේ custom condition එකක් නිර්මාණය කරමු. අපි හිතමු අපේ application එකේ "Developer Mode" එක enable කරලා තියෙනවා නම් විතරක් විශේෂිත Bean එකක් create වෙන්න ඕනේ කියලා. මුලින්ම, අපේ Condition class එක මෙහෙම ලියමු:

package com.scguide.conditionalbeans.conditions;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OnDeveloperModeCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Check if the 'app.developer.mode' property is set to true in the environment
        return context.getEnvironment().getProperty("app.developer.mode", Boolean.class, false);
    }
}

දැන් මේ Condition එක භාවිතා කරලා Bean එකක් create කරමු:

package com.scguide.conditionalbeans.config;

import com.scguide.conditionalbeans.conditions.OnDeveloperModeCondition;
import com.scguide.conditionalbeans.services.DeveloperService;
import com.scguide.conditionalbeans.services.impl.DeveloperServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    @Conditional(OnDeveloperModeCondition.class)
    public DeveloperService developerService() {
        System.out.println("DeveloperService Bean is being created!");
        return new DeveloperServiceImpl();
    }
}

මේකේදී, DeveloperService Bean එක create වෙන්නේ, application.properties (නැත්නම් environment variables) වල app.developer.mode=true කියලා තියෙනවා නම් විතරයි. මේකෙන් අපිට බලාගන්න පුළුවන් @Conditional සහ අපේම Condition class එකක මූලික ක්‍රියාකාරිත්වය. හරිම සරලයි නේද?

Condition Interface එක භාවිතයෙන් Custom Conditions හදමු

දැන් අපි ටිකක් සංකීර්ණ උදාහරණයක් බලමු, අපේ Spring Boot application එකක Environment Properties වලට අනුව Beans create කරන්නේ කොහොමද කියලා. මේකෙන් අපිට පුළුවන් Production, Development, Test වගේ විවිධ environments වලට අදාලව Services වෙනස් කරන්න. උදාහරණයක් විදිහට, අපේ application එකට SMS යවන්නයි, Email යවන්නයි Services දෙකක් තියෙනවා කියලා හිතමු. හැබැයි අපිට ඕනේ SMS Service එක enable වෙන්නේ app.messaging.sms.enabled=true කියලා තියෙනවා නම් විතරයි, ඒ වගේම Email Service එක enable වෙන්නේ app.messaging.email.enabled=true කියලා තියෙනවා නම් විතරයි.

පළමුව, Service Interfaces සහ Implementations:

package com.scguide.conditionalbeans.services;

public interface SmsService {
    void sendSms(String number, String message);
}
package com.scguide.conditionalbeans.services;

public interface EmailService {
    void sendEmail(String to, String subject, String body);
}
package com.scguide.conditionalbeans.services.impl;

import com.scguide.conditionalbeans.services.EmailService;
import org.springframework.stereotype.Service;

public class RealEmailService implements EmailService {
    @Override
    public void sendEmail(String to, String subject, String body) {
        System.out.println("Sending REAL Email to " + to + " with Subject: " + subject);
        // Actual email sending logic goes here
    }
}
package com.scguide.conditionalbeans.services.impl;

import com.scguide.conditionalbeans.services.SmsService;
import org.springframework.stereotype.Service;

public class RealSmsService implements SmsService {
    @Override
    public void sendSms(String number, String message) {
        System.out.println("Sending REAL SMS to " + number + ": " + message);
        // Actual SMS sending logic goes here
    }
}

අපි Mock implementations දෙකකුත් හදාගමු, මොකද development වලදී අපිට සැබෑ Services අවශ්‍ය වෙන්නේ නැති වෙන්න පුළුවන්:

package com.scguide.conditionalbeans.services.impl;

import com.scguide.conditionalbeans.services.EmailService;

public class MockEmailService implements EmailService {
    @Override
    public void sendEmail(String to, String subject, String body) {
        System.out.println("Sending MOCK Email to " + to + " with Subject: " + subject);
    }
}
package com.scguide.conditionalbeans.services.impl;

import com.scguide.conditionalbeans.services.SmsService;

public class MockSmsService implements SmsService {
    @Override
    public void sendSms(String number, String message) {
        System.out.println("Sending MOCK SMS to " + number + ": " + message);
    }
}

දෙවනුව, Custom Condition Classes:

දැන් අපි අපේ Conditions ලියාගමු. මේවා තමයි අපේ properties check කරලා true හෝ false return කරන්නේ.

package com.scguide.conditionalbeans.conditions;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OnSmsEnabledCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Checks if 'app.messaging.sms.enabled' property is true in the environment
        // Default to false if property is not found
        boolean smsEnabled = context.getEnvironment().getProperty("app.messaging.sms.enabled", Boolean.class, false);
        System.out.println("Checking OnSmsEnabledCondition: app.messaging.sms.enabled = " + smsEnabled);
        return smsEnabled;
    }
}
package com.scguide.conditionalbeans.conditions;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OnEmailEnabledCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Checks if 'app.messaging.email.enabled' property is true in the environment
        boolean emailEnabled = context.getEnvironment().getProperty("app.messaging.email.enabled", Boolean.class, false);
        System.out.println("Checking OnEmailEnabledCondition: app.messaging.email.enabled = " + emailEnabled);
        return emailEnabled;
    }
}

අපි තව Condition එකක් හදාගමු, application එක Production environment එකේ running වෙනවාද කියලා බලන්න. මේකෙන් අපිට පුළුවන් real services load කරන්න.

package com.scguide.conditionalbeans.conditions;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OnProductionEnvironmentCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Checks if "prod" profile is active
        String[] activeProfiles = context.getEnvironment().getActiveProfiles();
        for (String profile : activeProfiles) {
            if ("prod".equalsIgnoreCase(profile)) {
                System.out.println("Checking OnProductionEnvironmentCondition: 'prod' profile is active.");
                return true;
            }
        }
        System.out.println("Checking OnProductionEnvironmentCondition: 'prod' profile is NOT active.");
        return false;
    }
}

තෙවනුව, Configuration Classes:

දැන් අපේ Beans configure කරමු, @Conditional annotation එකත් එක්ක.

package com.scguide.conditionalbeans.config;

import com.scguide.conditionalbeans.conditions.OnEmailEnabledCondition;
import com.scguide.conditionalbeans.conditions.OnProductionEnvironmentCondition;
import com.scguide.conditionalbeans.conditions.OnSmsEnabledCondition;
import com.scguide.conditionalbeans.services.EmailService;
import com.scguide.conditionalbeans.services.SmsService;
import com.scguide.conditionalbeans.services.impl.MockEmailService;
import com.scguide.conditionalbeans.services.impl.MockSmsService;
import com.scguide.conditionalbeans.services.impl.RealEmailService;
import com.scguide.conditionalbeans.services.impl.RealSmsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class MessagingConfig {

    // Mock Services for non-production environments
    @Bean
    @Conditional(OnSmsEnabledCondition.class)
    @Profile("!prod") // Also ensures this bean is only active if 'prod' is NOT the active profile
    public SmsService mockSmsService() {
        System.out.println("MockSmsService Bean is being created!");
        return new MockSmsService();
    }

    @Bean
    @Conditional(OnEmailEnabledCondition.class)
    @Profile("!prod")
    public EmailService mockEmailService() {
        System.out.println("MockEmailService Bean is being created!");
        return new MockEmailService();
    }

    // Real Services for production environment
    @Bean
    @Conditional(OnSmsEnabledCondition.class)
    @Profile("prod") // Ensures this bean is only active if 'prod' is the active profile
    public SmsService realSmsService() {
        System.out.println("RealSmsService Bean is being created!");
        return new RealSmsService();
    }

    @Bean
    @Conditional(OnEmailEnabledCondition.class)
    @Profile("prod")
    public EmailService realEmailService() {
        System.out.println("RealEmailService Bean is being created!");
        return new RealEmailService();
    }
}

(සටහන: එකම Bean method එකක @Conditional annotation එක දෙපාරක් දැමීමෙන්, ඒ conditions දෙකම සත්‍ය විය යුතු බව අදහස් වේ. කෙසේ වෙතත්, @Profile annotation එක Spring Framework හි built-in condition එකක් ලෙස ක්‍රියා කරන අතර, එයට වඩාත් සරල හා ප්‍රබල ක්‍රමවේදයක් සපයයි. අපගේ OnProductionEnvironmentCondition එක @Profile("prod") ට සමාන අරමුණක් ඉටු කරයි.)

සිව්වනුව, Application Properties:

දැන් අපේ application.properties (හෝ application.yml) file එකේ අවශ්‍ය properties සකස් කරමු.

Scenario 1: SMS enabled, Email disabled, Development profile (default)

# application.properties
app.messaging.sms.enabled=true
app.messaging.email.enabled=false

මේ වෙලාවේදී MockSmsService එක load වෙන්න ඕනේ, EmailService එකක් load වෙන්නේ නැහැ.

Scenario 2: Both enabled, Production profile

# application-prod.properties (or set profile via command line: --spring.profiles.active=prod)
app.messaging.sms.enabled=true
app.messaging.email.enabled=true

මේ වෙලාවේදී RealSmsService සහ RealEmailService දෙකම load වෙන්න ඕනේ.

පස්වනුව, Main Application Class:

අපේ application එක run කරලා Beans create වෙනවද කියලා බලන්න main class එකක් හදමු.

package com.scguide.conditionalbeans;

import com.scguide.conditionalbeans.services.EmailService;
import com.scguide.conditionalbeans.services.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class ConditionalBeansApplication implements CommandLineRunner {

    // Use @Autowired(required = false) to prevent errors if a bean is not found
    @Autowired(required = false)
    private SmsService smsService;

    @Autowired(required = false)
    private EmailService emailService;

    @Autowired
    private ApplicationContext context; // To inspect beans

    public static void main(String[] args) {
        SpringApplication.run(ConditionalBeansApplication.class, args);
    }

    @Override
    public void run(String... args) {
        System.out.println("\n--- Checking Services ---");

        if (smsService != null) {
            System.out.println("SMS Service Bean is available: " + smsService.getClass().getSimpleName());
            smsService.sendSms("0771234567", "Hello from SC Guide Conditional SMS!");
        } else {
            System.out.println("SMS Service Bean is NOT available.");
        }

        if (emailService != null) {
            System.out.println("Email Service Bean is available: " + emailService.getClass().getSimpleName());
            emailService.sendEmail("[email protected]", "Conditional Bean Test", "This is a test email.");
        } else {
            System.out.println("Email Service Bean is NOT available.");
        }

        System.out.println("\n--- Beans in ApplicationContext ---");
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            if (beanName.toLowerCase().contains("sms") || beanName.toLowerCase().contains("email")) {
                System.out.println("Bean Found: " + beanName);
            }
        }
    }
}

මේක Run කරලා බලන්න. application.properties වල app.messaging.sms.enabled සහ app.messaging.email.enabled values වෙනස් කරලා, ඒ වගේම --spring.profiles.active=prod argument එක command line එකට එකතු කරලා බලන්න. ඔයාලට පෙනෙයි කොහොමද Beans වෙනස් විදිහට load වෙන්නේ කියලා. නියමයි නේද!

සැලකිලිමත් විය යුතු කරුණු

Conditional Beans කියන්නේ හරිම ප්‍රයෝජනවත් සංකල්පයක් වුණත්, මේවා භාවිතා කරනකොට මතක තියාගන්න ඕනේ කරුණු කීපයක් තියෙනවා:

  1. සරලව තියාගන්න: ඔයාලගේ Condition logic එක හැමවිටම සරලව තියාගන්න උත්සාහ කරන්න. සංකීර්ණ logic නිසා debugging කරන්න අමාරු වෙන්න පුළුවන්.
  2. Order matters: සමහර වෙලාවට conditions වල order එක වැදගත් වෙන්න පුළුවන්, විශේෂයෙන් එකම Bean එකට conditions කිහිපයක් දානකොට.
  3. Debugging: Conditional Beans debugging කරන එක පොඩ්ඩක් අමාරු වෙන්න පුළුවන්. Spring framework එකේ DEBUG level logging enable කරලා "Conditional" කියලා search කරලා බලන්න. ඒ වගේම අපේ Condition classes වල System.out.println වගේ statements දාලා මොකද වෙන්නේ කියලා බලාගන්නත් පුළුවන් (production වලට කලින් remove කරන්න අමතක කරන්න එපා!).
  4. Built-in vs. Custom: Spring Boot වල තියෙන @ConditionalOn... annotation set එකෙන් වැඩේ ගොඩයනවා නම්, පුළුවන් තරම් ඒවා භාවිතා කරන්න. Custom conditions හදන්න අවශ්‍ය වෙන්නේ ඒ built-in annotations වලින් අපිට අවශ්‍ය logic එක cover වෙන්නේ නැත්නම් විතරයි.
  5. Performance: සාමාන්‍යයෙන් condition check එකක් performance එකට ලොකු බලපෑමක් කරන්නේ නැහැ. හැබැයි condition එක ඇතුලේ database queries වගේ resource-intensive operations කරනවා නම්, ඒක application startup time එකට බලපාන්න පුළුවන්.

නිගමනය

අද අපි කතා කළේ Spring Boot වල Conditional Beans කියන්නේ මොනවාද, @Conditional annotation එක සහ Condition interface එක භාවිතා කරලා කොහොමද අපේම custom conditions හදාගන්නේ කියලා. Environment properties පාවිච්චි කරලා කොහොමද Beans dynamically create කරන්නේ කියලා practical උදාහරණයක් එක්ක අපි මේක තවදුරටත් පැහැදිලි කරගත්තා. මේ සංකල්පය ඔයාලගේ Spring Boot applications වල flexibility එක, maintainability එක, සහ efficiency එක වැඩි කරගන්න ගොඩක් උදව් වෙයි.

ඔයාලට මේ ගැන අදහස්, ප්‍රශ්න, නැත්නම් වෙනත් Spring Boot topics ගැන දැනගන්න ඕනේ නම් පහළින් Comment කරන්න අමතක කරන්න එපා. අපි හැමෝටම මේ Knowledge එක බෙදාහදා ගැනීම ගොඩක් වටිනවා. මේ concepts ඔයාලගේ project වලදී apply කරලා බලන්නත් අමතක කරන්න එපා! එහෙනම්, ඊළඟ ලිපියකින් හමුවෙමු!