前言
在使用mybatis或者openFeign时只定义了一个接口类,并无实现类,可以把接口注入到service中并且能调用方法返回值。一个接口并无实现类,为什么可以实例化并且交给了spring管理。mybatis,OpenFeign又是怎么实现的?接下来给大家一一揭晓
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();
}
}
2、CkScannerRegistrar类实现
解析此类时发现实现了spring的ImportBeanDefinitionRegistrar接口并且重新写了registerBeanDefinitions方法,会调用此方法。里面关键点new CkClassPathScanner类并且调用了doScan。
public class CkScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 获取SpringBootApplication自定义注解CkScan值
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()));
//将接口转换为BeanDefinition对象放入spring中
//CkClassPathScanner为自定义扫描类
CkClassPathScanner classPathScanner = new CkClassPathScanner(beanDefinitionRegistry);
classPathScanner.doScan(StringUtils.collectionToCommaDelimitedString(basePackages));
}
}
}
3、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) {
// 增加过滤,为接口类,并且接口上包含CkInterfaceAnnotation注解
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) {
// spring默认不会扫描接口,此处设置为true,不做过滤
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) {
// 此段作用,将所有带CkInterfaceAnnotation注解接口,定义成beanDefinition对象
// beanDefinitions中的bean对象指向接口的代理类
// 在使用@Autowired注解注入接口时,其实注入的是接口代理对象
beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
System.out.println("接口名称" + beanClassName);
// 设置CkFactoryBean构造方法参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(CkFactoryBean.class);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
});
}
}
4、processBeanDefinitions方法实现
直接看注释理解
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
// 可以理解为,将所有接口类转换为beanDefinition对象,
// beanName为接口名称,bean对应实际实例化对象需要从CkFactoryBean对象对应的getObject获取
// 在使用@Autowired注解注入接口时,其实注入的是接口代理对象,即CkFactoryBean类中getObject方法获取对象
beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
System.out.println("接口名称" + beanClassName);
// 实例化CkFactoryBean类时构造方法传的参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(CkFactoryBean.class);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
});
}
5、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;
}
/**
* bean实例化对象,指向代理类即可
*/
@Override
public T getObject() throws Exception {
// 返回CkInterfaceProxy代理对象
return (T) Proxy.newProxyInstance(ckInterface.getClassLoader(),
new Class[]{ckInterface},
new CkInterfaceProxy<>(ckInterface));
}
/**
* bean对象类型
*/
@Override
public Class<T> getObjectType() {
return this.ckInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
6、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;
}
}
7、测试
增加接口类,并且没有任何类实现此接口
Controller里面注入接口,调用Controller接口方法,日志是代理类打印出来