Spring--基于注解的IoC容器配置

在阅读本文前,需要对Spring中的依赖注入机制有一定了解。本文的内容以代码为主,每个标题对应一种具体的使用案例,文字描述较少,详细内容可查看:Spring–基于注解的IoC容器配置(详细版)

1. 使用@Autowired

下面的代码展示了@Autowired的多种使用方式。

1.1 构造函数

@Component
public class MovieRecommender {

	private final CustomerPreferenceDao customerPreferenceDao;

	@Autowired
	public MovieRecommender(CustomerPreferenceDao customerPreferenceDao {
		this.customerPreferenceDao = customerPreferenceDao;
	}

	// ...
}

bean定义

@Configuration
public class BeanConfiguration {
    @Bean
    public CustomerPreferenceDao customerPreferenceDao() {
        return new CustomerPreferenceDao();
    }
}

1.2 setter方法

public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Autowired
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// ...
}

bean定义

@Configuration
public class BeanConfiguration {
    @Bean
    public MovieFinder movieFinder() {
        return new MovieFinder();
    }
}

1.3 任意名称和多个参数的方法

public class MovieRecommender {

	private MovieCatalog movieCatalog;

	private CustomerPreferenceDao customerPreferenceDao;

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

	// ...
}

bean定义

@Configuration
public class BeanConfiguration {
    @Bean
    public CustomerPreferenceDao customerPreferenceDao() {
        return new CustomerPreferenceDao();
    }
    
    @Bean
    public MovieCatalog movieCatalog() {
        return new MovieCatalog();
    }
}

1.4 属性字段

@Autowired可以同时存在于字段和构造函数上。

public class MovieRecommender {

	private final CustomerPreferenceDao customerPreferenceDao;

	@Autowired
	private MovieCatalog movieCatalog;

	@Autowired
	public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
		this.customerPreferenceDao = customerPreferenceDao;
	}

	// ...
}

1.5 数组、集合类型

通过将@Autowired注解添加到需要该类型数组的字段或方法上,让Spring从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;
	}

	// ...
}

key中存放的是在bean的名称,如果bean通过方法定义,则key为方法名称。

public class MovieRecommender {

	private Map<String, MovieCatalog> movieCatalogs;

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

	// ...
}

1.6 可选依赖

  • 默认情况下,当给定注入点没有匹配的候选 bean 时,自动装配会失败。对于声明的数组、集合或map,至少需要一个匹配元素。
  • @Autowired中required值默认为true。你可以更改此行为,通过将 @Autowired 中的 required 属性设置为 false,跳过无法满足的注入点:
public class SimpleMovieLister {

	private MovieFinder movieFinder;

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

	// ...
}

或者,也可以通过Java 8的java.util.Optional来表示特定依赖关系的非必须属性:

public class SimpleMovieLister {

	@Autowired
	public void setMovieFinder(Optional<MovieFinder> movieFinder) {
		...
	}
}

从 Spring Framework 5.0 开始,还可以使用 @Nullable 注解:

public class SimpleMovieLister {

	@Autowired
	public void setMovieFinder(@Nullable MovieFinder movieFinder) {
		...
	}
}

1.7 可解析依赖

可以将 @Autowired 用于众所周知的可解析的依赖:BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource。这些接口及其扩展接口(例如 ConfigurableApplicationContextResourcePatternResolver)会被自动解析,无需特殊设置。

public class MovieRecommender {

	@Autowired
	private ApplicationContext context;

	public MovieRecommender() {
	}

	// ...
}

2. 使用@Primary

以下配置将firstMovieCatalog 定义为primaryMovieCatalog 类型:

@Configuration
public class MovieConfiguration {

	@Bean
	@Primary
	public MovieCatalog firstMovieCatalog() { ... }

	@Bean
	public MovieCatalog secondMovieCatalog() { ... }

	// ...
}

通过上面的配置,MovieReecommender将使用firstMovieCatalog来装配:

@Component
public class MovieRecommender {

	@Autowired
	private MovieCatalog movieCatalog;

	// ...
}

3. 使用Qualifiers

3.1 @Qualifier注解

可以将qualifier的值与特定参数相关联,从而缩小类型匹配集的范围,以便为每个参数选择特定的 bean。

public class MovieRecommender {

	@Autowired
	@Qualifier("main")
	private MovieCatalog movieCatalog;

	// ...
}

可以在单独的构造器参数或setter方法参数上使用@Qualifier注解,如下例所示:

@Component
public class MovieRecommender {

	private final MovieCatalog movieCatalog;

	private final CustomerPreferenceDao customerPreferenceDao;

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

	// ...
}

下面的代码展示了对应的bean定义:

@Configuration
public class BeanConfiguration {
    @Bean
    public CustomerPreferenceDao customerPreferenceDao() {
        return new CustomerPreferenceDao();
    }
    
    @Bean
    @Qualifier("main")
    public MovieCatalog movieCatalog() {
        return new MovieCatalog();
    }
}

如果匹配失败,bean的名称被视为默认qualifier值。

qualifier也适用于泛型集合,例如 Set<MovieCatalog>。在这种情况下,根据声明的qualifier,所有匹配的 bean 都会作为集合注入。这意味着qualifier不必是唯一的。相反,它们构成了过滤标准。例如,你可以使用相同的qualifier值“action”定义多个 MovieCatalog bean,所有这些 bean 都被注入到使用 @Qualifier("action") 注解的 Set<MovieCatalog> 中。

3.2 自定义Qualifier注解

你可以创建自定义的qualifier注解。为此,请定义一个注解并在定义中提供 @Qualifier 注解:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

	String value();
}

然后,你可以在自动装配字段和参数上提供自定义qualifier,如下例所示:

@Component
public class MovieRecommender {

	@Autowired
	@Genre("Action")
	private MovieCatalog actionCatalog;

	private MovieCatalog comedyCatalog;

	@Autowired
	public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
		this.comedyCatalog = comedyCatalog;
	}

	// ...
}

bean定义代码如下:

@Configuration
public class BeanConfiguration {
    @Bean
    @Genre("action")
    public MovieCatalog actionCatalog() {
        MovieCatalog movieCatalog = new MovieCatalog();
        movieCatalog.setName("action");
        return movieCatalog;
    }
    
    @Bean
    @Genre("comedy")
    public MovieCatalog comedyCatalog() {
        MovieCatalog movieCatalog = new MovieCatalog();
        movieCatalog.setName("comedy");
        return movieCatalog;
    }
}
a. 无属性注解

在某些情况下,使用不带值的注解就足够了。当注解服务于更通用的目的,并且可以应用于多种不同类型的依赖关系时,这可能很有用。例如,你可以提供一个离线目录,可以在没有可用 Internet 连接时进行搜索。首先,定义简单的注解,如下例所示:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}

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

public class MovieRecommender {

	@Autowired
	@Offline
	private MovieCatalog offlineCatalog;

	// ...
}

以下是bean定义:

@Configuration
public class BeanConfiguration {
    @Bean
    @Offline
    public MovieCatalog offlineCatalog() {
        MovieCatalog movieCatalog = new MovieCatalog();
        movieCatalog.setName("offline");
        return movieCatalog;
    }
}
b. 多属性注解

除了简单的value属性,还可以定义命名的属性。如果之后在要自动装配的字段或参数上指定了多个属性值,则 Bean 定义必须与所有属性值匹配才能被视为自动装配的候选者。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

	String genre();

	Format format();
}

Format 是一个枚举,定义如下:

public enum Format {
	VHS, DVD, BLURAY
}

要自动装配的字段使用自定义qualifier进行注解,并包含两个属性的值:genre和format,如下例所示:

@Component
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 定义中应该包含匹配的qualifier值:

@Configuration
public class BeanConfiguration {
    @Bean
    @MovieQualifier(format=Format.VHS, genre="Action")
    public MovieCatalog actionVhsCatalog() {
        MovieCatalog movieCatalog = new MovieCatalog();
        movieCatalog.setName("actionVhsCatalog");
        return movieCatalog;
    }
    @Bean
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    public MovieCatalog comedyVhsCatalog() {
        MovieCatalog movieCatalog = new MovieCatalog();
        movieCatalog.setName("comedyVhsCatalog");
        return movieCatalog;
    }
    @Bean
    @MovieQualifier(format=Format.DVD, genre="Action")
    public MovieCatalog actionDvdCatalog() {
        MovieCatalog movieCatalog = new MovieCatalog();
        movieCatalog.setName("actionDvdCatalog");
        return movieCatalog;
    }
    @Bean
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    public MovieCatalog comedyBlurayCatalog() {
        MovieCatalog movieCatalog = new MovieCatalog();
        movieCatalog.setName("comedyBlurayCatalog");
        return movieCatalog;
    }
}

4. 泛型作为Qualifiers

除了 @Qualifier 注解之外,还可以使用 Java 泛型作为隐式的Qualifier。

@Configuration
public class MyConfiguration {

	@Bean
	public StringStore stringStore() {
		return new StringStore();
	}

	@Bean
	public IntegerStore integerStore() {
		return new IntegerStore();
	}
}

假设上面的 beans 实现了通用接口(即 Store<String> Store<Integer>):

public class StringStore implements Store<String> {
}

public class IntegerStore implements Store<Integer> {
}

你可以 @Autowire Store 接口,泛型将作为qualifier:

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

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

qualifier也适用于自动装配列表、Map 实例和数组。下面示例自动装配一个List:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

5. 使用CustomAutowireConfigurer

CustomAutowireConfigurer 是一个 BeanFactoryPostProcessor,它允许你注册自定义的qualifier注解类型,即使它们没有使用 Spring 的 @Qualifier 注解。以下示例展示如何使用 CustomAutowireConfigurer

  • 定义一个注解
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomQualifier {
    String value();
}

  • 将该注解注册为qualifier注解
@Configuration
public class MyConfiguration {
	@Bean
    public CustomAutowireConfigurer customAutowireConfigurer() {
        CustomAutowireConfigurer customConfigurer = new CustomAutowireConfigurer();
        customConfigurer.setCustomQualifierTypes(Collections.singleton(CustomQualifier.class));
        return customConfigurer;
    }
}

以上代码定义了一个名为@CustomQualifier的qualifier注解。

假设有一个Driver接口有两个实现类NetworkDriverMemoryDriver

@Component
@CustomQualifier("net")
public class NetworkDriver implements Driver {
    // ...
}

@Component
@CustomQualifier("mem")
public class MemoryDriver implements Driver {
    // ...
}

在需要注入Driver实例的位置,可以使用@Autowired注解和自定义的qualifier进行装配,下面代码中注入的实例将是NetworkDriver类型:

@Autowired
@CustomQualifier("net")
private Driver driver;

6. 使用@Resource

@Resource 通过名称实现依赖注入。默认情况下,Spring 将该注解中的值解析为要注入的 bean 名称。它遵循名称语义。

public class SimpleMovieLister {

	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;
	}
}

使用@Resource时,如果没有指定名称,@Resource将通过类型进行匹配,而不是特定名称。@Resource也会解析可解析的依赖,包括以下接口:BeanFactoryApplicationContextResourceLoaderApplicationEventPublisher MessageSource,这与@Autowired类似。

下面这个例子中,customerPreferenceDao字段首先寻找一个名为customerPreferenceDao的bean,然后寻找能和CustomerPreferenceDao相匹配的primary类型:

public class MovieRecommender {

	@Resource
	private CustomerPreferenceDao customerPreferenceDao;

	@Resource
	private ApplicationContext context;

	public MovieRecommender() {
	}

	// ...
}

7. 使用@Value

@Value通常被用来注入外部属性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

使用以下配置:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

和对应的配置文件application.properties

catalog.name=MovieCatalog

在这个例子中,catalog参数和字段的值将等于MovieCatalog(配置文件中定义的值)

a. 值解析器配置

Spring 提供了默认的内置值解析器,解析规则较为宽松。它尝试解析属性值,如果无法解析,则将注入属性名称(例如 ${catalog.name})作为值。如果你想对不存在的值保持严格控制,你应该声明一个 PropertySourcesPlaceholderConfigurerbean,如下例所示:

@Configuration
public class AppConfig {

	@Bean
	public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}
}

使用 JavaConfig 配置 PropertySourcesPlaceholderConfigurer 时,@Bean 方法必须是静态的。

如果任何 ${} 占位符无法解析,使用上述配置可确保 Spring 初始化失败。还可以使用 setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator 等方法来自定义占位符。

Spring Boot 默认配置了一个 PropertySourcesPlaceholderConfigurer bean,它会从 application.properties application.yml 文件获取属性。

b. 设置默认值

可以为属性提供默认值,如下例所示:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

c. 自定义类型转换

Spring 提供的内置转换器可以自动处理简单的类型转换(例如,转换到 Integer 或 int)。多个逗号分隔值可以自动转换为字符串数组,无需额外的代码。

Spring BeanPostProcessor 在后台使用 ConversionService 来将 @Value 中的 String 值转换为目标类型。如果你想为你自定义的类型提供转换支持,你可以提供你自己的 ConversionService bean 实例,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

d. 使用SpEL表达式

@Value包含SpEL 表达式 时,其中的值将会在运行时动态计算获得,如下例所示:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL 还允许使用更复杂的数据结构:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

8. 使用@PostConstruct和@PreDestroy

CommonAnnotationBeanPostProcessor不仅可以识别@Resource注解,还支持JSR-250生命周期注解:jakarta.annotation.PostConstructjakarta.annotation.PreDestroy。Spring2.5提供了对这些注解的支持,为 initialization callbacksdestruction callbacks中描述的生命周期回调机制提供了替代方案。只要在Spring ApplicationContext中注册了CommonAnnotationBeanPostProcessor,拥有这些注解(@PostConstruct@PreDestroy)的方法将作为接口方法或显示声明的回调方法,在Spring生命周期对应的时间点上被调用。

在下面的示例中,cache在初始化后预先填充数据,并在销毁时清除数据:

public class CachingMovieLister {

	@PostConstruct
	public void populateMovieCache() {
		// populates the movie cache upon initialization...
	}

	@PreDestroy
	public void clearMovieCache() {
		// clears the movie cache upon destruction...
	}
}

关于组合使用多种生命周期机制的详细介绍,请参考组合使用生命周期机制

@Resource一样,@PostConstruct@PreDestroy注解类型是从JDK 6到8的标准Java库的一部分。但是,整个javax.annotation包在JDK 9中与核心Java模块分离,并最终在JDK 11中删除。从 Jakarta EE 9 开始,该包现在位于 jakarta.annotation 中。如果要使用,现在需通过 Maven Central 获取 jakarta.annotation-api 工件,只需像任何其他库一样将其添加到应用程序的类路径中即可。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值