среда, 4 июля 2012 г.

Почему я не участвую в спорах Spring vs JavaEE

На мой взгляд спор не имеет смысла - один и тот же POJO может замечательно жить и под управлением EJB-контейнера и под управлением Spring Framework. Аргументы в стиле "EJB плохо тестируются" так же не состоятельны: при тестировании можно использовать Spring.

По сути, разница заключается лишь в том, что в Spring этот инъектируемый POJO по-умолчанию будет синглтоном и все его методы не являются потокобезопасными. В EJB контейнере же как правило создается пул объектов и спецификация гарантирует потокобезопасность бизнес-методов. При этом инъектируемый EntityManager является потокобезопасным в обоих случаях.

Позволю себе продемонстрировать данное мнение кодом, думаю так будет максимально наглядно. Данную заметку так же можно использовать в качестве пособия по интеграции JPA и Spring Framework.

IService:

package name.samolisov.demo;



public interface IService {



    public void transaction();

}

 

Service:

package name.samolisov.demo;



import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;



import name.samolisov.transactions.entity.Entity;



public class Service implements IService {

   

    @PersistenceContext(unitName = "TransactionSA")

    private EntityManager em;

       

    public void transaction() {        

        em.persist(new Entity(Thread.currentThread().getName()));      

    }  

}

 

Entity:

package name.samolisov.transactions.entity;



import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.SequenceGenerator;



@javax.persistence.Entity

public class Entity {

   

    @Id

    @SequenceGenerator(name = "EntitySeqGen", sequenceName = "id_seq", allocationSize = 10)

    @GeneratedValue(generator = "EntitySeqGen")        

    private Long id;

   

    private String message;



    public Entity() {

       

    }

   

    public Entity(String message) {

        this.message = message;

    }

   

    public Entity(Long id, String message) {

        this.id = id;

        this.message = message;

    }

   

    public Long getId() {

        return id;

    }



    public void setId(Long id) {

        this.id = id;

    }



    public String getMessage() {

        return message;

    }



    public void setMessage(String message) {

        this.message = message;

    }  

}

 

persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

    <persistence-unit name="TransactionSA" transaction-type="JTA">

        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

        <jta-data-source>jdbc/xa/sa</jta-data-source>

        <class>name.samolisov.transactions.entity.Entity</class>

        <properties>

            <property name="eclipselink.target-server" value="WebLogic_10"/>

        </properties>

    </persistence-unit>

</persistence>

 


ejb-jar.xml:

<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">

  <enterprise-beans>

    <session>

      <ejb-name>ServiceBean</ejb-name>

      <business-local>name.samolisov.demo.IService</business-local>

      <ejb-class>name.samolisov.demo.Service</ejb-class>

      <session-type>Stateless</session-type>

    </session>

  </enterprise-beans>

</ejb-jar>

EJB Client:

package name.samolisov.transaction.web;



import java.io.IOException;



import javax.ejb.EJB;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



import name.samolisov.demo.IService;



public class TransactionServlet extends HttpServlet {

   

    @EJB

    private IService service;

   

    @Override

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)

            throws ServletException, IOException {

        service.transaction();

    }

}

 

EJB Client's web.xml:

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">

  <display-name>TransactionWeb</display-name>

  <servlet>

    <servlet-name>tservlet</servlet-name>

    <servlet-class>name.samolisov.transaction.web.TransactionServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>tservlet</servlet-name>

    <url-pattern>/demo</url-pattern>

  </servlet-mapping>

</web-app>

Результат работы:


applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:jee="http://www.springframework.org/schema/jee"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop"

    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

                        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd

                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd

                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

                        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">


   

    <tx:annotation-driven />



    <tx:jta-transaction-manager />

   

    <jee:jndi-lookup id="TransactionSA" jndi-name="persistence/TransactionSA"/>

   

    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

       

    <aop:config>

        <aop:pointcut id="transactionMethod" expression="execution(* name.samolisov.demo.IService.transaction(..))"/>

        <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionMethod"/>

    </aop:config>



    <tx:advice id="txAdvice" transaction-manager="transactionManager">

        <tx:attributes>

            <tx:method name="transaction" propagation="REQUIRED"/>      

        </tx:attributes>

    </tx:advice>

   

    <bean name="MyService" class="name.samolisov.demo.Service"/>   

</beans>

 

Spring client:

package name.samolisov.web;



import java.io.IOException;



import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



import name.samolisov.demo.IService;



import org.springframework.context.ApplicationContext;

import org.springframework.web.context.support.WebApplicationContextUtils;



public class DemoServlet extends HttpServlet {



    @Override

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)

            throws ServletException, IOException {

       

        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

        IService service = (IService) context.getBean("MyService");

        service.transaction(); 

    }  

}

 

Spring client's web.xml:

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">

  <display-name>SpringWeb</display-name>

  <servlet>

    <servlet-name>demo</servlet-name>

    <servlet-class>name.samolisov.web.DemoServlet</servlet-class>    

  </servlet>

  <servlet-mapping>

    <servlet-name>demo</servlet-name>

    <url-pattern>/demo</url-pattern>

  </servlet-mapping>

  <context-param>

    <param-name>contextConfigLocation</param-name>

    <param-value>/WEB-INF/applicationContext.xml</param-value>

  </context-param>

  <listener>

    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

  </listener>

  <persistence-unit-ref>

    <description>

        Persistence context for demo.

    </description>

    <persistence-unit-ref-name>

        persistence/TransactionSA

    </persistence-unit-ref-name>

    <persistence-unit-name>

        TransactionSA

    </persistence-unit-name>

    </persistence-unit-ref>  

</web-app>

Результат работы:


Я думаю, идея понятна.

Про интеграцию Spring Framework и WebLogic можно так же прочитать здесь. В блоге Сурового челябинского программиста есть так же статья, описывающая совместное использование Hibernate со Spring Framework.

А вы что предпочитаете использовать, Spring Framework или EJB?

Понравилось сообщение - подпишитесь на блог и Twitter

9 комментариев:

Роман Комаров комментирует...

Не понялю Можно пояснения?

Pavel Samolisov комментирует...

Просто спор не имеет смысла - один и тот же POJO может замечательно жить и под управлением EJB-контейнера и под управлением Spring Framework. Аргументы в стиле "EJB плохо тестируются" так же не состоятельны: при тестировании можно использовать Spring.

По сути, разница заключается лишь в том, что в Spring этот инъектируемый POJO по-умолчанию будет синглтоном и все его методы не являются потокобезопасными. В EJB контейнере же как правило создается пул объектов и спецификация гарантирует потокобезопасность бизнес-методов. При этом инъектируемый EntityManager является потокобезопасным в обоих случаях.

Antonio SAN комментирует...

EJB так же хорошо тестируется как и спринг, есть всякие тулзы типо arqullian.

В EJB3.1 можно так же как и в спринге использовать singleton. В свою очередь в спринге есть scope=prototype которое как бы похоже на steteless поведение, но врятли конечно для них контенейр создает пул.

Потокобезопасность контейнер EJB для методов stateless бина не гарантирует, и если например будет задействован какой либо общий объект (ну например заинжекшен singleton bean) который будет менятся в методе нашего stateless бина то будет по сути конкурентное изменение. Т.е. потокобезопасность должен реализовывать сам программист.

Pavel Samolisov комментирует...

Спасибо за развернутый комментарий.

По поводу тестирования EJB, в 3.1 вообще появился встроенный контейнер, что снижает зависимость от внешних утилит.

Немножко уточню. Синглтон можно использовать и в некоторых серверах приложений, реализующих 3.0. Например в WebLogic объект-синглтон будет создан один на кластер и в случае падения сервера, на котором он - синглтон - работает, будет осуществлена его миграция на другой сервер.

По поводу потокобезопасности. Понятно, что с не очень большой аккуратности, скажем так, можно и кое что важное сломать. Но контейнер должен или сериализовывать вызовы бизнес-методов или использовать пул объектов. Естественно, что если заинъектить синглтон, то доступ к нему будет конкурентным, если же у каждого экземпляра компонента в пуле своя копия объекта (например, EntityManager'а), то все в порядке. Впрочем, соглашусь с Вами, разработчик должен понимать, что именно он передает в компоненты.

Сторонники Спринга в данном случае говорят, что у управляемого контейнером компонента вообще не должно быть в полях класса ссылок на объекты с изменяемым состоянием. Все такие объекты должны создаваться и использоваться как локальные переменные методов. Это действительно защищает от ошибок, но очень напоминает процедурное программирование.

isalnikov комментирует...

Antonio SAN , для описанного вами случая с stateless и singleton в последнем надо использовать @Lock(LockType.READ) LockType.WRITE
на методах .

Павел, а вы можете провести тесты по времени что быстрее spring или ejb , есть мнение что ejb медленнее раза так в 1.5

Pavel Samolisov комментирует...

Коллеги, я не тестировал сравнительную скорость приложения, написанного с одной стороны на Спринг, а с другой - на EJB 3. В любом случае, чтобы тесты были корректными, нужно, чтобы данные приложения делали одно и то же, а так же взаимодействовали с БД одинаковым способом, например через JPA. Получается, что нужен набор синтетических тестов, которые по сути проверяют лишь слой, управляющий транзакциями и создающий объекты (IoC), причем большинство объектов - синглетные и создаются один раз при старте приложения. Откуда здесь может взяться разница в производительности в 1.5 раза непонятно.

Pavel Samolisov комментирует...

Позволю себе по некропостить. Я сделал бенчмарк, показывающий, что EJB немного, но быстрее, нежели Spring Framework (точнее, RESTификация в EJB быстрее нежели в Spring Web MVC).

Armen Arzumanyan комментирует...

Когда контейнер JBoss или Wildfly тогда выбор EJB3/Java EE, если под томкат то выбор Спринг, но Спринг как клей, для фронтенда JSF2.2 . EJB3 не нужна конфигурация, Спрингу нужно все, каждый раз когда Спринг обновляется может что то испортится, например под java 8 Спринг 3 не работает, а EJB3 как работал под JBoss 4.2.2 в 2008 году так и работает под wildfly 2016 + CDI . и потом стандартный java EE всегда на один шаг впереди Спринг.

Семен Горячкин комментирует...

Для стенделон без разницы что использовать. Может быть даже в Спринге есть по больше сахара чем в EJB, но для кластерных систем и для масштабирования мне кажется от EJB больший профит.

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!