16. 自定义Spring Data REST
有许多选项来定制Spring Data REST。展示如以下这些小节。
16.1. 定制项目的URI
默认情况下,项目资源的URI是由用于标识附加数据库馆藏资源的路径段组成的。允许我们用知识库中的findOne(…)
方查看实体实例。作为Spring Data REST2.5这可以通过在API repositoryrestconfiguration
定制(java 8优先)上进行配置或注册Entitylookup
为应用程序中的Spring bean的实现。Spring Data REST会重新整理并根据实施调整URI。
假设一个User
的username
的属性,唯一地标识它。同时,假设我们有一个方法Optional<User> findByUsername(String username)
在库中。
在java 8我们可以简单的映射方法为弱的URI创建如下方法参考:
@ComponentpublicclassSpringDataRestCustomizationextendsRepositoryRestConfigurerAdapter{@Overridepublicvoid configureRepositoryRestConfiguration(RepositoryRestConfiguration config){
config.withCustomEntityLookup().//
forRepository(UserRepository.class,User::getUsername,UserRepository::findByUsername);}}
forRepository(…)
将存储库类型作为第一个参数,一个方法参考映射到某个目标类型的存储域类型,以及另一个方法引用使用所说的第一个参数的存储库映射。
如果你不是使用java 8或更高版本,你可以这个方法,但它需要使用一些相当冗长的匿名内部类。这就是为什么在旧的java版本你可能喜欢实现UserEntityLookup
接口,就像:
@ComponentpublicclassUserEntityLookupextendsEntityLookupSupport<User>{privatefinalUserRepository repository;publicUserEntityLookup(UserRepository repository){this.repository = repository;}@OverridepublicSerializable getResourceIdentifier(User entity){return entity.getUsername();}@OverridepublicObject lookupEntity(Serializable id){return repository.findByUsername(id.toString());}}
注意,被创建使用URIgetResourceIdentifier(…)
如何返回用户名。我们通过现在实现lookupEntity(…)
接口使用查询方法在userrepository
的返回值加载实体实例
16.2. 配置其余的网址路径
在JPA库资源出口下配置部分的URL路径是简单的。您只需在类级别和/或查询方法级别上添加注释。
默认情况下,出口商将暴露你的 CrudRepository
使用域的类的名称。Spring Data REST也适用于 Evo Inflector 对多元化这个词。因此,一个存储库定义如下:
interfacePersonRepositoryextendsCrudRepository<Person,Long>{}
是的,默认情况下,暴露的URL [http://localhost:8080/persons/](http://localhost:8080/persons/)
为了改变该库的出口,增加一个@RestResource
在类级别注释:
@RepositoryRestResource(path ="people")interfacePersonRepositoryextendsCrudRepository<Person,Long>{}
存储库将被访问的网址: [http://localhost:8080/people/](http://localhost:8080/people/)
如果您有定义的查询方法,也将默认会被它们的名字所暴露:
interfacePersonRepositoryextendsCrudRepository<Person,Long>{List<Person> findByName(String name);}
这将被暴露在URL:[http://localhost:8080/persons/search/findByName](http://localhost:8080/persons/search/findByName)
下
所有的查询方法资源都暴露在资源下 search . |
|
---|---|
在查询方法暴露情况下,改变不跟URL,再次使用 @RestResource
标注:
@RepositoryRestResource(path ="people")interfacePersonRepositoryextendsCrudRepository<Person,Long>{@RestResource(path ="names")List<Person> findByName(String name);}
这个查询方法将暴露在URL下: [http://localhost:8080/people/search/names](http://localhost:8080/people/search/names)
16.2.1. 处理关系
由于这些资源都是可以找到的,你也可以影响到"rel"属性如何显示在出口商发送的链接中。、
例如,在默认配置中,如果你发送一个请求给[http://localhost:8080/persons/search](http://localhost:8080/persons/search)
找出哪些查询方法暴露了,你会得到一个链接列表:
{"_links":{"findByName":{"href":"http://localhost:8080/persons/search/findByName"}}}
为了改变相对价值,使用rel
特性对@RestResource
注释:
@RepositoryRestResource(path ="people")interfacePersonRepositoryextendsCrudRepository<Person,Long>{@RestResource(path ="names", rel ="names")List<Person> findByName(String name);}
这将产生一个链接:
{"_links":{"names":{"href":"http://localhost:8080/persons/search/names"}}}
这些片段的JSON默认你使用的是Spring Data REST的默认格式 HAL.关掉 HAL这是可能的,因为这将导致不同输出。但是你的重写相关名称是完全独立的呈现格式。 | |
---|---|
@RepositoryRestResource(path ="people", rel ="people")interfacePersonRepositoryextendsCrudRepository<Person,Long>{@RestResource(path ="names", rel ="names")List<Person> findByName(String name);}
改变相关存储库调整顶级名:
{"_links":{"people":{"href":"http://localhost:8080/people"},…}}
在上面的顶级片段:
path = "people"
把href
里面的/persons
值改成/people
rel = "people"
把链接名从persons
改成people
当你导航到 search 这个存储库的资源时, 这查找器方法 @RestResource
的注释改变了如下所示的路径:
{"_links":{"names":{"href":"http://localhost:8080/people/search/names"}}}
这组在你存储库定义中的注释造成了以下更改:
储存库层次注释的
path = "people"
反映在基本URI中/people
作为一个查找器方法为你提供
/people/search
path = "names"
创建一个URI的/people/search/names
rel = "names"
改变链接的名称由findByNames
到names
16.2.2. 隐藏某些存储库,查询方法或字段
你可能不希望某些存储库,存储库的查询方法,或者实体被导出。 例如包括隐藏在password
上的一个 User
的字段对象或类似的敏感数据. 去告诉输出口不输出这些项目, 为 @RestResource
添加注释和设定 exported = false
.
例如,跳过出口一个存储库:
@RepositoryRestResource(exported =false)interfacePersonRepositoryextendsCrudRepository<Person,Long>{}
跳过出口查询方法:
@RepositoryRestResource(path ="people", rel ="people")interfacePersonRepositoryextendsCrudRepository<Person,Long>{@RestResource(exported =false)List<Person> findByName(String name);}
或跳过输出字段:
@EntitypublicclassPerson{@Id@GeneratedValueprivateLong id;@OneToMany@RestResource(exported =false)privateMap<String,Profile> profiles;}
预测提供了有效的手段去改变导出side step these settings. 如果你对相同的域对象创建了预测, 这是你的责任不导出这些域. 看 | |
---|---|
16.2.3. 隐藏存储库的CRUD方法
如果你不想在你的 CrudRepository
上公开保存或删除方法, 你可以使用 @RestResource(exported = false)
把你想关掉和放置注释在重载版本通过重写的方式设置. 例如, 在CrudRepository
中为了防止HTTP用户调用删除方法, 覆盖所有的人,并将注释添加到重载方法.
@RepositoryRestResource(path ="people", rel ="people")interfacePersonRepositoryextendsCrudRepository<Person,Long>{@Override@RestResource(exported =false)voiddelete(Long id);@Override@RestResource(exported =false)voiddelete(Person entity);}
很重要的是,你想覆盖 both 删除方法作为导出 决定使用一些简单的算法,该方法使用CRUD具有更好的运行性能 . 现在不可能关闭一个需要身份证的删除的版本,但留下导出的版本需要一个实体实例。 F或者时间,您可以导出删除方法或不导出. 如果你想去关闭他们, 然后记住,你必须为这两个版本的注释 exported = false . |
|
---|---|
16.3. 添加 Spring Data REST 到现有的Spring MVC应用程序
如果你正在使用以下步骤,除非你正在使用 Spring Boot. spring-boot-starter-data-rest 将使它自动添加到您的应用程序. | |
---|---|
如果你有一个现有的 Spring MVC 应用程序并且你想整合 Spring Data REST, 这其实很简单。
在你的 Spring MVC 配置中 (最有可能在你配置MVC资源的那里) 添加一个引用JavaConfig类,负责配置 RepositoryRestController
. 这个类的名字是 org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
.
在java中你将看到这些:
import org.springframework.context.annotation.Import;import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration;@Configuration@Import(RepositoryRestMvcConfiguration.class)publicclassMyApplicationConfiguration{…}
在XML中看起来会是这样:
<beanclass="org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration"/>
当你遇到这个bean的定义和应用,它将引导必要的 Spring MVC 资源到完全配置控制器导出它发现的存储库 ApplicationContext
和任何父上下文.
16.3.1. More on required configuration
有一对Spring MVC资源Spring Data Rest取决于必须要在现有的Spring MVC应用工作的正确配置。我们试图隔离任何类似的资源,这些资源已经存在于你的应用程序,但它可能是你想自定义一些Spring Data Rest行为通过修改这些MVC组件
最重要的东西是我们的配置,特别包括Spring Data Rest。
RepositoryRestHandlerMapping
我们注册一个自定义 HandlerMapping
仅响应的实例 RepositoryRestController
只有当一条路径是由Spring Data Rest处理。为了保持的路径,是指由您的应用程序分开处理Spring Data Rest自定义 HandlerMapping
检查网址路径和检查,看看是否有一个存储库已被导出到该名称下. 如果它有,它允许要求被Spring Data REST处理. 如果没有在这个名称下导出的存储库, 它会返回 null
, 这就意味着 "让其他 HandlerMapping
实例试图服务此请求".
The Spring Data REST HandlerMapping
配置 order=(Ordered.LOWEST_PRECEDENCE - 100)
这意味着它通常会在第一次上线时,来映射一个网址路径. 为了一个存储库,您的现有应用程序将永远不会得到服务请求的机会。 举个列子, 如果您有一个根据名称导出的存储库 "person", 然后你请求的所有应用程序启动/person
将被 Spring Data REST 处理然后您的应用程序将永远不会看到该请求。 如果您的存储库是在不同的名称下导出的, (like "people"),然后请求 /people
将来到Spring Data REST 并要求 "/person" 将由您的应用程序处理.
16.4. Overriding Spring Data REST Response Handlers
Sometimes you may want to write a custom handler for a specific resource. To take advantage of Spring Data REST’s settings, message converters, exception handling, and more, use the @RepositoryRestController
annotation instead of a standard Spring MVC @Controller
or @RestController
:
@Repository RestController public class ScannerController{privatefinalScannerRepository repository;@AutowiredpublicScannerController(ScannerRepository repo){(1)
repository = repo;}@RequestMapping(method = GET, value ="/scanners/search/listProducers")(2)public@ResponseBodyResponseEntity<?> getProducers(){List<String> producers = repository.listProducers();(3)//// do some intermediate processing, logging, etc. with the producers//Resources<String> resources =newResources<String>(producers);(4)
resources.add(linkTo(methodOn(ScannerController.class).getProducers()).withSelfRel());(5)// add other links as neededreturnResponseEntity.ok(resources);(6)}}
该控制器将从RepositoryRestConfiguration.setBasePath
定义相同的API基本路径所使用的所有其它基于REST的端点(e.g. /api)提供服务。它还具有以下特点:
1 | 这个例子使用构造器注入。 |
---|---|
2 | 此处理程序插入一个Spring Data查找方法自定义处理程序。 |
3 | 该处理程序使用底层的存储库来获取数据,但是在将最终的数据集返回到客户端之前会做一些形式的后处理。 |
4 | 结果需要包裹在Spring HATEOAS Resources 对象来返回一个集合,但只有一个Resource 为单个项目。 |
5 | 添加一个链接返回到这个确切的方法作为一个"自我"链接 |
6 | 返回使用Spring MVC的ResponseEntity 包装确认收集正确包装和呈现在适当的接受式的集合。 |
Resources
为一个集合而Resource
为单个的项目。这些类型可以组合使用。如果你知道一个集合中每个项目的链接,使用Resources<Resource<String>>
(或任何核心域类型)。这可以让你组装环节的每个项目,以及整个集合。
在这个例子中, 合并的路径将是 RepositoryRestConfiguration.getBasePath() + /scanners/search/listProducers 。 |
|
---|---|
如果你对特定实体的操作不感兴趣但仍希望创建basePath
自定义操作,如Spring MVC的观点,资源等等。使用@BasePathAwareController
。
如果你对任意项目使用@Controller 或者 @RestController , 代码将完全超过 Spring Data REST的范围。 这扩展到请求处理,消息转换器,异常处理等。 |
|
---|---|
16.5. 定制的JSON输出
有时候,在你的应用程序中你需要提供由特定实体到其他资源的链接。例如,一个Customer
的反应可能会被链接到当前的购物车,或者链接来管理丰富的相关的实体资源。Spring Data REST提供集成Spring HATEOAS并为用户提供了一个扩展钩以改变对客户端的资源的表现形式。
16.5.1. 这个ResourceProcessor接口
Spring HATEOAS 定义了一个ResourceProcessor<>
接口用于处理实体。所有类型为ResourceProcessor<Resource<T>>
的beans会自动由Spring Data REST出口自动回收和序列化类型T
的实体时触发。
例如,要给一个Person
实体定义一个处理器,添加一个@Bean
到你的ApplicationContext像下面的(从 Spring Data REST 实验得到):
@BeanpublicResourceProcessor<Resource<Person>> personProcessor(){returnnewResourceProcessor<Resource<Person>>(){@OverridepublicResource<Person> process(Resource<Person> resource){
resource.add(newLink("http://localhost:8080/people","added-link"));return resource;}};}
这个例子的硬代码链接到 [http://localhost:8080/people](http://localhost:8080/people) . 如果在你的应用程序中有Spring MVC端点你想链接到,考虑使用Spring HATEOAS的 linkTo(…) 方法来避免管理URL. |
|
---|---|
16.5.2. 添加链接
这个可能就像上面的例子通过简单的调用resource.add(Link)
添加链接到实体的默认表示上。任何链接添加到Resource
都将被添加到最终输出。
16.5.3. 自定义表示
Spring Data REST在创建输出形式之前发现任意ResourceProcessor
s执行。它通过创建一个Converter<Entity, Resource>
实例与内部ConversionService
做到这一点。这是负责创建链接引用的实体部件(例如根据这些对象的_links在对象的JSON上表示属性)。这需要一个@Entity
和遍历其属性,创造了一个链接由一个Repository
跨入任何嵌入或简单的属性管理和复制这些属性。
如果你的项目需要有不同的输出格式,然而,它可能完全替代自己默认的传出JSON表示。如果在ApplicationContext中创建自己的ConversionService
并创建自己的Converter<Person, Resource>
,那么你就可以回到一个Resource
去实现你所选择的。
16.6. 添加定制的 Jackson’s ObjectMapper序列
有时候Spring Data REST’s ObjectMapper
已专门配置为使用智能串行器,可以把 域对象转化为链接,然后再返回,可能无法正确处理你的域模型的行为。有很多方法可以组织你的数据,你会发现你自己的域模型没有被翻译成正确的JSON。这也是有时并不在这些情况下,实际的尝试,并支持复杂的域模型的通用方法。有时,根据不同的复杂性,这甚至不可能提供一个通用的解决方案。
因此,为了适用用例的比例最大,Spring Data REST很难正确的呈现你的对象图,它会尝试序列化正常的POJOs为非受管的beans,它会尝试创建链接到受管的beans,这是必要的。但是,如果你的领域模型不容易让自己读取或写入纯JSON,你可能需要配置Jackson’s ObjectMapper与自己自定义的类映射和串行器。
16.6.1. 抽象类创建
当你在你的领域模型中使用抽象类或接口时,你可能需要挂接到一个关键的配置点。 Jackson将不知道怎样默认创建实现一个接口。看看下面的例子:
@Entity public class MyEntity{ @OneToMany private List<MyInterface> interfaces; }