在使用mybatis或者openFeign时只定义了一个接口类,并无实现类,可以把接口注入到service中并且能调用方法返回值。一个接口并无实现类,为什么可以实例化并且交给了spring管理。mybatis,OpenFeign又是怎么实现的?接下来给大家一一揭晓
用于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();
- }
-
- }
解析此类时发现实现了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));
- }
-
- }
-
- }
继承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);
- });
- }
- }
直接看注释理解
- 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);
- });
- }
实现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;
- }
- }
比如我们使用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接口方法,日志是代理类打印出来

