什么是RESTful架构
- 每一个URI代表一种资源
- 客户端和服务端之间传递这种资源的某种表现层(“资源”具体呈现出来的形式叫做它的“表现层”。)
- 对于资源的具体操作,由HTTP动词表示。客户端通过HTTP动词(GET,POST,PUT,DELETE)对服务端资源进行操作,实现表现层状态转换
当需要以更新的形式来修改某一具体资源的时候,如何判断用PUT还是POST呢?
很简单,如果该更新对应的URI多次调用的结果一致,则用PUT。比如更新某个blog文章,因为该文章具有单一的具体URI,所以每次更新提交相同的内容,结果都一致。接口例如:/blog/{document_id}/update。
在每次更新提交相同的内容,最终的结果不一致的时候,用POST。举个很常见的例子,一个接口的功能是将当前余额减一个值,每次提交指定该值为100,接口例如:/amount/deduction。调用一次,你的余额-100,调用两次,余额-200。这个时候就用POST。
JAX-RS annotations
Annotation | Description |
---|---|
@PATH(your_path) | Sets the path to base URL + /your_path. The base URL is based on your application name, the servlet and the URL pattern from the web.xml configuration file. |
@POST | Indicates that the following method will answer to an HTTP POST request. |
@GET | Indicates that the following method will answer to an HTTP GET request. |
@PUT | Indicates that the following method will answer to an HTTP PUT request. |
@DELETE | Indicates that the following method will answer to an HTTP DELETE request. |
@Produces(MediaType.TEXT_PLAIN[, more-types]) | @Produces defines which MIME type is delivered by a method annotated with @GET. In the example text (“text/plain”) is produced. Other examples would be “application/xml” or “application/json”. |
@Consumes(type[, more-types]) | @Consumes defines which MIME type is consumed by this method. |
@PathParam | Used to inject values from the URL into a method parameter. This way you inject, for example, the ID of a resource into the method to get the correct object. |
@DefaultValue | 设置@QueryParam参数的默认值,如: @DefaultValue(“2”) @QueryParam(“step”) int step |
@Context | 解释上下文参数 |
下面看一个简单例子:
服务端:
//'启动服务器
public class Test {
//服务器路径
public static final String BASE_URI="http://localhost:8080/myapp";
final static ResourceConfig rc = new ResourceConfig().packages("com.chm.test");
public static HttpServer startServer()
{
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);//URI.create(BASE_URI), rc
return server;
}
public static void main(String[] args) throws Exception {
// final HttpServer server = startServer();
// final HashMap<String, String> initParams = new HashMap<String, String>();
//
// initParams.put("jersey.config.server.provider.packages", "com.chm.test");//packages定义了Jersey寻找服务类的位置。 它必须指向定义的资源类。
System.out.println("Starting grizzly...");
// HttpServer server = GrizzlyWebContainerFactory.create(BASE_URI, initParams);
HttpServer server = startServer();
System.out.println(String.format("jersey app started" ));
System.in.read();
server.shutdown();
System.exit(0);
}
}
//资源类
@Path("/user")
public class User {
// @GET
//// @Path("/{id}")
//// @Produces(MediaType.TEXT_PLAIN)
// @Produces(MediaType.APPLICATION_JSON)
// public String sayHello(@Context HttpServletRequest request)
// {
// String age = request.getHeader("contentType");
// System.out.println(age);
// Map<String, String> map = new HashMap<String, String>();
//// System.out.println("hello");
// map.put("aa", "zhangsan");
// map.put("bb", "lisi");
// String str = "{\"name\":\"zhangsan\",\"age\":24}";
//// System.out.println(id);
// return str;
// }
@GET
public String get(@Context UriInfo ui) {
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
List<String> age = queryParams.get("age");
System.out.println(age);
MultivaluedMap<String, String> pathParams = ui.getPathParameters();
return "success";
}
@PUT
@Path("{chm}")
@Produces(MediaType.TEXT_PLAIN)
public String update(@PathParam("chm") String name, String hello)
{
System.out.println(hello);
System.out.println(name);
return "put invoke";
}
@POST
@Path("f")
public String postFile(final File file)
{
String result = "";
try
{
BufferedReader br = new BufferedReader(new FileReader(file));
return result = br.readLine();
}catch(Exception ex)
{
ex.printStackTrace();
}
return result;
}
}
//客户端
public class ClientTest {
public static void main(String[] args) {
// HttpServer server = Test.startServer();
//可以有多个过滤器
final Client client = ClientBuilder.newClient();
// client.register(Filter.class);
// WebTarget target = client.target(Test.BASE_URI);
// Entity<String> entity = Entity.entity("secrety", MediaType.TEXT_PLAIN);
// Response resp = target.path("/user?age=26;addr=ah").request().get();//
// System.out.println(resp.getStatus());
// resp.readEntity(String.class);
// System.out.println(resp.hasEntity());
// String str = resp.readEntity(String.class);
// System.out.println(str);
// Form form = new Form();
// form.param("x", "y");
WebTarget target = client.target("http://localhost:8080/jersey/rest/hello");
Entity<String> entity = Entity.entity("<MyBean><anyString>Hello World!</anyString><anyNumber>42</anyNumber></MyBean>", MediaType.TEXT_HTML_TYPE);
Response resp = target.request().post(entity);
resp.close();
// target.path("/hello/charming").request().put(entity, String.class);
// String path = System.getProperty("user.dir")+"/src/test.txt";
// System.out.println(path);
// File f = new File(path);
// Entity<File> e = Entity.entity(f, MediaType.TEXT_PLAIN_TYPE);
// String str1 = target.path("/user/f").request().post(e, String.class);
// System.out.println(str1);
//
}
}
资源的生命周期
如下所示的例子
import javax.inject.Singleton;
@Path("/item")
public class ItemResource {
@Path("content")
public Class<ItemContentSingletonResource> getItemContentResource() {
return ItemContentSingletonResource.class;
}
}
@Singleton
public class ItemContentSingletonResource {
// this class is managed in the singleton life cycle
}
JAX-RS resources are managed in per-request scope by default which means that new resource is created for each request. In this example the javax.inject.Singleton annotation says that the resource will be managed as singleton and not in request scope. The sub-resource locator method returns a class which means that the runtime will managed the resource instance and its life-cycle. If the method would return instance instead, the Singleton annotation would have no effect and the returned instance would be used.
我们来看看 Resource scopes
scope | Annotation | Annotation full class name | Description |
---|---|---|---|
Request scope | @RequestScoped (or none) | org.glassfish.jersey.process.internal.RequestScoped | Default lifecycle (applied when no annotation is present). In this scope the resource instance is created for each new request and used for processing of this request. If the resource is used more than one time in the request processing, always the same instance will be used. This can happen when a resource is a sub resource is returned more times during the matching. In this situation only on instance will server the requests. |
Per-lookup scope | @PerLookup | org.glassfish.hk2.api.PerLookup | In this scope the resource instance is created every time it is needed for the processing even it handles the same request. |
Singleton | @Singleton | javax.inject.Singleton | In this scope there is only one instance per jax-rs application. Singleton resource can be either annotated with @Singleton and its class can be registered using the instance of Application. You can also create singletons by registering singleton instances into Application. |
RESTful典型的设计误区
- URI包含动词。“资源”是一种实体,应该是名词。URI不应该有动词,动词放在HTTP协议中。
- URI中加入版本号。
例如
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo
不同的版本可以理解为同一种资源的不同表现形式,所以应该用同一个URI,版本号可以在HTTP请求头信息中的Accept字段中区分:
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0