001/*
002 * Copyright 2002-2020 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.io.Serializable;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.List;
025
026import org.aopalliance.aop.Advice;
027import org.aopalliance.intercept.Interceptor;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.aop.Advisor;
032import org.springframework.aop.TargetSource;
033import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry;
034import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry;
035import org.springframework.aop.framework.adapter.UnknownAdviceTypeException;
036import org.springframework.aop.target.SingletonTargetSource;
037import org.springframework.beans.BeansException;
038import org.springframework.beans.factory.BeanClassLoaderAware;
039import org.springframework.beans.factory.BeanFactory;
040import org.springframework.beans.factory.BeanFactoryAware;
041import org.springframework.beans.factory.BeanFactoryUtils;
042import org.springframework.beans.factory.FactoryBean;
043import org.springframework.beans.factory.FactoryBeanNotInitializedException;
044import org.springframework.beans.factory.ListableBeanFactory;
045import org.springframework.core.annotation.AnnotationAwareOrderComparator;
046import org.springframework.util.ClassUtils;
047import org.springframework.util.ObjectUtils;
048
049/**
050 * {@link org.springframework.beans.factory.FactoryBean} implementation that builds an
051 * AOP proxy based on beans in Spring {@link org.springframework.beans.factory.BeanFactory}.
052 *
053 * <p>{@link org.aopalliance.intercept.MethodInterceptor MethodInterceptors} and
054 * {@link org.springframework.aop.Advisor Advisors} are identified by a list of bean
055 * names in the current bean factory, specified through the "interceptorNames" property.
056 * The last entry in the list can be the name of a target bean or a
057 * {@link org.springframework.aop.TargetSource}; however, it is normally preferable
058 * to use the "targetName"/"target"/"targetSource" properties instead.
059 *
060 * <p>Global interceptors and advisors can be added at the factory level. The specified
061 * ones are expanded in an interceptor list where an "xxx*" entry is included in the
062 * list, matching the given prefix with the bean names (e.g. "global*" would match
063 * both "globalBean1" and "globalBean2", "*" all defined interceptors). The matching
064 * interceptors get applied according to their returned order value, if they implement
065 * the {@link org.springframework.core.Ordered} interface.
066 *
067 * <p>Creates a JDK proxy when proxy interfaces are given, and a CGLIB proxy for the
068 * actual target class if not. Note that the latter will only work if the target class
069 * does not have final methods, as a dynamic subclass will be created at runtime.
070 *
071 * <p>It's possible to cast a proxy obtained from this factory to {@link Advised},
072 * or to obtain the ProxyFactoryBean reference and programmatically manipulate it.
073 * This won't work for existing prototype references, which are independent. However,
074 * it will work for prototypes subsequently obtained from the factory. Changes to
075 * interception will work immediately on singletons (including existing references).
076 * However, to change interfaces or target it's necessary to obtain a new instance
077 * from the factory. This means that singleton instances obtained from the factory
078 * do not have the same object identity. However, they do have the same interceptors
079 * and target, and changing any reference will change all objects.
080 *
081 * @author Rod Johnson
082 * @author Juergen Hoeller
083 * @see #setInterceptorNames
084 * @see #setProxyInterfaces
085 * @see org.aopalliance.intercept.MethodInterceptor
086 * @see org.springframework.aop.Advisor
087 * @see Advised
088 */
089@SuppressWarnings("serial")
090public class ProxyFactoryBean extends ProxyCreatorSupport
091                implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
092
093        /**
094         * This suffix in a value in an interceptor list indicates to expand globals.
095         */
096        public static final String GLOBAL_SUFFIX = "*";
097
098
099        protected final Log logger = LogFactory.getLog(getClass());
100
101        private String[] interceptorNames;
102
103        private String targetName;
104
105        private boolean autodetectInterfaces = true;
106
107        private boolean singleton = true;
108
109        private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
110
111        private boolean freezeProxy = false;
112
113        private transient ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader();
114
115        private transient boolean classLoaderConfigured = false;
116
117        private transient BeanFactory beanFactory;
118
119        /** Whether the advisor chain has already been initialized */
120        private boolean advisorChainInitialized = false;
121
122        /** If this is a singleton, the cached singleton proxy instance */
123        private Object singletonInstance;
124
125
126        /**
127         * Set the names of the interfaces we're proxying. If no interface
128         * is given, a CGLIB for the actual class will be created.
129         * <p>This is essentially equivalent to the "setInterfaces" method,
130         * but mirrors TransactionProxyFactoryBean's "setProxyInterfaces".
131         * @see #setInterfaces
132         * @see AbstractSingletonProxyFactoryBean#setProxyInterfaces
133         */
134        public void setProxyInterfaces(Class<?>[] proxyInterfaces) throws ClassNotFoundException {
135                setInterfaces(proxyInterfaces);
136        }
137
138        /**
139         * Set the list of Advice/Advisor bean names. This must always be set
140         * to use this factory bean in a bean factory.
141         * <p>The referenced beans should be of type Interceptor, Advisor or Advice
142         * The last entry in the list can be the name of any bean in the factory.
143         * If it's neither an Advice nor an Advisor, a new SingletonTargetSource
144         * is added to wrap it. Such a target bean cannot be used if the "target"
145         * or "targetSource" or "targetName" property is set, in which case the
146         * "interceptorNames" array must contain only Advice/Advisor bean names.
147         * <p><b>NOTE: Specifying a target bean as final name in the "interceptorNames"
148         * list is deprecated and will be removed in a future Spring version.</b>
149         * Use the {@link #setTargetName "targetName"} property instead.
150         * @see org.aopalliance.intercept.MethodInterceptor
151         * @see org.springframework.aop.Advisor
152         * @see org.aopalliance.aop.Advice
153         * @see org.springframework.aop.target.SingletonTargetSource
154         */
155        public void setInterceptorNames(String... interceptorNames) {
156                this.interceptorNames = interceptorNames;
157        }
158
159        /**
160         * Set the name of the target bean. This is an alternative to specifying
161         * the target name at the end of the "interceptorNames" array.
162         * <p>You can also specify a target object or a TargetSource object
163         * directly, via the "target"/"targetSource" property, respectively.
164         * @see #setInterceptorNames(String[])
165         * @see #setTarget(Object)
166         * @see #setTargetSource(org.springframework.aop.TargetSource)
167         */
168        public void setTargetName(String targetName) {
169                this.targetName = targetName;
170        }
171
172        /**
173         * Set whether to autodetect proxy interfaces if none specified.
174         * <p>Default is "true". Turn this flag off to create a CGLIB
175         * proxy for the full target class if no interfaces specified.
176         * @see #setProxyTargetClass
177         */
178        public void setAutodetectInterfaces(boolean autodetectInterfaces) {
179                this.autodetectInterfaces = autodetectInterfaces;
180        }
181
182        /**
183         * Set the value of the singleton property. Governs whether this factory
184         * should always return the same proxy instance (which implies the same target)
185         * or whether it should return a new prototype instance, which implies that
186         * the target and interceptors may be new instances also, if they are obtained
187         * from prototype bean definitions. This allows for fine control of
188         * independence/uniqueness in the object graph.
189         */
190        public void setSingleton(boolean singleton) {
191                this.singleton = singleton;
192        }
193
194        /**
195         * Specify the AdvisorAdapterRegistry to use.
196         * Default is the global AdvisorAdapterRegistry.
197         * @see org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry
198         */
199        public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) {
200                this.advisorAdapterRegistry = advisorAdapterRegistry;
201        }
202
203        @Override
204        public void setFrozen(boolean frozen) {
205                this.freezeProxy = frozen;
206        }
207
208        /**
209         * Set the ClassLoader to generate the proxy class in.
210         * <p>Default is the bean ClassLoader, i.e. the ClassLoader used by the
211         * containing BeanFactory for loading all bean classes. This can be
212         * overridden here for specific proxies.
213         */
214        public void setProxyClassLoader(ClassLoader classLoader) {
215                this.proxyClassLoader = classLoader;
216                this.classLoaderConfigured = (classLoader != null);
217        }
218
219        @Override
220        public void setBeanClassLoader(ClassLoader classLoader) {
221                if (!this.classLoaderConfigured) {
222                        this.proxyClassLoader = classLoader;
223                }
224        }
225
226        @Override
227        public void setBeanFactory(BeanFactory beanFactory) {
228                this.beanFactory = beanFactory;
229                checkInterceptorNames();
230        }
231
232
233        /**
234         * Return a proxy. Invoked when clients obtain beans from this factory bean.
235         * Create an instance of the AOP proxy to be returned by this factory.
236         * The instance will be cached for a singleton, and create on each call to
237         * {@code getObject()} for a proxy.
238         * @return a fresh AOP proxy reflecting the current state of this factory
239         */
240        @Override
241        public Object getObject() throws BeansException {
242                initializeAdvisorChain();
243                if (isSingleton()) {
244                        return getSingletonInstance();
245                }
246                else {
247                        if (this.targetName == null) {
248                                logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
249                                                "Enable prototype proxies by setting the 'targetName' property.");
250                        }
251                        return newPrototypeInstance();
252                }
253        }
254
255        /**
256         * Return the type of the proxy. Will check the singleton instance if
257         * already created, else fall back to the proxy interface (in case of just
258         * a single one), the target bean type, or the TargetSource's target class.
259         * @see org.springframework.aop.TargetSource#getTargetClass
260         */
261        @Override
262        public Class<?> getObjectType() {
263                synchronized (this) {
264                        if (this.singletonInstance != null) {
265                                return this.singletonInstance.getClass();
266                        }
267                }
268                Class<?>[] ifcs = getProxiedInterfaces();
269                if (ifcs.length == 1) {
270                        return ifcs[0];
271                }
272                else if (ifcs.length > 1) {
273                        return createCompositeInterface(ifcs);
274                }
275                else if (this.targetName != null && this.beanFactory != null) {
276                        return this.beanFactory.getType(this.targetName);
277                }
278                else {
279                        return getTargetClass();
280                }
281        }
282
283        @Override
284        public boolean isSingleton() {
285                return this.singleton;
286        }
287
288
289        /**
290         * Create a composite interface Class for the given interfaces,
291         * implementing the given interfaces in one single Class.
292         * <p>The default implementation builds a JDK proxy class for the
293         * given interfaces.
294         * @param interfaces the interfaces to merge
295         * @return the merged interface as Class
296         * @see java.lang.reflect.Proxy#getProxyClass
297         */
298        protected Class<?> createCompositeInterface(Class<?>[] interfaces) {
299                return ClassUtils.createCompositeInterface(interfaces, this.proxyClassLoader);
300        }
301
302        /**
303         * Return the singleton instance of this class's proxy object,
304         * lazily creating it if it hasn't been created already.
305         * @return the shared singleton proxy
306         */
307        private synchronized Object getSingletonInstance() {
308                if (this.singletonInstance == null) {
309                        this.targetSource = freshTargetSource();
310                        if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
311                                // Rely on AOP infrastructure to tell us what interfaces to proxy.
312                                Class<?> targetClass = getTargetClass();
313                                if (targetClass == null) {
314                                        throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
315                                }
316                                setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
317                        }
318                        // Initialize the shared singleton instance.
319                        super.setFrozen(this.freezeProxy);
320                        this.singletonInstance = getProxy(createAopProxy());
321                }
322                return this.singletonInstance;
323        }
324
325        /**
326         * Create a new prototype instance of this class's created proxy object,
327         * backed by an independent AdvisedSupport configuration.
328         * @return a totally independent proxy, whose advice we may manipulate in isolation
329         */
330        private synchronized Object newPrototypeInstance() {
331                // In the case of a prototype, we need to give the proxy
332                // an independent instance of the configuration.
333                // In this case, no proxy will have an instance of this object's configuration,
334                // but will have an independent copy.
335                ProxyCreatorSupport copy = new ProxyCreatorSupport(getAopProxyFactory());
336
337                // The copy needs a fresh advisor chain, and a fresh TargetSource.
338                TargetSource targetSource = freshTargetSource();
339                copy.copyConfigurationFrom(this, targetSource, freshAdvisorChain());
340                if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
341                        // Rely on AOP infrastructure to tell us what interfaces to proxy.
342                        copy.setInterfaces(
343                                        ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader));
344                }
345                copy.setFrozen(this.freezeProxy);
346
347                return getProxy(copy.createAopProxy());
348        }
349
350        /**
351         * Return the proxy object to expose.
352         * <p>The default implementation uses a {@code getProxy} call with
353         * the factory's bean class loader. Can be overridden to specify a
354         * custom class loader.
355         * @param aopProxy the prepared AopProxy instance to get the proxy from
356         * @return the proxy object to expose
357         * @see AopProxy#getProxy(ClassLoader)
358         */
359        protected Object getProxy(AopProxy aopProxy) {
360                return aopProxy.getProxy(this.proxyClassLoader);
361        }
362
363        /**
364         * Check the interceptorNames list whether it contains a target name as final element.
365         * If found, remove the final name from the list and set it as targetName.
366         */
367        private void checkInterceptorNames() {
368                if (!ObjectUtils.isEmpty(this.interceptorNames)) {
369                        String finalName = this.interceptorNames[this.interceptorNames.length - 1];
370                        if (this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
371                                // The last name in the chain may be an Advisor/Advice or a target/TargetSource.
372                                // Unfortunately we don't know; we must look at type of the bean.
373                                if (!finalName.endsWith(GLOBAL_SUFFIX) && !isNamedBeanAnAdvisorOrAdvice(finalName)) {
374                                        // The target isn't an interceptor.
375                                        this.targetName = finalName;
376                                        if (logger.isDebugEnabled()) {
377                                                logger.debug("Bean with name '" + finalName + "' concluding interceptor chain " +
378                                                                "is not an advisor class: treating it as a target or TargetSource");
379                                        }
380                                        String[] newNames = new String[this.interceptorNames.length - 1];
381                                        System.arraycopy(this.interceptorNames, 0, newNames, 0, newNames.length);
382                                        this.interceptorNames = newNames;
383                                }
384                        }
385                }
386        }
387
388        /**
389         * Look at bean factory metadata to work out whether this bean name,
390         * which concludes the interceptorNames list, is an Advisor or Advice,
391         * or may be a target.
392         * @param beanName bean name to check
393         * @return {@code true} if it's an Advisor or Advice
394         */
395        private boolean isNamedBeanAnAdvisorOrAdvice(String beanName) {
396                Class<?> namedBeanClass = this.beanFactory.getType(beanName);
397                if (namedBeanClass != null) {
398                        return (Advisor.class.isAssignableFrom(namedBeanClass) || Advice.class.isAssignableFrom(namedBeanClass));
399                }
400                // Treat it as an target bean if we can't tell.
401                if (logger.isDebugEnabled()) {
402                        logger.debug("Could not determine type of bean with name '" + beanName +
403                                        "' - assuming it is neither an Advisor nor an Advice");
404                }
405                return false;
406        }
407
408        /**
409         * Create the advisor (interceptor) chain. Advisors that are sourced
410         * from a BeanFactory will be refreshed each time a new prototype instance
411         * is added. Interceptors added programmatically through the factory API
412         * are unaffected by such changes.
413         */
414        private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
415                if (this.advisorChainInitialized) {
416                        return;
417                }
418
419                if (!ObjectUtils.isEmpty(this.interceptorNames)) {
420                        if (this.beanFactory == null) {
421                                throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
422                                                "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
423                        }
424
425                        // Globals can't be last unless we specified a targetSource using the property...
426                        if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
427                                        this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
428                                throw new AopConfigException("Target required after globals");
429                        }
430
431                        // Materialize interceptor chain from bean names.
432                        for (String name : this.interceptorNames) {
433                                if (name.endsWith(GLOBAL_SUFFIX)) {
434                                        if (!(this.beanFactory instanceof ListableBeanFactory)) {
435                                                throw new AopConfigException(
436                                                                "Can only use global advisors or interceptors with a ListableBeanFactory");
437                                        }
438                                        addGlobalAdvisors((ListableBeanFactory) this.beanFactory,
439                                                        name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
440                                }
441
442                                else {
443                                        // If we get here, we need to add a named interceptor.
444                                        // We must check if it's a singleton or prototype.
445                                        Object advice;
446                                        if (this.singleton || this.beanFactory.isSingleton(name)) {
447                                                // Add the real Advisor/Advice to the chain.
448                                                advice = this.beanFactory.getBean(name);
449                                        }
450                                        else {
451                                                // It's a prototype Advice or Advisor: replace with a prototype.
452                                                // Avoid unnecessary creation of prototype bean just for advisor chain initialization.
453                                                advice = new PrototypePlaceholderAdvisor(name);
454                                        }
455                                        addAdvisorOnChainCreation(advice);
456                                }
457                        }
458                }
459
460                this.advisorChainInitialized = true;
461        }
462
463
464        /**
465         * Return an independent advisor chain.
466         * We need to do this every time a new prototype instance is returned,
467         * to return distinct instances of prototype Advisors and Advices.
468         */
469        private List<Advisor> freshAdvisorChain() {
470                Advisor[] advisors = getAdvisors();
471                List<Advisor> freshAdvisors = new ArrayList<Advisor>(advisors.length);
472                for (Advisor advisor : advisors) {
473                        if (advisor instanceof PrototypePlaceholderAdvisor) {
474                                PrototypePlaceholderAdvisor pa = (PrototypePlaceholderAdvisor) advisor;
475                                if (logger.isDebugEnabled()) {
476                                        logger.debug("Refreshing bean named '" + pa.getBeanName() + "'");
477                                }
478                                // Replace the placeholder with a fresh prototype instance resulting from a getBean lookup
479                                if (this.beanFactory == null) {
480                                        throw new IllegalStateException("No BeanFactory available anymore (probably due to " +
481                                                        "serialization) - cannot resolve prototype advisor '" + pa.getBeanName() + "'");
482                                }
483                                Object bean = this.beanFactory.getBean(pa.getBeanName());
484                                Advisor refreshedAdvisor = namedBeanToAdvisor(bean);
485                                freshAdvisors.add(refreshedAdvisor);
486                        }
487                        else {
488                                // Add the shared instance.
489                                freshAdvisors.add(advisor);
490                        }
491                }
492                return freshAdvisors;
493        }
494
495        /**
496         * Add all global interceptors and pointcuts.
497         */
498        private void addGlobalAdvisors(ListableBeanFactory beanFactory, String prefix) {
499                String[] globalAdvisorNames =
500                                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Advisor.class);
501                String[] globalInterceptorNames =
502                                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Interceptor.class);
503                if (globalAdvisorNames.length > 0 || globalInterceptorNames.length > 0) {
504                        List<Object> beans = new ArrayList<Object>(globalAdvisorNames.length + globalInterceptorNames.length);
505                        for (String name : globalAdvisorNames) {
506                                if (name.startsWith(prefix)) {
507                                        beans.add(beanFactory.getBean(name));
508                                }
509                        }
510                        for (String name : globalInterceptorNames) {
511                                if (name.startsWith(prefix)) {
512                                        beans.add(beanFactory.getBean(name));
513                                }
514                        }
515                        AnnotationAwareOrderComparator.sort(beans);
516                        for (Object bean : beans) {
517                                addAdvisorOnChainCreation(bean);
518                        }
519                }
520        }
521
522        /**
523         * Invoked when advice chain is created.
524         * <p>Add the given advice, advisor or object to the interceptor list.
525         * Because of these three possibilities, we can't type the signature
526         * more strongly.
527         * @param next advice, advisor or target object
528         */
529        private void addAdvisorOnChainCreation(Object next) {
530                // We need to convert to an Advisor if necessary so that our source reference
531                // matches what we find from superclass interceptors.
532                addAdvisor(namedBeanToAdvisor(next));
533        }
534
535        /**
536         * Return a TargetSource to use when creating a proxy. If the target was not
537         * specified at the end of the interceptorNames list, the TargetSource will be
538         * this class's TargetSource member. Otherwise, we get the target bean and wrap
539         * it in a TargetSource if necessary.
540         */
541        private TargetSource freshTargetSource() {
542                if (this.targetName == null) {
543                        // Not refreshing target: bean name not specified in 'interceptorNames'
544                        return this.targetSource;
545                }
546                else {
547                        if (this.beanFactory == null) {
548                                throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
549                                                "- cannot resolve target with name '" + this.targetName + "'");
550                        }
551                        if (logger.isDebugEnabled()) {
552                                logger.debug("Refreshing target with name '" + this.targetName + "'");
553                        }
554                        Object target = this.beanFactory.getBean(this.targetName);
555                        return (target instanceof TargetSource ? (TargetSource) target : new SingletonTargetSource(target));
556                }
557        }
558
559        /**
560         * Convert the following object sourced from calling getBean() on a name in the
561         * interceptorNames array to an Advisor or TargetSource.
562         */
563        private Advisor namedBeanToAdvisor(Object next) {
564                try {
565                        return this.advisorAdapterRegistry.wrap(next);
566                }
567                catch (UnknownAdviceTypeException ex) {
568                        // We expected this to be an Advisor or Advice,
569                        // but it wasn't. This is a configuration error.
570                        throw new AopConfigException("Unknown advisor type " + next.getClass() +
571                                        "; can only include Advisor or Advice type beans in interceptorNames chain " +
572                                        "except for last entry which may also be target instance or TargetSource", ex);
573                }
574        }
575
576        /**
577         * Blow away and recache singleton on an advice change.
578         */
579        @Override
580        protected void adviceChanged() {
581                super.adviceChanged();
582                if (this.singleton) {
583                        logger.debug("Advice has changed; re-caching singleton instance");
584                        synchronized (this) {
585                                this.singletonInstance = null;
586                        }
587                }
588        }
589
590
591        //---------------------------------------------------------------------
592        // Serialization support
593        //---------------------------------------------------------------------
594
595        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
596                // Rely on default serialization; just initialize state after deserialization.
597                ois.defaultReadObject();
598
599                // Initialize transient fields.
600                this.proxyClassLoader = ClassUtils.getDefaultClassLoader();
601        }
602
603
604        /**
605         * Used in the interceptor chain where we need to replace a bean with a prototype
606         * on creating a proxy.
607         */
608        private static class PrototypePlaceholderAdvisor implements Advisor, Serializable {
609
610                private final String beanName;
611
612                private final String message;
613
614                public PrototypePlaceholderAdvisor(String beanName) {
615                        this.beanName = beanName;
616                        this.message = "Placeholder for prototype Advisor/Advice with bean name '" + beanName + "'";
617                }
618
619                public String getBeanName() {
620                        return this.beanName;
621                }
622
623                @Override
624                public Advice getAdvice() {
625                        throw new UnsupportedOperationException("Cannot invoke methods: " + this.message);
626                }
627
628                @Override
629                public boolean isPerInstance() {
630                        throw new UnsupportedOperationException("Cannot invoke methods: " + this.message);
631                }
632
633                @Override
634                public String toString() {
635                        return this.message;
636                }
637        }
638
639}