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}