001/*
002 * Copyright 2012-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 *      http://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.boot.web.reactive.context;
018
019import java.util.Arrays;
020import java.util.LinkedHashSet;
021import java.util.Set;
022
023import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
024import org.springframework.beans.factory.support.BeanNameGenerator;
025import org.springframework.beans.factory.support.DefaultListableBeanFactory;
026import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
027import org.springframework.context.annotation.AnnotationConfigApplicationContext;
028import org.springframework.context.annotation.AnnotationConfigRegistry;
029import org.springframework.context.annotation.AnnotationConfigUtils;
030import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
031import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
032import org.springframework.context.annotation.ScopeMetadataResolver;
033import org.springframework.core.env.ConfigurableEnvironment;
034import org.springframework.stereotype.Component;
035import org.springframework.util.Assert;
036import org.springframework.util.ClassUtils;
037import org.springframework.util.ObjectUtils;
038
039/**
040 * {@link ReactiveWebServerApplicationContext} that accepts annotated classes as input -
041 * in particular
042 * {@link org.springframework.context.annotation.Configuration @Configuration}-annotated
043 * classes, but also plain {@link Component @Component} classes and JSR-330 compliant
044 * classes using {@code javax.inject} annotations. Allows for registering classes one by
045 * one (specifying class names as config location) as well as for classpath scanning
046 * (specifying base packages as config location).
047 * <p>
048 * Note: In case of multiple {@code @Configuration} classes, later {@code @Bean}
049 * definitions will override ones defined in earlier loaded files. This can be leveraged
050 * to deliberately override certain bean definitions via an extra Configuration class.
051 *
052 * @author Phillip Webb
053 * @since 2.0.0
054 * @see #register(Class...)
055 * @see #scan(String...)
056 * @see ReactiveWebServerApplicationContext
057 * @see AnnotationConfigApplicationContext
058 */
059public class AnnotationConfigReactiveWebServerApplicationContext
060                extends ReactiveWebServerApplicationContext implements AnnotationConfigRegistry {
061
062        private final AnnotatedBeanDefinitionReader reader;
063
064        private final ClassPathBeanDefinitionScanner scanner;
065
066        private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();
067
068        private String[] basePackages;
069
070        /**
071         * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext} that needs
072         * to be populated through {@link #register} calls and then manually
073         * {@linkplain #refresh refreshed}.
074         */
075        public AnnotationConfigReactiveWebServerApplicationContext() {
076                this.reader = new AnnotatedBeanDefinitionReader(this);
077                this.scanner = new ClassPathBeanDefinitionScanner(this);
078        }
079
080        /**
081         * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext} with the
082         * given {@code DefaultListableBeanFactory}. The context needs to be populated through
083         * {@link #register} calls and then manually {@linkplain #refresh refreshed}.
084         * @param beanFactory the DefaultListableBeanFactory instance to use for this context
085         */
086        public AnnotationConfigReactiveWebServerApplicationContext(
087                        DefaultListableBeanFactory beanFactory) {
088                super(beanFactory);
089                this.reader = new AnnotatedBeanDefinitionReader(this);
090                this.scanner = new ClassPathBeanDefinitionScanner(this);
091        }
092
093        /**
094         * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext}, deriving
095         * bean definitions from the given annotated classes and automatically refreshing the
096         * context.
097         * @param annotatedClasses one or more annotated classes, e.g. {@code @Configuration}
098         * classes
099         */
100        public AnnotationConfigReactiveWebServerApplicationContext(
101                        Class<?>... annotatedClasses) {
102                this();
103                register(annotatedClasses);
104                refresh();
105        }
106
107        /**
108         * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext}, scanning
109         * for bean definitions in the given packages and automatically refreshing the
110         * context.
111         * @param basePackages the packages to check for annotated classes
112         */
113        public AnnotationConfigReactiveWebServerApplicationContext(String... basePackages) {
114                this();
115                scan(basePackages);
116                refresh();
117        }
118
119        /**
120         * {@inheritDoc}
121         * <p>
122         * Delegates given environment to underlying {@link AnnotatedBeanDefinitionReader} and
123         * {@link ClassPathBeanDefinitionScanner} members.
124         */
125        @Override
126        public void setEnvironment(ConfigurableEnvironment environment) {
127                super.setEnvironment(environment);
128                this.reader.setEnvironment(environment);
129                this.scanner.setEnvironment(environment);
130        }
131
132        /**
133         * Provide a custom {@link BeanNameGenerator} for use with
134         * {@link AnnotatedBeanDefinitionReader} and/or
135         * {@link ClassPathBeanDefinitionScanner}, if any.
136         * <p>
137         * Default is
138         * {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}.
139         * <p>
140         * Any call to this method must occur prior to calls to {@link #register(Class...)}
141         * and/or {@link #scan(String...)}.
142         * @param beanNameGenerator the bean name generator
143         * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator
144         * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator
145         */
146        public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
147                this.reader.setBeanNameGenerator(beanNameGenerator);
148                this.scanner.setBeanNameGenerator(beanNameGenerator);
149                this.getBeanFactory().registerSingleton(
150                                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
151                                beanNameGenerator);
152        }
153
154        /**
155         * Set the {@link ScopeMetadataResolver} to use for detected bean classes.
156         * <p>
157         * The default is an {@link AnnotationScopeMetadataResolver}.
158         * <p>
159         * Any call to this method must occur prior to calls to {@link #register(Class...)}
160         * and/or {@link #scan(String...)}.
161         * @param scopeMetadataResolver the scope metadata resolver
162         */
163        public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
164                this.reader.setScopeMetadataResolver(scopeMetadataResolver);
165                this.scanner.setScopeMetadataResolver(scopeMetadataResolver);
166        }
167
168        /**
169         * Register one or more annotated classes to be processed. Note that
170         * {@link #refresh()} must be called in order for the context to fully process the new
171         * class.
172         * <p>
173         * Calls to {@code #register} are idempotent; adding the same annotated class more
174         * than once has no additional effect.
175         * @param annotatedClasses one or more annotated classes, e.g. {@code @Configuration}
176         * classes
177         * @see #scan(String...)
178         * @see #refresh()
179         */
180        @Override
181        public final void register(Class<?>... annotatedClasses) {
182                Assert.notEmpty(annotatedClasses,
183                                "At least one annotated class must be specified");
184                this.annotatedClasses.addAll(Arrays.asList(annotatedClasses));
185        }
186
187        /**
188         * Perform a scan within the specified base packages. Note that {@link #refresh()}
189         * must be called in order for the context to fully process the new class.
190         * @param basePackages the packages to check for annotated classes
191         * @see #register(Class...)
192         * @see #refresh()
193         */
194        @Override
195        public final void scan(String... basePackages) {
196                Assert.notEmpty(basePackages, "At least one base package must be specified");
197                this.basePackages = basePackages;
198        }
199
200        @Override
201        protected void prepareRefresh() {
202                this.scanner.clearCache();
203                super.prepareRefresh();
204        }
205
206        @Override
207        protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
208                super.postProcessBeanFactory(beanFactory);
209                if (!ObjectUtils.isEmpty(this.basePackages)) {
210                        this.scanner.scan(this.basePackages);
211                }
212                if (!this.annotatedClasses.isEmpty()) {
213                        this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
214                }
215        }
216
217}