EJB3 i Spring


Zapytano mnie pewnego razu, czy próbowałem połączyć Spring i EJB3. Podczas przygotowań do SCBCD nie chciałem tracić czasu na takie zabawy. Teraz jednak to co innego :). Wewnętrznie nie odczuwam potrzeby korzystania z dwóch kontenerów jednocześnie – lekkiego Spring’owego i JNDI. Zachęciła mnie jednak myśl o wykorzystaniu takich fragmentów API jak JdbcTemplate, które uprzyjemniają np. wywołanie procedur składowanych. Jeśli interesuje kogoś jednak współdzielenie bean’ów między kontenerami, to informacje na ten temat zamieściłem w dalszej części owego wpisu. Jako punkt początkowy development’u przykładowej aplikacji, przyjąłem końcową postać kodu źródłowego zamieszczonego w poście Budowanie aplikacji Java EE wykorzystując Apache Maven.

Dodanie zależności Spring do modułu Controller
Spring Framewrok jako bibliotekę zewnętrzną dodać można do projektu na kilka sposobów. Pierwszy, wykorzystany przeze mnie, polega na zdefiniowaniu nowego dependency w pliku pom.xml projektu Controller, a następnie uwzględnieniu modułu Spring’owego w konfiguracji maven-ear-plugin. Jeśli zdanie poprzednie nie wydaje Ci się jasne – odsyłam do wpisu na temat Java EE i Maven. Wybrane rozwiązanie nie jest według mnie eleganckie. W końcu od czego jest katalog lib modułu EJB w pliku EAR? Jedyne usprawiedliwienie pójścia tym tropem, to brak czasu i konieczność zajmowania się pracą magisterską… Inny sposób udostępnienia bibliotek zewnętrznych, to po prostu instalacja Spring Framework bezpośrednio na serwer aplikacyjny JBoss, a nie umieszczanie go wewnątrz pliku EAR.

Generalna konfiguracja Spring Framework
Dla (między innymi) standardu EJB, programiści Spring Framework stworzyli klasę BeanFactoryLocator, która odpowiada za załadowanie grupy ApplicationContext’ów. Domyślnie na classpath’ie szuka ona pliku beanRefContext.xml. W moim przypadku plik ten umieściłem w katalogu resources projektu Controller. Kod źródłowy wygląda następująco:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
  <context:annotation-config/>
  <bean id="applicationcontext">
    <constructor-arg>
      <list>
        <value>applicationContext.xml</value>
      </list>
    </constructor-arg>
  </bean>
</beans>

Linia 6 umożliwia wstrzykiwanie zależności przez adnotację @Autowired (dokumentacja Spring Framework 2.5.x http://static.springsource.org/spring/docs/2.5.x/reference/beans.html – podrozdział “3.11. Annotation-based configuration”). Reszta prezentowanego kodu odpowiada za wczytanie jednego bądź wielu plików konfiguracyjnych z bean’ami Spring’owymi. Referowany plik applicationContext.xml znaleźć się powinien w tym samym katalogu co beanRefContext.xml i na razie może nie definiować nowych ziaren.

JdbcTemplate
W celu wykorzystania Spring’owego JdbcTemplate stworzyłem po stronie bazy danych Oracle (schemat HR) nową procedurę składowaną:

CREATE OR REPLACE PROCEDURE add_job(p_job_id jobs.job_id%TYPE, p_job_title jobs.job_title%TYPE)
IS
BEGIN
   INSERT INTO jobs(job_id, job_title) VALUES(p_job_id, p_job_title);
END add_job;

W celu jej wywołania, wstrzyknąłem używane przez aplikację źródło danych tworząc przy tym nowy obiekt JdbcTemplate (zaleta setter injection). Kod odpowiedzialny za wstrzykiwanie zależności oraz wywołanie procedury add_job wyglądać powinien następująco:

public void addByPLSQLProcedure(Job job) {
   final String procedureName = "add_job";

   List<SqlParameter> declaredParameters = new ArrayList<SqlParameter>();
   declaredParameters.add(new SqlParameter("p_job_id", Types.VARCHAR));
   declaredParameters.add(new SqlParameter("p_job_title", Types.VARCHAR));

   Map parameterValues = new HashMap();
   parameterValues.put("p_job_id", job.getJobId() + "2");
   parameterValues.put("p_job_title", job.getJobTitle() + "2");

   SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate);
   call.withProcedureName(procedureName);
   for (SqlParameter p : declaredParameters) {
      call.addDeclaredParameter(p);
   }
   call.execute(parameterValues);
}

@Resource(mappedName="java:/OracleDS")
public void setDataSource(DataSource dataSource) {
   this.jdbcTemplate = new JdbcTemplate(dataSource);
}

Spring IoC i JNDI
W celu prezentacji wstrzykiwania bean’ów między kontenerami, posłużę się dość prostym, lecz dzięki temu nie zaciemniającym istoty działania przykładem. Stworzę nowy pakiet edu.lantoniak.controller.di, gdzie lokalne ziarno EJB (BottomEJBBean) wstrzyknę do bean’a Spring’owego (MiddleSpringBean), a następnie tak skonstruowany obiekt z powrotem do innego komponentu EJB (TopEJBBean). W celu wstrzyknięci BottomEJBBean do MiddleSpringBean należy zdefiniować odpowiednie odwołanie w pliku applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
  <jee:local-slsb id="bottomEJBLocal" jndi-name="ear-1.0/BottomEJBBean/local" business-interface="edu.lantoniak.controller.di.BottomEJBLocal"/>
  <bean id="middleSpringBean">
    <property name="bottomEJBLocal" ref="bottomEJBLocal"/>
  </bean>
</beans>

Kluczowa linia 6 tworzy referencję do komponentu EJB w kontenerze Spring IoC. Ważnym atrybutem, którego wartość różnić się może dla poszczególnych serwerów aplikacyjnych jest jndi-name. Element ten dla JBoss 5 powinien mieć następującą postać: [nazwa_aplikacji]/[bean’s_global_name]. Polecam zapoznanie się z dokumentacją: http://static.springsource.org/spring/docs/2.5.x/reference/ejb.html.

Aby wstrzyknąć stworzone poprzednio ziarno Spring’owe do komponentu EJB, skorzystałem z interceptora SpringBeanAutowiringInterceptor oraz adnotacji @Autowired:

@Stateless
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class TopEJBBean implements TopEJBRemote {
   @Autowired
   private MiddleSpringBean middleSpringBean = null;

   public double pow2(double x) {
      return Math.pow(x, middleSpringBean.getBottomEJBLocal().getOperand());
   }
}

Źródła projektu: javaee-app+spring.zip.
Inne ciekawe materiały: spring_ejb3_integration.pdf, spring-ejb3-integration-demo.zip, mbogoevici_1050_spring_on_jboss.pdf.
Errata: Biblioteki zewnętrzne, EAR i Maven.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: