在Jackson VirtualBeanPropertyWriter中注入Spring Bean

时间:2018-07-18 11:46:03

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

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

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

详细信息:

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

@Entity
public class Stream {
   ...
}

我创建了一个带有Mixin的Custom 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\"])"

我知道包含HalHandlerInstantiator的Spring Data Rest的AutowireCapableBeanFactory,但不确定如何/是否有帮助。请参阅DATAREST-840

1 个答案:

答案 0 :(得分:0)

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);
    }
}