自定义注解生成spring bean
Overview
最近看到公司一段关于thrift整合spring的项目源码。其中涉及到配置thrift接口所在package地址,自动就会扫描到方法,并注册到服务总心的代码。参看了源码,涉及到了一些spring boot配置文件自动装配,自定义注解扫描生成bean的过程。可能对于以后参看spring ioc源码有帮助。记录学习一下。
配置文件处理
使用 @ConfigurationProperties 可以轻松的处理好配置文件
比如如下一段yml
parkes:
servicecenter: #thrift 相关的配置
center: #集线塔配置
address: jxt-zk-test.yy.com:2181 #集线塔的地址
application: wolfkill #集线塔应用名称
export: #服务export时必须
host: 127.0.0.1
port: 10059 #服务生产者端口
packages: #定义需scan的export/reference package
exports: com.yy.wolfkill.thrift.pkgame.meta
references: com.yy.wolfkill.thrift.pkgame.metasync,com.yy.wolfkill.thrift.opconfig
group: "*"
我们可以这样定义一个容器,
package com.yy.parkes.servicecenter;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "parkes.servicecenter")
@Data
public class ServiceCenterProperties {
private Center center = new Center();
private Export export = new Export();
private String group = "*";
/**
* 设置需要扫描并且自动注册的bean的package
*
* @see com.yy.parkes.servicecenter.ServiceCenterExporter
* @see com.yy.parkes.servicecenter.ServiceCenterReference
*/
private Packages packages;
@Data
public static class Packages {
private List<String> exports;
private List<String> references;
}
@Data
public static class Export {
private String host;
private int port;
}
@Data
public static class Center {
private String address;
private String application;
}
}
再使用@EnableConfigurationProperties,让配置生成bean可以被Autowried.当然,这里可以使用@Component在ServiceCenterProperties上,让他直接生成bean,可以在需要时注入。
@EnableConfigurationProperties(ServiceCenterProperties.class);
public class ServiceCenterAutoConfiguration {}
关于@EnableConfigurationProperties还可以使用这种方式,多用于bean的生成.生成的ServiceCenterProperties也是会完成自动装配的。
@Bean
@ConfigurationProperties(prefix = "parkes.servicecenter")
public ServiceCenterProperties getServiceCenterProperties(){
return new ServiceCenterProperties();
}
根据扫描包和自定义注解,实例化你想要的bean
首先我们定义一个类,实现spring提供的3个接口
ImportBeanDefinitionRegistrar接口会在import这个类时,调用registerBeanDefinitions方法,进行bean的注入。
EnvironmentAware接口,会在import时,自动调用setEnvironment,为这个类注入环境变量
BeanFactoryAware接口,会在import是,自动调用setBeanFactory,为这个类注入BeanFactory
public class ServiceCenterRegistrar implements ImportBeanDefinitionRegistrar,
BeanFactoryAware, EnvironmentAware {
private Environment environment;
private ConfigurableListableBeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
....
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
这个类大致就是这样,提前帮我们安排好了environment,beanFactory,registry。让我们回到正题,如何扫描自定义注解,然后实例化为Bean
首先我们先定义一个Scanner类,确定我们要扫描的注解
static class Scanner extends ClassPathScanningCandidateComponentProvider { Scanner() { super(false); addIncludeFilter(new AnnotationTypeFilter( ServiceCenterReference.class) ); addIncludeFilter(new AnnotationTypeFilter( ServiceCenterExporter.class) ); addExcludeFilter( new RegexPatternTypeFilter(Pattern.compile(".*\\$Async")) ); } @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return true; } }
我们自定义扫描的为ServiceCenterReference.class,ServiceCenterExporter.class注解的类,以及$Async结尾的类。这里不讨论Scanner的原理,会专门拆分一篇博客来介绍spring的scanner原理
扫描得到BeanDefinition,然后进行处理
private void doRegisterServiceCenterService(String basePackage, BeanDefinitionRegistry registry, boolean isExport) { Scanner scanner = new Scanner(); //scanner 扫描后得到的BeanDefinition Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { //如果BeanDefinition是基于注解扫描 if (candidateComponent instanceof AnnotatedBeanDefinition) { //强转 AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; //拿到注解元信息 AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); //判定这个注解修饰的是否是interface Assert.isTrue(annotationMetadata.isInterface(), "@ServiceCenterReference or @ServiceCenterExporter can only be specified on an interface"); //isExport是判定这个是thrift服务提供者还是消费者,对这里影响不大。 //如果这个元信息是被注解@ServiceCenterExporter修饰,执行registerExporter if (isExport && annotationMetadata.hasAnnotation(ServiceCenterExporter.class.getName())) { registerExporter(registry, annotationMetadata); } else if (!isExport && annotationMetadata .hasAnnotation(ServiceCenterReference.class.getName())) { registerReference(registry, annotationMetadata); } } } }
取得注解中信息,并生成BeanDefinition
例如我们的注解如下@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ServiceCenterExporter { int DEFAULT_PORT = -1; ServiceProtocolType protocol() default ServiceProtocolType.thrift; ThriftProtocolType thriftProtocol() default ThriftProtocolType.BINARY; TransportType transport() default TransportType.TFRAMED; int port() default DEFAULT_PORT; }
生成BeanDefinition
private void registerExporter(BeanDefinitionRegistry registry, AnnotationMetadata metadata) { //取得Annotation的配置元素 Map<String, Object> attrs = metadata .getAnnotationAttributes(ServiceCenterExporter.class.getName()); ThriftProtocolType thriftProtocol = AnnotationUtils.getThriftProtocol(attrs); TransportType transport = AnnotationUtils.getTransport(attrs); String className = metadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(); definition.getBeanDefinition().setBeanClass(ServiceExporter.class); definition.addPropertyValue("protocol", AnnotationUtils.getProtocol(attrs)); definition.addPropertyValue("interfaceName", className); definition.addPropertyValue("thriftProtocol", thriftProtocol); definition.addPropertyValue("transport", transport); ExportConfig exportConfig = new ExportConfig(); int port = AnnotationUtils.getPort(attrs); if (port != ServiceCenterExporter.DEFAULT_PORT) { exportConfig.setPort(port); } definition.addPropertyValue("exportConfig", exportConfig); definition.setInitMethodName("export"); //创建beanDefinition,用特殊名字 BeanDefinitionHolder holder = new BeanDefinitionHolder(definition.getBeanDefinition(), getBeanName(className, true)); //真正注册,带有别名注册的功能 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); log.debug("[cmd=registerExported, attrs={}, interfaceName={}]", attrs, className); }