Merge branch 'master' into websocket-stomp
This commit is contained in:
@@ -42,14 +42,18 @@ import org.springframework.util.CollectionUtils;
|
||||
public abstract class AbstractCachingConfiguration implements ImportAware {
|
||||
|
||||
protected AnnotationAttributes enableCaching;
|
||||
|
||||
protected CacheManager cacheManager;
|
||||
|
||||
protected KeyGenerator keyGenerator;
|
||||
|
||||
@Autowired(required=false)
|
||||
private Collection<CacheManager> cacheManagerBeans;
|
||||
|
||||
@Autowired(required=false)
|
||||
private Collection<CachingConfigurer> cachingConfigurers;
|
||||
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
this.enableCaching = AnnotationAttributes.fromMap(
|
||||
@@ -59,6 +63,7 @@ public abstract class AbstractCachingConfiguration implements ImportAware {
|
||||
importMetadata.getClassName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine which {@code CacheManager} bean to use. Prefer the result of
|
||||
* {@link CachingConfigurer#cacheManager()} over any by-type matching. If none, fall
|
||||
|
||||
@@ -120,9 +120,9 @@ import org.springframework.core.Ordered;
|
||||
* customizing the strategy for cache key generation, per Spring's {@link
|
||||
* org.springframework.cache.interceptor.KeyGenerator KeyGenerator} SPI. Normally,
|
||||
* {@code @EnableCaching} will configure Spring's
|
||||
* {@link org.springframework.cache.interceptor.DefaultKeyGenerator DefaultKeyGenerator}
|
||||
* {@link org.springframework.cache.interceptor.SimpleKeyGenerator SimpleKeyGenerator}
|
||||
* for this purpose, but when implementing {@code CachingConfigurer}, a key generator
|
||||
* must be provided explicitly. Return {@code new DefaultKeyGenerator()} from this method
|
||||
* must be provided explicitly. Return {@code new SimpleKeyGenerator()} from this method
|
||||
* if no customization is necessary. See {@link CachingConfigurer} Javadoc for further
|
||||
* details.
|
||||
*
|
||||
|
||||
@@ -48,69 +48,17 @@ import org.w3c.dom.Element;
|
||||
*/
|
||||
class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
|
||||
/**
|
||||
* Simple, reusable class used for overriding defaults.
|
||||
*
|
||||
* @author Costin Leau
|
||||
*/
|
||||
private static class Props {
|
||||
|
||||
private String key;
|
||||
private String condition;
|
||||
private String method;
|
||||
private String[] caches = null;
|
||||
|
||||
Props(Element root) {
|
||||
String defaultCache = root.getAttribute("cache");
|
||||
key = root.getAttribute("key");
|
||||
condition = root.getAttribute("condition");
|
||||
method = root.getAttribute(METHOD_ATTRIBUTE);
|
||||
|
||||
if (StringUtils.hasText(defaultCache)) {
|
||||
caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
|
||||
}
|
||||
}
|
||||
|
||||
<T extends CacheOperation> T merge(Element element, ReaderContext readerCtx, T op) {
|
||||
String cache = element.getAttribute("cache");
|
||||
|
||||
// sanity check
|
||||
String[] localCaches = caches;
|
||||
if (StringUtils.hasText(cache)) {
|
||||
localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim());
|
||||
} else {
|
||||
if (caches == null) {
|
||||
readerCtx.error("No cache specified specified for " + element.getNodeName(), element);
|
||||
}
|
||||
}
|
||||
op.setCacheNames(localCaches);
|
||||
|
||||
op.setKey(getAttributeValue(element, "key", this.key));
|
||||
op.setCondition(getAttributeValue(element, "condition", this.condition));
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
String merge(Element element, ReaderContext readerCtx) {
|
||||
String m = element.getAttribute(METHOD_ATTRIBUTE);
|
||||
|
||||
if (StringUtils.hasText(m)) {
|
||||
return m.trim();
|
||||
}
|
||||
if (StringUtils.hasText(method)) {
|
||||
return method;
|
||||
}
|
||||
readerCtx.error("No method specified for " + element.getNodeName(), element);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String CACHEABLE_ELEMENT = "cacheable";
|
||||
|
||||
private static final String CACHE_EVICT_ELEMENT = "cache-evict";
|
||||
|
||||
private static final String CACHE_PUT_ELEMENT = "cache-put";
|
||||
|
||||
private static final String METHOD_ATTRIBUTE = "method";
|
||||
|
||||
private static final String DEFS_ELEMENT = "caching";
|
||||
|
||||
|
||||
@Override
|
||||
protected Class<?> getBeanClass(Element element) {
|
||||
return CacheInterceptor.class;
|
||||
@@ -226,4 +174,66 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple, reusable class used for overriding defaults.
|
||||
*
|
||||
* @author Costin Leau
|
||||
*/
|
||||
private static class Props {
|
||||
|
||||
private String key;
|
||||
|
||||
private String condition;
|
||||
|
||||
private String method;
|
||||
|
||||
private String[] caches = null;
|
||||
|
||||
|
||||
Props(Element root) {
|
||||
String defaultCache = root.getAttribute("cache");
|
||||
key = root.getAttribute("key");
|
||||
condition = root.getAttribute("condition");
|
||||
method = root.getAttribute(METHOD_ATTRIBUTE);
|
||||
|
||||
if (StringUtils.hasText(defaultCache)) {
|
||||
caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<T extends CacheOperation> T merge(Element element, ReaderContext readerCtx, T op) {
|
||||
String cache = element.getAttribute("cache");
|
||||
|
||||
// sanity check
|
||||
String[] localCaches = caches;
|
||||
if (StringUtils.hasText(cache)) {
|
||||
localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim());
|
||||
} else {
|
||||
if (caches == null) {
|
||||
readerCtx.error("No cache specified specified for " + element.getNodeName(), element);
|
||||
}
|
||||
}
|
||||
op.setCacheNames(localCaches);
|
||||
|
||||
op.setKey(getAttributeValue(element, "key", this.key));
|
||||
op.setCondition(getAttributeValue(element, "condition", this.condition));
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
String merge(Element element, ReaderContext readerCtx) {
|
||||
String m = element.getAttribute(METHOD_ATTRIBUTE);
|
||||
|
||||
if (StringUtils.hasText(m)) {
|
||||
return m.trim();
|
||||
}
|
||||
if (StringUtils.hasText(method)) {
|
||||
return method;
|
||||
}
|
||||
readerCtx.error("No method specified for " + element.getNodeName(), element);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.w3c.dom.Element;
|
||||
public class CacheNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager";
|
||||
|
||||
static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager";
|
||||
|
||||
static String extractCacheManager(Element element) {
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.springframework.cache.interceptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -28,11 +28,15 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.Cache.ValueWrapper;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.support.SimpleValueWrapper;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -61,23 +65,212 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public abstract class CacheAspectSupport implements InitializingBean {
|
||||
|
||||
public interface Invoker {
|
||||
Object invoke();
|
||||
}
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
|
||||
private CacheManager cacheManager;
|
||||
|
||||
private CacheOperationSource cacheOperationSource;
|
||||
|
||||
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
|
||||
|
||||
private KeyGenerator keyGenerator = new DefaultKeyGenerator();
|
||||
private KeyGenerator keyGenerator = new SimpleKeyGenerator();
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
private static final String CACHEABLE = "cacheable", UPDATE = "cacheupdate", EVICT = "cacheevict";
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.state(this.cacheManager != null, "'cacheManager' is required");
|
||||
Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSources' "
|
||||
+ "property is required: If there are no cacheable methods, "
|
||||
+ "then don't use a cache aspect.");
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to return a String representation of this Method
|
||||
* for use in logging. Can be overridden in subclasses to provide a
|
||||
* different identifier for the given method.
|
||||
* @param method the method we're interested in
|
||||
* @param targetClass class the method is on
|
||||
* @return log message identifying this method
|
||||
* @see org.springframework.util.ClassUtils#getQualifiedMethodName
|
||||
*/
|
||||
protected String methodIdentification(Method method, Class<?> targetClass) {
|
||||
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
|
||||
return ClassUtils.getQualifiedMethodName(specificMethod);
|
||||
}
|
||||
|
||||
protected Collection<Cache> getCaches(CacheOperation operation) {
|
||||
Set<String> cacheNames = operation.getCacheNames();
|
||||
Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
|
||||
for (String cacheName : cacheNames) {
|
||||
Cache cache = this.cacheManager.getCache(cacheName);
|
||||
Assert.notNull(cache, "Cannot find cache named [" + cacheName + "] for " + operation);
|
||||
caches.add(cache);
|
||||
}
|
||||
return caches;
|
||||
}
|
||||
|
||||
protected CacheOperationContext getOperationContext(CacheOperation operation,
|
||||
Method method, Object[] args, Object target, Class<?> targetClass) {
|
||||
return new CacheOperationContext(operation, method, args, target, targetClass);
|
||||
}
|
||||
|
||||
protected Object execute(Invoker invoker, Object target, Method method, Object[] args) {
|
||||
|
||||
// check whether aspect is enabled
|
||||
// to cope with cases where the AJ is pulled in automatically
|
||||
if (this.initialized) {
|
||||
Class<?> targetClass = getTargetClass(target);
|
||||
Collection<CacheOperation> operations = getCacheOperationSource().
|
||||
getCacheOperations(method, targetClass);
|
||||
if (!CollectionUtils.isEmpty(operations)) {
|
||||
return execute(invoker, new CacheOperationContexts(operations,
|
||||
method, args, target, targetClass));
|
||||
}
|
||||
}
|
||||
|
||||
return invoker.invoke();
|
||||
}
|
||||
|
||||
private Class<?> getTargetClass(Object target) {
|
||||
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
|
||||
if (targetClass == null && target != null) {
|
||||
targetClass = target.getClass();
|
||||
}
|
||||
return targetClass;
|
||||
}
|
||||
|
||||
private Object execute(Invoker invoker, CacheOperationContexts contexts) {
|
||||
|
||||
// Process any early evictions
|
||||
processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT);
|
||||
|
||||
// Collect puts from any @Cachable miss
|
||||
List<CachePutRequest> cachePutRequests = new ArrayList<CachePutRequest>();
|
||||
collectPutRequests(contexts.get(CacheableOperation.class),
|
||||
ExpressionEvaluator.NO_RESULT, cachePutRequests, true);
|
||||
|
||||
ValueWrapper result = null;
|
||||
|
||||
// We only attempt to get a cached result if there are no put requests
|
||||
if(cachePutRequests.isEmpty() && contexts.get(CachePutOperation.class).isEmpty()) {
|
||||
result = findCachedResult(contexts.get(CacheableOperation.class));
|
||||
}
|
||||
|
||||
// Invoke the method if don't have a cache hit
|
||||
if(result == null) {
|
||||
result = new SimpleValueWrapper(invoker.invoke());
|
||||
}
|
||||
|
||||
// Collect any explicit @CachePuts
|
||||
collectPutRequests(contexts.get(CachePutOperation.class), result.get(),
|
||||
cachePutRequests, false);
|
||||
|
||||
// Process any collected put requests, either from @CachePut or a @Cacheable miss
|
||||
for (CachePutRequest cachePutRequest : cachePutRequests) {
|
||||
cachePutRequest.apply(result.get());
|
||||
}
|
||||
|
||||
// Process any late evictions
|
||||
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get());
|
||||
|
||||
return result.get();
|
||||
}
|
||||
|
||||
private void processCacheEvicts(Collection<CacheOperationContext> contexts,
|
||||
boolean beforeInvocation, Object result) {
|
||||
for (CacheOperationContext context : contexts) {
|
||||
CacheEvictOperation operation = (CacheEvictOperation) context.operation;
|
||||
if (beforeInvocation == operation.isBeforeInvocation() &&
|
||||
isConditionPassing(context, result)) {
|
||||
performCacheEvict(context, operation, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void performCacheEvict(CacheOperationContext context,
|
||||
CacheEvictOperation operation, Object result) {
|
||||
Object key = null;
|
||||
for (Cache cache : context.getCaches()) {
|
||||
if (operation.isCacheWide()) {
|
||||
logInvalidating(context, operation, null);
|
||||
cache.clear();
|
||||
} else {
|
||||
if(key == null) {
|
||||
key = context.generateKey(result);
|
||||
}
|
||||
logInvalidating(context, operation, key);
|
||||
cache.evict(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logInvalidating(CacheOperationContext context,
|
||||
CacheEvictOperation operation, Object key) {
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Invalidating " +
|
||||
(key == null ? "entire cache" : "cache key " + key) +
|
||||
" for operation " + operation + " on method " + context.method);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectPutRequests(Collection<CacheOperationContext> contexts,
|
||||
Object result, Collection<CachePutRequest> putRequests, boolean whenNotInCache) {
|
||||
for (CacheOperationContext context : contexts) {
|
||||
if (isConditionPassing(context, result)) {
|
||||
Object key = generateKey(context, result);
|
||||
if (!whenNotInCache || findInCaches(context, key) == null) {
|
||||
putRequests.add(new CachePutRequest(context, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Cache.ValueWrapper findCachedResult(Collection<CacheOperationContext> contexts) {
|
||||
ValueWrapper result = null;
|
||||
for (CacheOperationContext context : contexts) {
|
||||
if (isConditionPassing(context, ExpressionEvaluator.NO_RESULT)) {
|
||||
if(result == null) {
|
||||
result = findInCaches(context,
|
||||
generateKey(context, ExpressionEvaluator.NO_RESULT));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
|
||||
for (Cache cache : context.getCaches()) {
|
||||
Cache.ValueWrapper wrapper = cache.get(key);
|
||||
if (wrapper != null) {
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isConditionPassing(CacheOperationContext context, Object result) {
|
||||
boolean passing = context.isConditionPassing(result);
|
||||
if(!passing && this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
|
||||
}
|
||||
return passing;
|
||||
}
|
||||
|
||||
private Object generateKey(CacheOperationContext context, Object result) {
|
||||
Object key = context.generateKey(result);
|
||||
Assert.notNull(key, "Null key returned for cache operation (maybe you "
|
||||
+ "are using named params on classes without debug info?) "
|
||||
+ context.operation);
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Computed cache key " + key + " for operation " + context.operation);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the CacheManager that this cache aspect should delegate to.
|
||||
@@ -116,7 +309,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||
|
||||
/**
|
||||
* Set the KeyGenerator for this cache aspect.
|
||||
* Default is {@link DefaultKeyGenerator}.
|
||||
* Default is {@link SimpleKeyGenerator}.
|
||||
*/
|
||||
public void setKeyGenerator(KeyGenerator keyGenerator) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
@@ -129,306 +322,30 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||
return this.keyGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.cacheManager == null) {
|
||||
throw new IllegalStateException("'cacheManager' is required");
|
||||
}
|
||||
if (this.cacheOperationSource == null) {
|
||||
throw new IllegalStateException("The 'cacheOperationSources' property is required: "
|
||||
+ "If there are no cacheable methods, then don't use a cache aspect.");
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
public interface Invoker {
|
||||
Object invoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to return a String representation of this Method
|
||||
* for use in logging. Can be overridden in subclasses to provide a
|
||||
* different identifier for the given method.
|
||||
* @param method the method we're interested in
|
||||
* @param targetClass class the method is on
|
||||
* @return log message identifying this method
|
||||
* @see org.springframework.util.ClassUtils#getQualifiedMethodName
|
||||
*/
|
||||
protected String methodIdentification(Method method, Class<?> targetClass) {
|
||||
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
|
||||
return ClassUtils.getQualifiedMethodName(specificMethod);
|
||||
}
|
||||
|
||||
protected Collection<Cache> getCaches(CacheOperation operation) {
|
||||
Set<String> cacheNames = operation.getCacheNames();
|
||||
Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
|
||||
for (String cacheName : cacheNames) {
|
||||
Cache cache = this.cacheManager.getCache(cacheName);
|
||||
if (cache == null) {
|
||||
throw new IllegalArgumentException("Cannot find cache named [" + cacheName + "] for " + operation);
|
||||
}
|
||||
caches.add(cache);
|
||||
}
|
||||
return caches;
|
||||
}
|
||||
private class CacheOperationContexts {
|
||||
|
||||
protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args,
|
||||
Object target, Class<?> targetClass) {
|
||||
private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts =
|
||||
new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>();
|
||||
|
||||
return new CacheOperationContext(operation, method, args, target, targetClass);
|
||||
}
|
||||
|
||||
protected Object execute(Invoker invoker, Object target, Method method, Object[] args) {
|
||||
// check whether aspect is enabled
|
||||
// to cope with cases where the AJ is pulled in automatically
|
||||
if (!this.initialized) {
|
||||
return invoker.invoke();
|
||||
}
|
||||
|
||||
// get backing class
|
||||
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
|
||||
if (targetClass == null && target != null) {
|
||||
targetClass = target.getClass();
|
||||
}
|
||||
final Collection<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass);
|
||||
|
||||
// analyze caching information
|
||||
if (!CollectionUtils.isEmpty(cacheOp)) {
|
||||
Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target, targetClass);
|
||||
|
||||
// start with evictions
|
||||
inspectBeforeCacheEvicts(ops.get(EVICT));
|
||||
|
||||
// follow up with cacheable
|
||||
CacheStatus status = inspectCacheables(ops.get(CACHEABLE));
|
||||
|
||||
Object retVal = null;
|
||||
Map<CacheOperationContext, Object> updates = inspectCacheUpdates(ops.get(UPDATE));
|
||||
|
||||
if (status != null) {
|
||||
if (status.updateRequired) {
|
||||
updates.putAll(status.cUpdates);
|
||||
}
|
||||
// return cached object
|
||||
else {
|
||||
return status.retVal;
|
||||
}
|
||||
}
|
||||
|
||||
retVal = invoker.invoke();
|
||||
|
||||
inspectAfterCacheEvicts(ops.get(EVICT), retVal);
|
||||
|
||||
if (!updates.isEmpty()) {
|
||||
update(updates, retVal);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
return invoker.invoke();
|
||||
}
|
||||
|
||||
private void inspectBeforeCacheEvicts(Collection<CacheOperationContext> evictions) {
|
||||
inspectCacheEvicts(evictions, true, ExpressionEvaluator.NO_RESULT);
|
||||
}
|
||||
|
||||
private void inspectAfterCacheEvicts(Collection<CacheOperationContext> evictions,
|
||||
Object result) {
|
||||
inspectCacheEvicts(evictions, false, result);
|
||||
}
|
||||
|
||||
private void inspectCacheEvicts(Collection<CacheOperationContext> evictions,
|
||||
boolean beforeInvocation, Object result) {
|
||||
|
||||
if (!evictions.isEmpty()) {
|
||||
|
||||
boolean log = logger.isTraceEnabled();
|
||||
|
||||
for (CacheOperationContext context : evictions) {
|
||||
CacheEvictOperation evictOp = (CacheEvictOperation) context.operation;
|
||||
|
||||
if (beforeInvocation == evictOp.isBeforeInvocation()) {
|
||||
if (context.isConditionPassing(result)) {
|
||||
// for each cache
|
||||
// lazy key initialization
|
||||
Object key = null;
|
||||
|
||||
for (Cache cache : context.getCaches()) {
|
||||
// cache-wide flush
|
||||
if (evictOp.isCacheWide()) {
|
||||
cache.clear();
|
||||
if (log) {
|
||||
logger.trace("Invalidating entire cache for operation " + evictOp + " on method " + context.method);
|
||||
}
|
||||
} else {
|
||||
// check key
|
||||
if (key == null) {
|
||||
key = context.generateKey();
|
||||
}
|
||||
if (log) {
|
||||
logger.trace("Invalidating cache key " + key + " for operation " + evictOp + " on method " + context.method);
|
||||
}
|
||||
cache.evict(key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (log) {
|
||||
logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CacheStatus inspectCacheables(Collection<CacheOperationContext> cacheables) {
|
||||
Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(cacheables.size());
|
||||
|
||||
boolean updateRequired = false;
|
||||
Object retVal = null;
|
||||
|
||||
if (!cacheables.isEmpty()) {
|
||||
boolean log = logger.isTraceEnabled();
|
||||
boolean atLeastOnePassed = false;
|
||||
|
||||
for (CacheOperationContext context : cacheables) {
|
||||
if (context.isConditionPassing()) {
|
||||
atLeastOnePassed = true;
|
||||
Object key = context.generateKey();
|
||||
|
||||
if (log) {
|
||||
logger.trace("Computed cache key " + key + " for operation " + context.operation);
|
||||
}
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Null key returned for cache operation (maybe you are using named params on classes without debug info?) "
|
||||
+ context.operation);
|
||||
}
|
||||
|
||||
// add op/key (in case an update is discovered later on)
|
||||
cUpdates.put(context, key);
|
||||
|
||||
boolean localCacheHit = false;
|
||||
|
||||
// check whether the cache needs to be inspected or not (the method will be invoked anyway)
|
||||
if (!updateRequired) {
|
||||
for (Cache cache : context.getCaches()) {
|
||||
Cache.ValueWrapper wrapper = cache.get(key);
|
||||
if (wrapper != null) {
|
||||
retVal = wrapper.get();
|
||||
localCacheHit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!localCacheHit) {
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (log) {
|
||||
logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return a status only if at least on cacheable matched
|
||||
if (atLeastOnePassed) {
|
||||
return new CacheStatus(cUpdates, updateRequired, retVal);
|
||||
public CacheOperationContexts(Collection<? extends CacheOperation> operations,
|
||||
Method method, Object[] args, Object target, Class<?> targetClass) {
|
||||
for (CacheOperation operation : operations) {
|
||||
this.contexts.add(operation.getClass(), new CacheOperationContext(operation,
|
||||
method, args, target, targetClass));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class CacheStatus {
|
||||
// caches/key
|
||||
final Map<CacheOperationContext, Object> cUpdates;
|
||||
final boolean updateRequired;
|
||||
final Object retVal;
|
||||
|
||||
CacheStatus(Map<CacheOperationContext, Object> cUpdates, boolean updateRequired, Object retVal) {
|
||||
this.cUpdates = cUpdates;
|
||||
this.updateRequired = updateRequired;
|
||||
this.retVal = retVal;
|
||||
public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
|
||||
return this.contexts.getOrDefault(operationClass, Collections.<CacheOperationContext> emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<CacheOperationContext, Object> inspectCacheUpdates(Collection<CacheOperationContext> updates) {
|
||||
|
||||
Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(updates.size());
|
||||
|
||||
if (!updates.isEmpty()) {
|
||||
boolean log = logger.isTraceEnabled();
|
||||
|
||||
for (CacheOperationContext context : updates) {
|
||||
if (context.isConditionPassing()) {
|
||||
|
||||
Object key = context.generateKey();
|
||||
|
||||
if (log) {
|
||||
logger.trace("Computed cache key " + key + " for operation " + context.operation);
|
||||
}
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Null key returned for cache operation (maybe you are using named params on classes without debug info?) "
|
||||
+ context.operation);
|
||||
}
|
||||
|
||||
// add op/key (in case an update is discovered later on)
|
||||
cUpdates.put(context, key);
|
||||
}
|
||||
else {
|
||||
if (log) {
|
||||
logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cUpdates;
|
||||
}
|
||||
|
||||
private void update(Map<CacheOperationContext, Object> updates, Object retVal) {
|
||||
for (Map.Entry<CacheOperationContext, Object> entry : updates.entrySet()) {
|
||||
CacheOperationContext operationContext = entry.getKey();
|
||||
if(operationContext.canPutToCache(retVal)) {
|
||||
for (Cache cache : operationContext.getCaches()) {
|
||||
cache.put(entry.getValue(), retVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Collection<CacheOperationContext>> createOperationContext(Collection<CacheOperation> cacheOp,
|
||||
Method method, Object[] args, Object target, Class<?> targetClass) {
|
||||
Map<String, Collection<CacheOperationContext>> map = new LinkedHashMap<String, Collection<CacheOperationContext>>(3);
|
||||
|
||||
Collection<CacheOperationContext> cacheables = new ArrayList<CacheOperationContext>();
|
||||
Collection<CacheOperationContext> evicts = new ArrayList<CacheOperationContext>();
|
||||
Collection<CacheOperationContext> updates = new ArrayList<CacheOperationContext>();
|
||||
|
||||
for (CacheOperation cacheOperation : cacheOp) {
|
||||
CacheOperationContext opContext = getOperationContext(cacheOperation, method, args, target, targetClass);
|
||||
|
||||
if (cacheOperation instanceof CacheableOperation) {
|
||||
cacheables.add(opContext);
|
||||
}
|
||||
|
||||
if (cacheOperation instanceof CacheEvictOperation) {
|
||||
evicts.add(opContext);
|
||||
}
|
||||
|
||||
if (cacheOperation instanceof CachePutOperation) {
|
||||
updates.add(opContext);
|
||||
}
|
||||
}
|
||||
|
||||
map.put(CACHEABLE, cacheables);
|
||||
map.put(EVICT, evicts);
|
||||
map.put(UPDATE, updates);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
protected class CacheOperationContext {
|
||||
|
||||
@@ -444,6 +361,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||
|
||||
private final Collection<Cache> caches;
|
||||
|
||||
|
||||
public CacheOperationContext(CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
|
||||
this.operation = operation;
|
||||
this.method = method;
|
||||
@@ -453,14 +371,10 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||
this.caches = CacheAspectSupport.this.getCaches(operation);
|
||||
}
|
||||
|
||||
protected boolean isConditionPassing() {
|
||||
return isConditionPassing(ExpressionEvaluator.NO_RESULT);
|
||||
}
|
||||
|
||||
protected boolean isConditionPassing(Object result) {
|
||||
if (StringUtils.hasText(this.operation.getCondition())) {
|
||||
EvaluationContext evaluationContext = createEvaluationContext(result);
|
||||
return evaluator.condition(this.operation.getCondition(), this.method,
|
||||
return CacheAspectSupport.this.evaluator.condition(this.operation.getCondition(), this.method,
|
||||
evaluationContext);
|
||||
}
|
||||
return true;
|
||||
@@ -476,7 +390,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||
}
|
||||
if(StringUtils.hasText(unless)) {
|
||||
EvaluationContext evaluationContext = createEvaluationContext(value);
|
||||
return !evaluator.unless(unless, this.method, evaluationContext);
|
||||
return !CacheAspectSupport.this.evaluator.unless(unless, this.method, evaluationContext);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -485,16 +399,16 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||
* Computes the key for the given caching operation.
|
||||
* @return generated key (null if none can be generated)
|
||||
*/
|
||||
protected Object generateKey() {
|
||||
protected Object generateKey(Object result) {
|
||||
if (StringUtils.hasText(this.operation.getKey())) {
|
||||
EvaluationContext evaluationContext = createEvaluationContext(ExpressionEvaluator.NO_RESULT);
|
||||
return evaluator.key(this.operation.getKey(), this.method, evaluationContext);
|
||||
EvaluationContext evaluationContext = createEvaluationContext(result);
|
||||
return CacheAspectSupport.this.evaluator.key(this.operation.getKey(), this.method, evaluationContext);
|
||||
}
|
||||
return keyGenerator.generate(this.target, this.method, this.args);
|
||||
return CacheAspectSupport.this.keyGenerator.generate(this.target, this.method, this.args);
|
||||
}
|
||||
|
||||
private EvaluationContext createEvaluationContext(Object result) {
|
||||
return evaluator.createEvaluationContext(this.caches, this.method, this.args,
|
||||
return CacheAspectSupport.this.evaluator.createEvaluationContext(this.caches, this.method, this.args,
|
||||
this.target, this.targetClass, result);
|
||||
}
|
||||
|
||||
@@ -502,4 +416,25 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||
return this.caches;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class CachePutRequest {
|
||||
|
||||
private final CacheOperationContext context;
|
||||
|
||||
private final Object key;
|
||||
|
||||
public CachePutRequest(CacheOperationContext context, Object key) {
|
||||
this.context = context;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public void apply(Object result) {
|
||||
if(this.context.canPutToCache(result)) {
|
||||
for (Cache cache : this.context.getCaches()) {
|
||||
cache.put(this.key, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ package org.springframework.cache.interceptor;
|
||||
public class CacheEvictOperation extends CacheOperation {
|
||||
|
||||
private boolean cacheWide = false;
|
||||
|
||||
private boolean beforeInvocation = false;
|
||||
|
||||
|
||||
|
||||
@@ -41,14 +41,6 @@ import org.aopalliance.intercept.MethodInvocation;
|
||||
@SuppressWarnings("serial")
|
||||
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
|
||||
|
||||
private static class ThrowableWrapper extends RuntimeException {
|
||||
private final Throwable original;
|
||||
|
||||
ThrowableWrapper(Throwable original) {
|
||||
this.original = original;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
Method method = invocation.getMethod();
|
||||
@@ -70,4 +62,14 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc
|
||||
throw th.original;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ThrowableWrapper extends RuntimeException {
|
||||
private final Throwable original;
|
||||
|
||||
ThrowableWrapper(Throwable original) {
|
||||
this.original = original;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,15 +23,18 @@ import java.util.Set;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class implementing {@link CacheOperation}.
|
||||
* Base class for cache operations.
|
||||
*
|
||||
* @author Costin Leau
|
||||
*/
|
||||
public abstract class CacheOperation {
|
||||
|
||||
private Set<String> cacheNames = Collections.emptySet();
|
||||
|
||||
private String condition = "";
|
||||
|
||||
private String key = "";
|
||||
|
||||
private String name = "";
|
||||
|
||||
|
||||
|
||||
@@ -42,8 +42,10 @@ import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
|
||||
|
||||
private final CacheInterceptor cachingInterceptor = new CacheInterceptor();
|
||||
|
||||
private Pointcut pointcut;
|
||||
|
||||
|
||||
/**
|
||||
* Set a pointcut, i.e a bean that can cause conditional invocation
|
||||
* of the CacheInterceptor depending on method and attributes passed.
|
||||
@@ -58,12 +60,11 @@ public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
|
||||
@Override
|
||||
protected Object createMainInterceptor() {
|
||||
this.cachingInterceptor.afterPropertiesSet();
|
||||
if (this.pointcut != null) {
|
||||
return new DefaultPointcutAdvisor(this.pointcut, this.cachingInterceptor);
|
||||
} else {
|
||||
if (this.pointcut == null) {
|
||||
// Rely on default pointcut.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
return new DefaultPointcutAdvisor(this.pointcut, this.cachingInterceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,7 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri
|
||||
|
||||
private final CacheOperationSource[] cacheOperationSources;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new CompositeCacheOperationSource for the given sources.
|
||||
* @param cacheOperationSources the CacheOperationSource instances to combine
|
||||
|
||||
@@ -27,15 +27,25 @@ import org.springframework.cache.interceptor.KeyGenerator;
|
||||
* Uses the constant value {@value #NULL_PARAM_KEY} for any
|
||||
* {@code null} parameters given.
|
||||
*
|
||||
* <p>NOTE: As this implementation returns only a hash of the parameters
|
||||
* it is possible for key collisions to occur. Since Spring 4.0 the
|
||||
* {@link SimpleKeyGenerator} is used when no explicit key generator
|
||||
* has been defined. This class remains for applications that do not
|
||||
* wish to migrate to the {@link SimpleKeyGenerator}.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
* @see SimpleKeyGenerator
|
||||
* @see org.springframework.cache.annotation.CachingConfigurer
|
||||
*/
|
||||
public class DefaultKeyGenerator implements KeyGenerator {
|
||||
|
||||
public static final int NO_PARAM_KEY = 0;
|
||||
|
||||
public static final int NULL_PARAM_KEY = 53;
|
||||
|
||||
|
||||
@Override
|
||||
public Object generate(Object target, Method method, Object... params) {
|
||||
if (params.length == 1) {
|
||||
|
||||
@@ -42,6 +42,7 @@ class ExpressionEvaluator {
|
||||
|
||||
public static final Object NO_RESULT = new Object();
|
||||
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
// shared param discoverer since it caches data internally
|
||||
|
||||
@@ -85,14 +85,14 @@ class LazyParamAwareEvaluationContext extends StandardEvaluationContext {
|
||||
return;
|
||||
}
|
||||
|
||||
String mKey = toString(this.method);
|
||||
Method targetMethod = this.methodCache.get(mKey);
|
||||
String methodKey = toString(this.method);
|
||||
Method targetMethod = this.methodCache.get(methodKey);
|
||||
if (targetMethod == null) {
|
||||
targetMethod = AopUtils.getMostSpecificMethod(this.method, this.targetClass);
|
||||
if (targetMethod == null) {
|
||||
targetMethod = this.method;
|
||||
}
|
||||
this.methodCache.put(mKey, targetMethod);
|
||||
this.methodCache.put(methodKey, targetMethod);
|
||||
}
|
||||
|
||||
// save arguments as indexed variables
|
||||
|
||||
@@ -42,9 +42,11 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
||||
*/
|
||||
protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class);
|
||||
|
||||
|
||||
/** Keys are method names; values are TransactionAttributes */
|
||||
private Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<String, Collection<CacheOperation>>();
|
||||
|
||||
|
||||
/**
|
||||
* Set a name/attribute map, consisting of method names
|
||||
* (e.g. "myMethod") and CacheOperation instances
|
||||
|
||||
73
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
vendored
Normal file
73
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cache.interceptor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A simple key as returned from the {@link SimpleKeyGenerator}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
* @see SimpleKeyGenerator
|
||||
*/
|
||||
public final class SimpleKey implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
public static final SimpleKey EMPTY = new SimpleKey(new Object[] {});
|
||||
|
||||
|
||||
private final Object[] params;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link SimpleKey} instance.
|
||||
* @param elements the elements of the key
|
||||
*/
|
||||
public SimpleKey(Object[] elements) {
|
||||
Assert.notNull(elements, "Elements must not be null");
|
||||
this.params = new Object[elements.length];
|
||||
System.arraycopy(elements, 0, this.params, 0, elements.length);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj != null && getClass() == obj.getClass()) {
|
||||
return Arrays.equals(this.params, ((SimpleKey) obj).params);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleKey [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
|
||||
}
|
||||
}
|
||||
50
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKeyGenerator.java
vendored
Normal file
50
spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKeyGenerator.java
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cache.interceptor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Simple key generator. Returns the parameter itself if a single non-null value
|
||||
* is given, otherwise returns a {@link SimpleKey} of the parameters.
|
||||
*
|
||||
* <p>Unlike {@link DefaultKeyGenerator}, no collisions will occur with the keys
|
||||
* generated by this class. The returned {@link SimpleKey} object can be safely
|
||||
* used with a {@link org.springframework.cache.concurrent.ConcurrentMapCache},
|
||||
* however, might not be suitable for all {@link org.springframework.cache.Cache}
|
||||
* implementations.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
* @see SimpleKey
|
||||
* @see DefaultKeyGenerator
|
||||
* @see org.springframework.cache.annotation.CachingConfigurer
|
||||
*/
|
||||
public class SimpleKeyGenerator implements KeyGenerator {
|
||||
|
||||
@Override
|
||||
public Object generate(Object target, Method method, Object... params) {
|
||||
if(params.length == 0) {
|
||||
return SimpleKey.EMPTY;
|
||||
}
|
||||
if(params.length == 1 && params[0] != null) {
|
||||
return params[0];
|
||||
}
|
||||
return new SimpleKey(params);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public class SimpleCacheManager extends AbstractCacheManager {
|
||||
|
||||
private Collection<? extends Cache> caches;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the collection of Cache instances to use for this CacheManager.
|
||||
*/
|
||||
|
||||
@@ -45,12 +45,12 @@ public class AnnotatedBeanDefinitionReader {
|
||||
|
||||
private final BeanDefinitionRegistry registry;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
|
||||
private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
|
||||
|
||||
private ConditionEvaluator conditionEvaluator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code AnnotatedBeanDefinitionReader} for the given registry.
|
||||
@@ -79,7 +79,8 @@ public class AnnotatedBeanDefinitionReader {
|
||||
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
|
||||
Assert.notNull(environment, "Environment must not be null");
|
||||
this.registry = registry;
|
||||
this.environment = environment;
|
||||
this.conditionEvaluator = new ConditionEvaluator(registry, environment,
|
||||
null, null, null);
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
|
||||
}
|
||||
|
||||
@@ -92,12 +93,13 @@ public class AnnotatedBeanDefinitionReader {
|
||||
|
||||
/**
|
||||
* Set the Environment to use when evaluating whether
|
||||
* {@link Profile @Profile}-annotated component classes should be registered.
|
||||
* {@link Conditional @Conditional}-annotated component classes should be registered.
|
||||
* <p>The default is a {@link StandardEnvironment}.
|
||||
* @see #registerBean(Class, String, Class...)
|
||||
*/
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
this.conditionEvaluator = new ConditionEvaluator(this.registry, environment,
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,8 +135,7 @@ public class AnnotatedBeanDefinitionReader {
|
||||
|
||||
public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
|
||||
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
|
||||
if (ConditionalAnnotationHelper.shouldSkip(abd, this.registry,
|
||||
this.environment, this.beanNameGenerator)) {
|
||||
if (conditionEvaluator.shouldSkip(abd.getMetadata())) {
|
||||
return;
|
||||
}
|
||||
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -117,7 +117,10 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
|
||||
(metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)) ||
|
||||
annotationType.equals("javax.annotation.ManagedBean") ||
|
||||
annotationType.equals("javax.inject.Named");
|
||||
return (isStereotype && attributes != null && attributes.containsKey("value"));
|
||||
|
||||
return (isStereotype && attributes != null &&
|
||||
attributes.containsKey("value") &&
|
||||
attributes.get("value") instanceof String);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.EnvironmentCapable;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
|
||||
@@ -52,7 +53,6 @@ import org.springframework.util.PatternMatchUtils;
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Phillip Webb
|
||||
* @since 2.5
|
||||
* @see AnnotationConfigApplicationContext#scan
|
||||
* @see org.springframework.stereotype.Component
|
||||
@@ -299,10 +299,6 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
|
||||
* bean definition has been found for the specified name
|
||||
*/
|
||||
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
|
||||
if (ConditionalAnnotationHelper.shouldSkip(beanDefinition, getRegistry(),
|
||||
getEnvironment(), this.beanNameGenerator)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.registry.containsBeanDefinition(beanName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,12 +25,11 @@ import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.EnvironmentCapable;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
@@ -39,7 +38,6 @@ import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternUtils;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
@@ -88,6 +86,8 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
|
||||
private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
|
||||
|
||||
private ConditionEvaluator conditionEvaluator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}.
|
||||
@@ -159,12 +159,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
|
||||
/**
|
||||
* Set the Environment to use when resolving placeholders and evaluating
|
||||
* {@link Profile @Profile}-annotated component classes.
|
||||
* {@link Conditional @Conditional}-annotated component classes.
|
||||
* <p>The default is a {@link StandardEnvironment}
|
||||
* @param environment the Environment to use
|
||||
*/
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
this.conditionEvaluator = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,6 +173,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link BeanDefinitionRegistry} used by this scanner or {@code null}.
|
||||
*/
|
||||
protected BeanDefinitionRegistry getRegistry() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource pattern to use when scanning the classpath.
|
||||
* This value will be appended to each base package name.
|
||||
@@ -333,17 +341,26 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
}
|
||||
for (TypeFilter tf : this.includeFilters) {
|
||||
if (tf.match(metadataReader, this.metadataReaderFactory)) {
|
||||
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
|
||||
if (!metadata.isAnnotated(Profile.class.getName())) {
|
||||
return true;
|
||||
}
|
||||
AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
|
||||
return this.environment.acceptsProfiles(profile.getStringArray("value"));
|
||||
return isConditionMatch(metadataReader);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given class is a candidate component based on any
|
||||
* {@code @Conditional} annotations.
|
||||
* @param metadataReader the ASM ClassReader for the class
|
||||
* @return whether the class qualifies as a candidate component
|
||||
*/
|
||||
private boolean isConditionMatch(MetadataReader metadataReader) {
|
||||
if (this.conditionEvaluator == null) {
|
||||
this.conditionEvaluator = new ConditionEvaluator(getRegistry(),
|
||||
getEnvironment(), null, null, getResourceLoader());
|
||||
}
|
||||
return !conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given bean definition qualifies as candidate.
|
||||
* <p>The default implementation checks whether the class is concrete
|
||||
|
||||
@@ -25,15 +25,18 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
* A single {@code condition} that must be {@linkplain #matches matched} in order
|
||||
* for a component to be registered.
|
||||
*
|
||||
* <p>Conditions are checked immediately before a component bean-definition is due to be
|
||||
* registered and are free to veto registration based on any criteria that can be
|
||||
* determined at that point.
|
||||
* <p>Conditions are checked immediately before the bean-definition is due to be
|
||||
* registered and are free to veto registration based on any criteria that can
|
||||
* be determined at that point.
|
||||
*
|
||||
* <p>Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor}
|
||||
* and take care to never interact with bean instances.
|
||||
* and take care to never interact with bean instances. For more fine-grained control
|
||||
* of conditions that interact with {@code @Configuration} beans consider the
|
||||
* {@link ConfigurationCondition} interface.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
* @see ConfigurationCondition
|
||||
* @see Conditional
|
||||
* @see ConditionContext
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.context.annotation;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
@@ -31,8 +32,8 @@ public interface ConditionContext {
|
||||
|
||||
/**
|
||||
* Returns the {@link BeanDefinitionRegistry} that will hold the bean definition
|
||||
* should the condition match.
|
||||
* @return the registry (never {@code null})
|
||||
* should the condition match or {@code null} if the registry is not available.
|
||||
* @return the registry or {@code null}
|
||||
*/
|
||||
BeanDefinitionRegistry getRegistry();
|
||||
|
||||
@@ -45,13 +46,11 @@ public interface ConditionContext {
|
||||
|
||||
/**
|
||||
* Returns the {@link ConfigurableListableBeanFactory} that will hold the bean
|
||||
* definition should the condition match. If a
|
||||
* {@link ConfigurableListableBeanFactory} is unavailable an
|
||||
* {@link IllegalStateException} will be thrown.
|
||||
* @return the bean factory
|
||||
* @throws IllegalStateException if the bean factory could not be obtained
|
||||
* definition should the condition match or {@code null} if the bean factory is
|
||||
* not available.
|
||||
* @return the bean factory or {@code null}
|
||||
*/
|
||||
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
|
||||
ConfigurableListableBeanFactory getBeanFactory();
|
||||
|
||||
/**
|
||||
* Returns the {@link ResourceLoader} currently being used or {@code null} if the
|
||||
@@ -67,4 +66,6 @@ public interface ConditionContext {
|
||||
*/
|
||||
ClassLoader getClassLoader();
|
||||
|
||||
ApplicationContext getApplicationContext();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.EnvironmentCapable;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Internal class used to evaluate {@link Conditional} annotations.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
*/
|
||||
class ConditionEvaluator {
|
||||
|
||||
private static final String CONDITIONAL_ANNOTATION = Conditional.class.getName();
|
||||
|
||||
|
||||
private final ConditionContextImpl context;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ConditionEvaluator} instance.
|
||||
*/
|
||||
public ConditionEvaluator(BeanDefinitionRegistry registry, Environment environment,
|
||||
ApplicationContext applicationContext, ClassLoader classLoader,
|
||||
ResourceLoader resourceLoader) {
|
||||
this.context = new ConditionContextImpl(registry, environment,
|
||||
applicationContext, classLoader, resourceLoader);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if an item should be skipped based on {@code @Conditional} annotations.
|
||||
* The {@link ConfigurationPhase} will be deduced from the type of item (i.e. a
|
||||
* {@code @Configuration} class will be {@link ConfigurationPhase#PARSE_CONFIGURATION})
|
||||
* @param metadata the meta data
|
||||
* @return if the item should be skipped
|
||||
*/
|
||||
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
|
||||
return shouldSkip(metadata, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an item should be skipped based on {@code @Conditional} annotations.
|
||||
* @param metadata the meta data
|
||||
* @param phase the phase of the call
|
||||
* @return if the item should be skipped
|
||||
*/
|
||||
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
|
||||
if (metadata == null || !metadata.isAnnotated(CONDITIONAL_ANNOTATION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (phase == null) {
|
||||
if (metadata instanceof AnnotationMetadata &&
|
||||
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
|
||||
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
for (String[] conditionClasses : getConditionClasses(metadata)) {
|
||||
for (String conditionClass : conditionClasses) {
|
||||
Condition condition = getCondition(conditionClass, context.getClassLoader());
|
||||
ConfigurationPhase requiredPhase = null;
|
||||
if (condition instanceof ConfigurationCondition) {
|
||||
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
|
||||
}
|
||||
if (requiredPhase == null || requiredPhase == phase) {
|
||||
if (!condition.matches(context, metadata)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
|
||||
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
|
||||
CONDITIONAL_ANNOTATION, true);
|
||||
Object values = attributes == null ? null : attributes.get("value");
|
||||
return (List<String[]>) (values == null ? Collections.emptyList() : values);
|
||||
}
|
||||
|
||||
private Condition getCondition(String conditionClassName,
|
||||
ClassLoader classloader) {
|
||||
Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName,
|
||||
classloader);
|
||||
return (Condition) BeanUtils.instantiateClass(conditionClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of a {@link ConditionContext}.
|
||||
*/
|
||||
private static class ConditionContextImpl implements ConditionContext {
|
||||
|
||||
private BeanDefinitionRegistry registry;
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
|
||||
public ConditionContextImpl(BeanDefinitionRegistry registry,
|
||||
Environment environment, ApplicationContext applicationContext,
|
||||
ClassLoader classLoader, ResourceLoader resourceLoader) {
|
||||
this.registry = registry;
|
||||
this.beanFactory = deduceBeanFactory(registry);
|
||||
this.environment = environment;
|
||||
this.applicationContext = applicationContext;
|
||||
this.classLoader = classLoader;
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
private ConfigurableListableBeanFactory deduceBeanFactory(Object source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
if (source instanceof ConfigurableListableBeanFactory) {
|
||||
return (ConfigurableListableBeanFactory) source;
|
||||
}
|
||||
else if (source instanceof ConfigurableApplicationContext) {
|
||||
return deduceBeanFactory(((ConfigurableApplicationContext) source).getBeanFactory());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanDefinitionRegistry getRegistry() {
|
||||
if (this.registry != null) {
|
||||
return this.registry;
|
||||
}
|
||||
if(getBeanFactory() != null && getBeanFactory() instanceof BeanDefinitionRegistry) {
|
||||
return (BeanDefinitionRegistry) getBeanFactory();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Environment getEnvironment() {
|
||||
if (this.environment != null) {
|
||||
return this.environment;
|
||||
}
|
||||
if (getRegistry() != null && getRegistry() instanceof EnvironmentCapable) {
|
||||
return ((EnvironmentCapable) getRegistry()).getEnvironment();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurableListableBeanFactory getBeanFactory() {
|
||||
Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory");
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLoader getResourceLoader() {
|
||||
if (this.resourceLoader != null) {
|
||||
return this.resourceLoader;
|
||||
}
|
||||
if (registry instanceof ResourceLoader) {
|
||||
return (ResourceLoader) registry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
if (this.classLoader != null) {
|
||||
return this.classLoader;
|
||||
}
|
||||
if (getResourceLoader() != null) {
|
||||
return getResourceLoader().getClassLoader();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
if (this.applicationContext != null) {
|
||||
return this.applicationContext;
|
||||
}
|
||||
if (getRegistry() != null && getRegistry() instanceof ApplicationContext) {
|
||||
return (ApplicationContext) getRegistry();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,11 +22,11 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that a component is is only eligible for registration when all
|
||||
* Indicates that a component is only eligible for registration when all
|
||||
* {@linkplain #value() specified conditions} match.
|
||||
*
|
||||
* <p>A <em>condition</em> is any state that can be determined programmatically
|
||||
* immediately before the bean is due to be created (see {@link Condition} for details).
|
||||
* before the bean definition is due to be registered (see {@link Condition} for details).
|
||||
*
|
||||
* <p>The {@code @Conditional} annotation may be used in any of the following ways:
|
||||
* <ul>
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.EnvironmentCapable;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Helper class used to determine if registration should be skipped based due to a
|
||||
* {@code @Conditional} annotation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
* @see Conditional
|
||||
*/
|
||||
abstract class ConditionalAnnotationHelper {
|
||||
|
||||
private static final String CONDITIONAL_ANNOTATION = Conditional.class.getName();
|
||||
|
||||
|
||||
public static boolean shouldSkip(BeanDefinition beanDefinition,
|
||||
BeanDefinitionRegistry registry, Environment environment,
|
||||
BeanNameGenerator beanNameGenerator) {
|
||||
if (hasCondition(getMetadata(beanDefinition))) {
|
||||
ConditionContextImpl context = new ConditionContextImpl(registry,
|
||||
environment, beanNameGenerator);
|
||||
return shouldSkip(getMetadata(beanDefinition), context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean shouldSkip(BeanMethod beanMethod,
|
||||
BeanDefinitionRegistry registry, Environment environment,
|
||||
BeanNameGenerator beanNameGenerator) {
|
||||
if (hasCondition(getMetadata(beanMethod))) {
|
||||
ConditionContextImpl context = new ConditionContextImpl(registry,
|
||||
environment, beanNameGenerator);
|
||||
return shouldSkip(getMetadata(beanMethod), context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean shouldSkip(ConfigurationClass configurationClass,
|
||||
BeanDefinitionRegistry registry, Environment environment,
|
||||
BeanNameGenerator beanNameGenerator) {
|
||||
if (hasCondition(configurationClass)) {
|
||||
ConditionContextImpl context = new ConditionContextImpl(registry,
|
||||
environment, beanNameGenerator);
|
||||
return shouldSkip(configurationClass, context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean shouldSkip(ConfigurationClass configClass,
|
||||
ConditionContextImpl context) {
|
||||
if (configClass == null) {
|
||||
return false;
|
||||
}
|
||||
return shouldSkip(configClass.getMetadata(), context);
|
||||
}
|
||||
|
||||
private static boolean shouldSkip(AnnotatedTypeMetadata metadata,
|
||||
ConditionContextImpl context) {
|
||||
if (metadata != null) {
|
||||
for (String[] conditionClasses : getConditionClasses(metadata)) {
|
||||
for (String conditionClass : conditionClasses) {
|
||||
if (!getCondition(conditionClass, context.getClassLoader()).matches(
|
||||
context, metadata)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static AnnotatedTypeMetadata getMetadata(BeanMethod beanMethod) {
|
||||
return (beanMethod == null ? null : beanMethod.getMetadata());
|
||||
}
|
||||
|
||||
private static AnnotatedTypeMetadata getMetadata(BeanDefinition beanDefinition) {
|
||||
if (beanDefinition != null && beanDefinition instanceof AnnotatedBeanDefinition) {
|
||||
return ((AnnotatedBeanDefinition) beanDefinition).getMetadata();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean hasCondition(ConfigurationClass configurationClass) {
|
||||
if (configurationClass == null) {
|
||||
return false;
|
||||
}
|
||||
return hasCondition(configurationClass.getMetadata())
|
||||
|| hasCondition(configurationClass.getImportedBy());
|
||||
}
|
||||
|
||||
private static boolean hasCondition(AnnotatedTypeMetadata metadata) {
|
||||
return (metadata != null) && metadata.isAnnotated(CONDITIONAL_ANNOTATION);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
|
||||
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
|
||||
CONDITIONAL_ANNOTATION, true);
|
||||
Object values = attributes == null ? null : attributes.get("value");
|
||||
return (List<String[]>) (values == null ? Collections.emptyList() : values);
|
||||
}
|
||||
|
||||
private static Condition getCondition(String conditionClassName,
|
||||
ClassLoader classloader) {
|
||||
Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName,
|
||||
classloader);
|
||||
return (Condition) BeanUtils.instantiateClass(conditionClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of a {@link ConditionContext}.
|
||||
*/
|
||||
private static class ConditionContextImpl implements ConditionContext {
|
||||
|
||||
private BeanDefinitionRegistry registry;
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
|
||||
public ConditionContextImpl(BeanDefinitionRegistry registry,
|
||||
Environment environment, BeanNameGenerator beanNameGenerator) {
|
||||
Assert.notNull(registry, "Registry must not be null");
|
||||
this.registry = registry;
|
||||
this.beanFactory = deduceBeanFactory(registry);
|
||||
this.environment = environment;
|
||||
if (this.environment == null) {
|
||||
this.environment = deduceEnvironment(registry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ConfigurableListableBeanFactory deduceBeanFactory(Object source) {
|
||||
if (source instanceof ConfigurableListableBeanFactory) {
|
||||
return (ConfigurableListableBeanFactory) source;
|
||||
}
|
||||
else if (source instanceof ConfigurableApplicationContext) {
|
||||
return deduceBeanFactory(((ConfigurableApplicationContext) source).getBeanFactory());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Environment deduceEnvironment(BeanDefinitionRegistry registry) {
|
||||
if (registry instanceof EnvironmentCapable) {
|
||||
return ((EnvironmentCapable) registry).getEnvironment();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanDefinitionRegistry getRegistry() {
|
||||
return this.registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurableListableBeanFactory getBeanFactory() {
|
||||
Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory");
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLoader getResourceLoader() {
|
||||
if (registry instanceof ResourceLoader) {
|
||||
return (ResourceLoader) registry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
ResourceLoader resourceLoader = getResourceLoader();
|
||||
return (resourceLoader == null ? null : resourceLoader.getClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -61,6 +62,9 @@ final class ConfigurationClass {
|
||||
private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
|
||||
new LinkedHashMap<String, Class<? extends BeanDefinitionReader>>();
|
||||
|
||||
private final Set<ImportBeanDefinitionRegistrar> importBeanDefinitionRegistrars =
|
||||
new LinkedHashSet<ImportBeanDefinitionRegistrar>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClass} with the given name.
|
||||
@@ -173,11 +177,18 @@ final class ConfigurationClass {
|
||||
this.importedResources.put(importedResource, readerClass);
|
||||
}
|
||||
|
||||
public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar) {
|
||||
this.importBeanDefinitionRegistrars.add(registrar);
|
||||
}
|
||||
|
||||
public Set<ImportBeanDefinitionRegistrar> getImportBeanDefinitionRegistrars() {
|
||||
return Collections.unmodifiableSet(importBeanDefinitionRegistrars);
|
||||
}
|
||||
|
||||
public Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
|
||||
return this.importedResources;
|
||||
}
|
||||
|
||||
|
||||
public void validate(ProblemReporter problemReporter) {
|
||||
// A configuration class may not be final (CGLIB limitation)
|
||||
if (getMetadata().isAnnotated(Configuration.class.getName())) {
|
||||
@@ -208,7 +219,6 @@ final class ConfigurationClass {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof ConfigurationClass &&
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import static org.springframework.context.annotation.MetadataUtils.attributesFor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -28,6 +26,7 @@ import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
@@ -43,6 +42,8 @@ import org.springframework.beans.factory.support.BeanDefinitionReader;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -52,6 +53,8 @@ import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.context.annotation.MetadataUtils.*;
|
||||
|
||||
/**
|
||||
* Reads a given fully-populated set of ConfigurationClass instances, registering bean
|
||||
* definitions with the given {@link BeanDefinitionRegistry} based on its contents.
|
||||
@@ -84,15 +87,17 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
|
||||
private final BeanNameGenerator importBeanNameGenerator;
|
||||
|
||||
private final ConditionEvaluator conditionEvaluator;
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
|
||||
* to populate the given {@link BeanDefinitionRegistry}.
|
||||
*/
|
||||
public ConfigurationClassBeanDefinitionReader(
|
||||
BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
|
||||
ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
|
||||
ResourceLoader resourceLoader, Environment environment, BeanNameGenerator importBeanNameGenerator) {
|
||||
BeanDefinitionRegistry registry, ApplicationContext applicationContext,
|
||||
SourceExtractor sourceExtractor, ProblemReporter problemReporter,
|
||||
MetadataReaderFactory metadataReaderFactory, ResourceLoader resourceLoader,
|
||||
Environment environment, BeanNameGenerator importBeanNameGenerator) {
|
||||
|
||||
this.registry = registry;
|
||||
this.sourceExtractor = sourceExtractor;
|
||||
@@ -101,6 +106,8 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.environment = environment;
|
||||
this.importBeanNameGenerator = importBeanNameGenerator;
|
||||
this.conditionEvaluator = new ConditionEvaluator(registry, environment,
|
||||
applicationContext, null, resourceLoader);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +116,9 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
* based on its contents.
|
||||
*/
|
||||
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
|
||||
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
|
||||
for (ConfigurationClass configClass : configurationModel) {
|
||||
loadBeanDefinitionsForConfigurationClass(configClass);
|
||||
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +126,13 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
* Read a particular {@link ConfigurationClass}, registering bean definitions for the
|
||||
* class itself, all its {@link Bean} methods
|
||||
*/
|
||||
public void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
||||
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
|
||||
TrackedConditionEvaluator trackedConditionEvaluator) {
|
||||
if (trackedConditionEvaluator.shouldSkip(configClass)) {
|
||||
removeBeanDefinition(configClass);
|
||||
return;
|
||||
}
|
||||
|
||||
if (configClass.isImported()) {
|
||||
registerBeanDefinitionForImportedConfigurationClass(configClass);
|
||||
}
|
||||
@@ -126,6 +140,17 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
loadBeanDefinitionsForBeanMethod(beanMethod);
|
||||
}
|
||||
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
|
||||
loadBeanDefinitionsFromRegistrars(configClass.getMetadata(), configClass.getImportBeanDefinitionRegistrars());
|
||||
}
|
||||
|
||||
private void removeBeanDefinition(ConfigurationClass configClass) {
|
||||
if (StringUtils.hasLength(configClass.getBeanName())) {
|
||||
try {
|
||||
this.registry.removeBeanDefinition(configClass.getBeanName());
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,8 +178,8 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
* with the BeanDefinitionRegistry based on its contents.
|
||||
*/
|
||||
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
|
||||
if (ConditionalAnnotationHelper.shouldSkip(beanMethod, this.registry,
|
||||
this.environment, this.importBeanNameGenerator)) {
|
||||
if (conditionEvaluator.shouldSkip(beanMethod.getMetadata(),
|
||||
ConfigurationPhase.REGISTER_BEAN)) {
|
||||
return;
|
||||
}
|
||||
ConfigurationClass configClass = beanMethod.getConfigurationClass();
|
||||
@@ -300,6 +325,14 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadBeanDefinitionsFromRegistrars(
|
||||
AnnotationMetadata importingClassMetadata,
|
||||
Set<ImportBeanDefinitionRegistrar> importBeanDefinitionRegistrars) {
|
||||
for (ImportBeanDefinitionRegistrar registrar : importBeanDefinitionRegistrars) {
|
||||
registrar.registerBeanDefinitions(importingClassMetadata, this.registry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition
|
||||
@@ -357,4 +390,32 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate {@Code @Conditional} annotations, tracking results and taking into
|
||||
* account 'imported by'.
|
||||
*/
|
||||
private class TrackedConditionEvaluator {
|
||||
|
||||
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<ConfigurationClass, Boolean>();
|
||||
|
||||
public boolean shouldSkip(ConfigurationClass configClass) {
|
||||
Boolean skip = this.skipped.get(configClass);
|
||||
if (skip == null) {
|
||||
if (configClass.isImported()) {
|
||||
if (shouldSkip(configClass.getImportedBy())) {
|
||||
// The config that imported this one was skipped, therefore we are skipped
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (skip == null) {
|
||||
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(),
|
||||
ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
this.skipped.put(configClass, skip);
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -411,7 +411,8 @@ class ConfigurationClassEnhancer {
|
||||
Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
|
||||
Assert.state(field != null, "Unable to find generated bean factory field");
|
||||
Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
|
||||
Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory);
|
||||
Assert.state(beanFactory != null, "The BeanFactory has not been injected into the @Configuration class");
|
||||
Assert.state(beanFactory instanceof ConfigurableBeanFactory, "The injected BeanFactory is not a ConfigurableBeanFactory");
|
||||
return (ConfigurableBeanFactory) beanFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -47,7 +46,10 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReader;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
|
||||
import org.springframework.core.NestedIOException;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
@@ -62,7 +64,6 @@ import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.context.annotation.MetadataUtils.*;
|
||||
@@ -75,7 +76,8 @@ import static org.springframework.context.annotation.MetadataUtils.*;
|
||||
*
|
||||
* <p>This class helps separate the concern of parsing the structure of a Configuration
|
||||
* class from the concern of registering BeanDefinition objects based on the
|
||||
* content of that model.
|
||||
* content of that model (with the exception of {@code @ComponentScan} annotations which
|
||||
* need to be registered immediately).
|
||||
*
|
||||
* <p>This ASM-based implementation avoids reflection and eager class loading in order to
|
||||
* interoperate effectively with lazy class loading in a Spring ApplicationContext.
|
||||
@@ -107,8 +109,6 @@ class ConfigurationClassParser {
|
||||
|
||||
private final BeanDefinitionRegistry registry;
|
||||
|
||||
private final BeanNameGenerator beanNameGenerator;
|
||||
|
||||
private final ComponentScanAnnotationParser componentScanParser;
|
||||
|
||||
private final Set<ConfigurationClass> configurationClasses = new LinkedHashSet<ConfigurationClass>();
|
||||
@@ -121,6 +121,8 @@ class ConfigurationClassParser {
|
||||
|
||||
private final List<DeferredImportSelectorHolder> deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
|
||||
|
||||
private final ConditionEvaluator conditionEvaluator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassParser} instance that will be used
|
||||
@@ -128,16 +130,18 @@ class ConfigurationClassParser {
|
||||
*/
|
||||
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
|
||||
ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
|
||||
BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
|
||||
BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry,
|
||||
ApplicationContext applicationContext) {
|
||||
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.problemReporter = problemReporter;
|
||||
this.environment = environment;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.registry = registry;
|
||||
this.beanNameGenerator = componentScanBeanNameGenerator;
|
||||
this.componentScanParser = new ComponentScanAnnotationParser(
|
||||
resourceLoader, environment, componentScanBeanNameGenerator, registry);
|
||||
this.conditionEvaluator = new ConditionEvaluator(registry, environment,
|
||||
applicationContext, null, resourceLoader);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +169,7 @@ class ConfigurationClassParser {
|
||||
* @param beanName may be null, but if populated represents the bean id
|
||||
* (assumes that this configuration class was configured via XML)
|
||||
*/
|
||||
public void parse(String className, String beanName) throws IOException {
|
||||
protected final void parse(String className, String beanName) throws IOException {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
processConfigurationClass(new ConfigurationClass(reader, beanName));
|
||||
}
|
||||
@@ -175,13 +179,14 @@ class ConfigurationClassParser {
|
||||
* @param clazz the Class to parse
|
||||
* @param beanName must not be null (as of Spring 3.1.1)
|
||||
*/
|
||||
public void parse(Class<?> clazz, String beanName) throws IOException {
|
||||
protected final void parse(Class<?> clazz, String beanName) throws IOException {
|
||||
processConfigurationClass(new ConfigurationClass(clazz, beanName));
|
||||
}
|
||||
|
||||
|
||||
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
|
||||
if (ConditionalAnnotationHelper.shouldSkip(configClass, this.registry, this.environment, this.beanNameGenerator)) {
|
||||
|
||||
if (conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,56 +202,58 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
// Recursively process the configuration class and its superclass hierarchy.
|
||||
AnnotationMetadata metadata = configClass.getMetadata();
|
||||
SourceClass sourceClass = asSourceClass(configClass);
|
||||
do {
|
||||
metadata = doProcessConfigurationClass(configClass, metadata);
|
||||
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
|
||||
}
|
||||
while (metadata != null);
|
||||
while (sourceClass != null);
|
||||
|
||||
this.configurationClasses.add(configClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return annotation metadata of superclass, {@code null} if none found or previously processed
|
||||
* Apply processing and build a complete {@link ConfigurationClass} by reading the
|
||||
* annotations, members and methods from the source class. This method can be called
|
||||
* multiple times as relevant sources are discovered.
|
||||
* @param configClass the configuration class being build
|
||||
* @param sourceClass a source class
|
||||
* @return the superclass, {@code null} if none found or previously processed
|
||||
*/
|
||||
protected AnnotationMetadata doProcessConfigurationClass(
|
||||
ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
|
||||
protected final SourceClass doProcessConfigurationClass(
|
||||
ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
|
||||
|
||||
// recursively process any member (nested) classes first
|
||||
processMemberClasses(configClass, metadata);
|
||||
processMemberClasses(configClass, sourceClass);
|
||||
|
||||
// process any @PropertySource annotations
|
||||
AnnotationAttributes propertySource = attributesFor(metadata, org.springframework.context.annotation.PropertySource.class);
|
||||
AnnotationAttributes propertySource = attributesFor(sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class);
|
||||
if (propertySource != null) {
|
||||
processPropertySource(propertySource);
|
||||
}
|
||||
|
||||
// process any @ComponentScan annotations
|
||||
AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class);
|
||||
AnnotationAttributes componentScan = attributesFor(sourceClass.getMetadata(), ComponentScan.class);
|
||||
if (componentScan != null) {
|
||||
// the config class is annotated with @ComponentScan -> perform the scan immediately
|
||||
Set<BeanDefinitionHolder> scannedBeanDefinitions =
|
||||
this.componentScanParser.parse(componentScan, metadata.getClassName());
|
||||
if (!conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
|
||||
Set<BeanDefinitionHolder> scannedBeanDefinitions =
|
||||
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
|
||||
|
||||
// check the set of scanned definitions for any further config classes and parse recursively if necessary
|
||||
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
|
||||
if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
|
||||
this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
|
||||
// check the set of scanned definitions for any further config classes and parse recursively if necessary
|
||||
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
|
||||
if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
|
||||
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process any @Import annotations
|
||||
Set<Object> imports = new LinkedHashSet<Object>();
|
||||
Set<Object> visited = new LinkedHashSet<Object>();
|
||||
collectImports(metadata, imports, visited);
|
||||
if (!imports.isEmpty()) {
|
||||
processImport(configClass, imports, true);
|
||||
}
|
||||
processImports(configClass, getImports(sourceClass), true);
|
||||
|
||||
// process any @ImportResource annotations
|
||||
if (metadata.isAnnotated(ImportResource.class.getName())) {
|
||||
AnnotationAttributes importResource = attributesFor(metadata, ImportResource.class);
|
||||
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
|
||||
AnnotationAttributes importResource = attributesFor(sourceClass.getMetadata(), ImportResource.class);
|
||||
String[] resources = importResource.getStringArray("value");
|
||||
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
|
||||
for (String resource : resources) {
|
||||
@@ -255,34 +262,22 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
// process individual @Bean methods
|
||||
Set<MethodMetadata> beanMethods = metadata.getAnnotatedMethods(Bean.class.getName());
|
||||
Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
|
||||
for (MethodMetadata methodMetadata : beanMethods) {
|
||||
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
|
||||
}
|
||||
|
||||
// process superclass, if any
|
||||
if (metadata.hasSuperClass()) {
|
||||
String superclass = metadata.getSuperClassName();
|
||||
if (sourceClass.getMetadata().hasSuperClass()) {
|
||||
String superclass = sourceClass.getMetadata().getSuperClassName();
|
||||
if (!this.knownSuperclasses.containsKey(superclass)) {
|
||||
this.knownSuperclasses.put(superclass, configClass);
|
||||
// superclass found, return its annotation metadata and recurse
|
||||
if (metadata instanceof StandardAnnotationMetadata) {
|
||||
Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
|
||||
return new StandardAnnotationMetadata(clazz.getSuperclass(), true);
|
||||
try {
|
||||
return sourceClass.getSuperClass();
|
||||
}
|
||||
else if (superclass.startsWith("java")) {
|
||||
// never load core JDK classes via ASM, in particular not java.lang.Object!
|
||||
try {
|
||||
return new StandardAnnotationMetadata(
|
||||
this.resourceLoader.getClassLoader().loadClass(superclass), true);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superclass);
|
||||
return reader.getAnnotationMetadata();
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,24 +288,13 @@ class ConfigurationClassParser {
|
||||
|
||||
/**
|
||||
* Register member (nested) classes that happen to be configuration classes themselves.
|
||||
* @param metadata the metadata representation of the containing class
|
||||
* @param sourceClass the source class to process
|
||||
* @throws IOException if there is any problem reading metadata from a member class
|
||||
*/
|
||||
private void processMemberClasses(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
|
||||
if (metadata instanceof StandardAnnotationMetadata) {
|
||||
for (Class<?> memberClass : ((StandardAnnotationMetadata) metadata).getIntrospectedClass().getDeclaredClasses()) {
|
||||
if (ConfigurationClassUtils.isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) {
|
||||
processConfigurationClass(new ConfigurationClass(memberClass, configClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (String memberClassName : metadata.getMemberClassNames()) {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName);
|
||||
AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata();
|
||||
if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) {
|
||||
processConfigurationClass(new ConfigurationClass(reader, configClass));
|
||||
}
|
||||
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
|
||||
for (SourceClass memberClass : sourceClass.getMemberClasses()) {
|
||||
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())) {
|
||||
processConfigurationClass(memberClass.asConfigClass(configClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,59 +334,46 @@ class ConfigurationClassParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code @Import} class, considering all meta-annotations.
|
||||
*/
|
||||
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
|
||||
Set<SourceClass> imports = new LinkedHashSet<SourceClass>();
|
||||
Set<SourceClass> visited = new LinkedHashSet<SourceClass>();
|
||||
collectImports(sourceClass, imports, visited);
|
||||
return imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collect all declared {@code @Import} values. Unlike most
|
||||
* meta-annotations it is valid to have several {@code @Import}s declared with
|
||||
* different values, the usual process or returning values from the first
|
||||
* meta-annotation on a class is not sufficient.
|
||||
* <p>For example, it is common for a {@code @Configuration} class to declare direct
|
||||
* <p>
|
||||
* For example, it is common for a {@code @Configuration} class to declare direct
|
||||
* {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
|
||||
* annotation.
|
||||
* @param metadata the metadata representation of the class to search
|
||||
*
|
||||
* @param sourceClass the class to search
|
||||
* @param imports the imports collected so far
|
||||
* @param visited used to track visited classes to prevent infinite recursion
|
||||
* @throws IOException if there is any problem reading metadata from the named class
|
||||
*/
|
||||
private void collectImports(AnnotationMetadata metadata, Set<Object> imports, Set<Object> visited) throws IOException {
|
||||
String className = metadata.getClassName();
|
||||
if (visited.add(className)) {
|
||||
if (metadata instanceof StandardAnnotationMetadata) {
|
||||
StandardAnnotationMetadata stdMetadata = (StandardAnnotationMetadata) metadata;
|
||||
for (Annotation ann : stdMetadata.getIntrospectedClass().getAnnotations()) {
|
||||
if (!ann.annotationType().getName().startsWith("java") && !(ann instanceof Import)) {
|
||||
collectImports(new StandardAnnotationMetadata(ann.annotationType()), imports, visited);
|
||||
}
|
||||
}
|
||||
Map<String, Object> attributes = stdMetadata.getAnnotationAttributes(Import.class.getName(), false);
|
||||
if (attributes != null) {
|
||||
Class[] value = (Class[]) attributes.get("value");
|
||||
if (!ObjectUtils.isEmpty(value)) {
|
||||
imports.addAll(Arrays.asList(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (String annotationType : metadata.getAnnotationTypes()) {
|
||||
if (!className.startsWith("java") && !className.equals(Import.class.getName())) {
|
||||
try {
|
||||
collectImports(
|
||||
new StandardAnnotationMetadata(this.resourceLoader.getClassLoader().loadClass(annotationType)),
|
||||
imports, visited);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String, Object> attributes = metadata.getAnnotationAttributes(Import.class.getName(), true);
|
||||
if (attributes != null) {
|
||||
String[] value = (String[]) attributes.get("value");
|
||||
if (!ObjectUtils.isEmpty(value)) {
|
||||
imports.addAll(Arrays.asList(value));
|
||||
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports,
|
||||
Set<SourceClass> visited) throws IOException {
|
||||
try {
|
||||
if (visited.add(sourceClass)) {
|
||||
for (SourceClass annotation : sourceClass.getAnnotations()) {
|
||||
if(!annotation.getMetadata().getClassName().startsWith("java") && !annotation.isAssignable(Import.class)) {
|
||||
collectImports(annotation, imports, visited);
|
||||
}
|
||||
}
|
||||
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new NestedIOException("Unable to collect imports", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void processDeferredImportSelectors() {
|
||||
@@ -411,16 +382,21 @@ class ConfigurationClassParser {
|
||||
try {
|
||||
ConfigurationClass configClass = deferredImport.getConfigurationClass();
|
||||
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
|
||||
processImport(configClass, Arrays.asList(imports), false);
|
||||
processImports(configClass, asSourceClasses(imports), false);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
catch (Exception ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class: ", ex);
|
||||
}
|
||||
}
|
||||
this.deferredImportSelectors.clear();
|
||||
}
|
||||
|
||||
private void processImport(ConfigurationClass configClass, Collection<?> classesToImport, boolean checkForCircularImports) throws IOException {
|
||||
private void processImports(ConfigurationClass configClass,
|
||||
Collection<SourceClass> sourceClasses, boolean checkForCircularImports)
|
||||
throws IOException {
|
||||
if(sourceClasses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (checkForCircularImports && this.importStack.contains(configClass)) {
|
||||
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
|
||||
}
|
||||
@@ -428,37 +404,33 @@ class ConfigurationClassParser {
|
||||
this.importStack.push(configClass);
|
||||
AnnotationMetadata importingClassMetadata = configClass.getMetadata();
|
||||
try {
|
||||
for (Object candidate : classesToImport) {
|
||||
Object candidateToCheck = (candidate instanceof Class ? (Class) candidate :
|
||||
this.metadataReaderFactory.getMetadataReader((String) candidate));
|
||||
if (checkAssignability(ImportSelector.class, candidateToCheck)) {
|
||||
for (SourceClass candidate : sourceClasses) {
|
||||
if (candidate.isAssignable(ImportSelector.class)) {
|
||||
// the candidate class is an ImportSelector -> delegate to it to determine imports
|
||||
Class<?> candidateClass = (candidate instanceof Class ? (Class) candidate :
|
||||
this.resourceLoader.getClassLoader().loadClass((String) candidate));
|
||||
Class<?> candidateClass = candidate.loadClass();
|
||||
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
|
||||
invokeAwareMethods(selector);
|
||||
if(selector instanceof DeferredImportSelector) {
|
||||
this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
|
||||
}
|
||||
else {
|
||||
processImport(configClass, Arrays.asList(selector.selectImports(importingClassMetadata)), false);
|
||||
String[] importClassNames = selector.selectImports(importingClassMetadata);
|
||||
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
|
||||
processImports(configClass, importSourceClasses, false);
|
||||
}
|
||||
}
|
||||
else if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) {
|
||||
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
|
||||
// the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
|
||||
Class<?> candidateClass = (candidate instanceof Class ? (Class) candidate :
|
||||
this.resourceLoader.getClassLoader().loadClass((String) candidate));
|
||||
Class<?> candidateClass = candidate.loadClass();
|
||||
ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
|
||||
invokeAwareMethods(registrar);
|
||||
registrar.registerBeanDefinitions(importingClassMetadata, this.registry);
|
||||
configClass.addImportBeanDefinitionRegistrar(registrar);
|
||||
}
|
||||
else {
|
||||
// candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class
|
||||
this.importStack.registerImport(importingClassMetadata.getClassName(),
|
||||
(candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate));
|
||||
processConfigurationClass((candidateToCheck instanceof Class ?
|
||||
new ConfigurationClass((Class) candidateToCheck, configClass) :
|
||||
new ConfigurationClass((MetadataReader) candidateToCheck, configClass)));
|
||||
candidate.getMetadata().getClassName());
|
||||
processConfigurationClass(candidate.asConfigClass(configClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,21 +443,15 @@ class ConfigurationClassParser {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkAssignability(Class<?> clazz, Object candidate) throws IOException {
|
||||
if (candidate instanceof Class) {
|
||||
return clazz.isAssignableFrom((Class) candidate);
|
||||
}
|
||||
else {
|
||||
return new AssignableTypeFilter(clazz).match((MetadataReader) candidate, this.metadataReaderFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
|
||||
* {@link BeanFactoryAware} contracts if implemented by the given {@code bean}.
|
||||
*/
|
||||
private void invokeAwareMethods(Object importStrategyBean) {
|
||||
if (importStrategyBean instanceof Aware) {
|
||||
if (importStrategyBean instanceof EnvironmentAware) {
|
||||
((EnvironmentAware) importStrategyBean).setEnvironment(this.environment);
|
||||
}
|
||||
if (importStrategyBean instanceof ResourceLoaderAware) {
|
||||
((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader);
|
||||
}
|
||||
@@ -524,10 +490,68 @@ class ConfigurationClassParser {
|
||||
return this.importStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}.
|
||||
*/
|
||||
public SourceClass asSourceClass(ConfigurationClass configurationClass)
|
||||
throws IOException {
|
||||
try {
|
||||
AnnotationMetadata metadata = configurationClass.getMetadata();
|
||||
if (metadata instanceof StandardAnnotationMetadata) {
|
||||
return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
|
||||
}
|
||||
return asSourceClass(configurationClass.getMetadata().getClassName());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to obtain a {@link SourceClass} from a {@link Class}.
|
||||
*/
|
||||
public SourceClass asSourceClass(Class<?> classType)
|
||||
throws ClassNotFoundException, IOException {
|
||||
try {
|
||||
// Sanity test that we can read annotations, if not fall back to ASM
|
||||
classType.getAnnotations();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return asSourceClass(classType.getName());
|
||||
}
|
||||
return new SourceClass(classType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to obtain {@link SourceClass}s from class names.
|
||||
*/
|
||||
public Collection<SourceClass> asSourceClasses(String[] classNamess)
|
||||
throws ClassNotFoundException, IOException {
|
||||
List<SourceClass> annotatedClasses = new ArrayList<SourceClass>();
|
||||
for (String className : classNamess) {
|
||||
annotatedClasses.add(asSourceClass(className));
|
||||
}
|
||||
return annotatedClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to obtain a {@link SourceClass} from a class name.
|
||||
*/
|
||||
public SourceClass asSourceClass(String className)
|
||||
throws ClassNotFoundException, IOException {
|
||||
if (className.startsWith("java")) {
|
||||
// Never use ASM for core java types
|
||||
return new SourceClass(this.resourceLoader.getClassLoader().loadClass(
|
||||
className));
|
||||
}
|
||||
return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
|
||||
}
|
||||
|
||||
|
||||
interface ImportRegistry {
|
||||
|
||||
String getImportingClassFor(String importedClass);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -607,6 +631,151 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple wrapper that allows annotated source classes to be dealt with in a uniform
|
||||
* manor, regardless of how they are loaded.
|
||||
*/
|
||||
private class SourceClass {
|
||||
|
||||
private final Object source; // Class or MetaDataReader
|
||||
|
||||
private final AnnotationMetadata metadata;
|
||||
|
||||
|
||||
private SourceClass(Object source) {
|
||||
this.source = source;
|
||||
if (source instanceof Class<?>) {
|
||||
this.metadata = new StandardAnnotationMetadata((Class<?>) source, true);
|
||||
}
|
||||
else {
|
||||
this.metadata = ((MetadataReader) source).getAnnotationMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Class<?> loadClass() throws ClassNotFoundException {
|
||||
if(source instanceof Class<?>) {
|
||||
return (Class<?>) source;
|
||||
}
|
||||
String className = ((MetadataReader) source).getClassMetadata().getClassName();
|
||||
return resourceLoader.getClassLoader().loadClass(className);
|
||||
}
|
||||
|
||||
public boolean isAssignable(Class<?> clazz) throws IOException {
|
||||
if (source instanceof Class) {
|
||||
return clazz.isAssignableFrom((Class) source);
|
||||
}
|
||||
return new AssignableTypeFilter(clazz).match((MetadataReader) source,
|
||||
metadataReaderFactory);
|
||||
}
|
||||
|
||||
public ConfigurationClass asConfigClass(ConfigurationClass importedBy)
|
||||
throws IOException {
|
||||
if (this.source instanceof Class<?>) {
|
||||
return new ConfigurationClass((Class<?>) this.source, importedBy);
|
||||
}
|
||||
return new ConfigurationClass((MetadataReader) source, importedBy);
|
||||
}
|
||||
|
||||
public Collection<SourceClass> getMemberClasses() throws IOException {
|
||||
List<SourceClass> members = new ArrayList<SourceClass>();
|
||||
if (source instanceof Class<?>) {
|
||||
Class<?> sourceClass = (Class<?>) source;
|
||||
for (Class<?> declaredClass : sourceClass.getDeclaredClasses()) {
|
||||
try {
|
||||
members.add(asSourceClass(declaredClass));
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
MetadataReader sourceReader = (MetadataReader) source;
|
||||
for (String memberClassName : sourceReader.getClassMetadata().getMemberClassNames()) {
|
||||
try {
|
||||
members.add(asSourceClass(memberClassName));
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
public SourceClass getSuperClass() throws ClassNotFoundException, IOException {
|
||||
if (source instanceof Class<?>) {
|
||||
return asSourceClass(((Class<?>) source).getSuperclass());
|
||||
}
|
||||
return asSourceClass(((MetadataReader) source).getClassMetadata().getSuperClassName());
|
||||
}
|
||||
|
||||
public Set<SourceClass> getAnnotations() throws ClassNotFoundException, IOException {
|
||||
Set<SourceClass> annotations = new LinkedHashSet<SourceClass>();
|
||||
for(String annotation : getMetadata().getAnnotationTypes()) {
|
||||
annotations.add(getRelated(annotation));
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public Collection<SourceClass> getAnnotationAttributes(String annotationType,
|
||||
String attribute) throws ClassNotFoundException, IOException {
|
||||
Map<String, Object> annotationAttributes = getMetadata().getAnnotationAttributes(
|
||||
annotationType, true);
|
||||
if (annotationAttributes == null
|
||||
|| !annotationAttributes.containsKey(attribute)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
String[] classNames = (String[]) annotationAttributes.get(attribute);
|
||||
Set<SourceClass> rtn = new LinkedHashSet<SourceClass>();
|
||||
for (String className : classNames) {
|
||||
rtn.add(getRelated(className));
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
private SourceClass getRelated(String className) throws IOException,
|
||||
ClassNotFoundException {
|
||||
if (source instanceof Class<?>) {
|
||||
try {
|
||||
Class<?> clazz = resourceLoader.getClassLoader().loadClass(className);
|
||||
return asSourceClass(clazz);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
}
|
||||
}
|
||||
return asSourceClass(className);
|
||||
}
|
||||
|
||||
public AnnotationMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof SourceClass) {
|
||||
return toString().equals(obj.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getMetadata().getClassName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Problem} registered upon detection of a circular {@link Import}.
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -26,17 +27,19 @@ import java.util.Stack;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
|
||||
import org.springframework.beans.factory.config.SingletonBeanRegistry;
|
||||
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
|
||||
import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
|
||||
@@ -47,8 +50,11 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
|
||||
import org.springframework.context.annotation.ConfigurationClassParser.ImportRegistry;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
@@ -86,7 +92,8 @@ import static org.springframework.context.annotation.AnnotationConfigUtils.*;
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
|
||||
ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
|
||||
ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware, ApplicationContextAware,
|
||||
Ordered {
|
||||
|
||||
private static final String IMPORT_AWARE_PROCESSOR_BEAN_NAME =
|
||||
ConfigurationClassPostProcessor.class.getName() + ".importAwareProcessor";
|
||||
@@ -94,6 +101,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
private static final String IMPORT_REGISTRY_BEAN_NAME =
|
||||
ConfigurationClassPostProcessor.class.getName() + ".importRegistry";
|
||||
|
||||
private static final String ENHANCED_CONFIGURATION_PROCESSOR_BEAN_NAME =
|
||||
ConfigurationClassPostProcessor.class.getName() + ".enhancedConfigurationProcessor";
|
||||
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -103,6 +113,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
|
||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
@@ -131,6 +143,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@link SourceExtractor} to use for generated bean definitions
|
||||
* that correspond to {@link Bean} factory methods.
|
||||
@@ -190,6 +203,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
|
||||
@@ -214,6 +233,10 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
iabpp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
registry.registerBeanDefinition(IMPORT_AWARE_PROCESSOR_BEAN_NAME, iabpp);
|
||||
|
||||
RootBeanDefinition ecbpp = new RootBeanDefinition(EnhancedConfigurationBeanPostProcessor.class);
|
||||
ecbpp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
registry.registerBeanDefinition(ENHANCED_CONFIGURATION_PROCESSOR_BEAN_NAME, ecbpp);
|
||||
|
||||
int registryId = System.identityHashCode(registry);
|
||||
if (this.registriesPostProcessed.contains(registryId)) {
|
||||
throw new IllegalStateException(
|
||||
@@ -280,7 +303,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
// Parse each @Configuration class
|
||||
ConfigurationClassParser parser = new ConfigurationClassParser(
|
||||
this.metadataReaderFactory, this.problemReporter, this.environment,
|
||||
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
|
||||
this.resourceLoader, this.componentScanBeanNameGenerator, registry,
|
||||
this.applicationContext);
|
||||
parser.parse(configCandidates);
|
||||
parser.validate();
|
||||
|
||||
@@ -302,15 +326,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
// Read the model and create bean definitions based on its content
|
||||
if (this.reader == null) {
|
||||
this.reader = new ConfigurationClassBeanDefinitionReader(
|
||||
registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory,
|
||||
registry, this.applicationContext, this.sourceExtractor,
|
||||
this.problemReporter, this.metadataReaderFactory,
|
||||
this.resourceLoader, this.environment, this.importBeanNameGenerator);
|
||||
}
|
||||
for (ConfigurationClass configurationClass : parser.getConfigurationClasses()) {
|
||||
if (!ConditionalAnnotationHelper.shouldSkip(configurationClass, registry,
|
||||
this.environment, this.importBeanNameGenerator)) {
|
||||
reader.loadBeanDefinitionsForConfigurationClass(configurationClass);
|
||||
}
|
||||
}
|
||||
|
||||
reader.loadBeanDefinitions(parser.getConfigurationClasses());
|
||||
|
||||
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
|
||||
if (singletonRegistry != null) {
|
||||
@@ -366,6 +387,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
|
||||
private static class ImportAwareBeanPostProcessor implements PriorityOrdered, BeanFactoryAware, BeanPostProcessor {
|
||||
|
||||
@@ -410,4 +436,40 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link InstantiationAwareBeanPostProcessorAdapter} that ensures
|
||||
* {@link EnhancedConfiguration} beans are injected with the {@link BeanFactory}
|
||||
* before the {@link AutowiredAnnotationBeanPostProcessor} runs (SPR-10668).
|
||||
*/
|
||||
private static class EnhancedConfigurationBeanPostProcessor extends
|
||||
InstantiationAwareBeanPostProcessorAdapter implements PriorityOrdered,
|
||||
BeanFactoryAware {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyValues postProcessPropertyValues(PropertyValues pvs,
|
||||
PropertyDescriptor[] pds, Object bean, String beanName)
|
||||
throws BeansException {
|
||||
// Inject the BeanFactory before AutowiredAnnotationBeanPostProcessor's
|
||||
// postProcessPropertyValues method attempts to auto-wire other configuration
|
||||
// beans.
|
||||
if (bean instanceof EnhancedConfiguration) {
|
||||
((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
|
||||
}
|
||||
return pvs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,7 +27,6 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Utilities for processing @{@link Configuration} classes.
|
||||
@@ -105,7 +104,6 @@ abstract class ConfigurationClassUtils {
|
||||
return false; // do not consider an interface or an annotation
|
||||
}
|
||||
return metadata.isAnnotated(Import.class.getName()) ||
|
||||
metadata.isAnnotated(Component.class.getName()) ||
|
||||
metadata.hasAnnotatedMethods(Bean.class.getName());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
/**
|
||||
* A {@link Condition} that offers more fine-grained control when used with
|
||||
* {@code @Configuration}. Allows certain {@link Condition}s to adapt when they match
|
||||
* based on the configuration phase. For example, a condition that checks if a bean has
|
||||
* already been registered might choose to only be evaluated during the
|
||||
* {@link ConfigurationPhase#REGISTER_BEAN REGISTER_BEAN} {@link ConfigurationPhase}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface ConfigurationCondition extends Condition {
|
||||
|
||||
/**
|
||||
* Returns the {@link ConfigurationPhase} in which the condition should be evaluated.
|
||||
*/
|
||||
ConfigurationPhase getConfigurationPhase();
|
||||
|
||||
/**
|
||||
* The various configuration phases where the condition could be evaluated.
|
||||
*/
|
||||
public static enum ConfigurationPhase {
|
||||
|
||||
/**
|
||||
* The {@link Condition} should be evaluated as a {@code @Configuration} class is
|
||||
* being parsed.
|
||||
*
|
||||
* <p>If the condition does not match at this point the {@code @Configuration}
|
||||
* class will not be added.
|
||||
*/
|
||||
PARSE_CONFIGURATION,
|
||||
|
||||
/**
|
||||
* The {@link Condition} should be evaluated when adding a regular (non
|
||||
* {@code @Configuration}) bean. The condition will not prevent
|
||||
* {@code @Configuration} classes from being added.
|
||||
*
|
||||
* <p>At the time that the condition is evaluated all {@code @Configuration}s
|
||||
* will have been parsed.
|
||||
*/
|
||||
REGISTER_BEAN
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
|
||||
* methods will be called prior to {@link #registerBeanDefinitions}:
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.context.EnvironmentAware}</li>
|
||||
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
|
||||
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
|
||||
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
|
||||
* methods will be called prior to {@link #selectImports}:
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.context.EnvironmentAware}</li>
|
||||
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
|
||||
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
|
||||
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -181,7 +181,7 @@ public class RmiRegistryFactoryBean implements FactoryBean<Registry>, Initializi
|
||||
throws RemoteException {
|
||||
|
||||
if (registryHost != null) {
|
||||
// Host explictly specified: only lookup possible.
|
||||
// Host explicitly specified: only lookup possible.
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
|
||||
}
|
||||
|
||||
@@ -83,7 +83,12 @@ public class MethodValidationPostProcessor extends AbstractAdvisingBeanPostProce
|
||||
* <p>Default is the default ValidatorFactory's default Validator.
|
||||
*/
|
||||
public void setValidator(Validator validator) {
|
||||
this.validator = validator;
|
||||
if(validator instanceof LocalValidatorFactoryBean) {
|
||||
this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
|
||||
}
|
||||
else {
|
||||
this.validator = validator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user