001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.context.annotation;
018
019import java.lang.annotation.Annotation;
020import java.util.Set;
021import java.util.regex.Pattern;
022
023import org.w3c.dom.Element;
024import org.w3c.dom.Node;
025import org.w3c.dom.NodeList;
026
027import org.springframework.beans.BeanUtils;
028import org.springframework.beans.factory.config.BeanDefinition;
029import org.springframework.beans.factory.config.BeanDefinitionHolder;
030import org.springframework.beans.factory.parsing.BeanComponentDefinition;
031import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
032import org.springframework.beans.factory.support.BeanNameGenerator;
033import org.springframework.beans.factory.xml.BeanDefinitionParser;
034import org.springframework.beans.factory.xml.ParserContext;
035import org.springframework.beans.factory.xml.XmlReaderContext;
036import org.springframework.context.ConfigurableApplicationContext;
037import org.springframework.core.type.filter.AnnotationTypeFilter;
038import org.springframework.core.type.filter.AspectJTypeFilter;
039import org.springframework.core.type.filter.AssignableTypeFilter;
040import org.springframework.core.type.filter.RegexPatternTypeFilter;
041import org.springframework.core.type.filter.TypeFilter;
042import org.springframework.lang.Nullable;
043import org.springframework.util.ClassUtils;
044import org.springframework.util.ReflectionUtils;
045import org.springframework.util.StringUtils;
046
047/**
048 * Parser for the {@code <context:component-scan/>} element.
049 *
050 * @author Mark Fisher
051 * @author Ramnivas Laddad
052 * @author Juergen Hoeller
053 * @since 2.5
054 */
055public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
056
057        private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
058
059        private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";
060
061        private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";
062
063        private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
064
065        private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
066
067        private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
068
069        private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";
070
071        private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";
072
073        private static final String INCLUDE_FILTER_ELEMENT = "include-filter";
074
075        private static final String FILTER_TYPE_ATTRIBUTE = "type";
076
077        private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
078
079
080        @Override
081        @Nullable
082        public BeanDefinition parse(Element element, ParserContext parserContext) {
083                String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
084                basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
085                String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
086                                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
087
088                // Actually scan for bean definitions and register them.
089                ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
090                Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
091                registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
092
093                return null;
094        }
095
096        protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
097                boolean useDefaultFilters = true;
098                if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
099                        useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
100                }
101
102                // Delegate bean definition registration to scanner class.
103                ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
104                scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
105                scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
106
107                if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
108                        scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
109                }
110
111                try {
112                        parseBeanNameGenerator(element, scanner);
113                }
114                catch (Exception ex) {
115                        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
116                }
117
118                try {
119                        parseScope(element, scanner);
120                }
121                catch (Exception ex) {
122                        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
123                }
124
125                parseTypeFilters(element, scanner, parserContext);
126
127                return scanner;
128        }
129
130        protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
131                return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters,
132                                readerContext.getEnvironment(), readerContext.getResourceLoader());
133        }
134
135        protected void registerComponents(
136                        XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
137
138                Object source = readerContext.extractSource(element);
139                CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
140
141                for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
142                        compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
143                }
144
145                // Register annotation config processors, if necessary.
146                boolean annotationConfig = true;
147                if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
148                        annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
149                }
150                if (annotationConfig) {
151                        Set<BeanDefinitionHolder> processorDefinitions =
152                                        AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
153                        for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
154                                compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
155                        }
156                }
157
158                readerContext.fireComponentRegistered(compositeDef);
159        }
160
161        protected void parseBeanNameGenerator(Element element, ClassPathBeanDefinitionScanner scanner) {
162                if (element.hasAttribute(NAME_GENERATOR_ATTRIBUTE)) {
163                        BeanNameGenerator beanNameGenerator = (BeanNameGenerator) instantiateUserDefinedStrategy(
164                                        element.getAttribute(NAME_GENERATOR_ATTRIBUTE), BeanNameGenerator.class,
165                                        scanner.getResourceLoader().getClassLoader());
166                        scanner.setBeanNameGenerator(beanNameGenerator);
167                }
168        }
169
170        protected void parseScope(Element element, ClassPathBeanDefinitionScanner scanner) {
171                // Register ScopeMetadataResolver if class name provided.
172                if (element.hasAttribute(SCOPE_RESOLVER_ATTRIBUTE)) {
173                        if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
174                                throw new IllegalArgumentException(
175                                                "Cannot define both 'scope-resolver' and 'scoped-proxy' on <component-scan> tag");
176                        }
177                        ScopeMetadataResolver scopeMetadataResolver = (ScopeMetadataResolver) instantiateUserDefinedStrategy(
178                                        element.getAttribute(SCOPE_RESOLVER_ATTRIBUTE), ScopeMetadataResolver.class,
179                                        scanner.getResourceLoader().getClassLoader());
180                        scanner.setScopeMetadataResolver(scopeMetadataResolver);
181                }
182
183                if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
184                        String mode = element.getAttribute(SCOPED_PROXY_ATTRIBUTE);
185                        if ("targetClass".equals(mode)) {
186                                scanner.setScopedProxyMode(ScopedProxyMode.TARGET_CLASS);
187                        }
188                        else if ("interfaces".equals(mode)) {
189                                scanner.setScopedProxyMode(ScopedProxyMode.INTERFACES);
190                        }
191                        else if ("no".equals(mode)) {
192                                scanner.setScopedProxyMode(ScopedProxyMode.NO);
193                        }
194                        else {
195                                throw new IllegalArgumentException("scoped-proxy only supports 'no', 'interfaces' and 'targetClass'");
196                        }
197                }
198        }
199
200        protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
201                // Parse exclude and include filter elements.
202                ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
203                NodeList nodeList = element.getChildNodes();
204                for (int i = 0; i < nodeList.getLength(); i++) {
205                        Node node = nodeList.item(i);
206                        if (node.getNodeType() == Node.ELEMENT_NODE) {
207                                String localName = parserContext.getDelegate().getLocalName(node);
208                                try {
209                                        if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
210                                                TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
211                                                scanner.addIncludeFilter(typeFilter);
212                                        }
213                                        else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
214                                                TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
215                                                scanner.addExcludeFilter(typeFilter);
216                                        }
217                                }
218                                catch (ClassNotFoundException ex) {
219                                        parserContext.getReaderContext().warning(
220                                                        "Ignoring non-present type filter class: " + ex, parserContext.extractSource(element));
221                                }
222                                catch (Exception ex) {
223                                        parserContext.getReaderContext().error(
224                                                        ex.getMessage(), parserContext.extractSource(element), ex.getCause());
225                                }
226                        }
227                }
228        }
229
230        @SuppressWarnings("unchecked")
231        protected TypeFilter createTypeFilter(Element element, @Nullable ClassLoader classLoader,
232                        ParserContext parserContext) throws ClassNotFoundException {
233
234                String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
235                String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
236                expression = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(expression);
237                if ("annotation".equals(filterType)) {
238                        return new AnnotationTypeFilter((Class<Annotation>) ClassUtils.forName(expression, classLoader));
239                }
240                else if ("assignable".equals(filterType)) {
241                        return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
242                }
243                else if ("aspectj".equals(filterType)) {
244                        return new AspectJTypeFilter(expression, classLoader);
245                }
246                else if ("regex".equals(filterType)) {
247                        return new RegexPatternTypeFilter(Pattern.compile(expression));
248                }
249                else if ("custom".equals(filterType)) {
250                        Class<?> filterClass = ClassUtils.forName(expression, classLoader);
251                        if (!TypeFilter.class.isAssignableFrom(filterClass)) {
252                                throw new IllegalArgumentException(
253                                                "Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
254                        }
255                        return (TypeFilter) BeanUtils.instantiateClass(filterClass);
256                }
257                else {
258                        throw new IllegalArgumentException("Unsupported filter type: " + filterType);
259                }
260        }
261
262        @SuppressWarnings("unchecked")
263        private Object instantiateUserDefinedStrategy(
264                        String className, Class<?> strategyType, @Nullable ClassLoader classLoader) {
265
266                Object result;
267                try {
268                        result = ReflectionUtils.accessibleConstructor(ClassUtils.forName(className, classLoader)).newInstance();
269                }
270                catch (ClassNotFoundException ex) {
271                        throw new IllegalArgumentException("Class [" + className + "] for strategy [" +
272                                        strategyType.getName() + "] not found", ex);
273                }
274                catch (Throwable ex) {
275                        throw new IllegalArgumentException("Unable to instantiate class [" + className + "] for strategy [" +
276                                        strategyType.getName() + "]: a zero-argument constructor is required", ex);
277                }
278
279                if (!strategyType.isAssignableFrom(result.getClass())) {
280                        throw new IllegalArgumentException("Provided class name must be an implementation of " + strategyType);
281                }
282                return result;
283        }
284
285}