乐趣区

Spring Cloud Feign Clients 无需 Controller自动暴露Restful接口

前言
在开发 SpringCloud 应用中,Feign 作为声明式调用的事实标准极大的简化了 Rest 远程调用,提供了类本地化的调用方式。服务提供方的接口暴露方式是通过 Controller 暴露 Restful,而在这个 Controller 的代码现实中大部分都是处理请求然后再调用 Service 中的方法,是一个比较模板化的功能,但是工作量确不少。本文介绍一种通过动态代理的方式无需 Controller 直接暴露 Restful 接口。本文中使用笔者在 Github 开源的框架来实现,本文的讲解也在这个框架基础之上来说明 Git 路径:https://github.com/leecho/spr…
依赖
<dependency>
<groupId>com.github.leecho</groupId>
<artifactId>spring-cloud-starter-feign-proxy</artifactId>
<version>{last-version}</version>
</dependency>
实现
定义 Service Interface
首先定义服务接口,使用 @FeignClient 标示是一个 Feign 接口,在这个示例 Sevice 中定义了 CURD 和文件上传方法,并使用了一些常用参数注解
@Api(tags = “DemoService”, description = “Demo Feign Client”)
@FeignClient(“demo-service”)
public interface DemoService {

/**
* create demo
*
* @param demo
* @return
*/
@ApiOperation(value = “Create demo”)
@PostMapping(value = “/demo”)
Demo create(@RequestBody @Valid @ApiParam Demo demo);

/**
* update demo
*
* @param demo
* @return
*/
@PutMapping(value = “/demo”)
Demo update(@RequestBody @Valid Demo demo);

/**
* delete demo
*
* @param id
* @return
*/
@DeleteMapping(value = “/demo/{id}”)
Demo delete(@PathVariable(name = “id”) String id);

/**
* list demo
*
* @param id
* @param headerValue test header value
* @return
*/
@GetMapping(value = “/demo/{id}”)
Demo get(@PathVariable(name = “id”) String id, @RequestHeader(name = “header”) String headerValue);

/**
* list demos
*
* @return
*/
@GetMapping(value = “/demos”)
List<Demo> list();

/**
* upload file
*
* @param file
* @return
*/
@PostMapping(value = “/demo/upload”)
String upload(@RequestPart(name = “file”) MultipartFile file);
}

实现 Service
在实现类中简单的实现了 CURD 和上传文件的方法
@Slf4j
@Primary
@Service
public class DemoServiceImpl implements DemoService {

@Override
public Demo create(Demo demo) {
log.info(“Create executed : ” + demo);
return demo;
}

@Override
public Demo update(Demo demo) {
log.info(“Update execute :” + demo);
return demo;
}

@Override
public Demo delete(String id) {
log.info(“Delete execute : ” + id);
return Demo.builder().name(“demo-” + id).data(“data-” + id).build();
}

@Override
public Demo get(String id, String header) {
Demo demo = Demo.builder()
.name(header)
.data(header).build();
System.out.println(“Get execute : ” + id + “,” + header);
return demo;
}

@Override
public List<Demo> list() {
System.out.println(“List execute”);
List<Demo> demos = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Demo demo = Demo.builder()
.name(“demo-” + i)
.data(“data” + i).build();
demos.add(demo);
}
return demos;
}

@Override
public String upload(MultipartFile file) {
return file.getOriginalFilename();
}
}

动态生成 Restful 接口的原理是动态生成 Controller 类实现 ServiceInterface,如果真正的实现类会被其他 BEAN 依赖,需要通过注解 @Primary 来方式来防止依赖冲突
配置应用
在应用中通过 @EnableFeignProxies 来标示应用在启动的过程中会扫描所有的 FeignClients 并暴露 Restful 接口
@EnableFeignProxies(basePackages = “com.github.leecho”)
@ComponentScan(“com.github.leecho”)
@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

测试用例
在测试用例中对 Service 的接口进行测试
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class DemoApplicationTest {
@Autowired
private WebApplicationContext context;

private MockMvc mvc;

@Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}

@Test
public void create() throws Exception {
mvc.perform(MockMvcRequestBuilders.post(“/demo”)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(new ObjectMapper().writeValueAsString(Demo.builder().name(“create”).data(“data”).build()))
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());

}

@Test
public void update() throws Exception {
mvc.perform(MockMvcRequestBuilders.put(“/demo”)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(new ObjectMapper().writeValueAsString(Demo.builder().name(“update”).data(“data”).build()))
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());

}

@Test
public void delete() throws Exception {
mvc.perform(MockMvcRequestBuilders.delete(“/demo/{id}”, “1”)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());

}

@Test
public void get() throws Exception {
mvc.perform(MockMvcRequestBuilders.get(“/demo/{id}”, “1”)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.header(“header”, “header-value”)
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());

}

@Test
public void list() throws Exception {
mvc.perform(MockMvcRequestBuilders.get(“/demos”)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());

}

@Test
public void upload() throws Exception {
mvc.perform(MockMvcRequestBuilders.multipart(“/demo/upload”).file(new MockMultipartFile(“file”, “test.txt”, “,multipart/form-data”, “upload test”.getBytes()))
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());

}
}

在启动日志中也可以看到 Restful 接口已经暴露成功。
后语
关于这个框架的介绍,后续详细的给大家进行介绍。文中所涉及到的代码也在 Git 中:spring-cloud-feign-proxy-sample

退出移动版