Design Patterns in the Spring Framework

1. Introduksjon

Designmønstre er en viktig del av programvareutvikling. Disse løsningene løser ikke bare tilbakevendende problemer, men hjelper også utviklere å forstå utformingen av et rammeverk ved å gjenkjenne vanlige mønstre.

I denne opplæringen vil vi se på fire av de vanligste designmønstrene som brukes i Spring Framework:

  1. Singleton mønster
  2. Factory Method mønster
  3. Fullmaktsmønster
  4. Malmønster

Vi vil også se på hvordan Spring bruker disse mønstrene for å redusere belastningen for utviklere og hjelpe brukerne med å raskt utføre kjedelige oppgaver.

2. Singleton Mønster

Singleton-mønsteret er en mekanisme som sikrer at det bare eksisterer en forekomst av et objekt per applikasjon. Dette mønsteret kan være nyttig når du administrerer delte ressurser eller leverer tverrgående tjenester, for eksempel logging.

2.1. Singleton Beans

Generelt er en singleton globalt unik for en applikasjon, men om våren er denne begrensningen avslappet. I stedet, Våren begrenser en singleton til ett objekt per Spring IoC-container. I praksis betyr dette at Spring bare vil lage en bønne for hver type per applikasjonskontekst.

Spring's tilnærming skiller seg fra den strenge definisjonen av en singleton siden et program kan ha mer enn en Spring container. Derfor, flere objekter av samme klasse kan eksistere i en enkelt applikasjon hvis vi har flere containere.

Som standard oppretter Spring alle bønner som singletoner.

2.2. Autowired singletons

For eksempel kan vi opprette to kontrollere innen en enkelt applikasjonskontekst og injisere en bønne av samme type i hver.

Først lager vi en BookRepository som klarer vår Bok domeneobjekter.

Deretter lager vi LibraryController, som bruker BookRepository for å returnere antall bøker i biblioteket:

@RestController public class LibraryController {@Autowired private BookRepository repository; @GetMapping ("/ count") offentlig Long findCount () {System.out.println (repository); returner repository.count (); }}

Til slutt lager vi en BookController, som fokuserer på Bok-spesifikke handlinger, for eksempel å finne en bok etter ID-en:

@RestController public class BookController {@Autowired private BookRepository repository; @GetMapping ("/ book / {id}") public Book findById (@PathVariable long id) {System.out.println (repository); return repository.findById (id) .get (); }}

Vi starter deretter dette programmet og utfører en GET on /telle og / bok / 1:

curl -X GET // localhost: 8080 / count curl -X GET // localhost: 8080 / book / 1

I applikasjonsutgangen ser vi at begge deler BookRepository objekter har samme objekt-ID:

[e-postbeskyttet] [e-postbeskyttet]

De BookRepository objekt-ID-er i LibraryController og BookController er de samme, og viser at Spring injiserte den samme bønnen i begge kontrollerne.

Vi kan opprette separate forekomster av BookRepository bønne ved å endre bønneomfanget fra singleton til prototype bruker @Omfang (ConfigurableBeanFactory.SCOPE_PROTOTYPE)kommentar.

Dette instruerer våren om å lage separate objekter for hver av dem BookRepository bønner det skaper. Derfor, hvis vi inspiserer objekt-ID-en til BookRepository i hver av våre kontrollere igjen, ser vi at de ikke lenger er de samme.

3. Fabrikkmetodemønster

Fabriksmetodemønsteret innebærer en fabrikklasse med en abstrakt metode for å lage ønsket objekt.

Ofte ønsker vi å lage forskjellige objekter basert på en bestemt kontekst.

For eksempel kan søknaden kreve et kjøretøyobjekt. I et nautisk miljø ønsker vi å lage båter, men i et romfartsmiljø ønsker vi å lage fly:

For å oppnå dette kan vi lage en fabrikkimplementering for hvert ønsket objekt og returnere ønsket objekt fra den konkrete fabrikkmetoden.

3.1. Søknadskontekst

Spring bruker denne teknikken som grunnlag for rammeverket for avhengighetsinjeksjon (DI).

Fundamentalt, Våren godbiteren bønnebeholder som en fabrikk som produserer bønner.

Dermed definerer våren BeanFactory grensesnitt som en abstraksjon av en bønnebeholder:

offentlig grensesnitt BeanFactory {getBean (Class requiredType); getBean (Class requiredType, Object ... args); getBean (strengnavn); // ...]

Hver av getBean metoder regnes som en fabrikkmetode, som returnerer en bønne som samsvarer med kriteriene som er gitt til metoden, som bønnens type og navn.

Våren strekker seg deretter BeanFactory med ApplicationContext grensesnitt, som introduserer ekstra applikasjonskonfigurasjon. Spring bruker denne konfigurasjonen til å starte en bønnebeholder basert på noen eksterne konfigurasjoner, for eksempel en XML-fil eller Java-merknader.

Bruker ApplicationContext klasseimplementeringer som AnnotationConfigApplicationContext, kan vi da lage bønner gjennom de forskjellige fabrikkmetodene arvet fra BeanFactory grensesnitt.

Først oppretter vi en enkel applikasjonskonfigurasjon:

@Configuration @ComponentScan (basePackageClasses = ApplicationConfig.class) offentlig klasse ApplicationConfig {}

Deretter lager vi en enkel klasse, Foo, som ikke godtar konstruktørargumenter:

@Komponent offentlig klasse Foo {}

Lag deretter en annen klasse, Bar, som godtar et enkelt konstruktørargument:

@Component @Scope (ConfigurableBeanFactory.SCOPE_PROTOTYPE) offentlig klasselinje {privat strengnavn; public Bar (String name) {this.name = name; } // Getter ...}

Til slutt lager vi våre bønner gjennom AnnotationConfigApplicationContext Implementering av ApplicationContext:

@Test offentlig ugyldig nårGetSimpleBean_thenReturnConstructedBean () {ApplicationContext context = new AnnotationConfigApplicationContext (ApplicationConfig.class); Foo foo = context.getBean (Foo.class); assertNotNull (foo); } @Test offentlig ugyldig nårGetPrototypeBean_thenReturnConstructedBean () {String expectedName = "Noe navn"; ApplicationContext context = new AnnotationConfigApplicationContext (ApplicationConfig.class); Bar bar = context.getBean (Bar.class, expectName); assertNotNull (bar); assertThat (bar.getName (), er (forventet navn)); }

Bruker getBean fabrikkmetoden, kan vi lage konfigurerte bønner ved å bruke bare klassetypen og - i tilfelle Bar - konstruktørparametere.

3.2. Ekstern konfigurasjon

Dette mønsteret er allsidig fordi vi kan endre applikasjonens atferd fullstendig basert på ekstern konfigurasjon.

Hvis vi ønsker å endre implementeringen av de autowired-objektene i applikasjonen, kan vi justere ApplicationContext implementering vi bruker.

For eksempel kan vi endre AnnotationConfigApplicationContext til en XML-basert konfigurasjonsklasse, for eksempel ClassPathXmlApplicationContext:

@Test offentlig ugyldighet gittXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean () {String expectNameName = "Noe navn"; ApplicationContext context = ny ClassPathXmlApplicationContext ("context.xml"); // Samme test som før ...}

4. Proxy-mønster

Fullmakter er et praktisk verktøy i vår digitale verden, og vi bruker dem veldig ofte utenfor programvare (for eksempel nettverksproxyer). I kode, proxy-mønsteret er en teknikk som lar ett objekt - proxyen - kontrollere tilgangen til et annet objekt - emnet eller tjenesten.

4.1. Transaksjoner

For å opprette en proxy oppretter vi et objekt som implementerer det samme grensesnittet som motivet vårt og inneholder en referanse til emnet.

Vi kan da bruke fullmakten i stedet for motivet.

På våren blir bønner proxiert for å kontrollere tilgangen til den underliggende bønnen. Vi ser denne tilnærmingen når vi bruker transaksjoner:

@Service public class BookManager {@Autowired private BookRepository repository; @ Transactional public Book create (String author) {System.out.println (repository.getClass (). GetName ()); return repository.create (forfatter); }}

I vår BookManager klasse, kommenterer vi skape metoden med @Transaksjonell kommentar. Denne kommentaren instruerer våren til atomisk å utføre vår skape metode. Uten fullmektig ville Spring ikke kunne kontrollere tilgangen til vår BookRepository bønne og sikre dens transaksjonelle konsistens.

4.2. CGLib fullmektiger

I stedet, Våren skaper en fullmektig som omslutter vår BookRepository bønne og instrumenter vår bønne for å utføre vår skape metode atomisk.

Når vi kaller vår BookManager # opprett metode, kan vi se utdataene:

com.baeldung.patterns.proxy.BookRepository $$ EnhancerBySpringCGLIB $$ 3dc2b55c

Vanligvis forventer vi å se en standard BookRepository objekt-ID; i stedet ser vi en EnhancerBySpringCGLIB objekt-ID.

Bak scenen, Våren har pakket vår BookRepository gjenstand inne som EnhancerBySpringCGLIB gjenstand. Våren styrer dermed tilgangen til vår BookRepository objekt (sikre transaksjonskonsistens).

Generelt bruker Spring to typer fullmakter:

  1. CGLib Proxies - Brukes når du proxyer klasser
  2. JDK Dynamic Proxies - Brukes når du proxyer grensesnitt

Mens vi brukte transaksjoner for å avsløre de underliggende fullmaktene, Spring vil bruke fullmakter for ethvert scenario der den må kontrollere tilgangen til en bønne.

5. Malmetodemønster

I mange rammer er en betydelig del av koden kjeleplatekode.

For eksempel, når du utfører en spørring i en database, må samme serie trinn være fullført:

  1. Opprett en forbindelse
  2. Utfør spørring
  3. Utfør opprydding
  4. Lukk forbindelsen

Disse trinnene er et ideelt scenario for malmetodemønsteret.

5.1. Maler og tilbakeringinger

Malmetodemønsteret er en teknikk som definerer trinnene som kreves for noen handlinger, implementerer kjeleplattetrinnene og lar de tilpassbare trinnene være abstrakte. Underklasser kan deretter implementere denne abstrakte klassen og gi en konkret implementering av de manglende trinnene.

Vi kan lage en mal når det gjelder databasespørringen:

offentlig abstrakt DatabaseQuery {public void execute () {Connection connection = createConnection (); executeQuery (tilkobling); closeConnection (tilkobling); } beskyttet tilkobling createConnection () {// Koble til database ...} beskyttet tomrom closeConnection (tilkoblingstilkobling) {// Lukk tilkobling ...} beskyttet abstrakt tomrom executeQuery (tilkoblingsforbindelse); }

Alternativt kan vi tilby det manglende trinnet ved å levere en tilbakeringingsmetode.

En tilbakekallingsmetode er en metode som lar motivet signalisere til klienten at en ønsket handling er fullført.

I noen tilfeller kan motivet bruke denne tilbakeringingen til å utføre handlinger - for eksempel kartleggingsresultater.

For eksempel i stedet for å ha en executeQuery metoden, kan vi levere henrette metode en spørringsstreng og en tilbakeringingsmetode for å håndtere resultatene.

Først oppretter vi tilbakekallingsmetoden som tar en Resultater objektet og kartlegger det til et objekt av typen T:

offentlig grensesnitt ResultsMapper {public T map (Results results); }

Så endrer vi vårt DatabaseQuery klasse for å bruke denne tilbakeringingen:

offentlig abstrakt DatabaseQuery {public T execute (String query, ResultsMapper mapper) {Connection connection = createConnection (); Resultatresultater = executeQuery (tilkobling, spørring); closeConnection (tilkobling); return mapper.map (resultater); ] beskyttet Resultater executeQuery (Tilkoblingstilkobling, strengforespørsel) {// Utfør spørring ...}}

Denne tilbakekallingsmekanismen er nettopp tilnærmingen som Spring bruker med JdbcTemplate klasse.

5.2. JdbcTemplate

De JdbcTemplate klasse gir spørsmål metode, som godtar et spørsmål String og ResultSetExtractor gjenstand:

public class JdbcTemplate {public T query (final String sql, final ResultSetExtractor rse) kaster DataAccessException {// Utfør spørring ...} // Andre metoder ...}

De ResultSetExtractor konverterer ResultatSett objekt - som representerer resultatet av spørringen - til et domeneobjekt av typen T:

@FunctionalInterface offentlig grensesnitt ResultSetExtractor {T extractData (ResultSet rs) kaster SQLException, DataAccessException; }

Våren reduserer kjeleplatekoden ytterligere ved å lage mer spesifikke tilbakekallingsgrensesnitt.

For eksempel RowMapper grensesnitt brukes til å konvertere en enkelt rad med SQL-data til et domeneobjekt av typen T.

@FunctionalInterface offentlig grensesnitt RowMapper {T mapRow (ResultSet rs, int rowNum) kaster SQLException; }

Å tilpasse RowMapper grensesnitt til forventet ResultSetExtractor, Våren skaper RowMapperResultSetExtractor klasse:

public class JdbcTemplate {public List query (String sql, RowMapper rowMapper) kaster DataAccessException {returresultat (spørring (sql, ny RowMapperResultSetExtractor (rowMapper))); } // Andre metoder ...}

I stedet for å gi logikk for å konvertere en helhet ResultatSett objekt, inkludert iterasjon over radene, kan vi gi logikk for hvordan du konverterer en enkelt rad:

offentlig klasse BookRowMapper implementerer RowMapper {@Override public Book mapRow (ResultSet rs, int rowNum) kaster SQLException {Book book = new Book (); book.setId (rs.getLong ("id")); book.setTitle (rs.getString ("tittel")); book.setAuthor (rs.getString ("forfatter")); returbok; }}

Med denne omformeren kan vi deretter søke på en database ved hjelp av JdbcTemplate og kartlegg hver resulterende rad:

JdbcTemplate mal = // lage mal ... template.query ("VELG * FRA bøker", ny BookRowMapper ());

Bortsett fra JDBC-databaseadministrasjon, bruker Spring også maler for:

  • Java Message Service (JMS)
  • Java Persistence API (JPA)
  • Dvalemodus (nå avviklet)
  • Transaksjoner

6. Konklusjon

I denne opplæringen så vi på fire av de vanligste designmønstrene som ble brukt i Spring Framework.

Vi undersøkte også hvordan Spring bruker disse mønstrene til å tilby rike funksjoner samtidig som byrden på utviklere reduseres.

Koden fra denne artikkelen finner du på GitHub.