001/* 002 * Copyright 2002-2016 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.context.annotation; 018 019import java.beans.Introspector; 020import java.beans.PropertyDescriptor; 021import java.io.Serializable; 022import java.lang.annotation.Annotation; 023import java.lang.reflect.AnnotatedElement; 024import java.lang.reflect.Constructor; 025import java.lang.reflect.Field; 026import java.lang.reflect.Member; 027import java.lang.reflect.Method; 028import java.lang.reflect.Modifier; 029import java.net.MalformedURLException; 030import java.net.URL; 031import java.util.Collections; 032import java.util.HashSet; 033import java.util.LinkedHashSet; 034import java.util.LinkedList; 035import java.util.Map; 036import java.util.Set; 037import java.util.concurrent.ConcurrentHashMap; 038import javax.annotation.PostConstruct; 039import javax.annotation.PreDestroy; 040import javax.annotation.Resource; 041import javax.ejb.EJB; 042import javax.xml.namespace.QName; 043import javax.xml.ws.Service; 044import javax.xml.ws.WebServiceClient; 045import javax.xml.ws.WebServiceRef; 046 047import org.springframework.aop.TargetSource; 048import org.springframework.aop.framework.ProxyFactory; 049import org.springframework.beans.BeanUtils; 050import org.springframework.beans.BeansException; 051import org.springframework.beans.PropertyValues; 052import org.springframework.beans.factory.BeanCreationException; 053import org.springframework.beans.factory.BeanFactory; 054import org.springframework.beans.factory.BeanFactoryAware; 055import org.springframework.beans.factory.NoSuchBeanDefinitionException; 056import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor; 057import org.springframework.beans.factory.annotation.InjectionMetadata; 058import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 059import org.springframework.beans.factory.config.ConfigurableBeanFactory; 060import org.springframework.beans.factory.config.DependencyDescriptor; 061import org.springframework.beans.factory.config.EmbeddedValueResolver; 062import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; 063import org.springframework.beans.factory.support.RootBeanDefinition; 064import org.springframework.core.BridgeMethodResolver; 065import org.springframework.core.MethodParameter; 066import org.springframework.core.Ordered; 067import org.springframework.jndi.support.SimpleJndiBeanFactory; 068import org.springframework.util.Assert; 069import org.springframework.util.ClassUtils; 070import org.springframework.util.ReflectionUtils; 071import org.springframework.util.StringUtils; 072import org.springframework.util.StringValueResolver; 073 074/** 075 * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation 076 * that supports common Java annotations out of the box, in particular the JSR-250 077 * annotations in the {@code javax.annotation} package. These common Java 078 * annotations are supported in many Java EE 5 technologies (e.g. JSF 1.2), 079 * as well as in Java 6's JAX-WS. 080 * 081 * <p>This post-processor includes support for the {@link javax.annotation.PostConstruct} 082 * and {@link javax.annotation.PreDestroy} annotations - as init annotation 083 * and destroy annotation, respectively - through inheriting from 084 * {@link InitDestroyAnnotationBeanPostProcessor} with pre-configured annotation types. 085 * 086 * <p>The central element is the {@link javax.annotation.Resource} annotation 087 * for annotation-driven injection of named beans, by default from the containing 088 * Spring BeanFactory, with only {@code mappedName} references resolved in JNDI. 089 * The {@link #setAlwaysUseJndiLookup "alwaysUseJndiLookup" flag} enforces JNDI lookups 090 * equivalent to standard Java EE 5 resource injection for {@code name} references 091 * and default names as well. The target beans can be simple POJOs, with no special 092 * requirements other than the type having to match. 093 * 094 * <p>The JAX-WS {@link javax.xml.ws.WebServiceRef} annotation is supported too, 095 * analogous to {@link javax.annotation.Resource} but with the capability of creating 096 * specific JAX-WS service endpoints. This may either point to an explicitly defined 097 * resource by name or operate on a locally specified JAX-WS service class. Finally, 098 * this post-processor also supports the EJB 3 {@link javax.ejb.EJB} annotation, 099 * analogous to {@link javax.annotation.Resource} as well, with the capability to 100 * specify both a local bean name and a global JNDI name for fallback retrieval. 101 * The target beans can be plain POJOs as well as EJB 3 Session Beans in this case. 102 * 103 * <p>The common annotations supported by this post-processor are available in 104 * Java 6 (JDK 1.6) as well as in Java EE 5/6 (which provides a standalone jar for 105 * its common annotations as well, allowing for use in any Java 5 based application). 106 * 107 * <p>For default usage, resolving resource names as Spring bean names, 108 * simply define the following in your application context: 109 * 110 * <pre class="code"> 111 * <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/></pre> 112 * 113 * For direct JNDI access, resolving resource names as JNDI resource references 114 * within the Java EE application's "java:comp/env/" namespace, use the following: 115 * 116 * <pre class="code"> 117 * <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"> 118 * <property name="alwaysUseJndiLookup" value="true"/> 119 * </bean></pre> 120 * 121 * {@code mappedName} references will always be resolved in JNDI, 122 * allowing for global JNDI names (including "java:" prefix) as well. The 123 * "alwaysUseJndiLookup" flag just affects {@code name} references and 124 * default names (inferred from the field name / property name). 125 * 126 * <p><b>NOTE:</b> A default CommonAnnotationBeanPostProcessor will be registered 127 * by the "context:annotation-config" and "context:component-scan" XML tags. 128 * Remove or turn off the default annotation configuration there if you intend 129 * to specify a custom CommonAnnotationBeanPostProcessor bean definition! 130 * <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection; thus 131 * the latter configuration will override the former for properties wired through 132 * both approaches. 133 * 134 * @author Juergen Hoeller 135 * @since 2.5 136 * @see #setAlwaysUseJndiLookup 137 * @see #setResourceFactory 138 * @see org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor 139 * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor 140 */ 141@SuppressWarnings("serial") 142public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor 143 implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable { 144 145 // Common Annotations 1.1 Resource.lookup() available? Not present on JDK 6... 146 private static final Method lookupAttribute = ClassUtils.getMethodIfAvailable(Resource.class, "lookup"); 147 148 private static Class<? extends Annotation> webServiceRefClass = null; 149 150 private static Class<? extends Annotation> ejbRefClass = null; 151 152 static { 153 try { 154 @SuppressWarnings("unchecked") 155 Class<? extends Annotation> clazz = (Class<? extends Annotation>) 156 ClassUtils.forName("javax.xml.ws.WebServiceRef", CommonAnnotationBeanPostProcessor.class.getClassLoader()); 157 webServiceRefClass = clazz; 158 } 159 catch (ClassNotFoundException ex) { 160 webServiceRefClass = null; 161 } 162 try { 163 @SuppressWarnings("unchecked") 164 Class<? extends Annotation> clazz = (Class<? extends Annotation>) 165 ClassUtils.forName("javax.ejb.EJB", CommonAnnotationBeanPostProcessor.class.getClassLoader()); 166 ejbRefClass = clazz; 167 } 168 catch (ClassNotFoundException ex) { 169 ejbRefClass = null; 170 } 171 } 172 173 174 private final Set<String> ignoredResourceTypes = new HashSet<String>(1); 175 176 private boolean fallbackToDefaultTypeMatch = true; 177 178 private boolean alwaysUseJndiLookup = false; 179 180 private transient BeanFactory jndiFactory = new SimpleJndiBeanFactory(); 181 182 private transient BeanFactory resourceFactory; 183 184 private transient BeanFactory beanFactory; 185 186 private transient StringValueResolver embeddedValueResolver; 187 188 private transient final Map<String, InjectionMetadata> injectionMetadataCache = 189 new ConcurrentHashMap<String, InjectionMetadata>(256); 190 191 192 /** 193 * Create a new CommonAnnotationBeanPostProcessor, 194 * with the init and destroy annotation types set to 195 * {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy}, 196 * respectively. 197 */ 198 public CommonAnnotationBeanPostProcessor() { 199 setOrder(Ordered.LOWEST_PRECEDENCE - 3); 200 setInitAnnotationType(PostConstruct.class); 201 setDestroyAnnotationType(PreDestroy.class); 202 ignoreResourceType("javax.xml.ws.WebServiceContext"); 203 } 204 205 206 /** 207 * Ignore the given resource type when resolving {@code @Resource} 208 * annotations. 209 * <p>By default, the {@code javax.xml.ws.WebServiceContext} interface 210 * will be ignored, since it will be resolved by the JAX-WS runtime. 211 * @param resourceType the resource type to ignore 212 */ 213 public void ignoreResourceType(String resourceType) { 214 Assert.notNull(resourceType, "Ignored resource type must not be null"); 215 this.ignoredResourceTypes.add(resourceType); 216 } 217 218 /** 219 * Set whether to allow a fallback to a type match if no explicit name has been 220 * specified. The default name (i.e. the field name or bean property name) will 221 * still be checked first; if a bean of that name exists, it will be taken. 222 * However, if no bean of that name exists, a by-type resolution of the 223 * dependency will be attempted if this flag is "true". 224 * <p>Default is "true". Switch this flag to "false" in order to enforce a 225 * by-name lookup in all cases, throwing an exception in case of no name match. 226 * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#resolveDependency 227 */ 228 public void setFallbackToDefaultTypeMatch(boolean fallbackToDefaultTypeMatch) { 229 this.fallbackToDefaultTypeMatch = fallbackToDefaultTypeMatch; 230 } 231 232 /** 233 * Set whether to always use JNDI lookups equivalent to standard Java EE 5 resource 234 * injection, <b>even for {@code name} attributes and default names</b>. 235 * <p>Default is "false": Resource names are used for Spring bean lookups in the 236 * containing BeanFactory; only {@code mappedName} attributes point directly 237 * into JNDI. Switch this flag to "true" for enforcing Java EE style JNDI lookups 238 * in any case, even for {@code name} attributes and default names. 239 * @see #setJndiFactory 240 * @see #setResourceFactory 241 */ 242 public void setAlwaysUseJndiLookup(boolean alwaysUseJndiLookup) { 243 this.alwaysUseJndiLookup = alwaysUseJndiLookup; 244 } 245 246 /** 247 * Specify the factory for objects to be injected into {@code @Resource} / 248 * {@code @WebServiceRef} / {@code @EJB} annotated fields and setter methods, 249 * <b>for {@code mappedName} attributes that point directly into JNDI</b>. 250 * This factory will also be used if "alwaysUseJndiLookup" is set to "true" in order 251 * to enforce JNDI lookups even for {@code name} attributes and default names. 252 * <p>The default is a {@link org.springframework.jndi.support.SimpleJndiBeanFactory} 253 * for JNDI lookup behavior equivalent to standard Java EE 5 resource injection. 254 * @see #setResourceFactory 255 * @see #setAlwaysUseJndiLookup 256 */ 257 public void setJndiFactory(BeanFactory jndiFactory) { 258 Assert.notNull(jndiFactory, "BeanFactory must not be null"); 259 this.jndiFactory = jndiFactory; 260 } 261 262 /** 263 * Specify the factory for objects to be injected into {@code @Resource} / 264 * {@code @WebServiceRef} / {@code @EJB} annotated fields and setter methods, 265 * <b>for {@code name} attributes and default names</b>. 266 * <p>The default is the BeanFactory that this post-processor is defined in, 267 * if any, looking up resource names as Spring bean names. Specify the resource 268 * factory explicitly for programmatic usage of this post-processor. 269 * <p>Specifying Spring's {@link org.springframework.jndi.support.SimpleJndiBeanFactory} 270 * leads to JNDI lookup behavior equivalent to standard Java EE 5 resource injection, 271 * even for {@code name} attributes and default names. This is the same behavior 272 * that the "alwaysUseJndiLookup" flag enables. 273 * @see #setAlwaysUseJndiLookup 274 */ 275 public void setResourceFactory(BeanFactory resourceFactory) { 276 Assert.notNull(resourceFactory, "BeanFactory must not be null"); 277 this.resourceFactory = resourceFactory; 278 } 279 280 @Override 281 public void setBeanFactory(BeanFactory beanFactory) { 282 Assert.notNull(beanFactory, "BeanFactory must not be null"); 283 this.beanFactory = beanFactory; 284 if (this.resourceFactory == null) { 285 this.resourceFactory = beanFactory; 286 } 287 if (beanFactory instanceof ConfigurableBeanFactory) { 288 this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); 289 } 290 } 291 292 293 @Override 294 public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { 295 super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName); 296 if (beanType != null) { 297 InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null); 298 metadata.checkConfigMembers(beanDefinition); 299 } 300 } 301 302 @Override 303 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { 304 return null; 305 } 306 307 @Override 308 public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { 309 return true; 310 } 311 312 @Override 313 public PropertyValues postProcessPropertyValues( 314 PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { 315 316 InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs); 317 try { 318 metadata.inject(bean, beanName, pvs); 319 } 320 catch (Throwable ex) { 321 throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex); 322 } 323 return pvs; 324 } 325 326 327 private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, PropertyValues pvs) { 328 // Fall back to class name as cache key, for backwards compatibility with custom callers. 329 String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); 330 // Quick check on the concurrent map first, with minimal locking. 331 InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); 332 if (InjectionMetadata.needsRefresh(metadata, clazz)) { 333 synchronized (this.injectionMetadataCache) { 334 metadata = this.injectionMetadataCache.get(cacheKey); 335 if (InjectionMetadata.needsRefresh(metadata, clazz)) { 336 if (metadata != null) { 337 metadata.clear(pvs); 338 } 339 try { 340 metadata = buildResourceMetadata(clazz); 341 this.injectionMetadataCache.put(cacheKey, metadata); 342 } 343 catch (NoClassDefFoundError err) { 344 throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() + 345 "] for resource metadata: could not find class that it depends on", err); 346 } 347 } 348 } 349 } 350 return metadata; 351 } 352 353 private InjectionMetadata buildResourceMetadata(final Class<?> clazz) { 354 LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>(); 355 Class<?> targetClass = clazz; 356 357 do { 358 final LinkedList<InjectionMetadata.InjectedElement> currElements = 359 new LinkedList<InjectionMetadata.InjectedElement>(); 360 361 ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() { 362 @Override 363 public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { 364 if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) { 365 if (Modifier.isStatic(field.getModifiers())) { 366 throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields"); 367 } 368 currElements.add(new WebServiceRefElement(field, field, null)); 369 } 370 else if (ejbRefClass != null && field.isAnnotationPresent(ejbRefClass)) { 371 if (Modifier.isStatic(field.getModifiers())) { 372 throw new IllegalStateException("@EJB annotation is not supported on static fields"); 373 } 374 currElements.add(new EjbRefElement(field, field, null)); 375 } 376 else if (field.isAnnotationPresent(Resource.class)) { 377 if (Modifier.isStatic(field.getModifiers())) { 378 throw new IllegalStateException("@Resource annotation is not supported on static fields"); 379 } 380 if (!ignoredResourceTypes.contains(field.getType().getName())) { 381 currElements.add(new ResourceElement(field, field, null)); 382 } 383 } 384 } 385 }); 386 387 ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() { 388 @Override 389 public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { 390 Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 391 if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { 392 return; 393 } 394 if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { 395 if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) { 396 if (Modifier.isStatic(method.getModifiers())) { 397 throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods"); 398 } 399 if (method.getParameterTypes().length != 1) { 400 throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method); 401 } 402 PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); 403 currElements.add(new WebServiceRefElement(method, bridgedMethod, pd)); 404 } 405 else if (ejbRefClass != null && bridgedMethod.isAnnotationPresent(ejbRefClass)) { 406 if (Modifier.isStatic(method.getModifiers())) { 407 throw new IllegalStateException("@EJB annotation is not supported on static methods"); 408 } 409 if (method.getParameterTypes().length != 1) { 410 throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method); 411 } 412 PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); 413 currElements.add(new EjbRefElement(method, bridgedMethod, pd)); 414 } 415 else if (bridgedMethod.isAnnotationPresent(Resource.class)) { 416 if (Modifier.isStatic(method.getModifiers())) { 417 throw new IllegalStateException("@Resource annotation is not supported on static methods"); 418 } 419 Class<?>[] paramTypes = method.getParameterTypes(); 420 if (paramTypes.length != 1) { 421 throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method); 422 } 423 if (!ignoredResourceTypes.contains(paramTypes[0].getName())) { 424 PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); 425 currElements.add(new ResourceElement(method, bridgedMethod, pd)); 426 } 427 } 428 } 429 } 430 }); 431 432 elements.addAll(0, currElements); 433 targetClass = targetClass.getSuperclass(); 434 } 435 while (targetClass != null && targetClass != Object.class); 436 437 return new InjectionMetadata(clazz, elements); 438 } 439 440 /** 441 * Obtain a lazily resolving resource proxy for the given name and type, 442 * delegating to {@link #getResource} on demand once a method call comes in. 443 * @param element the descriptor for the annotated field/method 444 * @param requestingBeanName the name of the requesting bean 445 * @return the resource object (never {@code null}) 446 * @since 4.2 447 * @see #getResource 448 * @see Lazy 449 */ 450 protected Object buildLazyResourceProxy(final LookupElement element, final String requestingBeanName) { 451 TargetSource ts = new TargetSource() { 452 @Override 453 public Class<?> getTargetClass() { 454 return element.lookupType; 455 } 456 @Override 457 public boolean isStatic() { 458 return false; 459 } 460 @Override 461 public Object getTarget() { 462 return getResource(element, requestingBeanName); 463 } 464 @Override 465 public void releaseTarget(Object target) { 466 } 467 }; 468 ProxyFactory pf = new ProxyFactory(); 469 pf.setTargetSource(ts); 470 if (element.lookupType.isInterface()) { 471 pf.addInterface(element.lookupType); 472 } 473 ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ? 474 ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null); 475 return pf.getProxy(classLoader); 476 } 477 478 /** 479 * Obtain the resource object for the given name and type. 480 * @param element the descriptor for the annotated field/method 481 * @param requestingBeanName the name of the requesting bean 482 * @return the resource object (never {@code null}) 483 * @throws BeansException if we failed to obtain the target resource 484 */ 485 protected Object getResource(LookupElement element, String requestingBeanName) throws BeansException { 486 if (StringUtils.hasLength(element.mappedName)) { 487 return this.jndiFactory.getBean(element.mappedName, element.lookupType); 488 } 489 if (this.alwaysUseJndiLookup) { 490 return this.jndiFactory.getBean(element.name, element.lookupType); 491 } 492 if (this.resourceFactory == null) { 493 throw new NoSuchBeanDefinitionException(element.lookupType, 494 "No resource factory configured - specify the 'resourceFactory' property"); 495 } 496 return autowireResource(this.resourceFactory, element, requestingBeanName); 497 } 498 499 /** 500 * Obtain a resource object for the given name and type through autowiring 501 * based on the given factory. 502 * @param factory the factory to autowire against 503 * @param element the descriptor for the annotated field/method 504 * @param requestingBeanName the name of the requesting bean 505 * @return the resource object (never {@code null}) 506 * @throws BeansException if we failed to obtain the target resource 507 */ 508 protected Object autowireResource(BeanFactory factory, LookupElement element, String requestingBeanName) 509 throws BeansException { 510 511 Object resource; 512 Set<String> autowiredBeanNames; 513 String name = element.name; 514 515 if (this.fallbackToDefaultTypeMatch && element.isDefaultName && 516 factory instanceof AutowireCapableBeanFactory && !factory.containsBean(name)) { 517 autowiredBeanNames = new LinkedHashSet<String>(); 518 resource = ((AutowireCapableBeanFactory) factory).resolveDependency( 519 element.getDependencyDescriptor(), requestingBeanName, autowiredBeanNames, null); 520 } 521 else { 522 resource = factory.getBean(name, element.lookupType); 523 autowiredBeanNames = Collections.singleton(name); 524 } 525 526 if (factory instanceof ConfigurableBeanFactory) { 527 ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory; 528 for (String autowiredBeanName : autowiredBeanNames) { 529 if (beanFactory.containsBean(autowiredBeanName)) { 530 beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName); 531 } 532 } 533 } 534 535 return resource; 536 } 537 538 539 /** 540 * Class representing generic injection information about an annotated field 541 * or setter method, supporting @Resource and related annotations. 542 */ 543 protected abstract class LookupElement extends InjectionMetadata.InjectedElement { 544 545 protected String name; 546 547 protected boolean isDefaultName = false; 548 549 protected Class<?> lookupType; 550 551 protected String mappedName; 552 553 public LookupElement(Member member, PropertyDescriptor pd) { 554 super(member, pd); 555 } 556 557 /** 558 * Return the resource name for the lookup. 559 */ 560 public final String getName() { 561 return this.name; 562 } 563 564 /** 565 * Return the desired type for the lookup. 566 */ 567 public final Class<?> getLookupType() { 568 return this.lookupType; 569 } 570 571 /** 572 * Build a DependencyDescriptor for the underlying field/method. 573 */ 574 public final DependencyDescriptor getDependencyDescriptor() { 575 if (this.isField) { 576 return new LookupDependencyDescriptor((Field) this.member, this.lookupType); 577 } 578 else { 579 return new LookupDependencyDescriptor((Method) this.member, this.lookupType); 580 } 581 } 582 } 583 584 585 /** 586 * Class representing injection information about an annotated field 587 * or setter method, supporting the @Resource annotation. 588 */ 589 private class ResourceElement extends LookupElement { 590 591 private final boolean lazyLookup; 592 593 public ResourceElement(Member member, AnnotatedElement ae, PropertyDescriptor pd) { 594 super(member, pd); 595 Resource resource = ae.getAnnotation(Resource.class); 596 String resourceName = resource.name(); 597 Class<?> resourceType = resource.type(); 598 this.isDefaultName = !StringUtils.hasLength(resourceName); 599 if (this.isDefaultName) { 600 resourceName = this.member.getName(); 601 if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) { 602 resourceName = Introspector.decapitalize(resourceName.substring(3)); 603 } 604 } 605 else if (embeddedValueResolver != null) { 606 resourceName = embeddedValueResolver.resolveStringValue(resourceName); 607 } 608 if (resourceType != null && Object.class != resourceType) { 609 checkResourceType(resourceType); 610 } 611 else { 612 // No resource type specified... check field/method. 613 resourceType = getResourceType(); 614 } 615 this.name = resourceName; 616 this.lookupType = resourceType; 617 String lookupValue = (lookupAttribute != null ? 618 (String) ReflectionUtils.invokeMethod(lookupAttribute, resource) : null); 619 this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName()); 620 Lazy lazy = ae.getAnnotation(Lazy.class); 621 this.lazyLookup = (lazy != null && lazy.value()); 622 } 623 624 @Override 625 protected Object getResourceToInject(Object target, String requestingBeanName) { 626 return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : 627 getResource(this, requestingBeanName)); 628 } 629 } 630 631 632 /** 633 * Class representing injection information about an annotated field 634 * or setter method, supporting the @WebServiceRef annotation. 635 */ 636 private class WebServiceRefElement extends LookupElement { 637 638 private final Class<?> elementType; 639 640 private final String wsdlLocation; 641 642 public WebServiceRefElement(Member member, AnnotatedElement ae, PropertyDescriptor pd) { 643 super(member, pd); 644 WebServiceRef resource = ae.getAnnotation(WebServiceRef.class); 645 String resourceName = resource.name(); 646 Class<?> resourceType = resource.type(); 647 this.isDefaultName = !StringUtils.hasLength(resourceName); 648 if (this.isDefaultName) { 649 resourceName = this.member.getName(); 650 if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) { 651 resourceName = Introspector.decapitalize(resourceName.substring(3)); 652 } 653 } 654 if (resourceType != null && Object.class != resourceType) { 655 checkResourceType(resourceType); 656 } 657 else { 658 // No resource type specified... check field/method. 659 resourceType = getResourceType(); 660 } 661 this.name = resourceName; 662 this.elementType = resourceType; 663 if (Service.class.isAssignableFrom(resourceType)) { 664 this.lookupType = resourceType; 665 } 666 else { 667 this.lookupType = resource.value(); 668 } 669 this.mappedName = resource.mappedName(); 670 this.wsdlLocation = resource.wsdlLocation(); 671 } 672 673 @Override 674 protected Object getResourceToInject(Object target, String requestingBeanName) { 675 Service service; 676 try { 677 service = (Service) getResource(this, requestingBeanName); 678 } 679 catch (NoSuchBeanDefinitionException notFound) { 680 // Service to be created through generated class. 681 if (Service.class == this.lookupType) { 682 throw new IllegalStateException("No resource with name '" + this.name + "' found in context, " + 683 "and no specific JAX-WS Service subclass specified. The typical solution is to either specify " + 684 "a LocalJaxWsServiceFactoryBean with the given name or to specify the (generated) Service " + 685 "subclass as @WebServiceRef(...) value."); 686 } 687 if (StringUtils.hasLength(this.wsdlLocation)) { 688 try { 689 Constructor<?> ctor = this.lookupType.getConstructor(URL.class, QName.class); 690 WebServiceClient clientAnn = this.lookupType.getAnnotation(WebServiceClient.class); 691 if (clientAnn == null) { 692 throw new IllegalStateException("JAX-WS Service class [" + this.lookupType.getName() + 693 "] does not carry a WebServiceClient annotation"); 694 } 695 service = (Service) BeanUtils.instantiateClass(ctor, 696 new URL(this.wsdlLocation), new QName(clientAnn.targetNamespace(), clientAnn.name())); 697 } 698 catch (NoSuchMethodException ex) { 699 throw new IllegalStateException("JAX-WS Service class [" + this.lookupType.getName() + 700 "] does not have a (URL, QName) constructor. Cannot apply specified WSDL location [" + 701 this.wsdlLocation + "]."); 702 } 703 catch (MalformedURLException ex) { 704 throw new IllegalArgumentException( 705 "Specified WSDL location [" + this.wsdlLocation + "] isn't a valid URL"); 706 } 707 } 708 else { 709 service = (Service) BeanUtils.instantiateClass(this.lookupType); 710 } 711 } 712 return service.getPort(this.elementType); 713 } 714 } 715 716 717 /** 718 * Class representing injection information about an annotated field 719 * or setter method, supporting the @EJB annotation. 720 */ 721 private class EjbRefElement extends LookupElement { 722 723 private final String beanName; 724 725 public EjbRefElement(Member member, AnnotatedElement ae, PropertyDescriptor pd) { 726 super(member, pd); 727 EJB resource = ae.getAnnotation(EJB.class); 728 String resourceBeanName = resource.beanName(); 729 String resourceName = resource.name(); 730 this.isDefaultName = !StringUtils.hasLength(resourceName); 731 if (this.isDefaultName) { 732 resourceName = this.member.getName(); 733 if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) { 734 resourceName = Introspector.decapitalize(resourceName.substring(3)); 735 } 736 } 737 Class<?> resourceType = resource.beanInterface(); 738 if (resourceType != null && Object.class != resourceType) { 739 checkResourceType(resourceType); 740 } 741 else { 742 // No resource type specified... check field/method. 743 resourceType = getResourceType(); 744 } 745 this.beanName = resourceBeanName; 746 this.name = resourceName; 747 this.lookupType = resourceType; 748 this.mappedName = resource.mappedName(); 749 } 750 751 @Override 752 protected Object getResourceToInject(Object target, String requestingBeanName) { 753 if (StringUtils.hasLength(this.beanName)) { 754 if (beanFactory != null && beanFactory.containsBean(this.beanName)) { 755 // Local match found for explicitly specified local bean name. 756 Object bean = beanFactory.getBean(this.beanName, this.lookupType); 757 if (beanFactory instanceof ConfigurableBeanFactory) { 758 ((ConfigurableBeanFactory) beanFactory).registerDependentBean(this.beanName, requestingBeanName); 759 } 760 return bean; 761 } 762 else if (this.isDefaultName && !StringUtils.hasLength(this.mappedName)) { 763 throw new NoSuchBeanDefinitionException(this.beanName, 764 "Cannot resolve 'beanName' in local BeanFactory. Consider specifying a general 'name' value instead."); 765 } 766 } 767 // JNDI name lookup - may still go to a local BeanFactory. 768 return getResource(this, requestingBeanName); 769 } 770 } 771 772 773 /** 774 * Extension of the DependencyDescriptor class, 775 * overriding the dependency type with the specified resource type. 776 */ 777 private static class LookupDependencyDescriptor extends DependencyDescriptor { 778 779 private final Class<?> lookupType; 780 781 public LookupDependencyDescriptor(Field field, Class<?> lookupType) { 782 super(field, true); 783 this.lookupType = lookupType; 784 } 785 786 public LookupDependencyDescriptor(Method method, Class<?> lookupType) { 787 super(new MethodParameter(method, 0), true); 788 this.lookupType = lookupType; 789 } 790 791 @Override 792 public Class<?> getDependencyType() { 793 return this.lookupType; 794 } 795 } 796 797}