9

在一个常见的 MVC 设计的应用程序中,使服务层依赖于用户会话是一个坏主意吗?假设有一个服务方法从数据库中获取一些对象,并且您希望根据谁初始化调用返回不同的结果 - 例如,管理员可能会获得 10 行对象,而普通用户可能只会获得 7 行因为最后 3 个是“仅限管理员”的对象。解决此问题的几种方法是:

  • 引入一个包含调用用户的新方法参数。在许多方法中必须投入用户参数,但不依赖但很麻烦。
  • 为不同的用户角色制定不同的方法(有多个结果)。也没有依赖,但有很多方法基本上做同样的事情,这增加了代码重复的风险。
  • 让该方法从存储当前用户会话的静态上下文中的 ThreadLocal 变量中读取。此变量在每个请求之前设置。

最近,我越来越多地开始使用最后一种方法,因为它提供了一个干净的界面并且感觉非常实用。过滤器确保当前线程始终具有用户集。这是糟糕的设计吗?我相信有些人会认为这是从服务层到 Web 层的依赖关系,尽管我个人认为它们是非常分离的。最大的后果是一个方法的行为会根据另一个类的状态而有所不同,这可能是一件坏事,也是一件好事。

您对此有何看法?如果这是一个糟糕的解决方案,那么更强大的解决方案是什么?

4

4 回答 4

5

我强烈建议 aginst ThreadLocal 风格的方法——它似乎有太多的“全局变量”设计气味。这有很多问题,最值得注意的是:

  • 随着您在测试之前设置越来越多的隐式全局状态的滑坡,您的代码变得更难测试
  • 无法清楚地看到某段代码正在使用哪些参数。这对于维护者来说可能会非常令人困惑,或者如果您在几个月后回到代码中。
  • 如果您将工作交给不同的线程,它可能会导致非常讨厌的错误/复杂性。您已经有效地使您的代码执行依赖于它正在运行的线程......可能会出现什么问题?:-)
  • 您正在创建循环依赖项(服务层 <-> 用户界面层)。绝不是一个好主意,您应该尝试仅以一种方式保持依赖关系(几乎总是用户界面层->服务层)

在其他两种方法之间,我认为“这取决于”:

  • 如果用户本质上是数据模型的一部分(例如,您有一个社交图数据库),那么将用户作为参数传递似乎很自然。
  • 如果用户数据仅用于身份验证等前端内容,那么我倾向于尽量减少对特定用户详细信息的依赖,而是为不同角色创建不同的方法(或等效地添加“角色”参数)。

另一种选择是将“上下文”对象传递给包含一组相关会话数据(即不仅仅是用户名)的服务层。如果您想最小化参数膨胀,这可能是有意义的。请注意它会变成一种“绕过”良好分层原则的方式。如果它只是“数据”,那么它可能没问题,但是一旦人们开始在上下文中传递回调对象/ UI 组件,那么你可能会走向一团糟......

于 2012-09-03T11:50:19.573 回答
3

只需使用Java EE 的角色机制,它已经存在。

举个例子:

@Stateless
public class AService {

    @Resource
    private SessionContext sessionContext;

    @PersistenceContext
    private EntityManager entityManager;

    public List<AObject> fetchAFewObjects() {

        String query = "aUserQuery";      

        if (sessionContext.isCallerInRole("ADMIN")
            query = "aAdminQuery";

        return entityManager.createNamedQuery(query, AObject.class)
            .getResultList();    
    }  
}

在 Web 端,确保您正在执行容器登录,以便服务器知道登录用户。

于 2012-09-04T09:37:34.437 回答
2

您不必为您的用例实施任何一种解决方案。

在 EJB 中,框架已经支持您想要的。它被称为安全上下文,它会自动传播到您调用的每个方法中(在幕后它确实经常由 TLS 实现,但这是一个实现细节)。

此安全上下文将使您能够访问用户名和他/她的角色。在您的情况下,您似乎只需要检查角色。

您可以通过在方法上使用注释 (@RolesAllowed) 或通过注入 EJB 会话上下文并询问当前用户的角色来以声明方式执行此操作。

于 2012-09-04T09:01:30.180 回答
1

从架构的角度来看,我认为您的最后一个选择是唯一明智的选择。

  1. 存在安全风险。您最终将不得不根据会话中的用户信息(或通过任何其他安全机制)验证用户名,这实际上会使这可能具有的任何优势无效(因为无论如何您都必须知道用户的姓名/角色)。

  2. 既不实用也不可扩展。想象一下,您需要添加另一个需要更多信息(而不仅仅是更多记录)的角色。然后,您将拥有用于现有 2 个用户角色的方法和用于新角色的另一组方法,可能具有不同的签名和if-if-if逻辑来处理不同的场景。这往往会变得非常混乱,非常非常快,并导致严重的维护问题。

如果您研究 Java 中的 Web 框架,您会注意到它们使用最后一种方法。他们还将用户信息放入更“抽象”的实体(身份、上下文等)中,并提倡在处理信息分层时使用角色而不是用户名。这种方式更容易管理存在于较大用户群中的复杂性,其中有多个角色需要不同的信息视图。只需将角色添加到用户配置文件即可。

作为参考,请参阅 Seam 的实现: http: //grepcode.com/file/repository.jboss.org/nexus/content/repositories/releases/org.jboss.seam/jboss-seam/2.0.0.GA/org/jboss /seam/contexts/Contexts.java#Contexts

由于评论而更新没有足够的空间

从您下面的推荐来看,您似乎正在耦合您的服务和 UI 层。只有当您从不期望外部实体使用您的服务层时,才会发生这种情况。这可能很好,这取决于您的情况。

除此之外,我同意 Mikera 的观点,但只是在一定程度上。ThreadLocals 不是全局的,因为静态值是全局的。对于您的服务层,用户的信息肯定是全球性的,将其与标准业务信息分开是有意义的。另请记住,您可能还有其他需要存储的此类信息。如果您需要用户的国家或语言或货币怎么办?

ThreadLocals 提供了一种有效且高效的解决方案。不完美?当然不是,但如果实施得当,它比其他解决方案更干净。也就是说,它确实需要一些努力来构建一个正确且万无一失的实现,但这几乎可以说是任何事情。

于 2012-09-03T11:57:28.883 回答