笔记基于教程:
一、基本介绍
官网:spring.io/
1.架构图
2.核心知识点
- IOC
- AOP
- 事务管理
二、核心概念 IOC
1.基本介绍
- 基本概念
IOC(控制反转):即由原本手动 new 对象变为容器自动创建对象。Spring 框架提供了一个 IOC 容器,会将框架自动创建的对象放到里面。需要使用什么对象从容器里拿即可,不需要自己手动 new 了。 - 基本概念
DI:对于有依赖关系的对象,IOC 容器会自动对他们进行关系的绑定 - 最终效果:使用的对象的时候可以直接从容器中获取,并且获取到的 bean 对象已经绑定了依赖关系
2.基本案例
- 在 Maven 项目中导入 Spring 的 IOC 依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.0.1</version>
</dependency>
</dependencies>
- 编写实体类
public class Student {
private String name;
private Integer age;
private String address;
public Student() {
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
- 创建配置文件,将实体类配置进去
id 属性:对象的唯一表示
class 属性:类的全路径
<?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="student" class="dto.Student"></bean>
</beans>
- 初始化容器,然后通过容器获取对象
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
}
3.基本案例分析
3.1 无参构造器是否会执行
无参构造会执行
3.2 怎么创建对象的
加载 xml 文件后,解析到里面的 bean 标签。然后读取 bean 的 class 属性作为全路径,通过反射创建对象。然后 bean 的 id 属性作为该对象的唯一表示。
3.3 创建的对象放到哪里了
会把创建的对象放到 IOC 容器中(一个 Map 集合,key 是 bean 标签的 id 属性,value 是类的定义信息)
4.IOC 基于 XML
操作案例可以参考 IOC 的基本案例部分,这部分主要进行详细知识点介绍
4.1 获取 Bean 的方式
有三种方式获取 Bean:
- 根据 id 属性
- 根据 class 属性(同一个类在多例情况下会报错)(同一个接口有多个实现类的情况下,无法通过接口类型的 class 获取对应的 bean)
- 根据 id 属性和 class 属性
4.1.1 根据 id 属性
- XML 中配置 Bean 信息,然后给 bean 加上 id 属性
<?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="student" class="dto.Student"></bean>
</beans>
- 然后 java 代码中根据 id 值获取对象
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
}
4.1.2 根据 class 属性
- XML 中配置 Bean 信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="dto.Student"></bean>
</beans>
- 然后 java 代码中根据 class 值获取对象
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Student student = (Student) context.getBean(Student.class);
System.out.println(student);
}
4.1.3 根据 id 和 class 属性
- XML 中配置 Bean 信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="dto.Student"></bean>
</beans>
- 然后 java 代码中根据 id 和 class 值获取对象
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Student student = (Student) context.getBean("student",Student.class);
System.out.println(student);
}
4.2 属性注入的方式
有两种方式注入依赖:
- 根据 setter 方法注入
- 根据构造器注入
4.2.1 基于 setter 方法注入
- 创建类,定义属性和set方法
- 在xml文件中配置对象
- 在xml中使用property标签注入属性
<!--创建Person对象(Bean)-->
<bean id="person1" class="com.junqing.test.Person">
<!--propert 使用set注入属性
name:属性名称
value:注入的值
-->
<property name="name" value="jack"></property>
<property name="age" value="20"></property>
</bean>
4.2.2 基于构造器注入
- 创建类,定义属性和set方法,声明有参构造器
- 在xml文件中配置对象
- 在xml中使用constructor-arg标签注入属性
<!--创建Person对象(Bean)-->
<bean id="person1" class="com.junqing.test.Person">
<!--constructor-arg 使用构造器注入属性
name:属性名称
value:注入的值
-->
<constructor-arg name="name" value="zyj"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>
4.3 特殊字段注入
直接给字段设置值,既不使用 set 方法,也不使用构造器
4.3.1 字面量注入
同 setter 注入方式一样
4.3.2 null 值注入
使用 null
<property name="address">
<null/>
</property>
4.3.3 特殊符号注入
当需要注入的特殊值包含特殊符号的时候,写入 xml 文件中会报错,因此可以用
<![CDATA[属性值]]>这种方式注入
<property name="address">
<value><![CDATA[1>=5]]></value>
</property>
4.4 对象属性注入
一个类调用另一个类的时候需要创建对象,这个时候怎么创建。例如:员工里面有个属性是部门对象。
4.4.1 外部 bean 注入
被引用的对象在外部声明
在xml文件中创建两个类,调用对象添加property标签调用ref
<!--调用类Service-->
<bean id="userService" class="com.junqing.test.UserService">
<!--ref 是调用类的id -->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<!--被调用类Dao-->
<bean id="userDaoImpl" class="com.junqing.test.UserDaoImpl"></bean>
4.4.2 内部 bean 注入
被引用的对象在内部声明
<bean id="emp" class="com.junqing.bean.Emp">
<property name="name" value="zyj"></property>
<property name="sex" value="girl"></property>
<!--内部bean-->
<property name="dept">
<bean id="dept" class="com.junqing.bean.Dept">
<!--内部bean属性注入-->
<property name="dname" value="英语部"></property>
</bean>
</property>
</bean>
4.4.3 级联属性赋值
用的不多,了解即可
4.5 数组属性注入
在
property标签中使用array标签注入
<bean id="test" class="com.junqing.bean.Test01">
<!--数组数据的注入-->
<property name="course">
<array>
<value>AJAX课程</value>
<value>java30天入门</value>
</array>
</property>
</bean>
4.6 集合属性注入
4.6.1 基本方式
在
property标签中使用List``Set``Map标签注入
<bean id="test" class="com.junqing.bean.Test01">
<!--List数据的注入-->
<property name="list">
<list>
<value>韩顺平</value>
<value>王振国</value>
</list>
</property>
<!--set数据的注入-->
<property name="set">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
<!--map数据的注入-->
<property name="map">
<map>
<entry key="01" value="韩顺平"></entry>
<entry key="02" value="王振国"></entry>
</map>
</property>
</bean>
4.6.2 引用集合方式
将注入的集合抽取到外面,然后创建对象的时候将抽取的集合注入
- 在spring配置文件中的
xsi:schemaLocation属性引入名称空间spring-util
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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创建集合对象,然后外部注入
<!--先抽取出list-->
<util:list id="BookList">
<value>豆奶粉</value>
<value>豆沙包</value>
</util:list>
<!--外部注入-->
<bean id="test1" class="com.junqing.bean.Test01">
<property name="list" ref="BookList"></property>
</bean>
4.7 自动装配
在一个 Bean 中注入另一个 Bean, 不再需要在 XML配置文件中写property标签,而是让 spring根据属性名称或者类型,自动进行属性注入。在 bean标签中设置属性autowire,即可开启自动装配。
autowire的两个常用值:
- byName:根据属性名注入(被注入bean的id值要和类内部set方法的参数名一致)
- byType:根据属性类型注入(没有找到对应的对象会为空,找到多个会报错)
<!--要把Dept注入emp中,Dept的id要和emp中的set方法的参数名一样-->
<bean id="dept" class="com.junqing.autowire.Dept"></bean>
<bean id="emp" class="com.junqing.autowire.Emp" autowire="byType"></bean>
public class Test {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("/com/junqing/autowire/Auto.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
}
5.IOC 基于 注解
5.1 标注型注解
在开启 Bean 扫描后。会自动将包含该类型注解的类加载进 IOC 容器中
Controller:一般用在web层,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。Service:一般用在Service层,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。Repository:一般用在Dao层,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。Component:通用的注解,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。
5.2 属性注入注解
5.1.1 Autowired
根据属性类型进行自动注入
- 作用位置:
构造器、set 方法、有参构造的方法形参、属性、注解 - 使用案例
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 根据类型注入
}
5.1.2 Qualifier
根据属性名称进行注入。这个注解需要和上面的@AutoWired一起使用
@Autowired
@Qualifier(value = "user")
5.1.3 Resource
这个注解不是 Spring 自带的,而是 JDK 中提供的(JDK8 中自带,但是当 JDK 高于 11 或低于 8 需要引入依赖)。
- 作用位置:
属性、set方法 - 使用案例
// 做法1:明确指定 name,则只根据属性名匹配容器中的bean
@Service
public class UserService {
@Resource(name = "mysqlUserDao")
private UserDao userDao;
}
// 做法2:不指定name,则先根据属性名匹配,找不到再根据类型匹配
@Service
public class UserService {
@Resource
private UserDao userDao;
}
- 使用规则
- 可以根据属性名称装配(默认),也可以根据类型装配。
- 当使用注解的
name属性时,只按name匹配容器中的 bean,匹配不到直接报错 - 当没有使用
name属性时,会先根据属性名匹配容器中的 bean,如果匹配不到会自动根据类型匹配容器中的 bean。
- 相关依赖
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>3.0.0</version>
</dependency>
6 单例/多例切换
在Spring中,可以设置创建bean实例时,是单实例还是多实例。在Spring 中,默认情况下,bean是单实例。
- 单实例:Bean 对象在 IOC 容器初始化的时候创建
- 多实例:每次获取 Bean 的时候都会创建一个新的
6.1 XML 方式切换
使用 Bean 的
scope属性实现切换:
singleton:表示单实例对象(默认)prototype:表示多实例对象
//多实例对象
<bean id="test1" class="com.junqing.bean.Test01" scope="prototype">
</bean>
6.2 注解方式切换
使用
Scope注解切换
@Component
@Scope("prototype") // 或 "singleton"
public class UserService { ... }
7.Bean 的生命周期
7.1 基本流程
- 通过反射调用构造器创建 Bean 对象
- 通过 set 方法设置相关属性
- 调用 Bean 的后置处理器的前置方法
postProcessBeforeInitialization()(这个方法对容器中的所有 bean 都会生效,不是只针对于一个 bean) - Bean 对象初始化(会调用指定初始化方法:给bean标签一个init-method属性,值是自己写的方法)
- 调用 Bean 后置处理器的后置方法
postProcessAfterInitialization()(这个方法对容器中的所有 bean 都会生效,不是只针对于一个 bean) - Bean 对象创建完成,供我们使用
- Bean 对象销毁(会调用指定销毁方法:给bean标签一个destroy-method属性,值是自己写的方法)
7.2 代码示例
- 创建实体类(编写属性、构造器、get 方法、set 方法、初始化方法、销毁方法)
public class Dept {
private String dname;//部门名称
public Dept() {
System.out.println("1.无参构造器被调用了");
}
public void setDname(String dname) {
this.dname = dname;
System.out.println("2.set方法被调用了");
}
public void initMethod(){
System.out.println("3.调用了init-method");
}
public void destoryMethod(){
System.out.println("4.调用了destoryMethod方法");
}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
- 创建 Bean 后置处理器,实现BeanPostProcessor 接口,重写
postProcessBeforeInitialization和postProcessAfterInitialization方法
public class BeanPostProcess implements BeanPostProcessor {
@Override
public @Nullable Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean初始化时后置处理器的前置方法被调用");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean初始化时后置处理器的后置方法被调用");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
- 在 xml 文件中配置 Bean、后置处理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dept" class="dto.Dept" init-method="initMethod" destroy-method="destoryMethod">
<property name="dname" value="再保部"></property>
</bean>
<bean id="beanPostProcess" class="BeanPostProcess"></bean>
</beans>
- 调用方法
public class IOCTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
System.out.println("bean对象可以被使用了");
Dept dept = (Dept) context.getBean("dept");
System.out.println("调用容器的摧毁方法");
context.close();
}
}
三、核心概念 AOP
1.基本介绍
- AOP:面向切面编程
- AOP的作用:利用AOP可以对业务逻辑的各个部分进行隔离,从而降低耦合度,提高程序的可重用性
- 通俗描述:不修改原来的方法,给程序增加新的功能
2.底层原理
AOP 底层通过动态代理实现:即不直接调用目标方法,而是调用代理类,代理类在内部调用目标方法,然后返回。可以理解为明星的经济人。
2.1 JDK 动态代理
对于有接口的情况,使用 JDK 的动态代理实现 AOP
2.2 CGLIB 动态代理
对于没有接口的情况,使用 CGLIB 动态代理
3.相关概念
连接点:类里面可以被增强的方法(不一定去增强)切入点:实际被增强的方法通知(增强):实际增强的逻辑部分(实现增强功能的方法)- 前置通知:在被增强的方法之前执行
- 后置通知:在被增强的方法之后执行(仅当方法正常返回,未抛出异常)
- 环绕通知:在被增强的方法前后都执行
- 异常通知:捕获到目标方法的异常后才会执行
- 最终通知:在目标方法执行结束后运行(无论是否抛出异常,类似于
finally块)
切面:把通知应用到切入点的过程(可以简单理解为封装通知方法的类)
4.基于注解的 AOP
4.1 技术说明
AspectJ:AspectJ不是Spring组成部分,是一个独立的AOP框架,里面提供了特殊的注解,Spring 觉得好用就引用了他的注解,完成AOP操作
4.2 基本案例
通过 AOP,给登录功能加一个日志输出功能
- 引入依赖:Spring 的容器、 Spring 的 AOP 依赖、AspectJ 的依赖
<!-- spring容器 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.0.1</version>
</dependency>
</dependencies>
<!-- spring-AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>7.0.2</version>
</dependency>
<!-- spring-AspectJ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>7.0.2</version>
</dependency>
- 编辑 Spring 的配置文件(开启组件扫描,开启 AspectJ 功能)
<?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: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--组件扫描-->
<context:component-scan base-package="*"></context:component-scan>
<!--开启AspectJ的自动代理功能(这样才能识别AspecJ注解,生成代理对象)-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 写一个接口
public interface LoginService {
void login(String username, String password);
}
- 写一个接口的实现类,实现基本功能
@Service
public class LoginServiceImpl implements LoginService {
@Override
public void login(String username, String password) {
if (username.equals("admin") && password.equals("admin")) {
System.out.println("登录成功");
}
}
}
- 创建切面类(实现增强功能的类),确认切入点和通知类型,编写增强方法
@Aspect //表示这是一个切面类
@Component //将切面类加入容器
public class LoginAOP {
@Around(value = "execution( * com.altman.service.LoginService.login(..) )") //环绕通知,里面是切入点表达式
public void AfterMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("登录方法开始执行:"+System.currentTimeMillis());
joinPoint.proceed(); //环绕通知中执行目标方法的方法
System.out.println("登录方法执行完毕:"+System.currentTimeMillis());
}
}
6.启动容器测试
public class IOCTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
LoginService loginService = (LoginService) context.getBean("loginServiceImpl");
loginService.login("admin", "admin");
}
}
4.3 切入点表达式
4.3.1 语法结构
基本结构:execution(权限修饰符 返回值 目标类全路径.目标方法(方法参数类型))
写法案例:execution(public int com.altguigu.aop.Cal.add(int,int))
简化写法:execution(* com.altguigu.aop.Cal.add(..))
写法解析:
execution:必须写的固定值权限修饰符、返回值:可以写具体值(*表示任意)。如果权限修饰符和返回值都默认可以合并为一个*全类名:方法所在类的全路径,可以用*表示任意。也可以用*开始,表示任意开始,指定结尾;也可以用*结束,表示指定开始,任意结束。方法名:方法的名称,可以用*表示任意。也可以用*开始,表示任意开始,指定结尾;也可以用*结束,表示指定开始,任意结束。参数列表:参数类型,可以用..表示任意
4.3.2 使用案例
- 基本写法
@Before(value = "execution(public int com.altguigu.aop.Cal.add(int,int))")
- 简化写法
execution(* com.altguigu.aop.Cal.add(..))
- 类名任意写法
execution(* com.altguigu.aop.*.add(..))
- 方法名前面匹配后面任意写法
execution(* com.altguigu.aop.*.add*(..))
- 方法名前面任意后面匹配写法
execution(* com.altguigu.aop.*.*add(..))
4.4 5种通知
环绕通知需要显示的在通知中使用
proceedingJoinPoint.proceed();调用目标方法,其余方法不需要显示调用
- 前置通知:
@Before(value = "切入点表达式")
@Before(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )")
public void AfterMethod() throws Throwable {
System.out.println("登录方法开始执行:"+System.currentTimeMillis());
}
- 后置通知:
@AfterReturning(value = "切入点表达式")。后置通知可以获取到目标方法的返回值。returning属性是后置通知注解的特有属性,用来给目标方法的返回值起别名,别名是随意的,但是需要和通知的形参列表上的返回值参数名一致(如果不用也可以不写这个参数)
@AfterReturning(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )" ,
returning = "result") //returning属性如果不需要可以省略
public void AfterReturningMethod(boolean result) {//方法返回值的参数名需要和returning属性一致,不需要的话可以省略
if (result) {
System.out.println("登录成功:"+System.currentTimeMillis());
}else {
System.out.println("登录失败:"+System.currentTimeMillis());
}
}
- 环绕通知:
@Around(value = "切入点表达式"),必须要写ProceedingJoinPoint 形参,必须要在通知里面显示通过proceedingJoinPoint.proceed()方法调用目标方法,不然目标方法不会执行
@Around(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )")
public void AfterMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("登录方法开始执行:"+System.currentTimeMillis());
proceedingJoinPoint.proceed();//执行目标方法
System.out.println("登录方法执行完毕:"+System.currentTimeMillis());
}
- 异常通知:
@AfterThrowing(value = "切入点表达式")捕获到目标方法的异常才会执行。异常通知可以获取到目标方法异常信息。throwing属性是后置通知注解的特有属性,用来给目标抛出的异常起别名,别名是随意的,但是需要和通知的形参列表上的返回值参数名一致(如果不用也可以不写这个参数)
@AfterThrowing(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )",
throwing = "ex") //给异常起个名,不用的话可以不用写
public void AfterThrowingMethod(Throwable ex) throws Exception {
System.out.println(ex.getMessage());
System.out.println("报错啦");
}
- 最终通知:
@After(value = "切入点表达式")
@After(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )")
public void AfterMethod() throws Exception {
System.out.println("报错我也执行");
}
5. 基于 XML 的 AOP
了解一下即可,有需要的时候再学习
6.通知的两个特殊参数
在通知上,我们经常会看见两个参数:JoinPoint、ProceedingJoinPoint。这两个参数可以用来获取目标方法的各种信息。
JoinPoint是ProceedingJoinPoint的父类。ProceedingJoinPoint只能用在环绕通知中,它可以用与执行目标方法,绝对不能用在其他通知类型上面。JoinPoint可以用于所用的通知类型,但是不能执行目标方法
7.重用切入点表达式
如果好几个通知上使用的都是同一个切入点表达式,以上面的方法需要写好几遍相同的切入点表达式,很麻烦。重用切入点表达式允许我们将切入点表达式写在一个空方法上,然后其他通知的切入点表达式引用方法就可以了。
- 定义一个空方法,给空方法加上
@Pointcut注解,里面写上公共的切入点表达式 - 在通知方法的切入点表达式上直接写方法的全路径即可(同一个类里直接写方法名也可以)
@Aspect //表示这是一个切面类
@Component //将切面类加入容器
public class LoginAOP {
//1.定义重用切入点表达式
@Pointcut(value = "execution(* com.altman.service.LoginServiceImpl.login(..))")
public void LoginPointCut() {
}
//直接引用上面申明的方法即可(同一个类里面直接写方法名也行,不同类里面需要写全路径)
@AfterReturning(value = "com.altman.aop.LoginAOP.LoginPointCut()" ,returning = "result")
public void AfterReturningMethod(boolean result) {
if (result) {
System.out.println("登录成功:"+System.currentTimeMillis());
}else {
System.out.println("登录失败:"+System.currentTimeMillis());
}
}
//直接引用上面申明的方法即可(同一个类里面直接写方法名也行,不同类里面需要写全路径)
@After(value = "LoginPointCut()")
public void AfterMethod() {
System.out.println("登录完毕!!!");
}
}
四、jdbcTemplate
1.基本概念
JdbcTemplate 是 Spring框架对jdbc进行封装,使用JdbcTemplate方便实现对数据库的操作。在实际开发用的很少,目前仅作了解
2.基本使用
- 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.0.3</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>7.0.3</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.27</version>
<scope>compile</scope>
</dependency>
</dependencies>
- 创建数据库配置文件
jdbc.propoerties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.1:3306/springstudy?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true
jdbc.username=root
jdbc.password=123456
- 将数据源配置在 spring 配置文件中
<?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:component-scan base-package="fun.altman"></context:component-scan>
<!--引入外部jdbc配置文件-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--创建数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--创建jdbctemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--将数据源注入jdbctemplate-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
- 创建数据库,创建表
create database springstudy;
use springstudy;
create table student(
name varchar(50),
age int,
gender char(1)
);
- 使用单元测试工具测试
@SpringJUnitConfig(locations = "classpath:application.xml")
public class Test1 {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
public void test(){
String sql = "insert into student(name,age,gender) values(?,?,?)";
jdbcTemplate.update(sql,"jack",18,1);
}
}
3.CRUD 操作
3.1 添加操作
调用
upate()方法即可
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//添加数据的功能
public int add(User user){
String sql="insert into user value(?,?,?)";
return jdbcTemplate.update(sql,user.getUsername(),user.getPassword(),user.getSsex());
}
}
3.2 删除操作
调用
upate()方法即可
public int delete(String username) {
String sql="delete from `user` where `username`=?";
return jdbcTemplate.update(sql,username);
}
3.3 修改操作
调用
upate()方法即可
public int update(User user,String username) {
String sql="update `user` set `username`=?,`password`=?,`ssex`=? where username=?";
return jdbcTemplate.update(sql,user.getUsername(),user.getPassword(),user.getSsex(),username);
}
3.4 查询操作
- 查询单值:调用
queryForObject()方法。需要两个参数sql语句,数据返回类型的Class对象
public int queryToone() {
String sql="select count(*) from `user`";
return jdbcTemplate.queryForObject(sql,Integer.class);
}
- 查询单列对象:调用
queryForObject()方法。需要三个参数sql语句,RowMapper(是一个接口,可以对返回的不同类型数据进行封装),sql语句参数的值
public User queryToObject(String username) {
String sql="select * from `user` where username=?";
//泛型是返回的类型,()里面是返回类型的class对象
//一定要给有参和无参构造器
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username);
}
- 查询集合:调用query()方法。需要三个参数
sql语句,RowMapper(是一个接口,可以对返回的不同类型数据进行封装),sql语句参数的值
public List<User> queryAll() {
String sql="select * from `user`";
List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
return query;
}
3.5 批量添加
调用
batchUpdate方法有两个参数:
- 第一个参数:sql语句
- 第二个参数:List集合(集合里面存放的是一个Object类型的数组,用来存放操作数据)
//需要的参数是一个list集合
//集合里面方的是一个数组
//如果放入对象,他不知道取出来的顺序
@Override
public void batchAll(List <Object []> list) {
String sql="insert into `user` values(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, list);
System.out.println(ints);
}
3.6 批量删除、修改
调用
batchUpdate方法
//批量删除
public void batchDelete(List<Object[]> list) {
String sql="delete from `user` where username=?";
int[] ints = jdbcTemplate.batchUpdate(sql, list);
System.out.println(ints.toString());
}
五、核心概念声明式事务
代码中事务的分类
- 编程式事务管理(用代码的方式实现)
- 声明式事务管理(用注解的方式实现)
1.基于注解的声明式事务
1.1 基本使用
- 在 spring 的配置文件中引入名称空间 tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
- 创建事务管理器,并给事务管理器指定需要管理的数据源
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 在 spring 的配置文件中开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 给需要事物的方法加上
@Transactional注解
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
@Override
public void addStudent(Student student) {
String studentSQL= "insert into student (name, age, gender, class_id) values (?,?,?,?)";
String classesSQL= "update classes set student_num = student_num+1 where class_id = ?";
jdbcTemplate.update(studentSQL,student.getName(),student.getAge(),student.getGender(),student.getClassId());
int i = 1/0;
jdbcTemplate.update(classesSQL,student.getClassId());
}
}
1.2 @Transactional 注解
1.2.1 使用位置
- 类:事务对类中的所有方法生效
- 具体方法:事务只对具体方法生效
1.2.2 相关属性
readOnly:设置是否只读操作。改为 true 之后只能进行查询操作不能进行添加删除修改timeout:设置超时时常,单位秒。超时之后抛出异常回滚(默认-1 表示永不超时)rollbackFor/norollbackFor:设置回滚策略:设置哪些异常回滚/不回滚。如果不写这个属性,默认只有出现RuntimeException才会回滚
@Transactional(noRollbackFor = ArithmeticException.class)
isolation:设置隔离级别
// 使用数据库默认的隔离级别
@Transactional(isolation = Isolation.DEFAULT)
// 读未提交(最低隔离级别,可能脏读)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// 读已提交(避免脏读,但可能不可重复读)
@Transactional(isolation = Isolation.READ_COMMITTED)
// 可重复读(MySQL 默认级别,避免脏读和不可重复读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
// 串行化(最高隔离级别,完全避免并发问题,性能最低)
@Transactional(isolation = Isolation.SERIALIZABLE)
propagation:设置事务传播行为,默认合并事务。常用的就两个Propagation.REQUIRED/Propagation.REQUIRES_NEW
// 如果当前存在事务,则加入该事务;否则新建一个事务(默认值)
@Transactional(propagation = Propagation.REQUIRED)
// 创建一个新事务,如果当前存在事务,则挂起当前事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
// 如果当前存在事务,则加入该事务;否则以非事务方式执行
@Transactional(propagation = Propagation.SUPPORTS)
// 如果当前存在事务,则加入该事务;否则抛出异常
@Transactional(propagation = Propagation.MANDATORY)
// 以非事务方式执行,如果当前存在事务,则挂起当前事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
// 以非事务方式执行,如果当前存在事务,则抛出异常
@Transactional(propagation = Propagation.NEVER)
// 如果当前存在事务,则在嵌套事务内执行;否则新建一个事务(依赖数据库支持,如 MySQL 的 savepoint)
@Transactional(propagation = Propagation.NESTED)
1.3 完全注解实现声明式注解
@Configuration//用来说明替代配置文件
@ComponentScan(basePackages = "com.junqing")//用来开启组件扫描(参数是被扫描的类)
@EnableTransactionManagement//开启事务
public class ConfigSpring {
//创建数据库连接池
@Bean
public DruidDataSource getDruidSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");//数据库驱动
dataSource.setUrl("jdbc:mysql://localhost:3306/test");//数据库地址
dataSource.setUsername("root");//用户名
dataSource.setPassword("yfj6688642");//密码
return dataSource;
}
//创建jdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dateSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器对象
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
2.基于 XML 的声明式事务
基于 XML 的声明式注解就是通过使用 AOP 实现事务管理
- 配置事务管理器
- 配置通知
- 配置切入点和切面
<!--开启组件扫描-->
<context:component-scan base-package="com.junqing"></context:component-scan>
<!--创建datesorce对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--创建jdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置通知-->
<tx:advice id="tongzhi">
<!--配置事务参数-->
<tx:attributes>
<!--指定在些方法上添加事务(*代表所有)-->
<tx:method name="change" isolation="READ_COMMITTED" propagation="NOT_SUPPORTED"/>
<tx:method name="change*"/><!--表示所有以change开头的方法都添加事务-->
</tx:attributes>
</tx:advice>
<!--配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.junqing.service.*(..))"/>
<!--配置切面:把通知应用到切入点的过程-->
<aop:advisor advice-ref="tongzhi" pointcut-ref="pt"></aop:advisor>
</aop:config>
六、整合 Junit5 单元测试
- 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.0.3</version>
</dependency>
<!-- junit依赖-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring-test依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>7.0.3</version>
</dependency>
</dependencies>
- 搭建项目结构
- 创建测试类,在测试类上加上
@SpringJUnitConfig(locations = "classpath:配置文件路径") - 注入对象,进行测试
@SpringJUnitConfig(locations = "classpath:application.xml")
public class Test1 {
@Autowired
UserController userController;
@Test
public void test(){
userController.login("admin","123456");
}
}