您当前的位置: 首页 >  容器

关于Spring注解容器配置的那些事,掌握这几点,不再难!

发布时间:2021-10-15 21:28:30 ,浏览量:0

在配置Spring时注解是否比XML更好?

基于注解配置的引入引出了一个问题——这种方式是否比基于XML的配置更好。简短的回答是视情况而定。长一点的回答是每种方法都有它的优点和缺点,通常是由开发者决定哪一种策略更适合他们。由于注解的定义方式,注解在它们的声明中提供了许多上下文,导致配置更简短更简洁。然而,XML擅长连接组件而不必接触源代码或重新编译它们。一些开发者更喜欢接近源代码,而另一些人则认为基于注解的类不再是POJOs,此外,配置变的去中心化,而且更难控制。

无论选择是什么,Spring都能容纳这两种风格,甚至可以将它们混合在一起。值得指出的是,通过它的Java配置选项,Spring允许注解以一种非入侵的方式使用,不触碰目标组件源码和那些工具,所有的配置风格由Spring工具套件支持。

基于注解的配置提供了一种XML设置的可替代方式,它依赖于字节码元数据来连接组件,而不是用尖括号声明的方式。代替使用XML来描述bean连接,开发者通过将注解使用在相关的类,方法或字段声明中,将配置移动到了组件类本身的内部。正如在“Example: The RequiredAnnotationBeanPostProcessor”那节提到的那样,使用BeanPostProcessor与注解结合是扩展Spring IoC容器的的常见方法。例如,Spring 2.0引入了@Required注解来执行需要的属性的可能性。Spring 2.5使以同样地通用方法来驱动Spring的依赖注入变为可能。本质上来说,@Autowired提供了如3.4.5小节描述的同样的能力。“Autowiring collaborators”但更细粒度的控制和更广的应用性。Spring 2.5也添加对JSR-250注解的支持,例如,@PostConstruct和@PreDestroy 。Spring 3.0添加了对JSR-330,包含在javax.inject包内的注解(Java的依赖注入)的支持,例如@Inject和@Named。关于这些注解的细节可以在相关的小节找到。

注解注入在XML注入之前进行,因此对于通过两种方法进行组装的属性后者的配置会覆盖前者。

跟以前一样,你可以作为单独的bean定义来注册它们,但也可以通过在一个基于XML的Spring配置(注入包含上下文命名空间)中包含下面的标签来隐式的注册它们:

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/>  private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } 

这个注解仅仅是表明受影响的bean属性必须在配置时通过显式的bean定义或自动组装填充。如果受影响的bean属性没有填充,容器会抛出一个异常,这允许及早明确的失败,避免NullPointerExceptions或后面出现类似的情况。仍然建议你在bean类本身加入断言,例如,加入到初始化方法中。这样做可以强制这些需要的引用和值,甚至是你在容器外部使用这个类的时候。

2 @Autowired

在下面的例子中JSR 330的@Inject注解可以用来代替Spring的@Autowired注解。

你可以将@Autowired注解应用到构造函数上。

public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... } 

从Spring框架4.3起,如果目标bena仅定义了一个构造函数,那么@Autowired注解的构造函数不再是必要的。如果一些构造函数是可获得的,至少有一个必须要加上注解,以便于告诉容器使用哪一个。

正如预料的那样,你也可以将@Autowired注解应用到“传统的”setter方法上:

public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } 

你也可以应用注解到具有任何名字和/或多个参数的方法上:

public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... } 

你也可以应用@Autowired到字段上,甚至可以与构造函数混合用:

public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired private MovieCatalog movieCatalog; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... } 

通过给带有数组的字段或方法添加@Autowired注解,也可以从ApplicationContext中提供一组特定类型的bean:

public class MovieRecommender { @Autowired private MovieCatalog[] movieCatalogs; // ... } 

同样也可以应用到具有同一类型的集合上:

public class MovieRecommender { private Set<MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... } 

如果你希望数组或列表中的项按指定顺序排序,你的bean可以实现org.springframework.core.Ordered接口,或使用@Order或标准@Priority注解。

只要期望的key是String,那么类型化的Maps就可以自动组装。Map的值将包含所有期望类型的beans,key将包含对应的bean名字:

public class MovieRecommender { private Map<String, MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... } 

默认情况下,当没有候选beans可获得时,自动组装会失败;默认的行为是将注解的方法,构造函数和字段看作指明了需要的依赖。这个行为也可以通过下面的方式去改变。

public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired(required=false) public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } 

每个类只有一个构造函数可以标记为必需的,但可以注解多个非必需的构造函数。在这种情况下,会考虑这些候选者中的每一个,Spring使用最贪婪的构造函数,即依赖最满足的构造函数,具有最大数目的参数。

建议在@Required注解之上使用@Autowired的required特性。required特性表明这个属性自动装配是不需要的,如果这个属性不能被自动装配,它会被忽略。另一方面@Required是更强大的,在它强制这个属性被任何容器支持的bean设置。如果没有值注入,会抛出对应的异常。

你也可以对那些已知的具有可解析依赖的接口使用@Autowired:BeanFactory,ApplicationContext,Environment,ResourceLoader,ApplicationEventPublisher和MessageSource。这些接口和它们的扩展接口,例如ConfigurableApplicationContext或ResourcePatternResolver,可以自动解析,不需要特别的设置。

public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ... } 

@Autowired、@Inject、@Resource和@Value都是通过Spring的BeanPostProcessor实现处理的,这反过来意味着,你不能在自己的BeanPostProcessor或BeanFactoryPostProcessor中使用这些注解。 这些类型必须显式通过XML或使用Spring的@Bean方法来装配。

3 用@Primary微调基于注解的自动装配

因为根据类型的自动装配可能会导致多个候选目标,所以在选择过程中进行更多的控制经常是有必要的。一种方式通过Spring的@Primary注解来完成。当有个多个候选bean要组装到一个单值的依赖时,@Primary表明指定的bean应该具有更高的优先级。如果确定一个’primary’ bean位于候选目标中间,它将是那个自动装配的值。

假设我们具有如下配置,将firstMovieCatalog定义为主要的MovieCatalog。

@Configuration public class MovieConfiguration { @Bean @Primary public MovieCatalog firstMovieCatalog() { ... } @Bean public MovieCatalog secondMovieCatalog() { ... } // ... } 

根据这样的配置,下面的MovieRecommender将用firstMovieCatalog进行自动装配。

public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; // ... } 

对应的bean定义如下:

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog" primary="true">    @Autowired @Qualifier("main") private MovieCatalog movieCatalog; // ... } 

@Qualifier注解也可以指定单个构造函数参数或方法参数:

public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main")MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... } 

对应的bean定义如下。限定符值为”main”的bean被组装到有相同值的构造函数参数中。

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier value="main"/>   ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Genre { String value(); } 

然后你可以在自动装配的字段和参数上提供定制的限定符:

public class MovieRecommender { @Autowired @Genre("Action") private MovieCatalog actionCatalog; private MovieCatalog comedyCatalog; @Autowired public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) { this.comedyCatalog = comedyCatalog; } // ... } 

接下来,提供候选bean定义的信息。你可以添加标记作为标记的子元素,然后指定匹配你的定制限定符注解的类型和值。类型用来匹配注解的全限定类名称。或者,如果没有名称冲突的风险,为了方便,你可以使用简写的类名称。下面的例子证实了这些方法。

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="Genre" value="Action"/>   ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { } 

然后将注解添加到要自动装配的字段或属性上:

public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... } 

现在bean定义只需要一个限定符类型:

<bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/>  ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MovieQualifier { String genre(); Format format(); } 

这种情况下Format是枚举类型:

public enum Format { VHS, DVD, BLURAY } 

要自动装配的字段使用定制限定符进行注解,并且包含了两个属性值:genre和format。

public class MovieRecommender { @Autowired @MovieQualifier(format=Format.VHS, genre="Action") private MovieCatalog actionVhsCatalog; @Autowired @MovieQualifier(format=Format.VHS, genre="Comedy") private MovieCatalog comedyVhsCatalog; @Autowired @MovieQualifier(format=Format.DVD, genre="Action") private MovieCatalog actionDvdCatalog; @Autowired @MovieQualifier(format=Format.BLURAY, genre="Comedy") private MovieCatalog comedyBluRayCatalog; // ... } 

最后,bean定义应该包含匹配的限定符值。这个例子也证实了bean元属性可以用来代替子元素。如果可获得,它和它的属性优先级更高,如果当前没有限定符,自动装配机制会将内的值作为备用,正如下面的例子中的最后两个bean定义。

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Action"/>      @Bean public StringStore stringStore() { return new StringStore(); } @Bean public IntegerStore integerStore() { return new IntegerStore(); } } 

假设上面的beans实现了一个泛型接口,例如,Store和Store,你可以@AutowireStore接口,泛型将作为限定符使用:

@Autowired private Store<String> s1; //qualifier, injects the stringStore bean @Autowired private Store<Integer> s2; //qualifier, injects the integerStore bean 

当自动装配Lists,Maps和Arrays时,也会应用泛型限定符:

// Inject all Store beans as long as they have angeneric // Storebeans will not appear in this list @Autowired private List<Store<Integer>> s; 
3.9.6 CustomAutowireConfigurer

CustomAutowireConfigurer是一个能使你注册自己的定制限定符注解类型的BeanFactoryPostProcessor,即使它们不使用Spring的@Qualifier

<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer"> <property name="customQualifierTypes"> <set> <value>example.CustomQualifier private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } 

如果没有显式的指定名字,默认名字从字段名或setter方法中取得。在字段情况下,它采用字段名称;在setter方法情况下,它采用bean的属性名。因此下面的例子将名字为movieFinder的bean注入到它的setter方法中:

public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } 

注解提供的名字被CommonAnnotationBeanPostProcessor感知的ApplicationContext解析为bean名字。如果你显式地配置了Spring的SimpleJndiBeanFactory,名字会通过JNDI解析。但是建议你依赖默认行为,简单使用Spring的JNDI查找功能保护间接查找级别。

在@Resource特有的没有显式名字指定的情况下,类似于@Autowired,@Resource会进行主要的匹配类型来代替指定名字的bean并解析已知的可解析依赖:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher和MessageSource接口。

因此在下面的例子中,customerPreferenceDao字段首先查找名字为customerPreferenceDao的bean,然后回退到主要的类型为CustomerPreferenceDao的类型匹配。”context”字段会注入基于已知的可解析依赖类型ApplicationContext。

public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; public MovieRecommender() { } // ... } 
8 @PostConstruct和@PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource注解,而且识别JSR-250生命周期注解。在Spring 2.5引入了对这些注解的支持,也提供了在初始化回调函数和销毁回调函数中描述的那些注解的一种可替代方式。假设CommonAnnotationBeanPostProcessor在Spring的ApplicationContext中注册,执行这些注解的方法在生命周期的同一点被调用,作为对应的Spring生命周期接口方法或显式声明的回调方法。在下面的例子中,缓存会预先放置接近初始化之前,并在销毁之前清除。

public class CachingMovieLister { @PostConstruct public void populateMovieCache() { // populates the movie cache upon initialization... } @PreDestroy public void clearMovieCache() { // clears the movie cache upon destruction... } } 
关注
打赏
1688896170
查看更多评论

暂无认证

  • 0浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0588s