Spring

Z FI WIKI
Přejít na: navigace, hledání


Úvod

Spring (web site,wikipedia) je platforma pro tvorbu enterprise aplikací na platformě Java.

Spring je alternativou k JavaEE, ale je jednodušší na použití. Není nutné využít všechno co nabízí najednou, lze si vybírat pouze požadovanou funkcionalitu. Také na rozdíl od JavaEE nevynucuje závislost na konkrétních třídách.

Něco o standardech

Existují různé druhy standardů:

  • státní normy vynucené zákonem, např. česká ČSN, německá DIN nebo americká ANSI
  • mezinárodní normy vynucované mezivládními dohodami, vydávané organizacemi jako ISO nebo ITU
  • de facto standardy, třeba TCP/IP používané místo ISO/OSI protokolů
  • internetové standardy jsou doporučení vydávaná organizacemi typu W3C nebo IETF (vydává RFC)
  • různé další standardy, vydávané zájmovými sdruženími nebo firmami, např. OASIS, v případě Javy procesem zvaným JCP (Java Community Process) končící vydáním hotového JSR (Java Specification Request)

Z právního pohledu Java SE a Java EE jsou copyrightované obchodní značky, a jejich implementace (SUN/Oracle, IBM a další) musí projít testem kompatibility (TCK) aby mohly používat název a logo Java.

JavaEE není tedy závazná norma, ale specifikace vydaná zájmovým sdružením firem, povětšinou firem prodávajících JavaEE servery.

Historicky se několikrát ukázalo, že některá standardizovaná API nebo řešení v Javě byla špatně navržená, a evolučně vzniklé alternativy jsou mnohem lepší. Byly to zejména:

  • java.util.Date a Calendar mají lepší alternativu Joda-time
  • EJB 2.0 Entity Beans byly strašně komplikované, jejich alternativa Hibernate byla přijata jako EJB 3.0
  • JSP 1.0 se scriptlety byly špatné, jejich alternativa Struts 1.0 Taglib byla pak s trochou modifikací zahrnuta do JSP 2.0 jako EL a JSTL
  • java.util.logging má lepší alternativu Log4J
  • původní rozhraní XML parserů DOM má lepší alternativy SAX a StAX, později přijaté do JavaSE
  • implementace XML DOM v Javě má lepší alternativy JDOM a DOM4J
  • v oblasti bezpečnosti má JSSE/JCE/JCA lepší alternativu Bouncy Castle

Spring framework byl evolučním výběrem shledán býti lepší alternativou JavaEE.

Dokumentace ke Spring

Srovnání Spring s Java SE a EE

  • Java SE (Standard Edition) je specifikace platformy složené z virtuálního stroje JVM (Java Virtual Machine) a knihoven tříd (napsaných v jazycích Java a C, přeložených do Java byte code a dynamických knihoven pro daný OS - .so,.dll)
    • JRE (Java Runtime Environment) je produkt složený z JVM + knihovny tříd
    • JDK (Java Development Kit) je vývojové prostředí složené z JRE + vývojové nástroje (javac, jvisualvm, javah, javap, atd.)
    • JRE a JDK jsou nainstalovatelné programy od více výrobců (Oracle, IBM, OpenJDK, IcedTea)
    • JRE nám garantuje základní knihovnu tříd, a zároveň i vnucuje takové, které ne vždy potřebujeme (Swing, XML processing, ...)
  • Java EE (Enterpise Edition) je specifikace rozhraní a služeb poskytovaných aplikačním serverem
    • mnoho aplikačních serverů splňující specifikaci Java EE od různých výrobců (GlassFish, JBoss, Oracle AS, IBM WebSphere, ...)
    • existuje referenční implementace
    • Java EE není modulární, samostatně se dají použít jen malé části (JavaMail, Servlet API)
  • Spring je aplikační framework a Inversion-of-Control container
    • jedna implementace od Springsource
    • modulární, lze použít jen právě jen tu část, kterou potřebujeme

Z hlediska enterprise aplikací nám Java SE a EE nabízí:

Java SE (Standard Edition)

  • základní třídy, práce s vlákny, Collections, Math, IO, NIO, net, regex, prefs, zip, reflection
  • práce s časem (Date,Calendar)
  • i18n, ResourceBundle
  • java.util.logging
  • AWT a Swing GUI
  • JDBC (Java Data Base Connection)
  • security (šifry, hash funkce, SSL, X509 certifikáty, Kerberos, PAM, SASL, JAAS )
  • HTTP klient
  • Activation Framework (práce s MIME typy souborů)
  • ImageIO - práce s obrázky
  • JNDI a LDAP
  • Print Service API
  • JMX (Java Management eXtension)
  • Scripting API + implementace JavaScript
  • XML
    • API pro parsery se SAX, DOM a StAX rozhraním + implementace
    • XSLT transformations API + implementace
    • XPath API + implementace
    • JAXB (Java XML Binding)
    • XML Schema validace API + implementace
    • XML Signature a XML Encryption API + implementace
  • RMI (Remote Method Invocation) a IIOP/CORBA
  • JAX-WS (web services)

Java EE (Enterprise Edition)

  • Activation Framework (je i v Java SE 6)
  • JAX-WS (je i v Java SE 6)
  • JAXB (je i v Java SE 6)
  • JavaMail (MIME, SMTP, POP3, IMAP)
  • EL - Unified Expression Language
  • web
    • Servlets
    • JSP - Java Server Pages
    • JSTL - JSP Standard Tag Library
    • JSF - Java Server Faces
  • Persistence API
  • EJB - Enterprise Java Beans
    • entity beans
    • session beans
      • stateful
      • stateless
  • JMS - Java Message Service API
  • JTA - Java Transactions API
  • Connector API
  • JACC - Java Authorization Contract for Containers API
  • validation
  • JAXR
  • JAX-RPC

JavaEE servery umožňují nasazovat (deploy) aplikace zabalené ve formě EAR archivů, nebo v případě webových aplikací ve formě WAR archivů.

Spring framework

Přehledový obrázek z dokumentace Spring:

Spring-overview.png

Jednotlivé části lze stahovat a používat samostatně, viz Obtaining Spring 3 Artifacts with Maven. V závorce je vždy název příslušné Maven dependency.

  • Data Access/Integration
    • Transaction Management (spring-tx)
    • Spring JDBC (spring-jdbc)
    • Object-to-Relation-Mapping (ORM) integration with Hibernate, JPA, and iBatis (spring-orm)
    • Object-to-XML Mapping (OXM) abstraction and integration with JAXB, JiBX, Castor, XStream, and XML Beans (spring-oxm)
  • Web
    • Web application development utilities applicable to both Servlet and Portlet Environments (spring-web)
    • Spring MVC for Servlet Environments (spring-webmvc)
    • Spring MVC for Portlet Environments (spring-webmvc-portlet)
  • Testing Spring applications with tools such as JUnit and TestN (spring-test)
  • IOC - Inversion-of-Control container
    • Bean Factory and JavaBeans utilities (spring-beans)
    • Core utilities (spring-core)
    • Spring context (spring-context)
    • Expression Language (spring-expression)
    • Context utilities including EhCache, JavaMail, Quartz, and Freemarker integration (spring-context-support)
  • AOP - Aspect Oriented Programming (spring-aop)
  •  integrace
  • modularita podle specifikace OSGi (viz Spring Dynamic Modules for OSGi(tm) Service Platforms)

Postupně si na této stránce probereme ty častěji používané části. Dále jsou k dispozici samostatné stránky:

Spring JDBC

Hlavní článek: Spring JDBC

Příklad použití: PV168/Spring JDBC v IntelliJ IDEA

Práce s čistým rozhraní JDBC je poměrně pracná. Je nutné stále dokola opakovat kusy kódu, které získávají spojení na databázi, formulují dotaz, zpracovávají odpověď a nakonec uklízí.

Například typická změna záznamu v tabulce vypadá nějak takto:

 
    public void updateBook(Book book) {
        Connection c = null;
        PreparedStatement st = null;
        try {
            c = dataSource.getConnection();
            st = c.prepareStatement("UPDATE BOOKS SET name=?,author=? WHERE id=?");
            st.setString(1, book.getName());
            st.setString(2, book.getAuthor());
            st.setInt(3, book.getId());
            st.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (st != null) st.close();
                if (c != null) c.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

Z toho celého jsou důležité vlastně jenom samotný SQL a nastavení jeho parametrů.

Spring JDBC využívá návrhový vzor Template Method a nové rysy jazyka Java zavedené ve verzi 5.0 k tomu, aby kód pro práci s databází mohl být maximálně jednoduchý. S její pomocí se dá výše uvedená metoda zapsat mnohem stručněji:

 
    public void updateBook(Book book) {
        jdbc.update("UPDATE BOOKS SET name=?,author=? WHERE id=?", book.getName(), book.getAuthor(), book.getId());
    }

To je rozdíl, který vidíte a cítíte :-)

RowMapper

Pro vytahování dat z relační databáze pomocí SQL příkazu SELECT nabízí rozhraní RowMapper, které umožňuje encapsulovat kód pro mapování dat z JDBC na objekty, a pak ho vícekrát použít. Například v následující ukázce kódu obsahuje konstanta BOOK_MAPPER anonymní třídu implementující rozhraní RowMapper tak, že vytváří instance třídy Book. Je pak použita vícekrát, pro získávání záznamů o všech knihách, o konkrétní knize a o knihách odpovídající nějakému vyhledávacímu kritériu:

 
public class BooksManagerImplSpring implements BooksManager {
 
    private JdbcTemplate jdbc;
 
    @Resource
    public void setDataSource(DataSource dataSource) {
        this.jdbc = new JdbcTemplate(dataSource);
    }
 
    private static final RowMapper<Book> BOOK_MAPPER = new RowMapper<Book>() {
        public Book mapRow(ResultSet rs, int i) throws SQLException {
            return new Book(rs.getInt("id"), rs.getString("name"), rs.getString("author"));
        }
    };
 
    public List<Book> getAllBooks() {
        return jdbc.query("SELECT * FROM BOOKS", BOOK_MAPPER);
    }
 
 
    public List<Book> findBooksWithAuthor(String authorNameBeginsWith) {
        //vsimnete si toho %, ktere v SQL prestavuje jakekoliv znaky
        return jdbc.query("SELECT * FROM BOOKS where author like ?", BOOK_MAPPER, authorNameBeginsWith + "%");
    }
 
...
}

queryFor*

Pro obecné dotazy slouží metoda query v rozhraní JdbcTemplate, která vrací List objektů.

Zjednodušením jsou metody queryForInt, queryForLong, queryForObject, které předpokládají dotaz vracející právě jeden záznam daného typu. Např:

 
 
    public int getNumberOfBooks() {
        return jdbc.queryForInt("SELECT count(*) FROM BOOKS");
    }
 
    public String getBookNameFromId(int id) {
        return jdbc.queryForObject("SELECT name FROM BOOKS WHERE id=?", String.class, id);
    }
 
    public Book getBookById(int id) {
        return jdbc.queryForObject("SELECT * FROM BOOKS WHERE id=?", BOOK_MAPPER, id);
    }

Transakce

Obdobně je ve Springu zjednodušena práce s transakcemi.

Někdy je potřeba zajistit, že několik SQL příkazů se buď provede jako celek, nebo se neprovede vůbec. Klasickým příkladem je přepis peněz v bance z účtu na účet - buď musí peníze z jednoho účtu ubýt a na druhém přibýt, nebo se nesmí stát nic. Nelze připustit stav, kdy peníze z jednoho účtu odejdou, ale nikam se nepřipíšou, protože třeba zrovna vypnuli přívod elektřiny k bankovnímu počítači.

Řešením jsou transakce. Transakce se zahájí, SQL příkazy se dělají a pokud všechny skončí úspěšně, provede se commit, který všechny změny udělá najednou. Pokud ne, provede se rollback a jako kdyby se nic nestalo.

V čistém JDBC se transakce zahajují voláním metody setAutoCommit(false) a končí voláním commit() případně rollback() a opětovným nastavením setAutoCommit(true).

Ve Springu je to mnohem jednodušší. Stačí příslušnou metodu označit anotací:

 
    @Transactional
    public void slozitaOperace() {
           jdbc.update("...");
           jdbc.update("...");
           jdbc.update("...");
           //...
    }

Spring při vstupu do metody označené anotací @Transactional zahájí transakci, při normálním ukončení metody pomocí return provede commit, při vyhození potomka RuntimeException provede rollback. (Pozor, při vyhození checkované vyjímky rollback neprovádí.)

Spring LDAP

Obdobně jako práci s JDBC, zjednodušuje Spring i práci s LDAP.

LDAP (Lightweight Directory Access Protocol) adresář je služba uchovávající typicky záznamy o osobách a jejich skupinách, často se používá v instalacích firem a organizací pro udržování účtů uživatelů a ověřování hesel. Klasickými LDAP servery jsou Microsoft Active Directory a OpenLDAP.

Standardní rozhraní v Javě pro práci s LDAP se jmenuje JNDI (Java Native Directory Interface) a je obdobou JDBC.

Spring LDAP pomocí vzoru Template Method opět práci s JNDI zjednodušuje na nezbytně nutné minimum.

Spring JPA

Místo přímého použití JDBC lze se Springem používat i JPA (Java Persistence API).

Podrobnosti jsou popsány v kapitole 14.5 JPA dokumentace Spring, příklad použití Spring JPA je na stránce Spring Tutorial.

Pro ilustraci, implementace třídy pro práci se záznamy typu Product by vypadala nějak takto:

 
public class ProductDaoImpl implements ProductDao {
 
    @PersistenceContext
    private EntityManager em;
 
    public Product create(Product p) {
        em.persist(p);
        return p;
    }
 
    public Product update(Product p) {
        em.merge(p);
        return p;
    }
 
    public Product getProductById(long id) {
        return em.find(Product.class, id);
    }
 
    public void remove(Product p) {
        em.remove(p);
    }
 
    public List<Product> getAll() {
        return em.createQuery("select p from Product p",Product.class).getResultList();
    }
 
    public List<Product> loadProductsByCategory(String category) {
       TypedQuery query = em.createQuery("from Product as p where p.category = :category",Product.class);
       query.setParameter("category", category);
       return query.getResultList(); 
    }
}

IoC container a Dependency Injection

Srdcem Springu je Inversion-of-Control (IoC) container.

Podrobný popis je v dokumentaci Springu v kapitole 3. The IoC container.

Spring IoC container umožňuje vytvářet instance objektů, tzv. beans, injektovat do nich odkazy na jiné objekty, a pomocí AOP (aspektové programování, viz níže) zajišťovat další věci, např. transakce nebo bezpečnost.

Dependency injection lze asi nejlépe demonstrovat na příkladu. Klasický způsob získávání např. poolu spojení na databázi je aktivní, získáním objektu z adresáře pomocí JNDI:

 
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection
 
 ...
    Context init = new InitialContext();
    Context ctx = (Context) init.lookup("java:comp/env");
    DataSource  pool = (DataSource) ctx.lookup("jdbc/mojedatabaze");
 ...
    Connection con = pool.getConnection();
 

kdežto při dependency injection se pasivně očekává, že instanci něco nastaví:

 
import javax.sql.DataSource;
import javax.annotation.Resource;
 
 ...
 
    @Resource(name = "jdbc/mojedatabaze")
    private DataSource pool;
...
    Connection con = pool.getConnection();
 
}

Kód tak má méně závislostí, snadněji se pro něj píší unit testy a snadněji se integruje do různých prostředí, například do webové nebo desktopové aplikace.

Libovolná třída může být Spring bean. V hantýrce Springu se tomu říká POJO - Plain Old Java Object.

Dependency injection lze provádět pomocí konstruktoru, pomocí setXXX() metody nebo dokonce přímo do proměnné. Například:

 
import javax.annotation.Resource;
 
public class Fazole {
 
    private String a;
    private String b;
 
    @Resource
    private String c;
 
    public Fazole(String a) {
        this.a = a;
    }
 
    public void setB(String b) {
        this.b = b;
    }
 

ApplicationContext

Základem je získání implementace třídy ApplicationContext, ze které pak lze získávat instance beans.

Ve stand-alone aplikacích to lze udělat nějak takto:

 
    ApplicationContext springCtx = new ClassPathXmlApplicationContext("spring-context.xml");
 
    Fazole f = springCtx.getBean("fazole",Fazole.class);

Ve webových aplikacích vytvoří aplikační kontext listener nakonfigurovaný ve WEB-INF/web.xml:

 
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-context.xml</param-value>
    </context-param>

a v Servletech lze pak kontext získat pomocí

 
  Fazole f = WebApplicationContextUtils.getWebApplicationContext(getServletContext()).getBean("fazole",Fazole.class);

Konfigurační metadata

Konfigurace beans může být zadána třemi způsoby:

  • v XML souboru (např. spring-context.xml v příkladech výše)
  • pomocí anotací @Component, @Autowire, @Resource a dalších
  • pomocí Java kódu s anotací @Configuration

Obvykle se používá XML soubor, protože je snadné pak mít různé konfigurace např. pro testování a provoz.

XML

Minimální konfigurační XML soubor vypadá nějak takto:

 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 
  <bean id="prvni" class="cz.moje.Prvni">
    <property name="druha" ref="druha"/>
  </bean>
 
  <bean id="druha" class="cz.moje.Druha">  
  </bean>
 
</beans>

a třída Prvni pak může vypadat tak jak na obrázku níže. V IntelliJ IDEA pak funguje zvýrazňování Spring beans a jejich závislostí:

Idea-spring-support.png

Anotace

Alternativně lze využít anotace. V XML souboru je nutné povolit procházení tříd a hledání anotací pomocí

 
    <context:component-scan base-package="cz.moje"/>
    <context:annotation-config/>

a třídy pak stačí anotovat:

Idea-spring-annotation.png

Místo anotace @Resource lze použít @Autowire, rozdíl je v tom, že @Resouce hledá vhodný bean pomocí jména odvozeného od názvu set metody, kdežto @Autowire ho hledá podle typu, tj. hledá instanci třídy nebo interface, která se dá použít.

konfigurace Java kódem

Třetí možností je místo XML a anotací použít Java kód.

Konfigurace je v tom případě ve třídě označené anotací @Configuration, která obsahuje metody označené anotací @Bean se jménu odpovídajícími jménům beanů. Například:

 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration  //je to konfigurace pro Spring
public class SpringConfig {
 
    @Bean
    public Prvni prvni() {
        Prvni prvni = new Prvni();
        prvni.setDruha(druha());
        return prvni;
    }
 
    @Bean
    public Druha druha() {
        return new Druha();
    }
}
 

Získání kontextu se pak provede mírně jinak, místo ClassPathXmlApplicationContext použijeme třídu AnnotationConfigApplicationContext:

 
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    Prvni prvni = ctx.getBean("prvni", Prvni.class);
    Druha druha = ctx.getBean("druha", Druha.class);

Výhodou oproti obyčejnému Java kódu vytvářejícímu instance Java tříd je to, že pořád fungují Spring anotace, a bude použita jen jedna instance od každého beanu.

Přístup k poolu databázových spojení

Výhody Spring IoC containeru vyniknou dobře při použití pro získání poolu databázových spojení. Implementace databázových operací je díky dependency injection nezávislá na způsobu získání poolu spojení, ve Spring beanu stačí mít set metodu:

 
    @Resource
    public void setDataSource(DataSource dataSource) {
        jdbc = new JdbcTemplate(dataSource);
    }

a ve stand-alone aplikaci pak můžeme použít XML konfiguraci nějaké implementace poolu

 
    <bean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource">
        <property name="driverClassName" value="org.postgresql.Driver"/>
        <property name="url" value="jdbc:postgresql://stroj.fi.muni.cz:5432/mojedb"/>
        <property name="username" value="${user}"/>
        <property name="password" value="${password}"/>
    </bean>

kterou v případě nasazení uvnitř JavaEE serveru pouze změníme na získání pomocí JNDI:

 
    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="/jdbc/mojedb"/>
        <property name="resourceRef" value="true"/>
    </bean>

čímž minimalizujeme změny při změně nasazení.

Spring AOP - Aspect Oriented Programming

AOP čili Aspektově Orientované Programování je programovací paradigma (styl programování), které umožňuje postihnout tzv. cross-cutting concerns, tj. záležitosti jdoucí napříč normálním rozdělením programu na části.

Při AOP programátor definuje tzv. aspekty, kde aspekt zahrnuje

  • kusy kódu, tzv. advice
  • přípojné body v programu, tzv. pointcuts, kde budou kusy kódu vykonány.

Podrobnosti implementace AOP ve Springu jsou popsány v kapitole Chapter 7. Aspect Oriented Programming with Spring

V podstatě při AOP definujeme, že určitý kód má být vykonán před, po nebo kolem specifikovaných míst v programu.

Místa v programu mohou být specifikována regulárním výrazem pro jméno metody, typy argumentů metody, anotacemi, atd.

Například transakce jsou v Spring JDBC zajištěny pomocí aspektu, který kolem metod označených anotací @Transactional vykoná kód pro zahájení transakce před vstupem do metody a kód pro commit/rollback po návratu z metody. Jinak řečeno, pointcut jsou v tomto případě metody s anotací @Transactional, a advice je kód pro zpracování transakcí.

Spring AOP je založeno na tzv. proxy třídách. Jako pointcut lze proto použít jen public metody. Jazyk pro specifikaci pointcuts je ale společný se systémem AspectJ, ve kterém lze definovat i jiné pointcuts.

Vlastní aspect je možno definovat např. následovně:

 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
 
@Aspect
@Component("profiler")
public class Profiler {
    
    @Around("execution(* cz.muni.fi.pa165.spring.Banka.*(..))")
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        StopWatch clock = new StopWatch("Profilovani " + call.toShortString());
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}

V tomto příkladu anotace @Aspect udává, že třída definuje aspect, a anotace @Around udává, že metoda profile má být zavolána místo volání libovolné metody rozhraní Banka.

V metodě profile() je pak kód pro změření doby vykonání původně volané metody, která je spuštěna voláním call.proceed().

Příklady specifikace pointcutu:

  • execution(public * *(..)) - zavolání libovolné public metody
  • execution(* set*(..)) - zavolání libovolné metody se jménem začínajícím na set
  • execution(* com.xyz.service.AccountService.*(..)) - zavolání libovolné metody definované v interface AccountService
  • execution(* com.xyz.service..*.*(..)) - zavolání libovolné metody v package com.xyz.service nebo v podřízeném package
  • @annotation(org.springframework.transaction.annotation.Transactional) - zavolání metody s anotací @Transactional
  • bean(tradeService) - zavolání libovolné metody ve Spring beanu tradeService

Shrnutí

Spring je tvořen

  • IoC containerem, který umožňuje snadno konfigurovat instance důležitých objektů
  • knihovnami, které obvykle poskytují jednodušší rozhraní pro standardní JavaSE/EE API, např. JDBC, LDAP, transakce atd.