001/*
002 * Copyright 2002-2019 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.framework;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.lang.reflect.Method;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.List;
026import java.util.Map;
027import java.util.concurrent.ConcurrentHashMap;
028
029import org.aopalliance.aop.Advice;
030
031import org.springframework.aop.Advisor;
032import org.springframework.aop.DynamicIntroductionAdvice;
033import org.springframework.aop.IntroductionAdvisor;
034import org.springframework.aop.IntroductionInfo;
035import org.springframework.aop.TargetSource;
036import org.springframework.aop.support.DefaultIntroductionAdvisor;
037import org.springframework.aop.support.DefaultPointcutAdvisor;
038import org.springframework.aop.target.EmptyTargetSource;
039import org.springframework.aop.target.SingletonTargetSource;
040import org.springframework.lang.Nullable;
041import org.springframework.util.Assert;
042import org.springframework.util.ClassUtils;
043import org.springframework.util.CollectionUtils;
044
045/**
046 * Base class for AOP proxy configuration managers.
047 * These are not themselves AOP proxies, but subclasses of this class are
048 * normally factories from which AOP proxy instances are obtained directly.
049 *
050 * <p>This class frees subclasses of the housekeeping of Advices
051 * and Advisors, but doesn't actually implement proxy creation
052 * methods, which are provided by subclasses.
053 *
054 * <p>This class is serializable; subclasses need not be.
055 * This class is used to hold snapshots of proxies.
056 *
057 * @author Rod Johnson
058 * @author Juergen Hoeller
059 * @see org.springframework.aop.framework.AopProxy
060 */
061public class AdvisedSupport extends ProxyConfig implements Advised {
062
063        /** use serialVersionUID from Spring 2.0 for interoperability. */
064        private static final long serialVersionUID = 2651364800145442165L;
065
066
067        /**
068         * Canonical TargetSource when there's no target, and behavior is
069         * supplied by the advisors.
070         */
071        public static final TargetSource EMPTY_TARGET_SOURCE = EmptyTargetSource.INSTANCE;
072
073
074        /** Package-protected to allow direct access for efficiency. */
075        TargetSource targetSource = EMPTY_TARGET_SOURCE;
076
077        /** Whether the Advisors are already filtered for the specific target class. */
078        private boolean preFiltered = false;
079
080        /** The AdvisorChainFactory to use. */
081        AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
082
083        /** Cache with Method as key and advisor chain List as value. */
084        private transient Map<MethodCacheKey, List<Object>> methodCache;
085
086        /**
087         * Interfaces to be implemented by the proxy. Held in List to keep the order
088         * of registration, to create JDK proxy with specified order of interfaces.
089         */
090        private List<Class<?>> interfaces = new ArrayList<>();
091
092        /**
093         * List of Advisors. If an Advice is added, it will be wrapped
094         * in an Advisor before being added to this List.
095         */
096        private List<Advisor> advisors = new ArrayList<>();
097
098        /**
099         * Array updated on changes to the advisors list, which is easier
100         * to manipulate internally.
101         */
102        private Advisor[] advisorArray = new Advisor[0];
103
104
105        /**
106         * No-arg constructor for use as a JavaBean.
107         */
108        public AdvisedSupport() {
109                this.methodCache = new ConcurrentHashMap<>(32);
110        }
111
112        /**
113         * Create a AdvisedSupport instance with the given parameters.
114         * @param interfaces the proxied interfaces
115         */
116        public AdvisedSupport(Class<?>... interfaces) {
117                this();
118                setInterfaces(interfaces);
119        }
120
121
122        /**
123         * Set the given object as target.
124         * Will create a SingletonTargetSource for the object.
125         * @see #setTargetSource
126         * @see org.springframework.aop.target.SingletonTargetSource
127         */
128        public void setTarget(Object target) {
129                setTargetSource(new SingletonTargetSource(target));
130        }
131
132        @Override
133        public void setTargetSource(@Nullable TargetSource targetSource) {
134                this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE);
135        }
136
137        @Override
138        public TargetSource getTargetSource() {
139                return this.targetSource;
140        }
141
142        /**
143         * Set a target class to be proxied, indicating that the proxy
144         * should be castable to the given class.
145         * <p>Internally, an {@link org.springframework.aop.target.EmptyTargetSource}
146         * for the given target class will be used. The kind of proxy needed
147         * will be determined on actual creation of the proxy.
148         * <p>This is a replacement for setting a "targetSource" or "target",
149         * for the case where we want a proxy based on a target class
150         * (which can be an interface or a concrete class) without having
151         * a fully capable TargetSource available.
152         * @see #setTargetSource
153         * @see #setTarget
154         */
155        public void setTargetClass(@Nullable Class<?> targetClass) {
156                this.targetSource = EmptyTargetSource.forClass(targetClass);
157        }
158
159        @Override
160        @Nullable
161        public Class<?> getTargetClass() {
162                return this.targetSource.getTargetClass();
163        }
164
165        @Override
166        public void setPreFiltered(boolean preFiltered) {
167                this.preFiltered = preFiltered;
168        }
169
170        @Override
171        public boolean isPreFiltered() {
172                return this.preFiltered;
173        }
174
175        /**
176         * Set the advisor chain factory to use.
177         * <p>Default is a {@link DefaultAdvisorChainFactory}.
178         */
179        public void setAdvisorChainFactory(AdvisorChainFactory advisorChainFactory) {
180                Assert.notNull(advisorChainFactory, "AdvisorChainFactory must not be null");
181                this.advisorChainFactory = advisorChainFactory;
182        }
183
184        /**
185         * Return the advisor chain factory to use (never {@code null}).
186         */
187        public AdvisorChainFactory getAdvisorChainFactory() {
188                return this.advisorChainFactory;
189        }
190
191
192        /**
193         * Set the interfaces to be proxied.
194         */
195        public void setInterfaces(Class<?>... interfaces) {
196                Assert.notNull(interfaces, "Interfaces must not be null");
197                this.interfaces.clear();
198                for (Class<?> ifc : interfaces) {
199                        addInterface(ifc);
200                }
201        }
202
203        /**
204         * Add a new proxied interface.
205         * @param intf the additional interface to proxy
206         */
207        public void addInterface(Class<?> intf) {
208                Assert.notNull(intf, "Interface must not be null");
209                if (!intf.isInterface()) {
210                        throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface");
211                }
212                if (!this.interfaces.contains(intf)) {
213                        this.interfaces.add(intf);
214                        adviceChanged();
215                }
216        }
217
218        /**
219         * Remove a proxied interface.
220         * <p>Does nothing if the given interface isn't proxied.
221         * @param intf the interface to remove from the proxy
222         * @return {@code true} if the interface was removed; {@code false}
223         * if the interface was not found and hence could not be removed
224         */
225        public boolean removeInterface(Class<?> intf) {
226                return this.interfaces.remove(intf);
227        }
228
229        @Override
230        public Class<?>[] getProxiedInterfaces() {
231                return ClassUtils.toClassArray(this.interfaces);
232        }
233
234        @Override
235        public boolean isInterfaceProxied(Class<?> intf) {
236                for (Class<?> proxyIntf : this.interfaces) {
237                        if (intf.isAssignableFrom(proxyIntf)) {
238                                return true;
239                        }
240                }
241                return false;
242        }
243
244
245        @Override
246        public final Advisor[] getAdvisors() {
247                return this.advisorArray;
248        }
249
250        @Override
251        public void addAdvisor(Advisor advisor) {
252                int pos = this.advisors.size();
253                addAdvisor(pos, advisor);
254        }
255
256        @Override
257        public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
258                if (advisor instanceof IntroductionAdvisor) {
259                        validateIntroductionAdvisor((IntroductionAdvisor) advisor);
260                }
261                addAdvisorInternal(pos, advisor);
262        }
263
264        @Override
265        public boolean removeAdvisor(Advisor advisor) {
266                int index = indexOf(advisor);
267                if (index == -1) {
268                        return false;
269                }
270                else {
271                        removeAdvisor(index);
272                        return true;
273                }
274        }
275
276        @Override
277        public void removeAdvisor(int index) throws AopConfigException {
278                if (isFrozen()) {
279                        throw new AopConfigException("Cannot remove Advisor: Configuration is frozen.");
280                }
281                if (index < 0 || index > this.advisors.size() - 1) {
282                        throw new AopConfigException("Advisor index " + index + " is out of bounds: " +
283                                        "This configuration only has " + this.advisors.size() + " advisors.");
284                }
285
286                Advisor advisor = this.advisors.remove(index);
287                if (advisor instanceof IntroductionAdvisor) {
288                        IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
289                        // We need to remove introduction interfaces.
290                        for (Class<?> ifc : ia.getInterfaces()) {
291                                removeInterface(ifc);
292                        }
293                }
294
295                updateAdvisorArray();
296                adviceChanged();
297        }
298
299        @Override
300        public int indexOf(Advisor advisor) {
301                Assert.notNull(advisor, "Advisor must not be null");
302                return this.advisors.indexOf(advisor);
303        }
304
305        @Override
306        public boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException {
307                Assert.notNull(a, "Advisor a must not be null");
308                Assert.notNull(b, "Advisor b must not be null");
309                int index = indexOf(a);
310                if (index == -1) {
311                        return false;
312                }
313                removeAdvisor(index);
314                addAdvisor(index, b);
315                return true;
316        }
317
318        /**
319         * Add all of the given advisors to this proxy configuration.
320         * @param advisors the advisors to register
321         */
322        public void addAdvisors(Advisor... advisors) {
323                addAdvisors(Arrays.asList(advisors));
324        }
325
326        /**
327         * Add all of the given advisors to this proxy configuration.
328         * @param advisors the advisors to register
329         */
330        public void addAdvisors(Collection<Advisor> advisors) {
331                if (isFrozen()) {
332                        throw new AopConfigException("Cannot add advisor: Configuration is frozen.");
333                }
334                if (!CollectionUtils.isEmpty(advisors)) {
335                        for (Advisor advisor : advisors) {
336                                if (advisor instanceof IntroductionAdvisor) {
337                                        validateIntroductionAdvisor((IntroductionAdvisor) advisor);
338                                }
339                                Assert.notNull(advisor, "Advisor must not be null");
340                                this.advisors.add(advisor);
341                        }
342                        updateAdvisorArray();
343                        adviceChanged();
344                }
345        }
346
347        private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
348                advisor.validateInterfaces();
349                // If the advisor passed validation, we can make the change.
350                Class<?>[] ifcs = advisor.getInterfaces();
351                for (Class<?> ifc : ifcs) {
352                        addInterface(ifc);
353                }
354        }
355
356        private void addAdvisorInternal(int pos, Advisor advisor) throws AopConfigException {
357                Assert.notNull(advisor, "Advisor must not be null");
358                if (isFrozen()) {
359                        throw new AopConfigException("Cannot add advisor: Configuration is frozen.");
360                }
361                if (pos > this.advisors.size()) {
362                        throw new IllegalArgumentException(
363                                        "Illegal position " + pos + " in advisor list with size " + this.advisors.size());
364                }
365                this.advisors.add(pos, advisor);
366                updateAdvisorArray();
367                adviceChanged();
368        }
369
370        /**
371         * Bring the array up to date with the list.
372         */
373        protected final void updateAdvisorArray() {
374                this.advisorArray = this.advisors.toArray(new Advisor[0]);
375        }
376
377        /**
378         * Allows uncontrolled access to the {@link List} of {@link Advisor Advisors}.
379         * <p>Use with care, and remember to {@link #updateAdvisorArray() refresh the advisor array}
380         * and {@link #adviceChanged() fire advice changed events} when making any modifications.
381         */
382        protected final List<Advisor> getAdvisorsInternal() {
383                return this.advisors;
384        }
385
386
387        @Override
388        public void addAdvice(Advice advice) throws AopConfigException {
389                int pos = this.advisors.size();
390                addAdvice(pos, advice);
391        }
392
393        /**
394         * Cannot add introductions this way unless the advice implements IntroductionInfo.
395         */
396        @Override
397        public void addAdvice(int pos, Advice advice) throws AopConfigException {
398                Assert.notNull(advice, "Advice must not be null");
399                if (advice instanceof IntroductionInfo) {
400                        // We don't need an IntroductionAdvisor for this kind of introduction:
401                        // It's fully self-describing.
402                        addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
403                }
404                else if (advice instanceof DynamicIntroductionAdvice) {
405                        // We need an IntroductionAdvisor for this kind of introduction.
406                        throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
407                }
408                else {
409                        addAdvisor(pos, new DefaultPointcutAdvisor(advice));
410                }
411        }
412
413        @Override
414        public boolean removeAdvice(Advice advice) throws AopConfigException {
415                int index = indexOf(advice);
416                if (index == -1) {
417                        return false;
418                }
419                else {
420                        removeAdvisor(index);
421                        return true;
422                }
423        }
424
425        @Override
426        public int indexOf(Advice advice) {
427                Assert.notNull(advice, "Advice must not be null");
428                for (int i = 0; i < this.advisors.size(); i++) {
429                        Advisor advisor = this.advisors.get(i);
430                        if (advisor.getAdvice() == advice) {
431                                return i;
432                        }
433                }
434                return -1;
435        }
436
437        /**
438         * Is the given advice included in any advisor within this proxy configuration?
439         * @param advice the advice to check inclusion of
440         * @return whether this advice instance is included
441         */
442        public boolean adviceIncluded(@Nullable Advice advice) {
443                if (advice != null) {
444                        for (Advisor advisor : this.advisors) {
445                                if (advisor.getAdvice() == advice) {
446                                        return true;
447                                }
448                        }
449                }
450                return false;
451        }
452
453        /**
454         * Count advices of the given class.
455         * @param adviceClass the advice class to check
456         * @return the count of the interceptors of this class or subclasses
457         */
458        public int countAdvicesOfType(@Nullable Class<?> adviceClass) {
459                int count = 0;
460                if (adviceClass != null) {
461                        for (Advisor advisor : this.advisors) {
462                                if (adviceClass.isInstance(advisor.getAdvice())) {
463                                        count++;
464                                }
465                        }
466                }
467                return count;
468        }
469
470
471        /**
472         * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects
473         * for the given method, based on this configuration.
474         * @param method the proxied method
475         * @param targetClass the target class
476         * @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
477         */
478        public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
479                MethodCacheKey cacheKey = new MethodCacheKey(method);
480                List<Object> cached = this.methodCache.get(cacheKey);
481                if (cached == null) {
482                        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
483                                        this, method, targetClass);
484                        this.methodCache.put(cacheKey, cached);
485                }
486                return cached;
487        }
488
489        /**
490         * Invoked when advice has changed.
491         */
492        protected void adviceChanged() {
493                this.methodCache.clear();
494        }
495
496        /**
497         * Call this method on a new instance created by the no-arg constructor
498         * to create an independent copy of the configuration from the given object.
499         * @param other the AdvisedSupport object to copy configuration from
500         */
501        protected void copyConfigurationFrom(AdvisedSupport other) {
502                copyConfigurationFrom(other, other.targetSource, new ArrayList<>(other.advisors));
503        }
504
505        /**
506         * Copy the AOP configuration from the given AdvisedSupport object,
507         * but allow substitution of a fresh TargetSource and a given interceptor chain.
508         * @param other the AdvisedSupport object to take proxy configuration from
509         * @param targetSource the new TargetSource
510         * @param advisors the Advisors for the chain
511         */
512        protected void copyConfigurationFrom(AdvisedSupport other, TargetSource targetSource, List<Advisor> advisors) {
513                copyFrom(other);
514                this.targetSource = targetSource;
515                this.advisorChainFactory = other.advisorChainFactory;
516                this.interfaces = new ArrayList<>(other.interfaces);
517                for (Advisor advisor : advisors) {
518                        if (advisor instanceof IntroductionAdvisor) {
519                                validateIntroductionAdvisor((IntroductionAdvisor) advisor);
520                        }
521                        Assert.notNull(advisor, "Advisor must not be null");
522                        this.advisors.add(advisor);
523                }
524                updateAdvisorArray();
525                adviceChanged();
526        }
527
528        /**
529         * Build a configuration-only copy of this AdvisedSupport,
530         * replacing the TargetSource.
531         */
532        AdvisedSupport getConfigurationOnlyCopy() {
533                AdvisedSupport copy = new AdvisedSupport();
534                copy.copyFrom(this);
535                copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic());
536                copy.advisorChainFactory = this.advisorChainFactory;
537                copy.interfaces = this.interfaces;
538                copy.advisors = this.advisors;
539                copy.updateAdvisorArray();
540                return copy;
541        }
542
543
544        //---------------------------------------------------------------------
545        // Serialization support
546        //---------------------------------------------------------------------
547
548        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
549                // Rely on default serialization; just initialize state after deserialization.
550                ois.defaultReadObject();
551
552                // Initialize transient fields.
553                this.methodCache = new ConcurrentHashMap<>(32);
554        }
555
556
557        @Override
558        public String toProxyConfigString() {
559                return toString();
560        }
561
562        /**
563         * For debugging/diagnostic use.
564         */
565        @Override
566        public String toString() {
567                StringBuilder sb = new StringBuilder(getClass().getName());
568                sb.append(": ").append(this.interfaces.size()).append(" interfaces ");
569                sb.append(ClassUtils.classNamesToString(this.interfaces)).append("; ");
570                sb.append(this.advisors.size()).append(" advisors ");
571                sb.append(this.advisors).append("; ");
572                sb.append("targetSource [").append(this.targetSource).append("]; ");
573                sb.append(super.toString());
574                return sb.toString();
575        }
576
577
578        /**
579         * Simple wrapper class around a Method. Used as the key when
580         * caching methods, for efficient equals and hashCode comparisons.
581         */
582        private static final class MethodCacheKey implements Comparable<MethodCacheKey> {
583
584                private final Method method;
585
586                private final int hashCode;
587
588                public MethodCacheKey(Method method) {
589                        this.method = method;
590                        this.hashCode = method.hashCode();
591                }
592
593                @Override
594                public boolean equals(@Nullable Object other) {
595                        return (this == other || (other instanceof MethodCacheKey &&
596                                        this.method == ((MethodCacheKey) other).method));
597                }
598
599                @Override
600                public int hashCode() {
601                        return this.hashCode;
602                }
603
604                @Override
605                public String toString() {
606                        return this.method.toString();
607                }
608
609                @Override
610                public int compareTo(MethodCacheKey other) {
611                        int result = this.method.getName().compareTo(other.method.getName());
612                        if (result == 0) {
613                                result = this.method.toString().compareTo(other.method.toString());
614                        }
615                        return result;
616                }
617        }
618
619}