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