接口注册为Spring中的bean
背景
阿里口碑同学主导了一个客户端jar包的项目,跟随P6开发参与其中,项目中使用到了sqlite这种轻量级的数据库,口碑同学直接手写了一个类Mybatis数据库框架用来操作数据库,Mybatis中的mapper类都是接口理论上是不能在spring中注册成bean的,但是为什么我们可以用@Autowired @Resource注解来注入到其他类里呢,其实这个bean是原接口的代理类而已
当然受保密协议影响我不可能将源码贴出来,但是我借鉴其中的实现方式来实现了一个简单的feign调用的demo
实现
概述
我们知道feign是springCloud的组件之一,也是我们经常用到的,它在Ribbon基础上做了使用上的封装,是我们使用起来就像调用本地接口一样方便,当然Ribbon的实现是基于RestTemplate而且做了软负载,我们的demo没有那么多内容,仅是实现了接口形式的RPC调用
注解
我们先准备三个注解如下
RemoteCallScan
这个注解的主要作用是在启动类上配置我们进行rpc调用的service接口的路径
注意这里我们使用@Import注解导入RemoteCallPackageRegistrar这个类,该类在后面会有描述
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({RemoteCallPackageRegistrar.class})
public @interface RemoteCallScan {
@AliasFor(value = "basePackages")
String[] value() default {};
@AliasFor(value = "value")
String[] basePackages() default {};
}
RemoteServer
这个注解用在rpc接口上,相当于@FeignClient注解,指定远程服务名,这里我们做了占位符解析,注解里可以写服务名,将服务名的真正ip配置在application.properties中
/**
* @description: 远程服务的name
* @author zjg
* @date 2020/4/23 16:39
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoteServer {
@AliasFor(value = "serverName")
String value() default "";
@AliasFor(value = "value")
String serverName() default "";
}
RemotePath
这个注解用在rpc接口的方法上指定请求的方法路径,相当于@RequestMapping注解
/**
* @description: 远程调用方法的path
* @author zjg
* @date 2020/4/23 16:39
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RemotePath {
@AliasFor(value = "methodUrl")
String value() default "";
@AliasFor(value = "value")
String methodUrl() default "";
}
类
RemoteCallProxy
这个就是接口动态代理类的拦截类,我们在这里拦截rpc方法,获取方法上的注解参数:服务名和请求方法以及请求参数,然拼装成完整的请求
/**
* @description: 代理类的拦截
* @author zjg
* @date 2020/5/1 15:49
*/
@Slf4j
public class RemoteCallProxy implements InvocationHandler {
private String serverName;
public RemoteCallProxy(String serverName){
this.serverName = serverName;
}
/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RemotePath remotePath = (RemotePath)method.getAnnotation(RemotePath.class);
AssertUtil.isNotNull(remotePath,"远程访问,remotePath不能为空");
String methodPath = remotePath.value();
AssertUtil.isNotBlank(methodPath,"远程访问,methodPath不能为空");
Class<?> returnType = method.getReturnType();
AssertUtil.isNotNull(returnType,"远程访问,returnType不能为空");
AssertUtil.isNotNull(args,"远程访问,args不能为空");
String url = serverName + "/" + remotePath.value();
HttpService httpService = SpringContextUtil.getBean(HttpService.class);
Object result = httpService.post(url, args[0], returnType);
AssertUtil.isNotNull(result,"远程访问,返回参数不能为空");
log.info("远程方法url:" + url + " 请求参数:" + args[0] + " 远程服务地址serverName:" + serverName);
return result;
}
}
RemoteFactoryBean
改类实现了FactoryBean接口,FactoryBean在spring中的特点是:当我们配置的bean的class指定是FactoryBean的时候,Spring最终给我们生成的这个bean将是FactoryBean中getObject()方法返回的bean
public class RemoteFactoryBean<T> implements FactoryBean<T> {
private Class<T> remoteCallServiceInterface;
/**
* 远程服务名称
*/
private String remoteServerName;
public RemoteFactoryBean(Class<T> remoteCallServiceInterface) {
this.remoteCallServiceInterface = remoteCallServiceInterface;
}
@Override
public T getObject() throws Exception {
return (T) Proxy.newProxyInstance(this.remoteCallServiceInterface.getClassLoader(), new Class[]{this.remoteCallServiceInterface}, new RemoteCallProxy(remoteServerName));
}
@Override
public Class<?> getObjectType() {
return this.remoteCallServiceInterface;
}
@Override
public boolean isSingleton() {
return true;
}
public String getRemoteServerName() {
return this.remoteServerName;
}
public void setRemoteServerName(String remoteServerName) {
this.remoteServerName = remoteServerName;
}
}
RemoteCallPackageRegistrar
该类是通过@Import注解导入到了RemoteCallScan注解中,@Import注解只能用在类上,里面的值可以是
- 定义了bean的Class数组
- 实现了ImportSelector接口的类
- 实现了ImportBeanDefinitionRegistrar接口的类
我们使用的就是第三种,在RemoteCallPackageRegistrar继承的registerBeanDefinitions方法中注册一个
RemoteCallScannerConfigurer的bean,并指定了属性值
/**
* @description: ImportBeanDefinitionRegistrar在ConfigurationClassPostProcessor
* 处理Configuration类期间被调用,用来生成该Configuration类所需要的BeanDefinition。
* 而ConfigurationClassPostProcessor正实现了BeanDefinitionRegistryPostProcessor接口
*
* spring启动我们自定义注册的入口
* @author zjg
* @date 2020/6/28 19:06
*/
public class RemoteCallPackageRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
public RemoteCallPackageRegistrar() {
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
}
/**
* AnnotationMetadata 表示当前被@Import注解给标注的所有注解信息
* 主要就是向spring中注入了一个RemoteCallScannerConfigurer的beanDefinition,对象名可以自己命名,
* 在后面RemoteCallScannerConfigurer类执行初始化的时候用到,主要是解析占位符
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annAttrs = AnnotationAttributes.fromMap(importingClassMetadata
.getAnnotationAttributes(RemoteCallScan.class.getName()));
if (null != annAttrs) {
this.registerBeanDefinitions(annAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RemoteCallScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
List<String> basePackages = new ArrayList();
basePackages.addAll((Collection)Arrays.stream(annAttrs.getStringArray("value"))
.filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText).collect(Collectors.toList()));
//Comma Delimited逗号分隔
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
String str = importingClassMetadata.getClassName() + "#" + RemoteCallScannerConfigurer.class.getSimpleName() + "#" + index;
return str;
}
}
RemoteCallScannerConfigurer
该类继承了BeanDefinitionRegistryPostProcessor接口,该接口又继承了BeanFactoryPostProcessor接口,其中有两个方法,postProcessBeanDefinitionRegistry是BeanDefinitionRegistryPostProcessor自带的,postProcessBeanFactory是从BeanFactoryPostProcessor继承过来的。
postProcessBeanFactory方法主要用来对bean定义做一些改变。postProcessBeanDefinitionRegistry是在所有Bean定义信息将要被加载,Bean实例还未创建的时候执行,优先postProcessBeanFactory执行,入参BeanDefinitionRegistry提供了丰富的方法来操作BeanDefinition,判断、注册、移除等方法都准备好了,我们在编写postProcessBeanDefinitionRegistry方法的内容时,就能直接使用入参registry的这些方法来完成判断和注册、移除等操作。
此处的postProcessBeanDefinitionRegistry方法进行了BeanDefinition的扫描,具体的扫描类是下面的ClassPathRemoteCallScanner
/**
* @description: BeanDefinitionRegistry的后处理器 用来注册额外的BeanDefinition
* 经常被用来注册BeanFactoryPostProcessor的BeanDefinition。
* @author zjg
* @date 2020/5/2 19:03
*/
public class RemoteCallScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean,
ApplicationContextAware, BeanNameAware {
private String beanName;
private String basePackage;
private ApplicationContext applicationContext;
private boolean processPropertyPlaceHolders;
public RemoteCallScannerConfigurer() {
}
@Override
public void setBeanName(String name) {
/**
* RemoteCallPackageRegistrar中generateBaseBeanName生成的beanName
*/
this.beanName = name;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.basePackage, "Property 'basePackage' is required");
}
/**
* 方法会在所有的BeanDefinition已经被加载了,但是所有的Bean还没有被创建前调用
* @param beanDefinitionRegistry
* @throws BeansException
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
if (this.processPropertyPlaceHolders) {
/**
* spring启动容器时,执行的顺序是先执行所有的BeanDefinitionRegistryPostProcessor,
* 再执行BeanFactoryPostProcessor中的postProcessBeanFactory,在里面会有占位符的替换
* 所以这里在执行扫描接口时,引用的占位符还没有解析替换成相应的properties中的值
*/
this.processPropertyPlaceHolders();
}
ClassPathRemoteCallScanner scanner = new ClassPathRemoteCallScanner(beanDefinitionRegistry);
scanner.setResourceLoader(this.applicationContext);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = this.applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && this.applicationContext instanceof ConfigurableApplicationContext) {
BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext)this.applicationContext).getBeanFactory()
.getBeanDefinition(this.beanName);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(this.beanName, mapperScannerBean);
Iterator var4 = prcs.values().iterator();
while(var4.hasNext()) {
PropertyResourceConfigurer prc = (PropertyResourceConfigurer)var4.next();
prc.postProcessBeanFactory(factory);
}
PropertyValues values = mapperScannerBean.getPropertyValues();
this.basePackage = this.updatePropertyValue("basePackage", values);
}
Optional var10001 = Optional.ofNullable(this.basePackage);
Environment var10002 = this.getEnvironment();
var10002.getClass();
this.basePackage = (String)var10001.map(str -> var10002.resolvePlaceholders((String) str)).orElse((Object)null);
}
private String updatePropertyValue(String propertyName, PropertyValues values) {
PropertyValue property = values.getPropertyValue(propertyName);
if (property == null) {
return null;
} else {
Object value = property.getValue();
if (value == null) {
return null;
} else if (value instanceof String) {
return value.toString();
} else {
return value instanceof TypedStringValue ? ((TypedStringValue)value).getValue() : null;
}
}
}
private Environment getEnvironment() {
return this.applicationContext.getEnvironment();
}
public String getBasePackage() {
return this.basePackage;
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public boolean isProcessPropertyPlaceHolders() {
return this.processPropertyPlaceHolders;
}
public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
this.processPropertyPlaceHolders = processPropertyPlaceHolders;
}
}
ClassPathRemoteCallScanner
该类的作用是扫描isCandidateComponent方法指定的类,将扫描到的这些BeanDefinition重新定义一下,即指定bean的class是RemoteFactoryBean类,并将这些bean上注解的值取出来赋值给属性
/**
* @description: remote接口的路径扫描
* @author zjg
* @date 2020/5/2 17:33
*/
@Slf4j
public class ClassPathRemoteCallScanner extends ClassPathBeanDefinitionScanner implements ApplicationContextAware {
private ApplicationContext applicationContext;
private Class<? extends RemoteFactoryBean> remoteFactoryBeanClass = RemoteFactoryBean.class;
/**
* 对标注RemoteServer注解的类生成的BeanDefinition进行修改,转为代理类
* @param basePackages
* @return
*/
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
log.warn("No RemoteCall interface was found in '{}' package. Please check your configuration.",
Arrays.toString(basePackages));
return beanDefinitions;
} else {
Iterator var3 = beanDefinitions.iterator();
while(var3.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition)definition;
AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata();
this.processBeanDefinitions(definition, metadata);
log.debug("Creating RemoteCallFactoryBean with name '{}' and '{}' RemoteCall Interface",
holder.getBeanName(), definition.getBeanClassName());
}
return beanDefinitions;
}
}
private void processBeanDefinitions(GenericBeanDefinition definition, AnnotationMetadata metadata) {
String beanClassName = definition.getBeanClassName();
if (!StringUtils.isEmpty(beanClassName)) {
definition.setBeanClass(this.remoteFactoryBeanClass);
//创建FactoryBean需要在构造方法中指定class参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
Map<String, Object> attributesMap = metadata.getAnnotationAttributes(RemoteServer.class.getName());
if (!CollectionUtils.isEmpty(attributesMap) && null != attributesMap.get("serverName")) {
String remoteServerName = String.valueOf(attributesMap.get("serverName"));
if (!StringUtils.isEmpty(remoteServerName)) {
//指定MapperFactoryBean的remoteServerName属性值
definition.getPropertyValues().add("remoteServerName", remoteServerName);
}
}
}
}
/**
* 指定扫描RemoteServer注解的接口
* @param beanDefinition
* @return
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
boolean hasAnnotation = metadata.hasAnnotation(RemoteServer.class.getName());
//isIndependent() top level class or nested class (静态内部类)
//指定是接口
return hasAnnotation && beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
public ClassPathRemoteCallScanner(BeanDefinitionRegistry registry) {
super(registry, true);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void registerFilters() {
this.addIncludeFilter(new AnnotationTypeFilter(RemoteServer.class));
}
}
结束
里面的注释已经比较详细了,大家可以结合注释看看代码运行一下