spring 6:HTTP Interface——获取HTTP服务的代理类并放置到IOC容器内

spring 6最近发布了新版本,带来了一个新特性HTTP interfaces。可以将http服务定义为一个java接口,通过Http服务代理工厂生成http代理类并通过接口方法进行http调用。
我们先建一个maven项目,增加pom文件依赖。

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.0.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

新建一个启动类DemoApplication。

package com.fallrain.application;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

推荐一个在线的rest http服务JSONPlaceholder
根据http返回值创建一个实体类。

package com.fallrain.application.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String body;
    private String category;
    private String cover;
    private String createdAt;
    private String id;
    private String isDraft;
    private String title;
    private String views;
}

然后去创建http请求接口。

package com.fallrain.application.service;

import com.fallrain.application.entity.User;
import org.springframework.stereotype.Component;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;

import java.util.List;

@HttpExchange("https://mockend.com/Fall-Rain/mockend/posts")
public interface UserApi {
    @GetExchange
    List<User> getUsers();
}

编写一个测试类。

import com.fallrain.application.DemoApplication;
import com.fallrain.application.entity.User;
import com.fallrain.application.service.UserApi;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;


@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class TestApplication {
    @Test
    public void demo() {
        //构建一个web客户端
        WebClient webClient = WebClient.builder().build();
        //根据web客户端去构建服http服务的代理工厂
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
        //根据代理工厂创建UserApi的代理类
        UserApi userApi = factory.createClient(UserApi.class);
        //执行getUsers的方法
        for (User user : userApi.getUsers()) {
            System.out.println(user);
        }

    }
}

运行demo测试方法。

14:20:33.242 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [TestApplication]: using SpringBootContextLoader
14:20:33.248 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [TestApplication]: no resource found for suffixes {-context.xml, Context.groovy}.
14:20:33.270 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using ContextCustomizers for test class [TestApplication]: [ExcludeFilterContextCustomizer, DuplicateJsonObjectContextCustomizer, MockitoContextCustomizer, TestRestTemplateContextCustomizer, WebTestClientContextCustomizer, DisableObservabilityContextCustomizer, PropertyMappingContextCustomizer, Customizer]
14:20:33.387 [main] DEBUG org.springframework.test.context.util.TestContextSpringFactoriesUtils - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom TestExecutionListener classes or make the default TestExecutionListener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
14:20:33.388 [main] DEBUG org.springframework.test.context.util.TestContextSpringFactoriesUtils - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom TestExecutionListener classes or make the default TestExecutionListener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
14:20:33.390 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners for test class [TestApplication]: [ServletTestExecutionListener, DirtiesContextBeforeModesTestExecutionListener, ApplicationEventsTestExecutionListener, MockitoTestExecutionListener, DependencyInjectionTestExecutionListener, DirtiesContextTestExecutionListener, EventPublishingTestExecutionListener, ResetMocksTestExecutionListener, RestDocsTestExecutionListener, MockRestServiceServerResetTestExecutionListener, MockMvcPrintOnlyOnFailureTestExecutionListener, WebDriverTestExecutionListener, MockWebServiceServerTestExecutionListener]
14:20:33.391 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [TestApplication]
14:20:33.393 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [TestApplication]
14:20:33.394 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [TestApplication]
14:20:33.394 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [TestApplication]
14:20:33.394 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [TestApplication]
14:20:33.394 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [TestApplication]
14:20:33.403 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [TestApplication]
14:20:33.403 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [TestApplication]
14:20:33.404 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [TestApplication]
14:20:33.404 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [TestApplication]
14:20:33.405 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [TestApplication]
14:20:33.405 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [TestApplication]
14:20:33.407 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: class [TestApplication], class annotated with @DirtiesContext [false] with mode [null]
14:20:33.409 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [TestApplication]
14:20:33.409 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [TestApplication]

  .   ____          _            __ _ _
 / / ___'_ __ _ _(_)_ __  __ _    
( ( )___ | '_ | '_| | '_ / _` |    
 /  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |___, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.2)

2023-02-06T14:20:33.708+08:00  INFO 15456 --- [           main] TestApplication                          : Starting TestApplication using Java 17.0.5 with PID 15456 (started by fallrain in D:codingspring6)
2023-02-06T14:20:33.711+08:00  INFO 15456 --- [           main] TestApplication                          : No active profile set, falling back to 1 default profile: "default"
2023-02-06T14:20:34.185+08:00  INFO 15456 --- [           main] o.s.c.a.ConfigurationClassPostProcessor  : Cannot enhance @Configuration bean definition 'httpPostProcessor' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.
2023-02-06T14:20:34.997+08:00  INFO 15456 --- [           main] TestApplication                          : Started TestApplication in 1.559 seconds (process running for 2.269)
User(body=Eos culpa et voluptatem., category=three, cover=https://picsum.photos/seed/96789/1920/270, createdAt=2016-02-08T08:12:09Z, id=1, isDraft=true, title=corrupti ducimus, views=647)
User(body=Illo saepe consequuntur a libero deserunt a eos, perferendis sint, accusantium qui dolorum sed at., category=three, cover=https://picsum.photos/seed/48269/1920/270, createdAt=2011-10-16T13:55:55Z, id=2, isDraft=false, title=repellendus, views=609)
User(body=Unde veniam., category=one, cover=https://picsum.photos/seed/97760/1920/270, createdAt=2014-08-12T22:37:04Z, id=3, isDraft=false, title=inventore, views=395)
User(body=Debitis et neque facilis magnam., category=one, cover=https://picsum.photos/seed/02770/1920/270, createdAt=2012-06-04T21:28:05Z, id=4, isDraft=true, title=optio ut hic, views=764)
User(body=Error nisi deserunt consectetur ea a., category=one, cover=https://picsum.photos/seed/79932/1920/270, createdAt=2018-12-29T15:15:32Z, id=5, isDraft=true, title=itaque nam sed, views=757)
User(body=Mollitia doloribus a reprehenderit vitae, incidunt incidunt., category=two, cover=https://picsum.photos/seed/65976/1920/270, createdAt=2017-10-08T08:45:12Z, id=6, isDraft=true, title=earum, views=921)
User(body=Reprehenderit et, asperiores sed reprehenderit nisi, architecto a reprehenderit ipsa., category=two, cover=https://picsum.photos/seed/79823/1920/270, createdAt=2012-10-30T16:06:08Z, id=7, isDraft=true, title=debitis in a, views=714)
User(body=Perspiciatis minima sit., category=one, cover=https://picsum.photos/seed/81255/1920/270, createdAt=2015-11-19T10:59:18Z, id=8, isDraft=false, title=maxime iusto, views=117)
User(body=Dolore perferendis deleniti ab, in molestiae quia, autem perspiciatis aliquam corrupti., category=two, cover=https://picsum.photos/seed/93942/1920/270, createdAt=2016-03-26T23:50:25Z, id=9, isDraft=false, title=et iusto, views=847)
User(body=Voluptatem reprehenderit cumque laborum, tempora magnam ea quo cum., category=three, cover=https://picsum.photos/seed/40537/1920/270, createdAt=2013-02-22T02:46:26Z, id=10, isDraft=true, title=quam earum, views=616)
Disconnected from the target VM, address: '127.0.0.1:61702', transport: 'socket'

Process finished with exit code 0

由于官方并没有将代理类放到ioc容器当中,所以每次使用都要手动构建。这里我们可以通过spring的拓展点ResourceLoaderAware和BeanDefinitionRegistryPostProcessor,利用扫描目录的方式将代理类放到ioc容器当中。建立一个HttpPostProcessor并实现ResourceLoaderAware和BeanDefinitionRegistryPostProcessor。

package com.fallrain.application.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

import java.io.IOException;

@Configuration
public class HttpPostProcessor implements ResourceLoaderAware, BeanDefinitionRegistryPostProcessor {
    private ResourceLoader resourceLoader;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            //获取指定目录下的class文件
            Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources("classpath*:com/fallrain/application/**/*.class");
            //根据resources创建数据读取工厂
            MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
            for (Resource resource : resources) {
                //获取元数据
                MetadataReader metadataReader = metaReader.getMetadataReader(resource);
                //判断是否存在HttpExchange注解(是否为http interface的接口调用)
                if (metadataReader.getAnnotationMetadata().hasAnnotation(HttpExchange.class.getName())) {
                    //构建一个web客户端
                    WebClient webClient = WebClient.builder().build();
                    //根据web客户端去构建服http服务的代理工厂
                    HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
                    //利用类的全限定名通过Class.forName获取class对象并利用http服务的代理工厂创建出代理对象
                    Object client = factory.createClient(Class.forName(metadataReader.getClassMetadata().getClassName()));
                    //将创建出来的代理对象放到io容器当中
                    beanFactory.registerSingleton(metadataReader.getClassMetadata().getClassName(), client);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

    }
}

这时我们的测试类就可以使用@Autowired进行注入然后进行调用。

import com.fallrain.application.DemoApplication;
import com.fallrain.application.entity.User;
import com.fallrain.application.service.UserApi;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class TestApplication {
    @Autowired
    //获取UserApi的代理类
    private UserApi userApi;
    @Test
    public void demo() {
        //执行getUsers的方法
        for (User user : userApi.getUsers()) {
            System.out.println(user);
        }
    }
}

到这里spring framework 6新特性HTTP Interface介绍完成。欢迎大家一起讨论。

参考文档:
https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#rest-http-interface
Spring 6 的新特性:HTTP Interface

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
# HttpInterface Windows上C++封装的HTTP库,包含三种实现模式(WinInet、WinHttp、socket) 主要实现了HTTP的get\post方法,下载到内存、下载到本地文件,回调下载进度等接口 测试程序中展现了常用的几个方法。 接口声明: class IHttpBase { public: virtual void SetDownloadCallback(IHttpCallback* pCallback, void* pParam)= 0; virtual bool DownloadFile(LPCWSTR lpUrl, LPCWSTR lpFilePath)= 0; virtual bool DownloadToMem(LPCWSTR lpUrl, OUT void** ppBuffer, OUT int* nSize)= 0; virtual void FreeInstance()= 0; virtual HttpInterfaceError GetErrorCode()= 0; }; //////////////////////////////////////////////////////////////////////////////////// //HTTP请求接口 class IWininetHttp :public IHttpBase { public: //HTTP请求功能 virtual string Request(LPCSTR lpUrl, HttpRequest type, LPCSTR lpPostData = NULL, LPCSTR lpHeader = NULL)= 0; virtual string Request(LPCWSTR lpUrl, HttpRequest type, LPCSTR lpPostData = NULL, LPCWSTR lpHeader = NULL)= 0; }; /////////////////////////////////////////////////////////////////////////////////////// //HTTP socket class ISocketHttp :public IHttpBase { public: virtual LPCWSTR GetIpAddr()const= 0; }; /////////////////////////////////////////////////////////////////////////////////////// //WinHttp class IWinHttp : public IWininetHttp { public: //设置超时时间,单位:毫秒 virtual void SetTimeOut(int dwConnectTime, int dwSendTime, int dwRecvTime)= 0; }; 能力有限,有bug欢迎指正 email:jelinyao@163.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值