복붙노트

[SPRING] Spring 데이터 REST : 단일 리소스의 투영 표현

SPRING

Spring 데이터 REST : 단일 리소스의 투영 표현

스프링 데이터 REST를 사용하여 노출 된 간단한 UserRepository가 있습니다. User 엔티티 클래스는 다음과 같습니다.

@Document(collection = User.COLLECTION_NAME)
@Setter
@Getter
public class User extends Entity {

    public static final String COLLECTION_NAME = "users";

    private String name;
    private String email;
    private String password;
    private Set<UserRole> roles = new HashSet<>(0);
}

다음과 같은 UserProjection 클래스를 만들었습니다.

@JsonInclude(JsonInclude.Include.NON_NULL)
@Projection(types = User.class)
public interface UserProjection {

    String getId();

    String getName();

    String getEmail();

    Set<UserRole> getRoles();
}

다음은 저장소 클래스입니다.

@RepositoryRestResource(collectionResourceRel = User.COLLECTION_NAME, path = RestPath.Users.ROOT,
        excerptProjection = UserProjection.class)
public interface RestUserRepository extends MongoRepository<User, String> {

    // Not exported operations

    @RestResource(exported = false)
    @Override
    <S extends User> S insert(S entity);

    @RestResource(exported = false)
    @Override
    <S extends User> S save(S entity);

    @RestResource(exported = false)
    @Override
    <S extends User> List<S> save(Iterable<S> entites);
}

또한 구성에서 사용자 투영을 사용하여 이것이 사용되는지 확인했습니다.

config.getProjectionConfiguration().addProjection(UserProjection.class, User.class);

그래서 GET / users 경로에서 다음 응답을 얻습니다 (프로젝션이 적용됨).

{
  "_embedded" : {
    "users" : [ {
      "name" : "Yuriy Yunikov",
      "id" : "5812193156aee116256a33d4",
      "roles" : [ "USER", "ADMIN" ],
      "email" : "yyunikov@gmail.com",
      "points" : 0,
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4"
        },
        "user" : {
          "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4{?projection}",
          "templated" : true
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://127.0.0.1:8080/users"
    },
    "profile" : {
      "href" : "http://127.0.0.1:8080/profile/users"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

그러나 단일 리소스에 대한 GET 호출을 만들려고 할 때 (예 : / users / 5812193156aee116256a33d4, 다음과 같은 응답이 표시됩니다.

{
  "name" : "Yuriy Yunikov",
  "email" : "yyunikov@gmail.com",
  "password" : "123456",
  "roles" : [ "USER", "ADMIN" ],
  "_links" : {
    "self" : {
      "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4"
    },
    "user" : {
      "href" : "http://127.0.0.1:8080/users/5812193156aee116256a33d4{?projection}",
      "templated" : true
    }
  }
}

보시다시피 암호 필드가 반환되고 투영이 적용되지 않습니다. @JsonIgnore 어노테이션은 민감한 데이터의 데이터를 숨길 수 있다는 것을 알고있다. 그러나 내 User 객체는 API 또는 JSON 표현에 대해 모르는 다른 응용 프로그램 모듈에 있으므로 @JsonIgnore 주석으로 필드를 표시하는 것이 적절하지 않습니다.

@Oliver Gierke가 한 발췌문이 단일 리소스에 자동으로 적용되지 않는 이유에 대한 게시물을 여기에서 보았습니다. 그러나, 그것은 여전히 ​​내 경우에 매우 불편하고 단일 리소스를 얻을 때 동일한 UserProjection을 반환하고자합니다. 어떻게 든 사용자 정의 컨트롤러를 만들거나 @JsonIgnore로 필드를 표시하지 않고 할 수 있습니까?

해결법

  1. ==============================

    1.나는 DATAREST-428에서 제안 된대로 모든 자원에 대한 투영을 적용하는 ResourceProcessor 클래스를 만들 수있었습니다. 다음과 같은 방식으로 작동합니다 : 투영 매개 변수가 URL에 지정된 경우 - 지정된 투영법이 적용됩니다. 그렇지 않은 경우 - 이름이 기본값 인 투영법이 반환되고 처음 적용될 투영법이 적용됩니다. 또한 링크를 무시하는 사용자 지정 ProjectingResource를 추가해야합니다. 그렇지 않으면 반환하는 JSON에 두 개의 _links 키가 있습니다.

    나는 DATAREST-428에서 제안 된대로 모든 자원에 대한 투영을 적용하는 ResourceProcessor 클래스를 만들 수있었습니다. 다음과 같은 방식으로 작동합니다 : 투영 매개 변수가 URL에 지정된 경우 - 지정된 투영법이 적용됩니다. 그렇지 않은 경우 - 이름이 기본값 인 투영법이 반환되고 처음 적용될 투영법이 적용됩니다. 또한 링크를 무시하는 사용자 지정 ProjectingResource를 추가해야합니다. 그렇지 않으면 반환하는 JSON에 두 개의 _links 키가 있습니다.

    /**
     * Projecting resource used for {@link ProjectingProcessor}. Does not include empty links in JSON, otherwise two 
     * _links keys are present in returning JSON.
     *
     * @param <T>
     */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    class ProjectingResource<T> extends Resource<T> {
    
        ProjectingResource(final T content) {
            super(content);
        }
    }
    
    /**
     * Resource processor for all resources which applies projection for single resource. By default, projections
     * are not
     * applied when working with single resource, e.g. http://127.0.0.1:8080/users/580793f642d54436e921f6ca. See
     * related issue <a href="https://jira.spring.io/browse/DATAREST-428">DATAREST-428</a>
     */
    @Component
    public class ProjectingProcessor implements ResourceProcessor<Resource<Object>> {
    
        private static final String PROJECTION_PARAMETER = "projection";
    
        private final ProjectionFactory projectionFactory;
    
        private final RepositoryRestConfiguration repositoryRestConfiguration;
    
        private final HttpServletRequest request;
    
        public ProjectingProcessor(@Autowired final RepositoryRestConfiguration repositoryRestConfiguration,
                                   @Autowired final ProjectionFactory projectionFactory,
                                   @Autowired final HttpServletRequest request) {
            this.repositoryRestConfiguration = repositoryRestConfiguration;
            this.projectionFactory = projectionFactory;
            this.request = request;
        }
    
        @Override
        public Resource<Object> process(final Resource<Object> resource) {
            if (AopUtils.isAopProxy(resource.getContent())) {
                return resource;
            }
    
            final Optional<Class<?>> projectionType = findProjectionType(resource.getContent());
            if (projectionType.isPresent()) {
                final Object projection = projectionFactory.createProjection(projectionType.get(), resource
                        .getContent());
                return new ProjectingResource<>(projection);
            }
    
            return resource;
        }
    
        private Optional<Class<?>> findProjectionType(final Object content) {
            final String projectionParameter = request.getParameter(PROJECTION_PARAMETER);
            final Map<String, Class<?>> projectionsForType = repositoryRestConfiguration.getProjectionConfiguration()
                    .getProjectionsFor(content.getClass());
    
            if (!projectionsForType.isEmpty()) {
                if (!StringUtils.isEmpty(projectionParameter)) {
                    // projection parameter specified
                    final Class<?> projectionClass = projectionsForType.get(projectionParameter);
                    if (projectionClass != null) {
                        return Optional.of(projectionClass);
                    }
                } else if (projectionsForType.containsKey(ProjectionName.DEFAULT)) {
                    // default projection exists
                    return Optional.of(projectionsForType.get(ProjectionName.DEFAULT));
                }
    
                // no projection parameter specified
                return Optional.of(projectionsForType.values().iterator().next());
            }
    
            return Optional.empty();
        }
    }
    
  2. ==============================

    2.나는 비슷한 것을 최근에보고 있었고 Spring Data / Jackson 측에서 접근하려고 할 때 서클에서 돌아서 버렸습니다.

    나는 비슷한 것을 최근에보고 있었고 Spring Data / Jackson 측에서 접근하려고 할 때 서클에서 돌아서 버렸습니다.

    대안이며 매우 간단한 해결책은 다른 각도에서 접근하여 HTTP 요청의 Projection 매개 변수가 항상 존재하도록하는 것입니다. 이것은 서블릿 필터를 사용하여 들어오는 요청의 매개 변수를 수정하여 수행 할 수 있습니다.

    이것은 다음과 같습니다.

    public class ProjectionResolverFilter extends GenericFilterBean {
    
        private static final String REQUEST_PARAM_PROJECTION_KEY = "projection";
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
    
            if (shouldApply(request)) {
                chain.doFilter(new ResourceRequestWrapper(request), res);
            } else {
                chain.doFilter(req, res);
            }
        }
    
        /**
         * 
         * @param request
         * @return True if this filter should be applied for this request, otherwise
         *         false.
         */
        protected boolean shouldApply(HttpServletRequest request) {
            return request.getServletPath().matches("some-path");
        }
    
        /**
         * HttpServletRequestWrapper implementation which allows us to wrap and
         * modify the incoming request.
         *
         */
        public class ResourceRequestWrapper extends HttpServletRequestWrapper {
    
            public ResourceRequestWrapper(HttpServletRequest request) {
                super(request);
            }
    
            @Override
            public String getParameter(final String name) {
                if (name.equals(REQUEST_PARAM_PROJECTION_KEY)) {
                    return "nameOfDefaultProjection";
                }
    
                return super.getParameter(name);
            }
        }
    }
    
  3. from https://stackoverflow.com/questions/40289665/spring-data-rest-projection-representation-of-single-resource by cc-by-sa and MIT license