Een aangepaste lente-annotatie voor een betere DAO

1. Overzicht

In deze tutorial zullen we implementeren een aangepaste Spring-annotatie met een bean-postprocessor.

Dus hoe helpt dit? Simpel gezegd - we kunnen dezelfde boon hergebruiken in plaats van meerdere, vergelijkbare bonen van hetzelfde type te moeten maken.

We doen dat voor de DAO-implementaties in een eenvoudig project - we vervangen ze allemaal door een enkele, flexibele Generiek.

2. Maven

Wij hebben nodig lente-kern, lente-uit, en spring-context-ondersteuning JARs om dit te laten werken. We kunnen het gewoon aangeven spring-context-ondersteuning in onze pom.xml.

 org.springframework spring-context-support 5.2.2.RELEASE 

Als je voor een nieuwere versie van de Spring-afhankelijkheid wilt gaan, bekijk dan de maven-repository.

3. Nieuwe generieke DAO

De meeste Spring / JPA / Hibernate-implementaties gebruiken de standaard DAO - meestal één voor elke entiteit.

We gaan die oplossing vervangen door een Generiek; we gaan in plaats daarvan een aangepaste annotatieprocessor schrijven en die gebruiken Generiek implementatie:

3.1. Generieke DAO

openbare klasse Genericbao {private klasse entityClass; openbare Genericensional (Class entityClass) {this.entityClass = entityClass; } public List findAll () {// ...} public Optioneel persist (E toPersist) {// ...}} 

In een real-world scenario moet u natuurlijk een PersistenceContext aansluiten en daadwerkelijk de implementaties van deze methoden bieden. Voor nu - we zullen dit zo eenvoudig mogelijk maken.

Laten we nu een annotatie maken voor aangepaste injectie.

3.2. Toegang tot de gegevens

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

We gebruiken de bovenstaande annotatie om een Generiek als volgt:

@DataAccess (entity = Person.class) privé Genericbao personensional;

Misschien vragen sommigen van jullie: 'Hoe herkent Spring onze Toegang tot de gegevens annotatie? ”. Het is niet standaard.

Maar we zouden Spring kunnen vertellen om de annotatie te herkennen via een gewoonte BeanPostProcessor - laten we dit als volgende geïmplementeerd krijgen.

3.3. DataAccessAnnotationProcessor

@Component openbare klasse DataAccessAnnotationProcessor implementeert BeanPostProcessor {private ConfigurableListableBeanFactory configurableBeanFactory; @Autowired openbare DataAccessAnnotationProcessor (ConfigurableListableBeanFactory beanFactory) {this.configurableBeanFactory = beanFactory; } @Override openbaar object postProcessBeforeInitialization (Object bean, String beanName) gooit BeansException {this.scanDataAccessAnnotation (bean, beanName); terugkeer boon; } @Override openbaar object postProcessAfterInitialization (Object bean, String beanName) gooit BeansException {return bean; } beschermde ongeldige scanDataAccessAnnotation (Object bean, String beanName) {this.configureFieldInjection (bean); } private void configureFieldInjection (Object bean) {Class managedBeanClass = bean.getClass (); FieldCallback fieldCallback = nieuwe DataAccessFieldCallback (configurableBeanFactory, bean); ReflectionUtils.doWithFields (managedBeanClass, fieldCallback); }} 

Volgende - hier is de implementatie van het DataAccessFieldCallback we gebruikten zojuist:

3.4. DataAccessFieldCallback

openbare klasse DataAccessFieldCallback implementeert FieldCallback {privé statische Logger-logger = LoggerFactory.getLogger (DataAccessFieldCallback.class); privé statische int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; private static String ERROR_ENTITY_VALUE_NOT_SAME = "@DataAccess (entiteit)" + "waarde moet hetzelfde type hebben met geïnjecteerd generiek type."; private static String WARN_NON_GENERIC_VALUE = "@DataAccess annotatie toegewezen" + "aan onbewerkte (niet-generieke) declaratie. Dit maakt je code minder type-safe."; private static String ERROR_CREATE_INSTANCE = "Kan geen instantie van" + "type '{}' maken of het maken van een instantie is mislukt omdat: {}"; privé ConfigurableListableBeanFactory configurableBeanFactory; particuliere boon; openbare DataAccessFieldCallback (ConfigurableListableBeanFactory bf, Object bean) {configurableBeanFactory = bf; this.bean = bean; } @Override public void doWith (Field field) gooit IllegalArgumentException, IllegalAccessException {if (! Field.isAnnotationPresent (DataAccess.class)) {return; } ReflectionUtils.makeAccessible (veld); Typ fieldGenericType = field.getGenericType (); // Haal in dit voorbeeld het werkelijke type "GenericDAO" op. Class generic = field.getType (); Class class classValue = field.getDeclaredAnnotation (DataAccess.class) .entity (); if (genericTypeIsValid (classValue, fieldGenericType)) {String beanName = classValue.getSimpleName () + generic.getSimpleName (); Object beanInstance = getBeanInstance (beanName, generiek, classValue); field.set (bean, beanInstance);} else {throw new IllegalArgumentException (ERROR_ENTITY_VALUE_NOT_SAME) (}} Class clazz, Type field) {if (field instanceof ParameterizedType) {ParameterizedType parameterizedType = (ParameterizedType) veld; Type 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 ("Nieuwe DataAccess-bean aanmaken met de naam '{}'. ", beanName); Object toRegister = null; probeer {Constructor ctr = genericClass.getConstructor (Class.class); toRegister = ctr.newInstance (paramClass); } catch (uitzondering e) {logger.error (ERROR_CREATE_INSTANCE, genericClass.getTypeName (), e); gooi nieuwe RuntimeException (e); } daoInstance = configurableBeanFactory.initializeBean (toRegister, beanName); configurableBeanFactory.autowireBeanProperties (daoInstance, AUTOWIRE_MODE, true); configurableBeanFactory.registerSingleton (beanName, daoInstance); logger.info ("Bean met de naam '{}' is succesvol aangemaakt.", beanName); } anders {daoInstance = configurableBeanFactory.getBean (beanName); logger.info ("Bean met de naam '{}' bestaat al gebruikt als huidige bean-referentie.", beanName); } return daoInstance; }} 

Nu - dat is nogal een implementatie - maar het belangrijkste deel ervan is de doen met() methode:

GenericensionalInstance = configurableBeanFactory.initializeBean (beanToRegister, beanName); configurableBeanFactory.autowireBeanProperties (generiekbaoInstance, autowireMode, true); configurableBeanFactory.registerSingleton (beanName, genericbaoInstance); 

Dit zou Spring vertellen om een ​​bean te initialiseren op basis van het object dat tijdens runtime via de @Toegang tot de gegevens annotatie.

De beanName zal ervoor zorgen dat we een uniek exemplaar van de bean krijgen omdat we - in dit geval - een enkel object van willen maken Generiek afhankelijk van de entiteit die via de @Toegang tot de gegevens annotatie.

Laten we tot slot deze nieuwe bean-processor gebruiken in een Spring-configuratie.

3.5. CustomAnnotationConfiguration

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

Een ding dat hier belangrijk is, is dat de waarde van de @BuienRadarNL annotatie moet verwijzen naar het pakket waar onze aangepaste bean-postprocessor zich bevindt en ervoor zorgen dat deze tijdens runtime door Spring wordt gescand en automatisch wordt bedraad.

4. Testen van de nieuwe DAO

Laten we beginnen met een Spring-enabled test en twee eenvoudige voorbeeld entiteitsklassen hier - Persoon en Account.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (klassen = {CustomAnnotationConfiguration.class}) openbare klasse DataAccessAnnotationTest {@DataAccess (entity = Person.class) privé Genericão personGenericão; @DataAccess (entiteit = Account.class) privé Genericbao accountGenericdred; @DataAccess (entity = Person.class) privé Genericensional anotherPersonGenericdred; ...}

We injecteren een paar exemplaren van de Generiek met behulp van de Toegang tot de gegevens annotatie. Om te testen of de nieuwe bonen correct zijn geïnjecteerd, moeten we het volgende behandelen:

  1. Als de injectie succesvol is
  2. Als bean-instanties met dezelfde entiteit hetzelfde zijn
  3. Als de methoden in het Generiek eigenlijk werken zoals verwacht

Punt 1 wordt eigenlijk gedekt door Spring zelf - aangezien het raamwerk vrij vroeg een uitzondering gooit als een boon niet kan worden aangesloten.

Om punt 2 te testen, moeten we kijken naar de 2 instanties van de Generiek die beide de Persoon klasse:

@Test openbare leegte whenGenericbaoInjected_thenItIsSingleton () {assertThat (personGenericensional, niet (sameInstance (accountGenericensional))); assertThat (personGenericensional, niet (equalTo (accountGenericão))); assertThat (personGenericbao, sameInstance (anotherPersonGenericensional)); }

We willen niet personGenericensional gelijk zijn aan de accountGenericensional.

Maar we willen de personGenericensional en anotherPersonGenericensional om precies dezelfde instantie te zijn.

Om punt 3 te testen, testen we hier enkele eenvoudige persistentiegerelateerde logica:

@Test openbare leegte whenFindAll_thenMessagesIsCorrect () {personGenericensional.findAll (); assertThat (personGenericbao.getMessage (), is ("Zou een findAll-zoekopdracht van persoon maken")); accountGenericensional.findAll (); assertThat (accountGenericdred.getMessage (), is ("Zou findAll-query maken vanuit Account")); } @Test openbare leegte whenPersist_thenMessagesIsCorrect () {personGenericbao.persist (nieuwe persoon ()); assertThat (personGenericbao.getMessage (), is ("Zou blijvende query van persoon maken")); accountGenericbao.persist (nieuw account ()); assertThat (accountGenericbao.getMessage (), is ("Zou blijvende query van account maken")); } 

5. Conclusie

In dit artikel hebben we een zeer coole implementatie gedaan van een aangepaste annotatie in het voorjaar - samen met een BeanPostProcessor. Het algemene doel was om de meerdere DAO-implementaties die we gewoonlijk in onze persistentielaag hebben, te verwijderen en een mooie, eenvoudige generieke implementatie te gebruiken zonder iets in het proces te verliezen.

De implementatie van al deze voorbeelden en codefragmenten kan gevonden worden in mijn GitHub-project - dit is een op Eclipse gebaseerd project, dus het zou gemakkelijk moeten kunnen worden geïmporteerd en uitgevoerd zoals het is.


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