به لیست annotaion هایی که در پکیج های
org.springframework.beans.factory.annotation org.springframework.context.annotation
قرار دارند Spring Core Annotaion میگویند که عمدتا برای استفاده در Dependency Injection کاربرد دارند و قدرت مانور بالایی در Develop Time به ما می دهند که در ادامه به انها می پردازیم :
annotation ها در Spring Core به دو دسته کلی تقسیم میشوند :
بخش اول Annotation های مربوط به تزریق وابستگی (DI-Related Annotations) :
Component@ : میتوان یک ابجکت را بصورت bean تعریف کرد و بعدا از آن در پروژه استفاده کنیم
@Component("optional_name") public class MyClass{ }
و بعد از طریق context میتوان به آن دسترسی داشت :
MyClass myclass = (MyClass)context.getBean(MyClass.class); MyClass myclass2 = (MyClass)context.getBean("optional_name");
اگر optional_name نداده باشیم بطور اتوماتیک میتوان از نام کلاس استفاده کرد ولی فقط با حرف کوچک شروع میشود
متد getBean میتواند یکسری پارامتر ورودی هم دریافت کند که به Bean ارسال میشود ولی باید حواسمان باشد که Bean از نوع Singleton نباشد چون مقادیر روی تمامی ابجکت های استفاده شده اش تاثیر می گذارد
چرخه عمر Bean ها :
ابتدا Constructor فراخوانی میشود
بعد متد PostConstruct@ زمانی فراخوانی میشود که Constructor کلاس Bean کال شد و بعد Dependency ها Inject شد این متد کال خواهد شد
بعد یک متد دیگری داریم بنام afterPropertiesSet () که داخل اینترفیس InitializingBean قرار دارد ، هرگاه Bean ما این اینترفیس را پیاده سازی کند متد ()afterPropertiesSet بعد از PostConstruct@ فراخوانی میشود ، فایده ای که این متد دارد این است که اگر در هنگام ساخته شدن Bean و دادن پارامتر ها به هر دلیلی پارامتر ها با اون Dependency ها و مقادیر خواسته شده مطابقت نداشت میتوانیم یک Exception را throw کنیم و Bean اجرا نشود و کد رو ملزم به رعایت Dependency های مورد نظر بکنیم
و در آخر متد PreDestroy@ زمانی فراخوانی میشود که Bean از داخل context خارج شود
*اگر از روش setter ما Bean را تعریف کنیم میتوانیم در همان Bean@ متد های PostConstruct و PreDestroy را تعریف کنیم :
@Bean(initMethod = "init" , destroyMethod = "destroy") public MyClass myClass(){ return new MyClass(); }
Autowired@ : میتوان یک ابجکت متناظر با وابستگی را پیدا و تزریق کرد که روی یکی از حالت Setter , Constractor و Field قابل استفاده است
حالت Constractor :
class Car { Engine engine; @Autowired Car(Engine engine) { this.engine = engine; } }
حالت Setter :
class Car { Engine engine; @Autowired void setEngine(Engine engine) { this.engine = engine; } }
حالت Filed :
class Car { @Autowired Engine engine; }
نکات : Autowired@ میتواند یک آرگومان از نوع boolean تحت عنوان required=true داشته باشد و وقتی Spring نتواند ابجکت متناظر با آنرا پیدا کند یک Exception پرتاب میکند .
اگر برای تزریق وابستگی از روش Constractor استفاده کنیم کلیه آرگومان ها اجباری خواهند بود .
از Spring 4.3 به بعد نیازی نیست روی Constractor از Autowired@ استفاده کنیم مگر اینکه بیش از یک Constractor داشته باشیم
چنانچه دو ابجکت با نام یکسان در لیست Bean های Spring وجود داشت ، موقع Resolve کردن خطای NoUniqueBeanDefinitionException دریافت خواهد شد برای این شرایط میتوان از دو روش استفاده کرد از نام فیلد مورد نظر ابجکت مورد نظر را پیدا کند
public class FooService { @Autowired private Formatter fooFormatter; }
یا از Qualifier@ استفاده کرد :
@Component("fooFormatter") public class FooFormatter implements Formatter { public String format() { return "foo"; } } @Component("barFormatter") public class BarFormatter implements Formatter { public String format() { return "bar"; } } //wrong way public class FooService { @Autowired private Formatter formatter; } //correct way public class FooService { @Autowired @Qualifier("fooFormatter") private Formatter formatter; }
استفاده از Qualifier@ بصورت سفارشی :
@Qualifier @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface FormatterType { String value(); } @FormatterType("Foo") @Component public class FooFormatter implements Formatter { public String format() { return "foo"; } } @FormatterType("Bar") @Component public class BarFormatter implements Formatter { public String format() { return "bar"; } } @Component public class FooService { @Autowired @FormatterType("Foo") private Formatter formatter; }
با Qualifier@ میتوان نام bean را مشخص کرد که در حالت های ابهام زا استفاده کرد
Bean@ : متد سازنده یک instance جدید از Spring Bean را میتوان تعریف کرد و بعدا با آن یک instance ایجاد کرد :
@Bean Engine engine() { return new Engine(); }
@Bean("engine") Engine getEngine() { return new Engine(); }
Requiered@ : اجباری بودن مقدار برای Field را مشخص میکند
Value@ : میتوان برای تزریق مقادیر به bean استفاده کرد که سه نوع میتوان آنرا بکار برد :
//Constructor injection Engine(@Value("8") int cylinderCount) { this.cylinderCount = cylinderCount; }
//Setter injection @Autowired void setCylinderCount(@Value("8") int cylinderCount) { this.cylinderCount = cylinderCount; }
//Alternatively setter injection @Value("8") void setCylinderCount(int cylinderCount) { this.cylinderCount = cylinderCount; }
//Field injection @Value("8") int cylinderCount;
نکته : تزریق مقادیر به فیلد های Static کاربردی نیست ولی میتوانیم مقادیر ثابتی را از روی فایل های تنظیمات properties و yaml بخوانیم و آنرا تزریق کنیم :
(config.properties file ) engine.fuelType=petrol
//injection with SpEL @Value("${engine.fuelType}") String fuelType;
DependsOn@ : هنگامی که ایجا یک bean وابسته به bean دیگری باشد از این annotation استفاده میکنیم :
@DependsOn("engine") class Car implements Vehicle {}
حتی میتوان موقع ایجاد یک bean توسط factory method ها هم از این annotation استفاده کرد :
@Bean @DependsOn("fuel") Engine engine() { return new Engine(); }
Lazy@ : زمانی که بخواهیم bean با تاخیر ایجاد شود از این annotation استفاده میکنیم . بطور پیش فرض در Spring روند ایجاد یک singleton bean در زمان آغاز بکار کردن Spring بصورت eager است ولی گاهی bean ها را از ابتدای آغاز بکار Spring نیاز نداریم و تنها مواقعی نیاز میشود که از آنها استفاده کنیم در این حالت از خاصیت lazy استفاده میکنیم اینکار باعث میشود منابع بیهوده استفاده نشوند.
این annotation در 4 مورد استفاده میشود و بسته به مورد استفاده رفتار مختلفی خواهد داشت :
همراه با Bean@ روی متد سازنده bean باعث میشود که متد سازنده دیرتر اجرا شود و ابتدا bean ساخته میشود
همراه با Configuration@ کلاس ، باعث میشود کلیه bean های داخلی آن تاثیر بگیرند
@Lazy @Configuration @ComponentScan(basePackages = "com.baeldung.lazy") public class AppConfig { @Bean public Region getRegion(){ return new Region(); } @Bean public Country getCountry(){ return new Country(); } }
@Configuration @Lazy class VehicleFactoryConfig { @Bean @Lazy(false) //disable lazy initialize Engine engine() { return new Engine(); } }
همراه با Component@ کلاس که یک کلاس از نوع Configuration@ نیست همان کلاس بصورت lazy آماده بکار خواهد شد
همراه با Autowired@ (بکمک proxy) باعث میشود وابستگیهایش بصورت lazy لود شوند
//The bean that we want to load lazily @Lazy @Component public class City { public City() { System.out.println("City bean initialized"); } }
//And it's reference //Note, that the @Lazy is mandatory in both places public class Region { @Lazy @Autowired private City city; public Region() { System.out.println("Region bean initialized"); } public City getCityInstance() { return city; } }
این annotation یک پارامتر درونی از نوع boolean دارد که eager (با false شدن) و lazy (با true شدن) را میتوان مشخص کرد
@Bean @Lazy(true) public Region getRegion(){ return new Region(); }
LookUp@ : برای dependency injection در سطح متد استفاده میشود . وقتی از این annotation روی متد استفاده میکنیم به Spring میگوییم که وقتی این متد call شد باید ابجکتی از نوع برگشتی متد به ما ارائه دهد و Spring بوسیله bean factory پارامتر ها را ایجاد و نوع برگشتی را از نوع برگشتی متد مورد نظر قرار میدهد ( Spring DI )
این annotation در دو مورد کاربردی است :
نوع اول: زمانی که بخواهیم bean از prototype-scope را به یک singleton-bean تزریق کنیم
نوع دوم: تزریق وابستگی ها بصورت procedural
نوع اول :
صورت مسئله از آنجا ایجاد میشود که ما تصمیم داشته باشیم یک Spring prototype bean داشته باشیم درصورتی که مشکل اصلی ارتباط و درسترسی آنها به Spring singleton bean ها است . در نگاه اول میتوانیم ببینیم که استفاده از Provider به عنوان راه کار اولیه به ذهن میرسد گرچه استفاده از Lookup@ در بعضی حالت ها ارجحیت دارد
برای مثال بیاییم یک prototype bean بسازیم که بعدا قرار است داخلی یک singleton bean تزریق شود :
@Component @Scope("prototype") public class SchoolNotification { // ... prototype-scoped state }
حالا یک singleton bean میسازیم که قرار است از Lookup@ استفاده کند :
@Component public class StudentServices { // ... member variables, etc. @Lookup public SchoolNotification getNotification() { return null; } // ... getters and setters }
حال میبینیم که با استفاده از Lookup@ میتوانیم نمونه ای از SchoolNotifier که یک prototype bean است را در درون singleton bean داشته باشیم :
@Test public void whenLookupMethodCalled_thenNewInstanceReturned() { // ... initialize context StudentServices first = this.context.getBean(StudentServices.class); StudentServices second = this.context.getBean(StudentServices.class); assertEquals(first, second); assertNotEquals(first.getNotification(), second.getNotification()); }
همانطور که میبینید در کد بالا ، شرط اول چون هر دو ابجکت singleton هستند و به یک ابجکت اشاره میکنند پس ابجکت ها به یک نقطه ارجاع دارند ولی در چک کردن شرط دوم چون متد getNotification یک ابجکت از نوع prototype -bean میسازد هر دو ابجکت یک رفرنس مشترک ندارند و هر یک بصورت مجزا ساخته شده اند
نوع دوم :
گاهی اوقات ما نیاز داریم که وابستگی ها را بصورت procedural تزریق کنیم که از عهده Provider بر نمی آید و اینجا قدرت استفاده از Lookup@ نمایان است
بیاییم به کلاس SchoolNotifier صفات و فیلد هایی را اضافه کنیم :
@Component @Scope("prototype") public class SchoolNotification { @Autowired Grader grader; private String name; private Collection<Integer> marks; public SchoolNotification(String name) { // ... set fields } // ... getters and setters public String addMark(Integer mark) { this.marks.add(mark); return this.grader.grade(this.marks); } }
و نحوه استفاده آن :
public abstract class StudentServices { private Map<String, SchoolNotification> notes = new HashMap<>(); @Lookup protected abstract SchoolNotification getNotification(String name); public String appendMark(String name, Integer mark) { SchoolNotification notification = notes.computeIfAbsent(name, exists -> getNotification(name))); return notification.addMark(mark); } }
در runtime متد مورد نظر توسط Spring به دو صورت پیاده سازی خواهد شد :
روش اول : همانطور که میتواند یک Spring bean را میتوان در قالب constructor تزریق کرد یک constructor پیچیده هم میتواند فراخوانی شود این امکان در این کد پیاده سازی متد getSchoolNotification توسط فراخوانی (beanFactory.getBean(SchoolNotification.class, name قابل انجام است
روش دوم : ما گاهی میتوانیم متدی که Lookup@ میکنیم را بصورت abstract تعریف کنیم همانطور که در کد بالا استفاده شد این حالت به نظر کد زیبا تری است اما محدودیتی که دارد این است که ما از این حالت فقط موقعی میتونیم استفاده کنیم که bean کامپوننت اسکن یا توسط Bean-Manage@ استفاده نشود
@Test public void whenAbstractGetterMethodInjects_thenNewInstanceReturned() { // ... initialize context StudentServices services = context.getBean(StudentServices.class); assertEquals("PASS", services.appendMark("Alex", 89)); assertEquals("FAIL", services.appendMark("Bethany", 78)); assertEquals("PASS", services.appendMark("Claire", 96)); }
با این ویژگی میتوانیم وابستگی های bean را توسط متد اضافه کنیم
محدودیت ها :
- متدهایی که با Lookup@ مشخص شده اند مثل مثال بالا (getNotification) وقتی در کلاسی قرار گرفته باشند که توسط Spring Component Scan اسکن بشوند، باید پیاده سازی شده باشند چون عملیات اسکن abstract bean ها را در نظر نمیگیرد
- هنگامی که متد های Lookup@ شده در داخل کلاس های Bean-Managed@ قرار بگیرند کار نخواهند کرد
در این موقعیت ها که نیاز داریم یک prototype bean را در singleton bean تزریق کنیم میتوانیم از Provider استفاده کنیم
Primary@ : گاهی اوقات میخواهیم از یک bean ، چندین نوع داشته باشیم که در این حالت تزریق bean ها موفق نخواهد بود چون Spring نمیتواند منظور ما کدامیک از bean های متشابه و یکسان است
در گذشته دیدیم که میتوان با Qualifier@ دقیقا نام bean مورد نظر را مشخص کرد
اگر در bean های مشترک یک bean بیشترین استفاده را داشته باشد و نخواهیم هر بار با Qualifier@ نوع آن را مشخص کنیم میتوانیم روی آن bean پر کاربرد از Primary@ استفاده کنیم و موقع تزریق کردن بدون Qualifier@ آنرا ایجاد کنیم :
@Component @Primary class Car implements Vehicle {}
@Component class Bike implements Vehicle {}
@Component class Driver { @Autowired Vehicle vehicle; }
@Component class Biker { @Autowired @Qualifier("bike") Vehicle vehicle; }
کلاس Car که Bean است نوعی Vehicle است توسط Primary@ مشخص شده است همچنین Bean دیگری از نوع Vehicle بنام Bike داریم پس در اینجا از Vehicle دو نوع Bean ساخته ایم حالا موقع استفاده وقتی نام انرا مشخص نکرده باشیم نوع Car تزریق میشود ولی وقتی با Qualifier@ نام آنرا به Bike تغییر دهیم نوع دیگر تزریق خواهد شد
Scope@ : میتوان Scope یک Bean@ یا Component@ را تعیین کرد که میتواند مقادیر singletone, prototype, request, session, globalSession و یا scope خودمان را با آن مشخص میکنیم
* مبحث Scope ها را میتوانید در پست دیگری مطالعه بفرمایید
بخش دوم Annotation های مربوط به تنظیمات هستند (Context Configuration Annotations) :
Profile@ : اگر ما بخواهیم Spring فقط از یک کلاس Component@ یا متد Bean@ در صورت فعال کردن یک profile خاص، استفاده کند میتوانید آن دسته از کلاس ها و متد ها را با نام Profile ای مشخص کنیم
* Profile ها و نحوه استفاده آنها در پست جداگانه پرداخته میشود که میتوانید مطالعه فرمایید
Import@ : با استفاده از این annotation میتوانیم کلاس های Configuration@ ای داشته باشیم که بدون نیاز به اسکن شدن از آنها استفاده کنیم که این کلاس های خاص را با Import@ مشخص میکنیم :
@Import(VehiclePartSupplier.class) class VehicleFactoryConfig {}
ImportResource@ : میتوانیم تنظیمات موجود در XML فایل ها را از طریق آدرس دهی آنها وارد و قابل استفاده کنیم :
@Configuration @ImportResource("classpath:/annotations.xml") class VehicleFactoryConfig {}
PropertySource@ : تنظیمات درون فایل Property را از طریق ادرس آن قابل استفاده میکند :
@Configuration @PropertySource("classpath:/annotations.properties") class VehicleFactoryConfig {}
با استفاده از ویژگی جاوا 8 که میتوان Annotation ها را تکرار کرد، میتوان چندین بار از آن استفاده کنیم :
@Configuration @PropertySource("classpath:/annotations.properties") @PropertySource("classpath:/vehicle-factory.properties") class VehicleFactoryConfig {}
PropertySources@ : با استفاده از این annotation میتوان چندین زیر PropertySource@ داشته باشیم که با استفاده از جاوا 8 این مشکل برطرف شده و میتوان روی هر کلاسی از annotation های هم نام مانند کد بالا چند بار استفاده کرد :
@Configuration @PropertySources({ @PropertySource("classpath:/annotations.properties"), @PropertySource("classpath:/vehicle-factory.properties") }) class VehicleFactoryConfig {}