本文使用的示例项目是 。
1.@Component
@Component
注解的用途与<bean/>
标签的用途是相同的,都用于向 IoC 容器添加一个 Bean 定义。
比如:
public class UserDaoImpl implements UserDao {
public void save(){
System.out.println("UserDaoImpl.save() is called.");
}
}
只使用@Component
注解是不够的,我们还需要告诉 IoC 容器在哪个包路径下检索 Bean 定义:
<context:component-scan base-package="cn.icexmoon.springdemo.dao"/>
需要引入命名空间 context,方式在上一篇中有介绍。
现在 Spring 会自动扫描cn.icexmoon.springdemo.dao
包下的 Java 类,如果有使用@Component
注解,就会将其添加到 Bean 定义。
实际上通常会使用项目的顶级包名,这样就可以扫描项目下的所有类:
<context:component-scan base-package="cn.icexmoon.springdemo"/>
现在通过 IoC 容器就可以获取 Bean 实例:
ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
UserDao userDao = ctx.getBean(UserDao.class);
userDao.save();
默认情况下@Component
注解创建的 Bean 的名称使用的是类名(首字母小写):
UserDao userDao = ctx.getBean("userDaoImpl", UserDao.class);
可以通过value
属性指定 Bean 名称:
"userDao")
(public class UserDaoImpl implements UserDao {
// ...
}
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
UserDao userDao = ctx.getBean("userDao", UserDao.class);
userDao.save();
}
}
@Component
有一些衍生注解,比如:
-
@Service
,表示 Service 层的 Bean。 -
@Controller
,表示 Controller 层的 Bean。 -
@Repository
,表示持久层的 Bean。
用途是相同的,都是用于定义 Bean,只不过对 Bean 的用途进行了进一步的区分。
所以示例可以改写为:
"userDao")
(public class UserDaoImpl implements UserDao {
// ...
}
public class UserServiceImpl implements UserService {
// ...
}
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
UserDao userDao = ctx.getBean("userDao", UserDao.class);
userDao.save();
UserService userService = ctx.getBean(UserService.class);
userService.save();
}
}
2.纯注解开发
虽然现在 Bean 定义已经通过@Component
注解实现了,但示例项目依然要加载 XML 配置文件。可以进一步改为不使用配置文件的“纯注解开发”。
需要先准备一个配置类:
basePackages = "cn.icexmoon.springdemo")
(public class WebConfig {
}
@Configuration
表示这个类是一个配置类,它充当了 XML 配置文件的功能,可以用于定义 Bean。
在配置类上添加一个@ComponentScan
注解可以用于指定扫描 Bean 定义的包范围,相当于之前使用的<context:component-scan/>
标签。
IoC 容器的创建也需要修改,实现类使用AnnotationConfigApplicationContext
,将不再加载 XML 配置文件,而是加载配置类作为 Bean 定义:
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
// ...
}
}
现在已经可以删除 XML 配置文件了。
3.@Scope
注解开发模式下,Bean 默认的作用域同样为单例,可以使用@Scope
注解修改作用域:
"userDao")
("prototype")
(public class UserDaoImpl implements UserDao {
public void save(){
System.out.println("UserDaoImpl.save() is called.");
}
}
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
UserDao userDao = ctx.getBean("userDao", UserDao.class);
UserDao userDao2 = ctx.getBean("userDao", UserDao.class);
System.out.println(userDao2 == userDao ? "is same object" : "is not same object");
}
}
虽然可以使用字符串指定作用域,但更好的方式是是用预定义常量:
ConfigurableBeanFactory.SCOPE_PROTOTYPE) (
除此之外还有其他常量可选,具体可以查看@Scope
的源码注释。
4.生命周期
注解模式下同样可以使用上篇文章提到的接口定义 Spring Bean 的生命周期回调:
"userDao")
(ConfigurableBeanFactory.SCOPE_SINGLETON)
(public class UserDaoImpl implements UserDao, InitializingBean, DisposableBean {
// ...
public void destroy() throws Exception {
System.out.println("before UserDaoImpl instance destroy.");
}
public void afterPropertiesSet() throws Exception {
System.out.println("after UserDaoImpl instance init.");
}
}
但更推荐的方式是使用注解:
"userDao")
(ConfigurableBeanFactory.SCOPE_SINGLETON)
(public class UserDaoImpl implements UserDao {
// ...
public void destroy() {
System.out.println("before UserDaoImpl instance destroy.");
}
public void afterPropertiesSet() {
System.out.println("after UserDaoImpl instance init.");
}
}
需要注意的是,@PreDestroy
和@PostConstruct
注解并不属于 spring-context 依赖,其属于 javax.annotation-api 依赖。所以需要添加:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
5.自动装配
5.1.属性
可以使用@Autowired
注解实现对属性依赖的自动装配:
public class UserServiceImpl implements UserService {
private UserDao userDao;
// ...
}
Spring 会寻找类型匹配的 Bean 并使用反射的方式完成依赖注入,所以这里并不需要添加 Set 方法。
5.2.构造器
用@Autowired
标记构造器同样可以实现自动装配:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
// ...
}
5.3.Setter
也可以用@Autowired
标记 Setter 实现自动装配:
public class UserServiceImpl implements UserService {
onMethod = ( ))
( private UserDao userDao;
// ...
}
这里使用的是 Lombok 生成 Setter 的写法,具体可以参考。
5.4.@Qualifier
如果有多个类型匹配,我们需要用@Qualifier
注解指定其中的一个 Bean 用于注入:
public class UserDaoImpl implements UserDao {
// ...
}
public class UserDaoImpl2 implements UserDao {
// ...
}
public class UserServiceImpl implements UserService {
"userDaoImpl")
( private UserDao userDao;
// ...
}
5.5.@Value
对于简单类型(基础类型和 String ),可以使用@Value
实现自动装配:
public class UserServiceImpl implements UserService {
"userDaoImpl")
( private UserDao userDao;
"icexmoon")
( private String name;
public void save() {
System.out.println("UserServiceImpl.save() is called.");
System.out.println("name:" + name);
userDao.save();
}
}
在这个示例中这样做多少显得有点多余,完全可以:
public class UserServiceImpl implements UserService {
// ...
private String name = "icexmoon";
}
但使用@Value
的好处是可以从 properties 文件加载属性。
比如 Resource 目录下有一个 jdbc.properties 文件:
name=icexmoon
在配置类上使用@PropertySource
注解加载这个 properties 文件:
basePackages = "cn.icexmoon.springdemo")
("classpath:jdbc.properties")
(public class WebConfig {
}
使用@Value
完成注入:
public class UserServiceImpl implements UserService {
// ...
"${name}")
( private String name;
// ...
}
6.管理第三方 Bean
添加第三方依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
在配置类中添加一个 Bean 方法:
basePackages = "cn.icexmoon.springdemo")
("classpath:jdbc.properties")
(public class WebConfig {
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setPassword("mysql");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc://localhost:3306/test");
return druidDataSource;
}
}
通过 IoC 容器获取 Bean:
public class Application {
public static void main(String[] args) {
// ...
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println(dataSource);
}
}
可以按照 Bean 类型对配置类进行拆分,比如将数据库的相关类放在JDBCConfig
中:
public class JDBCConfig {
public DataSource dataSource() {
// ...
}
}
因为@Configuration
同样是@Component
的一个衍生注解,所以在我们已经制定扫描顶级包的情况下,这个配置类同样是有效的。
也可以不使用扫描,在作为入口的配置类上通过@Import
注解导入其它配置类:
public class JDBCConfig {
public DataSource dataSource() {
// ...
}
}
basePackages = "cn.icexmoon.springdemo")
("classpath:jdbc.properties")
(JDBCConfig.class)
(public class WebConfig {
}
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
// ...
}
}
同样的,数据库连接信息应当从 properties 文件中读取,比如:
jdbc.user=root
jdbc.password=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc://localhost:3306/test
利用@Value
自动装配数据库连接信息到属性,并在 Bean 方法中使用:
public class JDBCConfig {
"${jdbc.user}")
( private String user;
"${jdbc.password}")
( private String password;
"${jdbc.driver}")
( private String driver;
"${jdbc.url}")
( private String url;
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername(user);
druidDataSource.setPassword(password);
druidDataSource.setDriverClassName(driver);
druidDataSource.setUrl(url);
return druidDataSource;
}
}
The End,谢谢阅读。
文章评论