SpringBoot注解开发 @Import 神器

@Import 是springboot用来注入bean的, 我们实际看下有哪些作用。

实例一. 直接导入

定义了一个Mapper注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperRegistrar.class)
public @interface Mapper {
	public String value() default "";
}

注解用@Import修饰, 并指定导入MapperRegistrar 类到spring容器中,MapperRegistrar代码:

public class MapperRegistrar {
	public MapperRegistrar() {
		System.err.println("MapperRegistrar init");
	}
}

springboot启动类使用:

@SpringBootApplication
@Mapper
public class OrderApplication {
	public static void main(String[] args) {
		SpringApplication.run(OrderApplication.class, args);
	}
}

程序打印:

说明我们的 MapperRegistrar 被加载到spring容器中了。

但这种用法几乎没什么卵用。。。。


实例二. 实现ImportBeanDefinitionRegistrar

下面我们实现一个扫描指定包下面所有类加载到spring容器案例。

新建Mapper接口:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperRegistrar.class)
public @interface Mapper {
        // 指定扫描包的路径
	public String value() default "";
}

通用import MapperRegistrar 类:

public class MapperRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// 拿到Mapper接口 的信息
		 AnnotationAttributes msapperAttrs = AnnotationAttributes
			        .fromMap(importingClassMetadata.getAnnotationAttributes(Mapper.class.getName()));
	     System.err.println(msapperAttrs);
		// 扫描 Mapper 注解指定包下面所有的类
		Set<Class<?>> classes = scanf( msapperAttrs.getString("value"));
		System.err.println("扫描到:" + classes);
        // 将类都注册到spring容器中
		for (Class<?> cls : classes) {
			BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(cls);
			// 向spring容器中注册bean
			registry.registerBeanDefinition(cls.getName(), builder.getBeanDefinition());
		}

	}

	// 扫描包下面所有的类
	private Set<Class<?>> scanf(String pack) {
		Set<Class<?>> results = new HashSet<>();
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		// 加載資源 classpath*:com/hadluo/**/*.class : 找环境变量下的 com/hadluo下的 所有.class文件
		Resource[] resources;
		pack = pack.replaceAll("\\.", "/");
		try {
			resources = resolver.getResources("classpath*:" + pack + "/**/*.class");
			for (Resource res : resources) {
				// 先获取resource的元信息,然后获取class元信息,最后得到 class 全路径
				String clsName = new SimpleMetadataReaderFactory().getMetadataReader(res).getClassMetadata()
						.getClassName();
				results.add(Class.forName(clsName));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return results;

	}
}

几个信息点:

  • 实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,spring初始化的时候会回调这个方法,用来提供钩子注册bean。
  • 通过AnnotationMetadata 可以拿到Mapper注解配置的值。
  • 通过 BeanDefinitionRegistry 可以用来动态注册bean到spring容器。
  • 通过PathMatchingResourcePatternResolver 可以扫描包下面的所有 class。

springboot启动类使用:

@SpringBootApplication
@Mapper("com.hadluo.order.mapper")
public class OrderApplication {
	public static void main(String[] args) {
		SpringApplication.run(OrderApplication.class, args);
	}
}

指定了 扫描路径为 : com.hadluo.order.mapper , 这个类下面我们有一个类:

public class TestMapper {
	public TestMapper() {
		System.err.println("TestMapper 实例化");
	}
}

启动程序输出:

发现 TestMapper 的构造函数执行了,说明已经被spring实例化注入了。


实例三. 实现ImportSelector

这个跟上面的差不多,只是selectImports 有返回值, 返回的class 的 name都会被spring注册。

public class MapperRegistrar implements ImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// 拿到Mapper接口 的信息
		AnnotationAttributes msapperAttrs = AnnotationAttributes
				.fromMap(importingClassMetadata.getAnnotationAttributes(Mapper.class.getName()));
		System.err.println(msapperAttrs);
		// 扫描 Mapper 注解指定包下面所有的类
		Set<String> classes = scanf(msapperAttrs.getString("value"));
		System.err.println("扫描到:" + classes);
		// 返回的 name 都会被注册到spring中
		return classes.toArray(new String[] {});
	}

	// 扫描包下面所有的类
	private Set<String> scanf(String pack) {
		Set<String> results = new HashSet<>();
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		// 加載資源 classpath*:com/hadluo/**/*.class : 找环境变量下的 com/hadluo下的 所有.class文件
		Resource[] resources;
		pack = pack.replaceAll("\\.", "/");
		try {
			resources = resolver.getResources("classpath*:" + pack + "/**/*.class");
			for (Resource res : resources) {
				// 先获取resource的元信息,然后获取class元信息,最后得到 class 全路径
				String clsName = new SimpleMetadataReaderFactory().getMetadataReader(res).getClassMetadata()
						.getClassName();
				// 通过名称加载
				results.add(Class.forName(clsName).getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return results;
	}
}


总结

有了@Import 注解, 我们可以把 基于注解开发原则 玩的走火入魔。像Mybatisplus插件就是通过这种方式指定了一个@MapperScan注解 就可以扫描到你写的mapper接口,然后通过jdk动态代理生成代理实现,然后注入到spring。


推荐一个Java架构师博客,带你一起写架构:

JAVA架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者