Spring Cloud Feign Clients 無需 Controller自動暴露Restful接口

前言

在開發SpringCloud應用中,Feign做爲聲明式調用的事實標準極大的簡化了Rest遠程調用,提供了類本地化的調用方式。服務提供方的接口暴露方式是經過Controller暴露Restful,而在這個Controller的代碼現實中大部分都是處理請求而後再調用Service中的方法,是一個比較模板化的功能,可是工做量確很多。本文介紹一種經過動態代理的方式無需Controller直接暴露Restful接口。
本文中使用筆者在Github開源的框架來實現,本文的講解也在這個框架基礎之上來講明
Git路徑:https://github.com/leecho/spr...git

依賴

<dependency>
        <groupId>com.github.leecho</groupId>
        <artifactId>spring-cloud-starter-feign-proxy</artifactId>
        <version>{last-version}</version>
    </dependency>

實現

定義Service Interface

首先定義服務接口,使用@FeignClient標示是一個Feign接口,在這個示例Sevice中定義了CURD和文件上傳方法,並使用了一些經常使用參數註解github

@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和上傳文件的方法web

@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接口spring

@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的接口進行測試mvc

@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接口已經暴露成功。app

後語

關於這個框架的介紹,後續詳細的給你們進行介紹。文中所涉及到的代碼也在Git中:spring-cloud-feign-proxy-sample框架

相關文章
相關標籤/搜索