说明
大家在使用mybatis或者openFeign时只定义了一个接口类,并无实现类,可以把接口注入到service中并且能调用方法返回值。 一个接口并无实现类,为什么可以实例化并且交给了spring管理。mybatis,OpenFeign又是怎么实现的? 看mybatis源代码理解的,删减了很多,保留一些关键初始化步骤。
直接上代码,链接(gitee上面)
1.先自定义注解,用于SpringBootApplication启动类。启动类加上CkScan注解,注解值即需要扫描那些包接口。springboot在启动时,发现注解里面Import导入CkScannerRegistrar类,会解析此类,此步就是实现入口。CkScannerRegistrar类下面会讲解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({CkScannerRegistrar.class})
public @interface CkScan {
String[] value() default {};
String[] basePackages() default {};
}
@org.springframework.boot.autoconfigure.SpringBootApplication
@MapperScan("com.ck.datacenter.**.dao")
@CkScan("com.ck.datacenter.itf")
@EnableOpenApi
public class SpringBootApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(SpringBootApplication.class);
application.run();
}
}
CkScannerRegistrar类实现,解析此类时发现实现了spring的ImportBeanDefinitionRegistrar接口并且重新写了registerBeanDefinitions方法,会调用此方法。里面关键点new CkClassPathScanner类并且调用了doScan。
public class CkScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
AnnotationAttributes attrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(CkScan.class.getName()));
if (attrs != null) {
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.stream(attrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(attrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
CkClassPathScanner classPathScanner = new CkClassPathScanner(beanDefinitionRegistry);
classPathScanner.doScan(StringUtils.collectionToCommaDelimitedString(basePackages));
}
}
}
CkClassPathScanner实现,继承ClassPathBeanDefinitionScanner扫描类,重写里面doScan,上步已经调用了doScan方法,进入此方法,调用了父类super.doScan(basePackages)得到所有满足条件BeanDefinitionHolder对象(接口一个包装类)。 扫描过滤条件:doScan中的addIncludeFilter方法可以增加过滤条件,isCandidateComponent方法也可以进行条件过滤
得到所有BeanDefinitionHolder对象后,调用processBeanDefinitions进行加工处理,此方法也是关键
public class CkClassPathScanner extends ClassPathBeanDefinitionScanner {
public CkClassPathScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() &&
beanDefinition.getMetadata().isIndependent() &&
beanDefinition.getMetadata().hasAnnotation(CkInterfaceAnnotation.class.getName());
}
@Override
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
if (super.checkCandidate(beanName, beanDefinition)) {
return true;
} else {
System.out.println("Skipping MapperFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface. Bean already defined with the same name!");
return false;
}
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
this.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
System.out.println("未扫描到有CkInterfaceAnnotation注解接口");
} else {
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
System.out.println("接口名称" + beanClassName);
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(CkFactoryBean.class);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
});
}
}
processBeanDefinitions方法实现,直接看注释理解
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
System.out.println("接口名称" + beanClassName);
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(CkFactoryBean.class);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
});
}
CkFactoryBean类实现,实现spring的FactoryBean接口即可。spring在初始化bean时,会调用getObject方法获取实例,我们只需要在此方法返回接口的代理类即可。在service中调用接口的方法时,实际就会调用到我们写的CkInterfaceProxy代理类
public class CkFactoryBean<T> implements FactoryBean<T> {
private Class<T> ckInterface;
public CkFactoryBean() {
}
public CkFactoryBean(Class<T> ckInterface) {
this.ckInterface = ckInterface;
}
@Override
public T getObject() throws Exception {
return (T) Proxy.newProxyInstance(ckInterface.getClassLoader(),
new Class[]{ckInterface},
new CkInterfaceProxy<>(ckInterface));
}
@Override
public Class<T> getObjectType() {
return this.ckInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
CkInterfaceProxy代理类的实现。比如我们使用OpenFeign,我们在此步做处理,获取类上注解和方法上的注解,通过类上注解值再从注册中心获取实际的服务器,再拼接方法上注解路径,就得到完整请求路径
public class CkInterfaceProxy<T> implements InvocationHandler, Serializable {
private final Class<T> ckInterface;
public CkInterfaceProxy(Class<T> ckInterface) {
this.ckInterface = ckInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (this.ckInterface.isAnnotationPresent(CkInterfaceAnnotation.class)) {
CkInterfaceAnnotation interfaceAnnotation = this.ckInterface.getAnnotation(CkInterfaceAnnotation.class);
System.out.println("调用接口类名:" + interfaceAnnotation.value());
if (method.isAnnotationPresent(CkMethodAnnotation.class)) {
CkMethodAnnotation methodAnnotation = method.getAnnotation(CkMethodAnnotation.class);
System.out.println("调用接口方法名:" + methodAnnotation.value());
}
}
return null;
}
}
测试
增加接口类,并且没有任何类实现此接口 Controller里面注入接口,调用Controller接口方法,日志是代理类打印出来
|