2.4 发布与调用REST服务
在系统间进行通信,很多系统都会选择SOAP协议,随着REST的兴起,现在很多系统在发布与调用Web Service时,都首选REST。这一节,我们介绍如何在Spring Boot中发布和调用REST服务。
2.4.1 REST
REST是英文Representational State Transfer的缩写,一般翻译为“表述性状态转移”,是Roy Thomas Fielding博士在他的论文“Architectural Styles and the Design of Network-based Software Architectures”中提出的一个术语。REST本身只是分布式系统设计中的一种架构风格,并不是某种标准或者规范,而是一种轻量级的基于HTTP协议的Web Service风格。从另外一个角度看,REST更像是一种设计原则,更可以将其理解为一种思想。
2.4.2 发布REST服务
在Spring Boot中发布REST服务非常简单,只需要在控制器中使用@RestController即可。下面我们来看一个示例。新建一个rest-server的Maven项目,加入“spring-boot-starter-web”依赖,将启动类和控制器写入同一个类中,请见代码清单2-9。
代码清单2-9:codes\02\rest-server\src\main\java\org\crazyit\boot\c2\RestApp.java
@SpringBootApplication @RestController public class RestApp { public static void main(String[] args) { SpringApplication.run(RestApp.class, args); } @GetMapping(value = "/person/{name}", produces = MediaType.APPLICATION_JSON_VALUE) public Person person(@PathVariable String name) { Person p = new Person(); p.setName(name); p.setAge(33); return p; } static class Person { String name; Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } }
在代码清单2-9中,发布了一个“/person/name”的服务,调用该服务后,会返回一个Person实例的JSON字符串,该服务对应的方法使用了组合注解@GetMapping,该注解的作用相当于@RequestMapping(method = RequestMethod.GET)。运行代码清单2-9,在浏览器中访问:http://localhost:8080/person/angus,则返回以下信息:{"name":"angus", "age":33}。
很简单的一个注解就帮我们完成了发布REST服务的工作,这再一次展示了Spring Boot的便捷。如果不使用Spring Boot,估计你还要为寻找依赖包而疲于奔命。
2.4.3 使用RestTemplate调用服务
下面,我们使用Spring的RestTemplate来调用服务。RestTemplate是Spring Framework的一个类,其主要用来调用REST服务,它提供了拦截器机制,我们可以对它进行个性化定制。另外,在Spring Cloud中也可以使用RestTemplate来调用服务,而且还可以实现负载均衡的功能,有兴趣的朋友可参考笔者的另外一本书《疯狂Spring Cloud微服务架构实战》。
我们来看一个例子。
新建一个rest-client的Maven项目,加入“spring-boot-starter-web”与“spring-boot-starter-test”的依赖,新建一个最普通的main方法,直接调用前面的服务,请见代码清单2-10。
代码清单2-10:codes\02\rest-client\src\main\java\org\crazyit\boot\c2\RestTemplateMain.java
/** * 在main方法中使用RestTemplate * @author杨恩雄 * */ public class RestTemplateMain { public static void main(String[] args) { RestTemplate tpl = new RestTemplate(); Person p = tpl.getForObject("http://localhost:8080/person/angus", Person.class); System.out.println(p.getName() + "---" + p.getAge()); } }
在main方法中,直接创建RestTemplate的实例并调用服务,操作非常简单。如果想在Spring的bean里面使用RestTemplate,则可以使用RestTemplateBuilder,请见代码清单2-11。
代码清单2-11:codes\02\rest-client\src\main\java\org\crazyit\boot\c2\MyService.java
@Service public class MyService { @Autowired private RestTemplateBuilder builder; @Bean public RestTemplate restTemplate() { return builder.rootUri("http://localhost:8080").build(); } /** * 使用RestTemplateBuilder来创建template */ public Person useBuilder() { Person p = restTemplate().getForObject("/person/angus", Person.class); return p; } }
在我们自已的bean里面注入RestTemplateBuilder,创建一个RestTemplate的bean。在创建RestTemplate实例时,使用RestTemplateBuilder的rootUri方法设置访问的URI。除了rootUri方法外,RestTemplateBuilder还提供了很多方法用于设置RestTemplate,在此不再赘述。接下来,编写一个单元测试类,来测试我们这个MyService的bean,请见代码清单2-12。
代码清单2-12:codes\02\rest-client\src\test\java\org\crazyit\boot\c2\MyServiceTest.java
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.NONE) public class MyServiceTest { @Autowired private MyService myService; @Test public void testUserTemplate() { Person p = myService.useBuilder(); System.out.println(p.getName() + "===" + p.getAge()); } }
与前面的单元测试类似,直接注入MyService即可。
注意:在运行单元测试时,项目中一定要有Spring Boot的启动类,否则会得到以下异常:java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use@ContextConfiguration or @SpringBootTest(classes=...) with your test
Spring的RestTemplate只是众多REST客户端中的一个。接下来,我们介绍另外一个REST客户端Feign。
2.4.4 使用Feign调用服务
Feign是Github上的一个开源项目,其目的是简化Web Service客户端的开发。Spring Cloud项目将Feign整合进来,让其作为REST客户端。这一节,我们来了解如何使用Feign框架调用REST服务。在rest-client项目中加入以下依赖:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> <version>9.5.0</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-gson</artifactId> <version>9.5.0</version> </dependency>
新建PersonClient接口,请见代码清单2-13。
代码清单2-13:codes\02\rest-client\src\main\java\org\crazyit\boot\c2\feign\PersonClient.java
/** * Feign客户端接口 * @author杨恩雄 */ public interface PersonClient { @RequestLine("GET /person/{name}") Person getPerson(@Param("name") String name); }
在接口中,使用了@RequestLine和@Param注解,这两个注解是Feign的注解。使用注解修饰后,getPerson方法被调用,然后使用HTTP的GET方法向“/person/name”服务发送请求。接下来编写客户端运行类,请见代码清单2-14。
代码清单2-14:codes\02\rest-client\src\main\java\org\crazyit\boot\c2\feign\FeignMain.java
public class FeignMain { public static void main(String[] args) { // 调用Hello接口 PersonClient pc = Feign.builder() .decoder(new GsonDecoder()) .target(PersonClient.class, "http://localhost:8080/"); Person p = pc.getPerson("angus"); System.out.println(p.getName() + "---" + p.getAge()); } }
在代码清单2-14中,使用Feign来创建PersonClient接口的实例,最后通过调用接口方法来访问服务。熟悉AOP的朋友可能已经猜到,Feign实际上帮助我们动态生成了代理类,Feign使用的是JDK的动态代理,代理类会将请求的信息封装,最终使用java.netHttpURLConnection来发送HTTP请求。如果想将这里的PersonClient作为bean放到Spring容器中,则可以添加一个创建该实例的方法:
@Bean public PersonClient personClient() { return Feign.builder() .decoder(new GsonDecoder()) .target(PersonClient.class, "http://localhost:8080/"); }
使用Feign来调用REST服务,使人感觉更加面向对象了。除了RestTemplate和Feign之外,还可以使用诸如Restlet、CXF等框架来调用REST服务,在此不再赘述。