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