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;
}
大概思路瞭解了,那我們也整一個
-
定義啓動類需要配置的掃描註解
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 {}; }
-
定義
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)); }
-
自定義掃描器
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); } }
-
寫一個接口和兩個實現類,試下不同的方式注入(這裏把所有類的代碼放在一起了)
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); } }
-
開始測試
@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)