001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.aop.support;
018
019import java.util.Map;
020import java.util.WeakHashMap;
021
022import org.aopalliance.intercept.MethodInvocation;
023
024import org.springframework.aop.DynamicIntroductionAdvice;
025import org.springframework.aop.IntroductionInterceptor;
026import org.springframework.aop.ProxyMethodInvocation;
027import org.springframework.lang.Nullable;
028import org.springframework.util.ReflectionUtils;
029
030/**
031 * Convenient implementation of the
032 * {@link org.springframework.aop.IntroductionInterceptor} interface.
033 *
034 * <p>This differs from {@link DelegatingIntroductionInterceptor} in that a single
035 * instance of this class can be used to advise multiple target objects, and each target
036 * object will have its <i>own</i> delegate (whereas DelegatingIntroductionInterceptor
037 * shares the same delegate, and hence the same state across all targets).
038 *
039 * <p>The {@code suppressInterface} method can be used to suppress interfaces
040 * implemented by the delegate class but which should not be introduced to the
041 * owning AOP proxy.
042 *
043 * <p>An instance of this class is serializable if the delegates are.
044 *
045 * <p><i>Note: There are some implementation similarities between this class and
046 * {@link DelegatingIntroductionInterceptor} that suggest a possible refactoring
047 * to extract a common ancestor class in the future.</i>
048 *
049 * @author Adrian Colyer
050 * @author Juergen Hoeller
051 * @since 2.0
052 * @see #suppressInterface
053 * @see DelegatingIntroductionInterceptor
054 */
055@SuppressWarnings("serial")
056public class DelegatePerTargetObjectIntroductionInterceptor extends IntroductionInfoSupport
057                implements IntroductionInterceptor {
058
059        /**
060         * Hold weak references to keys as we don't want to interfere with garbage collection..
061         */
062        private final Map<Object, Object> delegateMap = new WeakHashMap<>();
063
064        private Class<?> defaultImplType;
065
066        private Class<?> interfaceType;
067
068
069        public DelegatePerTargetObjectIntroductionInterceptor(Class<?> defaultImplType, Class<?> interfaceType) {
070                this.defaultImplType = defaultImplType;
071                this.interfaceType = interfaceType;
072                // Create a new delegate now (but don't store it in the map).
073                // We do this for two reasons:
074                // 1) to fail early if there is a problem instantiating delegates
075                // 2) to populate the interface map once and once only
076                Object delegate = createNewDelegate();
077                implementInterfacesOnObject(delegate);
078                suppressInterface(IntroductionInterceptor.class);
079                suppressInterface(DynamicIntroductionAdvice.class);
080        }
081
082
083        /**
084         * Subclasses may need to override this if they want to perform custom
085         * behaviour in around advice. However, subclasses should invoke this
086         * method, which handles introduced interfaces and forwarding to the target.
087         */
088        @Override
089        @Nullable
090        public Object invoke(MethodInvocation mi) throws Throwable {
091                if (isMethodOnIntroducedInterface(mi)) {
092                        Object delegate = getIntroductionDelegateFor(mi.getThis());
093
094                        // Using the following method rather than direct reflection,
095                        // we get correct handling of InvocationTargetException
096                        // if the introduced method throws an exception.
097                        Object retVal = AopUtils.invokeJoinpointUsingReflection(delegate, mi.getMethod(), mi.getArguments());
098
099                        // Massage return value if possible: if the delegate returned itself,
100                        // we really want to return the proxy.
101                        if (retVal == delegate && mi instanceof ProxyMethodInvocation) {
102                                retVal = ((ProxyMethodInvocation) mi).getProxy();
103                        }
104                        return retVal;
105                }
106
107                return doProceed(mi);
108        }
109
110        /**
111         * Proceed with the supplied {@link org.aopalliance.intercept.MethodInterceptor}.
112         * Subclasses can override this method to intercept method invocations on the
113         * target object which is useful when an introduction needs to monitor the object
114         * that it is introduced into. This method is <strong>never</strong> called for
115         * {@link MethodInvocation MethodInvocations} on the introduced interfaces.
116         */
117        protected Object doProceed(MethodInvocation mi) throws Throwable {
118                // If we get here, just pass the invocation on.
119                return mi.proceed();
120        }
121
122        private Object getIntroductionDelegateFor(Object targetObject) {
123                synchronized (this.delegateMap) {
124                        if (this.delegateMap.containsKey(targetObject)) {
125                                return this.delegateMap.get(targetObject);
126                        }
127                        else {
128                                Object delegate = createNewDelegate();
129                                this.delegateMap.put(targetObject, delegate);
130                                return delegate;
131                        }
132                }
133        }
134
135        private Object createNewDelegate() {
136                try {
137                        return ReflectionUtils.accessibleConstructor(this.defaultImplType).newInstance();
138                }
139                catch (Throwable ex) {
140                        throw new IllegalArgumentException("Cannot create default implementation for '" +
141                                        this.interfaceType.getName() + "' mixin (" + this.defaultImplType.getName() + "): " + ex);
142                }
143        }
144
145}