Design mønstre i forårets ramme

1. Introduktion

Designmønstre er en vigtig del af softwareudvikling. Disse løsninger løser ikke kun tilbagevendende problemer, men hjælper også udviklere med at forstå designet af en ramme ved at genkende fælles mønstre.

I denne vejledning ser vi på fire af de mest almindelige designmønstre, der bruges i Spring Framework:

  1. Singleton mønster
  2. Fabriksmetode mønster
  3. Proxy mønster
  4. Skabelon mønster

Vi vil også se på, hvordan Spring bruger disse mønstre til at reducere byrden for udviklere og hjælpe brugerne med hurtigt at udføre kedelige opgaver.

2. Singleton mønster

Singleton-mønsteret er en mekanisme, der sikrer, at der kun findes en forekomst af et objekt pr. Applikation. Dette mønster kan være nyttigt, når du administrerer delte ressourcer eller leverer tværgående tjenester såsom logning.

2.1. Singleton Beans

Generelt er en singleton globalt unik til en applikation, men i foråret er denne begrænsning afslappet. I stedet, Forår begrænser en singleton til et objekt pr. Spring IoC-container. I praksis betyder det, at Spring kun opretter en bønne for hver type pr. Applikationskontekst.

Spring's tilgang adskiller sig fra den strenge definition af en singleton, da en applikation kan have mere end en Spring container. Derfor, flere objekter af samme klasse kan eksistere i en enkelt applikation, hvis vi har flere containere.

Som standard opretter Spring alle bønner som singletoner.

2.2. Autowired singletons

For eksempel kan vi oprette to controllere inden for en enkelt applikationskontekst og injicere en bønne af samme type i hver.

Først opretter vi en BookRepository der styrer vores Bestil domæneobjekter.

Dernæst opretter vi LibraryController, der bruger BookRepository for at returnere antallet af bøger i biblioteket:

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

Endelig opretter vi en BookController, som fokuserer på Bestil-specifikke handlinger, såsom at finde en bog efter dens id:

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

Vi starter derefter denne applikation og udfører en GET on /tælle og / bog / 1:

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

I applikationsoutput ser vi, at begge BookRepository objekter har det samme objekt-id:

[email protected] [email protected]

Det BookRepository objekt-id'er i LibraryController og BookController er de samme, hvilket beviser, at Spring injicerede den samme bønne i begge controllere.

Vi kan oprette separate forekomster af BookRepository bønne ved at ændre bønnens omfang fra singleton til prototype bruger @Omfang (ConfigurableBeanFactory.SCOPE_PROTOTYPE)kommentar.

Dette instruerer Spring om at oprette separate objekter til hver af BookRepository bønner det skaber. Derfor, hvis vi inspicerer objekt-id'et for BookRepository i hver af vores controllere igen ser vi, at de ikke længere er de samme.

3. Fabriksmetodemønster

Fabriksmetodemønsteret indebærer en fabriksklasse med en abstrakt metode til oprettelse af det ønskede objekt.

Ofte vil vi oprette forskellige objekter baseret på en bestemt kontekst.

For eksempel kan vores ansøgning kræve et køretøjsobjekt. I et nautisk miljø ønsker vi at skabe både, men i et rumfartsmiljø vil vi skabe fly:

For at opnå dette kan vi oprette en fabriksimplementering for hvert ønsket objekt og returnere det ønskede objekt fra den konkrete fabriksmetode.

3.1. Applikationskontekst

Spring bruger denne teknik i roden af ​​dets DI-ramme (Dependency Injection).

Grundlæggende Forår godbidderen bønnebeholder som en fabrik, der producerer bønner.

Således definerer foråret BeanFactory interface som en abstraktion af en bønnebeholder:

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

Hver af de getBean metoder betragtes som en fabriksmetode, som returnerer en bønne, der matcher kriterierne, der er leveret til metoden, ligesom bønnens type og navn.

Foråret strækker sig derefter BeanFactory med ApplicationContext interface, der introducerer yderligere applikationskonfiguration. Spring bruger denne konfiguration til at starte en bønnebeholder baseret på en vis ekstern konfiguration, såsom en XML-fil eller Java-annoteringer.

Bruger ApplicationContext klasseimplementeringer som AnnotationConfigApplicationContext, kan vi derefter oprette bønner gennem de forskellige fabriksmetoder, der er arvet fra BeanFactory interface.

Først opretter vi en simpel applikationskonfiguration:

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

Dernæst opretter vi en simpel klasse, Foo, der accepterer ingen konstruktørargumenter:

@Komponent offentlig klasse Foo {}

Opret derefter en anden klasse, Bar, der accepterer et enkelt konstruktørargument:

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

Endelig opretter vi vores bønner gennem AnnotationConfigApplicationContext gennemførelse af 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 expectName = "Noget navn"; ApplicationContext context = ny AnnotationConfigApplicationContext (ApplicationConfig.class); Barbjælke = context.getBean (Bar.class, forventet navn); assertNotNull (bar); assertThat (bar.getName (), er (forventet navn)); }

Bruger getBean fabriksmetoden kan vi oprette konfigurerede bønner ved hjælp af bare klassetypen og - i tilfælde af Bar - konstruktorparametre.

3.2. Ekstern konfiguration

Dette mønster er alsidigt fordi Vi kan ændre applikationens adfærd fuldstændigt baseret på ekstern konfiguration.

Hvis vi ønsker at ændre implementeringen af ​​de autowired-objekter i applikationen, kan vi justere ApplicationContext implementering, vi bruger.

For eksempel kan vi ændre AnnotationConfigApplicationContext til en XML-baseret konfigurationsklasse, f.eks ClassPathXmlApplicationContext:

@Test offentlig ugyldighed givetXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean () {String expectedName = "Noget navn"; ApplicationContext context = ny ClassPathXmlApplicationContext ("context.xml"); // Samme test som før ...}

4. Proxy-mønster

Proxyer er et praktisk værktøj i vores digitale verden, og vi bruger dem meget ofte uden for software (såsom netværksproxyer). I kode, proxymønsteret er en teknik, der tillader et objekt - proxyen - at kontrollere adgangen til et andet objekt - emnet eller tjenesten.

4.1. Transaktioner

For at oprette en proxy opretter vi et objekt, der implementerer den samme grænseflade som vores emne og indeholder en henvisning til emnet.

Vi kan derefter bruge proxyen i stedet for emnet.

Om foråret proximeres bønner for at kontrollere adgangen til den underliggende bønne. Vi ser denne tilgang, når vi bruger transaktioner:

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

I vores BookManager klasse, kommenterer vi skab metode med @Transaktionel kommentar. Denne kommentar instruerer foråret at atomisk udføre vores skab metode. Uden fuldmagt ville Spring ikke være i stand til at kontrollere adgangen til vores BookRepository bønne og sikre dens transaktionelle konsistens.

4.2. CGLib fuldmægtige

I stedet, Spring skaber en proxy, der omslutter vores BookRepository bønne og instrumenter vores bønne til at udføre vores skab metode atomisk.

Når vi kalder vores BookManager # Opret metode, kan vi se output:

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

Vi forventer typisk at se en standard BookRepository objekt-ID; i stedet ser vi en EnhancerBySpringCGLIB objekt-id.

Bag scenen, Foråret har pakket vores BookRepository objekt indeni som EnhancerBySpringCGLIB objekt. Foråret styrer således adgangen til vores BookRepository objekt (sikrer transaktionel konsistens).

Generelt bruger Spring to typer fuldmagter:

  1. CGLib Proxies - Bruges til proxy-klasser
  2. JDK Dynamic Proxies - Bruges til proxy-interface

Mens vi brugte transaktioner til at eksponere de underliggende fuldmagter, Spring bruger fuldmagter til ethvert scenarie, hvor det skal kontrollere adgangen til en bønne.

5. Skabelonmetodemønster

I mange rammer er en betydelig del af koden kedelpladekode.

For eksempel, når du udfører en forespørgsel i en database, skal den samme række trin udføres:

  1. Opret forbindelse
  2. Udfør forespørgsel
  3. Udfør oprydning
  4. Luk forbindelsen

Disse trin er et ideelt scenario for skabelonmetodemønsteret.

5.1. Skabeloner og tilbagekald

Skabelonmetodemønsteret er en teknik, der definerer de nødvendige trin til en eller anden handling, implementering af kedelpladetrinene og efterlader de tilpassede trin som abstrakte. Underklasser kan derefter implementere denne abstrakte klasse og give en konkret implementering af de manglende trin.

Vi kan oprette en skabelon i tilfælde af vores databaseforespørgsel:

offentlig abstrakt DatabaseQuery {public void execute () {Connection connection = createConnection (); executeQuery (forbindelse); closeConnection (forbindelse); } beskyttet forbindelse createConnection () {// Opret forbindelse til database ...} beskyttet ugyldig closeConnection (Forbindelsesforbindelse) {// Luk forbindelse ...} beskyttet abstrakt ugyldig executeQuery (Forbindelsesforbindelse); }

Alternativt kan vi give det manglende trin ved at levere en tilbagekaldsmetode.

En tilbagekaldsmetode er en metode, der gør det muligt for motivet at signalere til klienten, at en eller anden ønsket handling er afsluttet.

I nogle tilfælde kan motivet bruge denne tilbagekaldelse til at udføre handlinger - såsom kortlægningsresultater.

For eksempel i stedet for at have en executeQuery metode, kan vi levere udføre metode en forespørgselsstreng og en tilbagekaldsmetode til at håndtere resultaterne.

Først opretter vi tilbagekaldsmetoden, der tager en Resultater objekt og kortlægger det til et objekt af typen T:

offentlig grænseflade ResultsMapper {public T map (Results results); }

Så ændrer vi vores DatabaseQuery klasse til at udnytte denne tilbagekaldelse:

offentlig abstrakt DatabaseQuery {public T execute (String query, ResultsMapper mapper) {Connection connection = createConnection (); Resultater resultater = executeQuery (forbindelse, forespørgsel); closeConnection (forbindelse); returner mapper.map (resultater); ] beskyttede resultater executeQuery (Forbindelsesforbindelse, strengforespørgsel) {// Udfør forespørgsel ...}}

Denne tilbagekaldelsesmekanisme er netop den tilgang, som Spring bruger med JdbcTemplate klasse.

5.2. JdbcTemplate

Det JdbcTemplate klasse giver forespørgsel metode, som accepterer en forespørgsel Snor og ResultSetExtractor objekt:

offentlig klasse JdbcTemplate {offentlig T-forespørgsel (endelig streng SQL, endelig ResultSetExtractor rse) kaster DataAccessException {// Udfør forespørgsel ...} // Andre metoder ...}

Det ResultSetExtractor konverterer ResultSet objekt - der repræsenterer resultatet af forespørgslen - til et domæneobjekt af typen T:

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

Spring reducerer kogepladekoden yderligere ved at oprette mere specifikke callback-grænseflader.

F.eks RowMapper interface bruges til at konvertere en enkelt række SQL-data til et domæneobjekt af typen T.

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

At tilpasse RowMapper interface til det forventede ResultSetExtractor, Foråret skaber RowMapperResultSetExtractor klasse:

offentlig klasse JdbcTemplate {offentlig listeforespørgsel (streng SQL, RowMapper rowMapper) kaster DataAccessException {returneresultat (forespørgsel (sql, ny RowMapperResultSetExtractor (rowMapper))); } // Andre metoder ...}

I stedet for at give logik til konvertering af en hel ResultSet objekt, inklusive iteration over rækkerne, kan vi give logik til, hvordan man konverterer en enkelt række:

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 ("titel")); book.setAuthor (rs.getString ("forfatter")); returbog; }}

Med denne konverter kan vi derefter spørge en database ved hjælp af JdbcTemplate og kortlæg hver resulterende række:

JdbcTemplate-skabelon = // opret skabelon ... template.query ("VÆLG * FRA bøger", ny BookRowMapper ());

Bortset fra JDBC-databasestyring bruger Spring også skabeloner til:

  • Java Message Service (JMS)
  • Java Persistence API (JPA)
  • Dvaletilstand (nu udfaset)
  • Transaktioner

6. Konklusion

I denne vejledning kiggede vi på fire af de mest almindelige designmønstre anvendt i Spring Framework.

Vi undersøgte også, hvordan Spring bruger disse mønstre til at levere rige funktioner og samtidig mindske byrden for udviklere.

Koden fra denne artikel kan findes på GitHub.