Circular Dependency (সার্কুলার ডিপেনডেন্সি) হল একটি পরিস্থিতি যেখানে দুটি বা তার বেশি Bean একে অপরের উপর নির্ভরশীল থাকে। অর্থাৎ, এক Bean অন্য Bean এর উপর নির্ভরশীল এবং অন্য Bean আবার প্রথম Bean এর উপর নির্ভরশীল, যার ফলে একটি infinitive loop তৈরি হয়। এই সমস্যা যখন স্প্রিং কনটেইনারের মধ্যে ঘটে, তখন এটি প্রোগ্রামের রানটাইমে StackOverflowError বা BeanCreationException এর কারণ হতে পারে।
স্প্রিং ফ্রেমওয়ার্কে Circular Dependency সমস্যা সমাধানের জন্য কিছু নির্দিষ্ট কৌশল রয়েছে, যা ব্যবহারের মাধ্যমে এটি এড়ানো বা সমাধান করা যায়।
Circular Dependency এর উদাহরণ
ধরা যাক, দুটি ক্লাস ClassA এবং ClassB রয়েছে, যেগুলোর মধ্যে একে অপরের উপর নির্ভরশীলতা রয়েছে:
public class ClassA {
private ClassB classB;
public ClassA(ClassB classB) {
this.classB = classB;
}
public void doSomething() {
System.out.println("ClassA is working");
classB.doSomethingElse();
}
}
public class ClassB {
private ClassA classA;
public ClassB(ClassA classA) {
this.classA = classA;
}
public void doSomethingElse() {
System.out.println("ClassB is working");
classA.doSomething();
}
}
এখানে ClassA একটি ClassB ইনস্ট্যান্স ইনজেক্ট করে এবং ClassB আবার ClassA ইনজেক্ট করে, যা সার্কুলার ডিপেনডেন্সি সৃষ্টি করছে। স্প্রিং কনটেইনার এই সার্কুলার ডিপেনডেন্সি সমাধান করতে ব্যর্থ হবে এবং BeanCreationException তৈরি করবে।
Circular Dependency সমাধানের কৌশল
স্প্রিং কনটেইনারে সার্কুলার ডিপেনডেন্সি সমাধান করার জন্য কিছু কৌশল ব্যবহার করা যেতে পারে:
1. Setter Injection ব্যবহার করা
Constructor Injection দ্বারা সার্কুলার ডিপেনডেন্সি সমাধান করা সম্ভব নয় কারণ একে অপরকে ইনজেক্ট করার জন্য কন্সট্রাকটরগুলি তৈরি হতে হবে। তবে Setter Injection ব্যবহার করার মাধ্যমে এই সমস্যা সমাধান করা যেতে পারে।
Setter Injection ব্যবহার করে, স্প্রিং কনটেইনার প্রথমে Bean গুলি তৈরি করতে পারে এবং তারপরে setter মেথড ব্যবহার করে তাদের মধ্যে ডিপেনডেন্সি ইনজেক্ট করে।
উদাহরণ: Setter Injection
public class ClassA {
private ClassB classB;
public void setClassB(ClassB classB) {
this.classB = classB;
}
public void doSomething() {
System.out.println("ClassA is working");
classB.doSomethingElse();
}
}
public class ClassB {
private ClassA classA;
public void setClassA(ClassA classA) {
this.classA = classA;
}
public void doSomethingElse() {
System.out.println("ClassB is working");
classA.doSomething();
}
}
স্প্রিং কনফিগারেশন:
<bean id="classA" class="com.example.ClassA">
<property name="classB" ref="classB"/>
</bean>
<bean id="classB" class="com.example.ClassB">
<property name="classA" ref="classA"/>
</bean>
এখানে Setter Injection ব্যবহার করার মাধ্যমে স্প্রিং কনটেইনার ডিপেনডেন্সি ইনজেকশন সম্পন্ন করতে সক্ষম হবে এবং সার্কুলার ডিপেনডেন্সি সমস্যা সমাধান হবে।
2. @Lazy Annotation ব্যবহার করা
স্প্রিং ফ্রেমওয়ার্কে @Lazy অ্যানোটেশন ব্যবহার করে আপনি একটি Bean এর ইন্সট্যান্স তৈরির প্রক্রিয়াটি বিলম্বিত করতে পারেন, যা সার্কুলার ডিপেনডেন্সি সমস্যা সমাধানে সাহায্য করতে পারে। @Lazy অ্যানোটেশনটি নির্দেশ করে যে স্প্রিং কনটেইনার Bean তৈরির সময় প্রথমে তা তৈরি না করে, যখন প্রথমবার প্রয়োজন হবে তখনই Bean তৈরি করবে।
উদাহরণ: @Lazy Annotation
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class ClassA {
private ClassB classB;
@Autowired
public ClassA(@Lazy ClassB classB) {
this.classB = classB;
}
public void doSomething() {
System.out.println("ClassA is working");
classB.doSomethingElse();
}
}
@Component
public class ClassB {
private ClassA classA;
@Autowired
public ClassB(@Lazy ClassA classA) {
this.classA = classA;
}
public void doSomethingElse() {
System.out.println("ClassB is working");
classA.doSomething();
}
}
ব্যাখ্যা:
এখানে @Lazy অ্যানোটেশন ব্যবহার করা হয়েছে যাতে ClassA এবং ClassB এর ইনস্ট্যান্স বিলম্বিতভাবে তৈরি হয়, যার ফলে স্প্রিং কনটেইনার সার্কুলার ডিপেনডেন্সি সমস্যার সমাধান করতে সক্ষম হয়।
3. Interfaces ব্যবহার করা
অন্য একটি কৌশল হল interface ব্যবহার করে সার্কুলার ডিপেনডেন্সি সমস্যার সমাধান করা। একে অপরকে নির্ভরশীল না করার জন্য দুইটি ক্লাসের মধ্যে interface ব্যবহার করা যেতে পারে। এতে একটি ক্লাস সরাসরি অন্য ক্লাসের উপর নির্ভরশীল না হয়ে একটি সাধারণ ইন্টারফেসের মাধ্যমে তাদের মধ্যে যোগাযোগ স্থাপন করে।
উদাহরণ: Interfaces ব্যবহার করা
public interface Engine {
void start();
}
@Component
public class DieselEngine implements Engine {
@Override
public void start() {
System.out.println("Diesel Engine started");
}
}
@Component
public class Car {
private Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public void drive() {
engine.start();
System.out.println("Car is driving");
}
}
ব্যাখ্যা:
এখানে, Car এবং Engine এর মধ্যে সরাসরি সার্কুলার ডিপেনডেন্সি নেই, কারণ Car শুধু Engine ইন্টারফেসে নির্ভরশীল। এটি স্প্রিং কনটেইনারকে একাধিক Engine Bean ইনজেক্ট করতে সুবিধা দেয় এবং সার্কুলার ডিপেনডেন্সি সমস্যা সমাধান হয়।
4. @PostConstruct এবং @PreDestroy ব্যবহার করা
এছাড়া @PostConstruct এবং @PreDestroy অ্যানোটেশন ব্যবহার করা যেতে পারে, যা আপনাকে Bean Initialization এবং Destruction সময় নিয়ন্ত্রণ করতে সাহায্য করে। এই অ্যানোটেশনগুলি সার্কুলার ডিপেনডেন্সি সৃষ্টির সময় সহায়ক হতে পারে, তবে এগুলি মূলত Bean Lifecycle নিয়ন্ত্রণের জন্য ব্যবহৃত হয়।
উপসংহার
Circular Dependency স্প্রিং অ্যাপ্লিকেশনে একটি গুরুত্বপূর্ণ সমস্যা, তবে Setter Injection, @Lazy Annotation, Interfaces ব্যবহার এবং অন্যান্য পদ্ধতি দিয়ে এই সমস্যা সমাধান করা সম্ভব। এই কৌশলগুলি ব্যবহারের মাধ্যমে আপনি সার্কুলার ডিপেনডেন্সি এড়াতে পারবেন এবং আপনার স্প্রিং অ্যাপ্লিকেশনকে আরও কার্যকরী ও মেইনটেনেবল করতে পারবেন।
Read more