최근 업무에서 /api/entities
이란 URL이 존재하는데, 아무리 Source를 뒤져봐도 해당 URL을 찾을 수 없었다.
1 2 3 4 5 6 7 8 9 10 |
class checkPage extends React.Component { render() { return ( <BasePage resource='/api/entities' ... /> ); } } |
몇 번 삽질을 하다가 결국 spring-data-rest
를 사용했음을 알게 되었고, 해당 Library를 초기 분석한 내용을 기록해둔다.
공식문서1에 따르면 dependency는 아래처럼 설정해주면 된다.
1 2 3 4 5 6 7 8 9 |
dependencies { compile("org.springframework.boot:spring-boot-starter-data-rest") ... } dependencies { compile("org.springframework.data:spring-data-rest-webmvc:3.0.5.RELEASE") ... } |
그리고 기본 BaseUrl은 아래처럼 설정할 수 있다. (구성에 따라 아래 3가지 방법중 어느 것을 선택해도 동작한다)
1 |
spring.data.rest.basePath=/api |
1 2 3 4 5 6 7 8 9 10 11 12 |
@Configuration class CustomRestMvcConfiguration { @Bean public RepositoryRestConfigurer repositoryRestConfigurer() { return new RepositoryRestConfigurerAdapter() { @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { config.setBasePath("/api"); } }; } } |
1 2 3 4 5 6 7 |
@Component public class CustomizedRestMvcConfiguration extends RepositoryRestConfigurerAdapter { @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { config.setBasePath("/api"); } } |
위 설정으로 인해 앞으로 자동으로 생성되는 모든 Rest URL은 /api
하위에 생성된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class RepositoryCollectionResourceMapping implements CollectionResourceMapping { private static final boolean EVO_INFLECTOR_IS_PRESENT = ClassUtils.isPresent("org.atteo.evo.inflector.English", null); RepositoryCollectionResourceMapping(RepositoryMetadata metadata, RelProvider relProvider, RepositoryDetectionStrategy strategy) { ... Class<?> domainType = metadata.getDomainType(); this.domainTypeMapping = EVO_INFLECTOR_IS_PRESENT ? new EvoInflectorTypeBasedCollectionResourceMapping(domainType, relProvider) : new TypeBasedCollectionResourceMapping(domainType, relProvider); ... } } |
EVO_INFLECTOR_ISPRESENT라는 것은 org.atteo.evo.inflector.English가 classpath에 존재하는지 체크하는 것이고,
일반적인 경우라면 spring dependency로 가져왔을테니까 일반적으로는 true값이 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class EvoInflectorTypeBasedCollectionResourceMapping extends TypeBasedCollectionResourceMapping { @Override protected String getDefaultPathFor(Class<?> type) { return English.plural(super.getDefaultPathFor(type)); } } class TypeBasedCollectionResourceMapping implements CollectionResourceMapping { @Override public Path getPath() { String path = annotation == null ? null : annotation.path().trim(); path = StringUtils.hasText(path) ? path : getDefaultPathFor(type); return new Path(path); } protected String getDefaultPathFor(Class<?> type) { return getSimpleTypeName(type); } private String getSimpleTypeName(Class<?> type) { return StringUtils.uncapitalize(type.getSimpleName()); } } |
EvoInflectorTypeBasedCollectionResourceMapping.java는 TypeBasedCollectionResourceMapping.java를 래핑한다.
class type이라는 것은 rest api를 생성할 entity의 class name인데, uncapitalize()를 통과하면서 첫자가 소문자로 변경된다.
그리고 EvoInflectorTypeBasedCollectionResourceMapping.java를 실행해서 영어 기준의 복수형으로 변환하여 준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class English extends TwoFormInflector { /** * Returns plural form of the given word. * <p> * For instance: * <pre> * {@code * English.plural("cat") == "cats"; * } * </pre> * </p> * @param word word in singular form * @return plural form of given word */ public static String plural(String word) { return inflector.getPlural(word); } } |
EvoInflectorTypeBasedCollectionResourceMapping.java의 English.java는 영어의 복수형을 제공하는 Library이다.
불규칙명사(Entity class name은 일반적으로 명사)인 경우도 모두 처리해주고 있다.
(단, person -> people은 처리되지 않고, persons라고 나온다)
자세한 건 evo-inflector2를 참고하면 좋을 것 같다.
생성되는 URL은 대략 아래와 같다.
아래 URL은 spring-data-rest-webmvc에서 제공하는 규칙을 따르는데, RepositoryEntityController.java를 참고하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
2018-03-07 11:23:28.869 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}],methods=[OPTIONS],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.869 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}],methods=[HEAD],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.870 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}],methods=[GET],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.870 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}],methods=[GET],produces=[application/x-spring-data-compact+json || text/uri-list]}" 2018-03-07 11:23:28.870 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}],methods=[POST],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.871 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}],methods=[OPTIONS],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.871 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}],methods=[HEAD],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.872 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}],methods=[GET],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.872 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}],methods=[PUT],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.872 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}],methods=[PATCH],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.873 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}],methods=[DELETE],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.874 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}/{property}],methods=[GET],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.874 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}/{property}/{propertyId}],methods=[GET],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.875 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}/{property}],methods=[DELETE],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.875 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}/{property}],methods=[GET],produces=[application/x-spring-data-compact+json || text/uri-list]}" 2018-03-07 11:23:28.875 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}/{property}],methods=[PATCH || PUT || POST],consumes=[application/json || application/x-spring-data-compact+json || text/uri-list],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.876 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/{id}/{property}/{propertyId}],methods=[DELETE],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.877 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/search],methods=[OPTIONS],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.877 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/search],methods=[HEAD],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.877 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/search],methods=[GET],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.878 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/search/{search}],methods=[GET],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.878 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/search/{search}],methods=[GET],produces=[application/x-spring-data-compact+json]}" 2018-03-07 11:23:28.878 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/search/{search}],methods=[OPTIONS],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.879 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/{repository}/search/{search}],methods=[HEAD],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.879 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/ || /api],methods=[HEAD],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.879 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/ || /api],methods=[GET],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.880 INFO 5634 - [main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/api/ || /api],methods=[OPTIONS],produces=[application/hal+json || application/json]}" 2018-03-07 11:23:28.887 INFO 5634 - [main] o.s.d.r.w.BasePathAwareHandlerMapping : Mapped "{[/api/profile/{repository}],methods=[GET],produces=[application/schema+json]}" 2018-03-07 11:23:28.887 INFO 5634 - [main] o.s.d.r.w.BasePathAwareHandlerMapping : Mapped "{[/api/profile/{repository}],methods=[GET],produces=[application/alps+json || */*]}" 2018-03-07 11:23:28.887 INFO 5634 - [main] o.s.d.r.w.BasePathAwareHandlerMapping : Mapped "{[/api/profile/{repository}],methods=[OPTIONS],produces=[application/alps+json]}" 2018-03-07 11:23:28.888 INFO 5634 - [main] o.s.d.r.w.BasePathAwareHandlerMapping : Mapped "{[/api/profile],methods=[OPTIONS]}" 2018-03-07 11:23:28.888 INFO 5634 - [main] o.s.d.r.w.BasePathAwareHandlerMapping : Mapped "{[/api/profile],methods=[GET]}" |