Spring容器:从依赖管理到注解配置全解析
# 一、Spring 容器的依赖管理
# 1、依赖注入
依赖注入(Dependency Injection,简称 DI)是一种设计模式,它通过将对象依赖关系的创建和管理从对象内部转移到外部,从而降低对象之间的耦合度。
在 Spring 框架中,依赖注入是实现控制反转(Inversion of Control,简称 IoC)的主要方式。
通过依赖注入,Spring 容器负责创建对象实例,并将对象所依赖的其他对象注入到该对象中,使得对象之间的依赖关系由容器来管理,而不是由对象自身来创建和维护。
# 1.1、构造器注入
构造器注入是依赖注入的一种方式,它通过对象的构造函数来注入依赖对象。
在 Spring 配置文件中,可以使用<constructor-arg>
标签来指定构造函数的参数。
构造器注入的优点是可以确保对象在创建时就已经拥有了所有必要的依赖,并且依赖关系清晰明了。缺点是如果依赖过多,构造函数的参数列表会变得冗长,不利于维护。
示例代码:
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="userRepository"/>
</bean>
<bean id="userRepository" class="com.example.UserRepository"/>
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 业务方法
}
# 1.2、Setter 方法注入
Setter 方法注入是另一种常见的依赖注入方式,它通过对象的 Setter 方法来注入依赖对象。在 Spring 配置文件中,使用<property>
标签来指定 Setter 方法的参数。Setter 方法注入的优点是灵活性高,可以在对象创建后动态地修改依赖关系。缺点是可能会导致对象在使用时依赖尚未被注入,从而引发空指针异常。
示例代码:
<bean id="userService" class="com.example.UserService">
<property name="userRepository" ref="userRepository"/>
</bean>
<bean id="userRepository" class="com.example.UserRepository"/>
public class UserService {
private UserRepository userRepository;
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 业务方法
}
# 1.3、字段注入
字段注入是一种通过直接在类的字段上使用注解(如@Autowired
)来实现依赖注入的方式。这种方式简洁明了,代码量少,不需要编写构造函数或 Setter 方法。然而,字段注入也有一些缺点,例如它使得代码的可测试性变差,因为无法在测试时轻易地替换依赖对象。
示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 业务方法
}
# 2、依赖与配置的详细信息
# 2.1、详细的依赖和配置
在 Spring 中,依赖和配置信息可以通过 XML 配置文件或注解来定义。
依赖配置包括指定依赖对象的类型、名称以及如何创建依赖对象等信息。配置信息还可以包括对象的属性值、初始化方法和销毁方法等。
通过详细的依赖和配置,Spring 容器能够准确地创建和管理对象及其依赖关系。
# 2.2、使用depends-on
depends-on
属性用于指定一个 Bean 在初始化之前需要依赖的其他 Bean。当一个 Bean 使用了depends-on
属性时,Spring 容器会先初始化depends-on
指定的 Bean,然后再初始化当前 Bean。
这在某些情况下非常有用,例如当一个 Bean 需要依赖另一个 Bean 的初始化结果时。
示例代码:
<bean id="dataSource" class="com.example.DataSource"/>
<bean id="userRepository" class="com.example.UserRepository" depends-on="dataSource">
<property name="dataSource" ref="dataSource"/>
</bean>
# 3、懒加载的 Beans
# 3.1、懒加载 Beans 的原理
懒加载(Lazy Loading)是指在需要使用 Bean 时才进行初始化,而不是在容器启动时就初始化所有的 Bean。
Spring 通过在 Bean 的定义中设置lazy-init="true"
来实现懒加载。在使用注解配置时,可以使用@Lazy
注解来实现懒加载。
懒加载的原理是利用代理机制,在容器启动时创建一个代理对象,当真正需要使用 Bean 时,代理对象才会去创建实际的 Bean 实例。
# 3.2、懒加载 Beans 的应用场景
懒加载适用于那些初始化过程比较耗时或者在应用启动阶段不需要立即使用的 Bean。例如,一些与数据库连接、远程服务调用相关的 Bean,使用懒加载可以加快应用的启动速度,提高系统的性能。
示例代码:
<bean id="userService" class="com.example.UserService" lazy-init="true"/>
# 4、自动装配协作器
# 4.1、自动装配的原理
自动装配(Autowiring)是 Spring 容器根据一定的规则自动为 Bean 装配依赖对象的机制。
Spring 提供了多种自动装配的方式,如按类型(byType)、按名称(byName)、构造函数(constructor)和自动检测(autodetect)等。
自动装配的原理是通过扫描容器中的 Bean 定义,根据自动装配的规则找到匹配的依赖对象,并将其注入到目标 Bean 中。
# 4.2、自动装配的配置
在 Spring 配置文件中,可以通过<bean>
标签的autowire
属性来配置自动装配的方式。例如,autowire="byType"
表示按类型自动装配,autowire="byName"
表示按名称自动装配。在使用注解配置时,可以使用@Autowired
注解来实现自动装配。
示例代码:
<bean id="userService" class="com.example.UserService" autowire="byType"/>
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 业务方法
}
# 5、方法注入
方法注入是指 Spring 容器能够动态地替换 Bean 中的方法实现。这种方式通常用于解决单例 Bean 中依赖非单例 Bean 的问题。Spring 通过使用 CGLIB 库来实现方法注入,它可以在运行时生成一个子类,并重写目标方法,从而实现对方法的替换。
示例代码:
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
factoryBean.setTargetObject(new SomeService());
factoryBean.setTargetMethod("someMethod");
factoryBean.setArguments(new Object[]{"arg1", "arg2"});
return factoryBean;
}
}
class SomeService {
public String someMethod(String arg1, String arg2) {
return arg1 + " " + arg2;
}
}
# 二、基于注解的容器配置
# 1、使用@Autowired
# 1.1、@Autowired
的基本用法
@Autowired
是 Spring 框架中最常用的注解之一,用于实现自动装配。它可以应用在字段、Setter 方法和构造函数上。当 Spring 容器扫描到带有@Autowired
注解的元素时,会根据类型在容器中查找匹配的 Bean,并将其注入到目标元素中。
示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 业务方法
}
# 1.2、@Autowired
的高级特性
@Autowired
注解还支持一些高级特性,例如可以使用required
属性来指定依赖是否必须存在。默认情况下,required
属性为true
,表示依赖必须存在,如果在容器中找不到匹配的 Bean,会抛出异常。
当required
属性设置为false
时,如果找不到匹配的 Bean,Spring 容器不会抛出异常,而是将依赖对象设置为null
。
示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired(required = false)
private UserRepository userRepository;
// 业务方法
}
# 2、使用@Primary
或@Fallback
微调自动装配
# 2.1、@Primary
的用法
@Primary
注解用于指定当有多个类型相同的 Bean 可供选择时,优先选择被@Primary
注解标记的 Bean 进行自动装配。这在解决自动装配时的歧义问题时非常有用。
示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private DataSource dataSource;
// 业务方法
}
@Configuration
public class AppConfig {
@Bean
@Primary
public DataSource dataSource1() {
// 创建并返回数据源1
}
@Bean
public DataSource dataSource2() {
// 创建并返回数据源2
}
}
# 2.2、@Fallback
的用法
@Fallback
注解用于指定当主要的 Bean 不可用时,作为备用的 Bean 进行自动装配。它通常与@Primary
注解配合使用,提供一种更加灵活的自动装配策略。
示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Fallback;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private DataSource dataSource;
// 业务方法
}
@Configuration
public class AppConfig {
@Bean
@Primary
public DataSource dataSource1() {
// 创建并返回数据源1
}
@Bean
@Fallback
public DataSource dataSource2() {
// 创建并返回数据源2
}
}
# 3、使用限定符微调自动装配
# 3.1、限定符的基本概念
限定符(Qualifier)是 Spring 提供的一种更细粒度的自动装配控制方式。它可以在多个相同类型的 Bean 中,根据限定符的值来选择特定的 Bean 进行自动装配。限定符可以通过@Qualifier
注解来定义。
# 3.2、限定符的应用示例
假设容器中有两个类型为DataSource
的 Bean,分别命名为dataSource1
和dataSource2
。
可以通过@Qualifier
注解来指定使用哪个DataSource
进行自动装配,例如:@Autowired @Qualifier("dataSource1") DataSource dataSource;
。
示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
@Qualifier("dataSource1")
private DataSource dataSource;
// 业务方法
}
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource1() {
// 创建并返回数据源1
}
@Bean
public DataSource dataSource2() {
// 创建并返回数据源2
}
}
# 4、使用泛型作为自动装配限定符
# 4.1、泛型限定符的原理
使用泛型作为自动装配限定符是一种基于泛型类型信息来进行自动装配的方式。Spring 容器在进行自动装配时,会根据泛型的类型信息来匹配相应的 Bean。这种方式可以在一定程度上简化配置,并且提高代码的可读性和维护性。
# 4.2、泛型限定符的示例
假设有一个泛型接口Repository<T>
,以及两个实现类UserRepository
和OrderRepository
。可以通过在注入时指定泛型类型来实现自动装配,例如:@Autowired Repository<User> userRepository;
。
示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private Repository<User> userRepository;
// 业务方法
}
public interface Repository<T> {
// 接口方法
}
public class UserRepository implements Repository<User> {
// 实现接口方法
}
# 5、使用CustomAutowireConfigurer
CustomAutowireConfigurer
是 Spring 框架中的一个类,它允许你自定义自动装配的规则。
通过 CustomAutowireConfigurer
,你可以指定哪些类型应该通过自动装配注入到哪些属性中,这在一些特殊的场景下非常有用,比如你想要覆盖 Spring 默认的自动装配行为。
首先,我们定义几个示例类,用于演示 CustomAutowireConfigurer
的用法。
// 定义一个接口
interface MyService {
void doSomething();
}
// 实现接口
class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
// 定义一个需要注入 MyService 的类
class MyClient {
private MyService myService;
public void setMyService(MyService myService) {
this.myService = myService;
}
public void performAction() {
if (myService != null) {
myService.doSomething();
}
}
}
使用 CustomAutowireConfigurer
进行自定义自动装配配置:
import org.springframework.beans.factory.config.CustomAutowireConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class AppConfig {
// 定义 MyService 的 Bean
@Bean
public MyService myService() {
return new MyServiceImpl();
}
// 定义 MyClient 的 Bean
@Bean
public MyClient myClient() {
return new MyClient();
}
// 配置 CustomAutowireConfigurer
@Bean
public CustomAutowireConfigurer customAutowireConfigurer() {
CustomAutowireConfigurer configurer = new CustomAutowireConfigurer();
Map<Class<?>, Object> customEditors = new HashMap<>();
// 指定 MyService 类型的属性应该注入 myService Bean
customEditors.put(MyService.class, myService());
configurer.setCustomEditors(customEditors);
return configurer;
}
}
测试配置:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 创建 Spring 应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取 MyClient Bean
MyClient myClient = context.getBean(MyClient.class);
// 调用方法
myClient.performAction();
// 关闭上下文
context.close();
}
}
代码解释:
- 定义示例类:
MyService
是一个接口,MyServiceImpl
是它的实现类,MyClient
类包含一个MyService
类型的属性,需要进行注入。 - 配置
CustomAutowireConfigurer
:在AppConfig
类中,我们创建了CustomAutowireConfigurer
的 Bean,并通过setCustomEditors
方法指定了MyService
类型的属性应该注入myService
Bean。 - 测试配置:在
Main
类中,我们创建了 Spring 应用上下文,获取MyClient
Bean,并调用其performAction
方法。由于我们使用了CustomAutowireConfigurer
进行自定义自动装配,MyClient
类中的MyService
属性会被正确注入。
注意事项:
CustomAutowireConfigurer
通常用于覆盖 Spring 默认的自动装配行为,在大多数情况下,Spring 的默认自动装配机制已经足够满足需求。- 确保在配置
CustomAutowireConfigurer
时,指定的 Bean 名称和类型是正确的,否则可能会导致注入失败。
# 6、使用@Resource
注入
# 6.1、@Resource
的基本用法
@Resource
是 JSR-250 规范中定义的注解,用于实现依赖注入。它可以应用在字段和 Setter 方法上。
@Resource
注解默认按照名称进行装配,如果找不到匹配的名称,则会按照类型进行装配。
示例代码:
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource
private UserRepository userRepository;
// 业务方法
}
# 6.2、@Resource
与@Autowired
的比较
@Resource
和@Autowired
都可以实现依赖注入,但它们有一些区别。
@Autowired
是 Spring 框架特有的注解,默认按照类型进行装配,而@Resource
是 JSR-250 规范中的注解,默认按照名称进行装配。
在实际使用中,可以根据具体的需求选择合适的注解。
# 7、使用@Value
# 7.1、@Value
的基本用法
@Value
注解用于将配置文件中的值注入到 Bean 的属性中。它可以应用在字段、Setter 方法和构造函数参数上。例如,可以使用@Value("${database.url}")
将配置文件中database.url
的值注入到相应的属性中。
示例代码:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class DatabaseService {
@Value("${database.url}")
private String databaseUrl;
// 业务方法
}
# 7.2、@Value
的高级用法
@Value
注解还支持一些高级用法,例如可以使用 SpEL 表达式来动态计算属性值。例如,@Value("#{systemProperties['os.name']}")
可以获取系统属性os.name
的值,并将其注入到属性中。
示例代码:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class SystemInfoService {
@Value("#{systemProperties['os.name']}")
private String osName;
// 业务方法
}
# 8、使用@PostConstruct
和@PreDestroy
# 8.1、@PostConstruct
的用法
@PostConstruct
注解用于标记一个方法,该方法会在 Bean 被创建并初始化完成后自动调用。通常用于在 Bean 初始化后执行一些额外的操作,例如加载数据、建立连接等。
示例代码:
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Service;
@Service
public class DataService {
@PostConstruct
public void init() {
// 初始化操作,如加载数据
}
// 业务方法
}
# 8.2、@PreDestroy
的用法
@PreDestroy
注解用于标记一个方法,该方法会在 Bean 被销毁之前自动调用。通常用于在 Bean 销毁前执行一些清理操作,例如关闭连接、释放资源等。
示例代码:
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Service;
@Service
public class DataService {
@PreDestroy
public void destroy() {
// 销毁前操作,如关闭连接
}
// 业务方法
}
# 三、定制 Bean 的性质
定制 Bean 的性质是指可以通过各种方式来改变 Bean 的行为和属性。
例如,可以通过实现InitializingBean
接口和DisposableBean
接口来定义 Bean 的初始化方法和销毁方法;可以通过使用BeanPostProcessor
接口来对 Bean 进行后置处理,在 Bean 初始化前后执行自定义的逻辑;还可以通过使用FactoryBean
接口来自定义 Bean 的创建逻辑。
# 1、实现 InitializingBean 和 DisposableBean 接口
InitializingBean
接口只有一个afterPropertiesSet
方法,在 Bean 的属性设置完成后,Spring 容器会调用该方法进行初始化操作;DisposableBean
接口只有一个destroy
方法,在 Bean 被销毁时,Spring 容器会调用该方法进行清理操作。
示例代码:
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class CustomBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("CustomBean初始化操作");
}
@Override
public void destroy() throws Exception {
System.out.println("CustomBean销毁操作");
}
}
# 2、使用 BeanPostProcessor 接口
BeanPostProcessor
接口允许在 Bean 初始化前后执行自定义逻辑。通过实现该接口的postProcessBeforeInitialization
和postProcessAfterInitialization
方法,可以对 Bean 进行修改或增强。
示例代码:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在" + beanName + "初始化之前执行");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在" + beanName + "初始化之后执行");
return bean;
}
}
# 3、使用 FactoryBean 接口
FactoryBean
接口用于自定义 Bean 的创建逻辑。通过实现该接口的getObject
、getObjectType
和isSingleton
方法,可以创建出特殊的 Bean 实例。
示例代码:
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class CustomFactoryBean implements FactoryBean<CustomBean> {
@Override
public CustomBean getObject() throws Exception {
return new CustomBean();
}
@Override
public Class<?> getObjectType() {
return CustomBean.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
# 四、使用 JSR 330 标准注解
JSR 330(Java Specification Requests 330)是 Java 依赖注入的标准规范,Spring 对其提供了支持。主要注解包括@Inject
、@Named
和@Provider
等。
# 1、@Inject 注解
@Inject
注解类似于 Spring 的@Autowired
,用于实现依赖注入。它按照类型进行自动装配。
示例代码:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class UserService {
private UserRepository userRepository;
@Inject
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 业务方法
}
# 2、@Named 注解
@Named
注解用于给 Bean 命名,相当于 Spring 的@Component
注解。可以在使用@Inject
注解时,通过名称来指定要注入的 Bean。
示例代码:
import javax.inject.Inject;
import javax.inject.Named;
@Named("userRepository")
public class UserRepositoryImpl implements UserRepository {
// 实现接口方法
}
@Named
public class UserService {
private UserRepository userRepository;
@Inject
@Named("userRepository")
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 业务方法
}
# 3、@Provider 注解
@Provider
注解用于提供一个创建对象的方法,当需要延迟创建对象或根据条件创建不同对象时非常有用。
示例代码:
import javax.inject.Provider;
import javax.inject.Singleton;
@Singleton
public class UserServiceProvider implements Provider<UserService> {
@Override
public UserService get() {
return new UserService(new UserRepositoryImpl());
}
}
# 五、基于 Java 的容器配置
# 1、基本概念
# 1.1、@Bean
和@Configuration
@Bean
注解用于在 Java 配置类中定义一个 Bean,相当于 XML 配置中的<bean>
标签;@Configuration
注解用于标记一个 Java 类为配置类,该类中的@Bean
方法会被 Spring 容器扫描并注册 Bean。
# 1.2、实例化 Spring 容器
通过AnnotationConfigApplicationContext
类可以实例化基于 Java 配置的 Spring 容器。
示例代码:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
// 使用userService
context.close();
}
}
# 2、使用@Bean
注解
# 2.1、@Bean
的基本用法
在配置类中,使用@Bean
注解标记的方法返回的对象会被注册为 Spring 容器中的 Bean。
示例代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
@Bean
public UserService userService() {
return new UserService(userRepository());
}
}
# 2.2、@Bean
的高级特性
@Bean
注解支持一些高级特性,例如可以指定 Bean 的作用域(@Scope
)、初始化方法和销毁方法等。
示例代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
@Bean(initMethod = "init", destroyMethod = "destroy")
public UserService userService() {
return new UserService(userRepository());
}
}
# 3、使用@Configuration
注解
# 3.1、@Configuration
的基本概念
@Configuration
注解标记的类是一个配置类,其中的@Bean
方法会被 Spring 容器解析并注册 Bean。配置类本身也会被注册为一个 Bean,默认的 Bean 名称是配置类的类名首字母小写。
# 3.2、@Configuration
的应用示例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
// 创建并返回数据源
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl(dataSource());
}
}
# 4、组合基于 Java 的配置类
# 4.1、配置类的组合方式
可以通过@Import
注解将多个配置类组合在一起,也可以在一个配置类中通过调用其他配置类的@Bean
方法来复用配置。
组合配置类的示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(DataSourceConfig.class)
public class AppConfig {
@Bean
public UserRepository userRepository(DataSource dataSource) {
return new UserRepositoryImpl(dataSource);
}
}
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
// 创建并返回数据源
}
}
# 4.2、以 @Configuration
类为中心结合使用 @ImportResource
和 XML
在以 @Configuration
类作为配置容器主要方式的应用程序中,有时仍需要使用一些 XML 配置。在这种场景下,你可以使用 @ImportResource
注解,仅定义所需的少量 XML 配置。
这样做可以实现以 Java 为中心的容器配置方式,并将 XML 配置量降至最低。
以下示例(包含一个配置类、一个定义 Bean 的 XML 文件、一个属性文件和 main()
方法)展示了如何使用 @ImportResource
注解实现按需使用 XML 的以 Java 为中心的配置方式:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
properties-config.xml
文件:
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
文件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
Java 主程序代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
}
上述代码中,AppConfig
类使用 @ImportResource
注解引入了 properties - config.xml
文件,该 XML 文件用于加载 jdbc.properties
中的属性配置。
在 AppConfig
类中,通过 @Value
注解注入属性值,并定义了数据源、账户仓库和转账服务等 Bean。最后,在 main
方法中创建 Spring 应用上下文并获取 TransferService
Bean。
# 5、高级配置技巧
# 5.1、条件化注册(@Conditional)
@Configuration
public class DatabaseConfig {
@Bean
@Conditional(ProdEnvCondition.class)
public DataSource prodDataSource() {
return new HikariDataSource(prodConfig());
}
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheManager redisCache() {
return new RedisCacheManager(redisTemplate());
}
}
class ProdEnvCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "prod".equals(context.getEnvironment().getProperty("spring.profiles.active"));
}
}
核心要点:
- 通过实现
Condition
接口创建自定义条件判断逻辑 - 条件注解可与属性值(
@ConditionalOnProperty
)、类存在性(@ConditionalOnClass
)等组合使用 - 条件检查发生在Bean定义注册阶段
# 5.2、Profile 环境隔离
# a、什么是 Profile
在软件开发中,我们通常会有不同的运行环境,如开发环境(Development)、测试环境(Testing)、生产环境(Production)等。每个环境可能有不同的配置,例如数据库连接信息、日志级别、缓存配置等。Spring 的 Profile
机制提供了一种方式来隔离这些不同环境的配置,使得我们可以根据不同的环境加载不同的 Bean 定义和配置。
Profile
本质上是一种逻辑分组,它允许我们将一组 Bean 定义和配置归为一个特定的环境。在应用启动时,我们可以指定要激活的 Profile
,Spring 容器会只加载与该 Profile
相关的 Bean 定义。
# b、如何使用 Profile
# 使用 @Profile
注解
在 Java 配置类中,我们可以使用 @Profile
注解来指定某个 Bean 或配置类只在特定的 Profile
下生效。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
// 开发环境的数据源配置
return new DriverManagerDataSource("jdbc:mysql://localhost:3306/devdb", "devuser", "devpassword");
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 生产环境的数据源配置
return new DriverManagerDataSource("jdbc:mysql://prodserver:3306/proddb", "produser", "prodpassword");
}
}
在上述代码中,devDataSource
方法定义的数据源 Bean 只在 dev
这个 Profile
激活时才会被创建,而 prodDataSource
方法定义的数据源 Bean 只在 prod
这个 Profile
激活时才会被创建。
# 在 XML 配置中使用 Profile
除了 Java 配置,我们也可以在 XML 配置文件中使用 Profile
。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans profile="dev">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/devdb"/>
<property name="username" value="devuser"/>
<property name="password" value="devpassword"/>
</bean>
</beans>
<beans profile="prod">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://prodserver:3306/proddb"/>
<property name="username" value="produser"/>
<property name="password" value="prodpassword"/>
</bean>
</beans>
</beans>
在这个 XML 配置中,不同 Profile
下定义了不同的数据源 Bean。
# c、激活 Profile
# 通过 JVM 参数激活
在启动应用程序时,我们可以通过 -Dspring.profiles.active
参数来指定要激活的 Profile
。
java -Dspring.profiles.active=dev -jar myapp.jar
上述命令指定了激活 dev
这个 Profile
,Spring 容器会加载与 dev
Profile
相关的 Bean 定义。
# 通过代码激活
在 Java 代码中,我们也可以通过编程的方式激活 Profile
。
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("dev");
context.register(AppConfig.class);
context.refresh();
// 使用上下文获取 Bean 等操作
}
}
# d、多 Profile 组合
有时我们可能需要同时激活多个 Profile
。可以通过逗号分隔的方式指定多个 Profile
。
java -Dspring.profiles.active=dev,logging -jar myapp.jar
在上述命令中,同时激活了 dev
和 logging
两个 Profile
,Spring 容器会加载与这两个 Profile
相关的所有 Bean 定义。
# e、默认 Profile
如果没有显式指定要激活的 Profile
,Spring 会使用默认的 Profile
。我们可以通过 spring.profiles.default
属性来设置默认的 Profile
。
java -Dspring.profiles.default=dev -jar myapp.jar
或者在 Java 代码中设置:
context.getEnvironment().setDefaultProfiles("dev");
# f、属性文件的Profile隔离
除了Bean配置的方式,Spring也支持属性文件的 Profile 切换,它允许你根据不同的运行环境(如开发、测试、生产)加载不同的属性配置。
Spring 支持为不同的 Profile 定义不同的属性文件。属性文件的命名遵循特定的规则,一般是 application-{profile}.properties
或 application-{profile}.yaml
格式,其中 {profile}
是 Profile 的名称。
当激活某个 Profile 时,Spring 会优先加载对应的属性文件。
假设项目的资源目录(src/main/resources
)下有以下属性文件:
src/main/resources/
├── application.properties
├── application-dev.properties
├── application-test.properties
└── application-prod.properties
application.properties
:这是通用的属性文件,包含所有环境都适用的配置。
# 通用配置
app.name=MyApp
application-dev.properties
:开发环境的特定配置。
# 开发环境数据库配置
db.url=jdbc:mysql://localhost:3306/dev_db
db.username=dev_user
db.password=dev_password
application-test.properties
:测试环境的特定配置。
# 测试环境数据库配置
db.url=jdbc:mysql://testserver:3306/test_db
db.username=test_user
db.password=test_password
application-prod.properties
:生产环境的特定配置。
# 生产环境数据库配置
db.url=jdbc:mysql://prodserver:3306/prod_db
db.username=prod_user
db.password=prod_password
在启动应用程序时,可以通过 -Dspring.profiles.active
参数指定要激活的 Profile。例如,要激活开发环境的 Profile,可以使用以下命令:
java -Dspring.profiles.active=dev -jar myapp.jar
此时,Spring 会先加载 application.properties
文件中的通用配置,然后再加载 application-dev.properties
文件中的配置,并覆盖相同属性名的配置。
也可以在 application.properties
文件中指定默认的 Profile:
spring.profiles.active=dev
这样,在没有通过其他方式指定 Profile 时,应用程序会默认激活 dev
这个 Profile。
在 Java 代码中,也可以通过编程的方式激活 Profile。以下是一个使用 Spring Boot 的示例:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApp.class);
ConfigurableApplicationContext context = app.run(args);
ConfigurableEnvironment env = context.getEnvironment();
// 激活测试环境的 Profile
env.setActiveProfiles("test");
// 重新启动应用以加载新的 Profile 配置
context.close();
app.run(args);
}
}
当存在多个属性文件和不同的配置方式时,Spring 的属性加载顺序如下:
application.properties
(或application.yml
)中的通用配置。- 激活的 Profile 对应的属性文件(如
application-dev.properties
)中的配置,相同属性名会覆盖通用配置。 - 通过 JVM 参数、系统环境变量等方式指定的属性,会覆盖前面加载的配置。
# 六、总结
通过以上全面的介绍,希望能帮助你深入理解 Spring 容器的各种配置方式及其应用。
在实际项目中,根据具体需求选择合适的配置方式,能够提高开发效率和系统的可维护性。
*祝你变得更强!