View Javadoc
1   /*
2    * Copyright 2002-2014 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    *      http://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  
17  package org.springframework.test;
18  
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Modifier;
21  import java.util.LinkedList;
22  import java.util.List;
23  
24  import org.springframework.beans.factory.NoSuchBeanDefinitionException;
25  import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
26  import org.springframework.context.ApplicationContext;
27  import org.springframework.util.Assert;
28  
29  /**
30   * This class is only used within tests in the spring-orm module.
31   *
32   * <p>
33   * Convenient superclass for JUnit 3.8 based tests depending on a Spring
34   * context. The test instance itself is populated by Dependency Injection.
35   * </p>
36   * <p>
37   * Really for integration testing, not unit testing. You should <i>not</i>
38   * normally use the Spring container for unit tests: simply populate your POJOs
39   * in plain JUnit tests!
40   * </p>
41   * <p>
42   * This supports two modes of populating the test:
43   * </p>
44   * <ul>
45   * <li>Via Setter Dependency Injection. Simply express dependencies on objects
46   * in the test fixture, and they will be satisfied by autowiring by type.
47   * <li>Via Field Injection. Declare protected variables of the required type
48   * which match named beans in the context. This is autowire by name, rather than
49   * type. This approach is based on an approach originated by Ara Abrahmian.
50   * Setter Dependency Injection is the default: set the
51   * {@code populateProtectedVariables} property to {@code true} in
52   * the constructor to switch on Field Injection.
53   * </ul>
54   *
55   * @author Rod Johnson
56   * @author Rob Harrop
57   * @author Rick Evans
58   * @author Sam Brannen
59   * @since 1.1.1
60   * @see #setDirty
61   * @see #contextKey
62   * @see #getContext
63   * @see #getConfigLocations
64   * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
65   * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests})
66   */
67  @Deprecated
68  public abstract class AbstractDependencyInjectionSpringContextTests extends AbstractSingleSpringContextTests {
69  
70  	/**
71  	 * Constant that indicates no autowiring at all.
72  	 *
73  	 * @see #setAutowireMode
74  	 */
75  	public static final int AUTOWIRE_NO = 0;
76  
77  	/**
78  	 * Constant that indicates autowiring bean properties by name.
79  	 *
80  	 * @see #setAutowireMode
81  	 */
82  	public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
83  
84  	/**
85  	 * Constant that indicates autowiring bean properties by type.
86  	 *
87  	 * @see #setAutowireMode
88  	 */
89  	public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
90  
91  	private boolean populateProtectedVariables = false;
92  
93  	private int autowireMode = AUTOWIRE_BY_TYPE;
94  
95  	private boolean dependencyCheck = true;
96  
97  	private String[] managedVariableNames;
98  
99  
100 	/**
101 	 * Default constructor for AbstractDependencyInjectionSpringContextTests.
102 	 */
103 	public AbstractDependencyInjectionSpringContextTests() {
104 	}
105 
106 	/**
107 	 * Constructor for AbstractDependencyInjectionSpringContextTests with a
108 	 * JUnit name.
109 	 * @param name the name of this text fixture
110 	 */
111 	public AbstractDependencyInjectionSpringContextTests(String name) {
112 		super(name);
113 	}
114 
115 
116 	/**
117 	 * Set whether to populate protected variables of this test case. Default is
118 	 * {@code false}.
119 	 */
120 	public final void setPopulateProtectedVariables(boolean populateFields) {
121 		this.populateProtectedVariables = populateFields;
122 	}
123 
124 	/**
125 	 * Return whether to populate protected variables of this test case.
126 	 */
127 	public final boolean isPopulateProtectedVariables() {
128 		return this.populateProtectedVariables;
129 	}
130 
131 	/**
132 	 * Set the autowire mode for test properties set by Dependency Injection.
133 	 * <p>The default is {@link #AUTOWIRE_BY_TYPE}. Can be set to
134 	 * {@link #AUTOWIRE_BY_NAME} or {@link #AUTOWIRE_NO} instead.
135 	 * @see #AUTOWIRE_BY_TYPE
136 	 * @see #AUTOWIRE_BY_NAME
137 	 * @see #AUTOWIRE_NO
138 	 */
139 	public final void setAutowireMode(final int autowireMode) {
140 		this.autowireMode = autowireMode;
141 	}
142 
143 	/**
144 	 * Return the autowire mode for test properties set by Dependency Injection.
145 	 */
146 	public final int getAutowireMode() {
147 		return this.autowireMode;
148 	}
149 
150 	/**
151 	 * Set whether or not dependency checking should be performed for test
152 	 * properties set by Dependency Injection.
153 	 * <p>The default is {@code true}, meaning that tests cannot be run
154 	 * unless all properties are populated.
155 	 */
156 	public final void setDependencyCheck(final boolean dependencyCheck) {
157 		this.dependencyCheck = dependencyCheck;
158 	}
159 
160 	/**
161 	 * Return whether or not dependency checking should be performed for test
162 	 * properties set by Dependency Injection.
163 	 */
164 	public final boolean isDependencyCheck() {
165 		return this.dependencyCheck;
166 	}
167 
168 	/**
169 	 * Prepare this test instance, injecting dependencies into its protected
170 	 * fields and its bean properties.
171 	 * <p>Note: if the {@link ApplicationContext} for this test instance has not
172 	 * been configured (e.g., is {@code null}), dependency injection
173 	 * will naturally <strong>not</strong> be performed, but an informational
174 	 * message will be written to the log.
175 	 * @see #injectDependencies()
176 	 */
177 	@Override
178 	protected void prepareTestInstance() throws Exception {
179 		if (getApplicationContext() == null) {
180 			if (this.logger.isInfoEnabled()) {
181 				this.logger.info("ApplicationContext has not been configured for test [" + getClass().getName()
182 						+ "]: dependency injection will NOT be performed.");
183 			}
184 		}
185 		else {
186 			injectDependencies();
187 		}
188 	}
189 
190 	/**
191 	 * Inject dependencies into 'this' instance (that is, this test instance).
192 	 * <p>The default implementation populates protected variables if the
193 	 * {@link #populateProtectedVariables() appropriate flag is set}, else uses
194 	 * autowiring if autowiring is switched on (which it is by default).
195 	 * <p>Override this method if you need full control over how dependencies are
196 	 * injected into the test instance.
197 	 * @throws Exception in case of dependency injection failure
198 	 * @throws IllegalStateException if the {@link ApplicationContext} for this
199 	 * test instance has not been configured
200 	 * @see #populateProtectedVariables()
201 	 */
202 	protected void injectDependencies() throws Exception {
203 		Assert.state(getApplicationContext() != null,
204 				"injectDependencies() called without first configuring an ApplicationContext");
205 		if (isPopulateProtectedVariables()) {
206 			if (this.managedVariableNames == null) {
207 				initManagedVariableNames();
208 			}
209 			populateProtectedVariables();
210 		}
211 		getApplicationContext().getBeanFactory().autowireBeanProperties(this, getAutowireMode(), isDependencyCheck());
212 	}
213 
214 	private void initManagedVariableNames() throws IllegalAccessException {
215 		List managedVarNames = new LinkedList();
216 		Class clazz = getClass();
217 		do {
218 			Field[] fields = clazz.getDeclaredFields();
219 			if (this.logger.isDebugEnabled()) {
220 				this.logger.debug("Found " + fields.length + " fields on " + clazz);
221 			}
222 			for (int i = 0; i < fields.length; i++) {
223 				Field field = fields[i];
224 				field.setAccessible(true);
225 				if (this.logger.isDebugEnabled()) {
226 					this.logger.debug("Candidate field: " + field);
227 				}
228 				if (isProtectedInstanceField(field)) {
229 					Object oldValue = field.get(this);
230 					if (oldValue == null) {
231 						managedVarNames.add(field.getName());
232 						if (this.logger.isDebugEnabled()) {
233 							this.logger.debug("Added managed variable '" + field.getName() + "'");
234 						}
235 					}
236 					else {
237 						if (this.logger.isDebugEnabled()) {
238 							this.logger.debug("Rejected managed variable '" + field.getName() + "'");
239 						}
240 					}
241 				}
242 			}
243 			clazz = clazz.getSuperclass();
244 		} while (!clazz.equals(AbstractDependencyInjectionSpringContextTests.class));
245 
246 		this.managedVariableNames = (String[]) managedVarNames.toArray(new String[managedVarNames.size()]);
247 	}
248 
249 	private boolean isProtectedInstanceField(Field field) {
250 		int modifiers = field.getModifiers();
251 		return !Modifier.isStatic(modifiers) && Modifier.isProtected(modifiers);
252 	}
253 
254 	private void populateProtectedVariables() throws IllegalAccessException {
255 		for (int i = 0; i < this.managedVariableNames.length; i++) {
256 			String varName = this.managedVariableNames[i];
257 			Object bean = null;
258 			try {
259 				Field field = findField(getClass(), varName);
260 				bean = getApplicationContext().getBean(varName, field.getType());
261 				field.setAccessible(true);
262 				field.set(this, bean);
263 				if (this.logger.isDebugEnabled()) {
264 					this.logger.debug("Populated field: " + field);
265 				}
266 			}
267 			catch (NoSuchFieldException ex) {
268 				if (this.logger.isWarnEnabled()) {
269 					this.logger.warn("No field with name '" + varName + "'");
270 				}
271 			}
272 			catch (NoSuchBeanDefinitionException ex) {
273 				if (this.logger.isWarnEnabled()) {
274 					this.logger.warn("No bean with name '" + varName + "'");
275 				}
276 			}
277 		}
278 	}
279 
280 	private Field findField(Class clazz, String name) throws NoSuchFieldException {
281 		try {
282 			return clazz.getDeclaredField(name);
283 		}
284 		catch (NoSuchFieldException ex) {
285 			Class superclass = clazz.getSuperclass();
286 			if (superclass != AbstractSpringContextTests.class) {
287 				return findField(superclass, name);
288 			}
289 			else {
290 				throw ex;
291 			}
292 		}
293 	}
294 
295 }