Spring - 基础篇
一、概念介绍
1.1 简介
开源框架,SSM之一,实现底层类的实例化喝生命周期的管理
Spring是一个开源的服务器开发框架,也是容器框架、中间层框架
Spring框架主要由七部分组成,分别是
Spring Core
Spring AOP
Spring ORM
Spring DAO
Spring Context
Spring Web
Spring Web MVC
1.2 Spring的五大功能模块
| 功能模块 | 功能介绍 |
|---|---|
| Core Container | 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。 |
| AOP&Aspects | 面向切面编程 |
| Testing | 提供了对 junit 或 TestNG 测试框架的整合。 |
| Data Access/Integration | 提供了对数据访问/集成的功能。 |
| Spring MVC | 提供了面向Web应用程序的集成功能。 |
1.3 读取配置文件方式(IoC容器)
Spring去加载配置文件使用的实现类,例如,new ClassPathXmlApplicationContext("XXXXXX.xml");
| 类型名 | 简介 |
|---|---|
| ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
| FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
| ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。 |
| WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
| AnnotationConfigApplicationContext | @Configuration注解的spring容器加载方式,用于完全注解开发,舍弃XML |
1.4 依赖传入
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
二、Bean实战案例
2.1 使用规范
- 所有属性为private
- 提供默认构造方法
- 提供getter和setter
- 实现serializable接口
- 禁止通过【非构造函数】传入依赖
- 禁止直接在类中进行实例化
2.2 基于XML的装配
2.2.1 bean常用属性和子元素
2.2.2 操作逻辑梳理
- XML中完成对象属性配置
- Java加载这个配置文件,得到对象
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="persion" class="com.test0607.Persion">
<property name="name" value="李明"/>
<!-- 这里的name调用了Persion类的setname方法,
让value进行常量复制==赋值 -->
</bean>
</beans>
Java
package com.test0607;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
class Persion {
public String getName() {
return name; }
public void setName(String name) {
this.name = name; }
private String name;
public void say() {
System.out.println("方法say的println输出");}
Persion() {
System.out.println("构造函数输出name:" + name); }
void show() {
System.out.println(name);
}}
public class T1 {
public static void main(String[] args) {
// 1、加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beanOne.xml");
// 2、创建对象
Persion persion = context.getBean("persion", Persion.class);
System.out.println("Persion的对象地址:" + persion);
persion.say();
persion.show(); }}
2.2.3 设值注入
<!-- 设值注入 ,用这种方式要提供setting方法-->
<bean id="student2" class="com.test0607.Student" scope="prototype">
<property name="name" value="cat"></property>
<property name="age" value="23"></property>
<property name="aList">
<list>
<value>"设值注入2-1"</value>
<value>"设值注入2-2"</value>
</list>
</property>
</bean>
2.2.4 构造注入
<!-- 构造注入 -->
<bean id="student1" class="com.test0607.Student" scope="prototype">
<constructor-arg index="0" value="gou"></constructor-arg>
<constructor-arg index="1" value="18"></constructor-arg>
<constructor-arg index="2">
<list>
<value>"list1_student1"</value>
<value>"list2_student1"</value>
</list>
</constructor-arg>
</bean>
2.2.5 内部Bean
<!-- 实验五 [重要]给bean的属性赋值:内部bean -->
<bean id="happyComponent5" class="com.atguigu.ioc.component.HappyComponent">
<property name="happyMachine">
<!-- 在一个 bean 中再声明一个 bean 就是内部 bean -->
<!-- 内部 bean 可以直接用于给属性赋值,可以省略 id 属性 -->
<bean class="com.atguigu.ioc.component.HappyMachine">
<property name="machineName" value="makeHappy"/>
</bean>
</property>
</bean>
2.2.6 级联Bean
<bean id="happyMachine2" class="com.atguigu.ioc.component.HappyMachine"/>
<!-- 实验七 给bean的属性赋值:级联属性赋值 -->
<bean id="happyComponent6" class="com.atguigu.ioc.component.HappyComponent">
<!-- 装配关联对象 -->
<property name="happyMachine" ref="happyMachine2"/>
<!-- 对HappyComponent来说,happyMachine的machineName属性就是级联属性 -->
<property name="happyMachine.machineName" value="cascadeValue"/>
</bean>
2.2.7 特殊值处理
一、基本赋值
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="commonValue" value="zhangsan"/>
二、bean中bean(类比如:一个类里面有其他类的对象,给这个对象赋值)
<!-- 使用ref属性给bean的属性复制是,Spring会把ref属性的值作为一个bean的id来处理 -->
<!-- 此时ref属性的值就不是一个普通的字符串了,它应该是一个bean的id -->
<property name="happyMachine" ref="happyMachine"/>
三、赋值null
<property name="commonValue">
<!-- null标签:将一个属性值明确设置为null -->
<null/>
</property>
四、假如String类型是‘大于号’‘小于号’符号呢
<bean id="propValue" class="com.atguigu.ioc.component.PropValue">
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a < b"/>
</bean>
<bean id="propValue" class="com.atguigu.ioc.component.PropValue">
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>
</bean>
2.2.8 使用p名称空间
使用 p 名称空间的方式可以省略子标签 property,将组件属性的设置作为 bean 标签的属性来完成
<bean id="happyMachine3"
class="com.atguigu.ioc.component.HappyMachine"
p:machineName="goodMachine"
</bean>
使用 p 名称空间需要导入相关的 XML 约束,在 IDEA 的协助下导入即可
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
2.2.9 集合赋值
<bean id="happyTeam2" class="com.atguigu.ioc.component.HappyTeam">
<property name="memberNameList">
<!-- list标签:准备一组集合类型的数据,给集合属性赋值 -->
<!--<list>
<value>member01</value>
<value>member02</value>
<value>member03</value>
</list>-->
<!-- 使用set标签也能实现相同效果,只是附带了去重功能 -->
<!--<set>
<value>member01</value>
<value>member02</value>
<value>member02</value>
</set>-->
<!-- array也同样兼容 -->
<array>
<value>member01</value>
<value>member02</value>
<value>member02</value>
</array>
</property>
<property name="managerList">
<!-- 给Map类型的属性赋值 -->
<!--<map>
<entry key="财务部" value="张三"/>
<entry key="行政部" value="李四"/>
<entry key="销售部" value="王五"/>
</map>-->
<!-- 也可以使用props标签 -->
<props>
<prop key="财务部">张三2</prop>
<prop key="行政部">李四2</prop>
<prop key="销售部">王五2</prop>
</props>
</property>
</bean>
2.3 基于注解的装配
2.3.1 常用注解说明
@Controller:就是我们在三层架构中表述层里面,使用的控制器。以前是Servlet,以后我们将会使用Controller来代替Servlet
@Service:三层架构中使用的业务逻辑组件
@Repository:
这个组件就是我们以前用的Dao类,但是以后我们整合了Mybatis,这里就变成了Mapper接口,而Mapper接口是由Mybatis和Spring的整合包负责扫描的。
由于Mybatis整合包想要把Mapper接口背后的代理类加入Spring的IOC容器需要结合Mybatis对Mapper配置文件的解析,所以这个事情是Mybatis和Spring的整合包来完成,将来由Mybatis负责扫描,也不使用@Repository注解
2.3.2 操作逻辑梳理
在使用annotation前,需要在beans.xml配置命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.test0607" />
</beans>
- 配置命名空间,开启包扫描
- 标注类为bean
- main方法,加载xml,拿到bean对象
2.3.3 代码范本(1)
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--
1、添加新的annotation命名空间
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
2、开启注解
<context:annotation-config></context:annotation-config>
3、开启自动扫描功能
<context:component-scan base-package="com.test0607">
</context:component-scan>
不需在 XML中显式使用 进行 Bean的配置。
Spring在容器初始化时将自动扫描 base-package指定的包及其子包下的所有 class 文件,
所有标注了 @Repository 的类都将被注册为 Spring Bean。
-->
<!-- 2 开启注解 -->
<context:annotation-config></context:annotation-config>
<!-- 3 开启自动扫描 -->
<context:component-scan base-package="springtest">
</context:component-scan>
</beans>
Java
package springtest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
// 就还能注解的形式添加作用域
// @Scop("")
@Repository("persion01")
public class Persion {
@Value("张三")
private String name;
@Value("18")
private Integer age;
public Persion() {
}
public Persion(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Persion{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Test测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import springtest.Persion;
public class Test {
public static void main(String[] args) {
// 1、加载XML文件
ApplicationContext context =
new ClassPathXmlApplicationContext("springconfig/springAnnoTest.xml");
Persion persion = context.getBean("persion01", Persion.class);
System.out.println(persion); }}
2.3.4 包扫描的规则处理
<!-- 情况一:配置自动扫描的包(常用) -->
<!-- 最基本的扫描方式 -->
<context:component-scan base-package="com.atguigu.ioc.component"/>
<!-- 情况二:在指定扫描包的基础上指定匹配模式 -->
<context:component-scan
base-package="com.atguigu.ioc.component"
resource-pattern="Soldier*.class"/>
<!-- 情况三:指定不扫描的组件 -->
<context:component-scan base-package="com.atguigu.ioc.component">
<!-- context:exclude-filter标签:指定排除规则 -->
<!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
<!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 情况四:仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.atguigu.ioc.component" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2.3.5 完全注解开发
看SpringBoot章节吧
2.4 自动装配
虽然使用注解的方式装配Bean在一定程度减少了配置文件的代码量,但是仍要考虑部分企业初始开发的时候没有使用注解,那有没有办法既可以减少代码量,又可以实现Bean的装配呢
通过设置Spring的元素中包含的autowire属性值来实现自动装配Bean
所谓自动装配,就是把Bean自动注入到其他Bean的Property中
注:这里的Peoperty,就是跟上面的一样,给属性赋值用的
2.4.1 常用autowire属性说明
2.4.2 操作逻辑梳理
XML
- 1 添加xml的文件头,并添加开启注解配置
- 2 相关的bean信息写入
JAVA
- 1 创建实体类
- 2 实体类属性添加注解(也可以放在set方法上)
- 3 使用@Test方法进行测试
注解方法装配属性的过程:
spring会默认优先根据(被注解修饰的)属性类型去容器中找对应的组件(bean),找到就赋值;若找到多个相同类型的组件,再将属性的名称作为组件(bean)的id去容器中查找。
2.4.3 代码范本(1)
实体类
// 猫狗类
public class Dog {
public void dogSay() {
System.out.println("dogSay...");
}}
public class Cat {
public void catSay() {
System.out.println("catSay...");
}}
// 人类,包含猫和狗的对象
import javafx.scene.chart.CategoryAxis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class Human {
@Autowired
@Qualifier(value = "dog")
private Dog dog;
@Autowired
@Qualifier(value = "cat")
private Cat cat;
private String name;
public Human() {
}
public Human(Dog dog, Cat cat, String name) {
this.dog = dog;
this.cat = cat;
this.name = name;
}
@Override
public String toString() {
return "Human{" +
"dog=" + dog +
", cat=" + cat +
", name='" + name + '\'' +
'}';
}
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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:annotation-config></context:annotation-config>
<bean id="cat" class="springtest.Cat"></bean>
<bean id="dog" class="springtest.Dog"></bean>
<bean id="human" class="springtest.Human">
<property name="name" value="张三"></property>
</bean>
</beans>
Test测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import springtest.Human;
public class Test02 {
public static void main(String[] args) {
// 1、加载XML文件
ApplicationContext context =
new ClassPathXmlApplicationContext("springconfig/springAnnoTest.xml");
Human human = context.getBean("human", Human.class);
human.getCat().catSay();
human.getDog().dogSay();
System.out.println(human);
}}
2.5 作用域
作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope配置项,可以灵活定义Bean的作用范围。
例如,
当你希望每次IOC容器返回的Bean是同一个实例时,可以设置scope为singleton;
当你希望每次IOC容器返回的Bean实例是一个新的实例时,可以设置scope为prototype。
| 取值 | 含义 | 创建对象的时机 | 默认值 |
|---|---|---|---|
| singleton | 在 IOC 容器中,这个 bean 的对象始终为单实例 | IOC 容器初始化时 | 是 |
| prototype | 这个 bean 在 IOC 容器中有多个实例 | 获取 bean 时 | 否 |
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
| 取值 | 含义 |
|---|---|
| request | 在一个请求范围内有效 |
| session | 在一个会话范围内有效 |
2.6 Bean的生命周期(重点)
2.6.1 流程图
深究Spring中Bean的生命周期 - Java知音号 - 博客园 (cnblogs.com)
2.6.2 阶段相关接口方法
容器级生命周期接口方法
- InstantiationAwareBeanPostProcessor接口:此接口可以在Bean实例化前、Bean实例化后分别进行操作(因为里面有Before和After方法),也可以对Bean实例化之后进行属性操作(为BeanPostProcessor的子接口)。
- BeanPostProcessor接口:此接口的方法可以对Bean的属性进行更改。
- InstantiationAwareBeanPostProcessorAdapter:适配器类,最终实现了
BeanPostProcessor这个顶级接口。
工厂级生命周期
在应用上下文装配配置文件之后立即调用
默认的bean是单例的,也就是说只有spring 容器关闭的时候才会销毁这些bean对象,如果声明的bean对象是prototype类型的话,就非单例了, 那么这些对象将不由spring容器维护,该对象没有引用的时候jvm会适时垃圾回收掉
三、AOP
AOP全称是 Aspect-Oriented Programming,既面向切面编程,是对OOP面向对象的一种补充
3.1 基本概念
虽然面向对象编程语言实现了纵向的对每个对象的行为进行归类和划分,实现了高度的抽象,但是,不同对象间的共性却不适合用面向对象编程的方式实现。如学生对象和汽车对象,都要实现与其自身业务逻辑无关的监控,在这种场景下,使用面向对象编程的方式可能最好的解决方案就是让学生对象和汽车对象都集成监控接口,然后学生对象和汽车对象分别实现监控方法。这种编程方式的缺点是,由于监控逻辑并不是对象本身的核心功能,并且不同对象的监控逻辑实现基本上相同——都是监控某个时间发生了某件事,只是记录的对象不同而已,这会导致监控对象行为的代码逻辑散落在系统的各个地方,并且几乎都是重复的代码,与对象的核心功能并无很强的关联性。这样的设计会导致大量的代码重复,并且不利于模块的复用。
AOP的出现,恰好解决了这个棘手的问题。其提供“横向”的切面逻辑,将与多个对象有关的公共模块分装成一个可重用模块,并将这个模块整合成为Aspect,即切面。切面就是对与具体的业务逻辑无关的,却是许多业务模块共同的特性或职责的一种抽象,其减少了系统中的重复代码,因此降低了模块的耦合度,更加有利于扩展。
AOP将软件系统分为两部分:核心逻辑和横切逻辑。核心逻辑主要处理系统正常的业务逻辑,横切逻辑不关注系统核心逻辑,其只关注与系统核心逻辑并非强相关的逻辑。
3.2 相关名词
- 横切关注点
- 切面(Aspect)
- 连接点(JoinPoint)
- 切入点(Pointcut)
- 通知(Advice)
- 目标对象(Target Object)
- 织入(Weaving)
- 引入(Introduction)
AOP增强类型
- Spring版本5.3.x以前:
- 前置通知
- 目标操作
- 后置通知
- 返回通知或异常通知
- Spring版本5.3.x以后:
- 前置通知
- 目标操作
- 返回通知或异常通知
- 后置通知
3.3 技术体系
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。
因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。 - cglib:通过
继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。 - AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解
3.4 基于XML方式实现AOP
选择性遗忘
3.5 基于AspectJ方式实现AOP
3.5.1 相关注解
3.5.2 操作逻辑梳理
<!-- AOP —— 使用注解方式实现 -->
前置条件:存在增强类以及被增强的类
XML中
1、完成名称空间处理
2、开启注解扫描
5、开启 Aspect生成代理对象
Java文件中
3、被增强的类中 加入通知注解
4、增强类 加入注解 Aspect并使用 @Component 标记为Bean
6、配置不同类型的通知:在增强类中,在作为通知方法上面添加通知类型注解,使用切入点表达式 execution 配置
3.5.3 入门程序案例
spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 1 -->
<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:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- AOP —— 使用注解方式实现 -->
<!--
前置条件:存在增强类以及被增强的类
XML中
1、完成名称空间处理
2、开启注解扫描
5、开启 Aspect生成代理对象
Java文件中
3、被增强的类中 加入注解
4、增强类 加入注解 Aspect
6、配置不同类型的通知:在增强类中,在作为通知方法上面添加通知类型注解,使用切入点表达式 execution 配置
-->
<!-- 2 -->
<context:component-scan base-package="springtest.test03"/>
<!-- 5 -->
<aop:aspectj-autoproxy />
</beans>
接口
package springtest.test03;
/**
* @author 13544
* say hello
*/
public interface Student {
public void say(String text);
}
实现类
package springtest.test03;
import org.springframework.stereotype.Component;
@Component
public class StudentDao implements Student {
@Override
public void say(String text) {
// int x = 1/0;
System.out.println("ta对你说:" + text);
}}
切面
package springtest.test03;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Aspect
@Component
public class UserProxy {
// 在目标方法被调用之前做增强处理
// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before(value = "execution(public void springtest.test03.StudentDao.say(String)) ")
public void before(JoinPoint joinPoint) {
System.out.println("---before,在目标方法被调用之前做增强处理");
// 1.通过JoinPoint对象获取目标方法签名对象
// 方法的签名:一个方法的全部声明信息
Signature signature = joinPoint.getSignature();
// 2.通过方法的签名对象获取目标方法的详细信息
String methodName = signature.getName();
System.out.println("methodName = " + methodName);
int modifiers = signature.getModifiers();
System.out.println("modifiers = " + modifiers);
String declaringTypeName = signature.getDeclaringTypeName();
System.out.println("declaringTypeName = " + declaringTypeName);
// 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
// 4.由于数组直接打印看不到具体数据,所以转换为List集合
List<Object> argList = Arrays.asList(args);
System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);
}
// 在目标方法正常完成后做增强
// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(value = "execution(* springtest.test03.StudentDao.*(..)) ", returning = "targetMethodReturnValue")
public void afterReturning(Object targetMethodReturnValue) {
System.out.println("---afterReturning,在目标方法正常完成后做增强");
System.out.println("targetMethodReturnValue的返回值" + targetMethodReturnValue);
}
@AfterThrowing(value = "execution(public void springtest.test03.StudentDao.say(String)) ", throwing = "targetMethodException")
public void afterThrowing(Throwable targetMethodException) {
System.out.println("---afterThrowing,处理程序中未处理的异常");
System.out.println("方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
}
@After(value = "execution(public void springtest.test03.StudentDao.say(String)) ")
public void after() {
System.out.println("---after,在目标方法完成之后做增强,无论目标方法时候成功完成");
}
// (5)Around:环绕通知,在目标方法完成前后做增强处理
// 环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
@Around(value = "execution(public void springtest.test03.StudentDao.say(String)) ")
public void around(ProceedingJoinPoint proceed) throws Throwable {
System.out.println("环绕之前");
proceed.proceed();
System.out.println("环绕之后");
}
}
执行测试方法1:基于测试
<!-- https://mvnrepository.com/artifact/org.chiknrice/concordion-spring-runner -->
<dependency>
<groupId>org.chiknrice</groupId>
<artifactId>concordion-spring-runner</artifactId>
<version>1.0.0</version>
</dependency>
--------------------------------------------------------------------------------------------------------------------
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import springtest.test03.Student;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:spring-config.xml"})
public class TestDemo03 {
@Autowired
private Student student;
@Test
public void test03() {
student.say("hi!");
}
}
执行测试方法2:常规main
@ContextConfiguration(value = {"classpath:spring-config.xml"})
public class Test11 {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext
("spring-config.xml");
context.getBean("studentDao",Student.class).say("hello");
}}
3.5.4 连接点对象的使用
// 在目标方法被调用之前做增强处理
// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before(value = "execution(public void springtest.test03.StudentDao.say(String)) ")
public void before(JoinPoint joinPoint) {
System.out.println("---before,在目标方法被调用之前做增强处理");
System.out.println("-----------------------------------------------------------------------");
// 1.通过JoinPoint对象获取目标方法签名对象
// 方法的签名:一个方法的全部声明信息
Signature signature = joinPoint.getSignature();
// 2.通过方法的签名对象获取目标方法的详细信息
String methodName = signature.getName();
System.out.println("methodName = " + methodName);
// JAVA 反射机制中,Field的getModifiers()方法返回int类型值表示该字段的修饰符
int modifiers = signature.getModifiers();
System.out.println("modifiers = " + modifiers);
// 返回类类型,以包路径的形式
String declaringTypeName = signature.getDeclaringTypeName();
System.out.println("declaringTypeName = " + declaringTypeName);
// 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
// 4.由于数组直接打印看不到具体数据,所以转换为List集合
List<Object> argList = Arrays.asList(args);
System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);
System.out.println("-----------------------------------------------------------------------");
}
3.5.5 方法返回值获取
/ 在目标方法正常完成后做增强
// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(value = "execution(* springtest.test03.StudentDao.*(..)) ", returning = "targetMethodReturnValue")
public void afterReturning(Object targetMethodReturnValue) {
System.out.println("---afterReturning,在目标方法正常完成后做增强");
System.out.println("targetMethodReturnValue的返回值" + targetMethodReturnValue);
}
3.5.6 获取异常对象
@AfterThrowing(value = "execution(public void springtest.test03.StudentDao.say(String)) ", throwing = "targetMethodException")
public void afterThrowing(Throwable targetMethodException) {
System.out.println("---afterThrowing,处理程序中未处理的异常");
System.out.println("方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
}
3.5.7 重用切入点表达式
在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。易于维护,一处修改,处处生效。声明方式如下:
// 切入点表达式重用
@Pointcut("execution(public int com.atguigu.aop.api.Calculator.add(int,int)))")
public void declarPointCut() {}
同一个类内部引用
@Before(value = "declarPointCut()")
public void printLogBeforeCoreOperation(JoinPoint joinPoint) {
在不同类中引用
@Around(value = "com.atguigu.spring.aop.aspect.LogAspect.declarPointCut()")
public Object roundAdvice(ProceedingJoinPoint joinPoint) {
集中管理
而作为存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于统一管理
@Component
public class AtguiguPointCut {
@Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
public void atguiguGlobalPointCut(){}
@Pointcut(value = "execution(public int *..Calculator.add(int,int))")
public void atguiguSecondPointCut(){}
@Pointcut(value = "execution(* *..*Service.*(..))")
public void transactionPointCut(){}
}
3.5.8 切入点表达式语法
3.5.9 环绕通知
环绕通知对应整个 try...catch...finally 结构,包括前面四种通知的所有功能
// 使用@Around注解标明环绕通知方法
@Around(value = "com.atguigu.aop.aspect.AtguiguPointCut.transactionPointCut()")
public Object manageTransaction(
// 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参,
// Spring会将这个类型的对象传给我们
ProceedingJoinPoint joinPoint) {
// 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组
Object[] args = joinPoint.getArgs();
// 通过ProceedingJoinPoint对象获取目标方法的签名对象
Signature signature = joinPoint.getSignature();
// 通过签名对象获取目标方法的方法名
String methodName = signature.getName();
// 声明变量用来存储目标方法的返回值
Object targetMethodReturnValue = null;
try {
// 在目标方法执行前:开启事务(模拟)
System.out.println("[AOP 环绕通知] 开启事务,方法名:" + methodName + ",参数列表:" + Arrays.asList(args));
// 过ProceedingJoinPoint对象调用目标方法
// 目标方法的返回值一定要返回给外界调用者
targetMethodReturnValue = joinPoint.proceed(args);
// 在目标方法成功返回后:提交事务(模拟)
System.out.println("[AOP 环绕通知] 提交事务,方法名:" + methodName + ",方法返回值:" + targetMethodReturnValue);
}catch (Throwable e){
// 在目标方法抛异常后:回滚事务(模拟)
System.out.println("[AOP 环绕通知] 回滚事务,方法名:" + methodName + ",异常:" + e.getClass().getName());
}finally {
// 在目标方法最终结束后:释放数据库连接
System.out.println("[AOP 环绕通知] 释放数据库连接,方法名:" + methodName);
}
return targetMethodReturnValue;
}
3.5.10 切面优先级
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
- 优先级高的切面:外面
- 优先级低的切面:里面
使用 @Order 注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
3.5.11 若实现类没有继承接口
在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理
3.6 基于Spring方式实现AOP
做一个注解
/**
* 自定义一个注解
* 用于标记在方法上进行切面编程
*
* @author 李家民
*/
public @interface AnnDefinedDemo {
}
做一个切面代理类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author 李家民
*/
@Component
@Aspect
public class AopDemo {
@Around("@annotation(com.ljm.aop.AnnDefinedDemo)")
public Object methodTest(ProceedingJoinPoint point) throws Throwable {
System.out.println("aop_first");
Object proceed = point.proceed();
System.out.println("aop_end");
return proceed;
}
}
套在方法头上
@AnnDefinedDemo
@Override
public String aopTest() {
System.out.println("...我是中间人.....");
return "aopTest";
}
就是这么简单,你学会了吗
四、声明式事务
4.1 基本概念
编程式事务
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
声明式事务
技术体系
我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 Mybatis 用的也是这个类。
DataSourceTransactionManager类中的主要方法:
- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务
4.2 JDBC Template
4.2.1 基本流程
- 导入JAR包
- 编写数据库连接,以及Druid的相关配置
- 编写代码(持久化层,服务层,POJO)
- 在测试类编写sql语句,执行(增删改查我就不一一列举了,最后都有mybatis代替)
4.2.2 代码范本
pom
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<!-- Spring 持久化层支持jar包 -->
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 -->
<!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.8</version>
</dependency>
<!-- Spring 测试相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.16</version>
</dependency>
</dependencies>
jdbc.properties
jdbc.url=jdbc:mysql://47.106.207.254:3306/ssm
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.username=******
jdbc.password=*****
jdbc.filters=stat
jdbc.initialSize=2
jdbc.maxActive=300
jdbc.maxWait=60000
jdbc.timeBetweenEvictionRunsMillis=60000
jdbc.minEvictableIdleTimeMillis=300000
jdbc.validationQuery=SELECT 1
jdbc.testWhileIdle=true
jdbc.testOnBorrow=false
jdbc.testOnReturn=false
jdbc.poolPreparedStatements=false
jdbc.maxPoolPreparedStatementPerConnectionSize=200
spring-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config></context:annotation-config>
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.jiamin"/>
<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:dbConfig/jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="filters" value="${jdbc.filters}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<property name="maxWait" value="${jdbc.maxWait}"/>
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
<property name="testOnBorrow" value="${jdbc.testOnBorrow}"/>
<property name="testOnReturn" value="${jdbc.testOnReturn}"/>
<property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 装配数据源 -->
<property name="dataSource" ref="druidDataSource"/>
</bean>
</beans>
Java代码
@Service
public class BookServices {
@Autowired
private BooksDao booksDao;
public void addBook(Books book){
booksDao.addBook(book);
}
}
---
public class Books {
private Integer bookId;
private String bookName;
private int bookCounts;
private String detail;
// get set 方法我省略了..实际代码要加上
}
---
public interface BooksDao {
public void addBook(Books book);
}
@Repository // 表示为bean
public class BooksDaoImpl implements BooksDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addBook(Books book) {
String sql = "insert into books values(?,?,?,?)";
int update = jdbcTemplate.update(sql, book.getBookId(), book.getBookName(), book.getBookCounts(), book.getDetail());
System.out.println("update = " + update);
}
}
---
public class TestBook {
public static void main(String[] args) {
String path = "SpringConfig/spring-context.xml";
// 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext(path);
// 获取bean
BookServices bookServices = context.getBean("bookServices", BookServices.class);
Books books = new Books();
books.setBookId(5);
books.setBookName("test");
books.setBookCounts(200);
books.setDetail("test01");
bookServices.addBook(books);
}
}
4.3 基于XML
4.3.1 操作流程
加入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
修改XML文件,去掉注解驱动支持
<aop:config>
<!-- 配置切入点表达式,将事务功能定位到具体方法上 -->
<aop:pointcut id="txPoincut" expression="execution(* *..*Service.*(..))"/>
<!-- 将事务通知和切入点表达式关联起来 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoincut"/>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
4.3.2 代码示例
必不可能写
4.4 基于注解
4.4.1 操作流程
xml中
- 添加事务管理器并关联数据源
- 添加命名空间
- 添加注解支持
- 添加事务管理器驱动
Service层
- 在类或方法上添加@Transactional事务注解
以上操作完成后,即可执行Test测试
4.4.2 代码范本
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/context/spring-tx.xsd">
<!-- 声明式事务管理tx:添加命名空间tx -->
<context:annotation-config></context:annotation-config>
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.jiamin"/>
<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:dbConfig/jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="filters" value="${jdbc.filters}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<property name="maxWait" value="${jdbc.maxWait}"/>
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
<property name="testOnBorrow" value="${jdbc.testOnBorrow}"/>
<property name="testOnReturn" value="${jdbc.testOnReturn}"/>
<property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 装配数据源 -->
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!-- 创建一个事务管理器 -->
<bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!-- 注册事务管理器驱动 -->
<tx:annotation-driven transaction-manager="TransactionManager"/>
</beans>
java
@SpringJUnitConfig(locations = {"classpath:spring-context.xml"})
public class SSMTest01 {
@Autowired
private DataSource dataSource;
@Autowired
private SeatMapper seatMapper;
@Test
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void testConn() throws SQLException {
seatMapper.insertSeat(new Seat(102, "test102"));
}
}
4.4.3 @Transactional注解参数
4.4.4 传播行为
4.5 基于完全注解
4.5.1 操作流程
1、做一个配置类,把数据库相关的信息进行写入
2、编写代码(持久化层,服务层,POJO)
3、新建Test类测试,要使用new AnnotationConfigApplicationContext加载配置类
4.5.2 代码示例
配置类
package com.allano.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.SQLException;
/**
* @author 13544
* 完全注解开发
* -
* @Configurable 指定此类为Spring配置类。作为配置类,替代XML配置文件
* @ComponentScan 指定Bean范围(注解范围),扫描装载
* @EnableTransactionManagement 开启事务
*/
@Configurable
@ComponentScan(basePackages = {"com.jiamin"})
@EnableTransactionManagement
public class SpringConfig {
@Bean
public DruidDataSource getDruidDataSource() throws SQLException {
// 创建数据库连接池
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://47.106.207.254:3306/ssm");
dataSource.setUsername("root");
dataSource.setPassword("szsti@123");
dataSource.setDriverClassName("com.alibaba.druid.pool.DruidDataSource");
dataSource.setFilters("stat");
dataSource.setMaxActive(300);
dataSource.setInitialSize(2);
dataSource.setMaxWait(60000);
return dataSource;
}
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
// 配置 JdbcTemplate
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager DataSourceTransactionManager(DataSource dataSource) {
// 创建事务管理器
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}