Java tutorial
/* * Copyright 2002-2018 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 * * https://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.aop.framework; import java.io.IOException; import java.io.ObjectInputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.aop.Advice; import org.springframework.aop.Advisor; import org.springframework.aop.DynamicIntroductionAdvice; import org.springframework.aop.IntroductionAdvisor; import org.springframework.aop.IntroductionInfo; import org.springframework.aop.TargetSource; import org.springframework.aop.support.DefaultIntroductionAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.target.EmptyTargetSource; import org.springframework.aop.target.SingletonTargetSource; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; /** * Base class for AOP proxy configuration managers. * These are not themselves AOP proxies, but subclasses of this class are * normally factories from which AOP proxy instances are obtained directly. * * <p>This class frees subclasses of the housekeeping of Advices * and Advisors, but doesn't actually implement proxy creation * methods, which are provided by subclasses. * * <p>This class is serializable; subclasses need not be. * This class is used to hold snapshots of proxies. * * @author Rod Johnson * @author Juergen Hoeller * @see org.springframework.aop.framework.AopProxy */ public class AdvisedSupport extends ProxyConfig implements Advised { /** use serialVersionUID from Spring 2.0 for interoperability. */ private static final long serialVersionUID = 2651364800145442165L; /** * Canonical TargetSource when there's no target, and behavior is * supplied by the advisors. */ public static final TargetSource EMPTY_TARGET_SOURCE = EmptyTargetSource.INSTANCE; /** Package-protected to allow direct access for efficiency. */ TargetSource targetSource = EMPTY_TARGET_SOURCE; /** Whether the Advisors are already filtered for the specific target class. */ private boolean preFiltered = false; /** The AdvisorChainFactory to use. */ AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory(); /** Cache with Method as key and advisor chain List as value. */ private transient Map<MethodCacheKey, List<Object>> methodCache; /** * Interfaces to be implemented by the proxy. Held in List to keep the order * of registration, to create JDK proxy with specified order of interfaces. */ private List<Class<?>> interfaces = new ArrayList<>(); /** * List of Advisors. If an Advice is added, it will be wrapped * in an Advisor before being added to this List. */ private List<Advisor> advisors = new ArrayList<>(); /** * Array updated on changes to the advisors list, which is easier * to manipulate internally. */ private Advisor[] advisorArray = new Advisor[0]; /** * No-arg constructor for use as a JavaBean. */ public AdvisedSupport() { this.methodCache = new ConcurrentHashMap<>(32); } /** * Create a AdvisedSupport instance with the given parameters. * @param interfaces the proxied interfaces */ public AdvisedSupport(Class<?>... interfaces) { this(); setInterfaces(interfaces); } /** * Set the given object as target. * Will create a SingletonTargetSource for the object. * @see #setTargetSource * @see org.springframework.aop.target.SingletonTargetSource */ public void setTarget(Object target) { setTargetSource(new SingletonTargetSource(target)); } @Override public void setTargetSource(@Nullable TargetSource targetSource) { this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE); } @Override public TargetSource getTargetSource() { return this.targetSource; } /** * Set a target class to be proxied, indicating that the proxy * should be castable to the given class. * <p>Internally, an {@link org.springframework.aop.target.EmptyTargetSource} * for the given target class will be used. The kind of proxy needed * will be determined on actual creation of the proxy. * <p>This is a replacement for setting a "targetSource" or "target", * for the case where we want a proxy based on a target class * (which can be an interface or a concrete class) without having * a fully capable TargetSource available. * @see #setTargetSource * @see #setTarget */ public void setTargetClass(@Nullable Class<?> targetClass) { this.targetSource = EmptyTargetSource.forClass(targetClass); } @Override @Nullable public Class<?> getTargetClass() { return this.targetSource.getTargetClass(); } @Override public void setPreFiltered(boolean preFiltered) { this.preFiltered = preFiltered; } @Override public boolean isPreFiltered() { return this.preFiltered; } /** * Set the advisor chain factory to use. * <p>Default is a {@link DefaultAdvisorChainFactory}. */ public void setAdvisorChainFactory(AdvisorChainFactory advisorChainFactory) { Assert.notNull(advisorChainFactory, "AdvisorChainFactory must not be null"); this.advisorChainFactory = advisorChainFactory; } /** * Return the advisor chain factory to use (never {@code null}). */ public AdvisorChainFactory getAdvisorChainFactory() { return this.advisorChainFactory; } /** * Set the interfaces to be proxied. */ public void setInterfaces(Class<?>... interfaces) { Assert.notNull(interfaces, "Interfaces must not be null"); this.interfaces.clear(); for (Class<?> ifc : interfaces) { addInterface(ifc); } } /** * Add a new proxied interface. * @param intf the additional interface to proxy */ public void addInterface(Class<?> intf) { Assert.notNull(intf, "Interface must not be null"); if (!intf.isInterface()) { throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface"); } if (!this.interfaces.contains(intf)) { this.interfaces.add(intf); adviceChanged(); } } /** * Remove a proxied interface. * <p>Does nothing if the given interface isn't proxied. * @param intf the interface to remove from the proxy * @return {@code true} if the interface was removed; {@code false} * if the interface was not found and hence could not be removed */ public boolean removeInterface(Class<?> intf) { return this.interfaces.remove(intf); } @Override public Class<?>[] getProxiedInterfaces() { return ClassUtils.toClassArray(this.interfaces); } @Override public boolean isInterfaceProxied(Class<?> intf) { for (Class<?> proxyIntf : this.interfaces) { if (intf.isAssignableFrom(proxyIntf)) { return true; } } return false; } @Override public final Advisor[] getAdvisors() { return this.advisorArray; } @Override public void addAdvisor(Advisor advisor) { int pos = this.advisors.size(); addAdvisor(pos, advisor); } @Override public void addAdvisor(int pos, Advisor advisor) throws AopConfigException { if (advisor instanceof IntroductionAdvisor) { validateIntroductionAdvisor((IntroductionAdvisor) advisor); } addAdvisorInternal(pos, advisor); } @Override public boolean removeAdvisor(Advisor advisor) { int index = indexOf(advisor); if (index == -1) { return false; } else { removeAdvisor(index); return true; } } @Override public void removeAdvisor(int index) throws AopConfigException { if (isFrozen()) { throw new AopConfigException("Cannot remove Advisor: Configuration is frozen."); } if (index < 0 || index > this.advisors.size() - 1) { throw new AopConfigException("Advisor index " + index + " is out of bounds: " + "This configuration only has " + this.advisors.size() + " advisors."); } Advisor advisor = this.advisors.get(index); if (advisor instanceof IntroductionAdvisor) { IntroductionAdvisor ia = (IntroductionAdvisor) advisor; // We need to remove introduction interfaces. for (int j = 0; j < ia.getInterfaces().length; j++) { removeInterface(ia.getInterfaces()[j]); } } this.advisors.remove(index); updateAdvisorArray(); adviceChanged(); } @Override public int indexOf(Advisor advisor) { Assert.notNull(advisor, "Advisor must not be null"); return this.advisors.indexOf(advisor); } @Override public boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException { Assert.notNull(a, "Advisor a must not be null"); Assert.notNull(b, "Advisor b must not be null"); int index = indexOf(a); if (index == -1) { return false; } removeAdvisor(index); addAdvisor(index, b); return true; } /** * Add all of the given advisors to this proxy configuration. * @param advisors the advisors to register */ public void addAdvisors(Advisor... advisors) { addAdvisors(Arrays.asList(advisors)); } /** * Add all of the given advisors to this proxy configuration. * @param advisors the advisors to register */ public void addAdvisors(Collection<Advisor> advisors) { if (isFrozen()) { throw new AopConfigException("Cannot add advisor: Configuration is frozen."); } if (!CollectionUtils.isEmpty(advisors)) { for (Advisor advisor : advisors) { if (advisor instanceof IntroductionAdvisor) { validateIntroductionAdvisor((IntroductionAdvisor) advisor); } Assert.notNull(advisor, "Advisor must not be null"); this.advisors.add(advisor); } updateAdvisorArray(); adviceChanged(); } } private void validateIntroductionAdvisor(IntroductionAdvisor advisor) { advisor.validateInterfaces(); // If the advisor passed validation, we can make the change. Class<?>[] ifcs = advisor.getInterfaces(); for (Class<?> ifc : ifcs) { addInterface(ifc); } } private void addAdvisorInternal(int pos, Advisor advisor) throws AopConfigException { Assert.notNull(advisor, "Advisor must not be null"); if (isFrozen()) { throw new AopConfigException("Cannot add advisor: Configuration is frozen."); } if (pos > this.advisors.size()) { throw new IllegalArgumentException( "Illegal position " + pos + " in advisor list with size " + this.advisors.size()); } this.advisors.add(pos, advisor); updateAdvisorArray(); adviceChanged(); } /** * Bring the array up to date with the list. */ protected final void updateAdvisorArray() { this.advisorArray = this.advisors.toArray(new Advisor[0]); } /** * Allows uncontrolled access to the {@link List} of {@link Advisor Advisors}. * <p>Use with care, and remember to {@link #updateAdvisorArray() refresh the advisor array} * and {@link #adviceChanged() fire advice changed events} when making any modifications. */ protected final List<Advisor> getAdvisorsInternal() { return this.advisors; } @Override public void addAdvice(Advice advice) throws AopConfigException { int pos = this.advisors.size(); addAdvice(pos, advice); } /** * Cannot add introductions this way unless the advice implements IntroductionInfo. */ @Override public void addAdvice(int pos, Advice advice) throws AopConfigException { Assert.notNull(advice, "Advice must not be null"); if (advice instanceof IntroductionInfo) { // We don't need an IntroductionAdvisor for this kind of introduction: // It's fully self-describing. addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice)); } else if (advice instanceof DynamicIntroductionAdvice) { // We need an IntroductionAdvisor for this kind of introduction. throw new AopConfigException( "DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor"); } else { addAdvisor(pos, new DefaultPointcutAdvisor(advice)); } } @Override public boolean removeAdvice(Advice advice) throws AopConfigException { int index = indexOf(advice); if (index == -1) { return false; } else { removeAdvisor(index); return true; } } @Override public int indexOf(Advice advice) { Assert.notNull(advice, "Advice must not be null"); for (int i = 0; i < this.advisors.size(); i++) { Advisor advisor = this.advisors.get(i); if (advisor.getAdvice() == advice) { return i; } } return -1; } /** * Is the given advice included in any advisor within this proxy configuration? * @param advice the advice to check inclusion of * @return whether this advice instance is included */ public boolean adviceIncluded(@Nullable Advice advice) { if (advice != null) { for (Advisor advisor : this.advisors) { if (advisor.getAdvice() == advice) { return true; } } } return false; } /** * Count advices of the given class. * @param adviceClass the advice class to check * @return the count of the interceptors of this class or subclasses */ public int countAdvicesOfType(@Nullable Class<?> adviceClass) { int count = 0; if (adviceClass != null) { for (Advisor advisor : this.advisors) { if (adviceClass.isInstance(advisor.getAdvice())) { count++; } } } return count; } /** * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects * for the given method, based on this configuration. * @param method the proxied method * @param targetClass the target class * @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers) */ public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) { MethodCacheKey cacheKey = new MethodCacheKey(method); List<Object> cached = this.methodCache.get(cacheKey); if (cached == null) { cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass); this.methodCache.put(cacheKey, cached); } return cached; } /** * Invoked when advice has changed. */ protected void adviceChanged() { this.methodCache.clear(); } /** * Call this method on a new instance created by the no-arg constructor * to create an independent copy of the configuration from the given object. * @param other the AdvisedSupport object to copy configuration from */ protected void copyConfigurationFrom(AdvisedSupport other) { copyConfigurationFrom(other, other.targetSource, new ArrayList<>(other.advisors)); } /** * Copy the AOP configuration from the given AdvisedSupport object, * but allow substitution of a fresh TargetSource and a given interceptor chain. * @param other the AdvisedSupport object to take proxy configuration from * @param targetSource the new TargetSource * @param advisors the Advisors for the chain */ protected void copyConfigurationFrom(AdvisedSupport other, TargetSource targetSource, List<Advisor> advisors) { copyFrom(other); this.targetSource = targetSource; this.advisorChainFactory = other.advisorChainFactory; this.interfaces = new ArrayList<>(other.interfaces); for (Advisor advisor : advisors) { if (advisor instanceof IntroductionAdvisor) { validateIntroductionAdvisor((IntroductionAdvisor) advisor); } Assert.notNull(advisor, "Advisor must not be null"); this.advisors.add(advisor); } updateAdvisorArray(); adviceChanged(); } /** * Build a configuration-only copy of this AdvisedSupport, * replacing the TargetSource. */ AdvisedSupport getConfigurationOnlyCopy() { AdvisedSupport copy = new AdvisedSupport(); copy.copyFrom(this); copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic()); copy.advisorChainFactory = this.advisorChainFactory; copy.interfaces = this.interfaces; copy.advisors = this.advisors; copy.updateAdvisorArray(); return copy; } //--------------------------------------------------------------------- // Serialization support //--------------------------------------------------------------------- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization; just initialize state after deserialization. ois.defaultReadObject(); // Initialize transient fields. this.methodCache = new ConcurrentHashMap<>(32); } @Override public String toProxyConfigString() { return toString(); } /** * For debugging/diagnostic use. */ @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getName()); sb.append(": ").append(this.interfaces.size()).append(" interfaces "); sb.append(ClassUtils.classNamesToString(this.interfaces)).append("; "); sb.append(this.advisors.size()).append(" advisors "); sb.append(this.advisors).append("; "); sb.append("targetSource [").append(this.targetSource).append("]; "); sb.append(super.toString()); return sb.toString(); } /** * Simple wrapper class around a Method. Used as the key when * caching methods, for efficient equals and hashCode comparisons. */ private static final class MethodCacheKey implements Comparable<MethodCacheKey> { private final Method method; private final int hashCode; public MethodCacheKey(Method method) { this.method = method; this.hashCode = method.hashCode(); } @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof MethodCacheKey && this.method == ((MethodCacheKey) other).method)); } @Override public int hashCode() { return this.hashCode; } @Override public String toString() { return this.method.toString(); } @Override public int compareTo(MethodCacheKey other) { int result = this.method.getName().compareTo(other.method.getName()); if (result == 0) { result = this.method.toString().compareTo(other.method.toString()); } return result; } } }