spring 6.0新特性
前段时间就一直听别人在说这个HTTP Interface了,说是spring 6.0新特性。只是自己一直没去了解具体情况。最近抽空把这个介绍看了。
Spring 允许你将HTTP服务定义为带有HTTP exchanges注解方法的Java接口。然后,你可以生成一个实现接口的代理执行HTTP请求。这有助于简化HTTP远程访问,通常涉及封装底层HTTP客户机使用细节的外观。
spring 很关心你怎么写代码
关于HTTP Interface的介绍,我是这么理解的:
在这之前,我们调用远程数据可能是在一个service里获取HTTPClient 比如在这里使用的WebClient,然后定义请求体,发送请求,获得数据后再进行业务操作。
定义了HTTP Interface之后,我们只要定义接口就行了,把它当成我们业务中的一个service,其他service怎么用,这个service就怎么用。至于HTTPClient是怎么样的,在使用之前就已经定义好了,这使我们在编码时可以更注重业务逻辑。
文档demo
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
RepositoryService service = factory.createClient(RepositoryService.class);
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void updateRepository(@PathVariable String owner, @PathVariable String repo,
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
}
以上demo来自官方文档,让我来的话,就是生成代理那边,就直接注册bean了。然后就在其他的地方直接@Autowired
个人demo
以下是我自己使用时写的demo。此时我的本地启着2个应用,有一个应用需要调用另一个应用。先来定义一个service
@HttpExchange(url = "/goods")
public interface RemoteHttpService {
@GetExchange(url = "/{id}")
String get(@PathVariable String id);
@PutExchange(url = "/")
String put(@RequestBody String json);
@PostExchange(url = "/")
String post(@RequestBody String json);
@DeleteExchange(url = "{id}")
String delete(@PathVariable String id);
}
CRUD都有了,只要调用就行了。但是这个只是接口啊,没有实现呢,接下来生成HTTPClient代理的实现,并注册到bean。
@Configuration
public class WebClientConfig {
@Bean
public RemoteHttpService remoteHttpService(){
WebClient webClient = WebClient.builder().baseUrl("localhost").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
return factory.createClient(RemoteHttpService.class);
}
}
建议一个主机写一个Client吧,这边为了demo的演示效果,一个模块做了一个Client,毕竟访问一个主机的设置一般来说都是一样的吧。
其实我们可以看到形式上,这个接口类跟我们使用了@Service的方法差不多。但在写法上更像是写了个@Controller。最后再整个活,通过扫包注册远程调用的bean
@Configuration
public class WebClientConfig implements BeanFactoryPostProcessor {
private MetadataReaderFactory metadataReaderFactory;
private ResourcePatternResolver resourcePatternResolver;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
WebClient webClient = WebClient.builder().build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
try {
Resource[] resources = getResourcePatternResolver().getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "org/example/service/**");
for (Resource resource: resources){
String filename = resource.getFilename();
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (metadataReader.getAnnotationMetadata().hasAnnotation(HttpExchange.class.getName())) {
assert filename != null;
beanFactory.registerSingleton(filename.substring(0,filename.lastIndexOf(".")), factory.createClient(Class.forName(metadataReader.getClassMetadata().getClassName())));
}
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private ResourcePatternResolver getResourcePatternResolver() {
if (this.resourcePatternResolver == null) {
this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
}
return this.resourcePatternResolver;
}
public final MetadataReaderFactory getMetadataReaderFactory() {
if (this.metadataReaderFactory == null) {
this.metadataReaderFactory = new CachingMetadataReaderFactory();
}
return this.metadataReaderFactory;
}
}