Skip to content

Commit 756531a

Browse files
committed
support exclude filter on mapper scan with xml
1 parent f449b4c commit 756531a

12 files changed

+646
-44
lines changed

src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
package org.mybatis.spring.config;
1717

1818
import java.lang.annotation.Annotation;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.regex.Pattern;
1922

2023
import org.mybatis.spring.mapper.ClassPathMapperScanner;
2124
import org.mybatis.spring.mapper.MapperFactoryBean;
@@ -28,9 +31,17 @@
2831
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
2932
import org.springframework.beans.factory.xml.ParserContext;
3033
import org.springframework.beans.factory.xml.XmlReaderContext;
34+
import org.springframework.core.type.filter.AnnotationTypeFilter;
35+
import org.springframework.core.type.filter.AspectJTypeFilter;
36+
import org.springframework.core.type.filter.AssignableTypeFilter;
37+
import org.springframework.core.type.filter.RegexPatternTypeFilter;
38+
import org.springframework.core.type.filter.TypeFilter;
39+
import org.springframework.lang.Nullable;
3140
import org.springframework.util.ClassUtils;
3241
import org.springframework.util.StringUtils;
3342
import org.w3c.dom.Element;
43+
import org.w3c.dom.Node;
44+
import org.w3c.dom.NodeList;
3445

3546
/**
3647
* A {#code BeanDefinitionParser} that handles the element scan of the MyBatis. namespace
@@ -57,6 +68,7 @@ public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionPar
5768
private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization";
5869
private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope";
5970
private static final String ATTRIBUTE_PROCESS_PROPERTY_PLACEHOLDERS = "process-property-placeholders";
71+
private static final String ATTRIBUTE_EXCLUDE_FILTER = "exclude-filter";
6072

6173
/**
6274
* {@inheritDoc}
@@ -98,6 +110,12 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa
98110
.loadClass(mapperFactoryBeanClassName);
99111
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
100112
}
113+
114+
// parse exclude-filter
115+
List<TypeFilter> typeFilters = parseTypeFilters(element, parserContext, classLoader);
116+
if (!typeFilters.isEmpty()) {
117+
builder.addPropertyValue("excludeFilters", typeFilters);
118+
}
101119
} catch (Exception ex) {
102120
XmlReaderContext readerContext = parserContext.getReaderContext();
103121
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
@@ -115,6 +133,62 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa
115133
return builder.getBeanDefinition();
116134
}
117135

136+
private List<TypeFilter> parseTypeFilters(Element element, ParserContext parserContext, ClassLoader classLoader) {
137+
// Parse exclude filter elements.
138+
List<TypeFilter> typeFilters = new ArrayList<>();
139+
NodeList nodeList = element.getChildNodes();
140+
for (int i = 0; i < nodeList.getLength(); i++) {
141+
Node node = nodeList.item(i);
142+
if (Node.ELEMENT_NODE == node.getNodeType()) {
143+
String localName = parserContext.getDelegate().getLocalName(node);
144+
try {
145+
if (ATTRIBUTE_EXCLUDE_FILTER.equals(localName)) {
146+
TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
147+
typeFilters.add(typeFilter);
148+
}
149+
} catch (ClassNotFoundException ex) {
150+
parserContext.getReaderContext().warning("Ignoring non-present type filter class: " + ex,
151+
parserContext.extractSource(element));
152+
} catch (Exception ex) {
153+
parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
154+
}
155+
}
156+
}
157+
return typeFilters;
158+
}
159+
160+
@SuppressWarnings("unchecked")
161+
private TypeFilter createTypeFilter(Element element, @Nullable ClassLoader classLoader, ParserContext parserContext)
162+
throws ClassNotFoundException {
163+
String filterType = element.getAttribute("type");
164+
String expression = element.getAttribute("expression");
165+
expression = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(expression);
166+
switch (filterType) {
167+
case "annotation":
168+
Class<?> filterAnno = ClassUtils.forName(expression, classLoader);
169+
if(!Annotation.class.isAssignableFrom(filterAnno)){
170+
throw new IllegalArgumentException(
171+
"Class is not assignable to [" + Annotation.class.getName() + "]: " + expression);
172+
}
173+
return new AnnotationTypeFilter((Class<Annotation>) filterAnno);
174+
case "custom":
175+
Class<?> filterClass = ClassUtils.forName(expression, classLoader);
176+
if (!TypeFilter.class.isAssignableFrom(filterClass)) {
177+
throw new IllegalArgumentException(
178+
"Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
179+
}
180+
return (TypeFilter) BeanUtils.instantiateClass(filterClass);
181+
case "assignable":
182+
return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
183+
case "regex":
184+
return new RegexPatternTypeFilter(Pattern.compile(expression));
185+
case "aspectj":
186+
return new AspectJTypeFilter(expression, classLoader);
187+
default:
188+
throw new IllegalArgumentException("Unsupported filter type: " + filterType);
189+
}
190+
}
191+
118192
/**
119193
* {@inheritDoc}
120194
*

src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@
3333
</xsd:documentation>
3434
</xsd:annotation>
3535
<xsd:complexType>
36+
<xsd:sequence>
37+
<xsd:element name="exclude-filter" type="filterType"
38+
minOccurs="0" maxOccurs="unbounded">
39+
<xsd:annotation>
40+
<xsd:documentation><![CDATA[
41+
Controls which eligible types to exclude for component scanning.
42+
]]></xsd:documentation>
43+
</xsd:annotation>
44+
</xsd:element>
45+
</xsd:sequence>
3646
<xsd:attribute name="base-package" type="xsd:string"
3747
use="required">
3848
<xsd:annotation>
@@ -160,4 +170,39 @@
160170
</xsd:attribute>
161171
</xsd:complexType>
162172
</xsd:element>
173+
174+
<xsd:complexType name="filterType">
175+
<xsd:attribute name="type" use="required">
176+
<xsd:annotation>
177+
<xsd:documentation><![CDATA[
178+
Controls the type of filtering to apply to the expression.
179+
180+
"annotation" indicates an annotation to be present or meta-present at the type level in target components;
181+
"assignable" indicates a class (or interface) that the target components are assignable to (extend/implement);
182+
"aspectj" indicates an AspectJ type pattern expression to be matched by the target components;
183+
"regex" indicates a regex pattern to be matched by the target components' class names;
184+
"custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface.
185+
186+
Note: This attribute will not be inherited by child bean definitions.
187+
Hence, it needs to be specified per concrete bean definition.
188+
]]></xsd:documentation>
189+
</xsd:annotation>
190+
<xsd:simpleType>
191+
<xsd:restriction base="xsd:string">
192+
<xsd:enumeration value="annotation"/>
193+
<xsd:enumeration value="assignable"/>
194+
<xsd:enumeration value="aspectj"/>
195+
<xsd:enumeration value="regex"/>
196+
<xsd:enumeration value="custom"/>
197+
</xsd:restriction>
198+
</xsd:simpleType>
199+
</xsd:attribute>
200+
<xsd:attribute name="expression" type="xsd:string" use="required">
201+
<xsd:annotation>
202+
<xsd:documentation><![CDATA[
203+
Indicates the filter expression, the type of which is indicated by "type".
204+
]]></xsd:documentation>
205+
</xsd:annotation>
206+
</xsd:attribute>
207+
</xsd:complexType>
163208
</xsd:schema>

src/test/java/org/mybatis/spring/filter/ScanFilterTest.java

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,73 +15,73 @@
1515
*/
1616
package org.mybatis.spring.filter;
1717

18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.junit.jupiter.api.Assertions.assertThrows;
20+
1821
import com.mockrunner.mock.jdbc.MockDataSource;
22+
1923
import org.junit.jupiter.api.Test;
2024
import org.mybatis.spring.SqlSessionFactoryBean;
2125
import org.mybatis.spring.filter.config.AppConfig;
2226
import org.springframework.beans.factory.support.GenericBeanDefinition;
2327
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2428

25-
import static org.assertj.core.api.Assertions.assertThat;
26-
import static org.junit.jupiter.api.Assertions.assertThrows;
27-
2829
/**
2930
* test the function of excludeFilters in @MapperScan
3031
*/
3132
public class ScanFilterTest {
3233

3334
private AnnotationConfigApplicationContext applicationContext;
3435

35-
3636
@Test
3737
void testCustomScanFilter() {
3838
startContext(AppConfig.CustomFilterConfig.class);
39-
// use org.mybatis.spring.scan.filter.datasource as basePackages and exclude package datasource2 by MapperScan.excludeFilters
39+
// use org.mybatis.spring.scan.filter.datasource as basePackages and exclude package datasource2 by
40+
// MapperScan.excludeFilters
4041
// mapper in package datasource2 will not be registered to beanFactory
41-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(false);
42+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isFalse();
4243

4344
// mapper in package datasource except datasource2 will be registered to beanFactory correctly.
44-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
45-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(true);
45+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
46+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isTrue();
4647
}
4748

4849
@Test
4950
void testAnnoScanFilter() {
5051
startContext(AppConfig.AnnoFilterConfig.class);
5152

5253
// use @AnnoTypeFilter to exclude mapper
53-
assertThat(applicationContext.containsBean("annoExcludeMapper")).isEqualTo(false);
54+
assertThat(applicationContext.containsBean("annoExcludeMapper")).isFalse();
5455

5556
// mapper in package datasource except datasource2 will be registered to beanFactory correctly.
56-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
57-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(true);
58-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(true);
57+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
58+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isTrue();
59+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isTrue();
5960
}
6061

61-
6262
@Test
6363
void testAssignableScanFilter() {
6464
startContext(AppConfig.AssignableFilterConfig.class);
6565

6666
// exclude AssignableMapper by AssignableFilter
67-
assertThat(applicationContext.containsBean("assignableMapper")).isEqualTo(false);
67+
assertThat(applicationContext.containsBean("assignableMapper")).isFalse();
6868

6969
// mapper in package datasource except datasource2 will be registered to beanFactory correctly.
70-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
71-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(true);
72-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(true);
70+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
71+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isTrue();
72+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isTrue();
7373
}
7474

7575
@Test
7676
void testRegexScanFilter() {
7777
startContext(AppConfig.RegexFilterConfig.class);
7878

7979
// exclude package datasource1 by Regex
80-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(false);
80+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isFalse();
8181

8282
// mapper in package datasource except datasource1 will be registered to beanFactory correctly.
83-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
84-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(true);
83+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
84+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isTrue();
8585
}
8686

8787
@Test
@@ -90,42 +90,40 @@ void testAspectJScanFilter() {
9090
startContext(AppConfig.AspectJFilterConfig.class);
9191

9292
// exclude dataSource1Mapper by AspectJ
93-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(false);
93+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isFalse();
9494

9595
// mapper in package datasource except datasource1 will be registered to beanFactory correctly.
96-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
97-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(true);
96+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
97+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isTrue();
9898
}
9999

100-
101100
@Test
102101
void combinedScanFilter() {
103102
// combined filter with Custom and Annotation
104103
startContext(AppConfig.CombinedFilterConfig.class);
105104

106105
// exclude datasource2.DataSource2Mapper by CustomTypeFilter
107-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(false);
106+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isFalse();
108107
// exclude datasource1.MapperWithAnnoFilter by AnnoTypeFilter
109-
assertThat(applicationContext.containsBean("mapperWithAnnoFilter")).isEqualTo(false);
108+
assertThat(applicationContext.containsBean("mapperWithAnnoFilter")).isFalse();
110109

111110
// other mapper could be registered to beanFactory correctly.
112-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
113-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(true);
111+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
112+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isTrue();
114113
}
115114

116-
117115
@Test
118116
void multiPatternRegexScanFilter() {
119117
// multi pattern regex filter
120118
startContext(AppConfig.MultiPatternRegexFilterConfig.class);
121119

122120
// exclude datasource1 by pattern[0]
123-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(false);
121+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isFalse();
124122
// exclude datasource2 by pattern[1]
125-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(false);
123+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isFalse();
126124

127125
// other mapper could be registered to beanFactory correctly.
128-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
126+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
129127
}
130128

131129
@Test
@@ -134,36 +132,32 @@ void multiPatternAspectJScanFilter() {
134132
startContext(AppConfig.MultiPatternAspectJFilterConfig.class);
135133

136134
// exclude datasource1 by pattern[0]
137-
assertThat(applicationContext.containsBean("dataSource1Mapper")).isEqualTo(false);
135+
assertThat(applicationContext.containsBean("dataSource1Mapper")).isFalse();
138136
// exclude datasource2 by pattern[1]
139-
assertThat(applicationContext.containsBean("dataSource2Mapper")).isEqualTo(false);
137+
assertThat(applicationContext.containsBean("dataSource2Mapper")).isFalse();
140138

141139
// other mapper could be registered to beanFactory correctly.
142-
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isEqualTo(true);
143-
assertThat(applicationContext.containsBean("dataSource2Mapper1")).isEqualTo(true);
140+
assertThat(applicationContext.containsBean("commonDataSourceMapper")).isTrue();
141+
assertThat(applicationContext.containsBean("dataSource2Mapper1")).isTrue();
144142
}
145143

146-
147144
@Test
148145
void invalidTypeFilter() {
149146
// invalid value using Annotation type filter
150-
assertThrows(IllegalArgumentException.class,
151-
() -> startContext(AppConfig.InvalidFilterTypeConfig.class));
147+
assertThrows(IllegalArgumentException.class, () -> startContext(AppConfig.InvalidFilterTypeConfig.class));
152148
}
153149

154150
@Test
155151
void invalidPropertyPattern() {
156-
assertThrows(IllegalArgumentException.class,
157-
() -> startContext(AppConfig.AnnoTypeWithPatternPropertyConfig.class));
152+
assertThrows(IllegalArgumentException.class, () -> startContext(AppConfig.AnnoTypeWithPatternPropertyConfig.class));
158153
}
159154

160155
@Test
161156
void invalidPropertyClasses() {
162157
assertThrows(IllegalArgumentException.class,
163-
() -> startContext(AppConfig.RegexTypeWithClassesPropertyConfig.class));
158+
() -> startContext(AppConfig.RegexTypeWithClassesPropertyConfig.class));
164159
}
165160

166-
167161
private void startContext(Class<?> config) {
168162
applicationContext = new AnnotationConfigApplicationContext();
169163
// use @MapperScan with excludeFilters in AppConfig.class
@@ -173,7 +167,6 @@ private void startContext(Class<?> config) {
173167
applicationContext.start();
174168
}
175169

176-
177170
private void setupSqlSessionFactory(String name) {
178171
GenericBeanDefinition definition = new GenericBeanDefinition();
179172
definition.setBeanClass(SqlSessionFactoryBean.class);

0 commit comments

Comments
 (0)