AbstractDependencyInjectionSpringContextTests.java
Upload User: jiancairen
Upload Date: 2007-08-27
Package Size: 26458k
Code Size: 8k
Category:

Java Develop

Development Platform:

Java

  1. /*
  2.  * Copyright 2002-2004 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. package org.springframework.test;
  17. import java.lang.reflect.Field;
  18. import java.lang.reflect.Modifier;
  19. import java.util.LinkedList;
  20. import org.springframework.beans.factory.NoSuchBeanDefinitionException;
  21. import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
  22. import org.springframework.context.ConfigurableApplicationContext;
  23. /**
  24.  * Convenient superclass for tests depending on a Spring context.
  25.  * Really for integration testing, not unit testing.
  26.  * You should <i>not</i> normally use the Spring container
  27.  * for unit tests: simply populate your POJOs in plain JUnit tests!
  28.  *
  29.  * <p>This supports two modes of populating the test:
  30.  * <ul>
  31.  * <li>Via Setter Dependency Injection. Simply express dependencies on objects
  32.  * in the test fixture, and they will be satisfied by autowiring by type.
  33.  * <li>Via Field Injection. Declare protected variables of the required type
  34.  * which match named beans in the context. This is autowire by name,
  35.  * rather than type. This approach is based on an
  36.  * approach originated by Ara Abrahmian. Setter Dependency Injection
  37.  * is the default: set the populateProtectedVariables property to true
  38.  * in the constructor to switch on Field Injection.
  39.  * </ul>
  40.  *
  41.  * <p>This class will normally cache contexts based on a <i>context key</i>:
  42.  * normally the config locations String array describing the Spring Resource
  43.  * descriptors making up the context. Unless the setDirty() method is called by
  44.  * a test, the context will not be reloaded, even across different subclasses
  45.  * of this test. This is particularly beneficial if your context is slow to
  46.  * construct, for example if you are using Hibernate and the time taken to
  47.  * load the mappings is an issue.
  48.  *
  49.  * <p>If you don't want this behaviour, you can override the contextKey() method,
  50.  * most likely to return the test class. In conjunction with this you would
  51.  * probably override the buildContext() method, which by default loads the
  52.  * locations specified in the getConfigLocations() method.
  53.  *
  54.  * @author Rod Johnson
  55.  * @since 1.1.1
  56.  */
  57. public abstract class AbstractDependencyInjectionSpringContextTests extends AbstractSpringContextTests {
  58. private boolean populateProtectedVariables = false;
  59. /**
  60.  * Key for the context.
  61.  * This enables multiple contexts to share the same key.
  62.  */
  63. private Object contextKey;
  64. /**
  65.  * Application context this test will run against.
  66.  */
  67. protected ConfigurableApplicationContext applicationContext;
  68. protected String[] managedVariableNames;
  69. private int loadCount = 0;
  70. public void setPopulateProtectedVariables(boolean populateFields) {
  71. this.populateProtectedVariables = populateFields;
  72. }
  73. public boolean isPopulateProtectedVariables() {
  74. return populateProtectedVariables;
  75. }
  76. public final int getLoadCount() {
  77. return loadCount;
  78. }
  79. /**
  80.  * Called to say that the applicationContext instance variable is dirty and
  81.  * should be reloaded. We need to do this if a test has modified the context
  82.  * (for example, by replacing a bean definition).
  83.  */
  84. public void setDirty() {
  85. setDirty(getConfigLocations());
  86. }
  87. protected final void setUp() throws Exception {
  88. if (this.contextKey == null) {
  89. this.contextKey = contextKey();
  90. }
  91. this.applicationContext = getContext(this.contextKey);
  92. if (isPopulateProtectedVariables()) {
  93. if (this.managedVariableNames == null) {
  94. initManagedVariableNames();
  95. }
  96. //System.out.println("POPULATED PROTECTED VARS");
  97. populateProtectedVariables();
  98. }
  99. else {
  100. this.applicationContext.getBeanFactory().autowireBeanProperties(
  101.     this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
  102. }
  103. try {
  104. onSetUp();
  105. }
  106. catch (Exception ex) {
  107. logger.error("Setup error: " + ex);
  108. throw ex;
  109. }
  110. }
  111. /**
  112.  * Return a key for this context. Usually based on config locations, but
  113.  * a subclass overriding buildContext() might want to return its class.
  114.  * Called once and cached.
  115.  */
  116. protected Object contextKey() {
  117. return getConfigLocations();
  118. }
  119. protected ConfigurableApplicationContext loadContextLocations(String[] locations) {
  120. ++this.loadCount;
  121. return super.loadContextLocations(locations);
  122. }
  123. protected void initManagedVariableNames() throws IllegalAccessException {
  124. LinkedList managedVarNames = new LinkedList();
  125. Class clazz = getClass();
  126. do {
  127. Field[] fields = clazz.getDeclaredFields();
  128. logger.debug(fields.length + " fields on " + clazz);
  129. for (int i = 0; i < fields.length; i++) {
  130. // TODO go up tree but not to this class
  131. Field f = fields[i];
  132. f.setAccessible(true);
  133. logger.debug("Candidate field " + f);
  134. if (!Modifier.isStatic(f.getModifiers()) && Modifier.isProtected(f.getModifiers())) {
  135. Object oldValue = f.get(this);
  136. if (oldValue == null) {
  137. managedVarNames.add(f.getName());
  138. logger.info("Added managed variable '" + f.getName() + "'");
  139. }
  140. else {
  141. logger.info("Rejected managed variable '" + f.getName() + "'");
  142. }
  143. }
  144. }
  145. clazz = clazz.getSuperclass();
  146. }
  147. while (clazz != AbstractSpringContextTests.class);
  148. this.managedVariableNames = (String[]) managedVarNames.toArray(new String[managedVarNames.size()]);
  149. }
  150. protected void populateProtectedVariables() throws IllegalAccessException {
  151. for (int i = 0; i < this.managedVariableNames.length; i++) {
  152. Object bean = null;
  153. Field f = null;
  154. try {
  155. f = findField(getClass(), this.managedVariableNames[i]);
  156. // TODO what if not found?
  157. bean = this.applicationContext.getBean(this.managedVariableNames[i]);
  158. f.set(this, bean);
  159. logger.info("Populated " + f);
  160. }
  161. catch (NoSuchFieldException ex) {
  162. logger.warn("No field with name '" + this.managedVariableNames[i] + "'");
  163. }
  164. catch (IllegalArgumentException ex) {
  165. logger.error("Value " + bean + " not compatible with " + f);
  166. }
  167. catch (NoSuchBeanDefinitionException ex) {
  168. logger.warn("No bean with name '" + this.managedVariableNames[i] + "'");
  169. }
  170. }
  171. }
  172. private Field findField(Class clazz, String name) throws NoSuchFieldException {
  173. try {
  174. return clazz.getDeclaredField(name);
  175. }
  176. catch (NoSuchFieldException ex) {
  177. Class superclass = clazz.getSuperclass();
  178. if (superclass != AbstractSpringContextTests.class) {
  179. return findField(superclass, name);
  180. }
  181. else {
  182. throw ex;
  183. }
  184. }
  185. }
  186. /**
  187.  * Subclasses can override this method in place of
  188.  * the setUp() method, which is final in this class.
  189.  * This implementation does nothing.
  190.  */
  191. protected void onSetUp() throws Exception {
  192. }
  193. /**
  194.  * Reload the context if it's marked as dirty.
  195.  * @see #onTearDown
  196.  */
  197. protected final void tearDown() {
  198. try {
  199. onTearDown();
  200. }
  201. catch (Exception ex) {
  202. logger.error("onTearDown error", ex);
  203. }
  204. }
  205. /**
  206.  * Subclasses can override this to add custom behavior on teardown.
  207.  */
  208. protected void onTearDown() throws Exception {
  209. }
  210. /**
  211.  * Subclasses must implement this method to return the
  212.  * locations of their config files. E.g.:
  213.  * "classpath:org/springframework/whatever/foo.xml"
  214.  * @return an array of config locations
  215.  */
  216. protected abstract String[] getConfigLocations();
  217. }