Spring-资源配置&技术整合
- 掌握基于注解定义
Bean
对象- 掌握注解开发模式
- 能够基于注解配置依赖注入
- 能够基于注解配置、管理第三方
Bean
- 能够基于注解为第三方
Bean
注入资源- 能够使用Spring整合MyBatis
- 能够使用Spring整合Junit
# 案例:第三方资源配置管理
前面我们学习Spring IOC容器和DI 依赖注入时,配置和管理的Bean
都是我们自己开发的,那么加入当前需要使用第三方工具来支持我们的开发工作,
就比如咱们在学习JDBC时介绍的Druid数据库连接池,或者需要使用一个从来没有接触过的工具,那该如何配置呢?
下面我们就以Druid数据库连接池为例讲解如何第三方资源的配置管理。
通过分析上面我们在JDBC章节使用Druid数据库连接池的步骤可以看出,数据库连接池对象DataSource
是关键步骤,
也是我们需要放到Spring中配置、管理的Bean
。
# 管理Druid数据库连接池
上面我们已经简单分析了使用Spring配置第三方资源的步骤,下面我们就按照步骤一步步实现Druid数据库连接池的管理。
在实际操作前,我们需要做两个工作:一是工程准备,也即是创建模块;二是数据库准备,创建测试用数据库和表。
# 工程准备
- 创建spring-09-datasource模块
注意
模块创建完成后,需要稍等几分钟,等待Maven下载远程依赖,初始化模块目录结构。
- spring-09-datasource模块初始化,补全目录结构
在main
和test
目录下分别创建resources
目录,目录补全后模块的目录结构如下:
删除main/java目录com.itheima
包下的App
类和test/java目录com.itheima
包下的AppTest
类。
- 编辑pom.xml文件,文件中只保留如下内容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring-09-datasource</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 通过Maven Projects刷新模块
# 数据库准备
不再创建新库,使用之前学习SQL语句时创建的db1库即可。
# 按步骤使用Spring配置管理Druid
导入依赖,由于Druid需要连接数据库,因此我们要引入三个依赖坐标,一个是MySQL 驱动依赖,一个是Druid依赖, 最后一个就是我们的Spring依赖,在pom.xml文件的
dependencies
标签内添加如下内容:<!-- mysql 驱动坐标 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <!-- druid 依赖坐标 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <!-- spring 依赖坐标 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
提示
- 如果事先不知道第三方工具的具体依赖坐标,可以到这个网站https://mvnrepository.com/搜索
- 修改完pom.xml文件后,记得通过Maven Projects刷新下Maven模块
- 在src/main/resources目录下创建applicationContext.xml文件
如果在Idea中没有找到Spring Config选项,也可以手动创建一个applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
2
3
4
5
6
- 配置、管理
DruidDataSource
连接池Bean
对象,请思考:DruidDataSource
中所需属性 比如driverClassName
、url
、username
、password
如何注入?setter
注入 还是 构造方法注入?
通过上面的截图,我们可以得知,DruidDataSource
中所需属性的注入只能选择setter
方式,原因是构造方法注入无法满足我们的需求。
在applicationContext.xml文件中beans
标签内添加如下代码:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db1?useSSL=false&useServerPrepStmts=true" />
<property name="username" value="root" />
<property name="password" value="1234" />
</bean>
2
3
4
5
6
- 在
com.itheima
包下创建AppForDruid
测试类
package com.itheima;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
public class AppForDruid {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 运行测试类,输出结果如下:
# 管理c3p0数据库连接池
- 引入c3p0依赖坐标
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
2
3
4
5
- 配置c3p0数据库连接池
Bean
对象
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1" />
<property name="user" value="root" />
<property name="password" value="1234" />
<property name="maxPoolSize" value="1000" />
</bean>
2
3
4
5
6
7
注意
同一个Spring 容器中不能有两个id = "dataSource"
的数据库连接池。
- 在测试类中从IOC 容器中获取连接池对象并打印
扩展
大家可以看到在用Spring管理、配置c3p0连接池时,我们只修改了pom.xml和applicationContext.xml两个文件, Java代码未做任何修改,这就是Spring的强大之处,我们可以很方便的将原来使用的Druid连接池替换为c3p0连接池,而Java代码不做 任何修改。
# 加载properties属性文件
上面我们讲解了如何使用Spring管理Druid、c3p0数据库连接池,在applicationContext.xml文件中有这么一段代码:
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db1?useSSL=false&useServerPrepStmts=true" />
<property name="username" value="root" />
<property name="password" value="1234" />
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1" />
<property name="user" value="root" />
<property name="password" value="1234" />
<property name="maxPoolSize" value="1000" />
</bean>
2
3
4
5
6
7
8
9
10
11
12
13
大家可以看到,在配置属性注入的时候,我们是在applicationContext.xml文件中写死的,在学习Druid数据库连接池使用的时候我们实际上 是定义了一个jdbc.properties配置文件,通过下面的代码来加载该配置文件:
Properties prop = new Properties();
prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
// 获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
2
3
4
那么,Spring能否实现读取配置文件中的配置项来实现属性的注入呢?如果可以,如何配置?
Spring框架的一个重要作用就是解耦,那么上面的问题它当然可以实现,我们完全可以将数据库的连接参数抽取到一个单独的文件中,使其与Spring的配置文件解耦。配置步骤如下:
# 基本用法
- 在main/resources目录下创建druid.properties属性文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=1234
2
3
4
- 在applicationContext.xml中开启
context
命名空间,并加载druid.properties属性文件
小技巧
如果觉得上述复制粘贴的方式不好改或者容易改错,可以使用Idea的提示功能进行输入,但是需要特别注意不要选错。有些版本的Idea没有提示, 那么就只能按照上面的方式进行修改了,改完之后我们可以将其做成live template模板,后面可以直接通过模板创建applicationContext.xml文件。
- 在配置Druid连接池
Bean
的地方使用EL表达式获取druid.properties属性文件中的值
经过2、3两步的修改,完整的applicationContext.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- <bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db1?useSSL=false&useServerPrepStmts=true" />
<property name="username" value="root" />
<property name="password" value="1234" />
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1" />
<property name="user" value="root" />
<property name="password" value="1234" />
<property name="maxPoolSize" value="1000" />
</bean> -->
<!-- 1. 创建druid.properties属性文件 -->
<!-- 2. 开启context命名空间 -->
<context:property-placeholder location="druid.properties" />
<!-- 3. 在配置连接池Bean的地方使用EL表达式获取属性文件中的值 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
</beans>
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
配置完成后,运行之前的获取Druid连接池代码,可以获取到连接池对象就表示配置成功。当然,我们还可以使用一种更直观的方式进行测试:
- 在模块中创建一个
com.itheima.config
包,在改包下创建DbConfig
类,代码如下
package com.itheima.config;
public class DbConfig {
private String driverClassName;
private String url;
private String username;
private String password;
public void showValue() {
System.out.println("driverClassName: " + this.driverClassName);
System.out.println("url: " + this.url);
System.out.println("username: " + this.username);
System.out.println("password: " + this.password);
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
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
35
- 修改applicationContext.xml配置文件,添加如下
bean
配置代码
<!-- DbConfig Bean -->
<bean id="dbConfig" class="com.itheima.config.DbConfig">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
2
3
4
5
6
7
- 修改
AppForDruid
测试类
package com.itheima;
import com.itheima.config.DbConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
public class AppForDruid {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// DataSource dataSource = (DataSource) ctx.getBean("dataSource");
// System.out.println(dataSource);
DbConfig dbConfig = (DbConfig) ctx.getBean("dbConfig");
dbConfig.showValue();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 运行
AppForDruid
,查看运行结果
# 配置不加载系统属性
上面基本用法中是有一些潜在问题的,比如:
如果属性文件中配置的不是jdbc.username
,而是username=root
,那么使用${username}
获取到的不是root
,而是计算机的名称。
演示效果如下图:
问题:通过上面的演示,我们可以看到,如果属性文件中的属性名和系统属性变量冲突后,通过EL表达式获取到的值为系统的属性值。
原因:系统属性的优先级比我们属性文件中的优先级高,最后替换了我们配置的username=root
。
解决方法:
- 方法一:属性文件中更换属性名,例如不再叫
username
,叫jdbc.username
,尽可能避免和系统属性冲突 - 方法二:使用
system-properties-mode="NEVER"
属性,其作用是不使用系统属性。
<!-- 加载属性文件时,不使用系统属性 -->
<context:property-placeholder location="druid.properties" system-properties-mode="NEVER" />
2
# 加载properties
文件写法
- 加载多个
properties
文件,不建议使用
<context:property-placeholder location="druid.properties,c3p0.properties" />
- 加载所有
properties
文件,不建议使用
<context:property-placeholder location="*.properties" />
- 加载所有
properties
文件,不包括依赖包中的properties
文件,标准格式,建议使用
<context:property-placeholder location="classpath:*.properties"/>
- 加载所有
properties
文件,包括依赖包中的properties
文件,标准格式,建议使用
<context:property-placeholder location="classpath*:*.properties"/>
# Spring容器
本小节我们主要的目的是总结和补充我们之前学习的知识:
按照工程准备章节步骤,创建模块spring-10-container,并初始化目录结构和相关配置文件。
# Spring核心容器介绍
# 创建容器
创建容器有两种方式:
方式一:通过类路径加载配置文件,即是从项目的classes目录下的配置文件,常用方式
// 1. 通过类路径加载配置文件 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml")
1
2方式二:通过文件路径加载配置文件,以文件系统绝对路径读取配置文件
// 2. 通过文件路径加载配置文件 ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\\itcast\\Source\\itcast\\Frame\\spring-10-container\\src\\main\\resources\\applicationContext.xml");
1
2注意
如果通过
FileSystemXmlApplicationContext
加载配置文件,需要输入配置文件的绝对路径,如果通过输入的绝对路径无法找到配置文件,会报错。
如何加载多个配置文件?
// 3. 加载多个配置文件
ApplicationContext ctx3 = new ClassPathXmlApplicationContext("applicationContext.xml", "beans.xml");
2
# 创建容器演示代码
- 创建
com.itheima.dao
和com.itheima.dao.impl
包,并在对应包下创建BookDao
、UserDao
接口和BookDaoImpl
、UserDaoImpl
实现类
package com.itheima.dao;
public interface BookDao {
void save();
}
2
3
4
5
package com.itheima.dao;
public interface UserDao {
void save();
}
2
3
4
5
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl Save ...");
}
}
2
3
4
5
6
7
8
9
10
11
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl Save ...");
}
}
2
3
4
5
6
7
8
9
10
在main/resources目录下创建两个配置文件applicationContext.xml和beans.xml
- applicationContext.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" 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>
1
2
3
4
5
6- beans.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" /> </beans>
1
2
3
4
5
6在
com.itheima
包下创建AppForContainer
测试类
package com.itheima;
import com.itheima.dao.BookDao;
import com.itheima.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class AppForContainer {
public static void main(String[] args) {
// 1. 通过类路径加载配置文件
// ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 通过文件路径加载配置文件
// ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\itcast\Source\itcast\Frame\spring-10-container\src\main\resources\applicationContext.xml");
// ApplicationContext ctx2 = new FileSystemXmlApplicationContext("applicationContext.xml");
// 3. 加载多个配置文件
ApplicationContext ctx3 = new ClassPathXmlApplicationContext("applicationContext.xml", "beans.xml");
BookDao bookDao = (BookDao) ctx3.getBean("bookDao");
bookDao.save();
UserDao userDao = (UserDao) ctx3.getBean("userDao");
userDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 运行
AppForContainer
测试类,查看结果
# 获取bean对象
获取Bean
对象有三种方式:
- 方式一:使用
bean
名称获取,缺点是需要强制类型转换
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
- 方式二:使用
bean
名称获取并指定bean
类型,推荐
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
- 方式三:使用
bean
类型获取,缺点是如果IOC 容器中同类型的Bean
对象有多个,会报错
BookDao bookDao = ctx.getBean(BookDao.class);
# 获取bean对象演示代码
- 在
com.itheima
包下创建AppForBean
测试类
package com.itheima;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForBean {
public static void main(String[] args) {
// 创建容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 方式一:通过bean名称获取,需要强制类型转换
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
// 方式二:通过bean名称获取,并指定bean类型
BookDao bookDao1 = ctx.getBean("bookDao", BookDao.class);
bookDao1.save();
// 方式三:通过bean类型获取
BookDao bookDao3 = ctx.getBean(BookDao.class);
bookDao3.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 运行测试类,查看结果
通过类型获取`bean`对象,需要保证容器中同一类型的`bean`只有一个,否则会报错
比如,将applicationContext.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"
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" />
<bean id="bookDao2" class="com.itheima.dao.impl.BookDaoImpl" />
</beans>
2
3
4
5
6
7
再次运行AppForBean
测试类,会看到下面的错误:
# Spring容器类层次结构及设计思想
在开始学习Spring框架前,我们介绍了如何学习Spring?
- 学习基础操作
- 学习Spring框架设计思想
- 学习基础操作和设计思想之间的练习
下面我们就通过学习Spring容器类的层次接口来学习一种设计思想Interface分级设计思想。
接口的分级设计,是针对不同的客户给予不同界别的产品。不同等级的接口关注点也各有不同,就像产品也分为多个级别, 如高级产品、中级产品、初级产品分别对应于不同的客户群,或者如同银行的客户渠道有银行卡、柜台、网银、手机银行、ATM机等等。 同样接口也分为内部接口和外部接口,就如同有些接口可能永远不被外部用户所知,仅仅是内部使用,例如工厂内部多个车间之间的内部产品。
org.springframework.beans.factory.BeanFactory
似乎Spring的顶层接口,
其中只有getBean
系列方法,containsBean
、isPrototype
、isSingleton
、getType
等成品Bean
的基本方法。
org.springframework.beans.factory.ListableBeanFactory
是Factory
的管理方法如containsBeanDefinition
、
containsBeanDefinition
、getBeanDefinitionCount
、getBeanDefinitionNames
、getBeanNamesForType
系列、
getBeansOfType
系列,ListableBeanFactory
是针对BeanDefinition
封装的半成品接口。
ApplicationContext
接口除继承ListableBeanFactory
接口外,还继承了ResourcePatternResolver
、HierarchicalBeanFactory
、
ApplicationEventPublisher
、MessageSource
等接口的功能,如在资源处理(国际化处理)、事件传递、及各应用层之间的context实现。
同样是对外的接口,Spring更建议采用ApplicationContext
的原因就在这里,ApplicationContext
更像是针对VIP客户的产品。
Spring的Interface扩展设计是开放性的设计思路,是值得我们开发者学习的,总结如下:
- 不同的包下面放不同的东西,这句话好想是废话,但放到实际开发场景中就很有用,比如在Spring中,上层包放接口,下层包放实现的抽象类及实体类,
如
context.support
包下面放的就是实现context
包下面的接口的抽象类及实现类。 - 为了适应WEB级别的
ApplicationContext
,创建了WebApplicationContext
的接口,该接口继承了ApplicationContext
, 又添加了WEB层的个性方法如getServletContext
及属性。相信到此,大家应该明白了接口间extends的含义,绝对是对上层接口的扩展, 扩展的目的是适应更具体化的环境。就类似于我们国家大,情况复制,上层以及中间的政策相对都是比较抽象的,而地方政策必须结合地方的特色, 既要满足上面政策的要求,即实现上层政策的接口,又要根据本地特殊情况,扩展个性化的政策,即地方政策接口,从而更具有可操作性。 - 在Spring中
org.springframework.web.context.support.AbstractRefreshableWebApplicationContext
类是 既实现了ConfigrableWebApplicationContext
接口, 即实现WEB的个性化特色,又继承了org.springframework.context.support.AbstractRefreshableApplicationContext
类。 这样就借助了已有的类,实现了架构上的复用,有点像制定政策既要满足地方上的特色,又可以参照其他地区现成的经验。 在实际工作中,也会常常有这样的设计。
# BeanFactory
上面我们了解了Spring的Interface分级设计思想,那么BeanFactory
这一上层接口该如何使用呢?以及它与ApplicationContext
的区别是什么?
日常开发中我们不会使用BeanFactory
这一上层接口,下面就来简单学习一种通过类路径加载配置文件获取BeanFactory
的方式:
// 通过Spring的Resource资源加载工具加载Spring核心配置文件
Resource resourse = new ClassPathResource("applicationContext.xml");
// 通过获取到的资源创建BeanFactory
// 注意:XmlBeanFactory已经被标记为弃用了,日常工作中不建议使用
BeanFactory beanFactory = new XmlBeanFactory(resourse);
BookDao bookDao = beanFactory.getBean("bookDao", BookDao.class);
bookDao.save();
2
3
4
5
6
7
提示
BeanFactory
创建完毕后,所有的Bean
均为延迟加载,也就是说我们调用getBean()
方法获取Bean
对象时才创建Bean
对象并返回给我们。
下面我们就通过代码来演示一下BeanFactory
的简单使用和延迟加载:
- 修改
BookDaoImpl
实现类,为其添加无参构造方法
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("BookDaoImpl 加载了 ...");
}
@Override
public void save() {
System.out.println("BookDaoImpl Save ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 在
com.itheima
包下创建AppForBeanFactory
测试类
package com.itheima;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class AppForBeanFactory {
public static void main(String[] args) throws InterruptedException {
// 通过Spring的Resource资源加载工具加载Spring核心配置文件
Resource resourse = new ClassPathResource("applicationContext.xml");
// 通过获取到的资源创建BeanFactory
// 注意:XmlBeanFactory已经被标记为弃用了,日常工作中不建议使用
BeanFactory beanFactory = new XmlBeanFactory(resourse);
Thread.sleep(10000);
System.out.println("此处休眠了10秒");
BookDao bookDao = beanFactory.getBean("bookDao", BookDao.class);
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 修改
AppForContainer
测试类
package com.itheima;
import com.itheima.dao.BookDao;
import com.itheima.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class AppForContainer {
public static void main(String[] args) throws InterruptedException {
// 1. 通过类路径加载配置文件
// ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 通过文件路径加载配置文件
// ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\itcast\Source\itcast\Frame\spring-10-container\src\main\resources\applicationContext.xml");
// ApplicationContext ctx2 = new FileSystemXmlApplicationContext("applicationContext.xml");
// 3. 加载多个配置文件
ApplicationContext ctx3 = new ClassPathXmlApplicationContext("applicationContext.xml", "beans.xml");
Thread.sleep(10000);
System.out.println("此处休眠了10秒");
BookDao bookDao = (BookDao) ctx3.getBean("bookDao");
bookDao.save();
UserDao userDao = (UserDao) ctx3.getBean("userDao");
userDao.save();
}
}
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
- 分别运行
AppForBeanFactory
和AppForContainer
测试类
扩展-ApplicationContext也可以实现延迟加载
修改applicationContext.xml文件,在bookDao
的bean
配置的里面添加lazy-init="true"
属性
<?xml version="1.0" encoding="UTF-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" lazy-init="true" />
<bean id="bookDao2" class="com.itheima.dao.impl.BookDaoImpl" />
</beans>
2
3
4
5
6
7
再次运行AppForContainer
测试类:
# Spring核心容器总结
# 容器相关
BeanFactory
是IOC容器的顶层接口,初始化BeanFactory
对象时,加载的bean
延迟加载ApplicationContext
接口是Spring容器的核心接口,初始化时bean
立即加载ApplicationContext
接口提供基础的bean
操作相关方法,通过其他接口扩展其功能ApplicationContext
接口常用初始化类- ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
# Bean相关
# 依赖注入相关
# Spring注解开发
从之前我们学习Spring的示例代码来看,使用XML配置、管理Bean
还是比较繁琐的,既然Spring一个简化开发的框架,就肯定有一种方式可以解决这种问题,它就是基于注解开发。
下面我们来看下Spring框架基于注解开发的发展历程:
- Spring 1.x注解驱动启蒙时代
此时,Java5刚发布,开始支持Annotation
,Spring 1.2提供了@Transactional
和@ManagedResource
注解,
但是此时Bean
的装载还是通过XML配置文件的方式
- Spring 2.x注解驱动过渡时期
Spring 2.0开始支持@Required
、@Repository
、@Aspect
等注解,同时也提升了XML的配置能力,可扩展XML文件的标签;
Spring 2.5提供了非常重要的注解@Autowired
、@Qualifier
、@Component
、@Service
、@Controller
等注解,
此时Spring还没有完全丢弃XML,还需要在XML中配置。
- Spring 3.x注解驱动黄金时代
Spring 3.x是一个里程碑的时代,提供了@Configuration
注解,可以完全去XML化配置,
@ComponentScan(basePackages="")
配置扫描的路径,@Import
导入其他配置类进行装载,@EnableXXX
可以模块化的装载。
- Spring 4.x注解驱动完善时代
Spring 4.x提供了@Conditional
注解,可以自定义条件来决定Bean
是否注册到容器中。
- Spring 5.x注解驱动成熟时代
Spring 5.x支持了@Indexed
注解,可以大大提升ComponentScan
扫描的性能。
下面我们就来逐步学习下Spring中各个注解的使用,依旧按照工程准备章节步骤,创建模块spring-11-annotation-bean, 并初始化目录结构和相关配置文件。
# 注解开发定义Bean对象
在讲解基于注解定义Bean
对象前,我们先把基于XML定义Bean
对象的代码给实现了:
- 在pom.xml文件中引入Spring依赖坐标
<!-- spring 依赖坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2
3
4
5
6
- 创建
com.itheima.dao
和com.itheima.dao.impl
包,并在对应包下创建BookDao
接口和BookDaoImpl
实现类
package com.itheima.dao;
public interface BookDao {
void save();
}
2
3
4
5
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("BookDaoImpl 加载了 ...");
}
@Override
public void save() {
System.out.println("BookDaoImpl Save ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 编辑applicationContext.xml文件,基于XML定义
bookDao
名的bean
对象
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" />
- 在
com.itheima
包下创建AppForXml
测试类
package com.itheima;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForXml {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- 运行
AppForXml
测试类,查看结果
上面是一个基于XML定义Bean
对象的简单实现,下面我们来分析下,基于XML定义Bean
对象有几部分内容:
那么,基于注解定义Bean
对象也应该包括这几部分内容,下面我们就按照下面的步骤来进行注解开发的快速入门
- 修改applicationContext.xml文件,引入
context
命名空间,并开启Sprng注解包扫描功能
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--
扫描com.itheima.dao.impl包及其子包下的所有类中的注解,
因此,此处建议写模块的顶层包名,比如com.itheima
-->
<!--<context:component-scan base-package="com.itheima.dao.impl" />-->
<context:component-scan base-package="com.itheima" />
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 在
BookDaoImpl
实现类上使用@Component
注解定义Bean
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.stereotype.Component;
@Component
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("BookDaoImpl 加载了 ...");
}
@Override
public void save() {
System.out.println("BookDaoImpl Save ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
补充说明
如果@Component
注解没有使用参数指定Bean
的名称,那么类名首字母小写就是Bean
在IOC容器中的默认名称。例如:BookServiceImpl
对象在IOC容器中的名称是bookServiceImpl
。
- 在
com.itheima
包下创建AppForAnno
测试类
package com.itheima;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppForAnno {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 如果@Component没有指定Bean名称,那么bean在IOC容器中的默认名是bookDaoImpl,此处通过bookDao获取bean对象会报错
// org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'bookDao' available
// BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
// 如果@Component没有指定bean名称,那么获取bean时可以通过默认名或者类型获取
// 默认名获取
// BookDao bookDao = ctx.getBean("bookDaoImpl", BookDao.class);
// 类型获取
BookDao bookDao = ctx.getBean(BookDao.class);
// 当然也可以通过@Component("bookDao")来指定bean名称
// BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component三个衍生注解
Spring为@Component
注解提供了三个衍生注解:
@Controller
: 用于表现层bean
定义@Service
: 用于业务层bean
定义@Repository
: 用于数据层bean
定义
# 纯注解开发模式
上衣章节学习的基于注解定义Bean
对象,还是没有完全摆脱XML文件的使用,比如applicationContext.xml中的内容
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--
扫描com.itheima.dao.impl包及其子包下的所有类中的注解,
因此,此处建议写模块的顶层包名,比如com.itheima
-->
<!--<context:component-scan base-package="com.itheima.dao.impl" />-->
<context:component-scan base-package="com.itheima" />
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
前面介绍Spring注解开发发展历程的时候说过,从Spring 3.0开始,已经支持纯注解开发模式了,那么如何操作呢?根本思路就是,分析applicationContext.xml文件完成的工作,使用Java代码替换XML文件。
# 纯注解开发模式介绍
- Spring 3.0开启了纯注解开发模式,使用Java类替代XML配置文件,开启了Spring快速开发模式
- Java类代替Spring核心配置文件
@Configuration
注解用于设定当前类为配置类@ComponentScan
注解用于设定扫描路径,此注解只能添加一次,多个数据使用数组格式
@ComponentScan({"com.itheima.service", "com.itheima.dao"})
- 读取Spring核心配置文件初始化容器切换为读取Java配置类初始化容器对象
// 加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
2
3
4
# 纯注解开发代码演示
依旧按照工程准备章节步骤,创建模块spring-11-annotation-develop,并初始化目录结构和相关配置文件,并在pom.xml文件 中引入Spring的依赖坐标,注意main/resources目录下的applicationContext.xml文件就不需要创建了,因为我们要基于纯注解开发。
- 定义配置类代替配置文件,创建
com.itheima.config
包,并在该包下创建SpringConfig
配置类
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
// 声明当前类为Spring配置类
@Configuration
// Spring扫描bean注解,相当于<context:component-scan base-package="com.itheima" />
@ComponentScan("com.itheima")
// @ComponentScan用于设置bean扫描路径,多个路径可书写为字符串数组格式
// @ComponentScan({"com.itheima.service", "com.itheima.dao"})
public class SpringConfig {
}
2
3
4
5
6
7
8
9
10
11
12
13
将spring-11-annotation-bean模块
com.itheima.dao
包拷贝到spring-11-annotation-develop模块在
com.itheima
包下创建AppForAnno
测试类,在该类中通过加载配置类初始化容器对象,通过容器获取Bean
对象并使用
package com.itheima;
import com.itheima.config.SpringConfig;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppForAnno {
public static void main(String[] args) {
// AnnotationConfigApplicationContext加载Spring配置类初始化Spring容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 通过类型获取Bean对象
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 运行结果
# 注解开发Bean作用范围和生命周期管理
# Bean作用范围注解配置
可以在类上使用@Scope
注解定义bean
的作用范围,如
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
// @Component("bookDao")
// @Scope定义bean作用范围:singleton-单例 prototype-非单例
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("BookDaoImpl 加载了 ...");
}
@Override
public void save() {
System.out.println("BookDaoImpl Save ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Bean生命周期注解配置
可以使用@PostConstruct
、@PreDestroy
定义bean
的生命周期控制方法。
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
// @Component("bookDao")
// @Scope定义bean作用范围:singleton-单例 prototype-非单例
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("BookDaoImpl 加载了 ...");
}
@Override
public void save() {
System.out.println("BookDaoImpl Save ...");
}
@PostConstruct
public void init() {
System.out.println("BookDaoImpl init ...");
}
@PreDestroy
public void destroy() {
System.out.println("BookDaoImpl destroy ...");
}
}
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
注意
和
注解是JDK中提供的注解,但是从JDK9开始,JDK中的javax.annotation
包被移除了,
也就是说JDK9及之后版本的JDK要想使用这两个注解,需要额外引入下面的这个依赖坐标:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
2
3
4
5
# 注解开发依赖注入
基于注解开发依赖注入有两种类型:
- 引用类型,通过
Autowired
、@Qualifier
注解 - 简单类型,通过
@Value
、@PropertySource
注解
依旧按照工程准备章节步骤,创建模块spring-13-annotation-di,并初始化目录结构和相关配置文件,并在pom.xml文件引入 Spring依赖坐标。
# 使用@Autowired
注解开启自动装配模式(按类型)
- 创建
com.itheima.dao
和com.itheima.dao.impl
包,并在对应包下创建BookDao
接口和BookDaoImpl
实现类
package com.itheima.dao;
public interface BookDao {
void save();
}
2
3
4
5
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl save ... ");
}
}
2
3
4
5
6
7
8
9
10
11
12
- 创建
com.itheima.service
和com.itheima.service.impl
包,并在对应包下创建BookService
接口和BookServiceImpl
实现类
package com.itheima.service;
public interface BookService {
void save();
}
2
3
4
5
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
// @Autowired: 注入引用类型,自动装配模式,默认按类型装配
@Autowired
private BookDao bookDao;
@Override
public void save() {
System.out.println("BookServiceImpl save ... ");
bookDao.save();
}
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 创建
com.itheima.config
包,并在包下创建SpringConfig
配置类
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
2
3
4
5
6
7
8
9
10
- 在
com.itheima
包下创建AppForAnno
测试类
package com.itheima;
import com.itheima.config.SpringConfig;
import com.itheima.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppForAnno {
public static void main(String[] args) {
// 使用配置类初始化Spring容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 根据类型获取Bean对象
BookService bookService = ctx.getBean(BookService.class);
bookService.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 运行
AppForAnno
测试类,查看结果
注意
自动装配基于反射创建对象并通过暴力反射的方式为私有属性初始化数据,因此无需提供setter
方法。
说明
无论是使用配置文件还是配置类,都必须进行对应的Spring注解@ComponentScan
才可以使用,@Autowired
默认按照类型自动匹配,
如果IOC容器中同类的Bean
有多个,那么使用默认的按类型匹配就会报错。
如果要解决上面的问题,就需要使用为每一种实现起名字,并使用@Qualifier
配合@Autowired
指定要装配的bean
名称。
# 使用@Qualifier
注解指定要装配的bean
名称
目的:解决上面IOC容器中同类型的
Bean
有多个,自动装配时装配哪一个的问题。
- 在
com.itheima.dao.impl
包下创建BookDaoImpl2
实现类
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.stereotype.Repository;
// 给bean起名字
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl2 Save ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- 修改
BookDaoImpl
实现类
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.stereotype.Repository;
// 给bean起名字
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl save ... ");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
扩展
此时如果再次运行AppForAnno
测试类,会发现不再报错,但是我们并没有修改BookServiceImpl
针对BookDao
自动装配的代码,原因是当IOC容器中有两个同类型bean
时,自动装配时会按照setter
注入的方式去查找容器中是否有和属性名相匹配的bean
对象。
- 修改
BookServiceImpl
实现类代码
package com.itheima.service.impl;
import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
// @Autowired: 注入引用类型,自动装配模式,默认按类型装配
@Autowired
// @Qualifier: 自动装配bean时按bean名称装配
@Qualifier("bookDao2")
private BookDao bookDao;
@Override
public void save() {
System.out.println("BookServiceImpl save ... ");
bookDao.save();
}
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
}
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
- 运行测试类,查看结果
注意
@Qualifier
注解无法单独使用,必须配合@Autowired
注解使用。
# 使用@Value
实现简单类型注入
@Value
一般用于简单类型的注入,常见场景是:从属性配置文件中读取属性值,注入到Bean
对象中。下面来学习下如何操作:
- 在main/resources目录下创建jdbc.properties属性配置文件
jdbc.username=root
- 修改
SpringConfig
配置类
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.itheima")
// @PropertySource: 从类路径下加载属性配置文件,classpath:可以省略
// 如果要加载多个配置文件,可以写成字符数组的形式
//@PropertySource({"jdbc.properties", "druid.properties"})
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 修改
BookDaoImpl2
实现类
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
// 给bean起名字
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao {
// @Value: 注入简单类型,无需提供setter方法
// 使用${jdbc.username}从属性配置文件中读取数据
@Value("${jdbc.username}")
private String name;
@Override
public void save() {
System.out.println("BookDaoImpl2 Save ...");
System.out.println("BookDaoImpl2 username = " + this.name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 再次运行
AppForAnno
测试类,查看结果
注意
@PropertySource()
中加载多个属性配置文件需使用字符数组格式配置,和XML配置不同的是,不允许使用通配符(*)。
# 注解开发管理第三方Bean
(重点)
在管理druid数据库连接池章节,我们使用XML配置,集成了Druid数据库连接池,那么基于注解如何来管理Druid数据库连接池呢?我们可以参考使用XML配置文件实现的思路:
- 引入Spring、Druid、MySQL驱动依赖坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring-14-third-bean</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- mysql 驱动坐标 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- druid 依赖坐标 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- spring 依赖坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
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
35
36
37
38
- 创建
com.itheima.config
包,并在该包下创建SpringConfig
配置类
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
// @Bean: 表示当前方法的返回值是一个Bean对象,添加到IOC容器中
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://127.0.0.1:3306/db1");
ds.setUsername("root");
ds.setPassword("1234");
return ds;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在
com.itheima
包下创建AppForThird
测试类
package com.itheima;
import com.itheima.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
public class AppForThird {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = ctx.getBean(DataSource.class);
System.out.println(dataSource);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意
上面的代码虽然可以正常获取数据库连接池对象,但是存在一个问题。
大家可以看到在SpringConfig
配置类中有一些内容是硬编码到我们的Java代码中的,这块需要优化一下。
# 注解开发为第三方Bean注入资源
上一章节我们发现了一个问题,SpringConfig
配置类中存在硬编码,针对SpringConfig
代码进行分析,
里面硬编码的内容实际上是Druid数据库连接池对象所需要的属性,也即是第三方Bean
所需要的属性,
那么我们就需要想办法通过Spring将这些属性注入Druid数据库连接池Bean
中,下面我们就来逐步实现它。
- 在main/resources目录下创建druid.properties配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/db1
jdbc.username=root
jdbc.password=1234
2
3
4
- 修改
SpringConfig
,加载属性配置文件
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
@Configuration
@PropertySource("druid.properties")
public class SpringConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
// @Bean: 表示当前方法的返回值是一个Bean对象,添加到IOC容器中
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(this.driverClassName);
ds.setUrl(this.url);
ds.setUsername(this.username);
ds.setPassword(this.password);
return ds;
}
}
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
35
36
37
38
- 再次运行测试类,查看效果
问题
上面代码确实实现了功能,加载属性配置文件,将属性值注入到Druid数据库连接池Bean
对象中。虽实现了功能,
但在代码规范、可读性方面还有优化的空间,比如:SpringConfig
配置类,我们当前是将Druid数据库连接池Bean
的管理放到这个类当中的,
假设后面要集成MyBatis、Redis、MQ等等一些列技术,难道都写到这个类中?这个类的代码量会变得相当庞大,因此我们就要进行配置类的拆分。
- 在
com.itheima.config
包下创建DruidConfig
配置类,并将SpringConfig
类中的代码剪切过去
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class DruidConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
// @Bean: 表示当前方法的返回值是一个Bean对象,添加到IOC容器中
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(this.driverClassName);
ds.setUrl(this.url);
ds.setUsername(this.username);
ds.setPassword(this.password);
return ds;
}
}
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
此时我们运行测试类会发现报错,原因是虽然做了配置文件的拆分,但是DruidConfig
并没有交由Spring IOC 容器管理,因此找不到Druid
数据库连接池Bean
对象。
怎么解决上面的问题呢?两种方式:
方式一:为
DruidConfig
类加上@Configuration
和@PropertySource("druid.properties")
注解,并在SpringConfig
类上加上@ComponentScan("com.itheima")
注解package com.itheima.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration // 只要Spring能扫描到com.itheima.config包就行 @ComponentScan("com.itheima") public class SpringConfig { }
1
2
3
4
5
6
7
8
9
10
11
12package com.itheima.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import javax.sql.DataSource; @Configuration @PropertySource("druid.properties") public class DruidConfig { @Value("${jdbc.driver}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; // @Bean: 表示当前方法的返回值是一个Bean对象,添加到IOC容器中 @Bean public DataSource dataSource() { DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(this.driverClassName); ds.setUrl(this.url); ds.setUsername(this.username); ds.setPassword(this.password); return ds; } }
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
35
36
37
38方式二:为
DruidConfig
类加上@PropertySource("druid.properties")
注解,在SpringConfig
类上加上@Import(DruidConfig.class)
注解package com.itheima.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration // 方式一 // 只要Spring能扫描到com.itheima.config包就行 // @ComponentScan("com.itheima") // 方式二 @Import(DruidConfig.class) public class SpringConfig { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.itheima.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import javax.sql.DataSource; // 方式一 // @Configuration // @PropertySource("druid.properties") // 方式二 @PropertySource("druid.properties") public class DruidConfig { @Value("${jdbc.driver}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; // @Bean: 表示当前方法的返回值是一个Bean对象,添加到IOC容器中 @Bean public DataSource dataSource() { DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(this.driverClassName); ds.setUrl(this.url); ds.setUsername(this.username); ds.setPassword(this.password); return ds; } }
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
35
36
37
38
39
40
41
建议使用方式二
注意
上面我们向Druid数据库连接池Bean
对象中注入的都是简单类型,假如现在Druid数据库连接池Bean
需要一个引用类型该怎么办?
在模块中创建com.itheima.dao
和com.itheima.dao.impl
包,并在对应包下创建BookDao
接口和BookDaoImpl
实现类
package com.itheima.dao;
public interface BookDao {
void save();
}
2
3
4
5
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl Save ...");
}
}
2
3
4
5
6
7
8
9
10
11
12
假设Druid数据库连接池就需要一个BookDao
类型的Bean
,如何操作?
实际上,很简单,只需要修改两个地方:
- 修改
DruidConfig
类中的dataSource()
方法,给dataSource()
这个方法设置一个形参; - 修改
SpringConfig
类,在类上添加@ComponentScan("com.itheima")
注解
// @Bean: 表示当前方法的返回值是一个Bean对象,添加到IOC容器中
@Bean
public DataSource dataSource(BookDao bookDao) {
bookDao.save();
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(this.driverClassName);
ds.setUrl(this.url);
ds.setUsername(this.username);
ds.setPassword(this.password);
return ds;
}
2
3
4
5
6
7
8
9
10
11
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
// 方式一
// 只要Spring能扫描到com.itheima.config包就行
// @ComponentScan("com.itheima")
// 方式二
@Import(DruidConfig.class)
@ComponentScan("com.itheima")
public class SpringConfig {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Spring会自动从IOC容器中找到BookDao
对象赋值给参数bookDao
变量,如果没有就会报错。
# 注解开发总结
# Spring整合其他技术
# Spring整合MyBatis(重点)
# Spring整合MyBatis思路分析
- MyBatis程序核心对象分析
需要确定MyBatis核心对象有哪些?SqlSessionFactory
、SqlSession
、UserMapper
?
SqlSessionFactory
是需要交由Spring管理的核心对象。
整合MyBatis
- 使用
SqlSessionFactoryBean
封装SqlSessionFactory
需要的环境信息
- 使用
MapperScannerConfigurer
加载Dao/Mapper接口,创建代理对象保存到IOC容器中
- 使用
# Spring整合MyBatis代码实现
依旧按照工程准备章节步骤,创建模块spring-15-mybatis,并初始化目录结构和相关配置文件,并在pom.xml文件。
- 在pom.xml文件中添加spring-context、druid、mybatis、mysql-connector-java等基础依赖
- 在main/resources目录下创建druid.properties属性配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/db1
jdbc.username=root
jdbc.password=1234
2
3
4
- 创建
com.itheima.domain
包,并在该包下创建User
类
package com.itheima.domain;
public class User {
private Integer id;
private String username;
private String password;
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
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
35
36
37
38
39
40
41
42
43
44
45
46
- 创建
com.itheima.mapper
包,并在该包下创建UserMapper
接口
package com.itheima.mapper;
import com.itheima.domain.User;
import java.util.List;
public interface UserMapper {
List<User> selectAll();
}
2
3
4
5
6
7
8
9
- 在main/resources目录下创建
com/itheima/mapper
目录,并在该目录下创建UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.UserMapper">
<resultMap id="UserMap" type="com.itheima.domain.User">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
</resultMap>
<select id="selectAll" resultMap="UserMap">
select id, username, password from tb_user
</select>
</mapper>
2
3
4
5
6
7
8
9
10
11
12
- 创建
com.itheima.service
和com.itheima.service.impl
包,并在对应包下创建UserService
接口和UserServiceImpl
实现类
package com.itheima.service;
import com.itheima.domain.User;
import java.util.List;
public interface UserService {
List<User> findAll();
}
2
3
4
5
6
7
8
9
package com.itheima.service.impl;
import com.itheima.domain.User;
import com.itheima.mapper.UserMapper;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.selectAll();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 导入Spring整合MyBatis依赖坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>spring-15-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- mysql 驱动坐标 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- druid 依赖坐标 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- spring 依赖坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- MyBatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- Spring整合MyBatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
- 创建
com.itheima.config
包,并在该包下创建SpringConfig
、DruidConfig
、MyBatisConfig
等配置类
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com.itheima")
@Import({DruidConfig.class, MyBatisConfig.class})
public class SpringConfig {
}
2
3
4
5
6
7
8
9
10
11
12
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
@PropertySource("druid.properties")
public class DruidConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(this.driver);
ds.setUrl(this.url);
ds.setUsername(this.username);
ds.setPassword(this.password);
return ds;
}
}
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
35
package com.itheima.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.itheima.domain");
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.itheima.mapper");
return mapperScannerConfigurer;
}
}
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
- 在
com.itheima
包下创建AppForMyBatis
测试类,运行并查看结果
package com.itheima;
import com.itheima.config.SpringConfig;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
public class AppForMyBatis {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = ctx.getBean(UserService.class);
List<User> all = userService.findAll();
System.out.println(all);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Spring整合Junit单元测试
- 第1步:导入Spring整合Junit所需依赖坐标junit、spring-test
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!-- spring 整合junit -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
第2步:使用Spring整合Junit专用的类加载器
第3步:加载配置文件或者配置类
package com.itheima.service;
import com.itheima.config.SpringConfig;
import com.itheima.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
// 第2步:使用Spring整合Junit专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
// 第3步:加载配置文件或者配置类
@ContextConfiguration(classes = {SpringConfig.class}) // 加载配置类
// @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) // 加载配置文件
public class UserServiceTest {
// 支持自动装配注入
@Autowired
private UserService userService;
@Test
public void testFindAll() {
List<User> all = userService.findAll();
System.out.println(all);
}
}
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
注意
junit的依赖至少要是4.12版本,也可以是比4.12更高的版本,否则会出现下面的异常。