Статьи

От весны до Java EE 6

Недавно я работал над довольно сложным проектом, в котором смешались многие технологии Java EE 6 (такие как JPA, JAXB, JMS, JTA, JAX-RS и т. Д.). В целях повышения производительности и планирования прототип приложения был разработан как отдельное приложение Spring. Когда началась разработка реального приложения, мы вновь поставили под сомнение наш первоначальный выбор (например, Spring v3) и проанализировали интерес к переходу на сервер приложений Java EE 6, такой как GlassFish или JBoss.
Это в конечном итоге привело к двум основным вопросам:

  • мы можем сделать в Java EE 6 все, что мы можем сделать в Spring?
  • мы можем сделать это так же легко, как весной?

Ну, я бы сказал, что в глобальном масштабе ответ таков: да, мы можем!

Я не хочу возобновлять (бесконечные) дебаты о том, какой из них, между Spring и Java EE 6, является лучшим. Нет, я просто хочу поделиться с вами своим опытом в отношении этой миграции. Я был — и я все еще — настоящий фанат Spring (которого я, исторически говоря, обнаружил после буквального отвращения к EJB 1.0), но я также знаю о недавнем прогрессе, не говоря уже об упрощениях, которые были введены в Java EE в последние годы, а также впечатляющие улучшения скорости на стороне серверов приложений Java EE 6.

Теперь давайте подробно изучим некоторые типичные требования к «корпоративным» приложениям и сравним код, создаваемый в обоих мирах для:

  • Контексты и внедрение зависимостей
  • обмен сообщениями
  • Управление транзакциями
  • Веб-сервисы

Это сравнение должно предоставить вам некоторые конкретные элементы решения в случае, если вы не решаетесь перейти с одной технологии на другую…

Часть I: контексты и внедрение зависимостей (CDI)

Spring позволяет определять бины с использованием различных стереотипов (например, @Repository, @Service, @Controller и @Component). Выбор не так важен (это не совсем так. Например, пометка вашего DAO как @Repository добавит автоматический перевод исключений SQL), поскольку это различие в основном предназначено для IDE (для категоризации bean-компонентов). При желании вы можете дать своему бину псевдоним.

1
public interface MyInterface {...}
1
2
3
4
5
6
7
import org.springframework.stereotype.Component;
 
@Component("firstBean")
public class MySpringBean implements MyInterface {...}
 
@Component("firstBeanMock")
public class MockImpl implements MyInterface {...}

Java EE предоставляет очень похожую аннотацию (@Named), но ее использование должно быть ограничено чисто pojo. В случае сервис-ориентированных bean-компонентов (особенно транзакционных сервисов общего назначения) рассмотрите возможность использования (предпочтительно без сохранения состояния) EJB — именно потому, что они обеспечивают лучшую масштабируемость.

1
2
3
4
import javax.inject.Named;
 
@Named("firstBean")
public class MyJeeBean implements MyInterface {...}
1
2
3
4
import javax.ejb.Stateless;
 
@Stateless(name="firstService")
public class MyJeeService implements MyInterface {...}

Также помните, что, в отличие от Spring, синглтоны должны быть явно помечены как таковые в Java EE:

1
2
3
4
import javax.inject.Singleton;
 
@Singleton
public class MyJeeSingleton implements MyInterface {...}

Примечание: вы можете запутаться при выборе между «javax.inject.Singleton» и «javax.ejb.Singleton». Первый определяет стандартный POJO, управляемый контейнером (он же « Управляемый компонент » в мире Java EE), а второй — «Корпоративный компонент». Помните, что последний предназначен для одновременного доступа (клиенту не нужно беспокоиться о любых других клиентах, которые могут одновременно вызывать те же методы синглтона), а также предлагает средства управления транзакциями (см. Далее).

Теперь, когда мы зарегистрировали (и, возможно, назвали) наши компоненты, мы можем внедрить их в другие компоненты. Еще раз, процедура несколько похожа на обе стороны:

ВЕСНА

01
02
03
04
05
06
07
08
09
10
11
12
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
 
@Component
public class UseCaseHandler {
 
  @Autowired
  @Qualifier("firstBean")
  private MyInterface serviceFacade;
   
}

JAVA EE 6

01
02
03
04
05
06
07
08
09
10
11
import javax.inject.Named;
import javax.inject.Inject;
 
@Named
public class UseCaseHandler {
 
  @Inject
  @Named("firstBean"
  private MyInterface serviceFacade;
   
}

Примечание: JSR-330 унифицировал способ ввода управляемых bean-компонентов. Конкретно это означает, что аннотацию @Inject можно использовать для введения простых POJO, а также EJB (что делает аннотацию @EJB немного устаревшей).

Хорошо! Однако в реальном мире имя (например, «firstBean») бинов, которые мы хотим внедрить, может быть динамическим. Это особенно верно, как только вы играете с поведенческими паттернами, генериками и т. Д.

Весной это довольно просто. Например, вы можете сделать свой компонент-компонент ApplicationContext-осведомленным, чтобы затем использовать вставленный контекст Spring для поиска конкретных экземпляров компонента:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
 
import com.javacodegeeks.Request;
 
@Service
public class Dispatcher implements ApplicationContextAware {
  
 private ApplicationContext appContext;
 
 public void setApplicationContext(ApplicationContext ctx) throws BeansException {
  appContext = ctx;
 }
  
 public void dispatch(Request request) throws Exception {
  String beanName = "requestHandler_" + request.getRequestTypeId();
  RequestHandler myHandler = appContext.getBean(beanName, RequestHandler.class);
  myHandler.handleRequest(request);
 }
  
}
1
2
3
public interface RequestHandler  {
 public void handleRequest(Request request);
}
1
2
@Component("requestHandler_typeA")
public class HandlerA implements RequestHandler {...}
1
2
@Component("requestHandler_typeB")
public class HandlerB implements RequestHandler {...}

В Java EE 6 то же самое возможно, но все же требует немного больше строк кода (которые могут быть централизованы в классе помощника):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
 
import com.javacodegeeks.Request;
 
@Named
public class Dispatcher
  
 @Inject
 private BeanManager beanManager;
  
 public void dispatch(Request request) throws Exception {
  String beanName = "requestHandler_" + request.getRequestTypeId();
  RequestHandler myHandler = this.getBean(beanName, RequestHandler.class);
  myHandler.handleRequest(request);
 }
  
 @SuppressWarnings("unchecked")
 private <T> T getBean(String name, Class<T> clazz) throws Exception {
  Set<Bean<?>> founds = beanManager.getBeans(name);
  if ( founds.size()==0 ) {
   throw new Exception("No such bean found: "+name);
  } else {
   Bean<T> bean = (Bean<T>) founds.iterator().next();
   CreationalContext<T> cc = beanManager.createCreationalContext(bean);
          T instance = (T) beanManager.getReference(bean, clazz, cc);
          return instance;
  }
 }
  
}
1
2
3
public interface RequestHandler  {
 public void handleRequest(Request request);
}
1
2
@Named("requestHandler_typeA")
public class HandlerA implements UseCaseHandler {…}
1
2
@Named("requestHandler_typeB")
public class HandlerB implements UseCaseHandler {...}

ЧАСТЬ II: JMS

Служба обмена сообщениями Java облегчает реализацию слабосвязанной распределенной связи.
Вот почему он стал классическим методом интеграции корпоративных приложений (EAI).

Spring имеет выдающуюся поддержку JMS. Вы можете очень быстро настроить JMS производителей или потребителей,
с резолвером назначения и, опционально, с автоматическим преобразованием сообщений JMS в pojos (и наоборот). С другой стороны, J2EE поставляется с богатым набором аннотаций для доступа или определения ресурсов JMS, таких как очередь / темы, соединения или ориентированные на сообщения компоненты.

Давайте начнем с клиента JMS, который получает сообщения, то есть потребителя сообщений (или подписчика):

ВЕСНА

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
 <property name="environment">
         <props>…</props>
 </property>
</bean>
     
<bean id="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
      <property name="jndiTemplate" ref="jndiTemplate" />
      <property name="jndiName" value="java:/JmsXA" />
</bean>
     
<bean id="jndiDestResolver"
 class="org.springframework.jms.support.destination.JndiDestinationResolver">
 <property name="jndiTemplate" ref="jndiTemplate" />
</bean>
  
<bean id="jmsContainer"
 class="org.springframework.jms.listener.DefaultMessageListenerContainer">
 <property name="connectionFactory" ref="jmsConnectionFactory"/>  
 <property name="destinationResolver" ref="jndiDestResolver"/> 
 <property name="destinationName" value="queue/myQueue"/>
 <property name="messageListener" ref="myMsgConsumer" />
</bean>
    
<bean id="myMsgConverter" class="com.javacodegeeks.MsgToRequestConverter"/>
    
<bean id="myMsgConsumer" class="com.javacodegeeks.MsgConsumer"/>
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import javax.jms.Message;
import javax.jms.MessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.support.converter.MessageConverter;
 
import com.javacodegeeks.Request;
import com.javacodegeeks.Dispatcher;
         
/**
 * Example of message consumer (Message-Driven-Pojo) in Spring
 */
public class MsgConsumer implements MessageListener {
  
    @Autowired
    private MessageConverter msgConverter;
  
    @Autowired
    private Dispatcher dispatcher;
      
    public void onMessage(Message message) {      
     try {
      Request request = (Request) msgConverter.fromMessage(message);
      dispatcher.dispatch(request);
     } catch (Exception e) {
    e.printStackTrace();   
     }
    }
 
}

JAVA EE 6

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import javax.inject.Inject;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.ejb.MessageDriven;
import javax.ejb.ActivationConfigProperty;
 
import com.javacodegeeks.Request;
import com.javacodegeeks.Dispatcher ;
import com.javacodegeeks.MsgToRequestConverter;
 
/**
 * Example of message consumer (Message-Driven-Bean) in JEE
 */
@MessageDriven(activationConfig = {
 @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
 @ActivationConfigProperty(propertyName="destination", propertyValue="queue/myQueue")
} )    
public class MsgConsumer implements MessageListener  {
  
 @Inject
     private MsgToRequestConverter msgConverter;
 
 @Inject
 private Dispatcher dispatcher;
  
 public void onMessage(Message message) {      
     try {
      Request request = msgConverter.fromMessage(message);
      dispatcher.dispatch(request);     
     } catch (Exception e) {
  e.printStackTrace();    
 }
    }
     
}

Теперь давайте закодируем JMS-клиент, который создает и отправляет сообщения, то есть производитель сообщений (или издатель):

ВЕСНА

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
 <property name="environment">
    <props>…</props>
 </property>
</bean>
     
<bean id="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
      <property name="jndiTemplate" ref="jndiTemplate" />
      <property name="jndiName" value="java:/JmsXA" />
</bean>
     
<bean id="jndiDestResolver"
 class="org.springframework.jms.support.destination.JndiDestinationResolver">
 <property name="jndiTemplate" ref="jndiTemplate" />
</bean>
 
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
 <property name="connectionFactory" ref="jmsConnectionFactory" />
 <property name="destinationResolver" ref="jndiDestResolver" />
 <property name="messageConverter" ref="myMsgConverter" />
</bean>
     
<bean id="myMsgConverter" class="com.javacodegeeks.MsgConverter">
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import com.javacodegeeks.Request;
 
/**
 * Example of message producer component in Spring
 */
@Component
public class MsgProducer {
 
 @Autowired
 private JmsTemplate jmsTemplate;
  
 public void postRequest(Request request) throws Exception {
  jmsTemplate.convertAndSend("queue/myQueue", request);
 }
  
}

JAVA EE 6

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.ejb.Stateless;
import javax.ejb.EJBException;
 
import com.javacodegeeks.Request;
import com.javacodegeeks.MsgToRequestConverter;
 
/**
 * Example of message producer (here a session bean) in JEE
 */
@Stateless(name="msgProducer")
public class MsgProducer {
  
 @Inject
     private MsgToRequestConverter msgConverter;
 
 @Resource(mappedName="java:/JmsXA")
 private ConnectionFactory connectionFactory;
  
 @Resource(mappedName="queue/myQueue")
 private Queue queue;
  
 private Connection jmsConnection;
 
 
 @PostConstruct
 private void initialize() {
  try {
   jmsConnection = connectionFactory.createConnection();
  } catch (JMSException e) {
   throw new EJBException(e);
  }
 }
  
  
 @PreDestroy
 private void cleanup() {
  try {
   if (jmsConnection!=null) jmsConnection.close();
  } catch (JMSException e) {
   throw new EJBException(e);
  }
 }
  
  
 public void postRequest(Request request) throws Exception {
  Session session = null;
  MessageProducer producer = null;
  try {
   session = jmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
   producer = session.createProducer(queue);
   Message msg = msgConverter.toMessage(request, session);
       producer.send(msg); 
  } finally {
   try {
    if (producer!=null) producer.close();
    if (session!=null) session.close();
   } catch (Exception e) {
    System.err.println("JMS session not properly closed: "+ e);
   }
  }
 }
  
}

Примечания:

  • Не забывайте, что, в отличие от соединений JMS и очередей JMS, сеансы JMS не являются поточно-ориентированными. Поэтому сеансы не должны совместно использоваться всеми экземплярами bean-компонентов и не должны создаваться в конструкторе или в методе PostConstruct.
  • Методы PostConstruct и PreDestroy должны генерировать только исключения времени выполнения; По этой причине исключения JMS должны быть включены (например) в исключения EJB.

Часть III. Управление транзакциями

Потребность в транзакциях имеет решающее значение в архитектуре системы, особенно с появлением SOA. В таких архитектурах грубые транзакционные сервисы могут быть построены путем объединения существующих — возможно, также транзакционных — более мелких сервисов (« микросервисов »).

Как Spring, так и Java EE удовлетворяют эту потребность, предлагая мощное декларативное (основанное на аннотациях) управление транзакциями.

ВЕСНА

1
2
3
4
5
6
7
<!-- Recognize @Transactional annotations in our beans -->
<tx:annotation-driven transaction-manager="txManager"/>
 
<!-- The transaction manager to use (here the JPA implementation) -->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
 ...
</bean>
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
 
import com.javacodegeeks.Request;
import com.javacodegeeks.RequestProcessor;
 
@Service
public class RequestProcessorImpl implements RequestProcessor {
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void process(Request request) throws Exception {
  ...
 }
  
}

JAVA EE 6

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
 
import com.javacodegeeks.Request;
import com.javacodegeeks.RequestProcessor;
 
@Stateless
@TransactionManagement(value=TransactionManagementType.CONTAINER)
public class RequestProcessorImpl implements RequestProcessor {
 
 @TransactionAttribute(TransactionAttributeType.REQUIRED)
 public void process(Request request) throws Exception {
  ...
 }
  
}

Будьте очень осторожны с исключениями времени выполнения / непроверенными в Java EE. По умолчанию они автоматически переносятся контейнером EJB в исключение EJBException, что может привести к неожиданным результатам (особенно в выражениях try… catch!). Если вам нужна более точная настройка случаев отката, попробуйте пометить такие исключения во время выполнения как аппликативные исключения, либо с помощью аннотации @ApplicationException, либо путем увеличения дескриптора ejb следующим образом:

1
2
3
4
5
6
7
8
<ejb-jar>
   <assembly-descriptor>
    <application-exception>
      <exception-class>java.lang.NullPointerException</exception-class>
      <rollback>true</rollback>
    </application-exception>
   </assembly-descriptor>
</ejb-jar>

Часть IV: Restful веб-сервисы

Корпоративным приложениям часто необходимо предоставлять некоторые из своих услуг внешнему миру, как правило, через Интернет. Это где веб-сервисы вступают в игру. Как и JMS (для асинхронной связи), веб-сервисы являются еще одним классическим методом интеграции для реализации синхронной, ориентированной на запрос-ответ, связи с использованием XML (или JSON) в качестве формата обмена.

ВЕСНА

1
2
3
4
5
6
7
8
<servlet>
 <servlet-name>ws</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
 <servlet-name>ws</servlet-name>
 <url-pattern>/services/*</url-pattern>
</servlet-mapping>
1
2
<!-- Dispatch requests to controllers + use JAXB (if found in the classpath) -->
<mvc:annotation-driven />
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
 
import com.javacodegeeks.Geek;
import com.javacodegeeks.GeekService;
 
@Controller
@RequestMapping("/geeks")
public class GeekWebService {
  
   @Autowired
   GeekService bizService;
    
  @RequestMapping(value="/{id}", method=RequestMethod.GET)
  @ResponseBody
  public Geek getGeek(@PathVariable("id") long geekId) {
     return bizService.findGeek(geekId);
 }
  
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
  
@XmlRootElement(name="geek")
public class Geek {
  
 private String name;
 private Long id;
  
 @XmlElement
 public String getName() {
  return name;
 }
  
 public void setName(String name) {
  this.name = name;
 }
  
 @XmlAttribute
 public Long getId() {
  return id;
 }
  
 public void setId(Long id) {
  this.id = id;
 }
  
}

JAVA EE 6

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
 
import com.javacodegeeks.Geek;
import com.javacodegeeks.GeekService;
 
@Path("/geeks")
@Produces(MediaType.APPLICATION_XML)
public class GeekWebService {
 
 @Inject
  GeekService bizService;
    
 @GET
 @Path("/{id}")
 public Geek getGeek(@PathParam("id") long geekId) {
    return bizService.findGeek(geekId);
  }
   
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
  
@XmlRootElement(name="geek")
public class Geek {
  
 private String name;
 private Long id;
  
 @XmlElement
 public String getName() {
  return name;
 }
  
 public void setName(String name) {
  this.name = name;
 }
  
 @XmlAttribute
 public Long getId() {
  return id;
 }
  
 public void setId(Long id) {
  this.id = id;
 }
  
}

Примечание: некоторые реализации JAX-RS, такие как JBoss RestEasy, не требуют изменения файла web.xml для настройки и установки веб-служб…

ЧАСТЬ V: Заключение

Утверждение, что в Spring все гораздо проще, намного легче, чем в Java EE, не является — точнее, не более — верным. Это просто вопрос вкуса. Более того, последние серверы приложений Java EE 6 (такие как GlassFish 3 или JBoss 6 и 7) загружаются очень быстро, практически так же быстро, как приложения Spring. Тем не менее, с точки зрения «лучшего в своем роде», все еще может быть интересно объединить обе технологии; это будет темой моего следующего поста на JCG 🙂

Справка: от Spring до Java EE 6 от нашего партнера по W4G Бернарда Линьи .

Статьи по Теме :