הערה מותאמת אישית באביב למען DAO טוב יותר

1. סקירה כללית

במדריך זה נבצע הערת אביב בהתאמה אישית עם מעבד שעועית שעועית.

אז איך זה עוזר? במילים פשוטות - נוכל לעשות שימוש חוזר באותה שעועית במקום שנצטרך ליצור שעועית דומה, מאותו סוג.

אנו נעשה זאת עבור יישומי DAO בפרויקט פשוט - להחליף את כולם בפרט יחיד וגמיש GenericDao.

2. מייבן

אנחנו צריכים ליבת קפיץ, אביב-אוף, ו אביב-תמיכה בהקשר JARs כדי שזה יעבוד. אנחנו יכולים פשוט להכריז אביב-תמיכה בהקשר בשלנו pom.xml.

 org.springframework spring-context-support 5.2.2.RELEASE 

אם אתה רוצה ללכת לגרסה חדשה יותר של תלות האביב - בדוק במאגר ה- maven.

3. DAO גנרי חדש

מרבית יישומי האביב / JPA / Hibernate משתמשים ב- DAO הסטנדרטי - בדרך כלל אחד לכל ישות.

אנו נחליף את הפיתרון הזה בא GenericDao; אנחנו נכתוב במקום זאת מעבד הערות מותאם אישית ונשתמש בזה GenericDao יישום:

3.1. DAO גנרי

מחלקה ציבורית GenericDao {entity ClassClass פרטי; Public GenericDao (Class entityClass) {this.entityClass = entityClass; } public list findAll () {// ...} public קבוע אופציונלי (E toPersist) {// ...}} 

בתרחיש של עולם אמיתי, כמובן שתצטרך לחבר PersistenceContext ולמעשה לספק את היישומים של שיטות אלה. לעת עתה - נעשה את זה כמה שיותר פשוט.

עכשיו, בואו ליצור הערה לצורך הזרקה מותאמת אישית.

3.2. גישה למידע

@Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @Documented public @interface DataAccess {ישות מחלקה (); }

נשתמש בהערה שלמעלה כדי להזריק א GenericDao כדלהלן:

@ DataAccess (ישות = Person.class) פרטי GenericDao personDao;

אולי כמה מכם שואלים, "איך האביב מזהה את שלנו גישה למידע ביאור? ”. זה לא - לא כברירת מחדל.

אבל יכולנו לומר לאביב להכיר את ההערה באמצעות מנהג מעבד BeanPost בואו נבצע את זה אחר כך.

3.3. DataAccessAnnotationProcessor

מחלקה ציבורית @Component DataAccessAnnotationProcessor מיישם את BeanPostProcessor {private ConfigurableListableBeanFactory configurableBeanFactory; @Autowered ציבורי DataAccessAnnotationProcessor (ConfigurableListableBeanFactory beanFactory) {this.configurableBeanFactory = beanFactory; } @Override אובייקט ציבורי postProcessBeforeInitialization (שעועית אובייקט, מחרוזת שעועית) זורק BeansException {this.scanDataAccessAnnotation (שעועית, שעועית); שעועית להחזיר; } @Override אובייקט ציבורי postProcessAfterInitialization (שעועית אובייקט, שעועית מחרוזת) זורק BeansException {שעועית חזרה; } ריק מוגן scanDataAccessAnnotation (שעועית אובייקט, מחרוזת שעועית) {this.configureFieldInjection (שעועית); } חלל פרטי configureFieldInjection (שעועית אובייקט) {Class managedBeanClass = bean.getClass (); FieldCallback fieldCallback = DataAccessFieldCallback חדש (להגדרה BeanFactory, שעועית); ReflectionUtils.doWithFields (managedBeanClass, fieldCallback); }} 

הבא - הנה יישום ה- DataAccessFieldCallback פשוט השתמשנו:

3.4. DataAccessFieldCallback

מחלקה ציבורית DataAccessFieldCallback מיישם FieldCallback {לוגר לוגר סטטי פרטי = LoggerFactory.getLogger (DataAccessFieldCallback.class); private static int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; מחרוזת סטטית פרטית ERROR_ENTITY_VALUE_NOT_SAME = "ערך @DataAccess (ישות)" + "צריך להיות מאותו סוג עם סוג כללי מוזרק."; מחרוזת סטטית פרטית WARN_NON_GENERIC_VALUE = "הערת @ DataAccess הוקצתה" + "להצהרה גולמית (לא כללית). הדבר יהפוך את הקוד שלך לפחות בטיחותי."; private static String ERROR_CREATE_INSTANCE = "לא ניתן ליצור מופע מסוג" + "'{}' או יצירת מופע נכשלה מכיוון: {}"; פרטי ConfigurableListableBeanFactory configurableBeanFactory; שעועית עצמית פרטית; DataAccessFieldCallback ציבורי (ConfigurableListableBeanFactory bf, שעועית אובייקט) {configurableBeanFactory = bf; this.bean = שעועית; } @ עקירה ציבורית בטל doWith (שדה שדה) זורק IllegalArgumentException, IllegalAccessException {אם (! Field.isAnnotationPresent (DataAccess.class)) {return; } ReflectionUtils.makeAccessible (שדה); הקלד fieldGenericType = field.getGenericType (); // בדוגמה זו, קבלו את סוג "GenericDAO" בפועל. Class generic = field.getType (); Class classValue = field.getDeclaredAnnotation (DataAccess.class) .entity (); if (genericTypeIsValid (classValue, fieldGenericType)) {String beanName = classValue.getSimpleName () + generic.getSimpleName (); אובייקט beanInstance = getBeanInstance (beanName, generic, classValue); field.set (bean, beanInstance);} אחר {זורק IllegalArgumentException (ERROR_ENTITY_VALUE_NOTT_SID_SAME); Class clazz, Type field) {if (field instanceof ParameterizedType) {ParameterizedType parameterizedType = (ParameterizedType) field; Type type = parameterizedType.getActualTypeArguments () [0]; return type.equals (clazz);} אחר {logger.warn (WARN_NON_GENERIC_VALUE ); להחזיר נכון;}} אובייקט ציבורי getBeanInstance (String beanName, Class genericClass, Class paramClass) {Object daoInstance = null; if (! configurableBeanFactory.containsBean (beanName)) {logger.info ("יצירת שעועית DataAccess חדשה בשם '{}'. ", שם שעועית); אובייקט toRegister = null; נסה את {Constructor ctr = genericClass.getConstructor (Class.class); toRegister = ctr.newInstance (paramClass); } לתפוס (חריג e) {logger.error (ERROR_CREATE_INSTANCE, genericClass.getTypeName (), e); לזרוק RuntimeException חדש (ה); } daoInstance = configurableBeanFactory.initializeBean (toRegister, beanName); configurableBeanFactory.autowireBeanProperties (daoInstance, AUTOWIRE_MODE, נכון); configableBeanFactory.registerSingleton (beanName, daoInstance); logger.info ("שעועית בשם '{}' נוצרה בהצלחה.", שעועית); } אחר {daoInstance = configurableBeanFactory.getBean (beanName); logger.info ("השעועית בשם '{}' כבר קיימת משמשת כהפניה לשעועית הנוכחית.", beanName); } להחזיר daoInstance; }} 

עכשיו - זה די יישום - אבל החלק החשוב ביותר בו הוא לעשות עם() שיטה:

genericDaoInstance = configurableBeanFactory.initializeBean (beanToRegister, beanName); configableBeanFactory.autowireBeanProperties (genericDaoInstance, autowireMode, true); configureBeanFactory.registerSingleton (beanName, genericDaoInstance); 

זה יגיד לאביב לאתחל שעועית על בסיס האובייקט שהוזרק בזמן הריצה באמצעות @גישה למידע ביאור.

ה שם שעועית נדאג שנקבל מופע ייחודי של השעועית מכיוון שבמקרה זה אנו רוצים ליצור אובייקט יחיד של GenericDao תלוי בישות שהוזרקה באמצעות @גישה למידע ביאור.

לבסוף, בואו נשתמש במעבד שעועית חדש זה בתצורת אביב.

3.5. CustomAnnotationConfiguration

@Configuration @ComponentScan ("com.baeldung.springcustomannotation") מחלקה ציבורית CustomAnnotationConfiguration {} 

דבר אחד שחשוב כאן הוא זה, הערך של @ComponentScan ההערה צריכה להצביע על החבילה בה נמצא מעבד השעועית המותאם אישית שלנו ולוודא שהיא נסרקה וחוטית אוטומטית על ידי Spring בזמן הריצה.

4. בדיקת ה- DAO החדש

נתחיל במבחן מאופשר באביב ובשני שיעורים פשוטים לדוגמא כאן - אדם ו חֶשְׁבּוֹן.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (class = {CustomAnnotationConfiguration.class}) מחלקה ציבורית DataAccessAnnotationTest {@DataAccess (entity = Person.class) פרטי GenericDao personGenericDao; @ DataAccess (ישות = Account.class) חשבון GenericDao פרטי GenericDao; @DataAccess (ישות = Person.class) פרטי GenericDao anotherPersonGenericDao; ...}

אנחנו מזריקים כמה מקרים של GenericDao בעזרת ה- גישה למידע ביאור. כדי לבדוק שהשעועית החדשה מוזרקת כהלכה, נצטרך לכסות:

  1. אם ההזרקה מוצלחת
  2. אם מופעי שעועית עם אותה ישות זהים
  3. אם השיטות ב GenericDao למעשה לעבוד כצפוי

נקודה 1 מכוסה למעשה על ידי האביב עצמו - מכיוון שהמסגרת מביאה חריג די מוקדם אם לא ניתן לחבר שעועית.

כדי לבדוק נקודה 2, עלינו לבחון את שני המקרים של ה- GenericDao ששניהם משתמשים ב- אדם מעמד:

@Test הציבור בטל כאשר GenericDaoInjected_thenItIsSingleton () {assertThat (personGenericDao, not (sameInstance (accountGenericDao))); assertThat (personGenericDao, not (equalTo (accountGenericDao))); assertThat (personGenericDao, sameInstance (anotherPersonGenericDao)); }

אנחנו לא רוצים personGenericDao להיות שווה ל- accountGenericDao.

אבל אנחנו כן רוצים את personGenericDao ו AnotherPersonGenericDao להיות בדיוק אותו מקרה.

כדי לבדוק את נקודה 3, אנו בודקים כאן כמה הגיונות פשוטים הקשורים להתמדה:

@Test הציבור בטל כאשר FindAll_thenMessagesIsCorrect () {personGenericDao.findAll (); assertThat (personGenericDao.getMessage (), הוא ("תיצור שאילתת findAll מאדם")); accountGenericDao.findAll (); assertThat (accountGenericDao.getMessage (), הוא ("תיצור שאילתת findAll מחשבון")); } @Test ציבורי בטל כאשר Persist_thenMessagesIsCorrect () {personGenericDao.persist (אדם חדש ()); assertThat (personGenericDao.getMessage (), הוא ("ייצור שאילתה מתמשכת מאדם")); accountGenericDao.persist (חשבון חדש ()); assertThat (accountGenericDao.getMessage (), הוא ("ייצור שאילתת קביעות מחשבון")); } 

5. מסקנה

במאמר זה ביצענו יישום מגניב מאוד של הערה מותאמת אישית באביב - יחד עם מעבד BeanPost. המטרה הכוללת הייתה להיפטר מיישומי ה- DAO המרובים שיש לנו בדרך כלל בשכבת ההתמדה שלנו ולהשתמש ביישום כללי נחמד ופשוט מבלי לאבד דבר בתהליך.

יישום כל הדוגמאות וקטעי הקוד יכול להימצא ב פרויקט GitHub שלי - זהו פרויקט מבוסס ליקוי חמה, כך שיהיה קל לייבא ולהפעיל אותו כמות שהוא.


$config[zx-auto] not found$config[zx-overlay] not found