View Javadoc
1   /*
2    * Copyright 2010-2024 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.mybatis.spring.config;
17  
18  import java.lang.annotation.Annotation;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.mybatis.spring.mapper.ClassPathMapperScanner;
25  import org.mybatis.spring.mapper.MapperFactoryBean;
26  import org.mybatis.spring.mapper.MapperScannerConfigurer;
27  import org.springframework.beans.BeanUtils;
28  import org.springframework.beans.factory.config.BeanDefinition;
29  import org.springframework.beans.factory.support.AbstractBeanDefinition;
30  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
31  import org.springframework.beans.factory.support.BeanNameGenerator;
32  import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
33  import org.springframework.beans.factory.xml.ParserContext;
34  import org.springframework.util.ClassUtils;
35  import org.springframework.util.StringUtils;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.Node;
38  
39  /**
40   * A {#code BeanDefinitionParser} that handles the element scan of the MyBatis. namespace
41   *
42   * @author Lishu Luo
43   * @author Eduardo Macarron
44   *
45   * @since 1.2.0
46   *
47   * @see MapperFactoryBean
48   * @see ClassPathMapperScanner
49   * @see MapperScannerConfigurer
50   */
51  public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionParser {
52  
53    private static final String ATTRIBUTE_BASE_PACKAGE = "base-package";
54    private static final String ATTRIBUTE_ANNOTATION = "annotation";
55    private static final String ATTRIBUTE_MARKER_INTERFACE = "marker-interface";
56    private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator";
57    private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref";
58    private static final String ATTRIBUTE_FACTORY_REF = "factory-ref";
59    private static final String ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS = "mapper-factory-bean-class";
60    private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization";
61    private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope";
62    private static final String ATTRIBUTE_PROCESS_PROPERTY_PLACEHOLDERS = "process-property-placeholders";
63    private static final String ATTRIBUTE_EXCLUDE_FILTER = "exclude-filter";
64  
65    @Override
66    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
67      var builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
68  
69      var classLoader = ClassUtils.getDefaultClassLoader();
70  
71      var processPropertyPlaceHolders = element.getAttribute(ATTRIBUTE_PROCESS_PROPERTY_PLACEHOLDERS);
72      builder.addPropertyValue("processPropertyPlaceHolders",
73          !StringUtils.hasText(processPropertyPlaceHolders) || Boolean.parseBoolean(processPropertyPlaceHolders));
74      try {
75        var annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION);
76        if (StringUtils.hasText(annotationClassName)) {
77          @SuppressWarnings("unchecked")
78          Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) classLoader
79              .loadClass(annotationClassName);
80          builder.addPropertyValue("annotationClass", annotationClass);
81        }
82        var markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE);
83        if (StringUtils.hasText(markerInterfaceClassName)) {
84          Class<?> markerInterface = classLoader.loadClass(markerInterfaceClassName);
85          builder.addPropertyValue("markerInterface", markerInterface);
86        }
87        var nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR);
88        if (StringUtils.hasText(nameGeneratorClassName)) {
89          Class<?> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName);
90          var nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class);
91          builder.addPropertyValue("nameGenerator", nameGenerator);
92        }
93        var mapperFactoryBeanClassName = element.getAttribute(ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS);
94        if (StringUtils.hasText(mapperFactoryBeanClassName)) {
95          @SuppressWarnings("unchecked")
96          Class<? extends MapperFactoryBean> mapperFactoryBeanClass = (Class<? extends MapperFactoryBean>) classLoader
97              .loadClass(mapperFactoryBeanClassName);
98          builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
99        }
100 
101       // parse raw exclude-filter in <mybatis:scan>
102       var rawExcludeFilters = parseScanTypeFilters(element, parserContext);
103       if (!rawExcludeFilters.isEmpty()) {
104         builder.addPropertyValue("rawExcludeFilters", rawExcludeFilters);
105       }
106 
107     } catch (Exception ex) {
108       var readerContext = parserContext.getReaderContext();
109       readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
110     }
111 
112     builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF));
113     builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF));
114     builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION));
115     builder.addPropertyValue("defaultScope", element.getAttribute(ATTRIBUTE_DEFAULT_SCOPE));
116     builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE));
117 
118     // for spring-native
119     builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
120 
121     return builder.getBeanDefinition();
122   }
123 
124   private List<Map<String, String>> parseScanTypeFilters(Element element, ParserContext parserContext) {
125     List<Map<String, String>> typeFilters = new ArrayList<>();
126     var nodeList = element.getChildNodes();
127     for (var i = 0; i < nodeList.getLength(); i++) {
128       var node = nodeList.item(i);
129       if (Node.ELEMENT_NODE == node.getNodeType()) {
130         var localName = parserContext.getDelegate().getLocalName(node);
131         if (ATTRIBUTE_EXCLUDE_FILTER.equals(localName)) {
132           Map<String, String> filter = new HashMap<>(16);
133           filter.put("type", ((Element) node).getAttribute("type"));
134           filter.put("expression", ((Element) node).getAttribute("expression"));
135           typeFilters.add(filter);
136         }
137       }
138     }
139     return typeFilters;
140   }
141 
142   @Override
143   protected boolean shouldGenerateIdAsFallback() {
144     return true;
145   }
146 
147 }