En vårdefinert kommentar for en bedre DAO

1. Oversikt

I denne opplæringen implementerer vi en tilpasset vårkommentar med en bønne-prosessor.

Så hvordan hjelper dette? Enkelt sagt - vi kan bruke samme bønne i stedet for å måtte lage flere, lignende bønner av samme type.

Vi gjør det for DAO-implementeringene i et enkelt prosjekt - og erstatter dem alle med en enkelt, fleksibel GenericDao.

2. Maven

Vi trenger vårkjernen, vår-aop, og vår-kontekst-støtte JAR for å få dette til å fungere. Vi kan bare erklære vår-kontekst-støtte i vår pom.xml.

 org.springframework spring-context-support 5.2.2.RELEASE 

Hvis du vil gå for en nyere versjon av våravhengighet - sjekk ut maven-depotet.

3. Ny generisk DAO

De fleste implementeringer av vår / JPA / dvalemodus bruker standard DAO - vanligvis en for hver enhet.

Vi skal erstatte den løsningen med en GenericDao; vi skal skrive en tilpasset kommentarprosessor i stedet og bruke den GenericDao gjennomføring:

3.1. Generisk DAO

offentlig klasse GenericDao {private Class entityClass; offentlig GenericDao (Class entityClass) {this.entityClass = entityClass; } public List findAll () {// ...} public Optional persist (E toPersist) {// ...}} 

I et virkelig verdensscenario må du selvfølgelig koble til en PersistenceContext og faktisk gi implementeringene av disse metodene. Foreløpig - vi gjør dette så enkelt som mulig.

Nå kan vi opprette merknader for tilpasset injeksjon.

3.2. Datatilgang

@Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @Documented public @interface DataAccess {Class entity (); }

Vi bruker merknaden ovenfor for å injisere en GenericDao som følger:

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

Kanskje noen av dere spør: “Hvordan kjenner våren vår DataAccess kommentar? ”. Det gjør det ikke - ikke som standard.

Men vi kunne be våren om å gjenkjenne kommentaren via en skikk BeanPostProcessor - la oss få implementert dette neste.

3.3. DataAccessAnnotationProcessor

@Komponent offentlig klasse DataAccessAnnotationProcessor implementerer BeanPostProcessor {private ConfigurableListableBeanFactory configurableBeanFactory; @Autowired offentlig DataAccessAnnotationProcessor (ConfigurableListableBeanFactory beanFactory) {this.configurableBeanFactory = beanFactory; } @Override public Object postProcessBeforeInitialization (Object bean, String beanName) kaster BeansException {this.scanDataAccessAnnotation (bean, beanName); retur bønne; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) kaster BeansException {return bean; } beskyttet tomrom scanDataAccessAnnotation (Object bean, String beanName) {this.configureFieldInjection (bean); } privat tomrom configureFieldInjection (Object bean) {Class managedBeanClass = bean.getClass (); FieldCallback fieldCallback = ny DataAccessFieldCallback (konfigurerbarBeanFactory, bønne); ReflectionUtils.doWithFields (managedBeanClass, fieldCallback); }} 

Neste - her er implementeringen av DataAccessFieldCallback vi brukte nettopp:

3.4. DataAccessFieldCallback

offentlig klasse DataAccessFieldCallback implementerer FieldCallback {privat statisk loggerlogger = LoggerFactory.getLogger (DataAccessFieldCallback.class); private static int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; private static String ERROR_ENTITY_VALUE_NOT_SAME = "@DataAccess (entity)" + "verdi skal ha samme type med generisk type injisert."; private static String WARN_NON_GENERIC_VALUE = "@DataAccess-merknad tildelt" + "til rå (ikke-generisk) erklæring. Dette vil gjøre koden din mindre typesikker."; private static String ERROR_CREATE_INSTANCE = "Kan ikke opprette forekomst av" + "type '{}' eller opprettelse av forekomst mislyktes fordi: {}"; private ConfigurableListableBeanFactory configurableBeanFactory; private Objekt bønner; public DataAccessFieldCallback (ConfigurableListableBeanFactory bf, Object bean) {configurableBeanFactory = bf; this.bean = bønne; } @Override public void doWith (Field field) kaster IllegalArgumentException, IllegalAccessException {if (! Field.isAnnotationPresent (DataAccess.class)) {return; } ReflectionUtils.makeAccessible (felt); Skriv fieldGenericType = field.getGenericType (); // I dette eksemplet får du den faktiske typen "GenericDAO. Class generic = field.getType (); Class classValue = field.getDeclaredAnnotation (DataAccess.class) .entity (); if (genericTypeIsValid (classValue, fieldGenericType)) {String beanName = classValue.getSimpleName () + generic.getSimpleName (); Object beanInstance = getBeanInstance (beanName, generic, classValue); field.set (bean, beanInstance);} ellers {kast ny IllegalArgumentException (ERROR_ENTITY_VALUE_NOTT_SAME); Class clazz, Type field) {if (field instanceof ParameterizedType) {ParameterizedType parameterizedType = (ParameterizedType) field; Type type = parameterizedType.getActualTypeArguments () [0]; return type.equals (clazz);} else {logger.warn (WARN_NON_GENERIC_VALUE ); return true;}} offentlig Object getBeanInstance (String beanName, Class genericClass, Class paramClass) {Object daoInstance = null; if (! configurableBeanFactory.containsBean (beanName)) {logger.info ("Opprette ny DataAccess bønne med navnet '{}'. ", beanName); Object toRegister = null; prøv {Constructor ctr = genericClass.getConstructor (Class.class); toRegister = ctr.newInstance (paramClass); } fange (Unntak e) {logger.error (ERROR_CREATE_INSTANCE, genericClass.getTypeName (), e); kaste nye RuntimeException (e); } daoInstance = configurableBeanFactory.initializeBean (toRegister, beanName); configurableBeanFactory.autowireBeanProperties (daoInstance, AUTOWIRE_MODE, true); configurableBeanFactory.registerSingleton (beanName, daoInstance); logger.info ("Bean kalt '{}' opprettet med hell.", beanName); } annet {daoInstance = configurableBeanFactory.getBean (beanName); logger.info ("Bean med navnet '{}' eksisterer allerede brukt som gjeldende bønnereferanse.", beanName); } returner daoInstance; }} 

Nå - det er ganske en implementering - men den viktigste delen av det er gjøre med() metode:

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

Dette vil fortelle våren å initialisere en bønne basert på gjenstanden som injiseres ved kjøretid via @DataAccess kommentar.

De beanName vil sørge for at vi får en unik forekomst av bønnen fordi - i dette tilfellet - ønsker vi å lage ett objekt av GenericDao avhengig av enheten som injiseres via @DataAccess kommentar.

Til slutt, la oss bruke denne nye bønneprosessoren i en vårkonfigurasjon neste.

3.5. CustomAnnotationConfiguration

@Configuration @ComponentScan ("com.baeldung.springcustomannotation") offentlig klasse CustomAnnotationConfiguration {} 

En ting som er viktig her er at verdien av @ComponentScan merknader må peke på pakken der vår tilpassede bønnepostprosessor er plassert og sørge for at den skannes og autotrådes av Spring i løpetid.

4. Testing av den nye DAO

La oss starte med en våraktivert test og to enkle eksempler på enhetsklasser her - Person og Regnskap.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (klasser = {CustomAnnotationConfiguration.class}) offentlig klasse DataAccessAnnotationTest {@DataAccess (entity = Person.class) private GenericDao personGenericDao; @DataAccess (entity = Account.class) privat GenericDao-kontoGenericDao; @DataAccess (entity = Person.class) private GenericDao anotherPersonGenericDao; ...}

Vi injiserer noen få forekomster av GenericDao ved hjelp av DataAccess kommentar. For å teste at de nye bønnene er riktig injisert, må vi dekke:

  1. Hvis injeksjonen er vellykket
  2. Hvis bønneforekomster med samme enhet er de samme
  3. Hvis metodene i GenericDao faktisk fungerer som forventet

Punkt 1 er faktisk dekket av selve våren - da rammeverket kaster et unntak ganske tidlig hvis en bønne ikke kan kobles inn.

For å teste punkt 2, må vi se på de to forekomster av GenericDao at begge bruker Person klasse:

@Test offentlig ugyldig nårGenericDaoInjected_thenItIsSingleton () {assertThat (personGenericDao, ikke (sameInstance (accountGenericDao))); assertThat (personGenericDao, ikke (equalTo (accountGenericDao))); assertThat (personGenericDao, sameInstance (anotherPersonGenericDao)); }

Vi vil ikke personGenericDao å være lik accountGenericDao.

Men vi vil ha personGenericDao og en annenPersonGenericDao å være nøyaktig samme forekomst.

For å teste punkt 3 tester vi bare litt enkel utholdenhetsrelatert logikk her:

@Test offentlig ugyldig nårFindAll_thenMessagesIsCorrect () {personGenericDao.findAll (); assertThat (personGenericDao.getMessage (), er ("Ville skape findAll spørring fra person")); accountGenericDao.findAll (); assertThat (accountGenericDao.getMessage (), er ("Ville opprette findAll spørring fra konto")); } @Test offentlig ugyldig nårPersist_thenMessagesIsCorrect () {personGenericDao.persist (ny person ()); assertThat (personGenericDao.getMessage (), er ("Vil opprette vedvarende forespørsel fra Person")); accountGenericDao.persist (ny konto ()); assertThat (accountGenericDao.getMessage (), er ("Ville opprette vedvarende forespørsel fra konto")); } 

5. Konklusjon

I denne artikkelen gjorde vi en veldig kul implementering av en tilpasset kommentar om våren - sammen med en BeanPostProcessor. Det overordnede målet var å kvitte seg med de mange DAO-implementeringene vi vanligvis har i vårt utholdenhetslag og bruke en fin, enkel generisk implementering uten å miste noe i prosessen.

Implementeringen av alle disse eksemplene og kodebiter finner du i mitt GitHub-prosjekt - dette er et formørkelsesbasert prosjekt, så det skal være enkelt å importere og kjøre som det er.


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