Design Patterns in the Spring Framework

1. Inleiding

Ontwerppatronen zijn een essentieel onderdeel van softwareontwikkeling. Deze oplossingen lossen niet alleen terugkerende problemen op, maar helpen ontwikkelaars ook het ontwerp van een raamwerk te begrijpen door gemeenschappelijke patronen te herkennen.

In deze zelfstudie bekijken we vier van de meest voorkomende ontwerppatronen die in het Spring Framework worden gebruikt:

  1. Singleton patroon
  2. Fabrieksmethode patroon
  3. Proxy patroon
  4. Sjabloon patroon

We zullen ook bekijken hoe Spring deze patronen gebruikt om de last voor ontwikkelaars te verminderen en gebruikers te helpen snel vervelende taken uit te voeren.

2. Singleton-patroon

Het singleton-patroon is een mechanisme dat ervoor zorgt dat er slechts één exemplaar van een object per toepassing bestaat. Dit patroon kan handig zijn bij het beheren van gedeelde bronnen of het leveren van transversale services, zoals logboekregistratie.

2.1. Singleton Bonen

Over het algemeen is een singleton wereldwijd uniek voor een toepassing, maar in het voorjaar wordt deze beperking versoepeld. In plaats daarvan, De lente beperkt een singleton van één object per Spring IoC-container. In de praktijk betekent dit dat Spring slechts één boon voor elk type per toepassingscontext zal maken.

De aanpak van Spring verschilt van de strikte definitie van een singleton, aangezien een applicatie meer dan één Spring-container kan hebben. Daarom meerdere objecten van dezelfde klasse kunnen in een enkele applicatie voorkomen als we meerdere containers hebben.

Spring maakt standaard alle bonen als singletons.

2.2. Singletons met automatische bedrading

We kunnen bijvoorbeeld twee controllers maken binnen een enkele toepassingscontext en in elk een bean van hetzelfde type injecteren.

Eerst maken we een BookRepository dat beheert onze Boek domein objecten.

Vervolgens maken we LibraryController, die de BookRepository om het aantal boeken in de bibliotheek terug te geven:

@RestController openbare klasse LibraryController {@Autowired privé BookRepository-repository; @GetMapping ("/ count") openbaar Lang findCount () {System.out.println (repository); retourneer repository.count (); }}

Ten slotte maken we een BookController, die zich richt op Boek-specifieke acties, zoals het vinden van een boek op ID:

@RestController openbare klasse BookController {@Autowired privé BookRepository-repository; @GetMapping ("/ book / {id}") openbaar boek findById (@PathVariable lange id) {System.out.println (repository); retourneer repository.findById (id) .get (); }}

We starten dan deze applicatie en voeren een GET uit uit / tel en / boek / 1:

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

In de applicatie-output zien we dat beide BookRepository objecten hebben dezelfde object-ID:

[e-mail beschermd] [e-mail beschermd]

De BookRepository object-ID's in het LibraryController en BookController zijn hetzelfde, wat bewijst dat Spring dezelfde boon in beide controllers heeft geïnjecteerd.

We kunnen afzonderlijke instanties van het BookRepository bean door de bonen scope te veranderen van singleton naar voorlopig ontwerp de ... gebruiken @Bereik (ConfigurableBeanFactory.SCOPE_PROTOTYPE)annotatie.

Hierdoor wordt Spring geïnstrueerd om afzonderlijke objecten te maken voor elk van de BookRepository bonen die het maakt. Daarom, als we de object-ID van het BookRepository in elk van onze controllers zien we weer dat ze niet meer hetzelfde zijn.

3. Fabrieksmethode patroon

Het patroon van de fabrieksmethode omvat een fabrieksklasse met een abstracte methode om het gewenste object te maken.

Vaak willen we verschillende objecten maken op basis van een bepaalde context.

Onze applicatie kan bijvoorbeeld een voertuigobject vereisen. In een nautische omgeving willen we boten maken, maar in een lucht- en ruimtevaartomgeving willen we vliegtuigen maken:

Om dit te bereiken, kunnen we voor elk gewenst object een fabrieksimplementatie maken en het gewenste object uit de betonfabriekmethode retourneren.

3.1. Toepassingscontext

Spring gebruikt deze techniek aan de basis van zijn Dependency Injection (DI) -raamwerk.

Fundamenteel, Lente traktatieseen bonenreservoir als een fabriek die bonen produceert.

Zo definieert Spring de BeanFactory interface als abstractie van een bonenreservoir:

openbare interface BeanFactory {getBean (Class requiredType); getBean (Klasse requiredType, Object ... args); getBean (String naam); // ...]

Elk van de getBean methoden wordt beschouwd als een fabrieksmethode, die een boon retourneert die overeenkomt met de criteria die aan de methode zijn verstrekt, zoals het type en de naam van de boon.

De lente strekt zich dan uit BeanFactory met de ApplicationContext interface, die aanvullende applicatieconfiguratie introduceert. Spring gebruikt deze configuratie om een ​​bonencontainer op te starten op basis van een externe configuratie, zoals een XML-bestand of Java-annotaties.

De ... gebruiken ApplicationContext class implementaties zoals AnnotationConfigApplicationContext, kunnen we dan bonen maken via de verschillende fabrieksmethoden die we hebben geërfd van de BeanFactory koppel.

Eerst maken we een eenvoudige applicatieconfiguratie:

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

Vervolgens maken we een eenvoudige klasse, Foo, die geen constructorargumenten accepteert:

@Component openbare klasse Foo {}

Maak vervolgens nog een klas, Bar, dat een enkel constructorargument accepteert:

@Component @Scope (ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Bar {private String naam; openbare balk (tekenreeksnaam) {this.name = naam; } // Getter ...}

Ten slotte maken we onze bonen via de AnnotationConfigApplicationContext invoer van ApplicationContext:

@Test openbare leegte whenGetSimpleBean_thenReturnConstructedBean () {ApplicationContext context = nieuwe AnnotationConfigApplicationContext (ApplicationConfig.class); Foo foo = context.getBean (Foo.class); assertNotNull (foo); } @Test public void whenGetPrototypeBean_thenReturnConstructedBean () {String verwachtNaam = "Een naam"; ApplicationContext context = nieuwe AnnotationConfigApplicationContext (ApplicationConfig.class); Bar bar = context.getBean (Bar.class, verwachteNaam); assertNotNull (balk); assertThat (bar.getName (), is (verwachteNaam)); }

De ... gebruiken getBean factory-methode, kunnen we geconfigureerde bonen maken met alleen het klassetype en - in het geval van Bar - constructorparameters.

3.2. Externe configuratie

Dit patroon is veelzijdig omdat we kunnen het gedrag van de applicatie volledig veranderen op basis van externe configuratie.

Als we de implementatie van de autowired-objecten in de applicatie willen wijzigen, kunnen we het ApplicationContext implementatie die we gebruiken.

We kunnen bijvoorbeeld de AnnotationConfigApplicationContext naar een XML-gebaseerde configuratieklasse, zoals ClassPathXmlApplicationContext:

@Test openbare leegte gegevenXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean () {String verwachtNaam = "Een naam"; ApplicationContext context = nieuwe ClassPathXmlApplicationContext ("context.xml"); // Zelfde test als voorheen ...}

4. Proxypatroon

Proxy's zijn een handig hulpmiddel in onze digitale wereld en we gebruiken ze heel vaak buiten de software om (zoals netwerkproxy's). In code, het proxy-patroon is een techniek waarmee het ene object - de proxy - de toegang tot een ander object - het subject of de dienst - kan regelen.

4.1. Transacties

Om een ​​proxy te maken, maken we een object dat dezelfde interface implementeert als ons onderwerp en een verwijzing naar het onderwerp bevat.

We kunnen dan de proxy gebruiken in plaats van het onderwerp.

In het voorjaar worden bonen via proxy's beheerd om de toegang tot de onderliggende boon te regelen. We zien deze benadering bij het gebruik van transacties:

@Service openbare klasse BookManager {@Autowired privé BookRepository-repository; @Transactional openbaar boek maken (tekenreeksauteur) {System.out.println (repository.getClass (). GetName ()); return repository.create (auteur); }}

In onze BookManager klasse, we annoteren de creëren methode met de @Transactional annotatie. Deze annotatie instrueert Spring om onze creëren methode. Zonder een proxy zou Spring de toegang tot ons BookRepository bonen en zorg voor de transactionele consistentie.

4.2. CGLib-proxy's

In plaats daarvan, De lente creëert een proxy die ons omhult BookRepository Boon en instrumenten onze boon om onze uit te voeren creëren methode atomair.

Als we ons bellen BookManager # create methode, kunnen we de output zien:

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

Meestal verwachten we een standaard te zien BookRepository object-ID; in plaats daarvan zien we een EnhancerBySpringCGLIB object-ID.

Achter de schermen, De lente heeft ons ingepakt BookRepository object binnen als EnhancerBySpringCGLIB voorwerp. De lente controleert dus de toegang tot onze BookRepository object (zorgen voor transactionele consistentie).

Over het algemeen gebruikt Spring twee soorten proxy's:

  1. CGLib-proxy's - Wordt gebruikt bij het proxy-klassen
  2. JDK dynamische proxy's - Gebruikt bij proxy-interfaces

Hoewel we transacties gebruikten om de onderliggende proxy's bloot te leggen, Spring gebruikt proxy's voor elk scenario waarin het de toegang tot een bean moet controleren.

5. Template Method Pattern

In veel frameworks is een aanzienlijk deel van de code standaardcode.

Als u bijvoorbeeld een query op een database uitvoert, moet u dezelfde reeks stappen uitvoeren:

  1. Breng een verbinding tot stand
  2. Voer de query uit
  3. Voer een opruimactie uit
  4. Sluit de verbinding

Deze stappen zijn een ideaal scenario voor het patroon van de sjabloonmethode.

5.1. Sjablonen en callbacks

Het sjabloonmethodepatroon is een techniek die de stappen definieert die nodig zijn voor een bepaalde actie, waarbij de standaardstappen worden geïmplementeerd en de aanpasbare stappen abstract blijven. Subklassen kunnen deze abstracte klasse vervolgens implementeren en een concrete implementatie bieden voor de ontbrekende stappen.

We kunnen een sjabloon maken in het geval van onze database-query:

openbare samenvatting DatabaseQuery {public void execute () {Connection connection = createConnection (); executeQuery (verbinding); closeConnection (verbinding); } protected Connection createConnection () {// Verbinden met database ...} protected void closeConnection (Connection verbinding) {// Close connection ...} protected abstract void executeQuery (Connection verbinding); }

Als alternatief kunnen we de ontbrekende stap voorzien door een callback-methode op te geven.

Een callback-methode is een methode waarmee het onderwerp aan de klant kan aangeven dat een gewenste actie is voltooid.

In sommige gevallen kan het onderwerp deze callback gebruiken om acties uit te voeren, zoals het in kaart brengen van resultaten.

Bijvoorbeeld, in plaats van een executeQuery methode, kunnen we de uitvoeren method een querytekenreeks en een callbackmethode om de resultaten af ​​te handelen.

Eerst maken we de callback-methode waarvoor een Resultaten object en wijst het toe aan een object van het type T:

openbare interface ResultsMapper {openbare T-kaart (resultatenresultaten); }

Dan veranderen we onze DatabaseQuery class om deze callback te gebruiken:

openbare samenvatting DatabaseQuery {openbare T-uitvoering (String-query, ResultsMapper-mapper) {Connection connection = createConnection (); Resultaten results = executeQuery (verbinding, query); closeConnection (verbinding); return mapper.map (resultaten); ] beschermd Resultaten executeQuery (Verbindingsverbinding, String-query) {// Voer query uit ...}}

Dit callback-mechanisme is precies de benadering die Spring gebruikt met de JdbcTemplate klasse.

5.2. JdbcTemplate

De JdbcTemplate klasse biedt de vraag methode, die een zoekopdracht accepteert Draad en ResultSetExtractor voorwerp:

openbare klasse JdbcTemplate {openbare T-query (laatste String sql, laatste ResultSetExtractor rse) gooit DataAccessException {// Query uitvoeren ...} // Andere methoden ...}

De ResultSetExtractor converteert het ResultSet object - dat het resultaat van de query vertegenwoordigt - in een domeinobject van het type T:

@FunctionalInterface openbare interface ResultSetExtractor {T extractData (ResultSet rs) gooit SQLException, DataAccessException; }

Spring reduceert de standaardcode verder door meer specifieke callback-interfaces te creëren.

Bijvoorbeeld de RowMapper interface wordt gebruikt om een ​​enkele rij SQL-gegevens om te zetten in een domeinobject van het type T.

@FunctionalInterface openbare interface RowMapper {T mapRow (ResultSet rs, int rowNum) gooit SQLException; }

Om het RowMapper interface naar het verwachte ResultSetExtractor, Spring creëert de RowMapperResultSetExtractor klasse:

openbare klasse JdbcTemplate {openbare lijstquery (String sql, RowMapper rowMapper) genereert DataAccessException {retourresultaat (query (sql, nieuwe RowMapperResultSetExtractor (rowMapper))); } // Andere methodes... }

In plaats van logica te bieden voor het omzetten van een geheel ResultSet object, inclusief iteratie over de rijen, kunnen we logica bieden voor het converteren van een enkele rij:

public class BookRowMapper implementeert RowMapper {@Override public Book mapRow (ResultSet rs, int rowNum) gooit SQLException {Book book = new Book (); book.setId (rs.getLong ("id")); book.setTitle (rs.getString ("title")); book.setAuthor (rs.getString ("auteur")); retourboek; }}

Met deze converter kunnen we vervolgens een database opvragen met behulp van de JdbcTemplate en breng elke resulterende rij in kaart:

JdbcTemplate-sjabloon = // maak sjabloon ... template.query ("SELECTEER * UIT boeken", nieuwe BookRowMapper ());

Naast JDBC-databasebeheer gebruikt Spring ook sjablonen voor:

  • Java Message Service (JMS)
  • Java Persistence API (JPA)
  • Slaapstand (nu verouderd)
  • Transacties

6. Conclusie

In deze tutorial hebben we gekeken naar vier van de meest voorkomende ontwerppatronen die in het Spring Framework worden toegepast.

We hebben ook onderzocht hoe Spring deze patronen gebruikt om uitgebreide functies te bieden en tegelijkertijd de last voor ontwikkelaars te verminderen.

De code uit dit artikel is te vinden op GitHub.