IOC/DI:第三方bean的配置管理、注解开发和整合开发
1. IOC/DI配置管理第三方bean
1.1 案例:数据源对象管理
本次将使用数据源Druid(德鲁伊)
和C3P0(是一个数据库连接池)
来配置学习。
1.1.1 环境准备
学习之前,先来准备下案例环境:
-
pom.xml添加依赖
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies> -
resources下添加spring的配置文件applicationContext.xml
1
2
3
4
5
6
7
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
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans> -
编写一个运行类App
1
2
3
4
5public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
1.1.2 思路分析
在上述环境下,来对数据源进行配置管理,先来分析下思路:
需求:使用Spring的IOC容器来管理Druid连接池对象
1.使用第三方的技术,需要在pom.xml添加依赖
2.在配置文件中将【第三方的类】制作成一个bean,让IOC容器进行管理
3.数据库连接需要基础的四要素
驱动
、连接
、用户名
和密码
,【如何注入】到对应的bean中4.从IOC容器中获取对应的bean对象,将其打印到控制台查看结果
1.1.3 实现Druid管理
步骤1:导入druid
的依赖
pom.xml中添加依赖
1 | <dependency> |
步骤2:配置第三方bean
在applicationContext.xml配置文件中添加DruidDataSource
的配置
1 |
|
说明:
- driverClassName:数据库驱动
- url:数据库连接地址
- username:数据库连接用户名
- password:数据库连接密码
- 数据库连接的四要素要和自己使用的数据库信息一致。
步骤3:从IOC容器中获取对应的bean对象
1 | public class App { |
步骤4:运行程序
打印如下结果: 说明第三方bean对象已经被spring的IOC容器进行管理
1 | { |
-
第三方的类指的是什么?
1
DruidDataSource
-
如何注入数据库连接四要素?
1
setter注入
1.1.4 实现C3P0管理
完成了DruidDataSource的管理,接下来再来加深下练习,这次来管理C3P0
数据源,具体的实现步骤是什么呢?
需求:使用Spring的IOC容器来管理C3P0连接池对象
实现方案和上面基本一致,重点要关注管理的是哪个bean对象`?
步骤1:导入C3P0
的依赖
pom.xml中添加依赖
1 | <dependency> |
对于新的技术,不知道具体的坐标该如何查找?
-
直接百度搜索
-
从mvn的仓库
https://mvnrepository.com/
中进行搜索
步骤2:配置第三方bean
在applicationContext.xml配置文件中添加配置
1 | <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> |
注意:
- ComboPooledDataSource的属性是通过setter方式进行注入
- 想注入属性就需要在ComboPooledDataSource类或其上层类中有提供属性对应的setter方法
- C3P0的四个属性和Druid的四个属性是不一样的
步骤3:运行程序
程序会报错,错误如下
1 | 警告: Could not load driverClass com.mysql.jdbc.Driver |
报的错为ClassNotFoundException,翻译出来是类没有发现的异常
,具体的类为com.mysql.jdbc.Driver
。错误的原因是缺少mysql的驱动包。
分析出错误的原因,具体的解决方案就比较简单,只需要在pom.xml把驱动包引入即可。
1 | <dependency> |
添加完mysql的驱动包以后,再次运行App,就可以打印出结果并不报错
注意:
- 数据连接池在配置属性的时候,除了可以注入数据库连接四要素外还可以配置很多其他的属性,具体都有哪些属性用到的时候再去查,一般配置基础的四个,其他都有自己的默认值
- Druid和C3P0在没有导入mysql驱动包的前提下,一个没报错一个报错,说明Druid在初始化的时候没有去加载驱动,而C3P0刚好相反
- Druid程序运行虽然没有报错,但是当调用DruidDataSource的getConnection()方法获取连接的时候,也会报找不到驱动类的错误
1.2 加载properties文件
上节中已经完成两个数据源druid
和C3P0
的配置,但是其中包含了一些问题,来分析下:
- 这两个数据源中都使用到了一些固定的常量如数据库连接四要素,把这些值写在Spring的配置文件中不利于后期维护
- 需要将这些值提取到一个外部的properties配置文件中
- Spring框架如何从配置文件中读取属性值来配置就是接下来要解决的问题。
问题提出来后,具体该如何实现?
1.2.1 第三方bean属性优化
1.2.1.1 实现思路
需求:将数据库连接四要素提取到properties配置文件,spring来加载配置信息并使用这些信息来完成属性注入。
1.在resources下创建一个jdbc.properties(文件的名称可以任意)
2.将数据库连接四要素配置到配置文件中
3.在Spring的配置文件中加载properties文件
4.使用加载到的值实现属性注入
其中第3,4步骤是需要重点关注,具体是如何实现。
1.2.1.2 实现步骤
步骤1:准备properties配置文件
resources下创建一个jdbc.properties文件,并添加对应的属性键值对
1 | jdbc.driver=com.mysql.jdbc.Driver |
步骤2:开启context
命名空间
在applicationContext.xml中开context
命名空间
步骤3:加载properties配置文件
在配置文件中使用context
命名空间下的标签来加载properties配置文件
1 | <context:property-placeholder location="jdbc.properties"/> |
步骤4:完成属性注入
使用${key}
来读取properties配置文件中的内容并完成属性注入
1 |
|
至此,读取外部properties配置文件中的内容就已经完成。
1.2.2 读取单个属性
1.2.2.1 实现思路
对于上面的案例,效果不是很明显,可以换个案例来演示下:
需求:从properties配置文件中读取key为name的值,并将其注入到BookDao中并在save方法中进行打印。
1.在项目中添加BookDao和BookDaoImpl类
2.为BookDaoImpl添加一个name属性并提供setter方法
3.在jdbc.properties中添加数据注入到bookDao中打印方便查询结果
4.在applicationContext.xml添加配置完成配置文件加载、属性注入(${key})
1.2.2.2 实现步骤
步骤1:在项目中添对应的类
BookDao和BookDaoImpl类,并在BookDaoImpl类中添加name
属性与setter方法
1 | public interface BookDao { |
步骤2:完成配置文件的读取与注入
在applicationContext.xml添加配置,bean的配置管理
、读取外部properties
、依赖注入
:
1 |
|
步骤3:运行程序
在App类中,从IOC容器中获取bookDao对象,调用方法,查看值是否已经被获取到并打印控制台
1 | public class App { |
输出结果为:
1 | book dao save ...com.mysql.jdbc.Driver |
1.2.2.3 注意事项
至此,读取properties配置文件中的内容就已经完成,但是在使用的时候,有些注意事项:
-
问题一:键值对的key为
username
引发的问题1.在properties中配置键值对的时候,如果key设置为
username
1
username=root666
2.在applicationContext.xml注入该属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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">
<context:property-placeholder location="jdbc.properties"/>
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<property name="name" value="${username}"/>
</bean>
</beans>3.运行后,在控制台打印的却不是
root666
,而是自己电脑的用户名1
book dao save ...自己电脑的用户名
4.出现问题的原因是
<context:property-placeholder/>
标签会加载系统的环境变量,而且环境变量的值会被优先加载,如何查看系统的环境变量?1
2
3
4public static void main(String[] args) throws Exception{
Map<String, String> env = System.getenv();
System.out.println(env);
}可以自行运行,在打印出来的结果中会有一个USERNAME=XXX[自己电脑的用户名称]
5.解决方案
1
2
3
4
5
6
7
8
9
10
11
12
<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">
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
</beans>system-properties-mode:设置为NEVER,表示不加载系统属性,就可以解决上述问题。
当然还有一个解决方案就是避免使用
username
作为属性的key
。 -
问题二:当有多个properties配置文件需要被加载,该如何配置?
1.调整下配置文件的内容,在resources下添加
jdbc.properties
,jdbc2.properties
,内容如下:jdbc.properties
1
2
3
4jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=rootjdbc2.properties
1
username=root666
2.修改applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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">
<!--方式一 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
<!--方式二-->
<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
<!--方式三 -->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
<!--方式四-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
</beans>说明:
- 方式一:可以实现,如果配置文件多的话,每个都需要配置
- 方式二:
*.properties
代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准 - 方式三:标准的写法,
classpath:
代表的是从根路径下开始查找,但是只能查询当前项目的根路径 - 方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties配置文件
1.2.3 加载properties文件小结
本节主要讲解的是properties配置文件的加载,需要掌握的内容有:
-
如何开启
context
命名空间 -
如何加载properties配置文件
1
<context:property-placeholder location="" system-properties-mode="NEVER"/>
-
如何在applicationContext.xml引入properties配置文件中的值
1
${key}
2. 核心容器
前面已经完成bean与依赖注入的相关知识学习,接下来主要学习的是IOC容器中的核心容器。
这里所说的核心容器,可以把它简单的理解为ApplicationContext
:
- 如何创建容器?
- 创建好容器后,如何从容器中获取bean对象?
- 容器类的层次结构是什么?
- BeanFactory是什么?
2.1 环境准备
在学习和解决上述问题之前,先来准备下案例环境:
-
创建一个Maven项目
-
pom.xml添加Spring的依赖
-
resources下添加applicationContext.xml
1
2
3
4
5
6
7
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans> -
添加BookDao和BookDaoImpl类
1
2
3
4
5
6
7
8public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
} -
创建运行类App
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
2.2 容器
2.2.1 容器的创建方式
案例中创建ApplicationContext
的方式为:
1 | ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); |
这种方式翻译为:类路径下的XML配置文件
除了上面这种方式,Spring还提供了另外一种创建方式(不推荐此方式):
1 | ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml"); |
这种方式翻译为:文件系统下的XML配置文件
2.2.2 Bean的三种获取方式
方式一,就是目前案例中获取的方式:
1 | BookDao bookDao = (BookDao) ctx.getBean("bookDao"); |
这种方式存在的问题是每次获取的时候都需要进行类型转换,有没有更简单的方式呢?
方式二:
1 | BookDao bookDao = ctx.getBean("bookDao",BookDao.class); |
这种方式可以解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少。
方式三:
1 | BookDao bookDao = ctx.getBean(BookDao.class); |
这种方式就类似之前所学习依赖注入中的按类型注入。必须要确保IOC容器中该类型对应的bean对象只能有一个。
2.2.3 BeanFactory的使用
使用BeanFactory来创建IOC容器的具体实现方式为:
1 | public class AppForBeanFactory { |
在Spring中,BeanFactory
和ApplicationContext
都是用来管理和提供对象的容器。它们之间的主要区别在于实例化和初始化bean对象的时机。
BeanFactory
采用延迟加载策略,即只有在你通过调用getBean()
方法获取bean对象时,才会触发相应的实例化和初始化过程。这意味着在容器加载时,并不会创建所有的bean对象,而是在需要时按需创建。
相反,ApplicationContext
是立即加载的,即在容器加载的过程中就会创建和初始化所有的bean对象。这是因为ApplicationContext
在创建的同时会解析和实例化所有的bean定义,并初始化它们的依赖关系,以提供更快的访问速度和更早的错误检测。
因此,如果你希望延迟加载和按需创建bean对象,可以使用BeanFactory
。如果你需要在应用程序启动时立即加载和初始化所有的bean对象,可以使用ApplicationContext
。
值得注意的是,ApplicationContext
是BeanFactory
的子接口,它提供了更多的功能,如国际化支持、事件发布、AOP等。因此,在大多数情况下,我们更倾向于使用ApplicationContext
来管理bean对象。
-
BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建
-
ApplicationContext是立即加载,容器加载的时候就会创建bean对象
-
ApplicationContext要想成为延迟加载,只需要按照如下方式进行配置
1
2
3
4
5
6
7
<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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" lazy-init="true"/>
</beans>
小结
这一节中所讲的知识点包括:
-
容器创建的两种方式
- ClassPathXmlApplicationContext[掌握]
- FileSystemXmlApplicationContext[知道即可]
-
获取Bean的三种方式
- getBean(“名称”):需要类型转换
- getBean(“名称”,类型.class):多了一个参数
- getBean(类型.class):容器中不能有多个该类的bean对象
上述三种方式,各有各的优缺点,用哪个都可以。
-
容器类层次结构
- 只需要知晓容器的最上级的父接口为 BeanFactory即可
-
BeanFactory
- 使用BeanFactory创建的容器是延迟加载
- 使用ApplicationContext创建的容器是立即加载
- 具体BeanFactory如何创建只需要了解即可。
2.3 核心容器总结
2.3.1 容器相关
- BeanFactory是IOC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
- ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
- ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
- ApplicationContext接口常用初始化类
- ClassPathXmlApplicationContext(常用)
- FileSystemXmlApplicationContext
2.3.2 bean相关
其实整个配置中最常用的就两个属性id和class。
3. IOC/DI注解开发
Spring的IOC/DI对应的配置开发就已经讲解完成,但是使用起来相对来说还是比较复杂的,复杂的地方在配置文件。
前面聊Spring的时候说过,Spring可以简化代码的开发,到现在并没有体会到。
所以Spring到底是如何简化代码开发的呢?
要想真正简化开发,就需要用到Spring的注解开发
关于注解开发,会讲解两块内容注解开发定义bean
和纯注解开发
。
注解开发定义bean用的是2.5版提供的注解,纯注解开发用的是3.0版提供的注解。
3.1 环境准备
在学习注解开发之前,先来准备下案例环境:
-
创建一个Maven项目
-
pom.xml添加Spring的依赖
-
resources下添加applicationContext.xml
1
2
3
4
5
6
7
<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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans> -
添加BookDao、BookDaoImpl、BookService、BookServiceImpl类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
public void save() {
System.out.println("book service save ...");
}
} -
创建运行类App
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
3.2 注解开发定义bean
步骤1:删除原XML配置
将配置文件中的<bean>
标签删除掉
1 | <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> |
步骤2:Dao上添加注解
在BookDaoImpl类上添加@Component
注解
1 |
|
注意:@Component注解不可以添加在接口上,因为接口是无法创建对象的。
XML与注解配置的对应关系:
步骤3:配置Spring的注解包扫描
为了让Spring框架能够扫描到写在类上的注解,需要在配置文件上进行包扫描
1 |
|
说明:
component-scan
- component:组件,Spring将管理的bean视作自己的一个组件
- scan:扫描
base-package指定Spring框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解。
- 包路径越多[如:com.itheima.dao.impl],扫描的范围越小速度越快
- 包路径越少[如:com.itheima],扫描的范围越大速度越慢
- 一般扫描到项目的组织名称即Maven的groupId下[如:com.itheima]即可。
步骤4:运行程序
步骤5:Service上添加注解
在BookServiceImpl类上也添加@Component
交给Spring框架管理
1 |
|
步骤6:运行程序
在App类中,从IOC容器中获取BookServiceImpl对应的bean对象,打印
1 | public class App { |
打印观察结果,两个bean对象都已经打印到控制台
1 | com.itheima.dao.impl.BookDaoImpl@57f23557 |
说明:
-
BookServiceImpl类没有起名称,所以在App中是按照类型来获取bean对象
-
@Component注解如果不起名称,会有一个默认值就是
当前类名首字母小写
,所以也可以按照名称获取,如1
2BookService bookService = (BookService)ctx.getBean("bookServiceImpl");
System.out.println(bookService);
对于@Component注解,还衍生出了其他三个注解@Controller
、@Service
、@Repository
通过查看源码会发现这三个注解和@Component注解的作用是一样的,为什么要衍生出这三个呢?
具体来说,这些注解的作用和功能与@Component
完全相同,都是将Java类标记为Spring组件,并交由Spring容器管理。但是,它们的语义和用途略有不同,如下所示:
@Controller
注解用于标记控制器层(MVC中的控制器),主要用于处理用户请求和响应。@Service
注解用于标记服务层(业务层),主要实现业务逻辑的处理。@Repository
注解用于标记数据访问层,主要实现数据访问和持久化操作。
知识点1:@Component等
名称 | @Component/@Controller/@Service/@Repository |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为spring管理的bean |
属性 | value(默认):定义bean的id |
3.2 纯注解开发模式
上面已经可以使用注解来配置bean,但是依然有用到配置文件,在配置文件中对包进行了扫描,Spring在3.0版已经支持纯注解开发
- Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道
具体如何实现?
3.2.1 思路分析
实现思路为:
- 将配置文件applicationContext.xml删除掉,使用类来替换。
3.2.2 实现步骤
步骤1:创建配置类
创建一个配置类SpringConfig
1 | public class SpringConfig { |
步骤2:标识该类为配置类
在配置类上添加@Configuration
注解,将其标识为一个配置类,替换applicationContext.xml
1 |
|
步骤3:用注解替换包扫描配置
在配置类上添加包扫描注解@ComponentScan
替换<context:component-scan base-package=""/>
1 |
|
步骤4:创建运行类并执行
创建一个新的运行类AppForAnnotation
1 | public class AppForAnnotation { |
运行AppForAnnotation,可以看到两个对象依然被获取成功
1 | com.itheima.dao.impl.BookDaoImpl@57f23557 |
至此,纯注解开发的方式就已经完成了,主要内容包括:
-
Java类替换Spring核心配置文件
-
@Configuration注解用于设定当前类为配置类
-
@ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
1
@ComponentScan({"com.itheima.service","com.itheima.dao"})
-
读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
1
2
3
4//加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
知识点1:@Configuration
名称 | @Configuration |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为spring配置类 |
属性 | value(默认):定义bean的id |
知识点2:@ComponentScan
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
属性 | value(默认):扫描路径,此路径可以逐层向下扫描 |
小结:
这一节重点掌握的是使用注解完成Spring的bean管理,需要掌握的内容为:
- 记住
@Component
、@Controller
、@Service
、@Repository
四个注解 applicationContext.xml
中<context:component-san/>
的作用是指定扫描包路径,注解为@ComponentScan
@Configuration
标识该类为配置类,使用类替换applicationContext.xml
文件ClassPathXmlApplicationContext
是加载XML配置文件AnnotationConfigApplicationContext
是加载配置类
3.3 注解开发bean作用范围与生命周期管理
使用注解已经完成了bean的管理,接下来按照前面所学习的内容,将通过配置实现的内容都换成对应的注解实现,包含两部分内容:bean作用范围
和bean生命周期
。
3.3.1 环境准备
老规矩,学习之前先来准备环境:
-
创建一个Maven项目
-
pom.xml添加Spring的依赖
-
添加一个配置类
SpringConfig
1
2
3
4
public class SpringConfig {
} -
添加BookDao、BookDaoImpl类
1
2
3
4
5
6
7
8
9public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
} -
创建运行类App
1
2
3
4
5
6
7
8
9public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao1 = ctx.getBean(BookDao.class);
BookDao bookDao2 = ctx.getBean(BookDao.class);
System.out.println(bookDao1);
System.out.println(bookDao2);
}
}
3.3.2 Bean的作用范围
(1)先运行App类,在控制台打印两个一摸一样的地址,说明默认情况下bean是单例
1 | com.itheima.BookDaoImpl@71809907 |
(2)要想将BookDaoImpl变成非单例,只需要在其类上添加@scope
注解
1 |
|
再次执行App类,打印结果:
1 | com.itheima.BookDaoImpl@6356695f |
知识点1:@Scope
名称 | @Scope |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类创建对象的作用范围 可用于设置创建出的bean是否为单例对象 |
属性 | value(默认):定义bean作用范围, 默认值singleton(单例),可选值prototype(非单例) |
3.3.3 Bean的生命周期
(1)在BookDaoImpl中添加两个方法,init
和destroy
,方法名可以任意
1 |
|
(2)如何对方法进行标识,哪个是初始化方法,哪个是销毁方法?
只需要在对应的方法上添加@PostConstruct
和@PreDestroy
注解即可。
1 |
|
(3)要想看到两个方法执行,需要注意的是destroy
只有在容器关闭的时候,才会执行,所以需要修改App的类
1 | public class App { |
(4)运行App,类查看打印结果,证明init和destroy方法都被执行了。
单例模式下的执行结果:
1 | init ... |
注意:@PostConstruct和@PreDestroy注解如果找不到,需要导入下面的jar包
1 | <dependency> |
找不到的原因是,从JDK9以后jdk中的javax.annotation包被移除了,这两个注解刚好就在这个包中。
知识点1:@PostConstruct
名称 | @PostConstruct |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为初始化方法 |
属性 | 无 |
知识点2:@PreDestroy
名称 | @PreDestroy |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为销毁方法 |
属性 | 无 |
小结
3.4 注解开发依赖注入
Spring为了使用注解简化开发,并没有提供构造函数注入
、setter注入
对应的注解,只提供了自动装配的注解实现。
3.4.1 环境准备
在学习之前,把案例环境介绍下:
-
创建一个Maven项目
-
pom.xml添加Spring的依赖
-
添加一个配置类
SpringConfig
1
2
3
4
public class SpringConfig {
} -
添加BookDao、BookDaoImpl、BookService、BookServiceImpl类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
} -
创建运行类App
1
2
3
4
5
6
7public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookService bookService = ctx.getBean(BookService.class);
bookService.save();
}
}
环境准备好后,运行后会发现有问题:
1 | Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.itheima.dao.BookDao.save()" because "this.bookDao" is null |
出现问题的原因是,在BookServiceImpl类中添加了BookDao的属性,并提供了setter方法,但是目前是没有提供配置注入BookDao的,所以bookDao对象为Null,调用其save方法就会报控指针异常
。
3.4.2 注解实现按照类型注入
对于这个问题使用注解该如何解决?
(1) 在BookServiceImpl类的bookDao属性上添加@Autowired
注解
1 |
|
注意:
- @Autowired可以写在属性上,也可也写在setter方法上,最简单的处理方式是
写在属性上并将setter方法删除掉
- 为什么setter方法可以删除呢?
- 自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值
- 普通反射只能获取public修饰的内容
- 暴力反射除了获取public修饰的内容还可以获取private修改的内容
- 所以此处无需提供setter方法
(2)@Autowired是按照类型注入,那么对应BookDao接口如果有多个实现类,比如添加BookDaoImpl2
1 |
|
这个时候再次运行App,就会报错
1 | NoUniqueBeanDefinitionException |
此时,按照类型注入就无法区分到底注入哪个对象,解决方案:按照名称注入
-
先给两个Dao类分别起个名称
1
2
3
4
5
6
7
8
9
10
11
12
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
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
-
3.4.3 注解实现按照名称注入
当根据类型在容器中找到多个bean,注入参数的属性名又和容器中bean的名称不一致,这个时候该如何解决,就需要使用到@Qualifier
来指定注入哪个名称的bean对象。
1 |
|
@Qualifier注解后的值就是需要注入的bean的名称。
注意:@Qualifier不能独立使用,必须和@Autowired一起使用
3.4.4 简单数据类型注入
引用类型看完,简单类型注入就比较容易懂了。简单类型注入的是基本数据类型或者字符串类型,下面在BookDaoImpl
类中添加一个name
属性,用其进行简单类型注入
数据类型换了,对应的注解也要跟着换,这次使用@Value
注解,将值写入注解的参数中就行了
1 |
|
注意数据格式要匹配,如将"abc"注入给int值,这样程序就会报错。
介绍完后,会有一种感觉就是这个注解好像没什么用,跟直接赋值是一个效果,还没有直接赋值简单,所以这个注解存在的意义是什么?
3.4.5 注解读取properties配置文件
@Value
一般会被用在从properties配置文件中读取内容进行使用,具体如何实现?
步骤1:resource下准备properties文件
jdbc.properties
1 | name=itheima888 |
步骤2: 使用注解加载properties配置文件
在配置类上添加@PropertySource
注解
1 |
|
步骤3:使用@Value读取配置文件中的内容
1 |
|
步骤4:运行程序
运行App类,查看运行结果,说明配置文件中的内容已经被加载到
1 | book service save ... |
注意:
-
如果读取的properties配置文件有多个,可以使用
@PropertySource
的属性来指定多个1
-
@PropertySource
注解属性中不支持使用通配符*
,运行会报错1
-
@PropertySource
注解属性中可以把classpath:
加上,代表从当前项目的根路径找文件1
知识点1:@Autowired
名称 | @Autowired |
---|---|
类型 | 属性注解 或 方法注解(了解) 或 方法形参注解(了解) |
位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 或 方法形参前面 |
作用 | 为引用类型属性设置值 |
属性 | required:true/false,定义该属性是否允许为null |
知识点2:@Qualifier
名称 | @Qualifier |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 |
作用 | 为引用类型属性指定注入的beanId |
属性 | value(默认):设置注入的beanId |
知识点3:@Value
名称 | @Value |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 |
作用 | 为 基本数据类型 或 字符串类型 属性设置值 |
属性 | value(默认):要注入的属性值 |
知识点4:@PropertySource
名称 | @PropertySource |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 加载properties文件中的属性值 |
属性 | value(默认):设置加载的properties文件对应的文件名或文件名组成的数组 |
4. IOC/DI注解开发管理第三方bean
前面定义bean的时候都是在自己开发的类上面写个注解就完成了,但如果是第三方的类,这些类都是在jar包中,没有办法在类上面添加注解,这个时候该怎么办?
遇到上述问题,就需要有一种更加灵活的方式来定义bean,这种方式不能在原始代码上面书写注解,一样能定义bean,这就用到了一个全新的注解==@Bean==。
4.1 环境准备
学习@Bean注解之前先来准备环境:
-
创建一个Maven项目
-
pom.xml添加Spring的依赖
-
添加一个配置类
SpringConfig
1
2
3
public class SpringConfig {
} -
添加BookDao、BookDaoImpl类
1
2
3
4
5
6
7
8
9public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
} -
创建运行类App
1
2
3
4
5public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
}
}
4.2 注解开发管理第三方bean
在上述环境中完成对Druid
数据源的管理,具体的实现步骤为:
步骤1:导入对应的jar包
1 | <dependency> |
步骤2:在配置类中添加一个方法
步骤3:在方法上添加@Bean
注解
@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象
1 |
|
注意:不能使用DataSource ds = new DruidDataSource()
因为DataSource接口中没有对应的setter方法来设置属性。
步骤4:从IOC容器中获取对象并打印
1 | public class App { |
打印结果:
1 | { |
至此使用@Bean来管理第三方bean的案例就已经完成。
如果有多个bean要被Spring管理,直接在配置类中多些几个方法,方法上添加@Bean注解即可。
4.3 引入外部配置类
如果把所有的第三方bean都配置到Spring的配置类SpringConfig
中,虽然可以,但是不利于代码阅读和分类管理,所有就想能不能按照类别将这些bean配置到不同的配置类中?
对于数据源的bean,新建一个JdbcConfig
配置类,并把数据源配置到该类下。
1 | public class JdbcConfig { |
现在的问题是,这个配置类如何能被Spring配置类加载到,并创建DataSource对象在IOC容器中?
针对这个问题,有两个解决方案:
4.3.1 使用包扫描引入(不推荐)
步骤1:在Spring的配置类上添加包扫描
1 |
|
步骤2:在JdbcConfig上添加配置注解
JdbcConfig类要放入到com.itheima.config
包下,需要被Spring的配置类扫描到即可
1 |
|
步骤3:运行程序
依然能获取到bean对象并打印控制台。
这种方式虽然能够扫描到,但是不能很快的知晓都引入了哪些配置类,所有这种方式不推荐使用。
4.3.2 使用@Import
引入(推荐)
方案一实现起来有点小复杂,Spring早就想到了这一点,于是又给提供了第二种方案。
这种方案可以不用加@Configuration
注解,但是必须在Spring配置类上使用@Import
注解手动引入需要加载的配置类
步骤1:去除JdbcConfig类上的注解
1 | public class JdbcConfig { |
步骤2:在Spring配置类中引入
1 |
|
注意:
-
扫描注解可以移除
-
@Import参数需要的是一个数组,可以引入多个配置类。
-
@Import注解在配置类中只能写一次,下面的方式是不允许的
1
2
3
4
5
6
7
//@ComponentScan("com.itheima.config")
public class SpringConfig {
}
步骤3:运行程序
依然能获取到bean对象并打印控制台
知识点1:@Bean
名称 | @Bean |
---|---|
类型 | 方法注解 |
位置 | 方法定义上方 |
作用 | 设置该方法的返回值作为spring管理的bean |
属性 | value(默认):定义bean的id |
知识点2:@Import
名称 | @Import |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 导入配置类 |
属性 | value(默认):定义导入的配置类类名, 当配置类有多个时使用数组格式一次性导入多个配置类 |
4.4 注解开发实现为第三方bean注入资源
在使用@Bean创建bean对象的时候,如果方法在创建的过程中需要其他资源该怎么办?
这些资源会有两大类,分别是简单数据类型
和引用数据类型
。
4.4.1 简单数据类型
4.4.1.1 需求分析
对于下面代码关于数据库的四要素不应该写死在代码中,应该是从properties配置文件中读取。如何来优化下面的代码?
1 | public class JdbcConfig { |
4.4.1.2 注入简单数据类型步骤
步骤1:类中提供四个属性
步骤2:使用@Value
注解引入值
1 | public class JdbcConfig { |
扩展
现在的数据库连接四要素还是写在代码中,需要做的是将这些内容提
取到jdbc.properties配置文件,思考下该如何实现?
1.resources目录下添加jdbc.properties
2.配置文件中提供四个键值对分别是数据库的四要素
3.使用@PropertySource加载jdbc.properties配置文件
4.修改@Value注解属性的值,将其修改为
${key}
,key就是键值对中的键的值
具体的实现就交由自行实现下。
4.4.2 引用数据类型
4.4.2.1 需求分析
假设在构建DataSource对象的时候,需要用到BookDao对象,该如何把BookDao对象注入进方法内让其使用呢?
1 | public class JdbcConfig { |
4.4.2.2 注入引用数据类型步骤
步骤1:在SpringConfig中扫描BookDao
扫描的目的是让Spring能管理到BookDao,也就是说要让IOC容器中有一个bookDao对象
1 |
|
步骤2:在JdbcConfig类的方法上添加参数
1 |
|
引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象。
步骤3:运行程序
5. Spring整合
学习到这里,已经对Spring有一个简单的认识了,Spring有一个容器,叫做IOC容器,里面保存bean。在进行企业级开发的时候,其实除了将自己写的类让Spring管理之外,还有一部分重要的工作就是使用第三方的技术。下面结合IOC和DI,整合2个常用技术,进一步加深对Spring的使用理解。