Java研究院 Java研究院

人非生而知之者,知之,而后知不知。

目录
Spring扫描组件完成自定注入的问题
/    

Spring扫描组件完成自定注入的问题

今天又被小老弟坑了一把,就因为一个Spring自动注入的问题。

一般面试的时候,IOC会是面试的重灾区,但是IOC问的是Spring如果实现对Bean的管理,但是哪些Bean会被Spring自动注入?

组件

首先需要明确一点,Spring自动注入有三种方式,但对于需要注入的Bean,主要是两种扫描模式,一种是组件,一种是指定包下的类。

组件有哪些?

首先,提到组件的时候,一定要知道注解 @Component ,除此之外,还有常用的 @Controller、@Service 等,具体有哪些,可以参照下图,但不仅限于此。

image.png

是不是很熟悉?

实际上说来,只要我们在类上加上了上述的注解,那么该类就是一个可以被 Spring 自动注入的组件。当然,你也可以直接使用 @Component 注解来标识你的类。

如何自定义扫描过滤?

Spring 既然能自动扫描需要注入的组件,当然也支持我们主动包含或者排除某些组件。

小老弟的问题,其实也很简单。他要实现的功能,是排除指定包下的某个 Service。于是,为了测试这个功能,他很鸡贼的自定义了一个 TypeFilter的过滤器,然后将指定包下的类全部过滤。比如这样:

@Slf4j
public class StudyingFilter2 implements TypeFilter {
	@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
		log.error("--------> 排除类:{}", metadataReader.getClassMetadata().getClassName());
		return false;
	}
}

将自定义的ComponentScan配置成这样:

@Configuration
@ComponentScan(value = "com.studying.aopdemo.business.scan.service",useDefaultFilters = false,
		includeFilters= {@ComponentScan.Filter(type = FilterType.CUSTOM,
				classes = StudyingFilter2.class)})
public class StudyingConfig {
}

这样能不能行?答案是肯定的,能行!

但是哪来的坑?

既然上文说行,那肯定是行的,为了不重复赘述,说个结论。

如果在指定包下,定义了一个 MyService,MyService 和你写的一个静态工具类没什么区别,并不会在Spring 容器上下文中被自动注入,因为已经被明确排除了。

但是!但是!但是!

如果你在 MyService 上加了 @Service 注解情况就不一样了!

示例

为了更为直观的演示自定义过滤器,我这里定义三个Service,代码如下,注意各自的区别。

Aservice0,普通类。

public class Aservice0 implements MyService {
	@Override
	public String name() {
		return "A0";
	}
}

Aservice1,同样也是普通类。

public class Aservice1 implements MyService {
	@Override
	public String name() {
		return "A1";
	}
}

Aservice2,加上了@Service 注解。

@Service
public class Aservice2 implements MyService {
	@Override
	public String name() {
		return "A2";
	}
}

然后将 StudyingFilter2 的 match 方法做个调整,具体如下:

@Slf4j
public class StudyingFilter2 implements TypeFilter {

	@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
		if (metadataReader.getClassMetadata().getClassName().endsWith("0")) {
			log.info("++++++++> 加载类:{}", metadataReader.getClassMetadata().getClassName());
			return true;
		} else {
			log.error("--------> 排除类:{}", metadataReader.getClassMetadata().getClassName());
			return false;
		}

	}
}

这里将类名以 0 结尾的类包含在Spring自动注入的组件之内。

为了查看 Spring 容器启动后,哪些类被注入到了容器上下文中,你可以在程序主入口后加上如下代码:

@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
	return args -> {
		String[] beanNames = ctx.getBeanDefinitionNames();
		Arrays.sort(beanNames);
		for (String beanName : beanNames) {
			log.info("====================>{}",beanName);
		}

	};
}

启动容器查看控制台,见证神奇的一幕!

image.png

注意红框框里面,上面的红框里,明确表示Aservice1和Aservice2都不包含在自动注入的组件之内,但是我们可以看到,Aservice0 和 Aservice2 已经在 Spring 容器启动后被自动注入了!

所以,Are you understand ?

更优雅的实现

假设我们的Aservice0 和 Aservice1都实现了MyService接口,但是内部逻辑不同,当分别部署在两台服务器Server0 和 Server1上,需要在不同的服务器上采用不同的实现的时候,其实可以有这样的实现(实际情况中可能不会这样,但是发挥思路去类比吧)。

先定义一个注解 @CustomerFilterType :

@Target( ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomerFilterType {
	boolean need() default false;
}

注意,这里为了方便直接用布尔类型的 need() 方法来演示,时机情况中可以根据配置来实现。

在定义一个 StudyingFilter 来实现过滤方法:

@Slf4j
public final class StudyingFilter extends AbstractTypeHierarchyTraversingFilter {

	private PathMatcher pathMatcher;

	public StudyingFilter() {
		super(false, false);
		pathMatcher = new AntPathMatcher();
	}

	@Override
	protected boolean matchClassName(String className) {
		if (!isPotentialPackageClass(className)) {
			log.info("+++++++++> 加载类:{}", className);
			return true;
		}
		Class<?> clazz = ClassUtil.loadClass(className, false);
		CustomerFilterType filterType = clazz.getAnnotation(CustomerFilterType.class);
		if (null != filterType && !filterType.need()) {
			log.error("--------> 排除类:{}", className);
			return false;
		}
		log.info("+++++++++> 加载类:{}", className);
		return true;
	}

	/**
	 * 潜在的满足条件的类的类名, 指定package下
	 */

	private static final String PATTERN_STANDARD = ClassUtils.convertClassNameToResourcePath("com.studying.aopdemo.business.scan.service.*");

	/**
	 * 本类逻辑中可以处理的类 -- 指定package下的才会进行逻辑判断,
	 *
	 * @param className
	 * @return boolean
	 */
	private boolean isPotentialPackageClass(String className) {
		// 将类名转换为资源路径, 以进行匹配测试
		final String path = ClassUtils.convertClassNameToResourcePath(className);
		return pathMatcher.match(PATTERN_STANDARD, path);
	}
}

和 StudyingFilter2 的不同在于,不仅指定了具体需要扫描的包,还对包下的类是否加了对应注解的判断,而且扫描路径定义在了过滤其中而不是注解内,这样更加灵活多变。

这种情况下,当我们使用 @Autowired private MyService 时,定义了 @CustomerFilterType(need = true) 的具体 Service 才是最终调用的 Service。

赶紧动起你勤快的小手撸代码吧!


才疏学浅,难免有坑,有坑也不填
标题:Spring扫描组件完成自定注入的问题
地址:https://studying.icu/articles/2021/10/08/1633678949490.html