En foråret brugerdefineret kommentar til en bedre DAO

1. Oversigt

I denne vejledning implementerer vi en brugerdefineret foråretotering med en bønne-postprocessor.

Så hvordan hjælper dette? Kort sagt - vi kan genbruge den samme bønne i stedet for at skulle oprette flere lignende bønner af samme type.

Vi gør det for DAO-implementeringerne i et simpelt projekt - erstatter dem alle med en enkelt, fleksibel GenericDao.

2. Maven

Vi behøver fjederkerne, spring-aopog spring-context-support JAR for at få dette til at fungere. Vi kan bare erklære spring-context-support i vores pom.xml.

 org.springframework spring-context-support 5.2.2.RELEASE 

Hvis du vil gå efter en nyere version af Spring-afhængighed - tjek maven-arkivet.

3. Ny generisk DAO

De fleste implementeringer i foråret / JPA / dvale bruger standard DAO - normalt en for hver enhed.

Vi skal erstatte den løsning med en GenericDao; vi skriver i stedet en brugerdefineret annoteringsprocessor og bruger den GenericDao implementering:

3.1. Generisk DAO

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

I et virkeligt verdensscenarie bliver du selvfølgelig nødt til at tilslutte en PersistenceContext og faktisk levere implementeringerne af disse metoder. For nu - vi gør dette så simpelt som muligt.

Lad os nu oprette en kommentar til tilpasset injektion.

3.2. Dataadgang

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

Vi bruger kommentaren ovenfor til at indsprøjte en GenericDao som følger:

@DataAccess (enhed = Person.class) privat GenericDao personDao;

Måske spørger nogle af jer: ”Hvordan genkender foråret vores DataAccess kommentar? ”. Det gør det ikke - ikke som standard.

Men vi kunne bede Spring om at genkende kommentaren via en brugerdefineret BeanPostProcessor - lad os få dette implementeret næste gang.

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 tomrum scanDataAccessAnnotation (Object bean, String beanName) {this.configureFieldInjection (bean); } privat ugyldighed configureFieldInjection (Object bean) {Class managedBeanClass = bean.getClass (); FieldCallback fieldCallback = ny DataAccessFieldCallback (konfigurerbarBeanFabrik, bønne); ReflectionUtils.doWithFields (managedBeanClass, fieldCallback); }} 

Næste - her er implementeringen af DataAccessFieldCallback vi har lige brugt:

3.4. DataAccessFieldCallback

offentlig klasse DataAccessFieldCallback implementerer FieldCallback {privat statisk loggerlogger = LoggerFactory.getLogger (DataAccessFieldCallback.class); privat statisk int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; privat statisk streng ERROR_ENTITY_VALUE_NOT_SAME = "@DataAccess (enhed)" + "-værdi skal have samme type med generisk type injiceret."; privat statisk streng WARN_NON_GENERIC_VALUE = "@DataAccess-kommentar tildelt" + "til rå (ikke-generisk) erklæring. Dette gør din kode mindre typesikker."; private static String ERROR_CREATE_INSTANCE = "Kan ikke oprette forekomst af" + "type '{}' eller oprettelse af forekomst mislykkedes, fordi: {}"; private ConfigurableListableBeanFactory configurableBeanFactory; private Objekt bønner; public DataAccessFieldCallback (ConfigurableListableBeanFactory bf, Object bean) {configurableBeanFactory = bf; this.bean = bønne; } @ Overstyr offentlig ugyldig doWith (felt felt) kaster IllegalArgumentException, IllegalAccessException {if (! Field.isAnnotationPresent (DataAccess.class)) {return; } ReflectionUtils.makeAccessible (felt); Skriv fieldGenericType = field.getGenericType (); // I dette eksempel skal du få den aktuelle "GenericDAO 'type. 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 {smid 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 ); returner sand;}} offentligt objekt getBeanInstance (String beanName, Class genericClass, Class paramClass) {Object daoInstance = null; if (! configurableBeanFactory.containsBean (beanName)) {logger.info ("Oprettelse af ny DataAccess-bønne med navnet '{}'. ", beanName); Objekt toRegister = null; prøv {Constructor ctr = genericClass.getConstructor (Class.class); toRegister = ctr.newInstance (paramClass); } fange (Undtagelse e) {logger.error (ERROR_CREATE_INSTANCE, genericClass.getTypeName (), e); smid ny RuntimeException (e); } daoInstance = configurableBeanFactory.initializeBean (toRegister, beanName); configurableBeanFactory.autowireBeanProperties (daoInstance, AUTOWIRE_MODE, true); configurableBeanFactory.registerSingleton (beanName, daoInstance); logger.info ("Bean med navnet '{}' oprettet med succes.", beanName); } andet {daoInstance = configurableBeanFactory.getBean (beanName); logger.info ("Bean med navnet '{}' findes allerede brugt som aktuel bønnereference.", beanName); } returner daoInstance; }} 

Nu - det er en ganske implementering - men den vigtigste del af det er gøre med() metode:

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

Dette vil bede Spring om at initialisere en bønne baseret på det objekt, der injiceres ved kørsel via @DataAccess kommentar.

Det beanName vil sørge for, at vi får en unik forekomst af bønnen, fordi vi - i dette tilfælde - ønsker at oprette et enkelt objekt af GenericDao afhængigt af den enhed, der injiceres via @DataAccess kommentar.

Lad os endelig bruge denne nye bønneprocessor i en Spring-konfiguration næste.

3.5. CustomAnnotationConfiguration

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

En ting, der er vigtig her, er, at værdien af @ComponentScan annotering skal pege på pakken, hvor vores brugerdefinerede bønnepostprocessor er placeret, og sørg for, at den er scannet og automatisk kablet af Spring ved kørsel.

4. Test af den nye DAO

Lad os starte med en Spring-aktiveret test og to enkle eksempler på enhedsklasser her - Person og Konto.

@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) privat GenericDao anotherPersonGenericDao; ...}

Vi injicerer et par tilfælde af GenericDao ved hjælp af DataAccess kommentar. For at teste, at de nye bønner er injiceret korrekt, skal vi dække:

  1. Hvis injektionen er vellykket
  2. Hvis bønneinstanser med den samme enhed er de samme
  3. Hvis metoderne i GenericDao faktisk fungerer som forventet

Punkt 1 er faktisk dækket af foråret selv - da rammen kaster en undtagelse ganske tidligt, hvis en bønne ikke kan kobles ind.

For at teste punkt 2 skal vi se på de to forekomster af GenericDao at begge bruger Person klasse:

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

Vi vil ikke personGenericDao at være lig med accountGenericDao.

Men vi ønsker personGenericDao og en andenPersonGenericDao at være nøjagtig den samme forekomst.

For at teste punkt 3 tester vi bare nogle enkle persistensrelaterede logikker her:

@Test offentlig ugyldig nårFindAll_thenMessagesIsCorrect () {personGenericDao.findAll (); assertThat (personGenericDao.getMessage (), er ("Ville skabe findAll forespørgsel fra person")); accountGenericDao.findAll (); assertThat (accountGenericDao.getMessage (), er ("Ville skabe findAll forespørgsel fra konto")); } @Test offentlig ugyldig nårPersist_thenMessagesIsCorrect () {personGenericDao.persist (ny person ()); assertThat (personGenericDao.getMessage (), er ("Vil oprette vedvarende forespørgsel fra person")); accountGenericDao.persist (ny konto ()); assertThat (accountGenericDao.getMessage (), er ("Ville oprette vedvarende forespørgsel fra konto")); } 

5. Konklusion

I denne artikel gennemførte vi en meget cool implementering af en brugerdefineret kommentar i foråret - sammen med en BeanPostProcessor. Det overordnede mål var at slippe af med de flere DAO-implementeringer, vi normalt har i vores persistenslag og bruge en flot, enkel generisk implementering uden at miste noget i processen.

Implementeringen af ​​alle disse eksempler og kodestykker kan findes i mit GitHub-projekt - dette er et Eclipse-baseret projekt, så det skal være let at importere og køre som det er.


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