spring扫描自定义注解标记接口到容器

58 阅读1分钟

之前学习mybatis扫描mapper到容器,逻辑和细节太多,于是先从一个最小的代码例子入手,找找感觉

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
public @interface MyLearning {
}

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE})
@Import({MyScannerRegistrar.class})
public @interface MyLearningScan {
    String[] value() default {};
}

@MyLearning
public interface HelloTest {
    String f1();
}

public class MyScannerRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyLearningScan.class.getName()));
        MyScanner myScanner = new MyScanner(registry);
        // 扫描MyLearningScan注解中的定义的包路径
        myScanner.doScan(annotationAttributes.getStringArray("value"));
    }
}

public class MyScanner extends ClassPathBeanDefinitionScanner {
    public MyScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
        // 添加需要扫描的注解
        addIncludeFilter(new AnnotationTypeFilter(MyLearning.class));
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 扫描到basePackages中所有被注解MyLearning标记的接口的Bean定义
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        for (BeanDefinitionHolder holder : beanDefinitionHolders) {
            BeanDefinition definition = holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            // 使用指定的Bean来产生实例
            definition.setBeanClassName(MyFactoryBean.class.getName());
           // 指定BeanClass构造函数的参数
           definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
        }
        return beanDefinitionHolders;
    }

    // 这句override很重要,父类默认注解标记的必须是实现类,这里的逻辑改为标记的是接口
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }
}

public class MyFactoryBean<T> implements FactoryBean<T> {
    private final Class<T> interfaceType;
    public MyFactoryBean(Class<T> interfaceType) {
        this.interfaceType = interfaceType;
    }
    @Override
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[]{interfaceType}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                } else {
                    return "hello world";
                }
            }
        });
    }
    @Override
    public Class<?> getObjectType() {
        return this.interfaceType;
        // 这里返回值很重要,不然只能通过@Resource注入,通过ConfigurableApplicationContext.getBean(Class<T> requiredType)可知会调用这里,先通过类型获取beanName,再通过beanName获取bean
    }
}

@MyLearningScan("com.example.mybatislearning.mymapper")
@SpringBootTest
class MybatisLearningApplicationTests {
    @Autowired
    private HelloTest helloTest;
    @Test
    void contextLoads() {
        String r = helloTest.f1();
        System.out.println("r:" + r);
    }
}