前言
在上一篇博文中,介绍了Spring Cloud如何为FeignClient生成代理类,并配置成Bean。该博文将参照FeignClient,实现接口代理并注册配置成Bean。
代码目录层次介绍
代码整体目录层次如下,参照FeignClient注册体系实现,即通过EnableDefinition引入DefinitionBeanRegister,从而开启标记DefinitionAnno接口的扫描。DefinitionClientFactoryBean,用于收集DefinitionAnno注解的相关信息,并通过动态代理,基于Ribbon完成微服务调用。
二、代码实现细节
1、DefinitionBeanRegister
通过EnableDefinition注解中的Import,引入DefinitionBeanRegister,基于该类完成特定接口的扫描动作。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(DefinitionBeanRegister.class)
public @interface EnableDefinition {
}
该Register工作内容主要分为如下几项
1、扫描特定路径下,标记有DefinitionAnno注解的接口。
2、收集DefinitionAnno注解,并配置成DefinitionClientFactoryBean。
public class DefinitionBeanRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
protected ResourceLoader resourceLoader;
protected Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(DefinitionAnno.class));
Set<BeanDefinition> definitions = scanner.findCandidateComponents("com.quelongjiang");
for (BeanDefinition definition : definitions) {
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) definition;
AnnotationMetadata metadata = beanDefinition.getMetadata();
Assert.isTrue(metadata.isInterface(), "@DefinitionAnno can only be specifyed on an interface");
Map<String, Object> attributes = metadata.getAnnotationAttributes(DefinitionAnno.class.getCanonicalName());
this.registerDefinitionClient(registry, metadata, attributes);
}
}
}
protected void registerDefinitionClient(BeanDefinitionRegistry registry, AnnotationMetadata metadata, Map<String, Object> attributes) {
String className = metadata.getClassName();
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefinitionClientFactoryBean.class);
definitionBuilder.addPropertyValue("serverName", attributes.get("serverName"));
definitionBuilder.addPropertyValue("requestUrl", attributes.get("requestUrl"));
definitionBuilder.addPropertyValue("requestMethod", attributes.get("requestMethod"));
definitionBuilder.addPropertyValue("type", className);
definitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
beanDefinition.setPrimary(true);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, metadata.getClassName(), new String[] {attributes.get("serverName") + "_Client"});
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
2、DefinitionClientFactoryBean
该类,通过RequestInvocationHandler内部类,实现接口的动态代理。代理类的主要动作,就是通过Ribbon实现请求发送处理。
public class DefinitionClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
private String serverName;
private String requestUrl;
private HttpMethod requestMethod;
private Class<?> type;
protected ApplicationContext atc;
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getRequestUrl() {
return requestUrl;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public HttpMethod getRequestMethod() {
return requestMethod;
}
public void setRequestMethod(HttpMethod requestMethod) {
this.requestMethod = requestMethod;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
@Override
public Object getObject() throws Exception {
RestTemplate restTemplate = this.atc.getBean("loadBalanced", RestTemplate.class);
return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] {type}, new RequestInvocationHandler(restTemplate));
}
@Override
public Class<?> getObjectType() {
return this.type;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.hasText(this.serverName, "serviceName must be set");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.atc = applicationContext;
}
class RequestInvocationHandler implements InvocationHandler {
protected RestTemplate restTemplate;
public RequestInvocationHandler(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String url = this.getRequestUrl(method);
HttpMethod executeMethod = this.getRequestMethod(method);
if (executeMethod == HttpMethod.GET) {
return this.restTemplate.getForObject(url, Map.class);
}
else {
return this.restTemplate.postForObject(url, new HashMap<>(), String.class);
}
}
protected String getRequestUrl(Method method) {
if (!method.isAnnotationPresent(RequestInfoAnno.class)) {
return String.format("http://%s/%s", serverName, requestUrl);
}
else {
String methodRequest = method.getAnnotation(RequestInfoAnno.class).requestUrl();
methodRequest = methodRequest.startsWith("/") ? methodRequest : "/" + methodRequest;
return String.format("http://%s/%s%s", serverName, requestUrl, methodRequest);
}
}
protected HttpMethod getRequestMethod(Method method) {
return method.isAnnotationPresent(RequestInfoAnno.class) ? method.getAnnotation(RequestInfoAnno.class).requestMethod() : requestMethod;
}
}
}
3、LoadBalanceRestTemplateConfiguration
通过LoadBalanced,配置RestTemplate。由于对接的服务,需要认证才能访问,此处为该RestTemplate添加了一个BasicAuthenticationInterceptor拦截器。
@Configuration
@EnableDefinition
public class LoadBalanceRestTemplateConfiguration {
@Bean("loadBalanced")
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor("que.longjiang", "123456"));
return restTemplate;
}
}
4、EurekaServerRequestClient
此处,定义一个使用DefinitionAnno标记的接口,用于后续测试代码使用。
@DefinitionAnno(serverName = "quelongjiang-cloud-server", requestMethod = HttpMethod.GET)
public interface EurekaServerRequestClient {
@RequestInfoAnno(requestUrl = "eureka/apps", requestMethod = HttpMethod.GET)
Object showEurekaInfoes();
@RequestInfoAnno(requestUrl = "eureka/apps/delta", requestMethod = HttpMethod.GET)
Map<String, Object> showEurekaDelta();
}
5、DefinitionRequestController
一个测试类
@RestController
@RequestMapping("quelongjiang/definitionRequestController")
public class DefinitionRequestController {
@Autowired
protected EurekaServerRequestClient requestClient;
@GetMapping("eureka-infoes")
public Object showEurekaInfoes() {
return this.requestClient.showEurekaInfoes();
}
@GetMapping("eureka-delta")
public Object showEurekaDelta() {
return this.requestClient.showEurekaDelta();
}
}
启动相关服务,调用请求。
总结
1、通过ClassPathScanningCandidateComponentProvider,获取注解标记类。
2、收集BeanDefinitionBuilder.genericBeanDefinition收集注解等相关信息,用于后续的注册。
3、通过动态代理,实现接口具体动作。