第3章 IOC/DI注解开发
常用注解:
注解 | 作用 | 属性 |
---|---|---|
@Component | 定义bean | |
@Controller | 用于表现层bean定义 | |
@Service | 用于业务层bean定义 | |
@Repository | 用于数据层bean定义 | |
@Configuration | 设置该类为spring配置类 | value(默认):定义bean的id |
@ComponentScan | 配置类扫描路径 | value(默认):扫描路径,此路径可以逐层向下扫描 |
@Scope | 设置该类创建对象的作用范围 可用于设置创建出的bean是否为单例对象 | value(默认):定义bean作用范围, 默认值singleton(单例),可选值prototype(非单例) |
@PostConstruct | 设置该方法为初始化方法 | |
@PreDestroy | 设置该方法为销毁方法 | |
@Autowired | 为引用类型属性设置值 | required:true/false,定义该属性是否允许为null |
@Qualifier | 为引用类型属性指定注入的beanId | value(默认):设置注入的beanId |
@Value | 为 基本数据类型 或 字符串类型 属性设置值 | value(默认):要注入的属性值 |
@PropertySource | 加载properties文件中的属性值 | value(默认):设置加载的properties文件对应的文件名或文件名组成的数 |
@Bean | 将方法的返回值制作为Spring管理的一个bean对象(管理第三方bean) | |
@Import | 导入配置类 | value(默认):定义导入的配置类类名, 当配置类有多个时使用数组格式一次性导入多个配置类 |
📖3.1 注解定义bean
使用@Component
定义bean
@Component
public class BookServiceImpl implements BookService {
}
@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
- 核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.spring"/>
Spring提供@Component注解的三个衍生注解
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层bean定义
@Service
public class BookServiceImpl implements BookService {
}
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
}
作用其实和@Component一样,只是这样可读性增强了。
📖3.2 纯注解开发
注解 | 作用 | 属性 |
---|---|---|
@Configuration | 设置该类为spring配置类 | value(默认):定义bean的id |
@ComponentScan | 配置类扫描路径 | value(默认):扫描路径,此路径可以逐层向下扫描 |
Spring3.0升级了纯注解开发模式,使用==]ava类替代配置文件==,开启了Spring快速开发赛道。
@Configuration
@ComponentScan("com.spring")
public class SpringConfig {
}
AnnotationConfigApplicationContext
加载配置类
public class AppForAnnotation {
public static void main(String[] args) {
//加载类配置文件
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDaoImpl) ctx.getBean("bookDao");
System.out.println(bookDao);
}
}
SpringConfig类替代了Spring核心配置文件:
@Configuration注解替代了如下内容:
@ComponentScan注解替代了如下内容:
✅小结
这一节重点掌握的是使用注解完成Spring的bean管理,需要掌握的内容为:
- 记住@Component、@Controller、@Service、@Repository这四个注解
- applicationContext.xml中
<context:component-san/>
的作用是指定扫描包路径,注解为@ComponentScan - @Configuration标识该类为配置类,使用类替换applicationContext.xml文件
- ClassPathXmlApplicationContext是加载XML配置文件
- AnnotationConfigApplicationContext是加载配置类
📖3.3 注解开发bean作用范围
注解 | 作用 | 属性 |
---|---|---|
@Scope | 设置该类创建对象的作用范围 可用于设置创建出的bean是否为单例对象 | value(默认):定义bean作用范围, 默认值singleton(单例),可选值prototype(非单例) |
@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao {
}
📖3.4 注解Bean生命周期
注解 | 作用 | 属性 |
---|---|---|
@PostConstruct | 设置该方法为初始化方法 | |
@PreDestroy | 设置该方法为销毁方法 |
@Repository("bookDao")
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("BookDao save ... ");
}
@PostConstruct
public void init(){
System.out.println("book init...");
}
@PreDestroy
public void destroy(){
System.out.println("book destroy...");
}
}
📖3.5 依赖注入(自动装配)
- @Autowired
- @Qualifier
- @Value
✅按照类型注入
使用@Autowired
注解开启自动装配模式(按类型)
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
注意:
- @Autowired可以写在属性上,也可也写在setter方法上,最简单的处理方式是
写在属性上并将setter方法删除掉
- 为什么setter方法可以删除呢?
- 自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值
- 普通反射只能获取public修饰的内容
- 暴力反射除了获取public修饰的内容还可以获取private修改的内容
- 所以此处无需提供setter方法
(2)@Autowired是按照类型注入,那么对应BookDao接口如果有多个实现类,比如添加BookDaoImpl2
@Repository
public class BookDaoImpl2 implements BookDao {
public void save() {
System.out.println("book dao save ...2");
}
}
这个时候再次运行App,就会报错
此时,按照类型注入就无法区分到底注入哪个对象,解决方案:按照名称注入
先给两个Dao类分别起个名称
@Repository("bookDao") public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..." ); } } @Repository("bookDao2") public class BookDaoImpl2 implements BookDao { public void save() { System.out.println("book dao save ...2" ); } }
此时就可以注入成功,但是得思考个问题:
@Autowired是按照类型注入的,给BookDao的两个实现起了名称,它还是有两个bean对象,为什么不报错?
@Autowired默认按照类型自动装配,如果IOC容器中同类的Bean找到多个,就按照变量名和Bean的名称匹配。因为变量名叫
bookDao
而容器中也有一个booDao
,所以可以成功注入。分析下面这种情况是否能完成注入呢?
不行,因为按照类型会找到多个bean对象,此时会按照
bookDao
名称去找,因为IOC容器只有名称叫bookDao1
和bookDao2
,所以找不到,会报NoUniqueBeanDefinitionException
✅按照名称注入
当根据类型在容器中找到多个bean,注入参数的属性名又和容器中bean的名称不一致,这个时候该如何解决,就需要使用到
@Qualifier
来指定注入哪个名称的bean对象。@Service public class BookServiceImpl implements BookService { @Autowired @Qualifier("bookDao1") private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } }
@Qualifier注解后的值就是需要注入的bean的名称。
注意:@Qualifier不能独立使用,必须和@Autowired一起使用
✅简单类型注入
引用类型看完,简单类型注入就比较容易懂了。简单类型注入的是基本数据类型或者字符串类型,下面在BookDaoImpl
类中添加一个name
属性,用其进行简单类型注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
数据类型换了,对应的注解也要跟着换,这次使用@Value
注解,将值写入注解的参数中就行了
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("itheima")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
注意数据格式要匹配,如将"abc"注入给int值,这样程序就会报错。
📖3.6 加载properties文件
@Value
一般会被用在从properties配置文件中读取内容进行使用,具体如何实现?
步骤1:resource下准备properties文件
jdbc.properties
name=itheima888
步骤2: 使用注解加载properties配置文件
在配置类上添加@PropertySource
注解
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
步骤3:使用@Value读取配置文件中的内容
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
步骤4:运行程序
运行App类,查看运行结果,说明配置文件中的内容已经被加载到
注意:
如果读取的properties配置文件有多个,可以使用
@PropertySource
的属性来指定多个@PropertySource({"jdbc.properties","xxx.properties"})
@PropertySource
注解属性中不支持使用通配符*
,运行会报错@PropertySource({"*.properties"})
@PropertySource
注解属性中可以把classpath:
加上,代表从当前项目的根路径找文件@PropertySource({"classpath:jdbc.properties"})
📖3.7 IOC/DI注解开发管理第三方bean
前面定义bean的时候都是在自己开发的类上面写个注解就完成了,但如果是第三方的类,这些类都是在jar包中,我们没有办法在类上面添加注解,这个时候该怎么办?
遇到上述问题,我们就需要有一种更加灵活的方式来定义bean,这种方式不能在原始代码上面书写注解,一样能定义bean,这就用到了一个全新的注解==@Bean==。
注解 | 作用 | 属性 |
---|---|---|
@Bean | 将方法的返回值制作为Spring管理的一个bean对象(管理第三方bean) |
- 使用@Bean配置第三方bean
- 1.定义一个方法获得要管理的对象
- 2.添加@Bean,表示当前方法返回值是一个bean
@Configuration
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/mybatis");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
不推荐将所有第三方bean都在SpringConfig写,推荐导入式
✅环境准备
学习@Bean注解之前先来准备环境:
创建一个Maven项目
pom.xml添加Spring的依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
添加一个配置类
SpringConfig
@Configuration public class SpringConfig { }
添加BookDao、BookDaoImpl类
public interface BookDao { public void save(); } @Repository public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..." ); } }
创建运行类App
public class App { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); } }
最终创建好的项目结构如下:
Druid
数据源
✅管理在上述环境中完成对Druid
数据源的管理,具体的实现步骤为:
步骤1:导入对应的jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
步骤2:在配置类中添加一个方法
注意该方法的返回值就是要创建的Bean对象类型
@Configuration
public class SpringConfig {
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
@Bean
注解
步骤3:在方法上添加@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象
@Configuration
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
注意:不能使用DataSource ds = new DruidDataSource()
因为DataSource接口中没有对应的setter方法来设置属性。
步骤4:从IOC容器中获取对象并打印
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println(dataSource);
}
}
至此使用@Bean来管理第三方bean的案例就已经完成。
如果有多个bean要被Spring管理,直接在配置类中多些几个方法,方法上添加@Bean注解即可。
✅引入外部配置类
- 使用包扫描引入
- 导入式bean管理(推荐)
如果把所有的第三方bean都配置到Spring的配置类SpringConfig
中,虽然可以,但是不利于代码阅读和分类管理,所有我们就想能不能按照类别将这些bean配置到不同的配置类中?
对于数据源的bean,我们新建一个JdbcConfig
配置类,并把数据源配置到该类下。
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/mybatis");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
现在的问题是,这个配置类如何能被Spring配置类加载到,并创建DataSource对象在IOC容器中?
针对这个问题,有两个解决方案:
🔖使用包扫描引入
步骤1:在Spring的配置类上添加包扫描
@Configuration
@ComponentScan("com.itheima.config")
public class SpringConfig {
}
步骤2:在JdbcConfig上添加配置注解
JdbcConfig类要放入到com.itheima.config
包下,需要被Spring的配置类扫描到即可
@Configuration
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
步骤3:运行程序
依然能获取到bean对象并打印控制台。
这种方式虽然能够扫描到,但是不能很快的知晓都引入了哪些配置类,所有这种方式不推荐使用。
@Import
引入
🔖使用方案一实现起来有点小复杂,Spring早就想到了这一点,于是又给我们提供了第二种方案。
这种方案可以不用加@Configuration
注解,但是必须在Spring配置类上使用@Import
注解手动引入需要加载的配置类
步骤1:去除JdbcConfig类上的注解
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
步骤2:在Spring配置类中引入
@Configuration
//@ComponentScan("com.itheima.config")
@Import({JdbcConfig.class})
public class SpringConfig {
}
注意:
扫描注解可以移除
@Import参数需要的是一个数组,可以引入多个配置类。
@Import注解在配置类中只能写一次,下面的方式是不允许的
@Configuration //@ComponentScan("com.itheima.config") @Import(JdbcConfig.class) @Import(Xxx.class) public class SpringConfig { }
步骤3:运行程序
依然能获取到bean对象并打印控制台
✅实现为第三方bean注入资源
在使用@Bean创建bean对象的时候,如果方法在创建的过程中需要其他资源该怎么办?
这些资源会有两大类,分别是简单数据类型
和引用数据类型
。
🔖简单数据类型:@Value
需求分析:对于下面代码关于数据库的四要素不应该写死在代码中,应该是从properties配置文件中读取。如何来优化下面的代码?
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds
}
}
步骤1:类中提供四个属性
public class JdbcConfig {
private String driver;
private String url;
private String userName;
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
@Value
注解引入值
步骤2:使用public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("password")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
🔖引用数据类型:方法形参
需求分析 :假设在构建DataSource对象的时候,需要用到BookDao对象,该如何把BookDao对象注入进方法内让其使用呢?
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
步骤1:在SpringConfig中扫描BookDao
扫描的目的是让Spring能管理到BookDao,也就是说要让IOC容器中有一个bookDao对象
@Configuration
@ComponentScan("com.itheima.dao")
@Import({JdbcConfig.class})
public class SpringConfig {
}
步骤2:在JdbcConfig类的方法上添加参数
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象。
步骤3:运行程序
✅总结
前面已经完成了XML配置和注解的开发实现,至于两者之间的差异,放在一块去对比回顾下:
功能 | XML配置 | 注解 |
---|---|---|
定义bean | bean标签 - id属性 - class属性 | @Component - @Controller - @Service - @Repository @ComponentScan |
设置依赖注入 | setter注入(set方法) - 引用/简单 构造器注入(构造方法) - 引用/简单 自动装配 | @Autowired - @Qualifier @Value |
配置第三方bean | bean标签 静态工厂、实例工厂、FactoryBean | |
作用范围 | scope属性 | @Bean |
生命周期 | 标准接口 - init-method - destroy-method | @PostConstructor @PreDestroy |