Spring Boot 2+Thymeleaf企业应用实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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服务,在此不再赘述。