Java Spring开发记(一):IoC容器和Bean的装配

Spring Framework

一种开发Java应用的框架

IoC容器和Bean的装配

IoC (Inversion of Control)

当一个系统拥有大量的组件时,如果仅仅通过new来创建实例,则需要在不同的类中分别对所需的实例进行创建、实例化和维护。这导致当众多不同的组件具有相同的依赖时,需要创建大量相同的实例,使得出现实例化变得复杂、资源空间大量浪费、不易进行测试和维护和实例不易销毁等问题。

解决这类问题,IoC便是最佳方案。它负责实例化所有需要复用的组件,管理组件的生命周期,将组件的创建和配置与其使用相分离。

创建和装配Bean

引入依赖:

1
org.springframework:spring-context:6.0.0

1. 用XML装配Bean

假如现在有两个类AB,类B需要调用类A以实现所需功能。首先,类B中需要实现一个setClassA()函数以将类A进行注入。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ClassA.java
public class ClassA {
private String someVariable;
public String someFunction() {
return "Something to do...";
}
}

// ClassB.java
public class ClassB {
private ClassA classA;
public void setClassA(ClassA classA) {
this.classA=classA;
}
public void someFunc() {
classA.someFunction();
}
}

之后,需要编写一个配置文件application.xml,以阐明Bean的组装方式:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="classA" class="com.example.ClassA" />

<bean id="classB" class="com.example.ClassB">
<property name="classA" ref="classA" />
</bean>
</beans>

前面的xml格式是固定格式。之后对于每一个需要装配的类,对其分别进行bean的添加。注意这个id是唯一的,用于其他bean对其的引用。对于要引用的类,使用property标签进行注入。注入的内容如果是Bean,则使用ref进行引用;如果只是一些基础类型的数据,可以使用value。下面是对于一个HikariCP连接池创建的例子:

1
2
3
4
5
6
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>

接下来,在需要获得引用的AppConfig类中,通过ApplicationContext创建Spring容器:

1
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

再获取对Bean的引用:

1
2
ClassB classB = context.getBean(ClassB.class);// 写法1
ClassB classB = (ClassB)context.getBean("classB");// 写法2

上述两种写法均可,但通过类型获取引用的方法1更为常用。

AppConfig类示例如下:

1
2
3
4
5
6
7
8
//AppConfig.java
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
ClassB classB = context.getBean(ClassB.class);
classB.someFunc();
}
}

2. 用Annotation配置装配Bean

通过注解的标记,可以实现自动扫描配置并进行初始化。

@Component注解就相当于定义了一个Bean,并对其默认生成了一个类名首字母小写的id:

1
2
3
4
5
6
7
8
// ClassA.java
@Component
public class ClassA { // 生成一个id为classA的Bean
private String someVariable;
public String someFunction() {
return "Something to do...";
}
}

根据不同类的不同层次,还可将@Component细分为@Repository@Service@Controller三类(它们的作用均与@Component相同),使得工程的层次更清晰明了。

通过@Autowired注解将内部引用的类自动装配成property

1
2
3
4
5
6
7
8
9
10
11
12
// ClassB.java
@Component
public class ClassB {
@Autowired
private ClassA classA;
public void setClassA(ClassA classA) {
this.classA=classA;
}
public void someFunc() {
classA.someFunction();
}
}

注解也可以写在构造函数中,不过通常标于字段之前。

如果我们要注入一个特定的内容,可以使用@Value注解。例如:

1
2
@Value("1")
private int version;

接下来,对于application.xml文件,我们就可以在beans标签内这么写,以实现自动扫描注解和装配Bean:

1
<context:component-scan base-package="com.example"/>

或者,如果我们连application.xml也不想创建,这时也可以对AppConfig类增加@ComponentScan注解,并在创建Spring容器时使用AnnotationConfigApplicationContext函数,示例如下:

1
2
3
4
5
6
7
8
9
// AppConfig.java
@ComponentScan
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ClassB classB = context.getBean(ClassB.class);
classB.someFunc();
}
}

注入List

假设我们现在有一个接口,但是在这个接口上进行了一系列不同类的实现。现在需要把这些类都注入Beans,有什么简便的办法呢?

对每一个类都添加一个@Component注解,再在Config中进行如下引用(我们假设Class是一个接口,分别实现了ClassAClassBClassC三个类):

1
2
@Autowired
List<Class> classes;

Spring会自动把这三个类装配到这个List中。由于List是有序的,若需指定顺序,可加上@Order注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface Class {
void somemFunc();
}

@Component
@Order(1)
public class ClassA implements Class {
@Override
public void someFunc() { ... }
}

@Component
@Order(2)
public class ClassB implements Class {
@Override
public void someFunc() { ... }
}

@Component
@Order(3)
public class ClassC implements Class {
@Override
public void someFunc() { ... }
}

创建第三方Bean

如果想要为一个第三方库中存在的类创建Bean,我们不能直接修改对应的库函数,则可以通过编写一个函数方法来实现它,并在实现的函数前添加@Bean注解。

此时,AppConfig类中包含了@Bean注解,则需标记该类是Spring配置类,要在类前再行添加一个@Configuration注解。

例如,注入一个HikariDataSource

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@ComponentScan
public class AppConfig {
@Bean
public DataSource getDataSource() {
HikariConfig cfg=new HikariConfig();
cfg.setJdbcUrl("jdbc:mysql://localhost/db");
cfg.setUsername("root");
cfg.setPassword("root");
cfg.setDriverClassName("com.mysql.cj.jdbc.Driver");
return new HikariDataSource(cfg);
}
}

指定别名

如果存在多个返回为同类型的Bean,Spring不知道对哪个Bean创建实例(一般情况下相同类型的Bean只能创建一个实例)。这时,我们需要对每一个Bean取别名。

取别名的方法有两种。

一种是在后面直接跟别名:

1
@Bean("name")

另一种是再添加一个@Qualifier注解:

1
2
@Bean
@Qualifier("name")

条件装配

Spring提供了一个方式,可以让我们在指定条件或环境下才对某个Bean进行创建。

  1. 使用Profile

    Profile用来表示不同的环境,可以在启动应用程序时通过所传参数(-Dspring.profiles.active=...)进行更改。例如,指定以test环境启动,如果希望在该环境下装配某个Bean,可以这么写:

    1
    @Profile("test")

    如果希望不要在该环境下装配某个Bean,可以这么写:

    1
    @Profile("!test")

    如果希望同时满足nativetest两个条件,则可以这么写:

    1
    @Profile({ "native","test" })
  2. 使用Conditional

    @Conditional注解用于满足某个逻辑条件才进行装配。由于需要额外实现一个判断类较为繁琐,因此实际应用较少。

    Spring Boot提供了更多的条件装配注解,例如@ConditionalOnClass@ConditionalOnProperty等。

可选注入

对于部分引用,有时可能没有符合条件的Bean可供注入,这时Spring会抛出Exception。如果对于一个引用,它的注入不是必须的,这时可以添加一个required=false参数作为标记。

1
@Autowired(required=false)

注入外部资源

有时我们需要引入固定的外部资源,然而使用InputStream查找并读取文件又显得过于繁琐。这时,Spring为我们提供了一个简洁的方式,通过Resource类将文件“注入”进来。

我们使用@Value进行注入。例如,我们需要读取Classpath下的app.properties文件,可以这样写:

1
2
@Value("classpath:/app.properties")
private Resource resource;

此时,Spring会在Classpath下自动搜索该文件。随后,通过调用Resource.getInputStream()函数即可获得输入流,避免了繁琐的手动查找过程。

也可以指定文件的绝对位置并进行读取(并不常用):

1
2
@Value("file:/path/to/app.properties")
private Resource resource;

注入配置

对于一个配置文件,我们可能需要读取它并将其中的内容赋值给对应Beans内的变量。如果通过注入Resource再打开输入流读取的方式仍然显得过于繁琐,于是@PropertySource注解应运而生。

AppConfig类前,添加如下注解以读取Classpath中app.properties文件的配置:

1
@PropertySource("app.properties")

注意此时也需要向Spring声明此类为配置类,因此还需要加上@Configuration注解。

对于AppConfig类或其他Beans,若需要使用当前配置文件的内容初始化变量,仍然使用@Value注解注入。例如,我需要注入app.name变量可以使用如下写法:

1
2
@Value("${app.name}")
private String appName;

配置文件app.properties可以这么写:

1
app.name=example

如果配置文件中app.name键值不存在,此时Spring将抛出异常。为此我们可以指定一个默认值,假设app.name的默认值为example

1
2
@Value("${app.name:example}")
private String appName;

我们也可以注入其他Bean的属性值,例如现在有一个Config类,其中有一个title变量。我们将Config类实例化为Bean,在其他类中我们要将这个值设定到一个其他的变量里,可以使用如下方式(注意实例化Bean后的id中首字母变成了小写):

1
2
@Value("#{config.title}")
private String appName;

Java Spring开发记(一):IoC容器和Bean的装配
本文链接:http://blog.ac1liu.com/p/c31a4e05.html
发布时间
2026年1月22日
许可协议
转载说明
请注明出处!
发表评论