Пользовательский сканер аннотаций SpringBoot @XXXScan

Spring Boot

Предисловие:

В процессе изучения Netty в последнее время я следовал идеям предшественников и использовал Netty в качестве основного средства коммуникации для разработки очень крутой, первой во вселенной (фактически супер-мусора) Netty Rpc Demo. Почему бы не назвать фреймворк Demo?Хороший фреймворк требует очень много времени на разработку и оптимизацию, и это неотделимо от полной самоотдачи больших парней.Для новичка моего уровня это в лучшем случае называется demo. Ну, без лишних слов, исходная идея состоит в том, чтобы вручную настроить карту сопоставления между интерфейсами и классами реализации, как показано ниже:

    @Bean("handlerMap")
    public Map<String, Object> handlerMap(){
        Map<String, Object> handlerMap = new ConcurrentHashMap<String, Object>();
        handlerMap.put("com.jdkcb.mystarter.service.PersonService",new PersonServiceImpl());
        return handlerMap;
    }

Не распыляйте больших парней Когда я уверенно передал код старшим, старшие были очень терпеливы (неуправляемы) и указали на проблему, которую я сделал. Действительно, когда количество интерфейсных классов очень велико, настраивать карту в одиночку очень хлопотно, поэтому я вернулся, чтобы хорошенько подумать, и очень хорошо выспался.Через семь, семь, сорок девять минут вспыхнула лампочка. моя голова, и я подумал о позавчерашнем аннотации, которую я видел, когда писал Mybatis для настройки нескольких источников данных, @MapperScan

охохохохохох, это мой эксклюзивный момент, поэтому я решил, что это ты.

Далее мы будем имитировать реализацию Mybatis, чтобы создать сканер аннотаций для сканирования и регистрации наших пользовательских аннотаций как bean-компонентов, управляемых Spring.

Солдаты отправляются на поле боя и начинают работать непосредственно. (Ну, это на самом деле стебель, я думаю, многие люди не могут его получить)

Код боя:

Поскольку мы хотим сканировать наши пользовательские аннотации, сначала у нас должна быть пользовательская аннотация. Давай, Xiao Er, поставь пользовательскую аннотацию, совмещенную с моими личными потребностями, я настрою ее как NRpcServer, код такой:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NRpcServer {
    //服务的名称,用来RPC的时候调用指定名称的 服务
    String name() default "";
    String value() default "";
}


Однако у меня есть вопрос, как вы узнали, что нужно добавить аннотации @Target({ElementType.TYPE, ElementType.METHOD})?

ххх,кажется нашел.Не могу написать.Не могу скопировать.Поскольку это имитация аннотации @MapperScan(), просто нажмите на аннотацию @Mapper и посмотрите какие аннотации добавлены к нему, да? , эй-эй

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

Хорошо, удалите аннотации, которые нам не нужны, атрибуты, которые мы не используем, и измените имя на наши собственные аннотации. (смешной)

Как сканируется @Mapper?Я думаю, что с помощью аннотации @MapperScan мы можем прийти к соглашению.копировать один, Место ограничено, я не буду выкладывать код Mybatis позже, друзья, которым нужно знать, могут открыть исходный код mybatis для просмотра.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
//spring中的注解,加载对应的类
@Import(NRpcScannerRegistrar.class)//这个是我们的关键,实际上也是由这个类来扫描的
@Documented
public @interface NRpcScan {

    String[] basePackage() default {};

}

Конечно, сама по себе эта аннотация — ничто, где основная часть? Ответ заключается в том, что на классе NRpcScannerRegistrar, собственно, сканирующая фильтрация наших аннотаций в основном реализуется этим классом.

Создайте новый код класса NRpcScannerRegistrar и наследуйте два интерфейса ImportBeanDefinitionRegistrar, ResourceLoaderAware.

Где: ResourceLoaderAware — это интерфейс маркера, используемый для внедрения ResourceLoader через контекст ApplicationContext.

код показывает, как показано ниже:

public class NRpcScannerRegistrar  implements ImportBeanDefinitionRegistrar, ResourceLoaderAware{

    ResourceLoader resourceLoader;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;

    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        //获取所有注解的属性和值
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(NRpcScan.class.getName()));
        //获取到basePackage的值
        String[] basePackages = annoAttrs.getStringArray("basePackage");
        //如果没有设置basePackage 扫描路径,就扫描对应包下面的值
        if(basePackages.length == 0){
            basePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()};
        }

        //自定义的包扫描器
        FindNRpcServiceClassPathScanHandle scanHandle = new  FindNRpcServiceClassPathScanHandle(beanDefinitionRegistry,false);

        if(resourceLoader != null){
            scanHandle.setResourceLoader(resourceLoader);
        }
        //这里实现的是根据名称来注入
        scanHandle.setBeanNameGenerator(new RpcBeanNameGenerator());
        //扫描指定路径下的接口
        Set<BeanDefinitionHolder> beanDefinitionHolders = scanHandle.doScan(basePackages);


    }

}

Это включает в себя класс FindNRpcServiceClassPathScanHandle, который является нашим настраиваемым сканером пакетов, и мы можем добавить наши условия фильтрации к этому сканеру.

public class FindNRpcServiceClassPathScanHandle extends ClassPathBeanDefinitionScanner {

    public FindNRpcServiceClassPathScanHandle(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //添加过滤条件,这里是只添加了@NRpcServer的注解才会被扫描到
        addIncludeFilter(new AnnotationTypeFilter(NRpcServer.class));
        //调用spring的扫描
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        return beanDefinitionHolders;
    }
}

Когда вы увидите это, многие читатели почти обнаружат, что я на самом деле ничего не делаю.Все основные операции выполняются реализацией, предоставленной Spring.На самом деле, да, основной код на самом деле находится в

super.doScan(basePackages);

В этом предложении мы фактически выполнили вторичную обработку в соответствии с результатами сканирования Spring, В моей демонстрации упомянутая ранее карта фактически автоматически генерируется на этом этапе, потому что на этот раз мы в основном говорим о реализации сканера пакетов, так что часть логики была удалена.

В то же время класс RpcBeanNameGenerator в приведенном выше коде реализует внедрение указанного bean-компонента в соответствии с нашим именем, что мы фактически делаем здесь, так это получаем значение, установленное атрибутом в аннотации. код показывает, как показано ниже:

public class RpcBeanNameGenerator extends AnnotationBeanNameGenerator {

    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        //从自定义注解中拿name
        String name = getNameByServiceFindAnntation(definition,registry);
        if(name != null && !"".equals(name)){
            return name;
        }
        //走父类的方法
        return super.generateBeanName(definition, registry);
    }
    
    private String getNameByServiceFindAnntation(BeanDefinition definition, BeanDefinitionRegistry registry) {
        String beanClassName = definition.getBeanClassName();
        try {
            Class<?> aClass = Class.forName(beanClassName);
            NRpcServer annotation = aClass.getAnnotation(NRpcServer.class);
            if(annotation == null){
                return null;
            }
            //获取到注解name的值并返回
            return annotation.name();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

На данный момент это почти сделано.

контрольная работа:

Во-первых, мы готовим класс PersonService и аннотируем его нашим @NRpcServer, Код выглядит следующим образом:

@NRpcServer(name="PersonService")
public class PersonService {

    public String getName(){
        return "helloword";
    }
}

Затем добавьте аннотацию @NRpcScan в класс запуска Springboot и укажите пакеты, которые нам нужно сканировать.

@NRpcScan(basePackage = {"com.jdkcb.mybatisstuday.service"})

Создайте новый контроллер для тестирования, код выглядит следующим образом:

@RestController
public class TestController {

    @Autowired
    @Qualifier("PersonService")
    private PersonService personService;

    @RequestMapping("test")
    public String getName(){
        return personService.getName();
    }
    
}

Введите http://127.0.0.1:8080/test в браузере.

Выходной результат:

helloword

На данный момент, даже если настоящий успех достигнут.

В конце концов, всем привет, я Хань Шу, гудите, следуйте за мной и ешьте привет фрукты (акимбо).

Не забудьте поставить лайк перед уходом~

Подождите минутку:

Соответствующий исходный код можно загрузить на мой github (добро пожаловать звездочка):

GitHub.com/Хан Шуайкан…