接着上一篇文章,首先我们需要解决如何解决不修改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");
}
}