در Spring سه نوع annotation برای تزریق وابستگی ها وجود دارد
Autowired@
Resource@
Inject@
که هر یک کاربرد مختص به خود را دارند. با استفاده از این annotation ها، کلاس مورد نظر را میتوانیم داخل کد تزریق کنیم
دو annotation از سه تا (Inject , Resource) در پکیج javax.annotation.Resource and javax.inject.Inject قرار گرفته اند و Autowired در org.springframework.beans.factory.annotation
تمامی این سه annotation میتوانند روی field و setter method مورد استفاده قرار گیرند
بررسی Resource@ :
بخش از مجموعه استاندارد تعریف شده از JSR-250 است. این annotation دارای الویت اجرایی به ترتیب زیر است :
بالاترین الویت بوسیله Name
الویت بعدی با Type
و الویت آخر با استفاده از Qualifier@
فرض کنیم دو Bean از یک نوع به صورت زیر تعریف کرده ایم:
@Configuration public class ApplicationContextTestResourceNameType { @Bean(name="defaultFile") public File defaultFile() { File defaultFile = new File("defaultFile.txt"); return defaultFile; } @Bean(name="namedFile") public File namedFile() { File namedFile = new File("namedFile.txt"); return namedFile; } }
حال برای تزریق این Bean بوسیله Name آن بدین صورت میتوانیم استفاده کنیم :
@Resource(name="namedFile") private File defaultFile;
برای حالت تزریق بوسیله Type :
@Resource private File defaultFile;
و با استفاده از Qualifier@ :
@Resource private File dependency1; @Resource private File dependency2;
اگر کد بالا را تست بگیریم خطای org.springframework.beans.factory.NoUniqueBeanDefinitionException رخ خواهد داد و این بدین دلیل است که Application Context نمیتواند تشخیص دهد که منظور ما کدامیک از Bean ها بوده است و برای جلوگیری از این حالت از Qualifier@ استفاده میکنیم :
@Resource @Qualifier("defaultFile") private File dependency1; @Resource @Qualifier("namedFile") private File dependency2;
برای تمامی موارد بالا که حالت تزریق وابستگی روی field بود را همچنین میتوان برای حالت Setter Method هم داشت و فرقی ندارد
بررسی Inject@ :
این annotation بخشی از استاندارد JSR-330 است که پیاده سازی شده و ترتیب الویت اجرا آن بدین صورت است :
بالاترین الویت با استفاده از Type
الویت بعدی با استفاده از Name
و کمترین الویت با استفاده از Qualifier@
برای دسترسی به این Annotation نیاز داریم که کتابخانه javax.inject را به Spring اضافه کنیم :
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
برای تست فرض کنید یک کلاس bean به صورت زیر داریم :
@Component public class ArbitraryDependency { private final String label = "Arbitrary Dependency"; public String toString() { return label; } }
و جهت تزریق روی field :
@Inject private ArbitraryDependency fieldInjectDependency;
* بر خلاف Resource@ که الویت اول آنجا Name بود در اینجا الویت اول Type است
حال اگر چند bean از یک نوع داشته باشیم میتوانیم با کمک Qualifier@ آنرا مشخص کنیم که در Inject@ الویت دوم است :
@Inject @Qualifier("defaultFile") private ArbitraryDependency defaultDependency; @Inject @Qualifier("namedFile") private ArbitraryDependency namedDependency;
تزریق با استفاده از Name :
کلاس bean زیر را فرض کنید :
public class YetAnotherArbitraryDependency extends ArbitraryDependency { private final String label = "Yet Another Arbitrary Dependency"; public String toString() { return label; } }
و برای تزریق این bean در کد از روی Name آن :
@Inject @Named("yetAnotherFieldInjectDependency") private ArbitraryDependency yetAnotherFieldInjectDependency;
در مورد روش setter method هم همانند بالا میباشد
بررسی Autowired@ :
این annotation مشابه Inject@ است و فرق اصلیش اینه که این annotation قسمتی از Spring بوده و نیازی به کتابخانه خارجی ندارد و الویت هایش همانند Inject@ بصورت زیر است :
الویت اول با Type
دوم با Name
و الویت آخر با Qualifier است
سایر توضیحات آن مشابه Inject@ است و میتوان به آن مراجعه کرد.
در واقع Autowired@ جایگزینی برای Inject@ است و چون استفاده از این مدل از تزریق به صورت یک استاندارد حس میشد آنرا داخل Spring Framework پیاده سازی کرده اند
حال از بین این سه حالت کدامیک را باید ما استفاده کنیم ؟
پاسخ کوتاه این است که بستگی به سناریوی طراحی و چگونگی استفاده از آنها را دارد
حال بیاییم عمیق تر به انتخاب برسیم :
- وقتی که در برنامه بسیار زیاد از Singleton ها بصورت چند ریختی ( Polymorphism ) استفاده میکنیم که دارای کلاس های Abstract و Interface ها است از هر دوی Autowired@ و Inject@ میتوان استفاده کرد
- زمانی که طراحی دارای رفتار های پیچیده ای باشد که هر رفتار خود دارای کلاس های abstract و اینترفیس های متعدد باشد و هر یک از این رفتار ها در طول کارکرد برنامه متفاوت باشد بهتر است تزریق ها از روی Name صورت پذیرد و در این حالت Resource@ گزینه اصولی تری است
- زمانی که بنا به شرایطی باید با استاندارد صرفا موجود در JavaEE کار کنیم انتخاب ما محدود به Inject@ و Resource@ است
- زمانی که بنا به شرایطی فقط با امکانات داخلی Spring کار کنیم و کتابخانه خارجی کمتری استفاده کنیم گزینه Autowired@ در دسترس است
جدول مقایسه ای annotation ها کنار هم :
Scenario | @Resource | @Inject | @Autowired |
---|---|---|---|
Application-wide use of singletons through polymorphism | ✗ | ✔ | ✔ |
Fine-grained application behavior configuration through polymorphism | ✔ | ✗ | ✗ |
Dependency injection should be handled solely by the Java EE platform | ✔ | ✔ | ✗ |
Dependency injection should be handled solely by the Spring Framework | ✗ | ✗ | ✔ |
اگر از Autowired@ در حالت setter متد استفاده کنیم میتوانیم موارد پیچیده تری از ساخت Bean را داشته باشیم مثلا ارث بری داشته باشیم
خود Spring توصیه میکند که از نوع Constructor استفاده کنیم
اگر چند Bean یکسان داشته باشیم در هنگام تزریق وابستگی اسپرینگ به ابهام خواهد خورد که از کدامیک باید استفاده کند برای حل این مسئله میتوان از Primary@ استفاده کرد
یا جایی که داریم اون Bean رو میسازیم از Qualifier@ استفاده کنیم
یک مورد دیگر استفاده از SpEl است که میتوان وابستگی ها را تزریق کرد یا خروجی متد های Bean را هم دریافت کنیم :
@Value("#{myClass}") private MyClass myClass; @Value("#{myClass.getDate('2019-01-18')}") private Date date;