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