Spring 笔记(四):基于注解的 IoC 配置

前言

注解配置和 xml 配置 bean 对象的特点和实现的功能都是一样的,都是降低程序间的耦合,只是配置的形式不一样。

基于注解整合时,导入约束时需要多导入一个 context 名称空间下的约束。

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为context名称空间和约束中-->
<context:component-scan base-package="com.ourzh"></context:component-scan>
</beans>

常用注解

用于创建对象的

相当于:<bean id="" class=""></bean>

  • @Component

    • 作用:用于把当前类对象存入 spring 容器中
    • 属性:value,用于指定 bean 的 id。当我们不写时,它的默认值是当前类名,且首字母小写。如果注解中有且只有一个属性要赋值时,且名称是 value, value 在赋值时可以不写。
  • @Controller,@Service,@Repository

    这三个注解都是 @Component 的衍生注解,作用和属性均与 @Component 一模一样。它们是 spring 框架为了提供了更加明确的语义化,使三层对象更加清晰。

    • @Controller:一般用在表现层的注解;
    • @Service:一般用在业务层的注解;
    • Repository:一般用在持久层的注解;

用于注入数据的

相当于:<property name="" value=""></property><property name="" ref=""></property>

  • @Autowired

    • 作用:自动按照类型注入。只要 IoC 容器中有唯一的一个 bean 对象类型和要注入的变量类型匹配,就可以注入成功;如果容器中没有任何 bean 的类型和要注入的变量类型匹配,则报错;当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在容器中查找,找到了也可以注入成功,找不到就报错。

    • 细节:当使用注解注入属性时, set 方法可以省略。

  • @Qualifier

    • 作用:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowired 一起使用;但是给方法参数注入时,可以独立使用。
    • 属性:value,用于指定 bean 的 id。
  • @Resource

    • 作用:直接按照 bean的 id注入,可以独立使用。
    • 属性:name,用于指定 bean 的 id。

以上三个注解都只能注入其他bean类型的数据。

  • @Value
    • 作用:用于注入基本数据类型和 String 类型的数据。
    • 属性:value,用于指定数据的值。它可以使用 spring 中 SpEL(也就是spring的el表达式),SpEL 的写法:${表达式}。

集合类型的注入只能通过XML来实现。

用于改变作用范围的

相当于:bean 标签中的 scope 属性

  • @Scope
    • 作用:指定bean的作用范围
    • 属性:value,指定范围的值。取值:singletonprototype,request,session,globalsession

和生命周期相关(了解)

相当于:bean 标签中的 init-method 和destroy-methode 属性

  • @PostConstruct:用于指定初始化方法

  • @PreDestroy:用于指定销毁方法

注解 or XML

注解:

  • 优势:配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
  • 适用场景:Bean 的实现类由用户自己开发。

XML:

  • 优势:修改时,不用改源码。不涉及重新编译和部署。
  • 适用场景:Bean 来自第三方。

纯注解配置

待改造问题

我们发现,之所以离不开 xml 配置文件,是因为有一句很关键的配置:

1
2
<!-- 告知spring框架在创建容器时要扫描的包 -->
<context:component-scan base-package="com.ourzh"></context:component-scan>

另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>

新注解说明

@Configuration

  • 作用:用于指定当前类是一个 spring 配置类, 当创建容器时会从该类上加载注解。 获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)
  • 属性:value,用于指定配置类的字节码,通常不写.
1
2
3
@Configuration
public class SpringConfiguration {
}

我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢?

@ComponentScan

相当于:<context:component-scan base-package="com.ourzh"/>

  • 作用:用于指定 spring 在初始化容器时要扫描的包。
  • 属性:basePackages/value,用于指定要扫描的包。
1
2
3
4
@Configuration
@ComponentScan("com.ourzh")
public class SpringConfiguration {
}

我们已经配置好了要扫描的包,但是数据源和 JdbcTemplate 对象如何从配置文件中移除呢?

@Bean

  • 作用:该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
  • 属性:name,给当前 @Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
  • 细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。查找的方式和 Autowired 注解是一样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 连接数据库的配置类
*/
@Configuration
public class JdbcConfig {
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource){
return new QueryRunner(dataSource);
}

/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUser("root");
ds.setPassword("1234");
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}

我们已经把数据源和 JdbcTemplate 的配置从配置文件中移除了,此时可以删除 bean.xml 了。但是由于没有了配置文件,创建数据源的配置又都写死在类中了。如何把它们配置出来呢?

@PropertySource

  • 作用:用于加载 .properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 .properties 配置文件中,就可以使用此注解指定 .properties 配置文件的位置。
  • 属性:value[],用于指定 .properties 文件位置。如果是在类路径下,需要写上 classpath:

jdbc.properties 文件:

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 连接数据库的配置类
*/
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建一个数据源对象,并存入 spring 容器中
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢?

@Import

  • 作用:用于导入其他配置类。当我们使用 Import 的注解之后,有 Import 注解的类就父配置类,而导入的都是子配置类。
  • 属性:value[],用于指定其他配置类的字节码。
1
2
3
4
5
6
7
8
9
10
11
@Configuration
@ComponentScan("com.ourzh")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}

//@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
...
}

我们已经把要配置的都配置好了,但是新的问题产生了,由于没有配置文件了,如何获取容器呢?

通过注解获取容器

1
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

Spring 整合 Junit

问题

在测试类中,每个测试方法都有以下两行代码:

1
2
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService as = ac.getBean("accountService", AccountService.class);

这样就造成代码重复。这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常,所以又不能轻易删掉。

解决思路分析

  1. 应用程序的入口:main 方法
  2. junit 单元测试中,没有 main 方法也能执行
    • junit集成了一个main方法
    • 该方法就会判断当前测试类中哪些方法有 @Test 注解
    • junit 就让有 Test 注解的方法执行
  3. junit 不会管我们是否采用 spring 框架
    • 在执行测试方法时,junit 根本不知道我们是不是使用了 spring 框架
    • 所以也就不会为我们读取配置文件/配置类创建 spring 核心容器
  4. 由以上三点可知,当测试方法执行时,没有 Ioc 容器,就算写了 Autowired 注解,也无法实现注入

针对上述问题,我们需要的是程序能自动帮我们创建容器。但显然,junit 自身是无法实现的,因为它无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。

配置步骤

  1. 导入 Spring 整合 Junit 的 jar(坐标)

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
    </dependency>
  2. 使用 @RunWith 注解替换原有运行器

  3. 使用 @ContextConfiguration 指定 spring 配置文件的位置

    @ContextConfiguration 注解属性:

    • locations: 用于指定 xml 配置文件的位置。如果是类路径下,需要用 classpath: 表明
    • classes: 用于指定注解的类。
  4. 使用 @Autowired 给测试类中的变量注入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 测试类
    */
    @RunWith(SpringJUnit4ClassRunner.class)
    //@ContextConfiguration(locations={"classpath:bean.xml"})
    @ContextConfiguration(classes=SpringConfiguration.class)
    public class AccountServiceTest {
    @Autowired
    private AccountService as ;
    }

为什么不把测试类配到 xml 中?

当我们在 xml 中配置了一个 bean, spring 加载配置文件创建容器时,就会创建对象。但测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。所以,我们不应该把测试类配置到 xml 文件中。

0%