Spring自定义TypeFilter
1. FilterType枚举
public enum FilterType {
/**
* Filter candidates marked with a given annotation.
* @see org.springframework.core.type.filter.AnnotationTypeFilter
*/
ANNOTATION,
/**
* Filter candidates assignable to a given type.
* @see org.springframework.core.type.filter.AssignableTypeFilter
*/
ASSIGNABLE_TYPE,
/**
* Filter candidates matching a given AspectJ type pattern expression.
* @see org.springframework.core.type.filter.AspectJTypeFilter
*/
ASPECTJ,
/**
* Filter candidates matching a given regex pattern.
* @see org.springframework.core.type.filter.RegexPatternTypeFilter
*/
REGEX,
/** Filter candidates using a given custom
* {@link org.springframework.core.type.filter.TypeFilter} implementation.
*/
CUSTOM
}
2.TypeFilter
@FunctionalInterface
public interface TypeFilter {
/**
* Determine whether this filter matches for the class described by
* the given metadata.
* @param metadataReader the metadata reader for the target class
* @param metadataReaderFactory a factory for obtaining metadata readers
* for other classes (such as superclasses and interfaces)
* @return whether this filter matches
* @throws IOException in case of I/O failure when reading metadata
*/
/**
* 此方法返回一个boolean类型的值。
* 当返回true时,表示加入到spring的容器中。返回false时,不加入容器。
* 参数metadataReader:表示读取到的当前正在扫描的类的信息
* 参数metadataReaderFactory:表示可以获得到其他任何类的信息
*/
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}
3.自定义过滤
场景:
在实际开发中,有很多下面这种业务场景:一个业务需求根据环境的不同可能会有很多种实现。针对不同
的环境,要加载不同的实现。我们看下面这个案例:
我们现在是一个汽车销售集团,在成立之初,只是在北京销售汽车,我们的项目研发完成后只在北京部署
上线。但随着公司的业务发展,现在全国各地均有销售大区,总部设在北京。各大区有独立的项目部署,但是
每个大区的业绩计算和绩效提成的计算方式并不相同。
例如:
在华北区销售一台豪华级轿车绩效算5,提成销售额1%,销售豪华级SUV绩效算3,提成是0.5%。
在西南区销售一台豪华级轿车绩效算3,提成销售额0.5%,销售豪华级SUV绩效算5,提成是1.5%。
这时,我们如果针对不同大区对项目源码进行删减替换,会带来很多不必要的麻烦。而如果加入一些
if/else的判断,显然过于简单粗暴。此时应该考虑采用桥接设计模式,把将涉及到区域性差异的模块功能单
独抽取到代表区域功能的接口中。针对不同区域进行实现。并且在扫描组件注册到容器中时,采用哪个区域的
具体实现,应该采用配置文件配置起来。而自定义TypeFilter就可以实现注册指定区域的组件到容器中。
代码实现
/**
* @author WGR
* @create 2020/9/15 -- 13:23
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface District {
/**
* 指定区域的名称
* @return
*/
String value() default "";
}
接口:
/**
* @author WGR
* @create 2020/9/15 -- 13:26
*/
public interface DistrictPercentage {
/**
* 不同车型的提成
* @param carType
*/
void salePercentage(String carType);
}
/**
* @author WGR
* @create 2020/9/15 -- 13:27
*/
public interface DistrictPerformance {
/**
* 计算绩效
* @param carType
*/
void calcPerformance(String carType);
}
实现:
/**
* @author WGR
* @create 2020/9/15 -- 13:28
*/
@Component("districtPercentage")
@District("north")
public class NorthDistrictPercentage implements DistrictPercentage {
@Override
public void salePercentage(String carType) {
if ("SUV".equalsIgnoreCase(carType)) {
System.out.println("华北区" + carType + "提成1%");
} else if ("car".equalsIgnoreCase(carType)) {
System.out.println("华北区" + carType + "提成0.5%");
}
}
}
/**
* @author WGR
* @create 2020/9/15 -- 13:36
*/
@Component("districtPerformance")
@District("north")
public class NorthDistrictPerformance implements DistrictPerformance {
@Override
public void calcPerformance(String carType) {
if ("SUV".equalsIgnoreCase(carType)) {
System.out.println("华北区" + carType + "绩效3");
} else if ("car".equalsIgnoreCase(carType)) {
System.out.println("华北区" + carType + "绩效5");
}
}
}
/**
* @author WGR
* @create 2020/9/15 -- 13:34
*/
@Component("districtPercentage")
@District("southwest")
public class SouthwestDistrictPercentage implements DistrictPercentage {
@Override
public void salePercentage(String carType) {
if ("SUV".equalsIgnoreCase(carType)) {
System.out.println("区" + carType + "提成1.5%");
} else if ("car".equalsIgnoreCase(carType)) {
System.out.println("华北区" + carType + "提成0.5%");
}
}
}
/**
* @author WGR
* @create 2020/9/15 -- 13:37
*/
@Component("districtPerformance")
@District("southwest")
public class SouthwestDistrictPerformance implements DistrictPerformance {
@Override
public void calcPerformance(String carType) {
if ("SUV".equalsIgnoreCase(carType)) {
System.out.println("西南区" + carType + "绩效5");
} else if ("car".equalsIgnoreCase(carType)) {
System.out.println("西南区" + carType + "绩效3");
}
}
}
过滤类:
/**
* @author WGR
* @create 2020/9/15 -- 13:44
*/
public class DistrictTypeFilter extends AbstractTypeHierarchyTraversingFilter {
protected DistrictTypeFilter(boolean considerInherited, boolean considerInterfaces) {
super(considerInherited, considerInterfaces);
}
//定义路径校验类对象
private PathMatcher pathMatcher;
//注意:使用@Value注解的方式是获取不到配置值的。
//因为Spring的生命周期里,负责填充属性值的InstantiationAwareBeanPostProcessor 与TypeFilter的实例化过程压根搭不上边。
// @Value("${common.district.name}")
private String districtName;
/**
* 默认构造函数
*/
public DistrictTypeFilter() {
//1.第一个参数:不考虑基类。2.第二个参数:不考虑接口上的信息
super(false, false);
//借助Spring默认的Resource通配符路径方式
pathMatcher = new AntPathMatcher();
//硬编码读取配置信息
try {
Properties loadAllProperties =
PropertiesLoaderUtils.loadAllProperties("district.properties");
districtName =
loadAllProperties.getProperty("common.district.name");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//注意本类将注册为Exclude, 返回true代表拒绝
@Override
protected boolean matchClassName(String className) {
try {
if (!isPotentialPackageClass(className)) {
return false;
}
// 判断当前区域是否和所配置的区域一致, 不一致则阻止载入Spring容器
Class<?> clazz = ClassUtils.forName(className,
DistrictTypeFilter.class.getClassLoader());
District districtAnnotation = clazz.getAnnotation(District.class);
if (null == districtAnnotation) {
return false;
}
final String districtValue = districtAnnotation.value();
return (!districtName.equalsIgnoreCase(districtValue));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 潜在的满足条件的类的类名, 指定package下
private static final String PATTERN_STANDARD = ClassUtils.convertClassNameToResourcePath("com.dalianpai.spring5.typefilter.*");
// 本类逻辑中可以处理的类 -- 指定package下的才会进行逻辑判断,
private boolean isPotentialPackageClass(String className) {
// 将类名转换为资源路径, 以进行匹配测试
final String path = ClassUtils.convertClassNameToResourcePath(className);
System.out.println(path);
System.out.println(PATTERN_STANDARD);
return pathMatcher.match(PATTERN_STANDARD, path);
}
}
测试类:
/**
* @author WGR
* @create 2020/9/15 -- 13:51
*/
public class SpringAnnotationTypeFilterTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
DistrictPerformance districtPerformance = ac.getBean("districtPerformance", DistrictPerformance.class);
districtPerformance.calcPerformance("SUV");
DistrictPercentage districtPercentage = ac.getBean("districtPercentage", DistrictPercentage.class);
districtPercentage.salePercentage("car");
}
}