EJB基础概念

EJB(Enterprise JavaBean)是JavaEE平台中用于开发和部署企业级分布式业务组件的技术。Sun公司对EJB的定义是用于开发和部署多层结构、分布式、面向对象的Java应用系统的跨平台构件体系结构,在JavaEE体系中,EJB也是构成企业级应用程序的核心规范。本系列笔记我们将基于最新的JavaEE8、EJB3.2和Wildfly14应用服务器对EJB技术进行介绍。

关于EJB和Spring的说明

EJB目前不是一个流行的技术,这有一定的历史原因。在早期的EJB2.0时代,EJB因其过度复杂和低效的设计而饱受诟病,很多采用JavaEE技术栈的项目被它推入深渊,Spring及其生态的快速崛起也让标准JavaEE(主要指EJB部分)错过了一个历史发展机遇,在那一时期大部分用户都迁移到了Spring生态中,尽管EJB3.0大幅简化了开发模型,但历史是有惯性的,即使EJB是“JavaEE标准”的,且无论EJB如何向Spring学习、设计的如何美好,在未来可预见的一段时间里,都不会有项目再采用EJB这个“冷门”技术了。

不过无论如何,作为标准JavaEE规范中的核心部分,我们对EJB进行学习和了解也是有意义的,通过和Spring的对比,我们可以更深入的理解两种技术体系的相似点和区别,在实际的开发中我们可以取长补短,编写符合最佳实践的代码。

EJB的核心概念

EJB规范将一般的业务抽象成了可被调用的“组件”,这种组件被称为企业级JavaBean(Enterprise JavaBean,即EJB)。

EJB容器:EJB需要部署在EJB容器内,EJB容器负责管理EJB的生命周期以及协助EJB对外提供服务。通常来说EJB容器是标准JavaEE应用服务器(例如Wildfly)的一个模块,因此EJB通常都部署在JavaEE应用服务器上。

EJB客户端:EJB是提供服务的业务组件,EJB可以被其它模块通过本地方法或远程(RMI)调用,而调用这些业务组件的就是EJB的“客户端”,它们可能是同样位于JavaEE应用服务器上的Servlet、JSF托管Bean组件,或是位于客户机的Swing程序等。通常JavaEE应用服务器会基于JNDI暴露EJB的调用路径,我们可以在“客户端”代码中通过JNDI调用本地或远程EJB。此外EJB本身也是CDI托管的,在Servlet、JSF或是其它EJB内我们可以通过依赖注入的形式注入EJB,这需要用到@EJB注解。

会话Bean(Session Bean):会话Bean是最常见的EJB形式,会话Bean接受客户端的本地或远程调用并返回结果。会话Bean的种类包括无状态会话Bean(Stateless Session Bean)有状态会话Bean(Stateful Session Bean)单例会话Bean(Singleton Session Bean)

消息驱动Bean(Message-Driven Bean,MDB):消息驱动Bean可以简单理解为一个JMS消费者组件,JMS中的消息可以触发消息驱动Bean内业务逻辑的异步执行。

通过上面这些概念,我们应该大致能理解EJB是个什么东西了。我们都知道企业级应用中的三层架构,它将一个工程划分为了表现层、业务逻辑层和持久层。举例来说,假如我们使用标准JavaEE开发一个银行系统,这个程序的Web部分(表现层)可以使用Servlet/JSP实现,业务逻辑层将使用EJB实现,而持久层可以选择JPA或JDBC等技术实现。对于一个转账功能,用户输入的数据是来源账户、目标账户和转账金额(为了保证操作幂等性通常还有个交易流水号),表现层负责展示操作页面并校验请求数据,业务逻辑层负责具体扣减来源账户并将金额加入目标账户,此外还需要把这一系列操作包裹在数据库事务中,这些就是所谓的业务逻辑,这种业务逻辑层组件就是EJB在一个JavaEE应用程序中的定位。

搭建EJB工程

在JavaEE6版本之前,EJB通常需要单独打包到EJB JAR文件中并部署到JavaEE应用服务器,EJB JAR还可以和Web应用程序的WAR包共同构建为EAR包(Enterprise Application Archive)并整体部署到JavaEE应用服务器。在JavaEE6后EJB的部署进一步简化了,现在WAR包其实可以直接包含EJB,这意味着Servlet、JSP、EJB等可以放在一个模块中,只需要构建一个WAR包即可,这在小型项目中简化了代码模块的拆分和管理工作。

不过我们这里还是以比较经典的EAR作为例子,我们将使用Maven构建系统搭建一个简单的多模块EAR例子工程并部署到Wildfly14应用服务器上。工程的Maven模块划分如下。

netstore
  |_ netstore-api # 接口模块,包含EJB接口和接口依赖的DTO类
    |_ pom.xml
  |_ netstore-ejb # EJB实现类模块
    |_ pom.xml
  |_ netstore-web # JavaWeb模块,包含Servlet、JSP等内容
    |_ pom.xml
  |_ netstore-ear # EAR构建模块,不包含代码,仅用于将EJB JAR和WAR打包为EAR
    |_ pom.xml
  |_ pom.xml

模块划分乍一看可能有点复杂,其中netstore-api是一个基础模块,它包含了EJB接口类和传输数据的DTO类,netstore-ejbnetstore-web都依赖于它,该模块最终构建为一个JAR文件;netstore-ejb包含具体的EJB实现代码,它最终构建为EJB JAR文件;netstore-web是包含Servlet、JSP等内容的Web模块,它最终构建为WAR包;netstore-ear中没有Java代码,它用于合并EJB JAR和Web项目的WAR,构建EAR包。

工程的根pom.xml内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gacfox.netstore</groupId>
    <artifactId>netstore</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <modules>
        <module>netstore-web</module>
        <module>netstore-ejb</module>
        <module>netstore-api</module>
        <module>netstore-ear</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.gacfox.netstore</groupId>
                <artifactId>netstore-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>com.gacfox.netstore</groupId>
                <artifactId>netstore-ejb</artifactId>
                <version>1.0-SNAPSHOT</version>
                <type>ejb</type>
            </dependency>
            <dependency>
                <groupId>com.gacfox.netstore</groupId>
                <artifactId>netstore-web</artifactId>
                <version>1.0-SNAPSHOT</version>
                <type>war</type>
            </dependency>
            <dependency>
                <groupId>com.gacfox.netstore</groupId>
                <artifactId>netstore-ear</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>javax</groupId>
                <artifactId>javaee-api</artifactId>
                <version>8.0.1</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet.jsp.jstl</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

netstore-api 模块

HelloService.java

package com.gacfox.netstore.api;

import javax.ejb.Local;

@Local
public interface HelloService {
    String hello();
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.gacfox.netstore</groupId>
        <artifactId>netstore</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>netstore-api</artifactId>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

netstore-api模块包含了一个接口类,它使用@Local标注该接口是一个本地的EJB接口。

netstore-ejb 模块

HelloServiceImpl.java

package com.gacfox.netstore.ejb;

import com.gacfox.netstore.api.HelloService;

import javax.ejb.Stateless;

@Stateless
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello() {
        return "Hello, EJB!";
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.gacfox.netstore</groupId>
        <artifactId>netstore</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>netstore-ejb</artifactId>
    <packaging>ejb</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.gacfox.netstore</groupId>
            <artifactId>netstore-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

EJB模块中的Java代码包含了EJB实现类,其中@Stateless注解标注该类是无状态会话Bean,这个类实现了netstore-api模块中定义的接口类。

netstore-ejb模块依赖netstore-api模块,不过这里注意这个依赖需要标注为<scope>provided</scope>,这是因为后面EAR包构建中我们会把netstore-api模块构建生成的JAR放在EAR包的统一依赖目录下,这里构建EJB JAR内如果重复包含了netstore-api模块JAR,可能导致接口类被JavaEE应用服务器的Module类加载器重复加载,出现明明是同一个Java接口类但却被JVM认为是不同类的奇怪错误。

netstore-web 模块

HelloServlet.java

package com.gacfox.netstore.web.servlet;

import com.gacfox.netstore.api.HelloService;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "HelloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @EJB
    private HelloService helloService;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String result = helloService.hello();
        resp.getWriter().write(result);
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.gacfox.netstore</groupId>
        <artifactId>netstore</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>netstore-web</artifactId>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.gacfox.netstore</groupId>
            <artifactId>netstore-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

netstore-web模块中我们编写了一个Servlet类,它使用@EJB注入了EJB类,这个注入是JavaEE应用服务器帮我们完成的。Servlet代码中,我们调用了EJB并将结果输出到响应流中。netstore-web模块中还包含了webapp/WEB-INF/web.xml描述文件,这里就不列出了。有关JavaWeb工程的内容可以参考Servlet相关章节。

netstore-web模块也依赖netstore-api,不过它也需要标注为<scope>provided</scope>,理由和之前相同。

netstore-ear 模块

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.gacfox.netstore</groupId>
        <artifactId>netstore</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>netstore-ear</artifactId>
    <packaging>ear</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.gacfox.netstore</groupId>
            <artifactId>netstore-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.gacfox.netstore</groupId>
            <artifactId>netstore-ejb</artifactId>
            <type>ejb</type>
        </dependency>
        <dependency>
            <groupId>com.gacfox.netstore</groupId>
            <artifactId>netstore-web</artifactId>
            <type>war</type>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ear-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <version>8</version>
                    <defaultLibBundleDir>lib</defaultLibBundleDir>
                    <modules>
                        <ejbModule>
                            <groupId>com.gacfox.netstore</groupId>
                            <artifactId>netstore-ejb</artifactId>
                            <bundleDir>/</bundleDir>
                        </ejbModule>
                        <webModule>
                            <groupId>com.gacfox.netstore</groupId>
                            <artifactId>netstore-web</artifactId>
                            <contextRoot>/</contextRoot>
                        </webModule>
                    </modules>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

netstore-ear模块中我们使用了maven-ear-plugin这个插件,它用于将依赖的EJB JAR、WAR等资源合并构建为EAR包。EAR包有特定的包结构,该插件可以帮我们自动按照EAR包结构组装部署包,以及填写EAR包的描述配置文件等操作。

  • <version>:该配置指定EAR包使用JavaEE 8版本
  • <defaultLibBundleDir>:该配置指定了EAR包的JAR依赖放置的目录,这样配置后netstore-api构建的JAR就会放在EAR包内/lib
  • <modules>:该配置指定了EAR包内的部署模块,我们这里有EJB和Web两个模块

按照如上配置,最终构建的EAR包内部大致结构如下(EAR包本身也是ZIP格式的压缩包,可以用7z等压缩软件查看内部结构)。

netstore-ear-1.0-SNAPSHOT.ear
  |_ lib
    |_ com.gacfox.netstore-netstore-api-1.0-SNAPSHOT.jar
  |_ META-INF
    |_ application.xml
    |_ MANIFEST.MF
  |_ com.gacfox.netstore-netstore-ejb-1.0-SNAPSHOT.jar
  |_ com.gacfox.netstore-netstore-web-1.0-SNAPSHOT.war

部署EAR工程

执行Maven命令构建项目。

mvn clean package

执行完成后,我们可以在netstore-ear模块的target目录下找到netstore-ear-1.0-SNAPSHOT.ear文件,将其复制到Wildfly14应用服务器的部署路径下即可。以Wildfly14单实例(standalone)模式启动为例,我们可以将EAR包复制到wildfly-14.0.1.Final\standalone\deployments文件夹内。

执行以下命令即可启动Wildfly应用服务器。

standalone.sh -c standalone-full.xml

启动成功后,我们即可使用浏览器访问http://localhost:8080/hello查看Servlet的输出结果了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap