一、一些概念 1.1 OCP 开闭原则 Open Close Principle,软件七大开发原则中最基本的原则,其他六个原则都为这个原则服务
在进行系统功能扩展后,不修改之前的程序,这就是 OCP 原则
1.2 DIP 依赖倒置原则 Dependence Inversion Principle,软件七大开发原则之一
Dependence:上层与下层的依赖关系
Inversion:让上层与下层不再依赖
DIP 原则,主要倡导面向接口编程,面向抽象编程,使上下层之间不再依赖,从而降低程序的耦合度,提高扩展力
1.3 IoC 控制反转思想 Inversion of Control,面向对象编程的一种思想(或者叫一种新型设计模式,出现较晚,没有被纳入 GoF 23种设计模式中),可以降低代码之间的耦合度,符合 DIP 原则
控制反转原则的核心:将对象的创建权、对象和对象之间关系的管理权交出去,由第三方容器负责创建和维护
1.4 依赖注入 控制反转常见的实现方式:依赖注入(Dependency Injection,简称 DI)
依赖:对象和对象的关系
注入:一种手段,可以让对象与对象间产生关系
通常,依赖注入有两种实现方式:
二、Spring 概述 2.1 Spring 简介 Spring 是一个
开源框架,由 Rod JohnSon 创建。它是为了解决企业应用开发的复杂性而创建的 (最初的出现是为了解决 EJB (Enterprise JavaBean)臃肿的设计,以及难以测试等问题)
轻量级的控制反转(IoC)和面向切面(AOP)的容器框架
作用:
可以帮我们 new 对象
可以帮我们维护对象和对象之间的关系
2.2 Spring 八大模块
Spring Core 模块
这是 Spring 框架最基础的部分,它提供了依赖注入(Dependency Injection)特征来实现容器对 Bean 的管理。核心容器的主要组件是 BeanFactory,BeanFactory 是工厂模式的一个实现,是任何 Spring 应用的核心。它使用 IoC 将应用配置和依赖从实际的应用代码中分离出来
Spring Context 模块
如果说核心模块中的 BeanFactory 使 Spring 成为容器的话,那么上下文模块就是 Spring 成为框架的原因
这个模块扩展了 BeanFactory,增加了对国际化(I18N)消息、事件传播、验证的支持。另外提供了许多企业服务,例如电子邮件、JNDI 访问、EJB 集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如 Velocity 和 FreeMarker 集成的支持
Spring AOP 模块
Spring 在它的 AOP 模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作
Spring DAO 模块
提供了一个 JDBC 的抽象层和异常层次结构,消除了繁琐的 JDBC 编码和数据库厂商特有的错误代码解析,用于简化 JDBC
Spring ORM 模块
Spring 提供了 ORM 模块。Spring 并不试图实现它自己的 ORM 解决方案,而是为几种流行的 ORM 框架提供了集成方案,包括 Hibernate、JDO 和 iBATIS SQL 映射,这些都遵从 Spring 的通用事务和 DAO 异常层次结构
Spring Web MVC 模块
Spring 为构建 Web 应用提供了一个功能全面的 MVC 框架。虽然 Spring 可以很容易地与其它 MVC 框架集成,例如 Struts,但 Spring 的 MVC 框架使用 IoC 对控制逻辑和业务对象提供了完全的分离
Spring WebFlux 模块
Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器构建的。反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版的后期添加的。它是完全非阻塞的,支持反应式流(Reactive Stream)背压,并在 Netty,Undertow 和 Servlet 3.1+容器等服务器上运行
Spring Web 模块
Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,提供了 Spring 和其它 Web 框架的集成,比如 Struts、WebWork。还提供了一些面向服务支持,例如:实现文件上传的 multipart 请求
2.3 Spring 特点 轻量
从大小与开销两方面而言 Spring 都是轻量的。完整的 Spring 框架可以在一个大小只有 1MB 多的 JAR 文件里发布。并且 Spring 所需的处理开销也是微不足道的
Spring 是非侵入式的:Spring 应用中的对象不依赖于 Spring 的特定类
控制反转
Spring 通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了 IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为 IoC 与 JNDI 相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它
面向切面
Spring 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计【auditing】和事务【transaction】管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持
容器
Spring 包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个 bean 如何被创建——基于一个可配置原型(prototype),你的 bean 可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring 不应该被混同于传统的重量级的 EJB 容器,它们经常是庞大与笨重的,难以使用
框架
Spring 可以将简单的组件配置,组合成为复杂的应用。在 Spring 中,应用对象被声明式地组合,典型地是在一个 XML 文件里。Spring 也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你
所有 Spring 的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为 Spring 中的各种模块提供了基础支持
三、Spring 入门程序 3.1 引入 Spring 第一步:进入 官网 或 中文官网 ,Projects –> Spring Framework
第二步:点击 GitHub 图标
第三步:找到下图位置,点击超链接
里边有 Maven 中引入 spring 6 的方法
spring 目录:
docs:spring 框架的 API 帮助文档
libs:spring 框架的 jar 文件
schemas:spring 框架的 XML 配置文件相关的约束文件
3.2 Spring 的 jar 包
JAR文件
描述
spring-aop-5.3.9.jar
这个 jar 文件包含在应用中使用 Spring 的 AOP 特性时所需的类
spring-aspects-5.3.9.jar
提供对 AspectJ 的支持,以便可以方便的将面向切面的功能集成进 IDE 中
spring-beans-5.3.9.jar
这个 jar 文件是所有应用都要用到的,它包含访问配置文件、创建和管理 bean 以及进行 Inversion ofControl / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的 IoC/DI 支持,引入 spring-core.jar 及 spring-beans.jar 文件就可以了。
spring-context-5.3.9.jar
这个 jar 文件为 Spring 核心提供了大量扩展。可以找到使用 Spring ApplicationContext 特性时所需的全部类,JDNI 所需的全部类,instrumentation 组件以及校验 Validation 方面的相关类。
spring-context-indexer-5.3.9.jar
虽然类路径扫描非常快,但是 Spring 内部存在大量的类,添加此依赖,可以通过在编译时创建候选对象的静态列表来提高大型应用程序的启动性能。
spring-context-support-5.3.9.jar
用来提供 Spring 上下文的一些扩展模块,例如实现邮件服务、视图解析、缓存、定时任务调度等
spring-core-5.3.9.jar
Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。
spring-expression-5.3.9.jar
Spring 表达式语言。
spring-instrument-5.3.9.jar
Spring3.0对服务器的代理接口。
spring-jcl-5.3.9.jar
Spring 的日志模块。JCL,全称为”Jakarta Commons Logging”,也可称为”Apache Commons Logging”。
spring-jdbc-5.3.9.jar
Spring对 JDBC 的支持。
spring-jms-5.3.9.jar
这个 jar 包提供了对 JMS 1.0.2/1.1的支持类。JMS 是 Java 消息服务。属于 JavaEE 规范之一。
spring-messaging-5.3.9.jar
为集成 messaging api 和消息协议提供支持
spring-orm-5.3.9.jar
Spring 集成 ORM 框架的支持,比如集成 hibernate,mybatis 等。
spring-oxm-5.3.9.jar
为主流 O/X Mapping 组件提供了统一层抽象和封装,OXM 是 Object Xml Mapping。对象和 XML 之间的相互转换。
spring-r2dbc-5.3.9.jar
Reactive Relational Database Connectivity (关系型数据库的响应式连接) 的缩写。这个 jar 文件是 Spring 对 r2dbc 的支持。
spring-test-5.3.9.jar
对 Junit 等测试框架的简单封装。
spring-tx-5.3.9.jar
为 JDBC、Hibernate、JDO、JPA、Beans 等提供的一致的声明式和编程式事务管理支持。
spring-web-5.3.9.jar
Spring 集成 MVC 框架的支持,比如集成 Struts 等。
spring-webflux-5.3.9.jar
WebFlux 是 Spring5 添加的新模块,用于 web 的开发,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流程响应式编程出现的框架。
spring-webmvc-5.3.9.jar
SpringMVC 框架的类库
spring-websocket-5.3.9.jar
Spring 集成 WebSocket 框架时使用
3.3 第一个 Spring 程序 第一步:先新建一个 Maven 模块,然后在 pom.xml 文件中添加 spring context 依赖和 junit 依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.shameyanng</groupId > <artifactId > spring6-001-first</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > jar</packaging > <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.1.4</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.13.2</version > <scope > test</scope > </dependency > </dependencies > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > </project >
引入 spring context 依赖后,会关联引入其他依赖:
spring aop:面向切面编程
spring beans:IoC 核心
spring core:spring 的核心工具包
spring jcl:spring 的日志包
spring expression:spring 表达式
第二步:定义 bean 这里以 User 为例:
第三步:类的根路径下(src/main/java/resource),新建 spring 的配置文件:beans.xml (IDEA 中自带 spring 配置文件的模板,New –> XML Configuration File –> Spring Config)
使用 <bean>
标签配置 bean
1 2 3 4 5 6 7 <?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 ="userBean" class ="com.shameyang.spring6.bean.User" /> </beans >
第四步:编写测试程序
1 2 3 4 5 6 7 8 9 10 public class Spring6Test { @Test public void testFirst () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); Object userBean = applicationContext.getBean("userBean" ); System.out.println(userBean); } }
第五步:运行
3.4 Spring 程序分析
id 不能重复,对象的唯一标识 class 用来指定要创建的 Java 对象的类名,必须是全类名
1 <bean id ="userBean" class ="com.shameyang.spring6.bean.User" />
bean 可以是任意类,前提:该类不是抽象的,并且提供了无参构造方法 例如 Date:
1 <bean id ="dateBean" class ="java.util.Date" />
spring 通过调用类的无参构造方法来创建对象,所以如果想要 spring 为我们创建对象,必须保证无参构造方法存在
原理如下:
1 2 Class clazz = Class.forName("com.shameyang.spring6.bean.User" );Object obj = clazz.newInstance();
spring 配置文件可以有多个,名字由我们负责提供
1 ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" , "xxx.xml" , ...);
spring 创建的对象存储的数据结构:Map<String, Object>
1 2 Object userBean = applicationContext.getBean("userBean" );
getBean() 方法返回类型是 Object,如果访问子类特有的属性或方法,需要向下转型
1 User user = applicationContext.getBean("userBean" , User.class);
ClassPathXmlApplicationContext 是从类路径中加载配置文件,如果没有在类路径当中,需要使 FileSystemXmlApplicationContext 类进行加载配置文件
1 2 3 ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext ("spring.xml's path" );BeanClass beanClass = applicationContext2.getBean("beanId" , BeanClass.class);System.out.println(beanClass);
3.5 Spring6 启用 Log4j2 日志框架 Spring5 之后,Spring 框架支持 Log4j2。下面演示如何启用该日志框架:
第一步:引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependencies > <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-core</artifactId > <version > 2.20.0</version > </dependency > <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-slf4j2-impl</artifactId > <version > 2.20.0</version > </dependency > </dependencies >
第二步:类的根路径下提供配置文件:log4j2.xml(名字固定,必须放在根路径下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <configuration > <loggers > <root level ="DEBUG" > <appender-ref ref ="spring6log" /> </root > </loggers > <appenders > <console name ="spring6log" target ="SYSTEM_OUT" > <PatternLayout pattern ="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n" /> </console > </appenders > </configuration >
使用框架
1 2 3 4 5 6 public class Log4jTest { public static void main (String[] args) { Logger logger = LoggerFactory.getLogger(Log4jTest.class); logger.info("hello" ); } }
四、Spring 对 IoC 的实现 4.1 set 注入 通过 set 方法给属性赋值 UserDao:
1 2 3 4 5 public class UserDao { public void insert () { System.out.println("UserDao insert..." ); } }
UserService:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class UserService { private UserDao userDao; public void setUserDao (UserDao userDao) { this .userDao = userDao; } public void save () { userDao.insert(); } }
spring.xml:
1 2 3 4 5 6 7 8 9 10 11 <?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 ="userDaoBean" class ="com.shameyang.spring6.dao.UserDao" /> <bean id ="userServiceBean" class ="com.shameyang.spring6.service.UserService" > <property name ="userDao" ref ="userDaoBean" /> </bean > </beans >
Test:
1 2 3 4 5 6 7 8 public class DITest { @Test public void testDI () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("spring.xml" ); UserService userService = applicationContext.getBean("userServiceBean" , UserService.class); userService.save(); } }
4.2 构造注入 通过构造方法给属性赋值 OrderDao:
1 2 3 4 5 public class OrderDao { public void deleteById () { System.out.println("OrderDao delete..." ); } }
OrderService:
1 2 3 4 5 6 7 8 9 10 11 12 public class OrderService { private OrderDao orderDao; public OrderService (OrderDao orderDao) { this .orderDao = orderDao; } public void delete () { orderDao.deleteById(); } }
spring.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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 ="orderDaoBean" class ="com.shameyang.spring6.dao.OrderDao" /> <bean id ="orderServiceBean" class ="com.shameyang.spring6.service.OrderService" > <constructor-arg index ="0" ref ="orderDaoBean" /> <constructor-arg name ="orderDao" ref ="orderDaoBean" > <constructor-arg ref ="orderDaoBean" > </bean > </beans >
Test:
1 2 3 4 5 6 7 8 public class DITest { @Test public void testConstructorDI () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("spring.xml" ); OrderService orderService = applicationContext.getBean("orderServiceBean" , OrderService.class); orderService.delete(); } }
4.3 set 注入专题 注入外部 Bean(常用):使用 ref 属性 1 2 3 4 5 <bean id ="userDaoBean" class ="com.shameyang.spring6.dao.UserDao" /> <bean id ="userServiceBean" class ="com.shameyang.spring6.service.UserService" > <property name ="userDao" ref ="userDaoBean" /> </bean >
注入内部 Bean(了解) 嵌套 bean 标签
1 2 3 4 5 <bean id ="userServiceBean" class ="com.shameyang.spring6.service.UserService" > <property name ="userDao" > <bean class ="com.shameyang.spring6.dao.UserDao" /> </property > </bean >
注入简单类型:使用 value 属性或 value 标签
1 2 3 4 5 6 7 8 <bean id ="userBean" class ="com.shameyang.spring6.bean.User" > <property name ="age" value ="20" /> <property name ="age" > <value > 20</value > </property > </bean >
简单类型有哪些?
基本数据类型、以及对应的包装类
Enum 子类
String 或其它的 CharSequence 子类
Number 子类
Date 子类
Temporal 子类
URI、URL
Locale
Class
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class BeanUtils { public static boolean isSimpleProperty (Class<?> type) { Assert.notNull(type, "'type' must not be null" ); return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType())); } public static boolean isSimpleValueType (Class<?> type) { return (Void.class != type && void .class != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type)); } }
注意:Date 当作简单类型时,注意日期格式
级联属性赋值(了解) 要点:
spring 配置文件要注意顺序,先 ref 再 value
级联属性必须有 getter 方法
1 2 3 4 5 6 7 8 9 <bean id ="clazzBean" class ="com.shameyang.spring6.bean.Clazz" /> <bean id ="student" class ="com.shameyang.spring6.bean.Student" > <property name ="name" value ="张三" /> <property name ="clazz" ref ="clazzBean" /> <property name ="clazz.name" value ="高三一班" /> </bean > </beans >
注入数组 简单类型 <array>
嵌套 <value>
1 2 3 4 5 6 7 8 9 <bean id ="person" class ="com.shameyang.spring6.bean.Person" > <property name ="hobbies" > <array > <value > sing</value > <value > jump</value > <value > rap</value > </array > </property > </bean >
非简单类型 <array>
嵌套 <ref>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <bean id ="goods1" class ="com.shameyang.spring6.bean.Goods" > <property name ="name" value ="西瓜" /> </bean > <bean id ="goods2" class ="com.shameyang.spring6.bean.Goods" > <property name ="name" value ="苹果" /> </bean > <bean id ="order" class ="com.shameyang.spring6.bean.Order" > <property name ="goods" > <array > <ref bean ="goods1" /> <ref bean ="goods2" /> </array > </property > </bean >
注入 List、Set 集合 将上边注入数组的 <array>
改为集合对应的标签即可,集合中元素是简单类型用 <value>
,非简单类型用 <ref>
注入 Map 集合 <map>
嵌套 <entry>
1 2 3 4 5 6 7 8 9 10 <bean id ="peopleBean" class ="com.shameyang.spring6.bean.People" > <property name ="addrs" > <map > <entry key ="1" value ="北京" /> <entry key ="2" value ="上海" /> <entry key ="3" value ="深圳" /> </map > </property > </bean >
注入 Properties <props>
嵌套 <prop>
1 2 3 4 5 6 7 8 9 10 <bean id ="peopleBean" class ="com.shameyang.spring6.bean.People" > <property name ="properties" > <props > <prop key ="driver" > com.mysql.cj.jdbc.Driver</prop > <prop key ="url" > jdbc:mysql://localhost:3306/spring</prop > <prop key ="username" > root</prop > <prop key ="password" > 123456</prop > </props > </property > </bean >
注入 null 和空字符串 注入null:
注入空字符串:
注入的值中含有特殊符号 方案一:转义字符
特殊字符
转义字符
<
<
>
>
‘
'
“
"
&
&
方案二:<![CDATA[ 放在这里 ]]>,放在 CDATA 区中的数据不会被 XML 文件解析器解析
1 2 3 4 5 6 <bean id ="mathBean" class ="com.shameyang.spring6.bean.Math" > <property name ="result" > <value > <![CDATA[2 < 3]]></value > </property > </bean >
4.4 p 命名空间注入(简化 set) 基于 p 命名空间注入,可以简化 set 注入
第一步:在 spring 配置文件头部添加:xmlns:p="http://www.springframework.org/schema/p"
第二步:spring.xml 中配置 bean(对应的属性提供 setter 方法)
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:p ="http://www.springframework.org/schema/p" 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 ="customerBean" class ="com.shameyang.spring6.bean.Customer" p:name ="zhangsan" p:age ="20" /> </beans >
4.5 c 命名空间注入(简化构造方法) 基于 c 命名空间注入,可以简化构造方法注入
第一步:spring 配置文件头部添加:xmlns:c="http://www.springframework.org/schema/c"
第二步:提供构造方法
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:c ="http://www.springframework.org/schema/c" 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 ="myTimeBean" class ="com.shameyang.spring6.bean.MyTime" c:_0 ="2000" c:_1 ="1" c:_2 ="1" /> </beans >
4.6 util 命名空间 使用 util 命名空间可以实现配置复用
引入 util 命名空间:在 spring 配置文件头部添加如下配置信息
例如:不同数据源共用数据库信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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:util ="http://www.springframework.org/schema/util" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" > <util:properties id ="prop" > <prop key ="driver" > com.mysql.cj.jdbc.Driver</prop > <prop key ="url" > jdbc:mysql://localhost:3306/spring</prop > <prop key ="username" > root</prop > <prop key ="password" > 123456</prop > </util:properties > <bean id ="dataSource1" class ="com.shameyang.spring6.bean.MyDataSource1" > <property name ="properties" ref ="prop" /> </bean > <bean id ="dataSource2" class ="com.shameyang.spring6.bean.MyDataSource2" > <property name ="properties" ref ="prop" /> </bean > </beans >
4.7 基于 XML 的自动装配 Spring 可以完成自动化注入,自动化注入又称为自动装配
根据名称自动装配 1 2 3 <bean id ="userService" class ="com.shameyang.spring6.service.UserService" autowire ="byName" /> <bean id ="userDao" class ="com.shameyang.spring6.dao.UserDao" />
根据类型自动装配 1 2 3 4 <bean id ="userService" class ="com.shameyang.spring6.service.UserService" autowire ="byType" /> <bean class ="com.shameyang.spring6.dao.UserDao" />
4.8 引入外部属性配置文件 编写数据源时需要连接数据库的信息,我们可以将信息写到外部配置文件中,这样用户修改会非常方便。下面介绍如何在 spring 中引入外部属性配置文件
第一步:定义一个数据源类,并提供相关属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MyDataSource implements DataSource { @Override public String toString () { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}' ; } private String driver; private String url; private String username; private String password; public void setDriver (String driver) { this .driver = driver; } public void setUrl (String url) { this .url = url; } public void setUsername (String username) { this .username = username; } public void setPassword (String password) { this .password = password; } }
第二步:类路径下新建 jdbc.properties 文件,配置信息
1 2 3 4 jdbc.driver =com.mysql.cj.jdbc.Driver jdbc.url =jdbc:mysql://localhost:3306/spring jdbc.username =root jdbc.password =root123
第三步:在 spring 配置文件中引入 context 命名空间
1 2 3 4 5 6 7 8 <?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 >
第四步:spring 中配置使用 jdbc.properties 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?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" > <context:property-placeholder location ="jdbc.properties" /> <bean id ="dataSource" class ="com.shameyang.spring6.bean.MyDataSource" > <property name ="driver" value ="${jdbc.driver}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> </bean > </beans >
五、Bean 的作用域 我们可以在 bean 标签中,使用 scope 属性配置 Bean 的作用域
scope 的值有如下几种:
singleton(default):默认的,单例
prototype:原型。每调用一次 getBean() 方法则获取一个新的 Bean 对象,或每次注入的时候都是新对象
request:一个请求对应一个 Bean(仅限于在 WEB 应用中使用 )
session:一个会话对应一个 Bean(仅限于在 WEB 应用中使用 )
global session:portlet 应用中专用的 。如果在 Servlet 的 WEB 应用中使用 global session 的话,和 session 一个效果。(portlet 和 servlet 都是规范。servlet 运行在servlet 容器中,例如Tomcat。portlet 运行在 portlet 容器中)
application:一个应用对应一个 Bean(仅限于在 WEB 应用中使用 )
websocket:一个 websocket 生命周期对应一个 Bean(仅限于在 WEB 应用中使用 )
自定义scope:很少使用
自定义一个 Scope(了解即可):线程级别的 Scope
六、Bean 的获取方式 Spring 提供了多种获取 Bean 对象的方式:
构造方法
简单工厂模式(静态工厂方法模式)
factory-bean
FactoryBean 接口
6.1 构造方法 我们之前使用的都是这种方式:在 spring 配置文件中直接配置类的全路径。默认情况下,会调用 Bean 的无参构造方法
1 2 3 4 5 6 7 8 <?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 ="userBean" class ="com.shameyang.spring6.bean.User" /> </beans >
6.2 简单工厂模式(静态工厂方法模式) 假设已经定义了 UserBean。现在我们提供一个工厂类,工厂类里有一个静态方法:
1 2 3 4 5 public class UserFactory { public static User get () { return new User (); } }
然后在 spring 配置文件中指定创建该 Bean 的方法
1 <bean id ="userBean" class ="com.shameyang.spring6.bean.UserFactory" factory-method ="get" />
6.3 factory-bean(工厂方法模式) 假设已经定义了 UserBean。现在定义具体工厂类(一个 Bean 对应一个 BeanFactory),工厂类中提供实例方法
1 2 3 4 5 public class UserFactory { public User get () { return new User (); } }
然后在 spring 配置文件中指定 factory-bean 和 factory-method
1 2 <bean id ="userFactory" class ="com.shameyang.spring6.bean.UserFactory" /> <bean id ="userBean" factory-bean ="userFactory" factory-method ="get" />
6.4 实现 FactoryBean 接口 我们可以编写一个类实现 FactoryBean 接口,这样 factory-bean 和 factory-method 就无需手动配置了
factory-bean 会自动指向实现 FactoryBean 接口的类
factory-method 会自动指向 getObject() 方法
假设已经定义了 UserBean。现在编写一个类实现 FactoryBean 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class UserFactoryBean implements FactoryBean <User> { @Override public User getObject () throws Exception { return new User (); } @Override public Class<?> getObjectType() { return null ; } @Override public boolean isSingleton () { return true ; } }
在 spring 配置文件中配置 FactoryBean
1 <bean id ="userBean" class ="com.shameyang.spring6.bean.UserFactoryBean" />
七、关于 FactoryBean 7.1 FactoryBean 和 BeanFactory 的区别 FactoryBean 是一个 Bean,能够辅助 Spring 实例化其它 Bean 对象
BeanFactory 是一个工厂,是 Spring IoC 容器的顶级对象,负责创建 Bean 对象
7.2 注入自定义 Date 在之前的学习中,java.util.Date 在注入时,如果当作简单类型需要使用规定的格式:Mon Jan 01 00:00:00 CST 2023
如果想用自定义的格式(当作非简单类型),我们就可以使用 FactoryBean 来辅助完成
第一步:编写 DateFactoryBean 实现 FactoryBean 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DateFactoryBean implements FactoryBean <Date> { private String date; public DateFactoryBean (String date) { this .date = date; } @Override public Date getObject () throws Exception { SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd" ); return sdf.parse(this .date); } @Override public Class<?> getObjectType() { return null ; } }
第二步:编写 spring 配置文件
1 2 3 4 5 6 7 <bean id ="dateBean" class ="com.shameyang.spring6.bean.DateFactoryBean" > <constructor-arg name ="date" value ="2000-01-01" /> </bean > <bean id ="..." class ="..." > <property name ="..." ref ="dateBean" > </bean >
八、Bean 的生命周期 8.1 生命周期五步
实例化 Bean
Bean 属性赋值
初始化 Bean
使用 Bean
销毁 Bean
注意:
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class User { private String name; public User () { System.out.println("1.实例化 Bean" ); } public void setName (String name) { this .name = name; System.out.println("2.Bean 属性赋值" ); } public void initBean () { System.out.println("3.初始化 Bean" ); } public void destroyBean () { System.out.println("5.销毁 Bean" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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 ="userBean" class ="com.shameyang.spring6.bean.User" init-method ="initBean" destroy-method ="destroyBean" > <property name ="name" value ="zhangsan" /> </bean > </beans >
1 2 3 4 5 6 7 8 9 10 11 public class BeanLifecycleTest { @Test public void testLifecycle () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("spring.xml" ); User userBean = applicationContext.getBean("userBean" , User.class); System.out.println("4.使用 Bean" ); ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext; context.close(); } }
8.2 生命周期七步 在上面的五步中,如果想在初始化前和初始化后添加代码,可以加入 Bean 后处理器
Bean 后处理器实现步骤
第一步:编写一个类,实现 BeanPostProcessor 类,重写 before 和 after 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public class LogBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { System.out.println("Bean 后处理器的 before 方法执行,即将开始初始化" ); return bean; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { System.out.println("Bean 后处理器的 after 方法执行,已完成初始化" ); return bean; } }
第二步:在 spring 配置文件中配置
1 2 <bean class ="com.shameyang.spring6.bean.LogBeanPostProcessor" />
8.3 生命周期十步 对 Bean 的生命周期更加细分,可以再增加三步,检查 Bean 是否实现了特定的接口,如果实现了,spring 容器会调用接口中的方法
实例化 Bean
Bean 属性赋值
检查是否实现了 Aware 的相关接口
BeanNameAware 当实现了该接口,Spring 会传递 Bean 的名字
BeanClassLoaderAware 当实现了该接口,Spring 会传递 Bean 的类加载器
BeanFactoryAware 当实现了该接口,Spring 会传递 BeanFactory 对象
Bean 后处理器 before 方法
检查是否实现了 InitializingBean 接口
初始化 Bean
Bean 后处理器 after 方法
使用 Bean
检查是否实现了 DisposableBean 接口
销毁 Bean
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class User implements BeanNameAware , BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { private String name; public User () { System.out.println("1.实例化Bean" ); } public void setName (String name) { this .name = name; System.out.println("2.Bean属性赋值" ); } public void initBean () { System.out.println("6.初始化Bean" ); } public void destroyBean () { System.out.println("10.销毁Bean" ); } @Override public void setBeanClassLoader (ClassLoader classLoader) { System.out.println("3.类加载器:" + classLoader); } @Override public void setBeanFactory (BeanFactory beanFactory) throws BeansException { System.out.println("3.Bean工厂:" + beanFactory); } @Override public void setBeanName (String name) { System.out.println("3.bean名字:" + name); } @Override public void destroy () throws Exception { System.out.println("9.DisposableBean destroy" ); } @Override public void afterPropertiesSet () throws Exception { System.out.println("5.afterPropertiesSet执行" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 public class LogBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { System.out.println("4.Bean后处理器的before方法执行,即将开始初始化" ); return bean; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { System.out.println("7.Bean后处理器的after方法执行,已完成初始化" ); return bean; } }
8.4 Bean 的作用域对生命周期管理的影响 Spring 容器只对 singleton 的 Bean 进行完整的生命周期管理
如果是 prototype 的 Bean,Spring 只负责创建,创建完毕后交给客户端代码管理
8.5 自己 new 的对象如何被 spring 管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class RegisterBeanTest { @Test public void testBeanRegister () { User user = new User (); System.out.println(user); DefaultListableBeanFactory factory = new DefaultListableBeanFactory (); factory.registerSingleton("userBean" , user); User userBean = factory.getBean("userBean" , User.class); System.out.println(userBean); } }
九、Spring IoC 注解式开发 注解主要为了简化 XML 的配置。Spring6 倡导全注解开发
9.1 声明 Bean 的注解 负责声明 Bean 的注解,常见的有四个:
@Component
@Controller
@Service
@Repository
@Controller、@Service、@Repository 都是@Component 的别名
这四个注解的功能都是一样的,只是为了增强程序的可读性
控制器上用@Controller
service 类上用@Service
dao 类上用@Repository
他们都只有一个 value 属性,用来指定 bean 的 id,与之前 XML 配置文件中的 id 同理
部分源码如下:
1 2 3 4 5 @Target(value = {ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Component { String value () ; }
1 2 3 4 5 6 7 8 9 10 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { @AliasFor( annotation = Component.class ) String value () default "" ; }
1 2 3 4 5 6 7 8 9 10 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { @AliasFor( annotation = Component.class ) String value () default "" ; }
1 2 3 4 5 6 7 8 9 10 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { @AliasFor( annotation = Component.class ) String value () default "" ; }
9.2 注解的使用 第一步:引入 spring-aop 依赖
引入 spring-context 依赖后,会关联加入 spring-aop 的依赖 pom.xml:
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.1.4</version > </dependency >
第二步:在 spring 配置文件中添加 context 命名空间
1 2 3 4 5 6 7 8 <?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 >
第三步:在 spring 配置文件中指定要扫描的包
1 2 3 4 5 6 7 8 <?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" > <context:component-scan base-package ="com.shameyang.spring6.bean" /> </beans >
第四步:在 Bean 类上使用注解
1 2 3 @Component(value = "studentBean") public class Student {}
9.3 选择性实例化 Bean 由于业务需要,我们有时只需要让某一个注解参与 Bean 管理,其他的都不实例化
有以下两种方式:use-default-filters="true | false"
第一种:use-default-filters=”false”,不使用默认的实例化规则,指定实例化的注解
1 2 3 <context:component-scan base-package ="com.shameyang.spring6.bean3" use-default-filters ="false" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
第二种:use-default-filters=”true”,使用默认的实例化规则,排除不参与实例化的注解
1 2 3 <context:component-scan base-package ="com.shameyang.spring6.bean3" use-default-filters ="true" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
9.4 负责注入的注解 上边的注解,只是声明了 Bean,我们还需要给其赋值。给 Bean 属性赋值需要这些注解:
@Value
@Autowired
@Qualifier
@Resource
@Value 该注解用来注入简单类型,可以出现在属性、setter 方法以及构造参数的形参上,非常灵活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component(value = "studentBean") public class Student { @Value("001") private String sno; private String sname; @Override public String toString () { return "Student{" + "sno='" + sno + '\'' + ", sname='" + sname + '\'' + '}' ; } @Value("jack") public void setSname (String sname) { this .sname = sname; } }
@AutoWired 和 @Qualifier 注入非简单类型时,需要这两个注解
@AutoWired 注解可以出现在:属性、构造方法、构造方法的参数、setter 方法上
带参的构造方法只有一个时,@AutoWired 可以省略
只有 @AutoWired 会根据类型注入,如果想要根据名字注入需要添加 @Qualifier 注解
@Resource @Resource 也可以用于注入非简单类型
它与 @AutoWired 的区别:
通用性
@Resource 是 JDK 扩展包中的,标准注解,更具有通用性
@AutoWired 注解是 Spring 框架自己的
运用的位置不同
@Resource 用在属性、setter 方法上
@AutoWired 用在属性、setter 方法、构造方法和构造方法的参数上
默认的装配方式不同
@Resource 默认根据名称 byName 装配
@AutoWired 默认根据类型 byType 装配
9.5 全注解式开发 全注解开发,即不使用 spring 配置文件,写一个配置类来代替配置文件
1 2 3 4 @Configuration @ComponentScan("com.shameyang.spring6.bean") public class Spring6Configuration {}
测试程序修改:不再 new ClassPathXMLApplicationContext 对象,而是 new AnnotationConfigApplicationContext
1 2 3 4 5 6 7 8 public class IoCAnnotationTest { @Test public void testNew () { ApplicationContext applicationContext = new AnnotationConfigApplicationContext (Spring6Configuration.class); Student student = applicationContext.getBean("studentBean" , Student.class); System.out.println(student); } }
十、面向切面编程 AOP 10.1 AOP 介绍 Aspect Oriented Programming,面向切面编程,是一种编程技术
将与核心业务无关的代码抽取出来,形成一个独立的组件,将业务看作纵向,以横向交叉的方式应用到业务流程的这个过程,即 AOP
AOP 的优点:
10.2 AOP 术语 织入 Weaving
切点 Pointcut
通知 Advice
切面 Aspect
连接点 Joinpoint
代理对象 Proxy
目标对象 Target
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class UserService { public void do1 () { System.out.println("do 1" ); } public void service () { try { do1(); } catch (Exception e) { } finally { } } }
10.3 切点表达式 切点表达式用来定义通知往哪些方法上切入
语法格式:execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形参列表) [异常])
访问控制权限修饰符:
返回值类型:
全限定类名:
两个点表示当前包以及子包下的所有类
省略时表示所有的类
方法名:
* 表示所有方法
xxx* 表示所有的 xxx 开始的方法
形参列表:
()表示无参方法
(..)参数类型和个数随意的方法
(*)只有一个参数的方法
(*, String)第一个参数类型随意,第二个参数为 String
异常:
示例如下:
service 包下所有的类中以 delete 开始的所有 public 方法
1 execution(public * com.shameyang.mall.service.*.delete*(..))
mall 包下所有的类的所有的方法
1 execution(* com.shameyang.mall..*(..))
所有类的所有方法
10.4 基于 AspectJ 的 AOP 注解式开发 XML 方式开发 第一步:配置 pom.xml 文件和 spring.xml
第二步:定义目标类和目标方法,并纳入 spring bean 管理
1 2 3 4 5 6 7 8 @Service public class OrderService { public void generate () { System.out.println("订单已生成" ); } }
第三步:定义切面类,并纳入 spring bean 管理
1 2 3 4 5 @Aspect @Component public class MyAspect { }
第四步:spring.xml 中开启组件扫描,启用自动代理
1 2 <context:component-scan base-package ="com.shameyang.spring6.service" /> <aop:aspectj-autoproxy proxy-target-class ="true" />
第五步:切面类中添加通知,然后添加切点表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Aspect @Component public class MyAspect { @Before("execution(* com.shameyang.spring6.service.OrderService.*(..))") public void adviceBefore () { System.out.println("前置通知..." ); } @Around("execution(* com.shameyang.spring6.service.OrderService.*(..))") public void adviceAround (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始..." ); proceedingJoinPoint.proceed(); System.out.println("环绕通知结束..." ); } }
第六步:测试程序
1 2 3 4 5 6 7 8 public class AOPTest { @Test public void testAOP () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("spring.xml" ); OrderService orderService = applicationContext.getBean("orderService" , OrderService.class); orderService.generate(); } }
通知对应的注解 前置通知:@Before
后置通知:@AfterReturning
环绕通知:@Around
异常通知:@AfterThrowing
最终通知:@After
设置切面的优先级 可以使用@Order 注解来标识切面,为 value 指定一个整型数,数字越小,优先级越高
切点表达式复用 我们可以将切点表达式单独定义出来,在需要的位置引入,这样可以复用切点方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Aspect @Component public class MyAspect { @Pointcut("execution(* com.shameyang.spring6.service.OrderService.*(..))") public void pointcut () { } @Before("pointcut()") public void adviceBefore () { System.out.println("前置通知..." ); } @Around("pointcut()") public void adviceAround (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始..." ); proceedingJoinPoint.proceed(); System.out.println("环绕通知结束..." ); } }
全注解式开发 编写一个类,代替 spring.xml 文件:
1 2 3 4 5 @Configuration @ComponentScan("com.shameyang.spring6.service") @EnableAspectJAutoProxy(proxyTargetClass = true) public class Spring6Configuration {}
修改测试程序: new AnnotationConfigApplicationContext
1 2 3 4 5 6 7 8 public class AOPTest { @Test public void testAOP () { ApplicationContext applicationContext = new AnnotationConfigApplicationContext (Spring6Configuration.class); OrderService orderService = applicationContext.getBean("orderService" , OrderService.class); orderService.generate(); } }
十一、Spring 对事务的支持 11.1 事务管理 API Spring 对事务的管理,底层是基于 AOP 实现的,采用 AOP 的方式进行了封装
核心接口:PlatformTransactionManager,在 Spring6 中的实现:
DataSourceTransactionManager:支持 JdbcTemplate、Mybatis、Hibernate 等事务管理
JtaTransactionManager:支持分布式事务管理
11.2 声明式事务基于注解的实现方式 第一步:spring.xml 中配置事务管理器
1 2 3 <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean >
第二步:spring.xml 中引入 tx 命名空间
1 2 3 4 5 6 7 8 <?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" xmlns:tx ="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" >
第三步:spring.xml 中配置 事务注解驱动器
1 <tx:annotation-driven transaction-manager ="transactionManager" />
第四步:在 service 类或方法上添加 @Transactional 注解
事务中的重点属性 1 2 3 4 5 6 7 8 9 10 11 12 Propagation propagation () default Propagation.REQUIRED; Isolation isolation () default Isolation.DEFAULT; int timeout () default -1 ;boolean readOnly () default false ;Class<? extends Throwable >[] rollbackFor() default {}; Class<? extends Throwable >[] noRollbackFor() default {};
propagation 事务传播行为 事务传播行为用来描述某个事务传播行为修饰的方法被嵌套进另一个方法的事务时如何传播
设置事务的传播行为:@Transaction(propagation = Propagation.REQUIRED)
propagation 的 value 如下:
属性值
描述
REQUIRED(默认)
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中
SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行
MANDATORY
使用当前的事务,如果当前没有事务,就抛出异常
REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起
NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER
以非事务方式执行,如果当前存在事务,则抛出异常
NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 REQUIRED 类似的操作
isolation 事务隔离级别 设置事务的隔离级别:@Transaction(isolation = Isolation.XXX)
isolation 的 value 如下:
属性值
描述
DEFAULT
不设置隔离级别
READ_UNCOMMITTED
读未提交。能够读取到其他事务未提交的数据(脏读)
READ_COMMITTED
提已提交。其他事务提交以后才能读到,但是不可重复读
REPEATABLE_READ
可重复读。只要当前事务不结束,读取到的数据一直都是一样的(幻读)
SERIALIZABLE
序列化。解决了幻读问题,事务排队执行,不支持并发
timeout 事务超时 设置事务超时时间:@Transaction(timeout = ...s)
默认值为-1,表示没有时间限制
当超过设置的时间,如果 DML 语句还没有执行完,则回滚
readOnly 只读事务 设置只读事务:@Transaction(readOnly = true)
启动 spring 优化策略,提高 select 语句执行效率
事务中只能执行 select 语句
rollbackFor 设置遇到哪些异常时回滚
例如:只有发生 RuntimeException 异常或该异常的子类异常才回滚
1 @Transaction(rollbackFor = RuntimeException.class)
norollbackFor 设置遇到哪些异常时不回滚
例如:发生 NullPointException 或该异常的子类异常时不回滚
1 @Transaction(norollbackFor = NullPointException.class)
11.3 事务的全注解式开发 编写一个类来代替配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Configuration @ComponentScan("...") @EnableTransactionManagement public class Spring6Config { @Bean(name = "dataSource") public DataSource getDataSource () { DruidDataSource dataSource = new DruidDataSource (); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver" ); dataSource.setUrl("jdbc:mysql://localhost:3306/spring6" ); dataSource.setUsername("root" ); dataSource.setPassword("123456" ); return dataSource; } @Bean(name = "jdbcTemplate") public JdbcTemplate getJdbcTemplate (DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate (); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } @Bean(name = "transactionManager") public DataSourceTransactionManager getDataSourceTransactionManager (DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager (); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
11.4 声明式事务基于 XML 的实现方式 第一步:添加 AspectJ 依赖
1 2 3 4 5 6 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 6.1.0-M2</version > </dependency >
第二步:配置事务管理器
1 2 3 <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean >
第三步:配置通知
1 2 3 4 5 6 7 8 <tx:advice id ="txAdvice" transaction-manager ="txManager" > <tx:attributes > <tx:method name ="save*" propagation ="REQUIRED" rollback-for ="java.lang.Throwable" /> <tx:method name ="del*" propagation ="REQUIRED" rollback-for ="java.lang.Throwable" /> <tx:method name ="update*" propagation ="REQUIRED" rollback-for ="java.lang.Throwable" /> <tx:method name ="transfer*" propagation ="REQUIRED" rollback-for ="java.lang.Throwable" /> </tx:attributes > </tx:advice >
第四步:配置切面
1 2 3 4 <aop:config > <aop:pointcut id ="txPointcut" expression ="execution(* com.shameyang.bank.service..*(..))" /> <aop:advisor advice-ref ="txAdvice" pointcut-ref ="txPointcut" /> </aop:config >
十二、Spring6 整合 JUnit 12.1 Spring 对 JUnit4 的支持 在单元测试类上使用 @RunWith 和 @ContextConfiguration 之后,测试类属性上可以使用 @AutoWired
相关依赖如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 6.1.4</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.13.2</version > <scope > test</scope > </dependency >
单元测试类中使用相关注解:
1 2 3 4 5 6 7 8 9 10 11 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring.xml") public class SpringJUnit4Test { @Autowired private OrderService orderService; @Test public void testConstructorDI () { orderService.delete(); } }
12.2 Spring 对 JUnit5 的支持 在单元测试类上使用 @ExtendWith 和 @ContextConfiguration 之后,测试类属性上可以使用 @AutoWired
相关依赖如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 6.1.4</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter</artifactId > <version > 5.10.0</version > <scope > test</scope > </dependency >
单元测试类中使用相关注解:
1 2 3 4 5 6 7 8 9 10 11 @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:spring.xml") public class SpringJUnit4Test { @Autowired private OrderService orderService; @Test public void testConstructorDI () { orderService.delete(); } }
十三、Spring6 集成 MyBatis 13.1 实现步骤 ① IDEA 中创建模块,引入依赖
spring-context
spring-jdbc
mysql 驱动
mybatis
mybatis-spring:mybatis 提供的与 spring 框架集成的依赖
德鲁伊连接池
junit
② 基于三层架构,创建相关的包
mapper
service service.impl
pojo
③ 编写 pojo 类
④ 编写 mapper 接口
⑤ 编写 mapper 配置文件
⑥ 编写 service 接口及其实现类
⑦ jdbc.properties
⑧ mybatis-config.xml
大部分的配置转移到 spring.xml 中
该文件中编写 mybatis 相关的系统级配置
⑨ spring.xml
组件扫描
引入外部的属性文件
SqlSessionFactoryBean 配置
Mapper 扫描配置器
事务管理器 DataSourceTransactionManager
启用事务注解
13.2 具体实现 引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.shameyanng</groupId > <artifactId > spring6-006-mybatis</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > jar</packaging > <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.1.4</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 6.1.4</version > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <version > 8.2.0</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.13</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 3.0.3</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.16</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.13.2</version > <scope > test</scope > </dependency > </dependencies > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > </project >
mybatis-config.xml
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings > </configuration >
spring.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?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" xmlns:tx ="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <context:component-scan base-package ="com.shameyang.bank" /> <context:property-placeholder location ="jdbc.properties" /> <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="driverClassName" value ="${jdbc.driver}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> </bean > <bean class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="configLocation" value ="mybatis-config.xml" /> <property name ="dataSource" ref ="dataSource" /> <property name ="typeAliasesPackage" value ="com.shameyang.bank.pojo" /> </bean > <bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="com.shameyang.bank.mapper" /> </bean > <bean id ="txManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean > <tx:annotation-driven transaction-manager ="txManager" /> </beans >