Proljetna prilagođena bilješka za bolji DAO

1. Pregled

U ovom ćemo uputstvu implementirati prilagođena proljetna bilješka s post-procesorom graha.

Pa kako ovo pomaže? Jednostavno rečeno - možemo ponovno koristiti isti grah, umjesto da moramo stvarati više, sličnih zrna iste vrste.

To ćemo učiniti za implementacije DAO-a u jednostavnom projektu - zamjenjujući sve njih jednim, fleksibilnim GenericDao.

2. Maven

Trebamo opružna jezgra, proljeće-aop, i proljeće-kontekst-podrška JAR-ovi da ovo uspije. Možemo se samo izjasniti proljeće-kontekst-podrška u našem pom.xml.

 org.springframework proljeće-kontekst-podrška 5.2.2.OSLOBOĐENJE 

Ako želite odabrati noviju verziju ovisnosti Spring - provjerite spremište maven.

3. Novi generički DAO

Većina implementacija Spring / JPA / Hibernate koristi standardni DAO - obično jedan za svaki entitet.

Zamijenit ćemo to rješenje s GenericDao; umjesto toga napisat ćemo prilagođeni procesor bilješki i to upotrijebiti GenericDao provedba:

3.1. Generički DAO

javna klasa GenericDao {private class entityClass; javni GenericDao (Klasa entityClass) {this.entityClass = entityClass; } javni popis findAll () {// ...} javni Neobvezno ustrajati (E toPersist) {// ...}} 

U scenariju iz stvarnog svijeta, naravno, morat ćete povezati PersistentContext i zapravo pružiti implementacije ovih metoda. Za sada ćemo ovo učiniti što jednostavnijim.

Sada, napravimo napomenu za prilagođeno ubrizgavanje.

3.2. Pristup podacima

@Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @Dokumentirani javni @interface DataAccess {Entitet klase (); }

Gornju ćemo napomenu upotrijebiti za ubrizgavanje a GenericDao kako slijedi:

@DataAccess (entity = Person.class) private GenericDao personDao;

Možda neki od vas pitaju: „Kako Proljeće prepoznaje naše Pristup podacima bilješka? ”. Ne - ne prema zadanim postavkama.

No, Springu bismo mogli reći da prepozna napomenu putem običaja BeanPostProcessor - ajmo to implementirati sljedeće.

3.3. DataAccessAnnotationProcessor

@Component javna klasa DataAccessAnnotationProcessor implementira BeanPostProcessor {private ConfigurableListableBeanFactory configurableBeanFactory; @Autowired javni DataAccessAnnotationProcessor (ConfigurableListableBeanFactory beanFactory) {this.configurableBeanFactory = beanFactory; } @Override public Object postProcessBeforeInitialization (Object bean, String beanName) baca BeansException {this.scanDataAccessAnnotation (bean, beanName); grah za povratak; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) baca BeansException {return bean; } zaštićena praznina scanDataAccessAnnotation (grah objekta, niz beanName) {this.configureFieldInjection (grah); } private void configureFieldInjection (Object bean) {Class managedBeanClass = bean.getClass (); FieldCallback fieldCallback = novi DataAccessFieldCallback (configurableBeanFactory, bean); ReflectionUtils.doWithFields (managedBeanClass, fieldCallback); }} 

Dalje - evo provedbe DataAccessFieldCallback upravo smo koristili:

3.4. DataAccessFieldCallback

javna klasa DataAccessFieldCallback implementira FieldCallback {private static Logger logger = LoggerFactory.getLogger (DataAccessFieldCallback.class); private static int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; privatni statički niz ERROR_ENTITY_VALUE_NOT_SAME = "@DataAccess (entity)" + "vrijednost treba imati isti tip s ubačenim generičkim tipom."; privatni statički niz WARN_NON_GENERIC_VALUE = "@DataAccess bilješka dodijeljena" + "sirovoj (ne-generičkoj) deklaraciji. To će vaš kôd učiniti manje sigurnim za tip."; private static String ERROR_CREATE_INSTANCE = "Nije moguće stvoriti instancu" + "tipa '{}' ili izrada instance nije uspjela jer: {}"; private ConfigurableListableBeanFactory configurableBeanFactory; privatni objekt grah; javni DataAccessFieldCallback (ConfigurableListableBeanFactory bf, Object bean) {configurableBeanFactory = bf; this.bean = grah; } @Override public void doWith (Polje polja) baca IllegalArgumentException, IllegalAccessException {if (! Field.isAnnotationPresent (DataAccess.class)) {return; } ReflectionUtils.makeAccessible (polje); Upišite fieldGenericType = field.getGenericType (); // U ovom primjeru dobivamo stvarni "GenericDAO 'tip. Class generic = field.getType (); Class classValue = field.getDeclaredAnnotation (DataAccess.class) .entity (); if (genericTypeIsValid (classValue, fieldGenericType)) {String beanName = classValue.getSimpleName () + generic.getSimpleName () objekt beanInstance = getBeanInstance (beanName, općenito, classValue) field.set (grah, beanInstance);} else {baciti novi IllegalArgumentException (ERROR_ENTITY_VALUE_NOT_SAME);}} javni logički genericTypeIsValid ( Klasa klasa, polje tipa) {if (polje instance ParameterizedType) {polje ParameterizedType parameterizedType = (ParameterizedType); Tip type = parameterizedType.getActualTypeArguments () [0]; return type.equals (clazz);} else {logger.warn (WARN_NON_GENERIC_VALUE ); return true;}} public Object getBeanInstance (String beanName, Class genericClass, Class paramClass) {Object daoInstance = null; if (! configurableBeanFactory.containsBean (beanName)) {logger.info ("Stvaranje novog DataAccess zrna s imenom '{}'. ", ime graha); Objekt toRegister = null; isprobajte {Constructor ctr = genericClass.getConstructor (Class.class); toRegister = ctr.newInstance (paramClass); } catch (Iznimka e) {logger.error (ERROR_CREATE_INSTANCE, genericClass.getTypeName (), e); baciti novi RuntimeException (e); } daoInstance = configurableBeanFactory.initializeBean (toRegister, beanName); configurableBeanFactory.autowireBeanProperties (daoInstance, AUTOWIRE_MODE, true); configurableBeanFactory.registerSingleton (beanName, daoInstance); logger.info ("Bean pod nazivom '{}' uspješno stvoren.", beanName); } else {daoInstance = configurableBeanFactory.getBean (beanName); logger.info ("Bean pod nazivom '{}' već postoji kao trenutna referenca za grah.", beanName); } return daoInstance; }} 

To je prilično implementacija - ali najvažniji dio je doWith () metoda:

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

To bi reklo Springu da inicijalizira grah na temelju objekta ubrizganog u vrijeme izvođenja putem @DataAccess bilješka.

The grahName pobrinut ćemo se da dobijemo jedinstveni primjerak graha jer - u ovom slučaju - želimo stvoriti jedan objekt od GenericDao ovisno o entitetu ubrizganom putem @DataAccess bilješka.

Konačno, upotrijebimo ovaj novi procesor graha u konfiguraciji Spring sljedeće.

3.5. CustomAnnotationConfiguration

@Configuration @ComponentScan ("com.baeldung.springcustomannotation") javna klasa CustomAnnotationConfiguration {} 

Jedna stvar koja je ovdje važna je ta vrijednost @ComponentScan napomena treba ukazati na paket u kojem se nalazi naš prilagođeni procesor za grah i osigurati je li ga Spring skenirao i automatski povezao u vrijeme izvođenja.

4. Testiranje novog DAO-a

Počnimo s testom koji je omogućio Spring i dvije jednostavne klase entiteta ovdje - Osoba i Račun.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {CustomAnnotationConfiguration.class}) javna klasa DataAccessAnnotationTest {@DataAccess (entity = Person.class) private GenericDao personGenericDao; @DataAccess (entity = Account.class) private GenericDao accountGenericDao; @DataAccess (entity = Person.class) private GenericDao anotherPersonGenericDao; ...}

Ubrizgavamo nekoliko primjera GenericDao uz pomoć Pristup podacima bilješka. Da bismo testirali je li novi grah pravilno ubrizgan, trebamo pokriti:

  1. Ako je injekcija uspješna
  2. Ako su instance graha s istim entitetom iste
  3. Ako su metode u GenericDao zapravo rade kako se očekivalo

Točku 1. zapravo pokriva samo Proljeće - budući da okvir prilično rano donosi iznimku ako se grah ne može povezati.

Da bismo testirali točku 2, moramo pogledati 2 slučaja GenericDao koje oboje koriste Osoba razred:

@Test public void whenGenericDaoInjected_thenItIsSingleton () {assertThat (personGenericDao, not (sameInstance (accountGenericDao))); assertThat (personGenericDao, ne (jednakTo (accountGenericDao))); assertThat (personGenericDao, sameInstance (anotherPersonGenericDao)); }

Ne želimo personGenericDao biti jednak accountGenericDao.

Ali mi želimo personGenericDao i drugiPersonGenericDao da bude potpuno ista instanca.

Da bismo testirali točku 3, ovdje samo testiramo neku jednostavnu logiku upornosti:

@Test javna void whenFindAll_thenMessagesIsCorrect () {personGenericDao.findAll (); assertThat (personGenericDao.getMessage (), is ("Stvorio bi upit findAll od osobe")); accountGenericDao.findAll (); assertThat (accountGenericDao.getMessage (), is ("Stvorio bi upit findAll iz računa")); } @Test public void whenPersist_thenMessagesIsCorrect () {personGenericDao.persist (new Person ()); assertThat (personGenericDao.getMessage (), is ("Stvorio bi trajni upit od osobe")); accountGenericDao.persist (novi račun ()); assertThat (accountGenericDao.getMessage (), is ("Stvorio bi trajni upit s računa")); } 

5. Zaključak

U ovom smo članku na proljeće izvršili vrlo cool primjenu prilagođene bilješke, zajedno s BeanPostProcessor. Ukupni cilj bio je riješiti se višestrukih implementacija DAO-a koje obično imamo u svom sloju postojanosti i koristiti lijepu, jednostavnu generičku implementaciju, a da se pritom ništa ne izgubi.

Implementacija svih ovih primjera i isječaka koda može se naći u moj GitHub projekt - ovo je projekt zasnovan na Eclipseu, pa bi ga trebalo lako uvesti i pokrenuti kakav jest.