Package org.springframework.cache.interceptor

Source Code of org.springframework.cache.interceptor.CacheAspectSupport

/*
* 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 org.apache.commons.logging.Log;
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.*;

import java.lang.reflect.Method;
import java.util.*;

/**
* Base class for caching aspects, such as the {@link CacheInterceptor}
* or an AspectJ aspect.
* <p/>
* <p>This enables the underlying Spring caching infrastructure to be
* used easily to implement an aspect for any aspect system.
* <p/>
* <p>Subclasses are responsible for calling methods in this class in
* the correct order.
* <p/>
* <p>Uses the <b>Strategy</b> design pattern. A {@link CacheManager}
* implementation will perform the actual cache management, and a
* {@link CacheOperationSource} is used for determining caching
* operations.
* <p/>
* <p>A cache aspect is serializable if its {@code CacheManager} and
* {@code CacheOperationSource} are serializable.
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @author Sam Brannen
* @since 3.1
*/
public abstract class CacheAspectSupport implements InitializingBean {

    protected final Log logger = LogFactory.getLog(getClass());

    private final ExpressionEvaluator evaluator = new ExpressionEvaluator();

    private CacheManager cacheManager;

    private CacheOperationSource cacheOperationSource;

    private KeyGenerator keyGenerator = new SimpleKeyGenerator();

    private boolean initialized = false;


    /**
     * Set the CacheManager that this cache aspect should delegate to.
     */
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    /**
     * Return the CacheManager that this cache aspect delegates to.
     */
    public CacheManager getCacheManager() {
        return this.cacheManager;
    }

    /**
     * Set one or more cache operation sources which are used to find the cache
     * attributes. If more than one source is provided, they will be aggregated using a
     * {@link CompositeCacheOperationSource}.
     *
     * @param cacheOperationSources must not be {@code null}
     */
    public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) {
        Assert.notEmpty(cacheOperationSources);
        this.cacheOperationSource = (cacheOperationSources.length > 1 ?
                new CompositeCacheOperationSource(cacheOperationSources) : cacheOperationSources[0]);
    }

    /**
     * Return the CacheOperationSource for this cache aspect.
     */
    public CacheOperationSource getCacheOperationSource() {
        return this.cacheOperationSource;
    }

    /**
     * Set the KeyGenerator for this cache aspect.
     * Default is {@link DefaultKeyGenerator}.
     */
    public void setKeyGenerator(KeyGenerator keyGenerator) {
        this.keyGenerator = keyGenerator;
    }

    /**
     * Return the KeyGenerator for this cache aspect,
     */
    public KeyGenerator getKeyGenerator() {
        return this.keyGenerator;
    }

    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<? extends 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;

        Collection<CacheOperationContext> cacheOperationContexts = contexts.get(CacheableOperation.class);
        // We only attempt to get a cached result if there are has @Cachable
        if (!cacheOperationContexts.isEmpty()) {
            result = findCachedResult(cacheOperationContexts);
        }

        // 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 || findInAnyCaches(contexts, 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 findInAnyCaches(Collection<CacheOperationContext> contexts, Object key) {
        for (CacheOperationContext context : contexts) {
            ValueWrapper wrapper = findInCaches(context, key);
            if (wrapper != null) {
                return wrapper;
            }
        }
        return null;
    }

    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;
    }


    public interface Invoker {

        Object invoke();
    }


    private class CacheOperationContexts {

        private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts =
                new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>();

        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));
            }
        }

        public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
            Collection<CacheOperationContext> result = this.contexts.get(operationClass);
            return (result != null ? result : Collections.<CacheOperationContext>emptyList());
        }
    }


    protected class CacheOperationContext {

        private final CacheOperation operation;

        private final Method method;

        private final Object[] args;

        private final Object target;

        private final Class<?> targetClass;

        private final Collection<? extends Cache> caches;

        public CacheOperationContext(CacheOperation operation, Method method,
                                     Object[] args, Object target, Class<?> targetClass) {
            this.operation = operation;
            this.method = method;
            this.args = extractArgs(method, args);
            this.target = target;
            this.targetClass = targetClass;
            this.caches = CacheAspectSupport.this.getCaches(operation);
        }

        private Object[] extractArgs(Method method, Object[] args) {
            if (!method.isVarArgs()) {
                return args;
            }
            Object[] varArgs = (Object[]) args[args.length - 1];
            Object[] combinedArgs = new Object[args.length - 1 + varArgs.length];
            System.arraycopy(args, 0, combinedArgs, 0, args.length - 1);
            System.arraycopy(varArgs, 0, combinedArgs, args.length - 1, varArgs.length);
            return combinedArgs;
        }

        protected boolean isConditionPassing(Object result) {
            if (StringUtils.hasText(this.operation.getCondition())) {
                EvaluationContext evaluationContext = createEvaluationContext(result);
                return evaluator.condition(this.operation.getCondition(), this.method, evaluationContext);
            }
            return true;
        }

        protected boolean canPutToCache(Object value) {
            String unless = "";
            if (this.operation instanceof CacheableOperation) {
                unless = ((CacheableOperation) this.operation).getUnless();
            } else if (this.operation instanceof CachePutOperation) {
                unless = ((CachePutOperation) this.operation).getUnless();
            }
            if (StringUtils.hasText(unless)) {
                EvaluationContext evaluationContext = createEvaluationContext(value);
                return !evaluator.unless(unless, this.method, evaluationContext);
            }
            return true;
        }

        /**
         * Computes the key for the given caching operation.
         *
         * @return generated key (null if none can be generated)
         */
        protected Object generateKey(Object result) {
            if (StringUtils.hasText(this.operation.getKey())) {
                EvaluationContext evaluationContext = createEvaluationContext(result);
                return evaluator.key(this.operation.getKey(), this.method, evaluationContext);
            }
            return keyGenerator.generate(this.target, this.method, this.args);
        }

        private EvaluationContext createEvaluationContext(Object result) {
            return evaluator.createEvaluationContext(this.caches, this.method, this.args, this.target, this.targetClass, result);
        }

        protected Collection<? extends Cache> getCaches() {
            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);
                }
            }
        }
    }

}
TOP

Related Classes of org.springframework.cache.interceptor.CacheAspectSupport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.