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");
}
}