在一个常见的MVC设计应用程序中,使服务层依赖于用户会话是一个坏主意吗?假设有一种服务方法从数据库中提取一些对象,并且您希望根据初始化调用的人返回不同的结果 - 例如,管理员可能获得10行对象,而普通用户可能只获得7行因为最后3个是“仅限管理员”的对象。解决这个问题的几种方法是:
最近,我开始越来越多地使用最后一种方法,因为它提供了一个干净的界面,并且使用起来非常实用。过滤器确保当前线程始终具有用户设置。这是不好的设计吗?我相信有些人可以将其视为从服务层到Web层的依赖,尽管我个人认为它们非常不相关。最大的后果是方法行为将根据另一个类的状态而有所不同,这可能既是坏事也是好事。
您对此有何看法?如果这是一个糟糕的解决方案,那会是一个更强大的解决方案吗?
答案 0 :(得分:5)
我强烈建议使用aginst ThreadLocal风格方法 - 它看起来像是一个“全局变量”设计气味太多了。这有很多问题,最值得注意的是:
在其他两种方法之间,我认为“这取决于”:
另一种选择是将“上下文”对象传递到包含一组相关会话数据(即不仅仅是用户名)的服务层。如果您想最小化参数膨胀,这可能是有意义的。请注意它变成了一种“绕过”良好分层原则的方法。如果它只是“数据”那么它可能很好,但是一旦人们开始在上下文中传递回调对象/ UI组件,那么你可能会走向一点点......
答案 1 :(得分: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();
}
}
在网络方面,请确保您正在执行container login,以便服务器知道已登录的用户。
答案 2 :(得分:2)
您不必为您的用例实施任何解决方案。
在EJB中,框架已经支持您所需的内容。它被称为安全上下文,它会自动传播到您调用的每个方法中(在幕后它确实经常由TLS实现,但这是一个实现细节)。
此安全上下文将允许您访问用户名和他/她的角色。在您的情况下,您似乎需要检查角色。
您可以通过在方法(@RolesAllowed)上使用注释,或者通过注入EJB会话上下文并向其询问当前用户的角色来以声明方式执行此操作。
答案 3 :(得分:1)
从架构的角度来看,我认为你的最后一个选择是唯一理智的选择。
存在安全风险。您最终必须根据会话中的用户信息(或通过任何其他安全机制)验证用户名,这有效地使这可能具有的任何优势无效(因为您必须已经知道用户的名称/角色)。 / p>
不实用也不可扩展。想象一下,您需要添加另一个需要更多信息的角色(而不仅仅是更多记录)。然后,您将拥有现有2个用户角色的方法和新角色的另一个方法,可能使用不同的签名和 if-if-if 逻辑来处理不同的方案。这往往会变得混乱,真的,非常快,并导致严重的维护问题。
如果你研究Java中的Web框架,你会发现它们使用的是最后一种方法。他们还将用户信息放入更“抽象”的实体(身份,上下文等),并在处理信息分层时提倡使用角色而不是用户名。这样,管理大型用户群中存在的复杂性要容易得多,其中有多个角色需要不同的信息视图。这只是为用户配置文件添加角色的问题。
由于评论而导致的更新空间不足
根据您的推荐,您似乎正在将服务和UI层结合起来。只有在您不希望外部实体使用您的服务层时,才会发生这种情况。这可能没问题,这取决于你的情况。
除此之外,我同意迈克拉的观点,但仅限于某一点。 ThreadLocal
在静态值是全局的意义上不是全局的。对于您的服务层,用户的信息肯定是全球性的,将其与标准业务信息隔离是有意义的。还要记住,您可能还有其他需要存储的信息。如果您需要用户的国家/地区或语言或货币怎么办?
ThreadLocal
提供了一种有效且高效的解决方案。不完美?当然不是,但如果正确实施,它比其他解决方案更清洁。也就是说,它确实需要一些努力来构建一个适当且简单的实现,但几乎可以说任何事情。