Статьи

Monster Component в Java EE 7

С  выходом Java EE 7  я подумал, что пришло время обновить  компонент Monster  ! Несколько лет назад у  Людовика Шампенуа  возникла идея добавить как можно больше аннотаций Java EE в класс Java. Затем он использовался  Алексисом Мусином Пушкиным  на своих семинарах по Java EE. Пришло время возродить и обновить код, чтобы он соответствовал новинкам Java EE 7.

Что такое компонент монстр?

Компонент monster — это бесполезный кусок кода, в который вы добавляете несколько аннотаций Java EE, придающих ему различные аспекты. Java EE — это управляемая среда: возьмите класс Java, добавьте аннотацию @Stateless, и контейнер предоставит вам транзакции, безопасность, объединение в пул… возьмите другой класс, добавьте аннотацию @Path, и контейнер даст вам вызов REST. Таким образом, если вы добавляете @Stateless и @Path к одному и тому же классу, вы накапливаете службы EJB без состояния и веб-службы REST.

Монстр Компонент

Класс Book ниже представляет собой постоянный класс с методом (createAndListBooks), который сохраняется сам и извлекает все книги из базы данных. Это было превращено в Компонент Монстра, потому что это накапливает несколько услуг:

  • Постоянство ( JPA ): класс Book помечается @Entity, @Table и объявляет @NamedQuery. Некоторые атрибуты Book настраивают отображение (с помощью @Column) или помечаются как @Transient (например, EntityManager).
  • XML-привязка ( JAXB ): с помощью аннотаций @XmlRootElement и @XmlAccessorType класс Book может использовать маршализацию для получения XML-представления книги. Он настраивает привязку XML с помощью @XmlElement или @XmlTransient (для EntityManager)
  • Ограничения (Bean Validation): некоторые атрибуты Книги имеют ограничения (@Size), а также параметры метода (@NotNull в createAndListBooks)
  • EJB  : класс Book является EJB без сохранения состояния (аннотирован @Stateless), поэтому он внедряет EntityManager и объявляет транзакционный метод createAndListBooks
  • Сервлет  : класс Book также является сервлетом, поскольку он расширяет HttpServlet, аннотируется @WebServlet и переопределяет doGetmethod. Этот метод использует внедренный Book EJB для сохранения себя.
  • Веб-служба RESTful ( JAX-RS ): метод @Path и @GET позволяет вам вызывать метод createAndListBooks через HTTP GET. Он создает XML-представление всех книг благодаря аннотации JAXB @XmlRootElement
  • Управление жизненным циклом  : два частных метода имеют управление жизненным циклом (@PostConstruct и @PreDestroy)
  • Перехват  : класс Book определяет метод-перехватчик (метод logMethod, аннотированный @AroundInvoke), который регистрирует весь вызов метода
@Path("/MonsterRest")
@Stateless
@WebServlet(urlPatterns = "/MonsterServlet")
@Entity
@Table(name = "MonsterEntity")
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@NamedQuery(name = "findAll", query = "SELECT c FROM Book c")
public class Book extends HttpServlet {
 
    // ======================================
    // =             Attributes             =
    // ======================================
 
    @Id
    @GeneratedValue
    private Long id;
    private String isbn;
    private Integer nbOfPage;
    private Boolean illustrations;
    private String contentLanguage;
    @Column(nullable = false)
    @Size(min = 5, max = 50)
    @XmlElement(nillable = false)
    private String title;
    private Float price;
    @Column(length = 2000)
    @Size(max = 2000)
    private String description;
    @ElementCollection
    @CollectionTable(name = "tags")
    private List<String> tags = new ArrayList<>();
 
    // ======================================
    // =         Injected Resources         =
    // ======================================
 
    @XmlTransient
    @Transient
    @EJB
    private Book monsterEJB;
 
    @XmlTransient
    @Transient
    @PersistenceContext(unitName = "monsterPU")
    private EntityManager em;
 
    // ======================================
    // =        Servlet Entry Point         =
    // ======================================
 
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String title = request.getParameter("title");
        try {
            response.getWriter().println("Servlet calling EJB " + monsterEJB.createAndListBooks(title));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    // ======================================
    // =          Business methods          =
    // ======================================
 
    @GET
    @Path("/{title}")
    @Produces(MediaType.APPLICATION_XML)
    public List<Book> createAndListBooks(@PathParam("title") @NotNull String title) {
        // Sets data
        this.id = null;
        this.title = title + " " + new Date();
        this.price = new Float(0.01);
        this.description = "The hard-coded description";
        this.isbn = "978-1-4302-1954-5";
        this.nbOfPage = 210;
        this.illustrations = Boolean.TRUE;
        List<String> tags = new ArrayList<>();
        tags.add("Monster");
        tags.add("Component");
        this.tags = tags;
 
        // Persists the book
        em.persist(this);
 
        // Returns all books
        TypedQuery<Book> query = em.createNamedQuery("findAll", Book.class);
        List<Book> allBooks = query.getResultList();
        return allBooks;
    }
 
    // ======================================
    // =            Interceptor             =
    // ======================================
 
    @AroundInvoke
    public Object logMethod(InvocationContext ic) throws Exception {
        System.out.println(">>> " + ic.getTarget().getClass() + " - " + ic.getMethod().getName());
        try {
            return ic.proceed();
        } finally {
            System.out.println("<<< " + ic.getTarget().getClass() + " - " + ic.getMethod().getName());
        }
    }
 
    @PostConstruct
    private void prepare() {
        System.out.println("\n=> PostConstruct");
        System.out.println("================");
    }
 
    @PreDestroy
    private void release() {
        System.out.println("=============");
        System.out.println("=> PreDestroy");
    }
 
    (...)
}

Выполнить компонент монстра

Прежде всего, если вы  загрузите код  и запустите интеграционный тест, вы заметите, что  этот код действительно работает; o)  я добавил простую веб-страницу JSF и вспомогательный компонент для вызова компонента Monster несколькими способами (EJB, Сервлет и веб-сервис REST). Таким образом, вы можете упаковать все в файл войны и развернуть его в  GlassFish 4 . Существует также интеграционный тест, который проверяет, работает ли он во встроенном контейнере EJB.

Чего не хватает ?

Я мог бы добавить больше аннотаций … но вещи не всегда работают так, как мы хотим. Я хотел добавить возможности SOAP Web Service ( JAX-WS ) с аннотациями @WebService и @WebMethod. Прежде всего, если вы добавите аннотацию @WebService, это приведет к сбою интеграционного теста (веб-службы SOAP не являются частью встроенного контейнера EJB). Я также не смог сгенерировать WSDL, что-то не так с комбинацией аннотаций сопоставления JAX-WS и JPA. Также помните, что JAX-WS не является частью  веб-профиля Java EE 7 , как JAX-RS.

Сервлет должен ввести EJB. Невозможно использовать аннотацию CDI @Inject, потому что это циклическая ссылка (книга внедряет себя). Но с @EJB это работает. Поэтому я не добавил CDI в этом примере.

Вы видите что-нибудь еще, что можно добавить?

Что это показывает?

Этот пример кода показывает, что вам не нужно чрезмерно проектировать свой код и добавлять несколько отделенных архитектурных слоев. Просто поместите все в один класс; o) Этот код немного шокирует, несколько концепций встроены в один класс (без разделения интересов), и большинство из вас (включая меня) сочтут это уродливым. С другой стороны, наличие нескольких уровней, абстракции, интерфейсов, DAO, DTO и т. Д. Также ужасно (но, похоже, мы привыкли к такой сложности). Не помещайте все в один класс, но также не распространяйте беспокойство на несколько артефактов. Найти правильный баланс и поцелуй.

Вывод

В этом посте я показал вам, что философия Java EE 7 сводится к добавлению метаданных в класс Java и оставлению контейнера для выполнения работы. Метаданные могут быть определены с помощью XML или аннотаций. Класс Book превратился в компонент Monster, добавив как можно больше аннотаций. Класс может сохраняться в транзакционном режиме благодаря JPA и EJB, а также вызываться как сервлет и веб-служба RESTful, перехватывая каждый вызов метода и проверяя ограничения.