Spring Data RESTはSpring DataのRepository
を作ればREST APIとして公開してくれる便利プロジェクト。自分は好きで、RESTサービスを作る時は基本これを使う。
Spring Data RESTを使うと、RESTアプリケーションの作りは、典型的な
- Controller -> Service -> Repository
ではなく
- Repository -> RepositoryEventHandler
になる。
Tipsというか、いつも設定している内容をメモっておく。
サーバーサイド
リソースのURLにprefixをつける
デフォルトでは<Host Name>/<Resource Name(複数形)>
になるが、Spring Data RESTが提供するAPIのパスにはprefixをつけた方が認可設定とかしやすい。
例えば、<Host Name>/v1/<Resource Name(複数形)>
にする場合は、application.properties
に
spring.data.rest.base-path=/v1
を設定する。
Bean Validationを有効にする
依存関係の順番で@Lazy
つけておかないとエラーになる。Validator
が複数登録されることもあるので、@Qualifier("mvcValidator")
もあると無難。
@Configuration
public static class RestConfig extends RepositoryRestConfigurerAdapter {
private final Validator validator;
public RestConfig(@Lazy @Qualifier("mvcValidator") Validator validator) {
this.validator = validator;
}
@Override
public void configureValidatingRepositoryEventListener(
ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
/* ... */
}
EntityのIDをレスポンスに含める
デフォルトだと、リソースのレスポンスにIDフィールドが含まれず、_links
にエンティにアクセスするためのURLが入る。
これでもいいが、ID入っている方が便利な時があるので、その時は明示的にexposeする設定を書く。
@Configuration
public static class RestConfig extends RepositoryRestConfigurerAdapter {
/* ... */
@Override
public void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
config.exposeIdsFor(FooBar.class);
}
}
一部のAPIだけRESTで公開する
デフォルトだとRepository
のCRUD全APIが公開される。
公開して欲しくないAPIを除く場合はメソッドをOverrideして@RestResource(exported = false)
をつける。
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RestResource;
public interface FooBarRepository extends CrudRepository<FooBar, Long> {
@RestResource(exported = false)
FooBar save(FooBar foobar);
@RestResource(exported = false)
void delete(Long id);
}
欲しいAPIだけ定義する場合は空のRepository
から定義していく。
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.repository.Repository;
public interface FooBarRepository extends Repository<FooBar, Long> {
Optional<FooBar> findOne(Long id);
FooBar save(FooBar foobar);
void delete(Long id);
}
公開されているAPIはhttp://localhost:8080/profile/foobars
で確認できる(spring.data.rest.base-path=/v1
が付いている場合はhttp://localhost:8080/v1/profile/foobars
)。
トランザクション管理
デフォルトでは@RepositoryEventHandler
の処理とRepository
の処理が別トランザクションになってしまう。作り上難しいのだが、同じトランザクション内で処理するのに、無理やりインターセプタを適用する。
// Workarround http://stackoverflow.com/a/30713264/5861829
@Configuration
static class TxConfig {
private final PlatformTransactionManager transactionManager;
public TxConfig(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Bean
TransactionInterceptor txAdvice() {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.addTransactionalMethod("post*", new DefaultTransactionAttribute());
source.addTransactionalMethod("put*", new DefaultTransactionAttribute());
source.addTransactionalMethod("delete*", new DefaultTransactionAttribute());
source.addTransactionalMethod("patch*", new DefaultTransactionAttribute());
return new TransactionInterceptor(transactionManager, source);
}
@Bean
Advisor txAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(
"execution(* org.springframework.data.rest.webmvc.RepositoryEntityController.*(..))");
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
OAuth2のリソースサーバーにした時のスコープによる認可設定
例えばAccountリソース(パスは/v1/accoounts
)に対してGETはaccount.read
またはadmin.read
スコープ、POST, PUT, PATCH, DELETEにはaccount.write
またはadmin.write
スコープが必要にしたい場合。
@Configuration
@EnableResourceServer
static class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.antMatcher("/v1/**").authorizeRequests()
.antMatchers(HttpMethod.GET, "/v1/accounts/**")
.access("#oauth2.hasAnyScope('account.read', 'admin.read')")
.antMatchers(HttpMethod.POST, "/v1/accounts/**")
.access("#oauth2.hasAnyScope('account.write', 'admin.write')")
.antMatchers(HttpMethod.PUT, "/v1/accounts/**")
.access("#oauth2.hasAnyScope('account.write', 'admin.write')")
.antMatchers(HttpMethod.PATCH, "/v1/accounts/**")
.access("#oauth2.hasAnyScope('account.write', 'admin.write')")
.antMatchers(HttpMethod.DELETE, "/v1/accounts/**")
.access("#oauth2.hasAnyScope('account.write', 'admin.write')");
}
}
当然、OAuth2クライアントに対するスコープの設定は認可サーバー側の話。
クライアントサイド
Jackson2HalModule
を有効にする
サーバーサイドがspring-data-rest
だとResources
型ででシリアライズしたい。デフォルトではでシリアライザが有効にならないのでObjectMapper
に追加が必要。
@Bean
Jackson2ObjectMapperBuilderCustomizer objectMapperBuilderCustomizer() {
return builder -> {
builder.modulesToInstall(new Jackson2HalModule());
};
}
RestTemplate
はRestTemplateBuilder
で作る。
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
OAuth2RestTemplate
を使う場合はMessageConverter
を明示的に設定しないといけない。
@Bean
OAuth2RestTemplate restTemplate(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource,
HttpMessageConverters messageConverters, ObjectMapper objectMapper) {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource,
oauth2ClientContext);
// Set same message converters as RestTemplate to use Jackson2HalModule
restTemplate.setMessageConverters(messageConverters.getConverters());
// ...
return restTemplate;
}
ちなみにwebアプリ側で@EnableOAuthSso
を使っている場合は、でトークン上記の認可設定をしている場合は、次の設定をしてspring-cloud-starter-oauth2
を依存ライブラリに追加すれば、OAuth2RestTemplate
に自動でアクセストークンが入る。
security.oauth2.client.client-id=<Client ID>
security.oauth2.client.client-secret=<Client Secret>
security.oauth2.client.access-token-uri=<Auth Server URL>/oauth/token
security.oauth2.client.user-authorization-uri=<Auth Server URL>/oauth/authorize
security.oauth2.client.scope=account.read,account.write
また追加する。