目前我们的电影服务只提供了对电影信息的访问服务,现在我们要再增加两项级服务,分别用来访问导演和演员信息。加上原先的电信信息服务,我们把 URI 统一放到 /ms/rest/service/
的子路径下。最先想到的方法就是为这三个 URI 分别写 JAX-RS 服务:
@Singleton
@Path("service/movie")
public class MovieService {
// 此处省略若干行
}
@Singleton
@Path("service/director")
public class DirectorService {
// 此处省略若干行
}
@Singleton
@Path("service/director")
public class ActorService {
// 此处省略若干行
}
这种写法的缺点就是让三个本来有点关系(父级 URI 相同)的服务被放到了毫不相干的三个类里面,不一个个类地查看注解难以看出这点关系。为此,JAX-RS 提供了动态资源绑定的功能,让我们能够对这种情况做一些整理。
首先,我们引入一个服务定位器来处理集中管理这三个子级服务:
@Singleton
@Path("service")
public class ServiceLocator {
@Inject
private MovieService movieService;
@Inject
private DirectorService directorService;
@Inject
private ActorService actorService;
private Map<String, Object> serviceMap;
@PostConstruct
private initServiceMap() {
serviceMap = new HashMap<>();
serviceMap.put("movie", movieService);
serviceMap.put("director", directorService);
serviceMap.put("actor", actorService);
}
@Path("{name}")
public Object locateService(@PathParam("name") String name) {
Object service = serviceMap.get(name);
if (service == null) {
throw new WebApplicationException(Status.SERVICE_UNAVAILABLE);
}
return service;
}
}
该类中的 locateService
方法根据服务的名称返回相应的服务实例,注意该方法只有一个 @Path
注解,因为它并不清楚请求的具体内容;返回对象的类型为 Object
,表明动态资源定位不要求服务类实现相同的接口,只需要它们的方法带有相应的 JAX-RS 注解,就能够被 JAX-RS 自动发现和处理(专业术语称为 introspect,内省),以 MovieService
为例:
@Singleton
public class MovieService {
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Movie find(@PathParam("id") int id) {
return movieDao.find(id);
}
// 此处省略若干行
}
这样,每个请求实际上都由两个类先后处理。例如,处理请求 GET /ms/rest/service/movie/1
的时候,先由 ServiceLocator
返回相配的服务实例 movieService
,然后再由该实例的 find
方法返回结果。比起最开始那三个简单的类,虽然多了一层调用,但换来了更加清晰的结构。
动态资源定位是一个非常灵活强大的功能,用好的话,完全可以把 URI 层次整理成一个类似于文件目录结构的抽象文件系统。