Spring为开发REST服务提供一流的支持。在本文中,我们将使用Spring 4 @RestController
注解开发基于Spring 4 MVC的RESTful JSON服务和RESTful XML服务。
Spring在内部使用 HttpMessageConverters
将响应转换为所需的格式[JSON / XML / etc ..],这些格式基于类路径中可用的某些库,并可以选择使用请求中的 Accept Headers
。
为了服务JSON,我们将使用Jackson库[jackson-databind.jar]。 对于XML,我们将使用Jackson XML扩展[jackson-dataformat-xml.jar]。 只有在类路径中存在这些库才会触发Spring以所需格式转换输出。 此外,我们将进一步通过使用JAXB批注注释域类来支持XML,以防Jackson的XML扩展库由于某种原因而不可用。
注意:如果你通过在浏览器中输入网址发送请求,则可以添加后缀[.xml / .json],以帮助确定要提供的内容的类型。
文章使用的是SpringBoot 1.5.2版本,并使用MAVEN3管理项目。
public class Message { String name; String text; public Message(String name, String text) { this.name = name; this.text = text; } public String getName() { return name; } public String getText() { return text; } }
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.websystique.springmvc.domain.Message; @RestController public class HelloWorldRestController { @RequestMapping("/") public String welcome() { return "Welcome to RestTemplate Example."; } @RequestMapping("/hello/{player}") public Message message(@PathVariable String player) { Message msg = new Message(player, "Hello " + player); return msg; } }
如果jackson-dataformat-xml.jar不可用,并且您仍希望获得XML响应,则只需在模型类(Message)上添加JAXB注释,即可启用XML输出支持。 以下是相同的演示
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "player") public class Message { String name; String text; public Message(){ } public Message(String name, String text) { this.name = name; this.text = text; } @XmlElement public String getName() { return name; } @XmlElement public String getText() { return text; } }
有了以上的准备,你可以通过下面的请求url来获取指定格式的响应:
http://127.0.0.1:2223/hello/tom
http://127.0.0.1:2223/hello/tom.jsonhttp://127.0.0.1:2223/hello/tom 添加请求头 Accept:application/xml
或
http://127.0.0.1:2223/hello/tom.xml
ContentNegotiationStrategy
是一个策略接口,作用是将给定的请求解析为媒体类型( MediaType
)列表。
它有两个重要的实现类,如下所示
根据请求路径的扩展名来解析
根据请求头 Accept
来解析
Spring 在内部会根据请求的MediaType信息和HttpMessageConverter支持的MediaType进行匹配,如果能找到支持该请求的MediaType的HttpMessageConverter,则利用该HttpMessageConverter输出响应。
REST代表 Representational State Transfer
。它是一种可用于设计Web服务的架构风格,可从各种客户端使用。 其核心思想是,不使用诸如CORBA,RPC或SOAP之类的复杂机制来连接机器,而是使用简单的HTTP来进行调用。
在基于REST的设计中,对资源的操作是通过一组通用的动词来实现:
这意味着,作为REST服务开发人员或调用方,你应该遵守上述标准。
通常基于Rest的Web服务返回JSON或XML作为响应,尽管它不仅限于这些类型。 客户端可以指定(使用HTTP Accept头)他们感兴趣的资源类型,服务器可以返回资源,指定它正在服务的资源的Content-Type。 想要详细了解REST,这个 StackOverflow 是必须要阅读的。
以下是一个基于Rest的 Contrller
,实现了REST API。
该 Contrller
是提供了如下的API:
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; import com.websystique.springmvc.model.User; import com.websystique.springmvc.service.UserService; @RestController public class HelloWorldRestController { @Autowired UserService userService; //Service which will do all data retrieval/manipulation work //-------------------Retrieve All Users-------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.GET) public ResponseEntity<List<User>> listAllUsers() { List<User> users = userService.findAllUsers(); if(users.isEmpty()){ return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND } return new ResponseEntity<List<User>>(users, HttpStatus.OK); } //-------------------Retrieve Single User-------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<User> getUser(@PathVariable("id") long id) { System.out.println("Fetching User with id " + id); User user = userService.findById(id); if (user == null) { System.out.println("User with id " + id + " not found"); return new ResponseEntity<User>(HttpStatus.NOT_FOUND); } return new ResponseEntity<User>(user, HttpStatus.OK); } //-------------------Create a User-------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.POST) public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) { System.out.println("Creating User " + user.getName()); if (userService.isUserExist(user)) { System.out.println("A User with name " + user.getName() + " already exist"); return new ResponseEntity<Void>(HttpStatus.CONFLICT); } userService.saveUser(user); HttpHeaders headers = new HttpHeaders(); headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri()); return new ResponseEntity<Void>(headers, HttpStatus.CREATED); } //------------------- Update a User -------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT) public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) { System.out.println("Updating User " + id); User currentUser = userService.findById(id); if (currentUser==null) { System.out.println("User with id " + id + " not found"); return new ResponseEntity<User>(HttpStatus.NOT_FOUND); } currentUser.setName(user.getName()); currentUser.setAge(user.getAge()); currentUser.setSalary(user.getSalary()); userService.updateUser(currentUser); return new ResponseEntity<User>(currentUser, HttpStatus.OK); } //------------------- Delete a User -------------------------------------------------------- @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE) public ResponseEntity<User> deleteUser(@PathVariable("id") long id) { System.out.println("Fetching & Deleting User with id " + id); User user = userService.findById(id); if (user == null) { System.out.println("Unable to delete. User with id " + id + " not found"); return new ResponseEntity<User>(HttpStatus.NOT_FOUND); } userService.deleteUserById(id); return new ResponseEntity<User>(HttpStatus.NO_CONTENT); } //------------------- Delete All Users -------------------------------------------------------- @RequestMapping(value = "/user/", method = RequestMethod.DELETE) public ResponseEntity<User> deleteAllUsers() { System.out.println("Deleting All Users"); userService.deleteAllUsers(); return new ResponseEntity<User>(HttpStatus.NO_CONTENT); } }
@RestController
注解。此注释避免我们在每个方法上添加 @ResponseBody``注解。在Spring-MVC内部下,
@RestController 本身是用
@ResponseBody 注解的,可以被认为是
@Controller 和
@ResponseBody`的组合。 @RequestBody
进行注解,那么Spring会将传入的HTTP请求主体(针对该方法的@RequestMapping中提到的URL)绑定到该参数。原理是Spring内部使用 HttpMessageConverter
将HTTP请求体转换为域对象[将请求主体反序列化为域对象],这是基于请求中存在的 ACCEPT
或 Content-Type
头。 HttpMessageConverter
将返回值转换为HTTP响应主体[将对象序列化到响应主体],并基于请求HTTP头中的Content-Type。如前所述,在Spring 4中,你可能会停止使用此注释。 @RestController
, @RequestBody
, ResponseEntity
和 @PathVariable
是你在Spring 4中实现一个REST API所需要知道的。另外,spring提供了几个支持类来帮助你实现一些自定义的东西。 @RequestMapping
注释,你可以另外指定要通过特定控制器方法生成或使用的 MediaType
(使用生成或消费属性),以进一步缩小映射范围。 PostMan是一个很棒用来测试Rest API的客户端。 但是,如果你想要在应用程序中调用基于REST的Web服务,则需要为你的应用程序提供REST客户端。 最受欢迎的HTTP客户端之一是Apache HttpComponents HttpClient。 但是,该客户端提供的功能过于基础,需要自己编写大量符合REST风格的代码。
Spring提供的RestTemplate提供了更高级别的方法,这些方法对应于六种主要的HTTP方法中的每一种,这些方法使得调用许多RESTful服务只需一行代码,并成为实施REST的最佳实践。
下面显示了HTTP方法和相应的RestTemplate方法来处理这种类型的HTTP请求。
HTTP 方法和 RestTemplate 方法对应关系:
import java.net.URI; import java.util.LinkedHashMap; import java.util.List; import org.springframework.web.client.RestTemplate; import com.websystique.springmvc.model.User; public class SpringRestTestClient { public static final String REST_SERVICE_URI = "<a class="vglnk" href="http://localhost:8080/Spring4MVCCRUDRestService" rel="nofollow"><span>http</span><span>://</span><span>localhost</span><span>:</span><span>8080</span><span>/</span><span>Spring4MVCCRUDRestService</span></a>"; /* GET */ @SuppressWarnings("unchecked") private static void listAllUsers(){ System.out.println("Testing listAllUsers API-----------"); RestTemplate restTemplate = new RestTemplate(); List<LinkedHashMap<String, Object>> usersMap = restTemplate.getForObject(REST_SERVICE_URI+"/user/", List.class); if(usersMap!=null){ for(LinkedHashMap<String, Object> map : usersMap){ System.out.println("User : id="+map.get("id")+", Name="+map.get("name")+", Age="+map.get("age")+", Salary="+map.get("salary"));; } }else{ System.out.println("No user exist----------"); } } /* GET */ private static void getUser(){ System.out.println("Testing getUser API----------"); RestTemplate restTemplate = new RestTemplate(); User user = restTemplate.getForObject(REST_SERVICE_URI+"/user/1", User.class); System.out.println(user); } /* POST */ private static void createUser() { System.out.println("Testing create User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(0,"Sarah",51,134); URI uri = restTemplate.postForLocation(REST_SERVICE_URI+"/user/", user, User.class); System.out.println("Location : "+uri.toASCIIString()); } /* PUT */ private static void updateUser() { System.out.println("Testing update User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(1,"Tomy",33, 70000); restTemplate.put(REST_SERVICE_URI+"/user/1", user); System.out.println(user); } /* DELETE */ private static void deleteUser() { System.out.println("Testing delete User API----------"); RestTemplate restTemplate = new RestTemplate(); restTemplate.delete(REST_SERVICE_URI+"/user/3"); } /* DELETE */ private static void deleteAllUsers() { System.out.println("Testing all delete Users API----------"); RestTemplate restTemplate = new RestTemplate(); restTemplate.delete(REST_SERVICE_URI+"/user/"); } public static void main(String args[]){ listAllUsers(); getUser(); createUser(); listAllUsers(); updateUser(); listAllUsers(); deleteUser(); listAllUsers(); deleteAllUsers(); listAllUsers(); } }
输出:
Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 Testing getUser API---------- User [id=1, name=Sam, age=30, salary=70000.0] Testing create User API---------- Location : <a class="vglnk" href="http://localhost:8080/Spring4MVCCRUDRestService/user/5" rel="nofollow"><span>http</span><span>://</span><span>localhost</span><span>:</span><span>8080</span><span>/</span><span>Spring4MVCCRUDRestService</span><span>/</span><span>user</span><span>/</span><span>5</span></a> Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing update User API---------- User [id=1, name=Tomy, age=33, salary=70000.0] Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing delete User API---------- Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing all delete Users API---------- Testing listAllUsers API----------- No user exist----------
在访问REST API时,您可能会面临有关同源策略的问题。
可能的错误如下:
http://abc.com/bla
。 原始 http:// localhost:12345
不被Access-Control-Allow-Origin允许。“在这种情况下很常见。 解决方案是Cross-Origin Resource Sharing(跨源资源共享)。 基本上,在服务器端,我们可以返回额外的CORS访问控制头和响应,这最终将允许进一步的域间通信。
在Spring中,我们可以编写一个简单的过滤器,在每个响应中添加这些CORS特定的响应头信息。
import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; @WebFilter public class CORSFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { System.out.println("Filtering on..........................................................."); HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); chain.doFilter(req, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} }