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.beans.factory.config; 018 019import java.lang.reflect.Field; 020 021import org.springframework.beans.factory.BeanClassLoaderAware; 022import org.springframework.beans.factory.BeanFactoryUtils; 023import org.springframework.beans.factory.BeanNameAware; 024import org.springframework.beans.factory.FactoryBean; 025import org.springframework.beans.factory.FactoryBeanNotInitializedException; 026import org.springframework.beans.factory.InitializingBean; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.util.ClassUtils; 030import org.springframework.util.ReflectionUtils; 031import org.springframework.util.StringUtils; 032 033/** 034 * {@link FactoryBean} which retrieves a static or non-static field value. 035 * 036 * <p>Typically used for retrieving public static final constants. Usage example: 037 * 038 * <pre class="code"> 039 * // standard definition for exposing a static field, specifying the "staticField" property 040 * <bean id="myField" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"> 041 * <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/> 042 * </bean> 043 * 044 * // convenience version that specifies a static field pattern as bean name 045 * <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE" 046 * class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/> 047 * </pre> 048 * 049 * <p>If you are using Spring 2.0, you can also use the following style of configuration for 050 * public static fields. 051 * 052 * <pre class="code"><util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/></pre> 053 * 054 * @author Juergen Hoeller 055 * @since 1.1 056 * @see #setStaticField 057 */ 058public class FieldRetrievingFactoryBean 059 implements FactoryBean<Object>, BeanNameAware, BeanClassLoaderAware, InitializingBean { 060 061 @Nullable 062 private Class<?> targetClass; 063 064 @Nullable 065 private Object targetObject; 066 067 @Nullable 068 private String targetField; 069 070 @Nullable 071 private String staticField; 072 073 @Nullable 074 private String beanName; 075 076 @Nullable 077 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 078 079 // the field we will retrieve 080 @Nullable 081 private Field fieldObject; 082 083 084 /** 085 * Set the target class on which the field is defined. 086 * Only necessary when the target field is static; else, 087 * a target object needs to be specified anyway. 088 * @see #setTargetObject 089 * @see #setTargetField 090 */ 091 public void setTargetClass(@Nullable Class<?> targetClass) { 092 this.targetClass = targetClass; 093 } 094 095 /** 096 * Return the target class on which the field is defined. 097 */ 098 @Nullable 099 public Class<?> getTargetClass() { 100 return this.targetClass; 101 } 102 103 /** 104 * Set the target object on which the field is defined. 105 * Only necessary when the target field is not static; 106 * else, a target class is sufficient. 107 * @see #setTargetClass 108 * @see #setTargetField 109 */ 110 public void setTargetObject(@Nullable Object targetObject) { 111 this.targetObject = targetObject; 112 } 113 114 /** 115 * Return the target object on which the field is defined. 116 */ 117 @Nullable 118 public Object getTargetObject() { 119 return this.targetObject; 120 } 121 122 /** 123 * Set the name of the field to be retrieved. 124 * Refers to either a static field or a non-static field, 125 * depending on a target object being set. 126 * @see #setTargetClass 127 * @see #setTargetObject 128 */ 129 public void setTargetField(@Nullable String targetField) { 130 this.targetField = (targetField != null ? StringUtils.trimAllWhitespace(targetField) : null); 131 } 132 133 /** 134 * Return the name of the field to be retrieved. 135 */ 136 @Nullable 137 public String getTargetField() { 138 return this.targetField; 139 } 140 141 /** 142 * Set a fully qualified static field name to retrieve, 143 * e.g. "example.MyExampleClass.MY_EXAMPLE_FIELD". 144 * Convenient alternative to specifying targetClass and targetField. 145 * @see #setTargetClass 146 * @see #setTargetField 147 */ 148 public void setStaticField(String staticField) { 149 this.staticField = StringUtils.trimAllWhitespace(staticField); 150 } 151 152 /** 153 * The bean name of this FieldRetrievingFactoryBean will be interpreted 154 * as "staticField" pattern, if neither "targetClass" nor "targetObject" 155 * nor "targetField" have been specified. 156 * This allows for concise bean definitions with just an id/name. 157 */ 158 @Override 159 public void setBeanName(String beanName) { 160 this.beanName = StringUtils.trimAllWhitespace(BeanFactoryUtils.originalBeanName(beanName)); 161 } 162 163 @Override 164 public void setBeanClassLoader(ClassLoader classLoader) { 165 this.beanClassLoader = classLoader; 166 } 167 168 169 @Override 170 public void afterPropertiesSet() throws ClassNotFoundException, NoSuchFieldException { 171 if (this.targetClass != null && this.targetObject != null) { 172 throw new IllegalArgumentException("Specify either targetClass or targetObject, not both"); 173 } 174 175 if (this.targetClass == null && this.targetObject == null) { 176 if (this.targetField != null) { 177 throw new IllegalArgumentException( 178 "Specify targetClass or targetObject in combination with targetField"); 179 } 180 181 // If no other property specified, consider bean name as static field expression. 182 if (this.staticField == null) { 183 this.staticField = this.beanName; 184 Assert.state(this.staticField != null, "No target field specified"); 185 } 186 187 // Try to parse static field into class and field. 188 int lastDotIndex = this.staticField.lastIndexOf('.'); 189 if (lastDotIndex == -1 || lastDotIndex == this.staticField.length()) { 190 throw new IllegalArgumentException( 191 "staticField must be a fully qualified class plus static field name: " + 192 "e.g. 'example.MyExampleClass.MY_EXAMPLE_FIELD'"); 193 } 194 String className = this.staticField.substring(0, lastDotIndex); 195 String fieldName = this.staticField.substring(lastDotIndex + 1); 196 this.targetClass = ClassUtils.forName(className, this.beanClassLoader); 197 this.targetField = fieldName; 198 } 199 200 else if (this.targetField == null) { 201 // Either targetClass or targetObject specified. 202 throw new IllegalArgumentException("targetField is required"); 203 } 204 205 // Try to get the exact method first. 206 Class<?> targetClass = (this.targetObject != null ? this.targetObject.getClass() : this.targetClass); 207 this.fieldObject = targetClass.getField(this.targetField); 208 } 209 210 211 @Override 212 @Nullable 213 public Object getObject() throws IllegalAccessException { 214 if (this.fieldObject == null) { 215 throw new FactoryBeanNotInitializedException(); 216 } 217 ReflectionUtils.makeAccessible(this.fieldObject); 218 if (this.targetObject != null) { 219 // instance field 220 return this.fieldObject.get(this.targetObject); 221 } 222 else { 223 // class field 224 return this.fieldObject.get(null); 225 } 226 } 227 228 @Override 229 public Class<?> getObjectType() { 230 return (this.fieldObject != null ? this.fieldObject.getType() : null); 231 } 232 233 @Override 234 public boolean isSingleton() { 235 return false; 236 } 237 238}