spring源码解读(二)

接着上一篇文章,首先我们需要解决如何解决不修改spring源码的情况下,使注解在接口上生效?

根据前一篇文章分析,使注解不在接口上生效的原因是因为ClassPathScanningCandidateComponentProvider的isCandidateComponent的方法

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
	}

而ClassPathScanningCandidateComponentProvider是ClassPathBeanDefinitionScanner继承的,所以我们自定义一个类,继承ClassPathBeanDefinitionScanner重写isCandidateComponent方法

public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        return super.doScan(basePackages);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }
}

但是我们如何使我们自己写得这个生效?我们需要先了解下@import注解,所以我们来完整的理一下代码:

1:首先我们需要自定义一个注解,这个注解是注入接口的@EnableScannerMapper,添加一个属性 basePackage,这个是用来动态传入要扫描的接口包路径

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(value = MyImportBeanDefinitionRigister.class)
public @interface EnableScannerMapper {
    String basePackage();
}

然后我们需要定义一个扫描器,用来扫描上述注解的,也就是我们上述所说的extends ClassPathBeanDefinitionScanner

public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        return super.doScan(basePackages);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface();
    }
}

然后通过注解传入的basePackage包名,通过以上的自定义扫描器来进行扫描所有的接口,并生成beanDefinfition对象

public class MyImportBeanDefinitionRigister implements ImportBeanDefinitionRegistrar {

    private static Class targetClass = MyFactoryBean.class;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //手动注册配置
        /*RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(InstD.class);
        registry.registerBeanDefinition("instD",rootBeanDefinition);*/
        //通过扫描器自动扫描
        AnnotationAttributes annotationAttributes = (AnnotationAttributes) importingClassMetadata.getAnnotationAttributes(EnableScannerMapper.class.getName());
        if (annotationAttributes == null) {
            return;
        }
        String basePackage = annotationAttributes.getString("basePackage");
        //使用自定义扫描器扫描bean定义
        MyClassPathBeanDefinitionScanner definitionScanner = new MyClassPathBeanDefinitionScanner(registry);
        definitionScanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        //此时扫描出来的bean定义是一个个接口
        Set<BeanDefinitionHolder> beanDefinitionHolders = definitionScanner.doScan(basePackage);
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
            //从beanDefinition中拿到接口class的字符串,例如:com.fmj.dao.AccountMapper
            String source = beanDefinition.getBeanClassName();
            System.out.println("原生的class类型为:" + source);
            beanDefinition.setBeanClass(targetClass);
            //将参数targetClass通过构造器传入到MyFactoryBean中
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(source);
        }
    }
}

上述代码需要注意几点,因为我们是通过动态传入的代理对象,所以我们使用泛型,并通过构造方法进行传入,因为接口是不能被实例化的,所以我们需要重写FactoryBean,并且使用jdk动态代理生成接口的代理对象,也是通过构造方法将接口的class对象动态传入,所以我们上述的生成beanDefinition对象需要调用beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(source);将targetClass通过构造方法传入进去,否则factoryBean将无法拿到接口的class对象,下面我们来看生成代理对象的逻辑

public class MyFactoryBean<T> implements FactoryBean<T> {
    private Class<T> targetClass;

    public MyFactoryBean(Class<T> targetClass) {
        this.targetClass = targetClass;
    }

    @Override
    public T getObject() throws Exception {
        return (T)Proxy.newProxyInstance(targetClass.getClassLoader(),new Class[]{targetClass},new MyProxyHandler());
    }

    @Override
    public Class<?> getObjectType() {
        return targetClass;
    }
}

class MyProxyHandler implements InvocationHandler{
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> returnType = method.getReturnType();
        return returnType.newInstance();
    }
}

到这里@EnableScannerMapper注解就已经生效了,我们只需要将注解配置进去就可以使用

@Configuration
@EnableScannerMapper(basePackage = "com.fmj.seata.v2dao")
public class MyConfig {
}
public interface ProductorMapper {

    String queryId(String id);
}
public class MainTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        ProductorMapper productorMapper = (ProductorMapper) applicationContext.getBean(ProductorMapper.class);
        productorMapper.queryId("select 11111");
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值