PAYATHON 2026

如何在 SpEL 的 @PostFilter 中正确使用 instanceof

支付阿杰

结论

原表达式不合法,原因有两个:

  1. instanceof 被拼成了 instanseof。
  2. 在 SpEL 中,类型通常要通过 T(...) 引用,不能直接把 Project、Phase 等类名写在 instanceof 右侧。

正确写法如下:

filterObject instanceof T(com.example.domain.Project)

请将 com.example.domain.Project 替换为类的真实全限定名。

正确写法

可以继续使用嵌套三元表达式,但最好加上括号,让条件层级更清楚:

@PostFilter(
    "filterObject instanceof T(com.example.domain.Project) " +
    "? filterObject.manager == principal " +
    ": (filterObject instanceof T(com.example.domain.Phase) " +
        "? filterObject.project.manager == principal " +
        ": (filterObject instanceof T(com.example.domain.Invoice) " +
            "? filterObject.phase.project.manager == principal " +
            ": (filterObject instanceof T(com.example.domain.Payment) " +
                "? filterObject.invoice.phase.project.manager == principal " +
                ": true)))"
)
public List<Entity> getData() {
    // ...
}

T(...) 是 SpEL 的类型引用语法。运行时,@PostFilter 会依次将返回集合中的元素放入 filterObject,并根据表达式的计算结果决定是否保留。

三元表达式只计算命中的分支。因此,当 filterObject 是 Project 时,表达式不会继续访问 filterObject.project 等不存在的属性。

更易维护的方案

如果类型和权限规则还会增加,最好不要把所有逻辑都塞进注解,可以将判断移到独立的 Bean 中:

@Component
public class DataPermissionChecker {

    public boolean canRead(Entity entity, Object principal) {
        if (entity instanceof Project project) {
            return Objects.equals(project.getManager(), principal);
        }

        if (entity instanceof Phase phase) {
            return Objects.equals(phase.getProject().getManager(), principal);
        }

        if (entity instanceof Invoice invoice) {
            return Objects.equals(
                invoice.getPhase().getProject().getManager(),
                principal
            );
        }

        if (entity instanceof Payment payment) {
            return Objects.equals(
                payment.getInvoice()
                       .getPhase()
                       .getProject()
                       .getManager(),
                principal
            );
        }

        return true;
    }
}

注解中只需保留一次方法调用:

@PostFilter("@dataPermissionChecker.canRead(filterObject, principal)")
public List<Entity> getData() {
    // ...
}

这样更容易调试和测试,也可以避免 SpEL 表达式过长。

注意事项

  • principal 的实际类型由认证配置决定,可能是 UserDetails、自定义用户对象或字符串用户名,不一定能直接与 manager 比较。
  • 如果 manager 是业务用户实体,而 principal 是 UserDetails,应比较用户 ID、用户名等稳定标识,不要直接使用 ==。
  • SpEL 中的 filterObject.manager 这类属性写法通常会调用对应的 getManager()。
  • 原示例的 Invoice 分支使用了 filterObject.phase.project == principal,与其他分支比较 manager 的逻辑不一致,需要确认是否漏写了 .manager。
  • 如果未知的 Entity 子类型不应放行,应将最后的 true 改为 false。未知类型默认返回 true 可能造成权限绕过。
  • 返回类型最好声明泛型,例如 List<Entity>,避免使用原始类型 List。

备注:内容仅供参考。