【问题标题】:Inject Spring Bean in Jackson VirtualBeanPropertyWriter在 Jackson VirtualBeanPropertyWriter 中注入 Spring Bean
【发布时间】:2018-12-26 07:51:06
【问题描述】:

tldr; 我想在使用 Jackson @JsonAppend 将 JPA 实体序列化为 JSON 时添加虚拟字段。虚拟字段的值必须通过 Spring 管理的服务来确定。如何在 Jackson 类中注入我的 Spring 管理服务?

技术:Spring Boot 1.5.10、Spring Data Rest、JPA 2.1、Jackson 2.8.10

详情:

我有一个 Spring Data 托管的 JPA 实体:

@Entity
public class Stream {
   ...
}

我使用 Mixin 创建了一个自定义 Jackson 模块,以添加 @JsonAppend 虚拟字段,如下所示:

@Bean
public Module customModule() {
    return new CustomModule();
}

@Component
class CustomModule extends SimpleModule {

    CustomModule() {
        setMixInAnnotation(Stream.class, StreamMixin.class);
    }

    @JsonAppend(
            props = {
                    @JsonAppend.Prop(name = "canEdit", value = ABACInspector.class)
            }
    )
    abstract class StreamMixin {}

}

ABACInspector 类扩展了 Jackson 的 VirtualBeanPropertyWriter 以确定虚拟字段 canEdit 的值。如果此类不使用 Spring 服务(例如设置硬编码值),它可以正常工作,并且该字段显示在 REST API JSON 响应中。但是自动装配 Spring bean 不起作用,对象仍然是 null

@Component
class ABACInspector extends VirtualBeanPropertyWriter {
    @Autowired
    private PermissionEvaluator permissionEvaluator;

    public ABACInspector() {
    }

    public ABACInspector(BeanPropertyDefinition propDef, Annotations contextAnnotations, JavaType declaredType) {
        super(propDef, contextAnnotations, declaredType);
    }

    @Override
    protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        boolean permission = permissionEvaluator.hasPermission(authentication, bean, Action.STREAM_VIEW);
        System.out.println("evaluated permission is " + permission);
        return permission;
    }

    @Override
    public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass, BeanPropertyDefinition propDef, JavaType type) {
        return new ABACInspector(propDef, null, type);
    }
}

下面是 NPE 错误(因为permissionEvaluator 从未注入):

{"status":"INTERNAL_SERVER_ERROR","message":"Could not write JSON: 
(was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
(was java.lang.NullPointerException) (through reference chain: org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1[\"content\"]->com.example.streammanagement.Stream[\"canView\"])"

我知道 Spring Data Rest 的 HalHandlerInstantiator 包含 AutowireCapableBeanFactory,但我不确定这如何/是否可以提供帮助。参考DATAREST-840

【问题讨论】:

    标签: spring spring-mvc spring-boot jackson spring-data-rest


    【解决方案1】:

    Jackson 在内部调用组件的 withConfig 函数来构建 VirtualBeanPropertyWriter。 因此,如果您使用断点,您可以看到首先创建了一个带有注入 bean 的组件,然后调用 withConfig 函数并创建了新的 VirtualBeanPropertyWriter 对象,该对象由 jackson 使用,当然没有注入的 bin(因为您调用了构造函数手动)。

    所以你可以这样改:

    @Component
    class ABACInspector extends VirtualBeanPropertyWriter {
        private PermissionEvaluator permissionEvaluator;
    
        @Autowired
        public ABACInspector(PermissionEvaluator permissionEvaluator) {
            this.permissionEvaluator = permissionEvaluator;
        }
    
        public ABACInspector(BeanPropertyDefinition propDef, Annotations contextAnnotations, JavaType declaredType, PermissionEvaluator permissionEvaluator) {
            super(propDef, contextAnnotations, declaredType);
            this.permissionEvaluator = permissionEvaluator;
        }
    
        @Override
        protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            boolean permission = permissionEvaluator.hasPermission(authentication, bean, Action.STREAM_VIEW);
            System.out.println("evaluated permission is " + permission);
            return permission;
        }
    
        @Override
        public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass, BeanPropertyDefinition propDef, JavaType type) {
            return new ABACInspector(propDef, null, type, permissionEvaluator);
        }
    }
    

    【讨论】:

    • 你的ABACInspector初始化不是因为没有这样的构造函数而无效吗?构造函数注入也不起作用,因为还有另一个没有任何 PermissionEvaluator 参数的构造函数。
    • @xsreality 感谢您的评论。我修好了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-20
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 2019-08-11
    • 2012-02-19
    • 2017-02-27
    相关资源
    最近更新 更多