Статьи

Самозагрузка CDI в нескольких средах

 

скачатьЯ чувствую, что пишу несколько постов о CDI (контексты и инъекции зависимостей). Итак, это первый из серии сообщений x ( 0 <x <10 ). Я не буду подробно изучать историю CDI (ранее называвшуюся веб-компонентами, разделенными на две JSR … и т. Д.), Но постараюсь дать вам информацию о том, как использовать его в различных средах, объяснить вам внедрение, управление контекстом, область видимости. , декораторы и тд. Таким образом, вы можете думать об этой серии постов как о скромном пошаговом руководстве CDI. Вы также можете прочитать очень хорошую документацию на веб-сайте JBoss (где я получил некоторую помощь и вдохновение).

Версии программного обеспечения, используемого для этого артикля
Weld 1.1.0.CR1 (эталонная реализация CDI)
Java SE 1.6.0_23
GlassFish 3.0.1
Maven 3.0.1
Tomcat 6.0.29
Jetty 6.1.26

Этот первый пост о том, как использовать CDI, или, если быть точным, о том, как запустить его в нескольких средах. Какие среды вы можете использовать? Ну, во-первых, все контейнеры Java EE 6 ( Servlet 3.0 и EJB 3.1 ), но также и Java SE (да, простой POJO может использовать CDI). И, наконец, ваш старый контейнер Servlet 2.5, такой как Tomcat 6.x и Jetty 6.x.

Случай использования

Давайте начнем с самого лучшего варианта использования:   Hello World . В этом посте я не буду вдаваться в подробности о CDI, я просто хочу сосредоточиться на том, как его загрузить, поэтому я не использую много артефактов CDI… на самом деле, я просто использую инъекцию здесь. Для простоты я разработал класс Hello, который получает ссылку на класс World посредством внедрения (используя @ javax.inject.Inject). Затем я разработал несколько компонентов (POJO, EJB 3.1, Servlet 3.0 и Servlet 2.5), которые будут использовать эти классы посредством инъекций. Следующая диаграмма классов показывает все доступные классы этого примера:

Вот как выглядят два основных класса (Hello и World):

import javax.inject.Inject;

public class Hello {

    @Inject
    World world;

    public String sayHelloWorld() {
        return "Hello " + world.sayWorld();
    }
}

Мне нужно быть точным в моем описании. Аннотация @Inject не является частью спецификации CDI , а является частью спецификации @Inject ( JSR 330: внедрение зависимостей для Java ). Но @Inject — ничто без CDI, поэтому вы можете забыть эту текущую заметку, просто сосредоточиться на CDI.

public class World {

    public String sayWorld() {
        return "World !!!";
    }
}

Довольно просто, не правда ли? Не нужно много объяснять.

Старый добрый Maven

Я использую моего старого доброго друга Мейвена (ну, я так долго жаловался, что теперь привыкаю). Каждая среда начальной загрузки (Java SE, EJB, Servlet) будет разработана в отдельном проекте Maven. Родительский файл pom.xml определяет API-интерфейс CDI следующим образом:

<dependency>
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
</dependency>

Пустой beans.xml подойдет

Чтобы включить CDI, в вашем проекте должен быть файл beans.xml (в META-INF или WEB-INF). Это потому, что CDI необходимо идентифицировать bean-компоненты в вашем пути к классам (это называется bean discovery) и создать свою внутреннюю метамодель. С файлом beans.xml CDI знает, что он может обнаружить бины. Итак, для всех следующих примеров я сделаю это просто и оставлю этот файл полностью пустым.

Контейнеры Java EE 6

Начнем с самой простой среды: контейнеры Java EE 6 . Почему это самое простое? Ну, потому что вам не нужно ничего делать: CDI является частью Java EE 6, а также Web Profile 1.0, поэтому вам не нужно вручную загружать его. Давайте посмотрим, как внедрить компонент CDI в EJB 3.1 и сервлет 3.0 .

EJB 3.1

Начиная с EJB 3.1 вы можете использовать EJBContainer API для получения встроенного EJB-контейнера в памяти и легко тестировать свои EJB-модули. Итак, давайте напишем EJB и тестовый класс.

Сначала давайте посмотрим на код EJB. Как вы можете видеть, в версии 3.1 EJB — это просто POJO: без наследования, без интерфейса, только одна аннотация @Stateless. Он получает ссылку на покупку bean-компонента Hello с помощью аннотации @Inject и использует ее в методе saySomething ().

@Stateless
public class MainEJB31 {

    @Inject
    Hello hello;

    public String saySomething() {
        return hello.sayHelloWorld();
    }
}

Теперь вы можете упаковать классы MainEJB31, Hello и World с пустым файлом beans.xml в jar, развернуть его в GlassFish 3.x , и он будет работать. Но если вы не хотите развертывать его в GlassFish и просто тестировать его модулем, это то, что вам нужно сделать:

public class MainEJBTest {

    private static EJBContainer ec;
    private static Context ctx;

    @BeforeClass
    public static void initContainer() throws Exception {
        Map properties = new HashMap();
        properties.put(EJBContainer.MODULES, new File("target/classes"));
        ec = EJBContainer.createEJBContainer(properties);
        ctx = ec.getContext();
    }

    @AfterClass
    public static void closeContainer() throws Exception {
        if (ec != null)
            ec.close();
    }

    @Test
    public void shouldDisplayHelloWorld() throws Exception {
        // Looks up the EJB
        MainEJB31 mainEjb = (MainEJB31) ctx.lookup("java:global/classes/MainEJB!org.antoniogoncalves.cdi.helloworld.MainEJB");

        assertEquals("should say Hello World !!!", "Hello World !!!", mainEjb.saySomething());
    }
}

In the code above the method initContainer() initializes the EJBContainer. The shouldDisplayHelloWorld() looks up the EJB (using the new portable JNDI name), invokes it and makes sure the saySomething() method returns Hello World !!!. Green test. That was pretty easy too.

Servlet 3.0

Servlet 3.0 is part of Java EE 6, so again, there is no needed configuration to bootstrap CDI. Let’s use the new @WebServlet annotation and write a very simple one that injects a reference of Hello and displays an HTML page with Hello World !!!. This is what the Servlet looks like :

@WebServlet(urlPatterns = "/mainServlet")
public class MainServlet30 extends HttpServlet {

    @Inject
    Hello hello;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("<HTML>");
        out.println("<HEAD><TITLE>Bootstrap CDI</TITLE></HEAD>");
        out.println("<BODY>");
        out.println(saySomething());
        out.println("</BODY>");
        out.println("</HTML>");
        out.close();
    }

    public String saySomething() {
        return hello.sayHelloWorld();
    }
}

Thanks to the @WebServlet I don’t need any web.xml (it’s optional in Servlet 3.0) to map the MainServlet30 to the /mainServlet url. You can now package the MainServlet30, Hello and World classes with the empty beans.xml and no web.xml into a war, deploy it to GlassFish 3.x, go to http://localhost:8080/bootstrapping-servlet30-1.0/mainServlet and it will work.

Unfortunately Servlet 3.0 doesn’t have an API for the container (such as EJBContainer). There is no ServletContainer API that would let you use an embedded servlet container in a standard way and, why not, easily unit test it.

Application Client Container

Not many people know it, but Java EE (or even older J2EE versions) comes with an application client container (ACC). It’s like an EJB or Servlet container but for plain POJOs. For example you can develop a Swing application (yes, I’m sure that some of you still use Swing), run it into the ACC and get some extra services given by the container (security, naming, certain annotations…). GlassFish v3 has an ACC that you can launch in a command line : appclient -jar <the name of your jar>.

So I thought, great, I can use CDI with ACC the same way I use it within EJB or Servlet container, no need to bootstrap anything, it’s all out of the box. I was wrong. As per the CDI specification (Section 12.1), CDI is not required to support application client bean archives. So the GlassFish application client container doesn’t support it. I haven’t tried the JBoss ACC, maybe it works.

Other containers

The beauty of CDI is that it doesn’t require Java EE 6. You can use CDI with simple POJOs in a Java SE environment, as well as some Servlet 2.5 containers. Of course it’s not as easy to bootstrap because you need a bit of configuration. But it then works fine (not always but).

Java SE 6

Ok, so until now there was nothing to do to bootstrap CDI. It is already bundled with the EJB 3.1 and Servlet 3.0 containers of Java EE 6 (and Web Profile). So the idea here is to use CDI in a simple Java SE environment. Coming back to our Hello and World classes, we need a POJO with an entry point that will bootstrap CDI so we can use injection to get those classes. In standard Java SE when we say entry point, we think of a public static void main(String[] args) method. Well, we need something similar… but different.

Weld is the reference implementation of CDI. That means it implements the specification, the standard APIs (mostly found in javax.inject and javax.enterprise.context packages) but also some proprietary code (in org.jboss.weld package). Bootstrapping CDI in Java SE is not specified so you will need to use specific Weld features. You can do that in two different flavors: by observing the ContainerInitialized event or using the programatic bootstrap API consisting of the Weld and WeldContainer classes.

The following code uses the ContainerInitialized event. As you can see, it uses the @Observes annotation that I’ll explain in a future post. But the idea is that this class is listening to the event and processes the code once the event is triggered.

import org.jboss.weld.environment.se.events.ContainerInitialized;
import javax.enterprise.event.Observes;
import javax.inject.Inject;

public class MainJavaSE6 {

    @Inject
    Hello hello;

    public void saySomething(@Observes ContainerInitialized event) {
        System.out.println(hello.sayHelloWorld());
    }
}

But who trigers the ContainerInitialized event ? Well, it’s the org.jboss.weld.environment.se.StartMain class. I’m using Maven so a nice trick is to use the exec-maven-plugin to run the StartMain class. Download the code, have a look at the pom.xml and give it a try.

The other possibility is to programmatically bootstrap the Weld container. This can be handy in unit testing. The code below initializes the Weld container (with new Weld().initialize()) and then looks for the Hello class (using weld.instance().select(Hello.class).get()).

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
import org.junit.BeforeClass;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;

public class HelloTest {

    @Test
    public void shouldDisplayHelloWorld() {
        WeldContainer weld = new Weld().initialize();
        Hello hello = weld.instance().select(Hello.class).get();
        assertEquals("should say Hello World !!!", "Hello World !!!", hello.sayHelloWorld());
    }
}

Execute the test with mvn test and it should be green. As you can see, there is a bit more work using CDI in a Java SE environment, but it’s not that complicated.

Tomcat 6.x

Ok, and what about your legacy Servlet 2.5 containers ? The first one that comes in mind is Tomcat 6.x (note that Tomcat 7.x will implement Servlet 3.0 but is still in beta version at the time of writing this post). Weld provides support for Tomcat but you need to configure it a bit to make CDI work.

First of all, this is a Servlet 2.5, not a 3.0. So the code of the servlet is slightly different from the one seen before (no annotation allowed) and of course, you need your good old web.xml file :

public class MainServlet25 extends HttpServlet {

    @Inject
    Hello hello;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("<HTML>");
        out.println("<HEAD><TITLE>Bootstrap CDI in Tomcat</TITLE></HEAD>");
        out.println("<BODY>");
        out.println(saySomething());
        out.println("</BODY>");
        out.println("</HTML>");
        out.close();
    }

    public String saySomething() {
        return hello.sayHelloWorld();
    }
}

Because we don’t have a @WebServlet annotation in Servlet 2.5, we need to declare and map it in the web.xml (using the servlet and servlet-mapping tags). Then, you need to explicitly specify the servlet listener to boot Weld and control its interaction with requests (org.jboss.weld.environment.servlet.Listener). Tomcat has a read-only JNDI, so Weld can’t automatically bind the BeanManager extension SPI. To bind the BeanManager into JNDI, you should populate META-INF/context.xml and make the BeanManager available to your deployment by adding it to your web.xml:

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

    <servlet>
        <servlet-name>MainServlet25</servlet-name>
        <servlet-class>org.antoniogoncalves.cdi.bootstrapping.servlet.MainServlet25</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>MainServlet25</servlet-name>
        <url-pattern>/mainServlet</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
    </listener>

    <resource-env-ref>
        <resource-env-ref-name>BeanManager</resource-env-ref-name>
        <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
    </resource-env-ref>

</web-app>

The META-INF/context.xml file is an optional file which contains a Context for a single Tomcat web application. This can be used to define certain behaviours for your application, JNDI resources and other settings.

<Context>
    <Resource name="BeanManager"
              auth="Container"
              type="javax.enterprise.inject.spi.BeanManager"
              factory="org.jboss.weld.resources.ManagerObjectFactory"/>
</Context>

Package all the files (MainServlet25, Hello, World, META-INF/context.xml, beans.xml and web.xml) into a war and deploy it into Tomcat 6.x. Go to http://localhost:8080/bootstrapping-servlet25-tomcat-1.0/mainServlet and you will see your Hello World page.

Jetty 6.x

Another famous Servlet 2.5 containers is Jetty 6.x (at Codehaus) and Jetty 7.x (note that Jetty 8.x will implement Servlet 3.0 but it’s still in experimental stage at the time of writing this post). If you look at the Weld documentation,  there is actually support for Jetty 6.x and 7.x. The code is the same one as Tomcat (because it’s a Servlet 2.5 container), but the configuration changes. With Jetty you need to add two files under WEB-INF : jetty-env.xml and jetty-web.xml :

<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN"
        "http://jetty.mortbay.org/configure.dtd">
<Configure id="webAppCtx" class="org.mortbay.jetty.webapp.WebAppContext">
<New id="BeanManager" class="org.mortbay.jetty.plus.naming.EnvEntry">
      <Arg><Ref id="webAppCtx"/></Arg>
      <Arg>BeanManager</Arg>
      <Arg>
         <New class="javax.naming.Reference">
            <Arg>javax.enterprise.inject.spi.BeanManager</Arg>
            <Arg>org.jboss.weld.resources.ManagerObjectFactory</Arg>
            <Arg/>
         </New>
      </Arg>
      <Arg type="boolean">true</Arg>
   </New>
</Configure>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN"
        "http://jetty.mortbay.org/configure.dtd">
<Configure id="webAppCtx" class="org.mortbay.jetty.webapp.WebAppContext">
    <Call class="org.jboss.weld.environment.jetty.WeldServletHandler" name="process">
        <Arg>
            <Ref id="webAppCtx"/>
        </Arg>
    </Call>
</Configure>

Package all the files (MainServlet25, Hello, World, WEB-INF/jetty-env.xml, WEB-INF/jetty-web.xml, beans.xml and web.xml) into a war and deploy it into Jetty 6.x. Go tohttp://localhost:8080/bootstrapping-servlet25-jetty6/mainServlet and you will see your Hello World page.

There was a mistake in the Weld documentation so I couldn’t make it work. I started a thread on the Weld forum and thanks to Dan Allen , Pete Muir and all the Weld team, this was fixed and I managed to make it work. Simple as posting an email to the forum. Thanks for your help guys.

Spring 3.x

Here is the tricky part. Spring 3.x implements the JSR 330 : Dependency Injection for Java, which means that @Inject works out of the box. But I didn’t find a way to integrate CDI with Spring 3.x. The Weld documentation mentions that because of its extension points, “integration with third-party frameworks such as Spring (…) was envisaged by the designers of CDI“. I did find this blog that simulates CDI features by enabling Spring ones. What I didn’t find is a clear statement or roadmap on SpringSource about supporting CDI or not in future releases. The last trace of this topic is a comment on a long TSS flaming thread. At that time (16 december 2009), Juergen Huller said “With respect to implementing CDI on top of Spring (…) Trying to hammer it into the semantic frame of another framework such as CDI would be an exercise that is certainly achievable (…) but ultimately pointless“. But if you have any fresh news about it, let me know.

Conclusion

As I said, this post is not about explaining CDI, I’ll do that in future posts. I just wanted to focus on how to bootstrap it in several environments so you can try by yourself. As you saw, it’s much simpler to use CDI within an EJB 3.1 or Servlet 3.0 container in Java EE 6. I’ve used GlassFish 3.x but it should also work with other Java EE 6 or Web Profile containers such as JBoss 6 or Resin.

When you don’t use Java EE 6, there is a bit more work to do. Depending on your environment or servlet container you need some configuration to bootstrap Weld. By the way, I’ve used Weld because it’s the reference implementation, the one bunddled with GlassFish and JBoss. But you could also use OpenWebBeans, another CDI implementation.

Download the code, give it a try, and give me some feedback.

 

From http://agoncal.wordpress.com/2011/01/12/bootstrapping-cdi-in-several-environments/