SpringBoot實現自定義包掃描

SpringBoot實現自定義包掃描

最近很好奇在SpringBoot項目上加@MapperScan(basePackages = "xxx") 註解就能掃描到執行的包下面的東西。於是研究了一下Mybatis怎麼實現的。大致是根據Mybatis依葫蘆畫瓢

於是點開@MapperScan類,看到類結構如下:


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})  //MapperScannerRegistrar這個類纔是真正的註冊邏輯
@Repeatable(MapperScans.class)
public @interface MapperScan {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
	
    ...
}


然後點開MapperScannerRegistrar,看一下類結構


//當使用@Import標籤,實現ImportBeanDefinitionRegistrar 接口,那麼可以自定義擴展beanDefinition
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
    //資源加載器
    private ResourceLoader resourceLoader;


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

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //從啓動類上面獲取MapperScan註解
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if (mapperScanAttrs != null) {
            //存在的話那麼註冊自己的BeanDefinitions
            this.registerBeanDefinitions(mapperScanAttrs, registry);
        }

    }

    //已將不重要的內容刪除
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
        //自定義註解掃描器
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
           //這裏是對應處理bean scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
        }
       //獲取註解basePackages value   basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        scanner.registerFilters();
        //獲取到包,開始進行掃描, 
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
}

這裏是doScan的邏輯,Spring的代碼中,一般已do開頭的都是具體做事情的,這裏返回掃描包下面的的beanDefinitions集合,如果存在的話,那麼會調用對應的BeanNameGenerator 方法,對應上面scanner.setBeanNameGenerator

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

大概思路瞭解了,那我們也整一個

  1. 定義啓動類需要配置的掃描註解

    
    import org.springframework.context.annotation.Import;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * ClassName: MyAOP
     * @Description:
     * @author leegoo
     * @date 2020年03月14日
     */
    @Retention(RetentionPolicy.RUNTIME)//注意用這個註解才能在運行時使用反射
    @Target({ElementType.TYPE})
    @Documented
    @Import({CustomerScanRegister.class})
    public @interface CustomerScan {
        //掃描包路徑
        String[] basePackages() default {};
        //掃描類
        Class<?>[] basePackageClasses() default {};
    }
    
    
  2. 定義CustomerScanRegister以及實現

    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.BeanFactoryAware;
    import org.springframework.beans.factory.config.BeanDefinitionHolder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.ResourceLoaderAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.annotation.AnnotationAttributes;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.lang.NonNull;
    import org.springframework.util.ClassUtils;
    import org.springframework.util.StringUtils;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    
    public class CustomerScanRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanFactoryAware {
        private ResourceLoader resourceLoader;
    
        private BeanFactory beanFactory;
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }
    
        @Override
        public void setResourceLoader(@NonNull ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,@NonNull BeanDefinitionRegistry registry) {
            //這裏是獲取cn.withmes.springboot.my.aop.SpringBootMyAopApplication類上對應的註解
            //MergedAnnotations annotations = importingClassMetadata.getAnnotations();
            //這裏判斷是否存在MyAOP註解
            AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(CustomerScan.class.getName()));
            if (mapperScanAttrs == null)  return;
    
            this.registerBeanDefinitions(mapperScanAttrs, registry);
        }
    
    
        private  Set<BeanDefinitionHolder> registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
            List<String> basePackages = new ArrayList<>();
            //取到所有屬性的值
            basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
            basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
            CustomerScanner scanner = new CustomerScanner(registry);
            scanner.setBeanNameGenerator(( beanDefinition,beanDefinitionRegistry)->{
                String beanClassName = beanDefinition.getBeanClassName();
                try {
                    Class<?> clz = Class.forName(beanClassName);
                    MyService at = clz.getAnnotation(MyService.class);
                    if (null == at) return null;
                    //如果@MyService沒有指定名字,那麼默認首字母小寫進行註冊
                    if (at.name().equalsIgnoreCase("")  ) {
                        String clzSimpleName = clz.getSimpleName();
                        String first = String.valueOf(clzSimpleName.charAt(0));
                        return clzSimpleName.replaceFirst(first,first.toLowerCase());
                    }
                    return at.name();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    return null;
                }
            });
            if(resourceLoader != null){
                scanner.setResourceLoader(resourceLoader);
            }
            
            return scanner.doScan(StringUtils.toStringArray(basePackages));
        }
    
    
    
    
  3. 自定義掃描器

    
    import org.springframework.beans.factory.config.BeanDefinitionHolder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
    import org.springframework.core.type.filter.AnnotationTypeFilter;
    
    import java.util.Set;
    
    
    public class CustomerScanner extends ClassPathBeanDefinitionScanner {
    
        public CustomerScanner(BeanDefinitionRegistry registry) {
            super(registry, false);
        }
    
        @Override
        public Set<BeanDefinitionHolder> doScan(String... basePackages) {
            //添加過濾條件,這裏是只添加了@MyService的註解纔會被掃描到
            addIncludeFilter(new AnnotationTypeFilter(MyService.class));
            return super.doScan(basePackages);
        }
    
    }
    
    
  4. 寫一個接口和兩個實現類,試下不同的方式注入(這裏把所有類的代碼放在一起了)

    
    public interface UserService {
        User findUser(Integer id) ;
    }
    
    
    @MyService // 使用自定義註解,注入spring容器
    public class UserServiceImpl implements UserService {
    
        @Resource
        private Data data;
    
        @Override
        public User findUser(Integer id)  {
            return data.users.get(id);
        }
    
    }
    
    @MyService(name = "lsUser") // 使用自定義註解,注入spring容器
    public class UserServiceImpl2 implements UserService {
    
        @Resource
        private Data data;
    
        @Override
        public User findUser(Integer id)  {
            return data.users.get(id);
        }
    
    }
    
    
    //實現CommandLineRunner,應用初始化後,去執行一段代碼塊邏輯,這段初始化代碼在整個應用生命週期內只會執行一次
    @Service
    public class Data  implements CommandLineRunner {
        public Map<Integer, User> users = new HashMap<>(
    
        );
    
    
        @Override
        public void run(String... args) throws Exception {
            users.put(1, new User(1, "小紅"));
            users.put(2, new User(2, "小明"));
            users.put(3, new User(3, "小三"));
            System.out.println("初始化數據:"+users);
        }
    
        public User getUsers(Integer id) {
            return users.get(id);
        }
    
    }
    
    
    
  5. 開始測試

    @SpringBootTest
    class SpringBootMyAopApplicationTests {
    
        //這裏userServiceImpl 會報紅,但是還是能夠執行,因爲我們沒有使用spring註解進行Bean注入,所以會提示我們可能找不到bean
        @Resource(name = "userServiceImpl")
        private UserService  userService;
    
        @Resource(name = "lsUser")
        private UserService  lsUser;
    
        @Test
        void testCustomerAnnotation() {
            User user = userService.findUser(1);
            System.out.println("user:"+user); //user:User{id=1, name='小紅'}
            User ls = lsUser.findUser(2);
            System.out.println("ls:"+ls);//ls:User{id=2, name='小明'}
        }
    
    }
    

代碼已上傳github(github主頁:https://github.com/q920447939/java-study)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章