简介
Spring家族
Spring的作用:
- 简化开发
- 简化Java EE企业级开发的复杂度
- 框架整合
- 可以高效的整合其他技术,提高企业级应用开发、运行效率
Spring并不是单一的一个技术,而是一个大家族,Spring官方提供很多开源的项目
Spring发展到今天已经形成了一种开发生态圈,Spring提供了若干个子项目,每个项目用于完成特定的功能。
在项目开发时,一般会偏向于选择这一套spring家族的技术,来解决对应领域的问题,称这一套技术为spring全家桶。
Spring家族中常用的有:
-
Spring Framework
-
是Spring家族中最基础、最核心的技术,其他的spring家族的技术,都是基于SpringFramework的,SpringFramework中提供很多实用功能,如:依赖注入、事务管理、web开发支持、数据访问、消息服务等等。
-
-
Spring Boot
- 在Spring简化开发的基础上,快速开发
-
Spring Cloud
- 用来做分布式微服务架构的相关开发。
Spring发展史
- IBM在1997年提出了EJB思想,早期JAVAEE开发的主流思想。
- Rod Johnson在2002年出版的
Expert One-on-One J2EE Design and Development
,书中有阐述在开发中使用EJB该如何做。 - Rod Johnson在2004年出版的
Expert One-on-One J2EE Development without EJB
,书中提出了比EJB思想更高效的实现方案,并且在同年将方案进行了具体的落地实现,这个实现就是Spring1.0。
- Spring1.0是纯配置文件开发。
- Spring2.0为了简化开发引入了注解开发,此时是配置文件加注解的开发方式。
- Spring3.0可以使用纯注解开发。
- Spring4.0根据JDK的版本升级对个别API进行了调整。
- Spring5.0全面支持JDK8。
SpringFramework系统架构
Spring Framework的发展经历了很多版本的变更,每个版本都有相应的调整
Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,所以接下来主要研究的是4的架构图
核心层
- Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
AOP层
- AOP:面向切面编程,它依赖于核心层容器,可以在不改变原代码的前提下对其进行功能增强
- Aspects:AOP是思想,Aspects是对AOP思想的具体实现
数据层
- Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
- Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
- Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现
Web层
- Spring中用于Web开发的模块,即SpringMVC
Test层
- Spring整合了Junit来完成单元测试和集成测试
核心概念
Spring的核心概念主要包含:
- IOC/DI
- IOC容器
- Bean
目前项目中的问题
业务层需要调用数据层的方法,就需要在业务层new数据层的对象。
如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署
所以,现在代码在编写的过程中存在的问题是:耦合度偏高。
内聚:软件中各个功能模块内部的功能联系。
耦合:软件中各个层/模块之间的依赖、关联的程度。
但是软件设计原则是:高内聚低耦合。
高内聚指的是:
- 一个模块中各个元素之间的联系的紧密程度越高越好。
低耦合指的是:
- 软件中各个层、模块之间的依赖关联程序越低越好。
程序中高内聚的体现:
- 每个类中都只写与该类有关的代码,实现单一职责
程序中高耦合的体现:
- 在一个类中需要使用另一个类时,直接通过new关键字来创建一个对象
高内聚、低耦合的目的是:
- 使程序模块的可重用性、移植性大大增强。
那么针对这个问题,该如何解决呢?
如果能把new对象的部分给去掉,不就可以降低耦合了么?
但是又会引入新的问题,去掉以后程序能运行么?
答案肯定是不行,因为bookDao没有赋值为Null,强行运行就会出空指针异常。
所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该咋办呢?
针对这个问题,其实在Spring出现之前就有人提出了一个解决方案,即IOC思想:
- 使用对象时,在程序中不要主动使用new产生对象,而是由外部提供对象
IOC(Inversion of Control)控制反转
什么是控制反转呢?
- 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部。
- 业务层要用数据层的类对象,以前是自己
new
的,现在自己不new了,交给外部
来创建对象,外部
就反转控制了数据层对象的创建权。
- 业务层要用数据层的类对象,以前是自己
Spring和IOC之间的关系是什么呢?
- Spring技术对IOC思想进行了技术实现。
- Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的
外部
。
IOC容器的作用以及内部存放的是什么?
- IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的对象,被创建或被管理的对象在IOC容器中统称为Bean。
- IOC容器中存放的就是一个个的Bean对象。
在Spring中主要使用IOC容器管理什么对象?
- 主要管理项目中所使用到的类对象,比如(service和dao)。
当IOC容器中创建好service和dao对象后,程序能正确执行么?
- 不行,因为service运行需要依赖dao对象,IOC容器中虽然有service和dao对象,但是service对象和dao对象没有任何关系,需要把dao对象交给service,也就是说要绑定service和dao对象之间的关系。
在容器中建立对象与对象之间的绑定关系就要用到DI。
DI(Dependency Injection)依赖注入
什么是依赖注入呢?
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
- 业务层要用数据层的类对象,以前是自己
new
的,现在自己不new了,靠外部
来给注入进来
- 业务层要用数据层的类对象,以前是自己
IOC和DI的最终目标
解耦
具体实现:
- 使用IOC容器管理bean(IOC)
- 在IOC容器内将有依赖关系的bean进行关系绑定(DI)
最终体现为:
- 使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系.
入门案例
代码实现
-
创建新的空项目,并创建Maven模块spring_framework_demo
-
添加springframework依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
-
创建相关类
BookDao
public interface BookDao { public void save(); }
BookDaoImpl
public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } }
BookService
public interface BookService { public void save(); }
BookServiceImpl
public class BookServiceImpl implements BookService { private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } // 为要注入的属性提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
在resource下添加spring配置文件:applicationContext.xml
-
bean配置
<?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.fs.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.fs.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"></property> </bean> </beans>
bean标签
-
用于配置bean
-
id属性
- 配置bean的id
- 在一个应用程序上下文中bean的id不能重复
- 配置bean的id
-
class属性
- bean的全限定类名
- 必须是一个可实例化的类
- bean的全限定类名
property标签
-
用于配置DI
-
name属性
- 指定当前配置的是bean中的哪一个属性
-
ref属性
- 指定该属性使用哪一个bean进行注入
- 属性值为另一个bean的id或name
- ref指定的bean,必须存在于IOC容器中,如果不存在,则会报错NoSuchBeanDefinitionException
- 指定该属性使用哪一个bean进行注入
-
功能测试
创建单元测试类ServiceTest
public class ServiceTest {
@Test
public void Test(){
// 根据指定的应用程序上下文获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从IOC容器获取指定id或name的bean
BookService bookService = (BookService) applicationContext.getBean("bookService");
bookService.save();
}
}
核心容器
容器的创建
ClassPathXmlApplicationContext
翻译为类路径下的XML配置文件,是对于类路径而言的相对路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
FileSystemXmlApplicationContext
翻译为文件系统下的XML配置文件,是对于操作系统的文件系统而言的绝对路径
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\spring\\src\\main\\resources\\applicationContext.xml");
bean的获取
按名称获取
BookService bookService = (BookService) applicationContext.getBean("bookService");
按名称和类型获取
BookService bookService = applicationContext.getBean("bookService",BookService.class);
按类型获取
BookService bookService = applicationContext.getBean(BookService.class);
要确保IOC容器中该类型对应的bean唯一
容器类层次结构
BeanFactory是IOC容器的顶层接口。
ApplicationContext接口是Spring容器的核心接口,提供基础的bean操作相关方法,通过其他接口扩展其功能。
BeanFactory
修改ServiceTest
public class ServiceTest {
@Test
public void Test() {
// 使用BeanFactory获取IOC容器
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
// 获取bean
BookService bookService = (BookService) beanFactory.getBean("bookService");
bookService.save();
}
}
- BeanFactory为延迟加载bean,只有在获取bean对象的时候才会去创建bean对象
- ApplicationContext默认为立即加载bean,容器加载的时候就会创建bean对象
- bean标签
- lazy-init属性
- 配置ApplicationContext延迟加载
- 值为true(延迟加载)、false(立即加载,默认)
IOC
bean的别名
可以通过bean标签的name属性为bean配置别名。
别名可以定义多个,使用逗号、分号或空格进行分隔。
别名不能与该应用程序上下文中其他bean的id或别名重复。
代码实现
-
修改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" name="bookDao_name1,bookDao_name2;bookDao_name3 bookDao_name4" class="com.fs.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.fs.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"></property> </bean> </beans>
-
修改依赖注入时指定的bean,出现提示,说明配置的别名生效了
bean的作用范围
可以通过bean标签的scope属性配置bean的作用范围,取值为singleton(单例,默认值)、prototype(非单例)
代码实现
-
修改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.fs.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.fs.service.impl.BookServiceImpl" scope="prototype"> <property name="bookDao" ref="bookDao"></property> </bean> </beans>
-
修改ServiceTest
public class ServiceTest { @Test public void Test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取两个bean对象 BookService bookService1 = (BookService) applicationContext.getBean("bookService"); BookService bookService2 = (BookService) applicationContext.getBean("bookService"); // 输出对象的地址值 System.out.println(bookService1); System.out.println(bookService2); } }
两次输出的地址值不同,说明获取到的是两个对象,配置作用范围为非单例生效了
为什么bean默认为单例?
- bean为单例则在Spring的IOC容器中只会有该类的一个对象,bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高
单例bean在容器中是否存在线程安全问题?
- 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,因为所有请求线程共用一个bean对象,所以会存在线程安全问题。
- 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
哪些bean对象适合交给容器进行管理?
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
哪些bean对象不适合交给容器进行管理?
- 封装实例的领域对象
bean实例化
IOC容器**实例化bean(就是创建bean对象)**有三种方式:
- 构造方法
- 静态工厂
- 实例工厂
bean本质上就是对象,对象在new的时候会使用构造方法完成,那无论使用哪种方法创建bean,其本质都是使用构造方法完成的
构造方法实例化
在入门案例中就是使用的构造方法实例化的方式创建的bean的对象。
在Spring底层是通过反射机制调用类的无参构造方法来实例化bean的。
即使无参构造方法使用private修饰,Spring也能够访问到。
使用构造方法实例化的bean,要么提供无参构造,要么使用默认的无参构造。
静态工厂实例化
代码实现
-
创建factor包,并在其中创建一个作为静态工厂的类
public class BookDaoFactor { public static BookDao getBookDao() { return new BookDaoImpl(); } }
-
配置作为静态工厂的bean
<?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.fs.factor.BookDaoFactor" factory-method="getBookDao"></bean> </beans>
-
修改ServiceTest
public class ServiceTest { @Test public void Test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) applicationContext.getBean("bookDao"); bookDao.save(); } }
在工厂类中也是直接new对象的,和我自己直接new没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?
- 在工厂的静态方法中,除了new对象之外还可以处理一些其他业务操作
实例工厂实例化
实例化工厂运行的顺序是:
- 创建实例化工厂对象
- 调用实例工厂对象中的方法来创建bean
代码实现
-
准备一个作为实例工厂的类
public class BookDaoInstanceFactor { public BookDao getBookDao(){ return new BookDaoImpl(); } }
-
配置作为实例工厂的bean和工厂要生产的bean
<?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="bookDaoInstanceFactor" class="com.fs.factor.BookDaoInstanceFactor"></bean> <bean id="bookDao" factory-bean="bookDaoInstanceFactor" factory-method="getBookDao"></bean> </beans>
只需要为实例工厂配置class属性
- factory-bean属性
- 配置用于生产bean的实例工厂
- 取值为实例工厂的id或name
- factory-method属性
- 配置实例工厂中用于生产bean的方法
- factory-bean属性
FactoryBean接口
用于简化实例工厂的配置
代码实现
-
准备一个作为实例工厂的类,让其实现FactoryBean接口
public class BookDaoFactoryBean implements FactoryBean<BookDao> { // 代替原始实例工厂中创建对象的方法 @Override public BookDao getObject() throws Exception { return new BookDaoImpl(); } // 返回工厂生产的类的Class对象 @Override public Class<?> getObjectType() { return BookDaoImpl.class; } // 返回工厂生产的bean是否为单例 @Override public boolean isSingleton() { return FactoryBean.super.isSingleton(); } }
2、配置实例工厂
<?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.fs.factor.BookDaoFactoryBean"></bean> </beans>
bean生命周期
什么是生命周期?
- 从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期。
bean生命周期是什么?
- bean对象从创建到销毁的整体过程。
bean生命周期控制是什么?
- 在bean创建后和销毁前执行一段指定的代码
指定任意方法作为生命周期函数
代码实现
-
修改BookDaoImpl
public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } // 初始化方法 public void init() { System.out.println("init"); } // 销毁方法 public void destroy() { System.out.println("destroy"); } }
-
bean配置
<?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.fs.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"></bean> </beans>
init-method属性
- 用于指定bean的初始化方法
destroy-method属性
- 用于指定bean的销毁方法
-
运行ServiceTest
输出结果显示只执行了bean的初始化方法,没有执行销毁方法。
为什么初始化方法执行了,但销毁方法未执行?
- Spring的IOC容器是运行在JVM中的,在运行main方法后,JVM启动,Spring加载配置文件生成IOC容器,从容器中获取bean对象,并调用bean对象初始化方法,main方法执行完后,JVM退出,此时IOC容器中的bean还没来得及销毁JVM就已经关闭了。
执行销毁方法
调用close方法手动关闭容器
修改ServiceTest
public class ServiceTest {
@Test
public void Test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
//手动关闭容器
context.close();
}
}
ApplicationContext中没有close方法,需要使用其子类。
只有单例的bean才可以手动销毁。
注册钩子关闭容器
在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器
修改ServiceTest
public class ServiceTest {
@Test
public void Test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
// 注册关闭钩子
context.registerShutdownHook();
}
}
ApplicationContext中没有registerShutdownHook方法,需要使用其子类
实现接口作为生命周期函数
InitializingBean接口
- 重写afterPropertiesSet方法作为初始化方法
DisposableBean接口
- 重写destroy方法作为销毁方法
代码实现
-
修改BookDaoImpl
public class BookDaoImpl implements BookDao, InitializingBean, DisposableBean { public void save() { System.out.println("book dao save ..."); } // 销毁方法 @Override public void destroy() throws Exception { System.out.println("destroy"); } // 初始化方法 @Override public void afterPropertiesSet() throws Exception { System.out.println("init"); } }
对于InitializingBean接口中的afterPropertiesSet方法,翻译过来为
属性设置之后
。对于BookServiceImpl来说,BookDao是它的一个属性,setBookDao方法是Spring的IOC容器为其注入属性的方法,初始化方法afterPropertiesSet会在类中属性注入完成之后再执行。
-
配置bean
<?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.fs.dao.impl.BookDaoImpl"></bean> </beans>
DI
向一个类中传递数据的方式有几种?
- 普通方法(set方法)
- 构造方法
依赖注入描述了在容器中建立bean与bean之间的依赖关系的过程,如果bean运行需要的是基本类型或字符串呢?
- 引用类型
- 简单类型(包括基本数据类型与String)
Spring就是在此基础上,提供了两种注入方式,分别是:
- setter注入
- 简单类型
- 引用类型
- 构造器注入
- 简单类型
- 引用类型
setter注入
即使用属性的set方法注入,在入门案例中就是使用了set方法为bookService注入引用类型的bookDao
代码实现
-
修改BookServiceImpl,声明属性并提供set方法:
public class BookServiceImpl implements BookService { // 声明set注入的引用类型的属性 private BookDao bookDao; // 声明set注入的基本类型的属性 private String string; private int anInt; private double aDouble; private boolean aBoolean; public void save() { System.out.println("book service save ..."); System.out.println(string); System.out.println(anInt); System.out.println(aDouble); System.out.println(aBoolean); bookDao.save(); } // 为要注入的属性提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void setString(String string) { this.string = string; } public void setAnInt(int anInt) { this.anInt = anInt; } public void setaDouble(double aDouble) { this.aDouble = aDouble; } public void setaBoolean(boolean aBoolean) { this.aBoolean = aBoolean; } }
-
修改BookDaoImpl
public class BookDaoImpl implements BookDao{ public void save() { System.out.println("book dao save ..."); } }
-
配置bean的依赖关系:
<?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.fs.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.fs.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"></property> <property name="string" value="set注入基本类型"></property> <property name="anInt" value="1"></property> <property name="aDouble" value="1.1"></property> <property name="aBoolean" value="true"></property> </bean> </beans>
property标签
- value属性
- 用于指定需要注入的基本数据类型的值
- ref属性
- 用于指定需要注入的引用数据类型的bean
- value属性
-
修改ServiceTest
public class ServiceTest { @Test public void Test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); BookService bookService = (BookService) applicationContext.getBean("bookService"); bookService.save(); } }
构造器注入
即使用构造方法注入
代码实现
-
修改BookServiceImpl,删除对应的set方法,并提供对应的带参构造方法:
public class BookServiceImpl implements BookService { // 声明构造器注入的引用类型的属性 private BookDao bookDao; // 声明构造器注入的基本类型的属性 private String string; private int anInt; private double aDouble; private boolean aBoolean; public void save() { System.out.println("book service save ..."); System.out.println(string); System.out.println(anInt); System.out.println(aDouble); System.out.println(aBoolean); bookDao.save(); } // 为要注入的属性提供对应的构造方法 public BookServiceImpl(BookDao bookDao, String string, int anInt, double aDouble, boolean aBoolean) { this.bookDao = bookDao; this.string = string; this.anInt = anInt; this.aDouble = aDouble; this.aBoolean = aBoolean; } }
-
配置bean的依赖关系:
<?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.fs.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.fs.service.impl.BookServiceImpl"> <constructor-arg name="bookDao" ref="bookDao"></constructor-arg> <constructor-arg name="string" value="构造器注入"></constructor-arg> <constructor-arg name="anInt" value="1"></constructor-arg> <constructor-arg name="aDouble" value="1.1"></constructor-arg> <constructor-arg name="aBoolean" value="true"></constructor-arg> </bean> </beans>
constructor-arg标签
- name属性
- 指定当前构造器注入注入的是bean的哪一个属性
- 取值为构造方法中的形参名
- ref属性和value属性的作用和property标签一样
- name属性
按类型注入
使用按类型注入可以解决构造函数形参名发生变化带来的耦合问题。
将bean的依赖关系配置修改为:
<?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.fs.dao.impl.BookDaoImpl"></bean>
<bean id="bookService" class="com.fs.service.impl.BookServiceImpl">
<constructor-arg type="com.fs.dao.BookDao" ref="bookDao"></constructor-arg>
<constructor-arg type="java.lang.String" value="构造器注入"></constructor-arg>
<constructor-arg type="int" value="1"></constructor-arg>
<constructor-arg type="double" value="1.1"></constructor-arg>
<constructor-arg type="boolean" value="true"></constructor-arg>
</bean>
</beans>
type属性
- 按照类型指定当前构造器注入注入的是bean的哪一个属性
- 只适用于构造方法参数中没有类型相同的参数时使用
按索引注入
使用按索引注入可以解决参数类型重复问题。
将bean的依赖关系配置修改为:
<?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.fs.dao.impl.BookDaoImpl"></bean>
<bean id="bookService" class="com.fs.service.impl.BookServiceImpl">
<constructor-arg index="0" ref="bookDao"></constructor-arg>
<constructor-arg index="1" value="构造器注入"></constructor-arg>
<constructor-arg index="2" value="1"></constructor-arg>
<constructor-arg index="3" value="1.1"></constructor-arg>
<constructor-arg index="4" value="true"></constructor-arg>
</bean>
</beans>
index属性
- 按照要注入的属性在构造方法形参列表中的索引位置来指定,当前构造器注入注入的是bean的哪一个属性
setter注入和构造器注入的区别
强制依赖,使用构造器进行,使用setter注入有概率出现不进行注入导致null对象出现
- 强制依赖,指对象在创建的过程中必须要注入指定的参数
可选依赖,使用setter注入进行,灵活性强
- 可选依赖,指对象在创建过程中注入的参数可有可无
Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
自己开发的模块优先使用setter注入
自动装配
什么是自动装配?
- IOC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程
作用:
- 用于简化Spring配置文件的书写
特点:
- 只能用于引用类型依赖注入
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
按类型装配
自动装配依赖于setter注入。
按类型自动装配需要保证在IOC容器中为该类型的bean的唯一性。
代码实现
-
修改BookServiceImpl,声明属性并提供set方法:
public class BookServiceImpl implements BookService { // 声明使用按类型自动装配的引用类型的属性 private BookDao bookDao; @Override public void save() { System.out.println("save"); bookDao.save(); } // 用于自动装配的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
配置bean的依赖关系:
<?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.fs.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.fs.service.impl.BookServiceImpl" autowire="byType"> </bean> </beans>
autowire属性
- 用于开启自动装配,并指定自动装配的方式
- 取值byType(按类型)、byName(按名称)
-
运行ServiceTest
按名称装配
按名称装配中的名称值的是什么?
- 属性是private修饰的,外部类无法直接访问,外部类只能通过属性的set方法进行访问,所以对外部类来说,setBookDao方法名,去掉set后,首字母小写就是其属性名,即bookDao,按照该属性名到容器中查找bean。
为什么是去掉set首字母小写?
- 因为这个规则是set方法生成的默认规则,set方法的生成是把属性名首字母大写前面加set形成的方法名。
如果按照名称去找对应的bean对象找不到,则注入Null。
代码实现
-
将bean的依赖关系配置修改为:
<?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.fs.dao.impl.BookDaoImpl"></bean> <bean id="bookService" class="com.fs.service.impl.BookServiceImpl" autowire="byName"> </bean> </beans>
数组集合类型
使用set方法注入集合类型和构造器注入集合类型的操作类似
代码实现
-
修改BookServiceImpl,声明属性并提供set方法:
public class BookServiceImpl implements BookService { // 声明需要注入的集合类型的属性 private int[] ints; private List<String> list; private Set<String> strings; private Map<String, String> map; private Properties properties; @Override public void save() { System.out.println("int[]:"); for (int anInt : ints) { System.out.print(anInt); System.out.print("\t"); } System.out.println(); System.out.println("List<String>:"); System.out.println(list); System.out.println("Set<String>:"); System.out.println(strings); System.out.println("Map<String,String>:"); System.out.println(map); System.out.println("Properties:"); System.out.println(properties); } // 用于注入集合类型属性的set方法 public void setInts(int[] ints) { this.ints = ints; } public void setList(List<String> list) { this.list = list; } public void setStrings(Set<String> strings) { this.strings = strings; } public void setMap(Map<String, String> map) { this.map = map; } public void setProperties(Properties properties) { this.properties = properties; } }
-
配置bean的依赖关系:
<?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="bookService" class="com.fs.service.impl.BookServiceImpl"> <property name="ints"> <array> <value>1</value> <value>2</value> <value>3</value> </array> </property> <property name="list"> <list> <value>Hello</value> <value>Hello</value> <value>Hello</value> </list> </property> <property name="map"> <map> <entry key="key1" value="value1"></entry> <entry key="key2" value="value2"></entry> <entry key="key3" value="value3"></entry> </map> </property> <property name="strings"> <set> <value>Hello</value> <value>My</value> <value>Spring</value> </set> </property> <property name="properties"> <props> <prop key="key1">value1</prop> <prop key="key2">value2</prop> <prop key="key3">value3</prop> </props> </property> </bean> </beans>
在constructor-arg标签(用于构造器注入)内部也可以使用以下标签
<array></array> <list></list> <map></map> <set></set> <props></props>
List的底层也是通过数组实现的,所以
<list>
和<array>
标签是可以混用集合中要添加引用类型,只需要把
<value>
标签改成<ref>
标签 -
运行ServiceTest
IOC/DI配置管理第三方bean
Spring整合Druid
-
添加Druid依赖:
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
-
配置第三方bean:
<?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="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property> <property name="url" value="jdbc:mysql//localhost/8080"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </bean> </beans>
-
修改ServiceTest
public class ServiceTest { @Test public void Test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) applicationContext.getBean("druidDataSource"); System.out.println(dataSource); } }
Druid在初始化的时候没有去加载驱动,所以没有导入mysql驱动也不会报错,虽然没有报错,但是当调用DruidDataSource的getConnection()方法获取连接的时候,也会报找不到驱动类的错误
Spring整合C3P0
-
添加C3P0和mysql依赖:
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency>
C3P0在初始化的时候就加载了驱动,所以不导入mysql驱动的话会报错
-
配置第三方bean:
<?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="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql//localhost/8080"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> <property name="maxPoolSize" value="1000"></property> </bean> </beans>
-
在测试类ServiceTest中添加:
public class ServiceTest { @Test public void Test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) applicationContext.getBean("c3p0DataSource"); System.out.println(dataSource); } }
加载properties配置文件
代码实现
-
在resource下添加jdbc.properties配置文件:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql//localhost/8080 jdbc.username=root jdbc.password=123456 dataSource.maxPoolSize=1000
-
在applicationContext.xml中开启
context
命名空间:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
-
在配置文件中使用
context
命名空间下的标签来加载properties配置文件:<context:property-placeholder location="jdbc.properties"/>
-
使用
${key}
来读取properties配置文件中的内容并完成属性注入:<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" lazy-init="true"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="maxPoolSize" value="${dataSource.maxPoolSize}"></property> </bean>
在properties中配置键值对的时候,如果key设置为
username
,则获取到的值不是键值对中设置的值,而是电脑的用户名。因为
<context:property-placeholder/>
标签会加载系统的环境变量,而且环境变量的值会被优先加载。可以使用
System.getenv()
来查看系统环境变量。<context:property-placeholder/>
标签-
system-properties-mode属性
-
值为NEVER,表示不加载系统属性:
-
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
-
-
location属性
-
指定需要加载的properties配置文件
-
当需要加载多个配置文件时,有多种方式:
-
多个配置文件之间使用逗号分隔
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
-
使用通配符(*),
*.properties
表示加载所有以properties结尾的文件<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
-
使用
classpath:
和通配符(*),classpath:
代表的是从根路径下开始查找,但是只能查询当前项目的根路径<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
-
不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties配置文件
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
-
-
-
配置文件开发总结
IOC/DI注解开发
Spring对注解支持的版本历程:
- 2.0版开始支持注解
- 2.5版注解功能趋于完善
- 3.0版支持纯注解开发
注解开发定义bean用的是2.5版提供的注解,纯注解开发用的是3.0版提供的注解。
注解开发定义bean
使用注解配置bean,可以在类上添加一个注解:@Component 。
可以通过@Component注解的value属性指定bean的id。如果没有指定,默认为类名首字母小写。
配置文件与注解配置的对应关系:
代码开发
-
修改BookDaoImpl,添加@component注解
@Component("bookDaoImpl") public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } }
-
在配置文件中配置Spring的注解包扫描:
<context:component-scan base-package="com.fs"></context:component-scan>
context:component-scan标签
- base-package属性
- 用于配置Spring框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解
- base-package属性
-
修改ServiceTest
public class ServiceTest { @Test public void Test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) applicationContext.getBean("bookDaoImpl"); bookDao.save(); } }
@Component的衍生注解
Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:
@Component注解
- 写在类上,用于声明bean的基础注解
@Controller注解
- @Component的衍生注解,写在控制器类(controller层)上
@Service注解
- @Component的衍生注解,写在业务类(service层)上
@Repository注解
- @Component的衍生注解,写在数据访问类(dao层)上(由于与mybatis整合,用的少)
@Component注解及其衍生注解只能添加到可实例化的类上
使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
纯注解开发
纯注解开发使用Java类替代配置文件。
代码实现
-
使用注解配置Spring的注解包扫描,创建config包并在其中创建SpringConfig类作为配置类,代替配置文件:
@Configuration @ComponentScan("com.fs") public class SpringConfig { }
@ComponentScan注解只能添加一次,如果添加多个包路径可以使用数组静态初始化的格式进行添加
@Configuration注解
- 写在类定义上方,用于设置该类为spring配置类
@ComponentScan注解
- 写在类定义上方,用于设置spring配置类扫描路径,用于加载使用注解格式定义的bean
-
修改ServiceTest
public class ServiceTest { @Test public void Test() { // 根据指定的Spring配置类来获取IOC容器 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); // 获取bean BookDao bookDao = (BookDao) applicationContext.getBean("bookDaoImpl"); bookDao.save(); } }
bean作用范围
@Scope注解
- 写在类定义上方,用于设置该类创建对象的作用范围,即设置创建出的bean是否为单例对象
- value属性
- 定义bean作用范围,默认值singleton(单例),可选值prototype(非单例)
bean生命周期
@PostConstruct注解
- 写在方法上,用于设置该方法为初始化方法
@PreDestroy注解
- 写在方法上,用于设置该方法为销毁方法
Spring注解配置bean生命周期找不到@PostConstruct和@PreDestroy注解
原因
- @PostConstruct和 @PreDestroy注解位于 java.xml.ws.annotation包是Java EE的模块的一部分。J2EE已经在Java 9中被弃用,并且计划在Java 11中删除它。
解决方案
添加依赖(Java 9+中的Spring @PostConstruct和@PreDestroy替代品)
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
自动装配
Spring为了使用注解简化开发,并没有提供构造函数注入
、setter注入
对应的注解,只提供了自动装配的注解实现。
代码实现
-
修改BookServiceImpl
@Service("bookServiceImpl") @Scope("singleton") @Lazy(true) public class BookServiceImpl implements BookService { //注解自动装配 @Autowired private BookDao bookDao; @Override public void save() { System.out.println("save"); bookDao.save(); } // 销毁方法 @PreDestroy public void destroy(){ System.out.println("destroy"); } // 初始化方法 @PostConstruct public void afterPropertiesSet(){ System.out.println("init"); } }
@Autowired注解
- 写在属性、标准set方法 、类set方法,用于为引用类型属性设置值
- required属性
- 用于指定注入的属性是否是必需的,取值为true(必需的,当容器找不到匹配的bean进行注入时,会报错),或false(不必需的,当容器找不到匹配的bean进行注入时,会将该属性值设置为null,而不会报错),默认值为true
@Autowired可以写在属性上,也可也写在set方法上,其作用一样,一般情况下的处理方式是
写在属性上并将setter方法删除掉
。为什么set方法可以删除呢?
- 注解配置自动装配是基于反射创建对象,并通过反射为私有属性进行设值,和配置文件配置自动装配不同,反射除了获取public修饰的内容还可以获取private修改的内容,所以此处无需提供setter方法。
-
修改ServiceTest
public class ServiceTest { @Test public void Test() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); BookService bookService = (BookService) applicationContext.getBean("bookServiceImpl"); bookService.save(); applicationContext.registerShutdownHook(); } }
@Autowired先按属性的类型自动装配,如果IOC容器中同类型的Bean找到多个,再按属性名称自动装配,如果还是找不到与属性名称匹配的bean,则会报错。
按名称装配时被装配的bean的名称
修改BookServiceImpl,使用注解指定按名称装配时被bean的名称:
@Service("bookServiceImpl")
@Scope("singleton")
@Lazy(true)
public class BookServiceImpl implements BookService {
//注解自动装配
@Autowired
@Qualifier("bookDaoImpl")
private BookDao bookDao;
@Override
public void save() {
System.out.println("save");
bookDao.save();
}
// 销毁方法
@PreDestroy
public void destroy(){
System.out.println("destroy");
}
// 初始化方法
@PostConstruct
public void afterPropertiesSet(){
System.out.println("init");
}
}
@Qualifier注解
- 写在属性、方法上,不能单独使用,必须配合@Autowired使用,用于为引用类型属性指定按名称自动装配时bean的id
使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。
使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。
@Autowird 与 @Resource的区别:
- @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
- @Autowired 默认是按照类型注入,而@Resource是按照名称注入
基本类型注入
代码实现
-
修改BookServiceImpl,声明属性并@value注解注入值:
@Service("bookServiceImpl") public class BookServiceImpl implements BookService { // 使用注解注入简单类型 @Value("注解注入简单类型") private String string; @Value("1") private int anInt; @Value("1.1") private double aDouble; @Value("true") private boolean aBoolean; @Override public void save() { System.out.println("save"); System.out.println(string); System.out.println(anInt); System.out.println(aDouble); System.out.println(aBoolean); } }
@Value注解
- 写在属性、标准set方法、类set方法上方,用于为 基本数据类型 或 字符串类型 属性设置值
加载properties配置文件
代码实现
-
在配置类上添加@PropertySource注解:
@Configuration @ComponentScan("com.fs") @PropertySource("jdbc.properties") public class SpringConfig { }
@PropertySource注解
- 写在类定义上方,用于加载properties文件中的属性值
- value属性
- 设置加载的properties文件对应的文件名或使用数组静态初始化的格式进行添加多个文件
如果读取的properties配置文件有多个,可以使用数组静态初始化的格式进行添加多个文件
@PropertySource({"data.properties","xxx.properties"})
@PropertySource
注解属性中不支持使用通配符*
,运行会报错@PropertySource({"*.properties"})
@PropertySource
注解属性中可以使用classpath:
,表示从当前项目的根路径找文件@PropertySource({"classpath:data.properties"})
-
使用@Value注解注入值
@Component("bookDaoImpl") public class BookDaoImpl implements BookDao { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Value("${dataSource.maxPoolSize}") private String maxPoolSize; @Override public void save() { System.out.println(driver); System.out.println(url); System.out.println(username); System.out.println(password); System.out.println(maxPoolSize); System.out.println("save"); } }
配置管理第三方bean
代码实现
-
配置第三方bean:
@Configuration @ComponentScan("com.fs") @PropertySource("jdbc.properties") public class SpringConfig { @Bean("druidDataSource") public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } }
@Bean注解
- 写在方法定义上方,用于设置该方法的返回值作为spring管理的bean,IOC容器会根据类型自动装配该方法的形参
不能直接使用
DataSource ds = new DruidDataSource()
,因为DataSource接口中没有对应的setter方法来设置连接池属性。
功能测试
修改ServiceTest
public class ServiceTest {
@Test
public void Test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = (DataSource) applicationContext.getBean("druidDataSource");
System.out.println(dataSource);
}
}
引入外部配置类
如果把所有的第三方bean都配置到Spring的配置类SpringConfig
中,虽然可以,但是不利于代码阅读和分类管理,所以我们一般会将不同的bean按照类别配置到不同的配置类中,然后在 SpringConfig
中导入。
代码实现
-
准备DruidConfig:
public class DruidConfig { @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } }
-
导入配置类有两种方法。
方法1:包扫描导入
-
在DruidConfig上添加@Configuration注解:
@Configuration public class DruidConfig { @Bean("druidDataSource") public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } }
-
配置包扫描:
@Configuration @ComponentScan("com.fs") @PropertySource("jdbc.properties") public class SpringConfig { }
方法2:@Import导入
- 在Spring配置类中引入:
@Configuration
@Import({DruidConfig.class})
@PropertySource("jdbc.properties")
public class SpringConfig {
}
@Import注解
- 写在类定义上方,用于导入配置类
@Import的参数是一个Class数组,可以引入多个配置类。
@Import注解在配置类中只能写一次
注解开发和配置文件开发比较
Spring整合
Junit
代码实现
-
引入依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- Spring测试专用包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
创建JunitTest测试类
@RunWith(SpringJUnit4ClassRunner.class)//设置类运行器 @ContextConfiguration(classes = {SpringConfig.class})//加载配置类 //@ContextConfiguration(locations = {"classpath:applicationContext.xml"})//加载配置文件 public class JunitTest { }
Spring整合Junit后Junit是基于Spring环境运行的,所以Spring提供了一个专用的类运行器
SpringJUnit4ClassRunner
,这个类运行器在Spring的测试专用包中提供。@RunWith注解
- 写在测试类定义上方,用于设置Junit运行器
@ContextConfiguration注解
- 写在测试类定义上方,用于设置Junit加载的Spring核心配置
- classes属性
- 注解开发时用于指定配置类,可以使用数组静态初始化的格式指定多个配置类
- locations属性
- 配置文件开发时用于指定配置文件,可以使用数组静态初始化的格式指定多个配置文件
功能测试
在测试类JunitTest中添加:
//整合后可以使用自动装配注入对象
@Autowired
private AnnotationBookDaoImpl annotationBookDao;
@Test
public void test() {
System.out.println(annotationBookDao);
}
MyBatis
Spring与Mybatis的整合,大体需要做两件事:
- Spring要管理MyBatis中的SqlSessionFactory
- Spring要管理Mapper接口的扫描
代码实现
-
创建Maven模块spring_framework_mybatis
-
引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- Spring测试专用包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <!--Spring操作数据库需要该jar包--> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <!--Spring与Mybatis整合的jar包,这个jar包名称中mybatis在前面,因为是Mybatis提供的--> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency>
-
创建mybatis数据库,执行下面的代码创建用户表user
-- 用户表 create table user( id int unsigned primary key auto_increment comment 'ID', name varchar(100) comment '姓名', age tinyint unsigned comment '年龄', gender tinyint unsigned comment '性别, 1:男, 2:女', phone varchar(11) comment '手机号' ) comment '用户表'; -- 测试数据 insert into user(id, name, age, gender, phone) VALUES (null,'白眉鹰王',55,'1','18800000000'); insert into user(id, name, age, gender, phone) VALUES (null,'金毛狮王',45,'1','18800000001'); insert into user(id, name, age, gender, phone) VALUES (null,'青翼蝠王',38,'1','18800000002'); insert into user(id, name, age, gender, phone) VALUES (null,'紫衫龙王',42,'2','18800000003'); insert into user(id, name, age, gender, phone) VALUES (null,'光明左使',37,'1','18800000004'); insert into user(id, name, age, gender, phone) VALUES (null,'光明右使',48,'1','18800000005');
-
创建对应的实体类User
public class User { private Integer id; //id(主键) private String name; //姓名 private Short age; //年龄 private Short gender; //性别 private String phone; //手机号 //省略GET, SET方法 }
-
resources目录下创建jdbc.properties文件,用于配置数据库连接四要素
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password=123456
-
创建数据源的配置类,在配置类中完成数据源的创建
public class JdbcConfig { @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(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }
-
创建Mybatis配置类
public class MybatisConfig { //将SqlSessionFactoryBean配置为bean,用于生产SqlSessionFactory对象 @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) { SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); //设置实体类的别名扫描 ssfb.setTypeAliasesPackage("com.fs.pojo.entity"); //设置数据源 ssfb.setDataSource(dataSource); return ssfb; } //将MapperScannerConfigurer配置为bean @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer msc = new MapperScannerConfigurer(); //设置mapper接口的包扫描路径 msc.setBasePackage("com.fs.mapper"); return msc; } }
MapperScannerConfigurer对象是MyBatis提供的用于整合的jar包中的类,用来处理原始配置文件中的mappers相关配置,加载数据层的Mapper接口类。
-
创建Spring主配置类,并加载jdbc.properties配置文件、引入数据源配置类和MyBatis配置类
@Configuration @ComponentScan("com.fs") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { }
-
创建UserMapper
@Mapper public interface UserMapper { //查询所有用户数据 @Select("select id, name, age, gender, phone from user") public List<User> list(); }
@Mapper注解:表示是mybatis中的Mapper接口
- 程序运行时:框架会自动生成接口的实现类对象(代理对象),并给交Spring的IOC容器管理
Dao接口要想被容器扫描到,有两种解决方案:
@SpringBootApplication @MapperScan("com.myself.dao") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
- 方案一:在Dao接口上添加
@Mapper
注解,并且确保Dao处在引导类所在包或其子包中- 需要在每一个Dao接口中添加注解
- 方案二:在引导类上添加
@MapperScan
注解,其属性为所要扫描的Dao所在包- 只需要写一次,则指定包下的所有Dao接口都能被扫描到,
@Mapper
可以不写。
- 只需要写一次,则指定包下的所有Dao接口都能被扫描到,
功能测试
整合Junit,并运行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class JunitTest {
@Autowired
private UserMapper userMapper;
@Test
public void test() {
List<User> list = userMapper.list();
for (User user : list) {
System.out.println(user);
}
}
}