本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《Java EE 6企业级应用开发教程》是一门面向高等教育的系统性课程,全面讲授基于Java EE 6平台的企业级应用开发技术。课程涵盖Servlet、JSP、JSF、EJB、JPA、JMS、CDI、WebSocket、安全性配置、应用部署与测试等核心技术,帮助学生掌握构建分布式、多层架构Web应用的关键能力。通过理论与实践结合,学生不仅能深入理解Java EE 6的架构体系,还能熟练使用主流IDE进行项目开发与测试,为通过Oracle Java EE相关认证考试及从事企业级开发工作奠定坚实基础。
高教类课件:Java EE 6企业级应用开发教程

1. Java EE 6平台概述与企业级开发基石

Java EE 6建立在Java SE基础之上,专为构建高可用、分布式、多层企业应用而设计。其核心在于通过容器(如Web容器、EJB容器)对组件进行生命周期管理,实现事务、安全、并发等服务的自动化处理,显著提升系统的可维护性与扩展性。

graph TD
    A[客户端] --> B[表示层(Servlet/JSP/JSF)]
    B --> C[业务逻辑层(EJB)]
    C --> D[数据持久层(JPA)]
    D --> E[数据库/外部资源]

相较于传统“重量级”开发,Java EE 6引入注解驱动(如 @Stateless @WebServlet )和模块化Profile(如Web Profile),推动轻量化演进,降低复杂度,为现代企业开发奠定标准化基石。

2. Java EE Web Profile轻量级架构解析

在企业级Java开发的演进历程中,Java EE 6引入了一个极具战略意义的技术子集—— Java EE Web Profile 。它并非一个独立平台,而是对完整Java EE规范的精简与聚焦,专为Web应用开发者设计,去除了传统“重量级”组件如JMS、JCA、CORBA等非必要模块,保留了构建现代Web系统最核心的功能集。这一转变标志着Java EE从臃肿复杂的分布式系统框架向敏捷、可快速部署的Web应用平台转型的重要一步。Web Profile的核心价值在于其“轻量化”理念:通过减少依赖、简化配置、提升启动速度和降低运维复杂度,使开发者能够更专注于业务逻辑实现而非基础设施搭建。

该架构的设计哲学体现了容器驱动、注解优先、模块化部署和跨服务器可移植性的统一。其技术栈围绕Servlet、JSP、JSF、EJB Lite、JPA和CDI六大支柱展开,形成了一套自洽且高效的开发闭环。尤其值得注意的是,Web Profile并未牺牲企业级能力,而是在保证事务管理、安全控制、依赖注入和持久化支持的前提下,剔除了仅适用于大型分布式场景的冗余功能。例如,EJB 3.1 Lite虽然不支持消息驱动Bean或远程调用,但完全具备本地会话Bean的能力,并能无缝集成容器管理事务(CMT),足以满足绝大多数中小型Web应用的需求。

更为深远的影响体现在开发模式的变革上。Web Profile推动了“约定优于配置”的编程范式普及,大量使用Java SE 5引入的注解机制替代传统的 web.xml ejb-jar.xml 部署描述符,显著减少了XML配置负担。同时,CDI(Contexts and Dependency Injection)作为JSR-299标准的引入,首次在Java EE中提供了统一的依赖注入模型,打通了Servlet、JSF Managed Bean与EJB之间的调用壁垒,实现了真正意义上的组件松耦合。这种以运行时容器为核心、以注解为纽带的开发方式,极大提升了代码的可测试性与可维护性。

此外,Web Profile的标准化还促进了应用服务器生态的良性竞争。GlassFish作为官方参考实现,WildFly(原JBoss AS)、TomEE等开源容器纷纷提供完整的Web Profile兼容支持,使得开发者可以在不同厂商之间自由迁移而无需重写代码。Maven等构建工具也针对Web Profile优化了原型模板(archetype),一键生成符合规范的项目结构,进一步降低了入门门槛。可以说,Java EE Web Profile不仅是技术上的瘦身,更是开发理念的一次重构,为后续微服务架构时代的轻量级Java应用奠定了坚实基础。

2.1 Web Profile核心组件构成

Java EE Web Profile之所以被称为“轻量级”,并非因为功能缺失,而是因为它精准地选取了构建现代Web应用所必需的最小功能集合。这些组件共同构成了一个高内聚、低耦合的应用运行环境,既避免了传统Java EE的过度工程化问题,又保留了企业级系统的稳定性与扩展能力。本节将深入剖析三大关键组件群:表示层技术(Servlet/JSP/JSF)、业务逻辑容器(EJB 3.1 Lite)以及数据与上下文管理机制(JPA/CDI),揭示它们如何协同工作以支撑典型Web应用的全生命周期管理。

2.1.1 Servlet、JSP与JSF的协同机制

在Java EE Web Profile中,表示层由Servlet、JSP和JSF三者共同承担,各自扮演不同角色并形成清晰的职责划分。Servlet作为底层HTTP请求处理器,负责接收客户端请求、执行控制逻辑并分发响应;JSP则用于视图渲染,将动态内容嵌入HTML模板;而JSF作为基于组件的MVC框架,则提供了更高层次的UI抽象与事件驱动编程模型。

三者的协同通常遵循以下流程:

sequenceDiagram
    participant Client
    participant Servlet
    participant JSF_FacesServlet
    participant JSP_Page
    participant Model_Bean

    Client->>Servlet: HTTP Request (e.g., /login.xhtml)
    Servlet->>JSF_FacesServlet: Forward to FacesServlet
    JSF_FacesServlet->>Model_Bean: Instantiate & Bind UI Components
    Model_Bean-->>JSF_FacesServlet: Return View Model
    JSF_FacesServlet->>JSP_Page: Render XHTML via Facelets
    JSP_Page-->>Client: HTML Response

上述流程展示了JSF请求处理的标准路径。当用户访问 .xhtml 页面时,请求首先被映射到 FacesServlet (注册在 web.xml 中),该Servlet初始化 FacesContext ,加载关联的Managed Bean(现多由CDI管理),执行恢复视图、应用请求值、处理验证、更新模型、调用行为、渲染响应六个生命周期阶段,最终通过Facelets引擎将XHTML模板转换为HTML输出。

相比之下,传统Servlet+JSP组合更为直接:

@WebServlet("/user/login")
public class LoginServlet extends HttpServlet {
    @Inject
    private UserService userService;

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        if (userService.authenticate(username, password)) {
            req.getSession().setAttribute("user", username);
            resp.sendRedirect("dashboard.jsp"); // 跳转至JSP视图
        } else {
            req.setAttribute("error", "Invalid credentials");
            req.getRequestDispatcher("login.jsp").forward(req, resp);
        }
    }
}

代码逻辑逐行解读:

  • @WebServlet("/user/login") :使用注解代替 web.xml 配置URL映射,容器自动注册该类为Servlet。
  • @Inject :CDI依赖注入,获取 UserService 实例,无需手动new或查找JNDI。
  • doPost() :处理POST请求,提取表单参数。
  • req.getParameter() :从HTTP请求体中读取指定字段值。
  • userService.authenticate() :调用业务层进行认证,体现分层思想。
  • req.getSession().setAttribute() :将登录状态保存在Session中,实现会话跟踪。
  • resp.sendRedirect() vs forward() :前者是客户端跳转(URL变化),后者是服务器内部转发(URL不变)。

该模式下,Servlet充当控制器(Controller),JSP作为视图(View),JavaBean作为模型(Model),构成经典的MVC架构。而JSF则在此基础上进一步封装,将HTML元素抽象为UI组件对象(如 HtmlInputText ),并通过EL表达式绑定后端属性,实现声明式开发。

特性 Servlet+JSP JSF
开发模式 过程式控制流 组件化事件驱动
视图技术 JSP / JSTL Facelets (.xhtml)
状态管理 手动管理Session/Request 内置View State机制
可复用性 较低,需自定义Taglib 高,支持复合组件
AJAX支持 需集成第三方库 原生支持 <f:ajax> 标签

由此可见,JSF更适合需要复杂表单交互的企业级前端,而Servlet+JSP更适合轻量级API接口或简单页面渲染。

2.1.2 EJB 3.1 Lite与完整版功能对比

EJB 3.1 Lite是Java EE Web Profile中唯一允许使用的EJB子集,旨在提供轻量级业务逻辑容器服务,同时排除分布式企业级特性以保持简洁性。尽管名为“Lite”,但它仍继承了EJB 3.1的核心优势:无须继承特定基类、基于POJO开发、支持注解驱动配置、容器管理事务(CMT)和依赖注入。

以下是EJB 3.1 Lite与完整版的主要功能对比表:

功能特性 EJB 3.1 Lite(Web Profile) 完整EJB 3.1
无状态会话Bean(@Stateless) ✅ 支持
有状态会话Bean(@Stateful) ✅ 支持
Singleton Session Bean ✅ 支持
消息驱动Bean(MDB) ❌ 不支持
远程接口调用(@Remote) ❌ 不支持
定时器服务(@Schedule) ✅ 有限支持(仅本地触发) ✅ 全面支持
依赖注入(@EJB, @Inject)
容器管理事务(CMT)
Bean管理事务(BMT)
持久化上下文注入(@PersistenceContext)
JCA资源适配器集成

可以看出,EJB 3.1 Lite保留了本地业务逻辑封装所需的所有关键能力。例如,一个典型的无状态服务Bean可以这样定义:

@Stateless
@Local(UserService.class)
public class UserServiceImpl implements UserService {

    @PersistenceContext
    private EntityManager em;

    @Override
    public User findByUsername(String username) {
        return em.createQuery("SELECT u FROM User u WHERE u.username = :username", User.class)
                 .setParameter("username", username)
                 .getSingleResult();
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void createUser(User user) {
        em.persist(user);
    }
}

参数说明与逻辑分析:

  • @Stateless :标识此Bean为无状态会话Bean,由容器池化管理,线程安全。
  • @Local :指定本地接口,仅在同一JVM内调用,适合Web Profile内部调用场景。
  • @PersistenceContext :注入JPA实体管理器,用于数据库操作。
  • EntityManager.createQuery() :创建类型安全的JPQL查询。
  • @TransactionAttribute :显式声明事务边界, REQUIRED 表示若已有事务则加入,否则新建。

该Bean可在Servlet或CDI组件中通过 @EJB 注入使用:

@WebServlet("/profile")
public class ProfileServlet extends HttpServlet {
    @EJB
    private UserService userService;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws IOException {
        String user = req.getParameter("user");
        User profile = userService.findByUsername(user);
        resp.getWriter().println("Name: " + profile.getName());
    }
}

尽管缺少远程调用和MDB,但在大多数Web应用中,这些功能并非必需。因此,EJB 3.1 Lite在性能、易用性和功能性之间取得了良好平衡。

2.1.3 JPA与CDI在轻量级应用中的角色定位

在Java EE Web Profile中,JPA(Java Persistence API)和CDI(Contexts and Dependency Injection)分别承担数据持久化与组件协作的核心职责,二者相辅相成,构成了应用骨架的基础支撑。

JPA作为ORM标准,屏蔽了底层数据库差异,允许开发者以面向对象的方式操作数据。其核心组件包括:

  • @Entity :标记POJO为持久化实体;
  • EntityManager :执行CRUD操作;
  • Persistence Context :管理实体生命周期;
  • JPQL :类型安全的查询语言。

示例实体定义如下:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdAt;

    // getters and setters...
}

而CDI则是整个Web Profile的“粘合剂”。它不仅替代了旧有的 @ManagedBean @EJB 的部分功能,还提供了统一的作用域管理(如 @RequestScoped , @SessionScoped )、拦截器、观察者模式等高级特性。

@SessionScoped
public class ShoppingCart implements Serializable {
    private List<Item> items = new ArrayList<>();

    public void addItem(Item item) {
        items.add(item);
    }

    // scope-aware destruction
    @PreDestroy
    public void cleanup() {
        System.out.println("Cart cleared for session");
    }
}

结合使用时,可通过CDI注入带有 @PersistenceContext 的DAO:

@RequestScoped
public class UserDAO {
    @PersistenceContext
    private EntityManager em;

    public List<User> findAll() {
        return em.createQuery("FROM User", User.class).getResultList();
    }
}

并通过 @Inject 在其他组件中使用:

@Inject
private UserDAO userDAO;

这种方式实现了真正的松耦合:组件不再关心依赖如何创建或销毁,只需声明所需服务即可,由容器在运行时完成装配。这种基于契约的设计极大增强了系统的可测试性与可扩展性。

综上所述,Web Profile通过精选组件、强化容器服务能力、推广注解驱动开发,成功构建了一个高效、灵活且易于维护的轻量级企业级开发平台,为现代Java Web应用的发展指明了方向。

3. Servlet与JSP动态Web应用开发

在企业级Java Web开发的演进历程中,Servlet与JSP作为Java EE平台中最基础、最核心的技术组件,承担着处理HTTP请求、生成动态内容以及构建用户交互界面的关键职责。尽管现代框架如Spring MVC和JSF已广泛普及,理解原生Servlet与JSP的工作机制仍然是掌握Java Web底层原理不可或缺的一环。本章节将深入剖析基于Servlet与JSP的动态Web应用开发全过程,涵盖从HTTP协议整合、请求响应处理、会话管理、过滤器链设计,到视图层优化及安全防护等多个维度,帮助开发者建立对Web层技术栈的系统性认知。

通过本章的学习,读者不仅能够熟练使用 HttpServletRequest HttpServletResponse 进行精细化控制,还能掌握如何利用JSP编译机制实现高效的页面渲染,并结合EL表达式与JSTL标签库提升代码可读性。此外,还将探讨异常处理策略、跨站攻击防御机制以及日志监控集成方案,确保所构建的应用具备良好的健壮性与安全性。

3.1 请求响应模型与HTTP协议深度整合

Java EE中的Servlet技术本质上是对HTTP协议的封装与扩展。每一个HTTP请求到达服务器后,都会被Web容器(如Tomcat或GlassFish)映射到一个具体的Servlet实例上执行。这种基于“请求-响应”模型的设计模式,使得开发者可以精确控制客户端与服务端之间的通信流程。本节将重点解析 HttpServletRequest HttpServletResponse 对象的操作方式,介绍表单数据的获取方法、会话状态的维护手段,以及过滤器链在请求预处理中的关键作用。

3.1.1 HttpServletRequest与HttpServletResponse对象操作

当客户端发起HTTP请求时,Web容器会自动创建一个 HttpServletRequest 对象来封装所有请求信息,包括请求头、参数、方法类型(GET/POST)、URI路径等。与此同时, HttpServletResponse 对象用于构造返回给客户端的响应内容,包括状态码、响应头、正文输出流等。

以下是一个典型的Servlet中处理GET请求并输出HTML内容的示例:

@WebServlet("/user/profile")
public class UserProfileServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");

        // 获取输出流
        PrintWriter out = response.getWriter();

        // 获取请求参数
        String userId = request.getParameter("id");
        if (userId == null || userId.isEmpty()) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing user ID");
            return;
        }

        // 模拟用户信息查询
        String userName = "张三";
        int age = 28;

        // 输出HTML页面
        out.println("<!DOCTYPE html>");
        out.println("<html><head><title>用户资料</title></head><body>");
        out.println("<h1>用户资料</h1>");
        out.println("<p><strong>ID:</strong> " + userId + "</p>");
        out.println("<p><strong>姓名:</strong> " + userName + "</p>");
        out.println("<p><strong>年龄:</strong> " + age + "</p>");
        out.println("</body></html>");
    }
}
代码逻辑逐行分析:
行号 说明
@WebServlet("/user/profile") 使用注解配置该Servlet映射路径为 /user/profile ,无需web.xml部署描述符。
doGet(...) 覆盖父类方法,处理HTTP GET请求;对于POST应使用 doPost
response.setContentType(...) 声明响应内容为HTML格式,并指定字符集为UTF-8,防止中文乱码。
PrintWriter out = response.getWriter() 获取字符输出流,用于向客户端发送文本内容。
request.getParameter("id") 从查询字符串(如 ?id=1001 )中提取名为 id 的参数值。
response.sendError(...) 若参数缺失,返回400错误码及自定义错误消息。
out.println(...) 向响应体写入HTML片段,最终拼接成完整页面返回。

此代码展示了如何通过编程方式动态生成HTML内容,体现了Servlet对HTTP协议的高度可控性。

参数说明:
  • HttpServletRequest 提供的方法包括:
  • getParameter(String name) :获取单个请求参数。
  • getParameterValues(String name) :获取多个同名参数(如复选框)。
  • getHeader(String name) :读取HTTP请求头字段(如User-Agent)。
  • getSession() :获取当前用户的会话对象。
  • getRequestURI() :获取请求资源路径。

  • HttpServletResponse 的常用操作:

  • setContentType(String type) :设置MIME类型。
  • setStatus(int code) :设置HTTP状态码。
  • sendRedirect(String location) :重定向到其他URL。
  • getOutputStream() :用于输出二进制数据(如图片下载)。

3.1.2 表单数据处理、会话跟踪(Session/Cookie)实现

在实际应用中,用户经常通过HTML表单提交数据。Servlet可通过 request.getParameter() 系列方法接收这些数据。同时,为了维持用户状态(如登录状态),需借助Cookie或Session机制。

示例:处理用户登录表单
<!-- login.html -->
<form action="login" method="post">
    <input type="text" name="username" placeholder="用户名" required />
    <input type="password" name="password" placeholder="密码" required />
    <button type="submit">登录</button>
</form>

对应的Servlet处理逻辑如下:

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private static final Map<String, String> USERS = new HashMap<>();
    static {
        USERS.put("admin", "123456"); // 模拟数据库
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if (USERS.containsKey(username) && USERS.get(username).equals(password)) {
            // 登录成功,创建会话
            HttpSession session = request.getSession();
            session.setAttribute("loginUser", username);
            session.setMaxInactiveInterval(30 * 60); // 30分钟超时

            // 可选:设置记住我功能的Cookie
            if ("on".equals(request.getParameter("remember"))) {
                Cookie cookie = new Cookie("username", username);
                cookie.setMaxAge(7 * 24 * 60 * 60); // 7天
                cookie.setPath("/");
                response.addCookie(cookie);
            }

            response.sendRedirect("dashboard");
        } else {
            response.sendRedirect("login?error=1");
        }
    }
}
会话管理机制对比表:
特性 Cookie Session
存储位置 客户端浏览器 服务器内存(默认)
安全性 较低(可被篡改) 较高(仅保存ID)
生命周期 可长期保存(通过setMaxAge) 默认30分钟,可配置
数据大小限制 约4KB 无硬性限制(受服务器内存影响)
是否依赖Cookie 是(Session ID通常通过Cookie传输)

注意 :Session底层仍依赖Cookie传递 JSESSIONID 。若禁用Cookie,可通过URL重写( response.encodeURL() )携带Session ID。

3.1.3 过滤器(Filter)链式处理与拦截机制实践

Filter是Java EE中实现横切关注点(cross-cutting concerns)的重要工具,可用于统一处理编码、权限校验、日志记录、XSS过滤等任务。多个Filter可组成“过滤器链”,按声明顺序依次执行。

示例:字符编码过滤器
@WebFilter("/*")
public class EncodingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 强制设置请求与响应编码
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");

        // 继续执行下一个Filter或目标资源
        chain.doFilter(request, response);
    }
}
权限验证Filter示例:
@WebFilter("/admin/*")
public class AuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        HttpSession session = req.getSession(false);
        if (session == null || session.getAttribute("loginUser") == null) {
            resp.sendRedirect("../login?redirect=" + req.getRequestURI());
            return;
        }

        chain.doFilter(request, response);
    }
}
过滤器执行流程图(Mermaid)
graph TD
    A[客户端发起请求] --> B{匹配Filter URL Pattern?}
    B -->|是| C[执行Filter1: 编码设置]
    C --> D[执行Filter2: 认证检查]
    D --> E{已登录?}
    E -->|是| F[放行至目标Servlet]
    E -->|否| G[重定向到登录页]
    F --> H[生成响应]
    H --> I[返回客户端]
Filter注册顺序说明:

Filter的执行顺序由其在 web.xml 中的声明顺序决定,或根据注解 @WebFilter urlPatterns 匹配规则排序。建议使用 @Order 或显式配置以避免歧义。

关键API说明:
  • FilterChain.doFilter() :调用链中下一个元素,若已是最后一个,则进入目标资源。
  • ServletRequestWrapper / ServletResponseWrapper :可用于装饰请求/响应对象,实现内容修改(如压缩、加密)。

3.2 JSP页面技术原理与MVC模式落地

JSP(JavaServer Pages)是一种基于模板的动态网页技术,允许在HTML中嵌入Java代码片段,从而实现视图层的动态渲染。虽然直接在JSP中编写Java脚本已被视为不良实践,但理解其编译机制有助于更好地运用EL表达式与JSTL标签库,推动MVC架构的合理分离。

3.2.1 JSP编译过程与Servlet转换机制

每个JSP文件在首次被访问时,会被Web容器自动编译为一个继承自 HttpJspBase 的Servlet类。该过程分为三个阶段:

  1. 翻译阶段 :JSP引擎将 .jsp 文件解析为 .java 源文件(即Servlet类)。
  2. 编译阶段 :使用JDK将 .java 文件编译为 .class 字节码。
  3. 执行阶段 :加载并运行该Servlet实例,生成响应内容。

例如,以下JSP页面:

<%-- index.jsp --%>
<html>
<head><title>欢迎页</title></head>
<body>
    <h1>你好,<%= request.getParameter("name") %></h1>
</body>
</html>

会被转换为类似如下结构的Servlet代码:

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase {
    public void _jspService(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        JspWriter out = pageContext.getOut();
        String name = request.getParameter("name");

        out.write("<html>\n");
        out.write("<head><title>欢迎页</title></head>\n");
        out.write("<body>\n");
        out.write("    <h1>你好,");
        out.print(name);
        out.write("</h1>\n");
        out.write("</body></html>");
    }
}

这一机制揭示了JSP的本质——它只是Servlet的一种简化写法。

JSP隐含对象列表:
对象名 类型 作用
request HttpServletRequest 当前请求对象
response HttpServletResponse 响应对象
session HttpSession 用户会话
application ServletContext 应用上下文
out JspWriter 输出流
pageContext PageContext 页面上下文控制器
config ServletConfig Servlet配置信息
page Object 当前JSP实例(this)
exception Throwable 异常对象(仅在errorPage中可用)

3.2.2 EL表达式与JSTL标签库提升视图层可读性

为避免在JSP中混杂Java脚本,推荐使用EL(Expression Language)与JSTL(JSP Standard Tag Library)进行数据访问与流程控制。

示例:使用JSTL展示用户列表

首先引入JSTL依赖(Maven):

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

JSP页面:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>用户列表</title></head>
<body>
<h2>用户列表</h2>
<table border="1">
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>邮箱</th>
    </tr>
    <c:forEach var="user" items="${users}">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.email}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

对应Servlet准备数据:

List<User> users = userService.findAll(); // 查询用户
request.setAttribute("users", users);
request.getRequestDispatcher("user-list.jsp").forward(request, response);
JSTL核心标签分类表:
标签库 URI 主要标签 功能
Core c <c:if> , <c:forEach> , <c:set> 条件判断、循环、变量设置
Formatting fmt <fmt:formatDate> , <fmt:formatNumber> 国际化与格式化
Functions fn ${fn:length()} , ${fn:contains()} 字符串函数支持
SQL(不推荐生产环境使用) sql <sql:query> , <sql:update> 直接执行SQL语句

最佳实践 :禁止在JSP中执行数据库操作,应通过Servlet或Service层完成数据获取。

3.2.3 构建基于Servlet+JSP的图书管理系统前端交互模块

以“图书管理系统”为例,演示完整的MVC流程。

数据模型:
public class Book {
    private Long id;
    private String title;
    private String author;
    private String isbn;
    // getter/setter...
}
控制器(Servlet):
@WebServlet("/books")
public class BookServlet extends HttpServlet {
    private BookService bookService = new BookService();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        List<Book> books = bookService.findAll();
        request.setAttribute("books", books);
        request.getRequestDispatcher("/WEB-INF/views/book-list.jsp")
               .forward(request, response);
    }
}
视图(JSP):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head><title>图书列表</title></head>
<body>
<h1>图书管理系统</h1>
<a href="add-book.jsp">添加新书</a>
<table border="1">
    <tr>
        <th>标题</th>
        <th>作者</th>
        <th>ISBN</th>
        <th>操作</th>
    </tr>
    <c:forEach var="book" items="${books}">
        <tr>
            <td>${book.title}</td>
            <td>${book.author}</td>
            <td>${book.isbn}</td>
            <td>
                <a href="edit-book?id=${book.id}">编辑</a> |
                <a href="delete-book?id=${book.id}"
                   onclick="return confirm('确定删除吗?')">删除</a>
            </td>
        </tr>
    </c:forEach>
</table>
</body>
</html>
项目结构(WAR包组织):
WEB-INF/
├── web.xml
├── classes/
│   └── com/example/servlet/BookServlet.class
├── lib/
│   └── jstl-1.2.jar
└── views/
    ├── book-list.jsp
    └── add-book.jsp

该结构遵循Java EE WAR规范,确保可移植性。

3.3 异常处理与安全性增强

任何生产级Web应用都必须具备完善的异常处理机制和基本的安全防护能力。本节将介绍如何通过配置错误页面、防范常见攻击手段以及集成日志监控来提升系统的稳定性与安全性。

3.3.1 全局异常捕获与错误页面配置

可在 web.xml 中定义全局错误处理规则:

<error-page>
    <error-code>404</error-code>
    <location>/error/404.jsp</location>
</error-page>

<error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error/generic-error.jsp</location>
</error-page>

也可在Servlet中手动抛出异常并触发错误页:

throw new ServletException("数据库连接失败");

错误页面示例(generic-error.jsp):

<%@ page isErrorPage="true" %>
<html>
<head><title>发生错误</title></head>
<body>
<h1>抱歉,系统出现异常</h1>
<p>错误信息:<%= exception.getMessage() %></p>
</body>
</html>

isErrorPage="true" 启用 exception 隐含对象。

3.3.2 防止XSS与CSRF攻击的基础防护策略

XSS防护:

对用户输入进行转义输出。使用JSTL自动转义特性:

<%-- JSTL默认会对${}表达式进行HTML转义 --%>
<p>评论内容:<c:out value="${comment.content}" /></p>

<%-- 或手动使用fn:escapeXml --%>
${fn:escapeXml(userInput)}
CSRF防护:

生成一次性Token并在表单中提交验证:

// 在Servlet中生成Token
String csrfToken = UUID.randomUUID().toString();
session.setAttribute("csrfToken", csrfToken);

request.setAttribute("token", csrfToken);
request.getRequestDispatcher("form.jsp").forward(request, response);

表单中:

<input type="hidden" name="csrfToken" value="${token}" />

提交时验证:

String submittedToken = request.getParameter("csrfToken");
String sessionToken = (String) session.getAttribute("csrfToken");

if (submittedToken == null || !submittedToken.equals(sessionToken)) {
    response.sendError(403, "CSRF token invalid");
    return;
}

3.3.3 日志记录与请求监控的集成方案

推荐使用SLF4J + Logback组合进行日志管理。

添加依赖:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.6</version>
</dependency>

在Filter中记录请求日志:

@WebFilter("/*")
public class LoggingFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        long startTime = System.currentTimeMillis();

        try {
            chain.doFilter(request, response);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            logger.info("{} {} - {}ms", req.getMethod(), req.getRequestURI(), duration);
        }
    }
}
请求监控统计表(样例):
方法 URI 响应时间(ms) 时间戳
GET /books 120 2025-04-05 10:23:12
POST /login 85 2025-04-05 10:23:15
DELETE /books/5 200 2025-04-05 10:23:20

此类日志可用于性能分析与故障排查。

4. JSF与CDI驱动的现代化用户界面构建

JavaServer Faces(JSF)作为Java EE 6中核心的Web用户界面框架,提供了一套基于组件和事件驱动的开发模型,极大地提升了企业级应用前端开发的结构化程度。与此同时,上下文与依赖注入(Contexts and Dependency Injection, CDI)的引入,标志着Java EE从传统的EJB依赖管理向更加灵活、可扩展的轻量级容器化编程范式转型。本章将深入剖析JSF与CDI协同工作的内在机制,探讨如何通过组件树管理、事件处理、依赖注入和模板复用等手段,构建响应迅速、结构清晰且易于维护的企业级Web界面。

4.1 JSF组件树与事件驱动编程模型

JSF的核心设计理念是“以组件为中心”,它通过构建一个服务器端的UI组件树(Component Tree),将HTML元素抽象为可编程的对象,从而实现对用户交互的精细化控制。这种模型使得开发者可以像操作Swing或JavaFX组件一样,在服务端编写事件响应逻辑,而无需过多关注底层HTTP请求/响应细节。

4.1.1 FacesContext、ManagedBean与视图状态管理

在JSF运行时环境中, FacesContext 是贯穿整个请求生命周期的关键对象,它是当前JSF请求执行上下文的入口点。每一个HTTP请求在进入JSF生命周期时都会创建一个唯一的 FacesContext 实例,该实例封装了请求、响应、外部上下文(ExternalContext)、消息列表、转换器、验证器以及当前组件树的引用。

@Named
@RequestScoped
public class UserBean {
    private String username;
    private String email;

    public void saveUser() {
        FacesContext context = FacesContext.getCurrentInstance();
        if (context != null) {
            context.addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, 
                "用户保存成功", "欢迎," + username));
        }
    }

    // getter 和 setter 省略
}

代码逻辑逐行解读:

  • 第1行: @Named 注解使该类成为CDI托管Bean,可在EL表达式中直接引用。
  • 第2行: @RequestScoped 表示该Bean的作用域为单次HTTP请求。
  • 第5–10行: saveUser() 方法模拟用户保存操作,并使用 FacesContext.getCurrentInstance() 获取当前JSF上下文。
  • 第7–9行:调用 addMessage() 向客户端添加一条信息级提示消息, null 参数表示全局消息(不绑定具体组件)。
属性 说明
getCurrentInstance() 静态方法,返回当前线程绑定的 FacesContext
getExternalContext() 提供对Servlet API的封装访问,如获取Session、Request参数
getApplication() 访问JSF应用配置,注册转换器、验证器等
getViewRoot() 获取当前页面的UI组件根节点
graph TD
    A[HTTP Request] --> B{Is it a JSF Request?}
    B -->|Yes| C[Restore View Phase]
    C --> D[Apply Request Values]
    D --> E[Process Validations]
    E --> F[Update Model Values]
    F --> G[Invoke Application]
    G --> H[Render Response]
    B -->|No| I[Static Resource Handling]

上图为JSF标准生命周期流程图,展示了 FacesContext 在各阶段中的主导作用。

视图状态(View State)是JSF维持跨请求组件状态的核心机制。对于POST回发请求,JSF会通过隐藏字段 _ViewState 将组件树序列化并随表单调用返回服务器,以便还原之前的UI状态。这一机制支持复杂的富交互场景,但也带来性能开销。可通过配置 <faces-config> 优化:

<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <lifecycle>
        <phase-listener>com.example.LoggingPhaseListener</phase-listener>
    </lifecycle>
    <application>
        <view-handler>org.jboss.weld.jsf.WeldViewHandler</view-handler>
    </application>
    <client-behavior-renderer>
        <renderer>
            <component-family>javax.faces.Input</component-family>
            <renderer-type>javax.faces.Text</renderer-type>
            <renderer-class>org.apache.myfaces.html5.renderkit.input.TextRenderer</renderer-class>
        </renderer>
    </client-behavior-renderer>
</faces-config>

此配置文件定义了自定义监听器、视图处理器及客户端行为渲染器,增强了JSF的可扩展性。

4.1.2 标准UI组件(h:form, h:inputText等)使用规范

JSF提供了丰富的标准HTML标签库( http://java.sun.com/jsf/html ),允许开发者以声明式方式构建动态界面。以下是一个典型的登录表单示例:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
<h:head>
    <title>用户登录</title>
</h:head>
<h:body>
    <h:form id="loginForm">
        <h:panelGrid columns="2">
            <h:outputLabel for="username" value="用户名:" />
            <h:inputText id="username" value="#{userBean.username}" required="true">
                <f:ajax event="blur" render="usernameMsg" />
            </h:inputText>
            <h:message id="usernameMsg" for="username" style="color:red"/>

            <h:outputLabel for="password" value="密码:" />
            <h:inputSecret id="password" value="#{userBean.password}" required="true" />
            <h:commandButton value="登录" action="#{userBean.login}" />
        </h:panelGrid>
    </h:form>
</h:body>
</html>

代码解析:

  • <h:form> :生成 <form method="post"> ,自动包含 _ViewState 字段。
  • <h:inputText> :绑定到 UserBean.username required="true" 触发表单验证。
  • <f:ajax> :启用Ajax支持, event="blur" 表示失去焦点时触发, render="usernameMsg" 指定更新区域。
  • <h:message> :显示特定组件的验证错误信息。
组件标签 功能描述 常见属性
h:outputLabel 输出带关联功能的标签 for , value
h:inputText 单行文本输入框 value , required , converter
h:inputSecret 密码输入框(掩码) redisplay=false 安全增强
h:selectOneMenu 下拉选择框 value , rendered , disabled
h:commandButton 提交按钮 action , onclick , immediate

结合Ajax技术,JSF能够实现局部刷新,避免整页重载,显著提升用户体验。例如,上述代码中当用户离开用户名输入框时,系统自动校验非空规则并即时反馈结果。

4.1.3 转换器与验证器在表单输入中的应用

为了确保用户输入的数据类型正确且符合业务规则,JSF提供了内置及自定义的 转换器(Converter) 验证器(Validator) 机制。

自定义日期转换器示例:
@FacesConverter("localDateConverter")
public class LocalDateConverter implements Converter<LocalDate> {

    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public LocalDate getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.trim().isEmpty()) {
            return null;
        }
        try {
            return LocalDate.parse(value.trim(), formatter);
        } catch (DateTimeParseException e) {
            throw new ConverterException(
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "无效日期格式", "请输入格式为 yyyy-MM-dd 的日期"));
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, LocalDate value) {
        if (value == null) {
            return "";
        }
        return value.format(formatter);
    }
}

逐行分析:

  • @FacesConverter("localDateConverter") :注册一个名称为 localDateConverter 的转换器。
  • getAsObject() :将字符串转为 LocalDate ,捕获格式异常并抛出 ConverterException
  • getAsString() :反向转换,用于页面展示。

在XHTML中使用:

<h:inputText value="#{userBean.birthDate}" converter="localDateConverter">
    <f:ajax event="change" render="birthMsg" />
</h:inputText>
<h:message id="birthMsg" for="birthDate" style="color:orange"/>
自定义邮箱验证器:
@FacesValidator("emailValidator")
public class EmailValidator implements Validator<String> {

    private static final String EMAIL_PATTERN = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";

    @Override
    public void validate(FacesContext context, UIComponent component, String value) throws ValidatorException {
        if (value == null || value.isEmpty()) {
            return; // 可由 required 控制
        }
        if (!value.matches(EMAIL_PATTERN)) {
            throw new ValidatorException(
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "邮箱格式错误", "请填写有效的电子邮箱地址"));
        }
    }
}

使用方式:

<h:inputText value="#{userBean.email}" validator="emailValidator" />

该机制实现了关注点分离——数据转换与验证逻辑独立于视图层之外,提高了代码复用性和测试便利性。

4.2 CDI依赖注入与上下文管理机制

CDI(Contexts and Dependency Injection)是Java EE 6中最关键的创新之一,取代了早期JSF ManagedBean 的局限性,提供了统一的依赖注入解决方案,兼容EJB、JSF、JPA等多种组件。

4.2.1 @Named、@Inject与作用域注解(@RequestScoped/@SessionScoped)

CDI通过 @Named @Inject 实现松耦合的组件协作。以下是一个典型的服务注入模式:

@ApplicationScoped
public class UserService {
    @PersistenceContext
    private EntityManager em;

    public List<User> findAll() {
        return em.createQuery("SELECT u FROM User u", User.class).getResultList();
    }

    public void save(User user) {
        em.persist(user);
    }
}

@Named
@ViewScoped
public class UserViewController {

    @Inject
    private UserService userService;

    private List<User> users;
    private User selectedUser = new User();

    @PostConstruct
    public void init() {
        users = userService.findAll();
    }

    public void createUser() {
        userService.save(selectedUser);
        selectedUser = new User(); // reset form
    }

    // getters and setters
}

代码详解:

  • @ApplicationScoped UserService 在整个应用生命周期内唯一实例。
  • @Inject :由容器自动装配 UserService 实例。
  • @ViewScoped :JSF特有作用域,保持Bean状态直到视图改变(需Weld支持)。
  • @PostConstruct :初始化方法,在Bean构造后自动调用。
作用域注解 生命周期 适用场景
@Dependent 依附于注入点 默认值,每次注入新建实例
@RequestScoped 每个HTTP请求 表单处理、临时数据
@SessionScoped 用户会话期间 登录信息、购物车
@ApplicationScoped 应用启动至关闭 缓存、计数器
@ConversationScoped 手动开始/结束会话 多步骤向导流程
classDiagram
    class UserViewController {
        -UserService userService
        -List~User~ users
        +init()
        +createUser()
    }
    class UserService {
        -EntityManager em
        +findAll() List~User~
        +save(User)
    }
    UserViewController --> "1" UserService : @Inject
    UserService --> "1" EntityManager : @PersistenceContext

上图为CDI组件依赖关系类图,体现控制反转(IoC)设计思想。

4.2.2 自定义Qualifier实现精确依赖绑定

当存在多个相同类型的Bean时,CDI默认无法决定注入哪一个。此时可通过自定义 @Qualifier 解决歧义。

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface Preferred {
}

定义两个日志服务:

@ApplicationScoped
@Preferred
public class EmailNotificationService implements NotificationService {
    public void send(String msg) { /* via SMTP */ }
}

@ApplicationScoped
public class SmsNotificationService implements NotificationService {
    public void send(String msg) { /* via SMS gateway */ }
}

使用限定符注入:

@Inject @Preferred
private NotificationService notificationService;

这样容器就能准确选择带有 @Preferred 的实现类。

4.2.3 实践:通过CDI整合Service层与JSF Backing Bean

在一个员工管理系统中,我们希望点击“加载员工”按钮时,从数据库读取数据并展示在表格中。

<h:form>
    <h:commandButton value="加载员工" action="#{employeeController.loadEmployees}" />
    <h:dataTable value="#{employeeController.employees}" var="emp">
        <h:column><f:facet name="header">姓名</f:facet>#{emp.name}</h:column>
        <h:column><f:facet name="header">部门</f:facet>#{emp.department}</h:column>
    </h:dataTable>
</h:form>

后台控制器:

@Named
@RequestScoped
public class EmployeeController {

    @Inject
    private EmployeeService employeeService;

    private List<Employee> employees;

    public String loadEmployees() {
        employees = employeeService.getAll();
        return null; // stay on same page
    }

    // getter
}

该模式实现了清晰的分层架构:JSF负责视图渲染,CDI完成组件组装,EJB或普通Bean封装业务逻辑,三者职责分明,便于单元测试与后期重构。

4.3 复合组件与模板复用提升开发效率

大型项目中,界面一致性与开发效率至关重要。Facelets作为JSF的默认视图技术,支持模板继承与复合组件,极大增强了UI的模块化能力。

4.3.1 Facelets模板继承与ui:include动态包含

创建基础模板 template.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
    <title><ui:insert name="title">默认标题</ui:insert></title>
    <link rel="stylesheet" href="/css/main.css" />
</head>
<body>
    <div id="header"><ui:insert name="header">公共头部</ui:insert></div>
    <div id="content"><ui:insert name="content" /></div>
    <div id="footer"><ui:insert name="footer">© 2025 公司版权所有</ui:insert></div>
</body>
</html>

子页面继承:

<ui:composition template="/templates/template.xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets">
    <ui:define name="title">员工管理</ui:define>
    <ui:define name="header"><h1>人力资源系统</h1></ui:define>
    <ui:define name="content">
        <p>这里是员工列表...</p>
        <ui:include src="/fragments/employeeTable.xhtml" />
    </ui:define>
</ui:composition>

其中 ui:include 可动态嵌入片段,适用于菜单栏、分页控件等重复区域。

4.3.2 创建可重用的自定义UI组件

/resources/components 目录下创建复合组件 searchBox.xhtml

<ui:component xmlns="http://www.w3.org/1999/xhtml"
              xmlns:cc="http://java.sun.com/jsf/composite">

<cc:interface>
    <cc:attribute name="value" required="true" type="java.lang.String" />
    <cc:attribute name="searchAction" method-signature="void action()" />
</cc:interface>

<cc:implementation>
    <div class="search-box">
        <input type="text" value="#{cc.attrs.value}" placeholder="输入关键词..." />
        <button onclick="#{cc.attrs.searchAction}">搜索</button>
    </div>
</cc:implementation>

</ui:component>

使用方式:

<my:searchBox value="#{queryBean.keyword}" searchAction="#{queryBean.performSearch}" />

这种方式实现了真正的组件化开发,类似现代前端框架(如Vue/React)的理念。

4.3.3 响应式布局与Ajax局部刷新集成

结合CSS框架(如Bootstrap)与JSF Ajax,可轻松实现响应式界面:

<h:form>
    <div class="row">
        <div class="col-md-6">
            <h:inputText value="#{filterBean.keyword}" placeholder="搜索关键字" />
        </div>
        <div class="col-md-2">
            <h:commandButton value="搜索" styleClass="btn btn-primary">
                <f:ajax execute="@form" render="resultPanel" />
            </h:commandButton>
        </div>
    </div>
    <div id="resultPanel" class="mt-3">
        <h:dataTable value="#{filterBean.results}" var="item" rendered="#{not empty filterBean.results}">
            <!-- 表格内容 -->
        </h:dataTable>
    </div>
</h:form>

Ajax配置说明:

属性 含义
execute="@form" 提交整个表单数据
render="resultPanel" 仅重新渲染指定ID区域
listener="#{bean.ajaxListener}" 指定回调函数

综上所述,JSF与CDI的深度集成不仅简化了企业级UI开发流程,还通过组件化、依赖注入和模板机制,构建了一个高度可维护、可测试且具备现代交互能力的Web前端体系。

5. EJB 3.1与JPA实现企业级业务逻辑与数据持久化

在现代企业级Java应用开发中,业务逻辑的封装与数据持久化的高效管理是系统稳定性和可扩展性的核心支柱。Java EE 6通过引入EJB(Enterprise JavaBeans)3.1和JPA(Java Persistence API)两大技术规范,实现了对分布式服务、事务控制、对象关系映射以及资源生命周期的标准化处理。本章将深入剖析EJB 3.1中的无状态与有状态会话Bean的设计原理与应用场景,并结合JPA完成从实体建模到复杂查询优化的全流程实践。重点在于揭示组件间协同机制、容器托管优势及性能调优策略,为构建高内聚、低耦合的企业系统提供坚实支撑。

5.1 EJB 3.1无状态/有状态会话Bean开发

EJB 3.1作为Java EE平台的核心业务组件模型,极大简化了企业服务的开发流程。其最大的革新在于采用注解驱动的方式替代了早期繁琐的XML配置和接口定义,使开发者能够以POJO(Plain Old Java Object)形式编写业务类,同时由容器自动管理事务、安全、并发和远程调用等横切关注点。其中, @Stateless @Stateful 是两种最常用的会话Bean类型,分别适用于不同场景下的业务需求。

5.1.1 @Stateless与@Stateful Bean生命周期控制

无状态会话Bean(Stateless Session Bean)是最常见的EJB类型,适用于处理不依赖于客户端上下文状态的操作,例如用户认证、订单创建或库存检查等通用服务。这类Bean的特点是实例可以被多个客户端共享,容器通常维护一个池化机制来复用Bean实例,从而提升性能。

@Stateless
public class OrderService {

    @PostConstruct
    public void init() {
        System.out.println("OrderService 初始化");
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("OrderService 资源释放");
    }

    public boolean placeOrder(Order order) {
        // 模拟下单逻辑
        return true;
    }
}

代码逻辑逐行分析:

  • @Stateless :声明该类为无状态会话Bean,由EJB容器管理其生命周期。
  • @PostConstruct :标记初始化方法,在Bean实例创建后、投入使用前执行一次,常用于加载缓存或建立连接。
  • @PreDestroy :标记销毁前回调方法,仅在容器关闭或Bean被移除时调用,适合释放资源如数据库连接、线程池等。
  • placeOrder() 方法:暴露业务逻辑,供其他组件(如Servlet或JSF ManagedBean)调用。

相比之下,有状态会话Bean(Stateful Session Bean)则保留特定客户端的状态信息,适用于需要跨多次请求维持会话数据的场景,如购物车操作、多步骤表单填写等。每个客户端都会拥有独立的Bean实例,因此内存消耗较高,需谨慎使用。

@Stateful
@SessionScoped
public class ShoppingCart {

    private List<Item> items = new ArrayList<>();

    @Remove
    public void checkout() {
        System.out.println("结算完成,清空购物车");
    }

    public void addItem(Item item) {
        items.add(item);
    }

    public List<Item> getItems() {
        return items;
    }
}

参数说明与扩展性解释:

  • @Stateful :标识该Bean为有状态类型,容器保证同一客户端后续请求路由至相同实例。
  • @SessionScoped (CDI注解):明确作用域为HTTP会话级别,增强语义清晰度。
  • @Remove :标记移除方法,调用后容器将销毁该Bean实例并释放资源,防止内存泄漏。
  • items 成员变量存储客户私有状态,不同于无状态Bean中不允许保留字段数据。

下表对比两类Bean关键特性:

特性 无状态会话Bean ( @Stateless ) 有状态会话Bean ( @Stateful )
实例共享 多个客户端共享实例(池化) 每个客户端独占实例
状态保持 不保存客户端状态 保存客户端专属状态
并发支持 高,并发访问安全 低,需同步机制保障
生命周期方法 支持 @PostConstruct , @PreDestroy 同样支持,但更频繁触发
使用场景 认证、计算、查询服务 购物车、向导式流程

此外,EJB容器通过以下流程图管理Bean生命周期:

graph TD
    A[客户端请求] --> B{是否已有可用实例?}
    B -- 是 --> C[从池中获取实例]
    B -- 否 --> D[新建实例]
    D --> E[调用 @PostConstruct]
    C --> F[执行业务方法]
    D --> F
    F --> G[方法执行完毕,归还实例到池]
    H[容器关闭] --> I[遍历所有实例]
    I --> J[调用 @PreDestroy]
    J --> K[销毁实例]

该流程体现了EJB容器对资源的精细化调度能力,尤其在无状态Bean中通过实例池显著降低了对象创建开销。

5.1.2 本地与远程接口调用机制比较

EJB支持本地(Local)和远程(Remote)两种调用方式,决定了组件之间的可见性与通信协议。

本地接口(@Local)

用于同一JVM内的调用,性能高,直接通过引用传递,无需序列化。

@Local
public interface CustomerServiceLocal {
    Customer findByEmail(String email);
}

@Stateless
public class CustomerService implements CustomerServiceLocal {
    @Override
    public Customer findByEmail(String email) {
        // 查询逻辑
        return new Customer(email, "John Doe");
    }
}
远程接口(@Remote)

允许跨JVM甚至跨网络调用,底层基于RMI-IIOP或CORBA协议,参数和返回值必须可序列化。

@Remote
public interface CustomerServiceRemote extends Serializable {
    Customer findByEmail(String email) throws RemoteException;
}

@Stateless
public class CustomerService implements CustomerServiceRemote {
    @Override
    public Customer findByEmail(String email) throws RemoteException {
        return new Customer(email, "John Doe");
    }
}

调用示例:

@EJB
private CustomerServiceLocal localService; // 注入本地接口

@EJB(lookup = "java:global/app/CustomerService!com.example.CustomerServiceRemote")
private CustomerServiceRemote remoteService; // JNDI查找远程接口

两者的本质区别体现在调用代价上。本地调用类似于普通方法调用,而远程调用涉及网络传输、序列化反序列化、安全认证等多个环节,延迟明显增加。因此,在微服务架构普及之前,远程EJB常用于分布式系统集成;而在单一应用服务器部署模式下,推荐优先使用本地接口。

5.1.3 事务管理(CMT与BMT)在业务方法中的应用

EJB内置强大的事务支持,分为容器管理事务(CMT)和Bean管理事务(BMT),前者更为常用且简洁。

容器管理事务(CMT)

默认行为,通过注解声明事务属性,由容器自动开启、提交或回滚事务。

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class TransferService {

    @Resource
    private UserTransaction utx;

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void transferMoney(Account from, Account to, BigDecimal amount) {
        from.debit(amount);
        to.credit(amount);
    }
}

参数说明:

  • @TransactionManagement(CONTAINER) :显式指定CMT模式(默认值,可省略)。
  • @TransactionAttribute(REQUIRED) :若当前存在事务则加入,否则新建事务。这是最常用的配置。
  • 其他常见属性包括:
  • REQUIRES_NEW :总是启动新事务,挂起现有事务。
  • SUPPORTS :如有事务则参与,否则非事务执行。
  • MANDATORY :必须运行在已有事务中,否则抛出异常。

debit() credit() 抛出运行时异常(如 RuntimeException 或其子类),容器会自动回滚整个事务,确保资金转移的原子性。

Bean管理事务(BMT)

适用于细粒度控制事务边界的情况,如部分提交、长时间运行任务等。

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class LongRunningTask {

    @Resource
    private UserTransaction userTransaction;

    public void execute() {
        try {
            userTransaction.begin();
            // 执行第一阶段操作
            stepOne();

            userTransaction.commit(); // 提交第一阶段

            userTransaction.begin();
            // 执行第二阶段
            stepTwo();

            userTransaction.commit();
        } catch (Exception e) {
            try {
                userTransaction.rollback();
            } catch (SystemException se) {
                throw new EJBException(se);
            }
        }
    }
}

逻辑分析:

  • 使用 UserTransaction 显式控制事务开始与结束。
  • 可实现阶段性提交,避免长时间持有锁。
  • 错误处理更复杂,需手动捕获并回滚。

尽管BMT提供了灵活性,但在绝大多数业务场景中,CMT已足够满足需求,且更具可维护性。

5.2 JPA实体映射与ORM操作实战

Java Persistence API(JPA)是Java EE 6中统一的对象关系映射标准,屏蔽了底层数据库差异,使得开发者可以通过面向对象的方式操作数据,极大提升了开发效率与代码可读性。

5.2.1 @Entity标注与主键生成策略

JPA通过 @Entity 将POJO映射为数据库表,配合 @Id 和主键生成策略实现唯一标识管理。

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "first_name", length = 50, nullable = false)
    private String firstName;

    @Column(name = "last_name", length = 50)
    private String lastName;

    @Temporal(TemporalType.DATE)
    private Date hireDate;

    // getter and setter ...
}

代码解读:

  • @Entity :声明此类为持久化实体。
  • @Table :指定对应数据库表名。
  • @Id :标记主键字段。
  • @GeneratedValue(strategy = IDENTITY) :使用数据库自增列生成主键(MySQL、PostgreSQL适用)。
  • 其他策略还包括:
  • TABLE :使用单独表维护序号。
  • SEQUENCE :基于数据库序列(Oracle、PostgreSQL)。
  • AUTO :由JPA提供者自动选择。

5.2.2 关联关系映射(一对一、一对多、多对多)

JPA支持丰富的关联映射,以下以“部门-员工”为例演示一对多关系。

@Entity
public class Department {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Employee> employees = new ArrayList<>();
}

@Entity
public class Employee {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "dept_id")
    private Department department;
}

参数说明:

  • mappedBy :指明关系由对方维护,避免双向重复建表。
  • cascade = ALL :级联保存、更新、删除操作。
  • fetch = LAZY :延迟加载,仅在访问时查询关联数据。
  • @JoinColumn :指定外键列名。

对于多对多关系,可通过中间表实现:

@Entity
public class Project {

    @Id
    private Long id;

    @ManyToMany
    @JoinTable(
        name = "employee_project",
        joinColumns = @JoinColumn(name = "project_id"),
        inverseJoinColumns = @JoinColumn(name = "employee_id")
    )
    private Set<Employee> members;
}

5.2.3 JPQL查询语言与原生SQL执行优化

JPQL(Java Persistence Query Language)是一种面向对象的查询语言,语法类似HQL,独立于具体数据库。

@Stateless
public class EmployeeService {

    @PersistenceContext
    private EntityManager em;

    public List<Employee> findSeniorEmployees() {
        String jpql = "SELECT e FROM Employee e WHERE e.hireDate < :threshold";
        return em.createQuery(jpql, Employee.class)
                 .setParameter("threshold", Date.from(LocalDate.now().minusYears(5).atStartOfDay(ZoneId.systemDefault()).toInstant()))
                 .getResultList();
    }

    public List<Object[]> getEmployeeCountByDept() {
        String nativeSql = "SELECT d.name, COUNT(e.id) FROM departments d LEFT JOIN employees e ON d.id = e.dept_id GROUP BY d.id, d.name";
        return em.createNativeQuery(nativeSql)
                 .getResultList();
    }
}

执行逻辑分析:

  • @PersistenceContext :注入EntityManager,负责所有持久化操作。
  • createQuery() :接受JPQL字符串和结果类型。
  • 参数绑定防止SQL注入。
  • 原生SQL适用于复杂统计或数据库特有函数。
查询方式 优点 缺点 适用场景
JPQL 数据库无关,类型安全 不支持某些高级SQL功能 通用CRUD、条件查询
原生SQL 灵活,支持窗口函数、索引提示 丧失移植性 报表、性能敏感查询

5.3 数据访问层封装与性能调优

5.3.1 DAO模式设计与泛型基类抽象

采用DAO(Data Access Object)模式分离数据访问逻辑,提升代码复用性。

public abstract class GenericDAO<T> {

    @PersistenceContext
    protected EntityManager em;

    private Class<T> entityClass;

    public GenericDAO(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    public T findById(Serializable id) {
        return em.find(entityClass, id);
    }

    public void save(T entity) {
        em.persist(entity);
    }

    public void update(T entity) {
        em.merge(entity);
    }

    public void delete(Serializable id) {
        T entity = findById(id);
        if (entity != null) {
            em.remove(entity);
        }
    }
}

@Stateless
public class EmployeeDAO extends GenericDAO<Employee> {
    public EmployeeDAO() {
        super(Employee.class);
    }

    public List<Employee> findByDepartment(Long deptId) {
        return em.createQuery("FROM Employee e WHERE e.department.id = :deptId")
                 .setParameter("deptId", deptId)
                 .getResultList();
    }
}

5.3.2 二级缓存与懒加载策略配置

启用Hibernate二级缓存减少数据库压力:

<!-- persistence.xml -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>

并在实体上添加缓存注解:

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Department { ... }

5.3.3 使用EntityManager进行批量操作与事务边界控制

批量插入建议分批提交以避免OutOfMemoryError:

@Transactional
public void batchInsert(List<Employee> employees) {
    int batchSize = 50;
    for (int i = 0; i < employees.size(); i++) {
        em.persist(employees.get(i));
        if (i % batchSize == 0) {
            em.flush();
            em.clear();
        }
    }
}

此策略利用一级缓存定期清理,防止内存溢出。

flowchart LR
    A[开始事务] --> B[循环插入]
    B --> C{是否达到批次?}
    C -- 是 --> D[flush & clear]
    C -- 否 --> B
    D --> B
    B --> E[提交事务]

综上所述,EJB 3.1与JPA共同构成了Java EE 6中稳健的业务与数据处理基石。通过合理运用会话Bean、事务控制、实体映射与性能优化手段,开发者可在保证系统可靠性的同时大幅提升开发效率。

6. Java EE 6企业级项目全流程开发与部署实战

6.1 开发环境搭建与工具链整合

在Java EE 6企业级项目中,构建高效、稳定的开发环境是项目成功的基础。本节将详细介绍如何整合主流开发工具链,确保编码、调试、测试和部署流程的无缝衔接。

6.1.1 Eclipse IDE for Java EE集成配置

Eclipse IDE for Java EE提供了对Servlet、JSP、JSF、EJB和JPA等技术的原生支持。安装完成后需进行以下关键配置:

  • 安装并绑定应用服务器(如GlassFish 3.1或WildFly 8),通过 Preferences → Server → Runtime Environments 添加。
  • 启用Maven集成插件(m2e),用于自动解析依赖和构建生命周期管理。
  • 配置JDK版本为Java SE 6或以上,并确保项目编译级别一致。
<!-- 示例:pom.xml 中指定Java EE 6 API依赖 -->
<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>6.0</version>
    <scope>provided</scope>
</dependency>

说明 provided 表示该依赖由应用服务器提供,打包时不会包含进WAR文件,避免类冲突。

6.1.2 Maven依赖管理与pom.xml核心配置项

使用Maven可实现标准化的项目结构与依赖管理。以下是典型Java EE 6项目的 pom.xml 核心片段:

组件 作用
maven-compiler-plugin 设置源码兼容性为1.6
maven-war-plugin 打包成WAR格式
maven-surefire-plugin 支持单元测试执行
<properties>
    <maven.compiler.source>1.6</maven.compiler.source>
    <maven.compiler.target>1.6</maven.compiler.target>
    <failOnMissingWebXml>false</failOnMissingWebXml>
</properties>

<dependencies>
    <!-- JPA -->
    <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>eclipselink</artifactId>
        <version>2.5.2</version>
        <scope>provided</scope>
    </dependency>
    <!-- JSF -->
    <dependency>
        <groupId>com.sun.faces</groupId>
        <artifactId>jsf-api</artifactId>
        <version>2.2.13</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

6.1.3 热部署与远程调试技巧

热部署可通过启用应用服务器的自动发布功能实现(如GlassFish的 autodeploy 目录)。此外,开启远程调试可大幅提升排错效率:

  1. 启动服务器时添加JVM参数:
    -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
  2. 在Eclipse中创建“Remote Java Application”调试配置,连接至端口8000。
  3. 修改代码后保存,服务器自动重新加载类文件,无需重启。

此机制特别适用于业务逻辑频繁调整阶段,显著提升开发迭代速度。

6.2 安全模型与实时通信功能实现

6.2.1 基于web.xml的角色授权与登录认证机制

Java EE内置基于容器的安全模型,通过 web.xml 声明式配置即可实现基本认证与访问控制。

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Admin Pages</web-resource-name>
        <url-pattern>/admin/*</url-pattern>
        <http-method>GET</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>ADMIN</role-name>
    </auth-constraint>
</security-constraint>

<login-config>
    <auth-method>FORM</auth-method>
    <form-login-config>
        <form-login-page>/login.xhtml</form-login-page>
        <form-error-page>/error.xhtml</form-error-page>
    </form-login-config>
</login-config>

<security-role>
    <role-name>ADMIN</role-name>
</security-role>

实际运行中,用户角色通常由LDAP或数据库通过 Realm 机制验证。

6.2.2 WebSocket(JSR 356)构建在线聊天功能

Java EE 6虽未原生包含WebSocket,但在其扩展规范中可通过第三方库(如Tyrus)支持JSR 356。

@ServerEndpoint("/chat")
public class ChatEndpoint {
    private static Set<Session> peers = Collections.synchronizedSet(new HashSet<>());

    @OnOpen
    public void onOpen(Session session) {
        peers.add(session);
    }

    @OnMessage
    public String onMessage(String message, Session session) {
        return "Echo: " + message; // 可广播给所有peers
    }

    @OnClose
    public void onClose(Session session) {
        peers.remove(session);
    }
}

需引入Tyrus依赖并在web.xml注册监听器以启动WebSocket引擎。

6.2.3 消息驱动Bean与JMS异步通知集成

消息驱动Bean(MDB)用于处理异步JMS消息,常用于解耦系统模块。

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationLookup", 
                              propertyValue = "jms/OrderQueue"),
    @ActivationConfigProperty(propertyName = "destinationType", 
                              propertyValue = "javax.jms.Queue")
})
public class OrderProcessorMDB implements MessageListener {
    @Override
    public void onMessage(Message message) {
        try {
            TextMessage tm = (TextMessage) message;
            System.out.println("Received order: " + tm.getText());
            // 处理订单逻辑
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

配合 @ActivationConfigProperty 注解,容器自动监听指定队列,实现事件驱动架构。

6.3 测试、打包与生产部署一体化流程

6.3.1 使用JUnit进行DAO与Service层单元测试

针对数据访问层编写测试用例,确保CRUD操作正确性:

@Test
public void testFindEmployeeById() {
    EntityManager em = emf.createEntityManager();
    EmployeeDAO dao = new EmployeeDAO(em);
    Employee emp = dao.findById(1L);
    assertNotNull(emp);
    assertEquals("John Doe", emp.getName());
}

6.3.2 Arquillian实现容器内集成测试

Arquillian允许在真实应用服务器环境中运行测试:

@RunWith(Arquillian.class)
public class EmployeeServiceTest {

    @Deployment
    public static JavaArchive createDeployment() {
        return ShrinkWrap.create(JavaArchive.class)
                .addClasses(EmployeeService.class, EmployeeDAO.class)
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
    }

    @Inject
    EmployeeService service;

    @Test
    public void shouldCreateEmployee() {
        Employee e = new Employee("Alice");
        service.save(e);
        assertNotNull(e.getId());
    }
}

6.3.3 WAR包生成与在应用服务器上的部署验证

执行Maven命令生成WAR包:

mvn clean package

输出位于 target/employeesystem.war ,可通过管理控制台上传部署,或直接复制到 autodeploy 目录。

部署后访问:

http://localhost:8080/employeesystem/faces/index.xhtml

验证各功能模块是否正常响应。

6.4 综合案例:员工管理系统从零到上线

6.4.1 需求分析与分层架构设计

系统包含员工信息管理、部门分配、权限控制三大模块,采用标准四层架构:

层级 技术组件
表示层 JSF + Facelets + PrimeFaces
控制层 CDI Managed Beans + JSF Converter
业务层 EJB Stateless Session Bean
数据层 JPA Entity + DAO Pattern

6.4.2 各技术组件协同工作流程图解

sequenceDiagram
    participant Browser
    participant JSF
    participant CDI
    participant EJB
    participant JPA
    participant DB

    Browser->>JSF: 提交表单(/edit.xhtml)
    JSF->>CDI: 调用@Named EmployeeController
    CDI->>EJB: 注入@EJB EmployeeService
    EJB->>JPA: EntityManager.persist(employee)
    JPA->>DB: INSERT INTO EMPLOYEE ...
    DB-->>JPA: 返回主键
    JPA-->>EJB: 持久化完成
    EJB-->>CDI: 返回结果
    CDI-->>JSF: 更新视图状态
    JSF-->>Browser: 跳转success.xhtml

该流程清晰展示了从用户请求到数据落库的完整调用链路。

6.4.3 性能监控与常见问题排查指南

部署后应启用性能监控:

  • 开启JConsole连接JVM,观察内存与线程状态;
  • 启用EclipseLink日志输出SQL语句:
    xml <property name="eclipselink.logging.level" value="FINE"/>
  • 常见问题包括:
  • ClassNotFoundException : 缺少依赖jar;
  • NoInitialContextException : JNDI查找失败;
  • LazyInitializationException : Session关闭后访问关联对象。

可通过配置 open-in-view 模式或显式初始化解决。

员工管理系统最终实现了需求闭环,具备良好的可维护性与扩展能力。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《Java EE 6企业级应用开发教程》是一门面向高等教育的系统性课程,全面讲授基于Java EE 6平台的企业级应用开发技术。课程涵盖Servlet、JSP、JSF、EJB、JPA、JMS、CDI、WebSocket、安全性配置、应用部署与测试等核心技术,帮助学生掌握构建分布式、多层架构Web应用的关键能力。通过理论与实践结合,学生不仅能深入理解Java EE 6的架构体系,还能熟练使用主流IDE进行项目开发与测试,为通过Oracle Java EE相关认证考试及从事企业级开发工作奠定坚实基础。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐