39、Java 资源管理与优化策略

Java 资源管理与优化策略

1. 让外观模式更通用

传统的外观模式虽然能将缓存代码隔离在一处,但在向应用程序添加更多边界类时不够灵活。为了解决这个问题,我们利用 Java 的反射 API 创建了一个更通用的边界外观模式。

首先,要确保所有边界类都继承自同一个基类。这里我们有一个 BoundaryBase 类,它封装了数据库连接池与边界子类的关联。以下是 BoundaryBase 类的代码:

package com.nealford.art.facade.emotherearth.boundary;
import com.nealford.art.facade.emotherearth.util.DBPool;
public class BoundaryBase {
    private DBPool dBPool;
    public BoundaryBase() {
    }
    public DBPool getDBPool() {
        return dBPool;
    }
    public void setDBPool(DBPool dBPool) {
        this.dBPool = dBPool;
    }
}

知道所有边界类都是基类的特殊版本后,我们可以在外观模式中编写一个方法,以基类的形式返回边界对象。这样,外观模式就成为了一个根据特定实体返回边界对象的工厂。以下是通用的 borrowBoundary() 方法:

public BoundaryBase borrowBoundary(HttpSession session,
        Class boundaryClass) {
    ServletContext sc = session.getServletContext();
    boolean cacheInBoundaryInSession =
            Boolean.valueOf(sc.getInitParameter(
            CACHE_BOUNDARY_IN_SESSION)).
            booleanValue();
    BoundaryBase boundary = null;
    if (cacheInBoundaryInSession)
        boundary = (BoundaryBase) session.getAttribute(
                boundaryClass.getName());
    if (boundary == null) {
        try {
            GenericKeyedObjectPool boundaryPool =
                    (GenericKeyedObjectPool) sc.getAttribute(
                    BOUNDARY_POOL);
            boundary = (BoundaryBase) boundaryPool.
                       borrowObject(boundaryClass);
            if (cacheInBoundaryInSession &&
                    boundary instanceof Cacheable)
                session.setAttribute(boundaryClass.getName(),
                        boundary);
        } catch (Exception x) {
            session.getServletContext().log("Pool error", x);
        }
        boundary.setDBPool((DBPool) sc.getAttribute(DB_POOL));
        borrowedObjects.add(boundary);
    }
    return boundary;
}

borrowBoundary() 方法利用 Java 的反射 API,将一个类作为参数传入。例如,从目录控制器调用该方法的示例如下:

BoundaryFacade facade = BoundaryFacade.getInstance();
ProductDb products = (ProductDb) facade.borrowBoundary(session,
        ProductDb.class);

该方法首先从 Servlet 上下文中收集状态信息,以确定如何处理边界缓存。如果启用了缓存,它会先尝试从用户会话中检索边界对象。如果会话中没有,则从对象池中获取。

2. 缓存策略

在使用对象池和用户会话进行对象缓存时需要谨慎。例如,当对象池中有 60 个对象,且启用了会话缓存,有 60 个并发用户时,每个用户会从池中取出一个对象并存储在会话缓存中,导致对象池为空。解决这个问题的方法有:
- 关闭会话缓存:这会稍微减慢每个用户的交互速度,但可以降低整个应用程序的内存需求,支持更多并发用户。
- 创建自动增长的对象池:确保对象池永远不会耗尽边界对象。

3. Cacheable 接口

borrowBoundary() 方法中还使用了一个名为 Cacheable 的接口。这是一个标记接口,用于标记哪些边界类需要在应用程序中进行缓存。以下是 Cacheable 接口的代码:

package com.nealford.art.facade.emotherearth.util;
public interface Cacheable {
}

要指定一个类需要缓存,只需让该类实现这个接口,例如:

public class ProductDb extends BoundaryBase implements Cacheable {

borrowBoundary() 方法会同时检查 Servlet 上下文的部署标志和 Cacheable 接口的存在,以确定从池中借用的边界对象是否应放入会话中。

4. 通用返回边界对象

我们还修改了 returnBoundaries() 方法,以适应通用的边界外观模式。以下是新的 returnBoundaries() 方法:

public void returnBoundaries(HttpSession session,
                             boolean preserveCachedBoundaries) {
    GenericKeyedObjectPool boundaryPool =
        (GenericKeyedObjectPool) session.getServletContext().
        getAttribute(BOUNDARY_POOL);
    boolean cacheInBoundaryInSession =
        Boolean.valueOf(session.getServletContext().
        getInitParameter(CACHE_BOUNDARY_IN_SESSION)).
        booleanValue();
    Iterator borrowedObject = borrowedObjects.iterator();
    while (borrowedObject.hasNext()) {
        Object o = borrowedObject.next();
        if (o instanceof BoundaryBase)
            if (cacheInBoundaryInSession &&
                    preserveCachedBoundaries &&
                    o instanceof Cacheable)
                break;
            else {
                try {
                    boundaryPool.returnObject(o.getClass(), o);
                } catch (Exception x) {
                    session.getServletContext().log(
                            "Pool return exception: " +
                            x.getMessage());
                } finally {
                    borrowedObject.remove();
                }
            }
    }
}

该方法接收用户会话和一个布尔标志,用于指示是否保留缓存的边界对象。它会根据缓存设置、调用者是否希望保留缓存以及对象的可缓存性,有条件地将会话中的对象返回给对象池。

5. 框架中的资源管理

本章介绍的两种设计模式与 Model 2 框架配合良好。这些策略主要适用于边界类和实体类,框架则负责基础设施。不过,Tapestry 框架可能无法从这些模式中受益,因为该框架本身已经内置了许多对象池和缓存功能。

以下是不同框架与设计模式的适用性表格:
| 框架 | 是否适合设计模式 | 原因 |
| ---- | ---- | ---- |
| Model 2 框架 | 是 | 策略适用于边界和实体类,框架负责基础设施 |
| Tapestry | 否 | 框架本身内置了对象池和缓存功能 |
| InternetBeans Express | 否 | 虽初始构建快,但限制多,不够灵活 |

6. 资源管理流程
graph TD;
    A[开始] --> B[检查会话中是否有缓存的边界对象];
    B --> C{是否找到};
    C -- 是 --> D[使用会话中的对象];
    C -- 否 --> E[从对象池借用对象];
    E --> F{对象是否可缓存};
    F -- 是 --> G[将对象存入会话];
    F -- 否 --> H[不存入会话];
    D --> I[使用对象];
    G --> I;
    H --> I;
    I --> J[完成使用];
    J --> K{是否保留缓存};
    K -- 是 --> L[不返回对象到池];
    K -- 否 --> M[返回对象到池];
    L --> N[结束];
    M --> N;
7. 其他需要管理的资源

除了内存资源,我们还需要管理其他外部资源(如应用服务器的 JNDI 查找上下文)和内部资源(如各种集合)。

7.1 有效使用 JNDI

如果使用 JNDI 处理数据库连接池或 Enterprise JavaBeans,需要管理为用户维护的连接。JNDI 与应用服务器的连接类似于客户端/服务器应用程序与数据库的连接,建立连接都需要较长时间。

通常的策略是在用户登录时为其建立 JNDI 连接,并将上下文存储在用户会话中。以下是一个示例代码:

package com.nealford.art.ejbsched.controller;
import java.io.IOException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.nealford.art.ejbsched.model.ScheduleBean;
public class ViewSchedule extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response) throws
            ServletException, IOException {
        Context c = establishContext(request);
        forwardToView(request, response, populateModel(c));
    }
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response) throws
            ServletException, IOException {
        doGet(request, response);
    }
    private void forwardToView(HttpServletRequest request,
                               HttpServletResponse response,
                               ScheduleBean scheduleBean) throws
            ServletException, IOException {
        request.setAttribute("scheduleBean", scheduleBean);
        RequestDispatcher rd = request.getRequestDispatcher(
                "/ScheduleView.jsp");
        rd.forward(request, response);
    }
    private ScheduleBean populateModel(Context c) {
        ScheduleBean scheduleBean = new ScheduleBean();
        scheduleBean.setContext(c);
        try {
            scheduleBean.populate();
        } catch (Exception x) {
            getServletContext().log(
                    "Error: ScheduleBean.populate()");
        }
        return scheduleBean;
    }
    private Context establishContext(HttpServletRequest request) {
        HttpSession session = request.getSession(true);
        Context c = (Context) session.getAttribute("context");
        if (c == null) {
            c = getInitialContext();
            session.setAttribute("context", getInitialContext());
        }
        return c;
    }
    private Context getInitialContext() {
        Context c = null;
        try {
            c = new InitialContext();
        } catch (NamingException ex) {
            ex.printStackTrace();
        }
        return c;
    }
}

establishContext() 方法会检查会话中是否已经存在上下文,如果不存在则创建并存储在会话中。

7.2 使用懒加载

懒加载是一种资源分配策略,它允许我们在真正需要资源时才创建它们。这种方法适用于那些可能不需要的重量级资源,如管理模块。虽然它可以节省资源,但在需要资源时会增加响应时间。

7.3 处理 Web 集合

Java 开发者应该根据任务的需求选择最合适的属性集合,避免过度依赖会话集合。通常,应该选择作用域最小的集合来完成任务,以避免资源浪费。

8. 会话管理员的职责

在资源密集型的 Web 应用程序开发团队中,设立“会话管理员”是个不错的主意。会话管理员的职责包括:
- 确保使用正确的集合。
- 尽快清理集合属性。
- 防止开发者在内存中放置会影响应用程序可扩展性的大型数据结构。
- 要求开发者为将对象放入集合提供合理的理由。

当开发者在集合使用上出现冲突时,会话管理员应及时向项目技术负责人发出警告。

9. 总结

Java Web API 为资源管理提供了丰富的机会。缓存是一种有效的资源管理方式,Flyweight 和 Façade 设计模式为创建缓存提供了很好的选择。同时,我们还需要管理其他资源,如 JNDI 连接和标准集合。合理使用这些资源可以提高应用程序的性能和可扩展性。设立“会话管理员”可以帮助复杂应用程序优雅地管理资源,及时发现问题。

在后续的开发中,我们还需要关注调试和日志记录等方面,以确保应用程序的稳定性和可维护性。

Java 资源管理与优化策略

10. 资源管理的注意事项

在进行资源管理时,有一些关键的注意事项需要牢记,这些要点对于确保应用程序的性能和稳定性至关重要。

  • 缓存一致性 :当使用缓存时,要确保缓存中的数据与实际数据保持一致。例如,当数据库中的数据发生更新时,需要及时更新或清除相关的缓存,避免出现数据不一致的问题。
  • 资源释放 :对于使用的各种资源,如数据库连接、JNDI 上下文等,要确保在使用完毕后及时释放。否则,可能会导致资源泄漏,影响应用程序的性能和稳定性。
  • 并发控制 :在多用户并发访问的情况下,要注意对资源的并发控制。例如,在使用对象池时,要确保多个用户不会同时访问和修改同一个对象,避免出现数据竞争和错误。
11. 不同资源管理策略的对比

为了更好地理解各种资源管理策略的优缺点,我们可以通过以下表格进行对比:
| 策略 | 优点 | 缺点 | 适用场景 |
| ---- | ---- | ---- | ---- |
| 缓存策略 | 提高响应速度,减少资源重复创建 | 可能导致内存占用过高,需要处理缓存一致性问题 | 频繁访问的数据或资源 |
| 懒加载策略 | 节省资源,只在需要时创建资源 | 增加资源使用时的响应时间 | 可能不需要的重量级资源 |
| 预创建和池化策略 | 提高性能,减少资源创建时间 | 需要预先分配一定的资源,可能造成资源浪费 | 经常使用的资源,如数据库连接 |

12. 资源管理的最佳实践

结合前面介绍的各种资源管理策略和注意事项,我们可以总结出以下资源管理的最佳实践:
1. 合理使用缓存 :根据数据的访问频率和更新频率,选择合适的缓存策略。对于频繁访问且不经常更新的数据,可以使用缓存来提高性能。
2. 及时释放资源 :在使用完资源后,要确保及时释放,避免资源泄漏。可以使用 try-with-resources 语句来自动管理资源的释放。
3. 优化集合使用 :选择合适的集合来存储数据,避免使用过大的集合或不必要的集合。同时,要及时清理集合中的无用数据。
4. 使用设计模式 :利用 Flyweight 和 Façade 等设计模式来优化资源管理,提高代码的可维护性和可扩展性。
5. 监控和调整 :定期监控应用程序的资源使用情况,根据实际情况调整资源管理策略。例如,如果发现内存占用过高,可以考虑减少缓存的使用或增加内存分配。

13. 资源管理的流程图
graph TD;
    A[开始资源管理] --> B{是否需要缓存};
    B -- 是 --> C[选择合适的缓存策略];
    B -- 否 --> D{是否使用懒加载};
    C --> E[设置缓存参数];
    D -- 是 --> F[实现懒加载逻辑];
    D -- 否 --> G[使用预创建和池化策略];
    E --> H[使用资源];
    F --> H;
    G --> H;
    H --> I{资源使用完毕};
    I -- 是 --> J[释放资源];
    I -- 否 --> H;
    J --> K{是否需要调整策略};
    K -- 是 --> L[调整资源管理策略];
    K -- 否 --> M[结束资源管理];
    L --> H;
14. 总结

Java 资源管理是一个复杂而重要的任务,涉及到内存、外部资源(如 JNDI)和内部资源(如集合)等多个方面。通过合理使用缓存、懒加载、预创建和池化等策略,以及遵循资源管理的最佳实践,可以有效地提高应用程序的性能和可扩展性。

同时,设立“会话管理员”可以帮助团队更好地管理资源,避免资源浪费和冲突。在实际开发中,要根据应用程序的具体需求和特点,选择合适的资源管理策略,并不断监控和调整,以确保应用程序的稳定性和高效性。

在未来的开发中,随着技术的不断发展和应用场景的不断变化,资源管理也需要不断地优化和改进。我们需要持续关注新的技术和方法,不断提升自己的资源管理能力,以应对日益复杂的应用需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值