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