在之前的一篇文章《基于XML配置的方式使用Spring的IoC》中介绍了如何通过XML配置文件的方式来使用Spring的IoC,本文将在之前的文章中提到的案例的基础上通过注解方式使用Spring的IoC。
一、基本配置
1、导入依赖
要想使用Spring的注解功能,还需要导入spring-aop
的依赖,不过该依赖已经包含在spring-context
依赖中了,因此项目的pom.xml
文件的内容与基于XML配置文件方式是相同的:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
</dependencies>
这里我使用的版本是5.2.2.RELEASE
,请根据需要自行导入相应的版本。更多版本的信息请访问:spring-framework-releases或repo.spring.io 进行查询。
2、使用@Component注解配置管理的资源
(1)在数据访问层实现类AccountDaoImpl
上加上@Component
注解:
package cn.frankfang.dao.impl;
import cn.frankfang.dao.IAccountDao;
import org.springframework.stereotype.Component;
/**
* 数据访问层实现类
*/
@Component("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存账户...");
}
}
(2)在服务层实现类AccountServiceImpl
上加上@Component
注解:
package cn.frankfang.service.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.service.IAccountService;
import org.springframework.stereotype.Component;
/**
* 服务层实现类
*/
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
// 数据访问层接口
private IAccountDao accountDao;
/**
* 通过带参数的构造方法让Spring进行依赖注入
* @param accountDao 需要注入的bean
*/
public AccountServiceImpl(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
注:@Component
注解相当于<bean id="" class=""></bean>
3、配置XML文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- Spring创建容器时要扫描的包 -->
<context:component-scan base-package="cn.frankfang" />
</beans>
注意,基于注解的配置在基于XML配置文件的基础上还要加上一个context
命名空间。
在配置完之后运行程序,控制台已经打印出结果。下面将详细介绍基于注解配置的方法。
二、常用注解
Spring框架中与IoC容器相关的注解主要有四种,分别是:用于创建对象的、用于注入数据的、用于改变作用范围的、与生命周期相关的。下面将分别介绍这四种常用的注解。
1、创建对象
(1)@Component
作用:把资源交给Spring来管理,相当于XML配置中的<bean>
标签。
属性列表:
属性 | 作用 |
---|---|
value |
指定bean 的id 。如果不指定该属性,默认bean 的id 是当前的类名,且首字母小写 |
(2)@Controller、@Service 和 @Repository
作用:都是@Component
注解的衍生注解,它们的作用及属性都是相同的。下面给出这三个注解一般添加的位置:
注解 | 添加位置 |
---|---|
@Controller |
一般用于表现层 |
@Service |
一般用于业务层 |
@Repository |
一般用于持久层 |
由此可以将案例中数据访问层实现类和服务层实现类改写为如下形式:
AccountDaoImpl
类
package cn.frankfang.dao.impl;
import cn.frankfang.dao.IAccountDao;
import org.springframework.stereotype.Repository;
/**
* 数据访问层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存账户...");
}
}
AccountServiceImpl
类
package cn.frankfang.service.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.service.IAccountService;
import org.springframework.stereotype.Service;
/**
* 服务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
// 数据访问层接口
private IAccountDao accountDao;
/**
* 通过带参数的构造方法让Spring进行依赖注入
* @param accountDao 需要注入的bean
*/
public AccountServiceImpl(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
2、注入数据
(1)@Autowired
作用:自动按照类型进行注入。当使用注解注入属性时setter
方法可以省略。该注解只能注入其它bean
类型。当有多个类型匹配时,使用要注入的对象名称作为bean
的id
在Spring容器中查找,如果找到bean
便可进行注入,如果找不到就报错。
属性列表:
属性 | 作用 |
---|---|
required |
声明被注解的依赖是否是需要的,默认为true |
注:若需获取更多关于@Autowired
的内容,请参阅:Using @Autowired。
由此我们就可以将案例中服务层实现类改写为以下形式:
package cn.frankfang.service.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 服务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
// 数据访问层接口
@Autowired
private IAccountDao accountDao;
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
(2)@Qualifier
作用:在自动按照类型注入的基础上再按照bean
的id
注入。它在给成员变量注入时无法独立使用,必须和@Autowired
注解一起使用,但是给方法参数注入时可以独立使用。
属性列表:
属性 | 作用 |
---|---|
value |
指定bean 的id |
注:若需获取更多关于@Qualifier
的内容,请参阅:Using @Qualifier。
(3)@Resource
作用:直接根据bean
的id
注入,该注解只能注入其它bean
类型。
属性列表:
属性 | 作用 |
---|---|
name |
指定bean 的id |
若需获取更多关于@Resource
的内容,请参阅:Injection with @Resource。
(4)@Value
作用:注入基本数据类型和String类型的数据,通常用来获取.properties
和.yml
配置文件中的数据。
属性列表:
属性 | 作用 |
---|---|
value |
指定要注入的值,支持SpEL表达式 |
假设我们需要注入配置文件中的数据,可以采用如下写法:
@Configuration // 用于指定当前类为一个Spring配置类
@PropertySource("classpath:application.properties") // 用于指定配置文件路径
public class AppConfig {
@Value("${database.username}")
private String username;
@Value("${database.password}")
private String password;
}
application.properties
的内容如下:
database.username=root
database.password=123456
若需获取更多关于@Value
的内容,请参阅:Using @Value。
3、改变作用域
可使用@Scope
注解来改变bean
的作用域,@Scope
中的value
属性可以指定作用域范围,下表将对value
可取的值进行说明:
取值 | 作用 |
---|---|
singleton |
将单个bean 定义限定为单个实例(默认) |
prototype |
将单个bean 定义的范围限定为任意数量的对象实例(多例) |
request |
将单个bean 定义限定为单个HTTP请求的生命周期(仅适用于Web应用) |
session |
将单个bean 定义限定到HTTP会话的生命周期(仅适用于Web应用) |
application |
将单个bean 定义限定为ServletContext 的生命周期(仅适用于Web应用) |
websocket |
将单个bean 定义限定到WebSocket 的生命周期(仅适用于Web应用) |
若需获取更多关于@Scope
的内容,请参阅:Providing a Scope for Autodetected Components。
4、与生命周期相关
该部分的配置相当于<bean id="" class="" init-method="" destroy-method="" />
。下面将介绍替代init-method
属性和destroy-method
属性的注解。
(1)@PostConstruct
作用在方法上,代替init-method
,用于指定初始化方法。
(2)@PreDestroy
作用在方法上,代替destroy-method
,用于指定销毁方法。
若需获取更多关于@PostConstruct
和@PreDestroy
的内容,请参阅:Using @PostConstruct and @PreDestroy。
三、纯注解配置
在上面的内容中,我们介绍了基于注解的配置,但仍然需要编写XML配置文件,那么能不能不写XML配置文件,完全通过注解来使用Spring IoC容器呢?答案是肯定的,下面我们将介绍纯注解配置。
1、@Configuration
在项目中新建一个SpringConfiguration
类,并在该类上添加@Configuration
注解,该类就相当于spring-config.xml
配置文件。
package cn.frankfang.config;
import org.springframework.context.annotation.Configuration;
/**
* Spring的配置文件, 相当于spring-config.xml
*/
@Configuration
public class SpringConfiguration {
}
@Configuration
用于指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext
,并传入配置类的字节码对象。
该注解有一个value
属性,用于指定配置类的字节码。
2、@ComponentScan
前面我们已经把XML配置文件用SpringConfiguration
类来代替了,但是如何配置创建容器时要扫描的包呢?答案是使用@ComponentScan
注解。
package cn.frankfang.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* Spring的配置文件, 相当于spring-config.xml
*/
@Configuration
@ComponentScan("cn.frankfang")
public class SpringConfiguration {
}
@ComponentScan
注解的作用是指定Spring在初始化容器时要扫描的包,与XML配置文件中的<context:component-scan base-package=""/>
是相同的。
该注解中包含了basePackages
和value
属性,它们的作用是相同的,都是用于指定要扫描的包。
3、@Bean
@Bean
注解只能写在方法上,表明使用此方法创建一个对象,并且放入Spring容器中。该注解包含name
和value
属性,它们的作用是相同的,都是给当前被注解的方法指定一个id
。
假设现在该项目需要配置数据源,此时可以使用@Bean
将创建的数据源存入Spring容器中,代码如下:
package cn.frankfang.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* Spring的配置文件, 相当于spring-config.xml
*/
@Configuration
@ComponentScan("cn.frankfang")
public class SpringConfiguration {
/**
* 创建一个数据源, 并存入Spring容器中
* @return
*/
@Bean("datasource")
public DataSource dataSource () {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test?useAffectedRows=true");
dataSource.setUser("root");
dataSource.setPassword("123456");
return dataSource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
注:若需获取更多关于@Bean
的相关内容,请参阅:Using @Bean。
4、@PropertySource
@PropertySource
注解用于加载.properties
文件中的配置。该注解包含了value
属性,用于指定properties
文件的位置,可传入多个位置,如果在类路径下需要加上classpath:
。
针对前面配置数据源的代码,我们可以将数据库账户名和密码等信息写在properties
文件中,使用@PropertySource
注解指定配置文件的位置,并使用@Value
注解将properties
文件中的数据注入到SpringConfiguration
类的成员变量中。
package cn.frankfang.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
/**
* Spring的配置文件, 相当于spring-config.xml
*/
@Configuration
@ComponentScan("cn.frankfang")
@PropertySource("classpath:application.properties")
public class SpringConfiguration {
@Value("${database.driver}")
private String driver;
@Value("${database.url}")
private String url;
@Value("${database.username}")
private String username;
@Value("${database.password}")
private String password;
/**
* 创建一个数据源, 并存入Spring容器中
* @return
*/
@Bean("datasource")
public DataSource dataSource () {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
application.properties
的内容如下:
database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://127.0.0.1:3306/test?useAffectedRows=true
database.username=root
database.password=123456
注:若需获取更多关于@PropertySource
的相关内容,请参阅:Using @PropertySource。
5、@Import
@Import
注解的作用是用于导入其它配置类,在引入其它配置类时可以不添加@Configuration
注解。该注解包含value
属性,用于指定其它配置类的字节码,可传入多个值。
注:若需获取更多关于@Import
的相关内容,请参阅:Using the @Import Annotation。
6、通过注解获取容器
通过纯注解方式获取容器的方式与通过XML配置文件获取文件的方式不同,需要使用AnnotaionConfigApplicationContext
这一实现类去获取容器,使用方法如下:
// 传入配置类的字节码对象,支持传入多个字节码对象
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
到这里我们就实现了通过纯注解的方式来配置使用Spring容器。
四、总结
基于注释的配置的引入引发了这样一个问题:这种方法是否比XML更好?这个问题简短的回答是“视情况而定”,冗长的回答是每种方法都有其优缺点,而且通常由开发人员决定哪种策略更适合他们。
注解配置简单,维护方便,但会使配置变得分散,更难控制;而XML擅长在不接触源代码或重新编译的情况下连接组件,但XML的配置比较繁琐。总的来说还是要视情况而定。但无论是全部使用XML配置文件,或是全部使用注解,甚至是XML配置和注解混合使用,Spring框架都能对其提供非常好的支持,这也体现出Spring框架的强大和包容。