昆明网站建设公司电话,湖南省建设工程造价管理协会网站,wordpress还原旧版本,网站怎么没有排名【Spring 基础】
一、 Spring 介绍
1. 简述
Spring 技术是 JavaEE 开发必备技能#xff0c;企业开发技术选型专业角度 简化开发#xff0c;降低企业级开发的复杂性 IoCAOP 事务处理 框架整合#xff0c;高效整合其他技术#xff0c;提高企业级应用开发与运行效率 MyBat…【Spring 基础】
一、 Spring 介绍
1. 简述
Spring 技术是 JavaEE 开发必备技能企业开发技术选型专业角度 简化开发降低企业级开发的复杂性 IoCAOP 事务处理 框架整合高效整合其他技术提高企业级应用开发与运行效率 MyBatisMyBatis-plusStrutsStruts2Hibernate……
学习 Spring 架构设计思想学习基础操作思考操作与思想间的联系学习案例熟练应用操作的同时体会思想
2. Spring 发展 Spring 官网 Spring 发展到今天已经形成了一种开发的生态圈Spring 提供了若干个项目每个项目用于完成特定的功能 1 Spring 应用 2 Spring 家族
排位越靠前使用量越大越重要
3 Spring 的侧重学习
Spring Framework Spring 框架是 Java 平台的一个开源的全栈full-stack应用程序框架和控制反转容器实现一般被直接称为 SpringSpring Boot Spring Boot 是基于 Spring Framework 4.0 派生的用于快速搭建独立的基于生产级别的 Spring 应用的框架可以以最小的依赖引入来构建一个 Spring 应用Spring Cloud Spring Cloud 为开发者提供了快速构建分布式系统中一些常用模式的工具如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态分布式系统的协调导致样板模式使用 Spring Cloud 开发人员可以快速建立实现这些模式的服务和应用程序
4 Spring 发展
Spring 1.0 使用纯配置的形式开发Spring 2.0 引入了注解的功能Spring 3.0 演化成了可以不写配置的模式Spring 4.0 紧跟 JDK 版本的升级对个别的 API 进行了调整Spring 5.0 全面支持 JDK 8Spring 6.0 基于 JDK 17 源代码水平
二、 Spring Framework
1. Spring Framework 系统架构图
Spring Framework 4.0 系统架构图之后版本基本趋于稳定没有太大变化 在技术中上层总是是基于下层实现的第一部分 Core Container 核心容器 第二部分 AOP 面向切面编程Aspects AOP 思想实现 第三部分 Data Access 数据访问Data Integration 数据集成 第四部分 Web Web 开发 第五部分 Test 单元测试与集成测试
2. Spring Framework 学习线路图
基本可以简单描述为 Core containerData Access/IntegrationAOP、AspectsTransactions 具体表现为 第一部分核心容器 核心概念IoC/DI容器基本操作 第二部分整合整合在第四部分时体现更明显 整合数据层技术 MyBatis 第三部分AOP 核心概念AOP 基础操作AOP 实用开发 第四部分事务 事务实用开发 第五部分家族 SpringMVCSpring BootSpring Cloud
三、 核心容器
1. 核心容器Core Container的核心概念 主要涉及 IoC/DI、IoC 容器、Bean 三部分核心概念 问题的提出 业务层的定义 数据层的实现 一旦数据层的增加或更替实现就无法成功 代码书写现状 耦合度偏高 解决方案 使用对象时在程序中不要主动使用 new 创建对象转换为由外部提供对象 IoCInversion of Control控制反转 对象的创建控制权由程序转移到外部这种思想称为控制反转使用对象时由主动 new 产生对象转换为由外部提供对象此过程中对象创建控制权由程序转移到外部 Spring 技术对 IoC 思想进行了实现 Spring 提供了一个容器称为 IoC 容器用来充当 IoC 思想中的“外部”IoC 容器负责对象的创建初始化等一系列工作被创建或被管理的对象在 IoC 容器中统称为 Bean 由于业务层中的 Service 依赖 Dao 对象运行并且 IoC 容器中包含着 service 和 dao 对象所以 IoC 容器中将 service 和 dao 的依赖关系进行绑定 DIDependency Injection依赖注入 在容器中建立 bean 与 bean 之间的依赖关系的整个过程称为依赖注入 总结 目标充分解耦 使用 IoC 容器管理 beanIoC在 IoC 容器内将所有依赖关系的 bean 进行关系绑定DI 最终效果 使用对象时不仅可以直接从 IoC 容器中获取并且获取到的 bean 已经绑定了所有的依赖关系
2. IoC 控制反转入门
思路分析 管理什么Service 与 Dao如何将被管理的对象告知 IoC 容器配置被管理的对象交给 IoC 容器如何获取到 IoC 容器接口IoC 容器得到后如何从容器中获取 bean 接口方法使用 Spring 导入哪些坐标pom.xml 文件中
1 不使用 Spring 配置
实现 创建或使用 Maven 项目 以此目录演示不使用 Spring 配置
public interface BookDao {public void save();
}public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(book dao save...);}
}public interface BookService {public void save();
}public class BookServiceImpl implements BookService {private BookDao bookDaonew BookDaoImpl();Overridepublic void save() {System.out.println(book service save...);bookDao.save();}
}public class App {public static void main(String[] args) {BookServiceImpl bookService new BookServiceImpl();bookService.save();}
}2 使用 Spring 配置
在 pom.xml 中导入依赖项自行选择版本即可 dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.24/version/dependency之后右击 resources 目录选择新建 选择 Spring 配置Spring config 创建名称为 applicationContext.xml 的配置文件以上一个 Maven 项目为基础编辑 applicationContext.xml 配置文件 bean 定义时 id 属性在同一个上下文中不能重复
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd!-- 1. 导入 Spring 的坐标 spring-context及其对应版本--!-- 2. 配置 bean--!-- bean 标签表示配置 bean--!-- id 属性表示给 bean 起名字--!-- class 属性表示给 bean 定义类型--bean idbookDao classcom.example.dao.impl.BookDaoImpl/bean idbookService classcom.example.service.impl.BookServiceImpl/
/beans进行实现
public class App {public static void main(String[] args) {// 获取 IoC 容器加载配置文件得到上下文对象也就是容器对象ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);// 获取 beanBookDao bookDao (BookDao) applicationContext.getBean(bookDao);bookDao.save();BookService bookService (BookService) applicationContext.getBean(bookService);bookService.save();}
}但是此时仍然需要实现方法中的创建对象操作没有达到解耦的效果
3. DI 依赖注入入门 思路分析 基于 IoC 管理 beanService 中使用 new 形式创建的 Dao 对象是否保留否Service 中需要的 Dao 对象如何进入到 Service 中提供方法Service 与 Dao 间的关系如何描述配置 更改上文中的 BookServiceImpl 实现类和 applicationContext.xml 配置文件即可实现
public class BookServiceImpl implements BookService {// 删除业务层中使用 new 的方式创建的 dao 对象private BookDao bookDao;Overridepublic void save() {System.out.println(book service save...);bookDao.save();}// 提供对应的 set 方法public void setBookDao(BookDao bookDao) {this.bookDao bookDao;}
}?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd!-- 1. 导入 Spring 的坐标 spring-context及其对应版本--!-- 2. 配置 bean--!-- bean 标签表示配置 bean--!-- id 属性表示给 bean 起名字--!-- class 属性表示给 bean 定义类型--bean idbookDao classcom.example.dao.impl.BookDaoImpl/
!-- bean idbookService classcom.example.service.impl.BookServiceImpl/--bean idbookService classcom.example.service.impl.BookServiceImpl!-- 配置 service 与 dao 的关系--!-- property 标签表示配置当前 bean 的属性--!-- name 属性表示配置哪一个具体的属性即此属性下的 “bookDao” 为 BookServiceImpl 类中的对象--!-- ref 属性表示参照哪一个 bean即此属性下的 “bookDao” 为此配置文件中上文中的 id 属性 “bookDao”--property namebookDao refbookDao//bean
/beans依旧可以使用上文测试方式实现
4. bean 配置
1 bean 基础配置 2 bean 别名配置
例如修改配置文件此处 bean 标签中的 name 属性可以定义多个别名 bean 标签下的 ref 属性也可以指向此别名但是不建议规范写法就是指向 id 属性 bean idbookService nameservice service2 service3 classcom.example.service.impl.BookServiceImplproperty namebookDao refbookDao//bean对应的实现测试方法中直接输入别名就可以执行 public static void main(String[] args) {// 获取 IoC 容器ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);// 获取 beanBookService bookService (BookService) applicationContext.getBean(service);bookService.save();}注意 获取 bean 无论是通过 id 还是 name 属性获取如果无法获取到将抛出异常 NoSuchBeanDefinitionExceptionNoSuchBeanDefinitionExceptionNo bean named xxxxxx available
3 bean 作用范围配置
测试 public static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);BookDao bookDao01 (BookDao) applicationContext.getBean(bookDao);System.out.println(bookDao01);BookDao bookDao02 (BookDao) applicationContext.getBean(bookDao);System.out.println(bookDao02);}得出结果两个输出地址相同说明 Spring 默认创建 bean 的是一个单例修改配置文件增加 scope 属性 bean idbookDao classcom.example.dao.impl.BookDaoImpl scopeprototype/此时就会得到不同的两个地址 由于 Spring 创建的 bean 默认为单例的得出 适合交给容器进行管理的 bean 表现层对象业务层对象数据层对象工具对象 不适合交给容器进行管理的 bean 封装实体的域对象
5. bean 实例化 实例化 bean 的三种方式最后一种是基于第三种的改良 1 构造方法实例化
Spring 创建 bean 的时候调用的是无参的构造方法不论公共还是私有的都可以访问到进一步说明了 Spring 通过反射进行访问的 无参构造方法如果不存在将会抛出异常 BeanCreationException Spring 构造下的报错信息的阅读采用从后向前的读法最前面的报错信息是所有错误的整合查找以 Caused by: 开头的报错信息解决问题
2 静态工厂实例化 这种方式了解即可 首先创建 OrderDao 接口和 OrderDaoImpl 实现类创建 factory 目录与 dao 目录同级其中创建 OrderDaoFactory 静态工厂类实现创建 bean 对象
public interface OrderDao {public void save();
}public class OrderDaoImpl implements OrderDao {Overridepublic void save() {System.out.println(order dao save...);}
}静态工厂
public class OrderDaoFactory{public static OrderDao getOrderDao{// 此处的工厂类中还可以执行一些其他操作所以不能直接进行 new 对象操作而跳过工厂这一步return new OrderDaoImpl();}
}配置文件
!-- 使用静态工厂实例化 bean--
!-- factory-method 属性就是配置目标类中真正实现创建对象的方法名--
bean idorderDao classcom.example.factory.OrderDaoFactory factory-methodgetOrderDao/测试 public static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);OrderDao orderDao (OrderDao) applicationContext.getBean(orderDao);orderDao.save();}3 实例工厂实例化 这种方法了解即可 首先创建 UserDao 接口和 UserDaoImpl 实现类创建 factory 目录与 dao 目录同级其中创建 UserDaoFactory 实例工厂类实现创建 bean 对象
public interface UserDao {public void save();
}public class UserDaoImpl implements UserDao {Overridepublic void save() {System.out.println(user dao save...);}
}实例工厂
public class UserDaoFactory{public UserDao getUserDao{return new UserDaoImpl();}
}配置文件
!-- 使用实例工厂实例化 bean--
bean iduserFactory classcom.example.factory.UserDaoFactory/
bean iduserDao factory-methodgetUserDao factory-beanuserFactory/测试 public static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);UserDao userDao (UserDao) applicationContext.getBean(userDao);userDao.save();}缺陷显而易见前一个 bean 标签配合使用但是本身实际无意义第二个 bean 标签中的方法名不固定每次使用都需要配置
4 FactoryBean 实例化方式 这种方式是基于实例工厂Spring 进行的改良 依旧使用创建的 UserDao 接口和 UserDaoImpl 实现类在 factory 目录中创建 UserDaoFactoryBean 实例工厂类实现创建 bean 对象
public class UserDaoFactoryBean implements FactoryBeanUserDao {/*** 代替原始实例工厂中创建对象的方法* return* throws Exception*/Overridepublic UserDao getObject() throws Exception {return new UserDaoImpl();}Overridepublic Class? getObjectType() {return UserDao.class;}
}配置文件 bean iduserDao classcom.example.factory.UserDaoFactoryBean/依旧使用同样的测试方式这种创建方式出现的 bean 依旧是单例的如果需要非单例则需要多实现一个 isSingleton() 方法
public class UserDaoFactoryBean implements FactoryBeanUserDao {Overridepublic UserDao getObject() throws Exception {return new UserDaoImpl();}Overridepublic Class? getObjectType() {return UserDao.class;}Overridepublic boolean isSingleton() {// return true; // 单例return false; // 非单例}
}6. bean 的生命周期 生命周期从创建到消亡的完整过程 bean 的生命周期bean 从创建到销毁的整体过程 bean 生命周期控制在 bean 创建后到销毁前做的一些事情 生命周期测试
public interface BookDao {public void save();
}public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(book dao save...);}// 表示 bean 初始化对应的条件public void init(){System.out.println(init...);}// 表示 bean 销毁前对应的操作public void destroy(){System.out.println(destroy...);}
}bean idbookDao classcom.example.dao.impl.BookDaoImpl init-methodinit destroy-methoddestroy/bean idbookService classcom.example.service.impl.BookServiceImplproperty namebookDao refbookDao//beanpublic static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);BookDao bookDao (BookDao) applicationContext.getBean(bookDao);bookDao.save();}这种情况下的运行结果并没有最后 destroy 方法的显示
init...
book dao save...解释 虚拟机启动IoC 容器加载配置并启动之后 bean 初始化从容器中拿到 bean 并执行结束之后程序执行完后虚拟机退出了虚拟机没有给容器销毁 bean 的机会所以没有 destroy 方法的显示 显示 bean 生命周期 destroy 方法 在虚拟机退出之前先将容器关闭这种方式偏暴力直接将容器关闭设置容器“关闭钩子” public static void main(String[] args) {ClassPathXmlApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);BookDao bookDao (BookDao) applicationContext.getBean(bookDao);bookDao.save();applicationContext.close();}public static void main(String[] args) {ClassPathXmlApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);applicationContext.registerShutdownHook(); // 这段代码写在之后也是可以执行的BookDao bookDao (BookDao) applicationContext.getBean(bookDao);bookDao.save();}使用接口方式显示 bean 生命周期 只需要使 Service 实现类实现 InitializingBean、DisposableBean 接口
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {// 删除业务层中使用 new 的方式创建的 dao 对象private BookDao bookDao;Overridepublic void save() {System.out.println(book service save...);bookDao.save();}// 提供对应的 set 方法public void setBookDao(BookDao bookDao) {this.bookDao bookDao;}Overridepublic void destroy() throws Exception {System.out.println(service destroy...);}Overridepublic void afterPropertiesSet() throws Exception {System.out.println(service init...);}
}public static void main(String[] args) {ClassPathXmlApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);BookDao bookDao (BookDao) applicationContext.getBean(bookDao);bookDao.save();applicationContext.close();}结果
init...
service init...
book dao save...
service destroy...
destroy...总结bean 的生命周期 初始化容器 创建对象内存分配执行构造方法执行属性注入set 操作执行 bean 初始化方法 使用 bean 执行业务操作 关闭/销毁容器 执行 bean 销毁方法 bean 的销毁时机 容器关闭前触发 bean 的销毁关闭容器方式 手动关闭容器 ConfigurableApplicationContext 接口的 close() 操作 注册关闭钩子在虚拟机退出前先关闭容器再退出虚拟机 ConfigurableApplicationContext 接口的 registerShutdownHook() 操作 bean 生命周期的控制 配置 init-methoddestroy-method 接口 InitializingBeanDisposableBean 关闭容器 ConfigurableApplicationContext close()registerShutdownHook()
7. 依赖注入方式 引入 思考向一个类中传递数据的方式有几种 普通方法set 方法构造方法 思考依赖注入描述了在容器中建立 bean 与 bean 之间依赖关系的过程如果 bean 运行需要的是数字或字符串呢 引用类型简单类型基本数据类型与字符串(String) 依赖注入方式 setter 注入 简单类型引用类型 构造器注入 简单类型引用类型
1 setter 注入
① 引用类型
在 bean 中定义引用类型属性并提供可访问的 set 方法
public class BookServiceImpl implements BookService {private BookDao bookDao;public void setBookDao(BookDao bookDao) {this.bookDao bookDao;}
}配置中使用 property 标签 ref 属性注入引用类型对象 bean idbookDao classcom.example.dao.impl.BookDaoImpl/bean idbookService classcom.example.service.impl.BookServiceImplproperty namebookDao refbookDao//bean② 简单类型
在 bean 中定义引用类型属性并提供可访问的 set 方法
public class BookDaoImpl implements BookDao {private int connectionNum;private String databaseName;public void setConnectionNum(int connectionNum) {this.connectionNum connectionNum;}public void setDatabaseName(String databaseName) {this.databaseName databaseName;}Overridepublic void save() {System.out.println(book dao save...databaseName,connectionNum);}
}配置中使用 property 标签 value 属性注入简单类型数据 bean idbookDao classcom.example.dao.impl.BookDaoImpl property namedatabaseName valuemysql/property nameconnectionNum value10//beanbean idbookService classcom.example.service.impl.BookServiceImplproperty namebookDao refbookDao//bean2 构造器注入
① 引用类型
public class BookServiceImpl implements BookService{private BookDao bookDao;Overridepublic void save() {System.out.println(book service save...);bookDao.save();}public BookServiceImpl(BookDao bookDao) {this.bookDao bookDao;}
}bean idbookDao classcom.example.dao.impl.BookDaoImpl/bean idbookService classcom.example.service.impl.BookServiceImpl!-- 这里的 name 属性中的 “bookDao” 是构造器的形参名称--constructor-arg namebookDao refbookDao//beanApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);BookService bookService (BookService) applicationContext.getBean(bookService);bookService.save();② 简单类型
public class BookDaoImpl implements BookDao {private int connectionNum;private String databaseName;public BookDaoImpl(int connectionNum, String databaseName) {this.connectionNum connectionNum;this.databaseName databaseName;}Overridepublic void save() {System.out.println(book dao save...databaseName,connectionNum);}
}bean idbookDao classcom.example.dao.impl.BookDaoImplconstructor-arg namedatabaseName valuemysql/constructor-arg nameconnectionNum value10//beanbean idbookService classcom.example.service.impl.BookServiceImpl!-- 这里的 name 属性中的 “bookDao” 是构造器的形参名称--constructor-arg namebookDao refbookDao//bean由于具有形参与配置的耦合所以采用一种去耦合的方式但是这种方式一旦具有多个相同类型的形参就无法使用 bean idbookDao classcom.example.dao.impl.BookDaoImplconstructor-arg typejava.lang.String valuemysql/constructor-arg typeint value10//beanbean idbookService classcom.example.service.impl.BookServiceImpl!-- 这里的 name 属性中的 “bookDao” 是构造器的形参名称--constructor-arg namebookDao refbookDao//bean为解决参数类型重复问题引入了一种使用索引的方式规定形参的位置顺序 bean idbookDao classcom.example.dao.impl.BookDaoImplconstructor-arg index1 valuemysql/constructor-arg index0 value10//beanbean idbookService classcom.example.service.impl.BookServiceImpl!-- 这里的 name 属性中的 “bookDao” 是构造器的形参名称--constructor-arg namebookDao refbookDao//bean3 依赖注入方式的选择
强制依赖使用构造器进行使用 setter 注入有概率不进行注入导致 null 对象出现可选依赖使用 setter 注入进行灵活性强Spring 框架倡导使用构造器第三方框架内部大多数采用构造器注入的形式进行数据初始化相对严谨如果有必要可以两者同时使用使用构造器注入完成强制依赖的注入使用 setter 注入完成可选依赖的注入实际开发过程中还要根据实际情况分析如果受控对象没有提供 setter 方法就必须使用构造器注入自己开发的模块推荐使用 setter 注入
8. 依赖自动装配 IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配 自动装配方式 按类型常用按名称按构造方法不启用自动装配
1 按类型
public class BookServiceImpl implements BookService{private BookDao bookDao;Overridepublic void save() {System.out.println(book service save...);bookDao.save();}public void setBookDao(BookDao bookDao) {this.bookDao bookDao;}}bean idbookDao classcom.example.dao.impl.BookDaoImpl/bean idbookService classcom.example.service.impl.BookServiceImpl autowirebyType/无法实现多个或零个实现类满足 dao 接口的实现类的装配即 bean idbookDao classcom.example.dao.impl.BookDaoImpl/ 不可以没有或者是多个如果需要实现多个就需要使用按名称自动装配
2 按名称 bean idbookService classcom.example.service.impl.BookServiceImpl autowirebyName/使用按名称自动装配的方式它是按照 setBookDao() 方法中去掉 set 首字母变小写的那个名称 bookDao 判断区分进行分类的所以需要用标准命名法
3 依赖自动装配特征
自动装配用于引用类型依赖注入不能对简单类型进行操作使用按类型装配时byType必须保证容器中相同类型的 bean 唯一推荐使用使用按名称装配时byName必须保证容器中具有指定名称的 bean因变量名与配置耦合不推荐使用自动装配优先级低于 setter 注入与构造器注入同时出现时自动装配配置失效
9. 集合注入 五种集合方式 数组ListSetMapProperties
public interface BookDao {public void save();
}public class BookDaoImpl implements BookDao {private int[] array;private ListString list;private SetString set;private MapString,String map;private Properties properties;public void setArray(int[] array) {this.array array;}public void setList(ListString list) {this.list list;}public void setSet(SetString set) {this.set set;}public void setMap(MapString, String map) {this.map map;}public void setProperties(Properties properties) {this.properties properties;}Overridepublic void save() {System.out.println(book dao save...);System.out.println(遍历数组 Arrays.toString(array));System.out.println(遍历 Listlist);System.out.println(遍历 Setset);System.out.println(遍历 Mapmap);System.out.println(遍历 Propertiesproperties);}
}bean idbookDao classcom.example.dao.impl.BookDaoImplproperty namearrayarray!-- 简单类型--value1/valuevalue2/valuevalue3/value!-- 引用类型--!-- ref beanbeanID--/array/propertyproperty namelistlistvalueone/valuevaluetwo/valuevaluethree/value/list/propertyproperty namesetsetvaluewhen/valuevaluewhere/valuevaluewhat/valuevaluewhat/value/set/propertyproperty namemapmapentry keycountry valueChina/entry keyprovince value广东/entry keycity value深圳//map/propertyproperty namepropertiespropsprop keycountryChina/propprop keyprovince浙江/propprop keycity杭州/prop/props/property/beanpublic static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);BookDao bookDao (BookDao) applicationContext.getBean(bookDao);bookDao.save();}10. 数据源对象管理 第三方资源配置管理以 Druid 连接池和 C3P0 连接池为例 以 Druid 连接池为例导入依赖 dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.2.15/version/dependency配置文件 !-- 管理 DruidDataSource 对象--bean iddataSource classcom.alibaba.druid.pool.DruidDataSourceproperty namedriverClassName valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://localhost:3306/spring_db/property nameusername valueroot/property namepassword valueroot//bean测试 public static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);DataSource dataSource (DataSource) applicationContext.getBean(dataSource);System.out.println(dataSource);}以 C3P0 连接池为例导入依赖 dependencygroupIdc3p0/groupIdartifactIdc3p0/artifactIdversion0.9.1.2/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.46/version/dependency配置文件 bean iddataSource classcom.mchange.v2.c3p0.ComboPooledDataSourceproperty namedriverClass valuecom.mysql.jdbc.Driver/property namejdbcUrl valuejdbc:mysql://localhost:3306/spring_db/property nameuser valueroot/property namepassword valueroot//bean11. Spring 加载 properties 文件
properties 配置文件
jdbc.drivercom.mysql.jdbc.Driver
jdbc.urljdbc:mysql://localhost:3306/spring_db
jdbc.usernameroot
jdbc.passwordroot配置文件需要配置三部分 开启 context 命名空间使用 context 命名空间加载指定 properties 文件使用 ${} 读取加载的属性值
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd!-- 1. 开启 context 命名空间 --!-- 2. 使用 context 空间加载 properties 文件 --context:property-placeholder locationjdbc.properties/!-- 3. 使用属性占位符 ${} 读取 properties 文件中的属性 --bean iddataSource classcom.mchange.v2.c3p0.ComboPooledDataSourceproperty namedriverClass value${jdbc.driver}/property namejdbcUrl value${jdbc.url}/property nameuser value${jdbc.username}/property namepassword value${jdbc.password}//bean
/beans为了显示效果需要借助一些配置和接口实现方法来展示是否配置成功
public class BookDaoImpl implements BookDao {private String name;public void setName(String name) {this.name name;}Overridepublic void save() {System.out.println(bookDao save...name);}
}?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd!-- 1. 开启 context 命名空间 --!-- 2. 使用 context 空间加载 properties 文件 --context:property-placeholder locationjdbc.properties/!-- 3. 使用属性占位符 ${} 读取 properties 文件中的属性 --bean iddataSource classcom.mchange.v2.c3p0.ComboPooledDataSourceproperty namedriverClass value${jdbc.driver}/property namejdbcUrl value${jdbc.url}/property nameuser value${jdbc.username}/property namepassword value${jdbc.password}//bean!-- 此处的配置仅仅是为了通过 bookDao 展示配置效果--bean idbookDao classcom.example.dao.impl.BookDaoImplproperty namename value${jdbc.url}//bean
/beanspublic static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);// DataSource dataSource (DataSource) applicationContext.getBean(dataSource);// System.out.println(dataSource);BookDao bookDao (BookDao) applicationContext.getBean(bookDao);bookDao.save();}对于加载 properties 文件context 空间加载的多种配置
不加载系统属性为了避免使用中的操作系统设置干扰 context:property-placeholder locationjdbc.properties system-properties-modeNEVER/加载多个 properties 文件 context:property-placeholder locationjdbc.properties,msg.properties/加载所有 properties 文件 context:property-placeholder location*.properties/加载 properties 文件标准格式本工程中的 properties 文件推荐使用 context:property-placeholder locationclasspath:*.properties/从类路径或 jar 包中搜索并加载 properties 文件其他工程和 jar 包中的 properties 文件 context:property-placeholder locationclasspath*:*.properties/12. 容器
1 创建容器
创建容器分为两种 // 1. 加载类路径下的配置文件ApplicationContext applicationContext01 new ClassPathXmlApplicationContext(applicationContext.xml);// 2. 从文件系统下加载配置文件绝对路径ApplicationContext applicationContext02 new ClassPathXmlApplicationContext(E:\\Test\\spring-test\\src\\main\\resources\\applicationContext.xml);// 加载多个配置文件ApplicationContext applicationContext01 new ClassPathXmlApplicationContext(bean01.xml,bean02.xml);2 获取 bean
获取 bean 分为三种 // 1. 获取 bean 常规的类型强转式写法BookDao bookDao01 (BookDao) applicationContext.getBean(bookDao);// 2. 获取 bean 的时候通过它的名称指定它的类型BookDao bookDao02 applicationContext.getBean(bookDao,BookDao.class);// 3. 获取 bean 的时候直接按类型找但是前提条件容器中对应的 bean 只有它一个必须唯一BookDao bookDao03 applicationContext.getBean(BookDao.class);3 容器类层次结构 CTRL H 查看该类或接口的层次结构
4 BeanFactorybean 的最高层次接口
使用 BeanFactroy 接口实现需求的操作 类路径加载配置文件 bean idbookDao classcom.example.dao.impl.BookDaoImpl/public static void main(String[] args) {Resource resources new ClassPathResource(applicationContext.xml);BeanFactory beanFactory new XmlBeanFactory(resources);BookDao bookDao beanFactory.getBean(BookDao.class);bookDao.save();}BeanFactory 与 ApplicationContext 的核心区别两者加载 bean 的时机不同 BeanFactory 是延迟加载 bean ApplicationContext 是立即加载 bean启动容器直接初始化完毕验证通过构造器测试
public class BookDaoImpl implements BookDao {public BookDaoImpl() {System.out.println(constructor...);}Overridepublic void save() {System.out.println(bookDao save...);}
}public static void main(String[] args) {// 1. BeanFactory 的加载Resource resources new ClassPathResource(applicationContext.xml);BookDao bookDao beanFactory.getBean(BookDao.class);// 2. ApplicationContext 的加载ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);}要想 ApplicationContext 实现延迟加载需要在配置中加入一个属性 lazy-inittrue bean idbookDao classcom.example.dao.impl.BookDaoImpl lazy-inittrue/13. 核心容器总结
1 容器相关
BeanFactory 是 IoC 容器的顶层接口初始化 BeanFactory 对象时加载的 bean 延迟加载ApplicationContext 接口是 Spring 容器的核心接口初始化时 bean 立即加载ApplicationContext 接口提供基础的 bean 操作相关方法通过其他接口扩展其功能ApplicationContext 接口常用初始化类 ClassPathXmlApplicationContextFileSystemXmlApplicationContext
2 bean 相关 bean idbookDao bean 的 IDnamebookDaoImpl daoImpl bean 的别名classcom.example.dao.impl.BookDaoImpl bean 的类型静态工厂类FactoryBean 类scopesingleton 控制 bean 的实例数量init-methodinit 生命周期的初始化方法destroy-methoddestroy 生命周期的销毁方法autowirebyType 自动装配类型factory-methodgetInstance bean 工厂方法应用于静态工厂或实例工厂factory-beancom.example.factory.BookDaoFactory 实例工厂 beanlazy-inittrue 控制 bean 延迟加载/3 依赖注入相关 bean idbookService classcom.example.service.impl.BookServiceImplconstructor-arg namebookDao refbookDao/ 构造器注入引用类型constructor-arg nameuserDao refuserDao/ constructor-arg namemsg valueWARN/ 构造器注入简单类型constructor-arg typejava.lang.String index3 valueWARN/ 类型匹配与索引匹配property namebookDao refbookDao/ setter 注入引用类型property nameuserDao refuserDao/property namemsg valueWARN/ setter 注入简单类型property namenames setter 注入集合类型list list 集合valueexample/value 集合注入简单类型ref beandataSource/ 集合注入引用类型/list/property/bean四、 注解开发
1. 注解开发定义 bean
使用 Component 定义 bean
Component(bookDao) // 可以理解为指定名称指定的名称就是 ID
public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(bookDao save...);}
}Component
public class BookServiceImpl implements BookService{private BookDao bookDao;Overridepublic void save() {System.out.println(book service save...);bookDao.save();}public void setBookDao(BookDao bookDao) {this.bookDao bookDao;}
}核心配置文件中通过组件加载 bean
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd!--指定到哪一级系统就会递归查找该级目录下的所有类以及子包中的类--context:component-scan base-packagecom.example//beanspublic static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml);BookDao bookDao (BookDao) applicationContext.getBean(bookDao);System.out.println(bookDao);BookServiceImpl bookService applicationContext.getBean(BookServiceImpl.class);System.out.println(bookService);}Spring 提供了 Component 注解的三个衍生注解功能和 Component 一样只是为了方便理解和区分 Controller 用于表现层 bean 定义Service 用于业务层 bean 定义Repository 用于数据层 bean 定义
2. 纯注解开发 Spring 3.0 升级了纯注解开发模式使用 Java 类替代配置文件开启了 Spring 快速开发的赛道 创建 SpringConfig 类代替 applicationContext.xml 配置文件 原来配置文件的写法
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdcontext:component-scan base-packagecom.example/
/beans改为写成配置类作用完全一样
Configuration
ComponentScan(com.example)
public class SpringConfig {
}测试类 public static void main(String[] args) {ApplicationContext annotationConfigApplicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao (BookDao) annotationConfigApplicationContext.getBean(bookDao);System.out.println(bookDao);BookServiceImpl bookService annotationConfigApplicationContext.getBean(BookServiceImpl.class);System.out.println(bookService);}注意 Configuration 注解用于设定当前类为配置类ComponentScan 注解用于设定扫描路径此注解只能添加一次多个数据需要使用数组格式两种方式对比读取 Spring 核心配置文件初始化容器对象切换为读取 Java 配置类初始化容器对象
ComponentScan({com.example.service,com.example.dao})3. bean 的管理
1 bean 的作用范围
使用 Scope 注解定义 bean 的作用范围
Repository
// Scope(singleton) // 单例模式
Scope(prototype) // 非单例模式
public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(bookDao save...);}
}Configuration
ComponentScan(com.example)
public class SpringConfig {
}public static void main(String[] args) {ApplicationContext annotationConfigApplicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao01 annotationConfigApplicationContext.getBean(BookDao.class);BookDao bookDao02 annotationConfigApplicationContext.getBean(BookDao.class);System.out.println(bookDao01);System.out.println(bookDao02);}2 bean 的生命周期
使用 PostConstruct、PreDestroy 注解定义 bean 生命周期
Repository
Scope(singleton) // 单例模式
public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(bookDao save...);}PostConstruct // 在构造器之后public void init(){System.out.println(init...);}PreDestroy // 在销毁之前public void destroy(){System.out.println(destroy...);}
}注意销毁时的“关闭钩子”或“关闭容器”操作需要使用 AnnotationConfigApplicationContext 这个类 public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao01 annotationConfigApplicationContext.getBean(BookDao.class);BookDao bookDao02 annotationConfigApplicationContext.getBean(BookDao.class);System.out.println(bookDao01);System.out.println(bookDao02);annotationConfigApplicationContext.close();}4. 依赖注入自动装配
1 引用类型注入
使用 Autowired 注解开启自动装配模式按类型
Configuration
ComponentScan(com.example)
public class SpringConfig {
}单个数据层的实现类按类型进行装配
Repository
public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(bookDao save...);}
}Service
public class BookServiceImpl implements BookService{Autowired // 此处是按类型装配的使用反射中的暴力反射private BookDao bookDao;Overridepublic void save() {System.out.println(book service save...);bookDao.save();}
}public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookService bookService annotationConfigApplicationContext.getBean(BookService.class);bookService.save();}多个数据层的实现类使用按名称进行装配 使用 Qualifier 注解开启指定名称装配 beanQualifier 注解无法单独使用必须配合 Autowired 注解使用
Repository(bookDao01)
public class BookDaoImpl01 implements BookDao {Overridepublic void save() {System.out.println(bookDao01 save...);}
}Repository(bookDao02)
public class BookDaoImpl02 implements BookDao {Overridepublic void save() {System.out.println(bookDao02 save...);}
}Service
public class BookServiceImpl implements BookService{AutowiredQualifier(bookDao02) // 使用该注解还必须依赖 Autowired 注解// 如果没有相同类型的 bean一般使用不到这个注解private BookDao bookDao;Overridepublic void save() {System.out.println(book service save...);bookDao.save();}
}注意 自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据因此无需提供 setter 方法自动装配建议使用无参构造方法创建对象默认如果不提供对应的构造方法需要提供唯一的构造方法
2 简单类型注入 由于 value 这个单词用于简单类型的注入所以简单类型也常被称为“值类型” 使用 Value 实现简单类型注入
Repository(bookDao)
public class BookDaoImpl implements BookDao {Value(example)private String name;Overridepublic void save() {System.out.println(bookDao save...name);}
}简单类型的数据多数是会变化的所以可能会用到引入 properties 文件引入数据
nameexampleConfiguration
ComponentScan(com.example)
PropertySource(data.properties)
public class SpringConfig {
}Repository(bookDao)
public class BookDaoImpl implements BookDao {Value(${name})private String name;Overridepublic void save() {System.out.println(bookDao save...name);}
}注意 PropertySource 注解需要引入多个配置文件时需要使用 PropertySource({data01.properties,data02.properties,data03.properties}) 的格式PropertySource 注解路径仅支持单一文件配置不支持使用通配符
5. 第三方 bean 管理 第三方 bean 管理 第三方 bean 依赖注入 1 第三方 bean 管理
① 基本写法
导入 Druid 依赖坐标 dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.2.15/version/dependency使用 Bean 注解配置第三方 bean 然后定义一个方法方法定义完成后方法的返回值就是需要的 bean这种操作与工厂模式很像就是用来替代工厂模式的
Configuration
public class SpringConfig {// 1. 定义一个方法获得要管理的对象// 2. 添加 Bean 注解表示当前方法的返回值是一个 beanBeanpublic DataSource dataSource(){DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(com.mysql.cj.jdbc.Driver);druidDataSource.setUrl(jdbc:mysql://localhost:3306/spring_db);druidDataSource.setUsername(root);druidDataSource.setPassword(root);return druidDataSource;}
}public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext new AnnotationConfigApplicationContext(SpringConfig.class);DataSource dataSource annotationConfigApplicationContext.getBean(DataSource.class);System.out.println(dataSource);}② 解耦写法 由于示例中 JDBC 所属的配置文件写到 Spring 配置文件中假如再多一些配置会导致 Spring 配置臃肿所以一般采用单独抽出需求的配置最后导入 Spring 配置的方式 将独立的配置加入到核心配置中
Ⅰ、 扫描式
将 JDBC 配置文件也添加为配置类之后在 Spring 配置文件中扫描加载导入不推荐使用使用 ComponentScan 注解扫描配置类所在的包加载对应的配置类信息
Configuration
ComponentScan(com.example.config)
public class SpringConfig {
}Configuration
public class JdbcConfig {Beanpublic DataSource dataSource(){DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(com.mysql.cj.jdbc.Driver);druidDataSource.setUrl(jdbc:mysql://localhost:3306/spring_db);druidDataSource.setUsername(root);druidDataSource.setPassword(root);return druidDataSource;}
}Ⅱ、 导入式
这种方式可以清楚的看清导入了哪些配置文件推荐使用使用 Import 注解手动加入配置到核心配置此注解只能添加一次多个数据需要使用数组格式
Configuration
Import({JdbcConfig.class}) // 依旧是支持数组形式的导入
public class SpringConfig {
}public class JdbcConfig {Beanpublic DataSource dataSource(){DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(com.mysql.cj.jdbc.Driver);druidDataSource.setUrl(jdbc:mysql://localhost:3306/spring_db);druidDataSource.setUsername(root);druidDataSource.setPassword(root);return druidDataSource;}
}2 第三方 bean 依赖注入
① 简单类型注入
public class JdbcConfig {Value(com.mysql.cj.jdbc.Driver)private String driver;Value(jdbc:mysql://localhost:3306/spring_db)private String url;Value(root)private String username;Value(root)private String password;Beanpublic DataSource dataSource() {DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(driver);druidDataSource.setUrl(url);druidDataSource.setUsername(username);druidDataSource.setPassword(password);return druidDataSource;}
}② 引用类型注入
假如此处 JDBC 配置文件需要导入 BookDao 这个类在核心配置文件中扫描加载导入
Configuration
ComponentScan(com.example)
Import({JdbcConfig.class})
public class SpringConfig {
}Repository
public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(bookDao save...);}
}public class JdbcConfig {Value(com.mysql.cj.jdbc.Driver)private String driver;Value(jdbc:mysql://localhost:3306/spring_db)private String url;Value(root)private String username;Value(123456)private String password;Beanpublic DataSource dataSource(BookDao bookDao) { // 引用类型需要在方法名之后的形参位置填上所需要的引用数据System.out.println(bookDao);DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(driver);druidDataSource.setUrl(url);druidDataSource.setUsername(username);druidDataSource.setPassword(password);return druidDataSource;}
}public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext new AnnotationConfigApplicationContext(SpringConfig.class);DataSource dataSource annotationConfigApplicationContext.getBean(DataSource.class);System.out.println(dataSource);}原理通过自动装配的方法达到只添加一个形参变量可以达到导入的效果系统检测到了配置正在配置一个的 bean所以系统就会在容器中找这个类型的 bean 并自动提供按类型自动装配 只要在该示例中将 BookDaoImpl 的 Repository 注解删除就会导致系统找不到这个类型的 bean 报错简单来说引用类型注入只需要为 bean 定义方法设置形参即可容器会根据类型自动装配对象
6. 总结
XML 配置与注解配置比较
五、 Spring 整合
1. Spring 整合 Mybatis
1 简单的 Mybatis 应用项目
数据库中创建一张简易表格table_account作为数据查询 导入依赖 dependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.6/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.2.15/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.47/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.24/version/dependency
新建 domain 目录并创建 Account 实体类
public class Account implements Serializable {private Integer id;private String name;private Double money;Overridepublic String toString() {return Account{ id id , name name \ , money money };}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money money;}
}
JDBC 配置文件 jdbc.properties
jdbc.drivercom.mysql.jdbc.Driver
jdbc.urljdbc:mysql://localhost:3306/spring_data?useSSLfalse
jdbc.usernameroot
jdbc.passwordroot数据层接口 AccountDao 类
public interface AccountDao {Insert(insert into table_account(name,money) values(#{name},#{money}))void save(Account account);Delete(delete from table_account where id#{id})void delete(Integer id);Update(update table_account set name#{name},money#{money} where id#{id})void update(Account account);Select(select * from table_account)ListAccount findAll();Select(select * from table_account where id#{id})Account findById(Integer id);
}业务层接口 AccountService 接口
public interface AccountService {public void save(Account account);public void update(Account account);public void delete(Integer id);public Account findById(Integer id);public ListAccount findAll();
}接口实现类
public class AccountServiceImpl implements AccountService {private AccountDao accountDao;Overridepublic void save(Account account) {accountDao.save(account);}Overridepublic void update(Account account) {accountDao.update(account);}Overridepublic void delete(Integer id) {accountDao.delete(id);}Overridepublic Account findById(Integer id) {accountDao.findById(id);return null;}Overridepublic ListAccount findAll() {return accountDao.findAll();}
}JdbcConfig 配置类
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;Beanpublic DataSource dataSource() {DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(driver);druidDataSource.setUrl(url);druidDataSource.setUsername(username);druidDataSource.setPassword(password);return druidDataSource;}
}Mybatis 核心配置类
?xml version1.0 encodingUTF-8?
!DOCTYPE configurationPUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtd
configurationproperties resourcejdbc.properties/propertiestypeAliasespackage namecom.example.domain//typeAliasesenvironments defaultmysqlenvironment idmysqltransactionManager typeJDBC/transactionManagerdataSource typePOOLEDproperty namedriver value${jdbc.driver}/propertyproperty nameurl value${jdbc.url}/propertyproperty nameusername value${jdbc.username}/propertyproperty namepassword value${jdbc.password}/property/dataSource/environment/environmentsmapperspackage namecom.example.dao/package/mappers
/configuration测试类 public static void main(String[] args) throws Exception {// 1. 创建 SqlSessionFactoryBuilder 对象SqlSessionFactoryBuilder sqlSessionFactoryBuilder new SqlSessionFactoryBuilder();// 2. 加载 SqlMapConfig.xml 配置文件InputStream inputStream Resources.getResourceAsStream(SqlMapConfig.xml);// 3. 创建 SqlSessionFactory 对象SqlSessionFactory sqlSessionFactory sqlSessionFactoryBuilder.build(inputStream);// 4. 获取 SqlSessionSqlSession sqlSession sqlSessionFactory.openSession();// 5. 执行 SqlSession 对象执行查询获取结果 UserAccountDao accountDao sqlSession.getMapper(AccountDao.class);Account account accountDao.findById(1);System.out.println(account);// 6. 释放资源sqlSession.close();}2 思路分析
分析测试类得到操作的核心对象是 SqlSessionFactory public static void main(String[] args) throws Exception {// 初始化 SqlSessionFactory// 1. 创建 SqlSessionFactoryBuilder 对象SqlSessionFactoryBuilder sqlSessionFactoryBuilder new SqlSessionFactoryBuilder();// 2. 加载 SqlMapConfig.xml 配置文件InputStream inputStream Resources.getResourceAsStream(SqlMapConfig.xml);// 3. 创建 SqlSessionFactory 对象SqlSessionFactory sqlSessionFactory sqlSessionFactoryBuilder.build(inputStream);// 获取连接获取实现// 4. 获取 SqlSessionSqlSession sqlSession sqlSessionFactory.openSession();// 5. 执行 SqlSession 对象执行查询获取结果 UserAccountDao accountDao sqlSession.getMapper(AccountDao.class);// 获取数据层接口Account account accountDao.findById(1);System.out.println(account);// 关闭连接// 6. 释放资源sqlSession.close();}分析 Mybatis 核心配置类
!-- 初始化属性数据 --
configurationproperties resourcejdbc.properties/properties!-- 初始化类型别名 --typeAliasespackage namecom.example.domain//typeAliases!-- 初始化 DataSource --environments defaultmysqlenvironment idmysqltransactionManager typeJDBC/transactionManagerdataSource typePOOLEDproperty namedriver value${jdbc.driver}/propertyproperty nameurl value${jdbc.url}/propertyproperty nameusername value${jdbc.username}/propertyproperty namepassword value${jdbc.password}/property/dataSource/environment/environments!-- 初始化映射配置 --mapperspackage namecom.example.dao/package/mappers
/configuration得到 Mybatis 应该管理 SqlSessionFactory 对象
3 Spring 整合 Mybatis
导入依赖注意版本对应 dependencygroupIdorg.springframework/groupIdartifactIdspring-jdbc/artifactIdversion5.2.10.RELEASE/version/dependencydependencygroupIdorg.mybatis/groupIdartifactIdmybatis-spring/artifactIdversion1.3.0/version/dependency使用注解进行开发创建 config 目录并在该目录下创建 SpringConfig 类
Configuration
ComponentScan(com.example)
PropertySource(classpath:jdbc.properties)
Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}实现类添加注解
Service
public class AccountServiceImpl implements AccountService {Autowiredprivate AccountDao accountDao;Overridepublic void save(Account account) {accountDao.save(account);}Overridepublic void update(Account account) {accountDao.update(account);}Overridepublic void delete(Integer id) {accountDao.delete(id);}Overridepublic Account findById(Integer id) {return accountDao.findById(id);}Overridepublic ListAccount findAll() {return accountDao.findAll();}
}配置数据源在 config 目录下创建 JdbcConfig 类
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;Beanpublic DataSource dataSource(BookDao bookDao) {DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(driver);druidDataSource.setUrl(url);druidDataSource.setUsername(username);druidDataSource.setPassword(password);return druidDataSource;}
}在 config 目录下创建 MybatisConfig 类进行替代 Mybatis 核心配置类
public class MybatisConfig {Beanpublic SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setTypeAliasesPackage(com.example.domain);sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer mapperScannerConfigurer new MapperScannerConfigurer();mapperScannerConfigurer.setBasePackage(com.example.dao);return mapperScannerConfigurer;}
}测试 public static void main(String[] args) throws IOException {ApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig.class);AccountService accountService applicationContext.getBean(AccountService.class);Account account accountService.findById(1);System.out.println(account);}2. Spring 整合 JUnit 在上文中整合 Mybatis 的前提下进行 Junit 整合 1 导入依赖 dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.13.2/versionscopetest/scope/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-test/artifactIdversion5.2.10.RELEASE/version/dependency2 测试
使用 Spring 整合 Junit 专用的类加载器
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes SpringConfig.class)
public class AccountServiceTest {Autowiredprivate AccountService accountService;Testpublic void testFindById(){System.out.println(accountService.findById(2));}Testpublic void testFindAll(){System.out.println(accountService.findAll());}
}六、 AOP
1. AOP 简介
1 AOP 核心概念
AOPAspect Oriented Programming面向切面编程一种编程范式知道开发者如何组织程序结构 OOPObject Oriented Programming面向对象编程 AOP 作用 在不惊动原始设计的基础上为其进行功能增强 Spring 理念无侵入式/无入侵式核心概念 代理ProxySpringAOP 的核心本质是采用代理模式实现的连接点JoinPoint程序执行过程中的任意位置粒度为执行方法抛出异常设置变量等 在 SpringAOP 中理解为任意方法的执行 切入点PointCut匹配连接点的式子 一个具体的方法com.example.dao 包下的 BookDao 接口中的无形参无返回值的 save 方法匹配多个方法所有的 save 方法所有 get 开头的方法所有以 Dao 结尾的接口中的任意方法所有带有一个参数的方法 通知Advice在切入点处执行的操作也就是共性功能 在 SpringAOP 中功能最终以方法的形式呈现若干方法的共性功能在切入点处执行最终体现为一个方法 通知类定义通知的类切面Aspect描述通知与切入点的对应关系目标对象Target被代理的原始对象称为目标对象
2. AOP 入门案例 案例设定测试接口执行效率 简化设定在接口执行前输出当前系统时间 开发模式XML 或者注解 思路分析 导入坐标pom.xml制作连接点方法原始操作Dao 接口与实现类制作共性功能通知类与通知
1 导入依赖 dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.2.10.RELEASE/version/dependencydependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.9.4/version/dependency导入 spring-context 依赖默认自动导入 spring_aop 依赖 常规的定义接口定义实现类
public interface BookDao {public void save();public void update();
}Repository
public class BookDaoImpl implements BookDao {Overridepublic void save() {System.out.println(book dao save);System.out.println(System.currentTimeMillis()); // 原本程序中的方法}Overridepublic void update() {System.out.println(book dao update);}
}正常的运行测试类 public static void main(String[] args) {ApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao applicationContext.getBean(BookDao.class);bookDao.update();}仅需改动配置类中的部分内容以及定义一个通知类创建一个 aop 目录定义通知类 MyAdvice 定义切入点切入点定义依托一个不具有实际意义的方法进行即无参数无返回值方法体无实际逻辑绑定切入点与通知关系并指定通知添加到原始连接点的具体执行位置
Component
Aspect
public class MyAdvice {// 定义切入点在哪里执行Pointcut(execution(void com.example.dao.BookDao.update()))public void pointCut(){}Before(pointCut()) // 绑定切入点和通知// 定义通知共性功能public void method(){System.out.println(System.currentTimeMillis());}
}Configuration
ComponentScan(com.example)
EnableAspectJAutoProxy
public class SpringConfig {
}3. AOP 工作流程
Spring 容器启动读取所有切面配置中的切入点只会读取配置的切入点 初始化 bean判定 bean 对应的类中的方法是否匹配到任意切入点 匹配失败创建对象匹配成功创建原始对象目标对象的代理对象 获取 bean 执行方法 获取 bean 调用方法并执行完成操作获取的 bean 是代理对象时根据代理对象的运行模式运行原始方法与增强的内容完成操作
AOP 核心概念 目标对象Target原始功能去掉共性功能对应的类产生的对象这种对象是无法直接完成最终工作的代理Proxy目标对象无法直接完成工作需要对其进行功能回填通过原始对象的代理对象实现
4. AOP 切入点表达式
1 语法格式 切入点要进行增强的方法 切入点表达式要进行增强的方法的描述方式 对于上述示例
描述方式一执行 com.example.dao 包下的 BookDao 接口中的无参数 update 方法execution(void com.example.dao.BookDao.update())描述方式二执行 com.example.dao.impl 包下的 BookDaoImpl 类中的无参数 update 方法execution(void com.example.dao.impl.BookDaoImpl.update())
切入点表达式标准格式动作关键字访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名execution(public User com.example.service.UserService.findById(int)) 动作关键字描述切入点行为动作例如 execution 表示执行到指定切入点访问修饰符publicprivate 等可以省略返回值包名类/接口名方法名参数异常名方法定义中抛出指定异常可以省略
2 通配符
可以使用通配符描述切入点快速描述 * 单个独立的任意符号可以独立出现也可以作为前缀或者后缀的匹配符出现 execution(public * com.example.*.UserService.find*(*))匹配 com.example 包下的任意包中的 UserService 类或接口中所有 find 开头的带有一个参数的方法 .. 多个连续的任意符号可以独立出现常用于简化包名与参数的书写 execution(public User com..UserService.findById(..))匹配 com 包下的任意包中的 UserService 类或接口中的所有名称为 findById 的方法 专用于匹配子类类型 execution(* *..*Service.*(..))
3 书写技巧
书写技巧 所有代码按照标准规范开发否则一下技巧全部失效描述切入点通常描述接口而不描述实现类访问控制修饰符针对接口开发均采用 public 描述可省略访问控制修饰符描述返回值类型对于增删改类使用精准类型加速匹配对于查询类使用 * 通配符快速描述包名书写尽量不使用 .. 匹配效率过低常用 * 做单个包描述匹配或精准匹配接口名/类名书写名称与模块相关的采用 * 匹配例如 UserService 书写成 *Service 绑定业务层接口名方法名书写以动词进行精准匹配名词采用 * 匹配例如 getById 书写成 getBy*selectAll 书写成 selectAll参数规则较为复杂根据业务方法灵活调整通常不使用异常作为匹配规则
5. AOP 通知类型
AOP 通知描述了抽取的共性功能根据共性功能抽取的位置不同最终运行代码时要将其加入到合适的位置AOP 通知共分为5种类型 前置通知后置通知环绕通知重点 环绕通知依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用环绕通知可以隔离原始方法的调用执行环绕通知返回值设置为 Object 类型环绕通知中可以对原始方法调用过程中出现的异常进行处理 返回后通知了解抛出异常后通知了解
1 前置和后置通知
① 前置通知
名称Before类型方法注解位置通知方法定义上方作用设置当前通知方法与切入点之间的绑定关系当前通知方法在原始切入点方法后运行相关属性value默认切入点方法格式为 类名.方法名()
② 后置通知
名称After类型方法注解位置通知方法定义上方作用设置当前通知方法与切入点之间的绑定关系当前通知方法在原始切入点方法前运行相关属性value默认切入点方法格式为 类名.方法名()
③ 测试使用
接口
public interface BookDao {public void update();public int select();
}实现类
Repository
public class BookDaoImpl implements BookDao {Overridepublic void update() {System.out.println(book dao update is running...);}Overridepublic int select() {System.out.println(book dao select is running...);return 100;}
}配置
Configuration
ComponentScan(com.example)
EnableAspectJAutoProxy
public class SpringConfig {
}通知类
Component
Aspect
public class MyAdvice {Pointcut(execution(void com.example.dao.BookDao.update()))public void pointCut(){}Before(pointCut())public void before(){System.out.println(before advice ...);}After(pointCut())public void after(){System.out.println(after advice ...);}
}测试 public static void main(String[] args) {ApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao applicationContext.getBean(BookDao.class);bookDao.update();}2 环绕通知
① 环绕通知
名称Around类型方法注解位置通知方法定义上方作用设置当前通知方法与切入点之间的绑定关系当前通知方法在原始切入点方法前后运行相关属性value默认切入点方法格式为 类名.方法名()
② 测试使用
Component
Aspect
public class MyAdvice {Pointcut(execution(void com.example.dao.BookDao.update()))public void pointCut(){}Pointcut(execution(int com.example.dao.BookDao.select()))public void pointCut02(){}Around(pointCut())public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 所以这里标准的写法应该是返回 Object 类System.out.println(around before advice ...);// 表示对原始操作的调用proceedingJoinPoint.proceed();System.out.println(around after advice ...);}Around(pointCut02())public Object aroundSelect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println(around before advice ...);// 表示对原始操作的调用Object proceed proceedingJoinPoint.proceed();// 环绕通知对于存在返回值的方法可以取出返回值通常使用 Object 类返回如果需要具体的类型需要进行强制类型转换System.out.println(around after advice ...);return proceed;}
}测试 public static void main(String[] args) {ApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao applicationContext.getBean(BookDao.class);int select bookDao.select();System.out.println(select);}③ Around 注意事项
环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用进而实现原始方法调用前后同时添加通知通知中如果未使用 ProceedingJoinPoint 对原始方法进行调用将跳过原始方法的执行就会产生一种对原始操作隔离的效果可以做一些权限校验对原始方法的调用可以不接收返回值通知方法设置成 void 即可如果接收返回值必须设定成 Object 类型原始方法的返回值如果是 void 类型通知方法的返回值类型可以设置成 void也可以设置成 Object由于无法预知原始方法运行后是否会抛出异常因此环绕通知方法必须抛出 Throwable 对象
规范写法 Around(pointCut())public Object aroundSelect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println(around before advice ...);Object ret proceedingJoinPoint.proceed();System.out.println(around after advice ...);return ret;}3 返回后通知
① 返回后通知
名称AfterReturning类型方法注解位置通知方法定义上方作用设置当前通知方法与切入点之间的绑定关系当前通知方法在原始切入点方法正常执行完毕后运行相关属性value默认切入点方法格式为 类名.方法名()
② 测试使用
对比于后置通知
Repository
public class BookDaoImpl implements BookDao {Overridepublic void update() {System.out.println(book dao update is running...);}Overridepublic int select() {System.out.println(book dao select is running...);int i1/0; // 故意写出错误return 100;}
}Component
Aspect
public class MyAdvice {Pointcut(execution(void com.example.dao.BookDao.update()))public void pointCut(){}Pointcut(execution(int com.example.dao.BookDao.select()))public void pointCut02(){}After(pointCut02())public void after(){System.out.println(after advice ...);}AfterReturning(pointCut02()) // 只有在方法正常运行没有抛异常时此方法才会运行public void afterReturning(){System.out.println(afterReturning advice ...);}
}4 抛出异常后通知
① 返回后通知
名称AfterThrowing类型方法注解位置通知方法定义上方作用设置当前通知方法与切入点之间的绑定关系当前通知方法在原始切入点方法运行抛出异常后执行相关属性value默认切入点方法格式为 类名.方法名()
② 测试使用
Repository
public class BookDaoImpl implements BookDao {Overridepublic void update() {System.out.println(book dao update is running...);}Overridepublic int select() {System.out.println(book dao select is running...);// int i1/0; // 故意写出错误// 如果此处没有异常则不会显示抛出异常后通知反之如果存在异常则会执行后文中的抛出异常后通知return 100;}
}Component
Aspect
public class MyAdvice {Pointcut(execution(void com.example.dao.BookDao.update()))public void pointCut(){}Pointcut(execution(int com.example.dao.BookDao.select()))public void pointCut02(){}AfterThrowing(pointCut02())public void afterThrowing(){System.out.println(afterThrowing advice ...);}
}6. 案例测量业务层接口万次执行效率 需求任意业务层接口执行均可显示其执行效率执行时长 分析 业务功能业务层接口执行前后分别记录时间求差值得到执行效率通知类型选择前后均可以增强的类型使用**环绕通知 ** 依赖于上文示例 Account 数据表中的案例 环境准备依赖导入、jdbc.properties、JdbcConfig、MybatisConfig、Account、AccountDao、AccountService、AccountServiceImpl 改动配置
Configuration
ComponentScan(com.example)
PropertySource(classpath:jdbc.properties)
Import({JdbcConfig.class,MybatisConfig.class})
EnableAspectJAutoProxy
public class SpringConfig {
}AOP 的核心配置
Component
Aspect
public class ProjectAdvice {// 匹配业务层的所有方法Pointcut(execution(* com.example.service.*Service.*(..)))public void servicePointCut(){}Around(ProjectAdvice.servicePointCut())public void runSpeed(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Signature signature proceedingJoinPoint.getSignature();String className signature.getDeclaringTypeName();String methodName signature.getName();long start System.currentTimeMillis();for (int i 0; i 10000; i) {proceedingJoinPoint.proceed();}long end System.currentTimeMillis();System.out.println(万次执行className.methodName------ (end-start)ms);}
}测试类使用 Junit 测试类中创建方法进行测试
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes SpringConfig.class)
public class AccountServiceTestCase {Autowiredprivate AccountService accountService;Testpublic void testFindById(){Account account accountService.findById(1);System.out.println(account);}Testpublic void testFindAll(){ListAccount all accountService.findAll();System.out.println(all);}
}
7. AOP 通知获取数据 获取参数 获取切入点方法的参数 JoinPoint适用于前置、后置、返回后、抛出异常后通知ProceedJoinPoint适用于环绕通知 获取返回值 获取切入点方法返回值 返回后通知环绕通知 获取异常 获取切入点方法运行异常信息 抛出异常后通知环绕通知
1 获取参数 测试使用 JointPoint 对象描述了连接点方法的运行状态可以获取到原始方法的调用参数ProceedJoinPoint 是 JoinPoint 的子类 使用上文中的依赖 接口
public interface BookDao {public String findName(int id,String password);
}实现类
Repository
public class BookDaoImpl implements BookDao {Overridepublic String findName(int id, String password) {System.out.println(idid);return example;}
}配置
Configuration
ComponentScan(com.example)
EnableAspectJAutoProxy
public class SpringConfig {
}核心 AOP 配置
Component
Aspect
public class MyAdvice {Pointcut(execution(* com.example.dao.BookDao.findName(..)))private void pointCut(){}// 此处物种方式都能实现获取参数此处仅演示了前三种// Before(pointCut())public void before(JoinPoint joinPoint){Object[] args joinPoint.getArgs();System.out.println(Arrays.toString(args));System.out.println(before advice ...);}// After(pointCut())public void after(JoinPoint joinPoint){Object[] args joinPoint.getArgs();System.out.println(Arrays.toString(args));System.out.println(after advice ...);}Around(pointCut())public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Object[] args proceedingJoinPoint.getArgs();System.out.println(Arrays.toString(args));args[0]666; // 在调用原方法传递参数之前改动参数值如果本需要传递的参数有问题此处就可以添加默认值增强程序的健壮性Object ret proceedingJoinPoint.proceed(args);return ret;}// AfterReturning(pointCut())public void afterReturning(){System.out.println(afterReturning advice ...);}// AfterThrowing(pointCut())public void afterThrowing(){System.out.println(afterThrowing advice ...);}
}测试 public static void main(String[] args) {ApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao applicationContext.getBean(BookDao.class);String name bookDao.findName(100,123456);System.out.println(name);}2 获取返回值
测试使用 抛出异常后通知可以获取切入点方法中出现的异常信息使用形参可以接收对应的异常对象环绕通知中可以手工书写对原始方法的调用得到的结果即为原始方法的返回值
Component
Aspect
public class MyAdvice {Pointcut(execution(* com.example.dao.BookDao.findName(..)))private void pointCut(){}// 以下两种操作都可以实现主要演示后一种Around(pointCut())public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Object[] args proceedingJoinPoint.getArgs();System.out.println(Arrays.toString(args));args[0]666; // 在调用原方法传递参数之前改动参数值如果本需要传递的参数有问题此处就可以添加默认值增强程序的健壮性Object ret proceedingJoinPoint.proceed(args);return ret;}// AfterReturning(value pointCut(),returning ret)public void afterReturning(JoinPoint joinPoint,Object ret){ // 如果此处同时使用 JoinPoint 和返回值必须 JoinPoint 在前System.out.println(afterReturning advice ... ret);}
}3 获取异常
抛出异常后通知可以获取切入点中出现的异常信息使用形参可以接收对应的异常对象抛出异常后通知可以获取切入点方法运行的异常信息使用形参可以接收运行时抛出的异常对象
Component
Aspect
public class MyAdvice {Pointcut(execution(* com.example.dao.BookDao.findName(..)))private void pointCut(){}// 以下两种方式均可实现Around(pointCut())public Object around(ProceedingJoinPoint proceedingJoinPoint) {Object[] args proceedingJoinPoint.getArgs();System.out.println(Arrays.toString(args));args[0]666; // 在调用原方法传递参数之前改动参数值如果本需要传递的参数有问题此处就可以添加默认值增强程序的健壮性Object ret null;try {ret proceedingJoinPoint.proceed(args);} catch (Throwable e) {e.printStackTrace(); // 此处执行具体的操作}return ret;}AfterThrowing(value pointCut(),throwing throwable)public void afterThrowing(Throwable throwable){ // 如果使用这种方式需要原程序出现异常System.out.println(afterThrowing advice ... throwable);}
}8. 案例百度网盘分享链接输入密码数据错误兼容性处理 需求对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理 分析 在业务方法执行之前对所有的输入参数进行格式处理trim()使用处理后的参数调用原始方法使用环绕通知中存在对原始方法的调用 依赖导入 dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.2.10.RELEASE/version/dependencydependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.9.4/version/dependency接口及实现类
public interface ResourcesService {public boolean openURL(String url,String password);
}Service
public class ResourcesServiceImpl implements ResourcesService {Autowiredprivate ResourcesDao resourcesDao;Overridepublic boolean openURL(String url, String password) {return resourcesDao.readResources(url,password);}
}public interface ResourcesDao {boolean readResources(String url, String password);
}Repository
public class ResourcesDaoImpl implements ResourcesDao {Overridepublic boolean readResources(String url, String password) {// 模拟校验return password.equals(root);}
}配置
Configuration
ComponentScan(com.example)
EnableAspectJAutoProxy
public class SpringConfig {
}AOP 核心配置
Component
Aspect
public class DataAdvice {Pointcut(execution(boolean com.example.service.*Service.*(*,*)))private void servicePointCut(){}Around(DataAdvice.servicePointCut())public Object trimString(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Object[] args proceedingJoinPoint.getArgs();for (int i 0; i args.length; i) {if (args[i].getClass().equals(String.class)){args[i] args[i].toString().trim();}}Object ret proceedingJoinPoint.proceed(args);return ret;}
}public static void main(String[] args) {ApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig.class);ResourcesService resourcesService applicationContext.getBean(ResourcesService.class);boolean flag resourcesService.openURL(http://pan.baidu.com/resources, root );System.out.println(flag);}七、 Spring 事务 事物作用在数据层保障一系列的数据库操作同成功同失败 Spring 事务作用在数据层或业务层保障一系列的数据库操作同成功同失败 1. Spring 事务简介 以案例形式进行演示 案例模拟银行账户间转账业务 1 案例分析 需求实现任意两个账户间转账操作 需求微缩A 账户减钱B 账户加钱 分析 数据层提供基础操作指定账户减钱outMoney指定账户加钱inMoney业务层提供转账操作transfer调用减钱与加钱的操作提供两个账号和操作金额执行转账操作基于 Spring 整合 MyBatis 环境搭建上述操作 结果分析 程序正常执行时账户金额 A 减少 B 增加没有出现问题程序出现异常转账失败但是异常之前的操作成功异常之后操作失败整体业务失败
2 代码实现
数据库依旧采用前文中的 Account 账户JDBC 配置文件
public class Account implements Serializable {private Integer id;private String name;private Double money;Overridepublic String toString() {return Account{ id id , name name \ , money money };}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money money;}
}导入依赖 dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.13.2/versionscopetest/scope/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-test/artifactIdversion5.2.10.RELEASE/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-jdbc/artifactIdversion5.2.10.RELEASE/version/dependencydependencygroupIdorg.mybatis/groupIdartifactIdmybatis-spring/artifactIdversion1.3.0/version/dependencydependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.6/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.1.16/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.47/version/dependency接口及实现类
public interface AccountDao {Update(update table_account set money money #{money} where name#{name})void inMoney(Param(name) String name,Param(money) Double money);Update(update table_account set money money - #{money} where name #{name})void outMoney(Param(name) String name,Param(money) Double money);
}整合 MyBatis
public class MybatisConfig {Beanpublic SqlSessionFactoryBean sessionFactoryBean(DataSource dataSource){SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setTypeAliasesPackage(com.example.domain);sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer mapperScannerConfigurer new MapperScannerConfigurer();mapperScannerConfigurer.setBasePackage(com.example.dao);return mapperScannerConfigurer;}
}在业务层接口上添加 Spring 事务管理
public interface AccountService {/*** 在接口处开启事务管理* param out* param in* param money*/Transactionalpublic void transfer(String out,String in,Double money);
}注意事项 Spring 注解式事务通常添加在业务层接口中而不会添加到业务层实现类中降低耦合注解式事务可以添加到业务方法上表示当前方法开启事务也可以添加到接口上表示当前接口所有方法开启事务
Service
public class AccountServiceImpl implements AccountService {Autowiredprivate AccountDao accountDao;public void transfer(String out,String in,Double money){accountDao.outMoney(out,money);int i1/0; // 故意定义一个错误查看事务处理accountDao.inMoney(in,money);}
}JDBC 配置文件设置事务管理器
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;Beanpublic DataSource dataSource(){DruidDataSource druidDataSource new DruidDataSource();druidDataSource.setDriverClassName(driver);druidDataSource.setUrl(url);druidDataSource.setUsername(username);druidDataSource.setPassword(password);return druidDataSource;}/*** 定义一个事务管理器并且将这个事务管理器交给 Spring 管理* param dataSource* return*/Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager transactionManager new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}注意事项 事务管理器要根据实现技术进行选择MyBatis 框架使用的是 JDBC 事务 Spring 配置文件开启注解式事务驱动
Configuration
ComponentScan(com.example)
PropertySource(classpath:jdbc.properties)
Import({JdbcConfig.class,MybatisConfig.class})
EnableTransactionManagement // 用注解事务驱动
public class SpringConfig {
}2. Spring 事务角色
1 事务管理员
事务管理员发起事务方在 Spring 中通常指代业务层开启事务的方法
2 事务协调员
事务协调员加入事务方在 Spring 中通常指代数据层方法也可以是业务层方法
3. Spring 事务属性
1 事务属性 关于设置事务回滚异常 以下两种情况下事务才会回滚否则其他异常情况下不会进行事务回滚 Error 系列例如内存溢出运行时异常例如空指针异常 测试并解决设置事务回滚异常故意抛出异常
Service
public class AccountServiceImpl implements AccountService{Autowiredprivate AccountDao accountDao;public void transfer(String out,String in,Double money) throws IOException {accountDao.outMoney(out,money);if (true) throw new IOException(); // 故意定义一个异常查看事务处理accountDao.inMoney(in,money);}
}设置事务回滚异常解决
public interface AccountService {/*** 在接口处开启事务管理* param out* param in* param money*/Transactional(rollbackFor {IOException.class})public void transfer(String out,String in,Double money) throws IOException;
}2 案例转账业务追加日志 需求实现任意两个账户间转账操作并对每次转账操作在数据库进行留痕 需求微缩A 账户减少钱B 账户增加钱数据库记录日志 分析 基于转账操作案例添加日志模块实现数据库中记录日志业务层转账操作transfer调用减少钱增加钱与记录日志功能 实现效果预期 无论转账操作是否成功均进行转账操作的日志留痕
① 环境准备
创建一张 MySQL 数据表 id 字段数据类型为 INTinfo 字段数据类型为 LONGTEXTcreateDate 字段数据类型为 DATETIME
② 改动与添加部分
数据层日志接口
public interface LogDao {Insert(insert into table_log (info,createDate) values(#{info},now()))void log(String info);
}业务层日志接口
public interface LogService {Transactionalvoid log(String put,String in,Double money);
}业务层日志实现类
Service
public class LogServiceImpl implements LogService {Autowiredprivate LogDao logDao;Overridepublic void log(String out, String in, Double money) {logDao.log(转账操作由out到in金额money);}
}改动账户实现类添加日志记录操作
Service
public class AccountServiceImpl implements AccountService{Autowiredprivate AccountDao accountDao;Autowiredprivate LogService logService;public void transfer(String out,String in,Double money){try {accountDao.outMoney(out,money);accountDao.inMoney(in,money);} finally { // 保证记录日志的代码一定执行logService.log(out,in,money);}}
}③ 事务传播问题出现
存在的问题 日志的记录与转账操作隶属于同一个事务同成功同失败 实现效果预期改进 无论转账操作是否成功日志必须保留
3 事务传播行为 事务传播行为事务协调员对事务管理员所携带事务的处理态度 事务传播行为 在日志的业务层接口中设置事务的传播行为 在业务层接口上添加 Spring 事务设置事务传播行为 REQUIRES_NEW 需要新事务
public interface LogService {Transactional(propagation Propagation.REQUIRES_NEW)void log(String put,String in,Double money);
}更改账户业务层使其故意出现错误
Service
public class AccountServiceImpl implements AccountService{Autowiredprivate AccountDao accountDao;Autowiredprivate LogService logService;public void transfer(String out,String in,Double money){try {accountDao.outMoney(out,money);int i1/0;accountDao.inMoney(in,money);} finally { // 保证记录日志的代码一定执行logService.log(out,in,money);}}
}最终结果就是出现账户金额并未变动但日志成功记录