Spring

一简介

Spring框架是一个开放源代码的应用程序框架, Spring提供了功能强大IOC(控制反转)、AOP(面向切向)及Web MVC等功能。,提供了控制层SpringMVC、数据层SpringData、服务层事务管理等众多技术,并可以整合众多第三方框架。

IOC控制反转

IOC :程序将创建对象的权利交给框架。之前在开发过程中,对象实例的创建是由调用者管理的,而IOC思想是将创建对象的权利交给框架,框架会帮助我们创建对象,分配对象的使用,控制权由程序代码转移到了框架中,控制权发生了反转,这就是Spring的IOC思想。

Spring体系结构

在这里插入图片描述

1.1 体系图介绍

Core Container:Spring核心模块,任何功能的使用都离不开该模块,是其他模块建立的基础。
**Data Access/Integration:**该模块提供了数据持久化的相应功能。
**Web:**该模块提供了web开发的相应功能。
**AOP:**提供了面向切面编程实现
**Aspects:**提供与AspectJ框架的集成,该框架是一个面向切面编程框架。
**Instrumentation:**提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
**Messaging:**为Spring框架集成一些基础的报文传送应用
**Test:**提供与测试框架的集成

三 容器类型

BeanFactory:BeanFactory是Spring容器中的顶层接口,它可以对Bean对象进行管理。
ApplicationContext:ApplicationContext是BeanFactory的子接口。它除了继承 BeanFactory的所有功能外,还添加了对国际化、资源访问、事件传播等方面的良好支持。

ApplicationContext有以下三个常用实现类:
容器实现类
ClassPathXmlApplicationContext:该类可以从项目中读取配置文件
FileSystemXmlApplicationContext:该类从磁盘中读取配置文件
AnnotationConfigApplicationContext:使用该类不读取配置文件,而是会读取注解

四 创建对象的策略

Spring通过配置中的属性设置对象的创建策略,共有五种创建策略:<bean scope
singleton:单例,默认策略。整个项目只会创建一个对象,通过中的属性可以设置单例对象的创建时机:《bean》lazy-init
lazy-init=“false”(默认):立即创建,在容器启动时会创建配置文件中的所有Bean对象。
lazy-init=“true”:延迟创建,第一次使用Bean对象时才会创建。

销毁对象的时机

singleton:对象随着容器的销毁而销毁(单例)。
prototype:使用JAVA垃圾回收机制销毁对象。(多例)
request:当处理请求结束,bean实例将被销毁。
session:当HTTP Session最终被废弃的时候,bean也会被销毁掉。
gloabal-session:集群环境下的session销毁,bean实例也将被销

spring对象生命周期方法·:

Bean对象的生命周期包含创建init-method——使用——销毁destroy-method,Spring可以配置Bean对象在创建和销毁时自动执行的方法:
定义生命周期方法

public class StudentDaoImpl2 implements StudentDao{
   // 创建时自动执行的方法
  public void init(){
    System.out.println("创建StudentDao!!!");
   }


  // 销毁时自动执行的方法
  public void destory(){
    System.out.println("销毁StudentDao!!!");
   }
}

配置生命周期方法

<!-- init-method:创建对象时执行的方法  destroy-method:销毁对象时执行的方法  -->
<bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2" scope="singleton"
   init-method="init" destroy-method="destory"></bean>


测试

@Test
public void t3(){
  // 创建Spring容器
  ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean1.xml");

  // 销毁Spring容器,ClassPathXmlApplicationContext才有销毁容器的方法
  ac.close();
}

获取Bean对象方式

通过id/name获取

配置文件
<bean name="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean>
<bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl2"></bean>

获取对象
StudentDao studentDao = (StudentDao) ac.getBean("studentDao");

通过类型获取
通过类型+id/name获取:虽然使用类型获取不需要强转,但如果在容器中有一个接口的多个实现类对象,则获取时会报错,此时需要使用类型+id/name获取

五 依赖注入

简单来说,控制反转是创建对象,依赖注入是为对象的属性赋值。
spring可以通过setter或者构造方法给属性赋值

setter注入

被注入类编写属性的setter方法

public class StudentService {
  private StudentDao studentDao;
  public void setStudentDao(StudentDao studentDao) {
    this.studentDao = studentDao;
   }
}

配置文件中,给需要注入属性值的<bean中设置<property

<bean id="studentDao" class="com.itbaizhan.dao.StudentDaoImpl"></bean>
<bean id="studentService" class="com.itbaizhan.service.StudentService">
  <!--依赖注入-->
  <!--name:对象的属性名 ref:容器中对象的id值-->
  <property name="studentDao" ref="studentDao"></property>
</bean>

测试是否注入成功

 @Test
public void t2(){
  ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
  StudentService studentService = (StudentService) ac.getBean("studentService");
  System.out.println(studentService.findStudentById(1));
}

构造器注入

自动注入

六 注解实现IOC

@Component

@Component 代替bean,@Component注解中的value属性值表示id
作用:用于创建对象,放入Spring容器,相当于
位置:类上方
要在配置文件中配置扫描的包,扫描到该注解才能生效。

<context:component-scan base-package="com.itbaizhan"></context:component-scan>

@Component注解配置bean的默认id是首字母小写的类名。也可以手动设置bean的id值。

// 此时bean的id为studentDaoImpl
@Component
public class StudentDaoImpl implements StudentDao{
  public Student findById(int id) {
    // 模拟根据id查询学生
    return new Student(1,"百战程序员","北京");
   }
}


// 此时bean的id为studentDao
@Component("studentDao")
public class StudentDaoImpl implements StudentDao{
  public Student findById(int id) {
    // 模拟根据id查询学生
    return new Student(1,"百战程序员","北京");
   }
}

注解实现IOC_@Repository、@Service、@Controller

作用:这三个注解和@Component的作用一样,使用它们是为了区分该类属于什么层。
位置:
@Repository用于Dao层
@Service用于Service层
@Controller用于Controller层

@Scope(“singleton”)

作用:指定bean的创建策略
位置:类上方
取值:singleton单例 prototype多例 request session globalsession

@Autowired用于完成依赖自动注入

作用:从容器中查找符合属性类型的对象自动注入属性中。用于代替bean>中的依赖注入配置。
位置:属性上方、setter方法上方、构造方法上方。

@Qualifier

作用:在按照类型注入对象的基础上,再按照bean的id注入。
位置:属性上方
注意:@Qualifier必须和@Autowired一起使用。

@Value

作用:注入String类型和基本数据类型的属性值。
位置:属性上方
用法:

  1. 直接设置固定的属性值
@Service
public class StudentService {
  @Value("1")
  private int count;
  @Value("hello")
  private String str;
}
  1. 获取配置文件中的属性值:
    编写配置文件db.properties
jdbc.username=root
jdbc.password=123456

2.1 spring核心配置文件扫描配置文件

<context:property-placeholder location="db.properties"></context:property-placeholder>

2.2 注入配置文件中的属性值

@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;

@Configuration

纯注解实现IOC需要一个Java类代替xml文件。这个Java类上方需要添加@Configuration,表示该类是一个配置类,作用是代替配置文件。
作用:指定spring在初始化容器时扫描的包。
@ComponentScan(“com.itbaizhan”)配置文件

@Configuration
@ComponentScan("com.itbaizhan")
public class SpringConfig {
}

@PropertySource

作用:代替配置文件中的context:property-placeholder扫描配置文件
位置:配置类上方
注意:配置文件位置前要加关键字classpath

@Configuration
@PropertySource("classpath:db.properties")
public class JdbcConfig {
  @Value("${jdbc.username}")
  private String username;


  @Value("${jdbc.password}")
  private String password;


}

@Bean

作用:将方法的返回值对象放入Spring容器中。如果想将第三方类的对象放入容器,可以使用@Bean
位置:配置类的方法上方。
属性:name:给bean对象设置id
注意:@Bean修饰的方法如果有参数,spring会根据参数类型从容器中查找可用对象。
举例:如果想将jdbc连接对象放入Spring容器,我们无法修改Connection源码添加@Component,此时就需要使用将@Bean该对象放入Spring容器
添加驱动依赖

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.27</version>
</dependency>

将Connection对象放入Spring容器

@Bean(name = "connection")
public Connection getConnection(){
  try {
    Class.forName("com.mysql.cj.jdbc.Driver");
    return DriverManager.getConnection("jdbc:mysql:///mysql", "root", "root");
   } catch (Exception exception) {
    return null;
   }
}

@Import

作用:如果配置过多,会有多个配置类,该注解可以为主配置类导入其他配置类

位置:主配置类上方

七 spring整合Mybatis

7.1 搭建环境

我们知道使用MyBatis时需要写大量创建SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession等对象的代码,而Spring的作用是帮助我们创建和管理对象,所以我们可以使用Spring整合MyBatis,简化MyBatis开发。
创建maven项目,引入依赖。

  <dependencies>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!--驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <!--spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.13</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.13</version>
        </dependency>
        <!--Spring和Mybatis的整合包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
    </dependencies>

7.2 编写配置文件

编写数据库配置文件db.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///student
jdbc.username=root
jdbc.password=root

创建MyBatis配置文件SqlMapConfig.xml,数据源、扫描接口都交由Spring管理,不需要在MyBatis配置文件中设置。(基本上都一样)

<?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>
</configuration>

创建Spring配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd">
  <!-- 包扫描 -->
  <context:component-scan base-package="com.itbaizhan"></context:component-scan>


  <!-- 读取配置文件 -->
  <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
  <!-- 创建druid数据源对象 -->
  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
  </bean>


  <!-- Spring创建封装过的SqlSessionFactory -->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
  </bean>


  <!-- Spring创建封装过的SqlSession -->
  <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
  </bean>


</beans>

7.3 准备数据库和实体类

准备数据库

CREATE DATABASE `student`;
USE `student`;
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) DEFAULT NULL,
 `sex` varchar(10) DEFAULT NULL,
 `address` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


insert into `student`(`id`,`name`,`sex`,`address`) values (1,'程序员','男','北京'),(2,'上海程序员','女','北京');

准备实体类 domain
编写持久层接口 dao

@Repository
public interface StudentDao {
  // 查询所有学生
  @Select("select * from student")
  List<Student> findAll();


  // 添加学生
  @Insert("insert into student values(null,#{name},#{sex},#{address})")
  void add(Student student);
}

编写service类

@Service
public class StudentService {
  // SqlSession对象
  @Autowired
  private SqlSessionTemplate sqlSession;


  // 使用SqlSession获取代理对象
  public List<Student> findAllStudent(){
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    return studentDao.findAll();
   }
}

7.4 编写持久层和service类

不写

注:使用SqlSessionTemplate创建代理对象还是需要注册接口或者映射文件的。
在MyBatis配置文件注册接口
<configuration>
  <mappers>
    <mapper class="com.itbaizhan.dao.StudentDao"></mapper>
  </mappers>
</configuration>
创建sqlSessionFactory时指定MyBatis配置文
<!-- 创建Spring封装过的SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"></property>
  <property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
</bean>

引入Junit和Spring整合Junit依赖

<!-- junit,如果Spring5整合junit,则junit版本至少在4.12以上 -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
<!-- spring整合测试模块 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.3.13</version>
</dependency>
    <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.12</version>
        </dependency>


编写测试类

// JUnit使用Spring方式运行代码,即自动创建spring容器。
@RunWith(SpringJUnit4ClassRunner.class)
// 告知创建spring容器时读取哪个配置类或配置文件
// 配置类写法:@ContextConfiguration(classes=配置类.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class StudentServiceTest {
  @Autowired
  private StudentService studentService;
  
  @Test
  public void testFindAll(){
    List<Student> allStudent = studentService.findAllStudent();
    allStudent.forEach(System.out::println);
   }
}

7.5 Spring整合Mybatis

7.6 自动创建代理对象

Spring提供了MapperScannerConfigurer对象,该对象可以自动扫描包创建代理对象,并将代理对象放入容器中,此时不需要使用SqlSession手动创建代理对象。
创建MapperScannerConfigurer对象

<!-- 该对象可以自动扫描持久层接口,并为接口创建代理对象 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <!-- 配置扫描的接口包 -->
  <property name="basePackage" value="com.itbaizhan.dao"></property>
</bean>

Service类直接使用代理对象即可

@Service
public class StudentService {
  // 直接注入代理对象
  @Autowired
  private StudentDao studentDao;
 
  // 直接使用代理对象
  public void addStudent(Student student){
    studentDao.add(student);
   }
}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class StudentServiceTest {
  @Autowired
  private StudentService studentService;
  
  @Test
  public void testAdd(){
    Student student = new Student(0, "百战2", "男", "上海");
    studentService.addStudent(student);
   }
}

二 AOP

AOP的全称是Aspect Oriented Programming,即面向切面编程。是实现功能统一维护的一种技术,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
作用:在不修改源码的基础上,对已有方法进行增强。
实现原理:动态代理技术。
优势:减少重复代码、提高开发效率、维护方便
应用场景:事务处理、日志管理、权限控制、异常处理等方面。

AOP术语

在这里插入图片描述

AOP入门

AspectJ是一个基于Java语言的AOP框架,在Spring框架中建议使用AspectJ实现AOP。

接下来我们写一个AOP入门案例:dao层的每个方法结束后都可以打印一条日志:

引入依赖

<!-- spring -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.13</version>
</dependency>
<!-- AspectJ -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.7</version>
</dependency>
<!--  junit  -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>

编写连接点

@Repository
public class UserDao {
  public void add(){
    System.out.println("用户新增");
   }
  public void delete(){
    System.out.println("用户删除");
   }
  public void update(){
    System.out.println("用户修改");
   }
}

编写通知类

public class MyAspectJAdvice {
  // 后置通知
  public void myAfterReturning() {
    System.out.println("打印日志...");
   }
}

配置切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd">


  <context:component-scan base-package="com.itbaizhan"></context:component-scan>


  <!-- 通知对象 -->
  <bean id="myAspectJAdvice" class="com.itbaizhan.advice.MyAspectAdvice"></bean>


  <!-- 配置AOP -->
  <aop:config>
    <!-- 配置切面 -->
    <aop:aspect ref="myAspectJAdvice">
      <!-- 配置切点 -->
      <aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/>
      <!-- 配置通知 -->
      <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
    </aop:aspect>
  </aop:config>
</beans>

测试

public class UserDaoTest {
  @Test
  public void testAdd(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    UserDao userDao = (UserDao) ac.getBean("userDao");
    userDao.add();
   }
  @Test
  public void testDelete(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    UserDao userDao = (UserDao) ac.getBean("userDao");
    userDao.delete();
   }
  @Test
  public void testUpdate(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    UserDao userDao = (UserDao) ac.getBean("userDao");
    userDao.update();
   }
}

AOP_通知类型

在这里插入图片描述
编写通知方法

// 通知类
public class MyAspectAdvice {
  // 后置通知
  public void myAfterReturning(JoinPoint joinPoint) {
    System.out.println("切点方法名:" + joinPoint.getSignature().getName());
    System.out.println("目标对象:" + joinPoint.getTarget());
    System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
   }


  // 前置通知
  public void myBefore() {
    System.out.println("前置通知...");
   }


  // 异常通知
  public void myAfterThrowing(Exception ex) {
    System.out.println("异常通知...");
    System.err.println(ex.getMessage());
   }


  // 最终通知
  public void myAfter() {
    System.out.println("最终通知");
   }


  // 环绕通知
  public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕前");
    Object obj = proceedingJoinPoint.proceed(); // 执行方法
    System.out.println("环绕后");
    return obj;
   }
}

配置切面

<!-- 配置AOP -->
<aop:config>
  <!-- 配置切面 -->
  <aop:aspect ref="myAspectJAdvice">
    <!-- 配置切点 -->
    <aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/>
    <!-- 前置通知 -->
    <aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>
    <!-- 后置通知 -->
    <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
    <!-- 异常通知 -->
    <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="ex"/>
    <!-- 最终通知 -->
    <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
    <!-- 环绕通知 -->
    <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
  </aop:aspect>
</aop:config>

AOP_切点表达式

使用AspectJ需要使用切点表达式配置切点位置,写法如下:
标准写法:访问修饰符 返回值 包名.类名.方法名(参数列表)
访问修饰符可以省略。
返回值使用代表任意类型。
包名使用
表示任意包,多级包结构要写多个*,使用*…表示任意包结构
类名和方法名都可以用实现通配。
参数列表
基本数据类型直接写类型
引用类型写包名.类名
表示匹配一个任意类型参数
…表示匹配任意类型任意个数的参数
全通配:
.
(…)

AOP_多切面配置

我们可以为切点配置多个通知,形成多切面,比如希望dao层的每个方法结束后都可以打印日志并发送邮件:

编写发送邮件的通知:
在这里插入图片描述
编写发送邮件的通知:

public class MyAspectJAdvice2 {
  // 后置通知
  public void myAfterReturning(JoinPoint joinPoint) {
    System.out.println("发送邮件");
   }
}

配置切面:

@Aspect
@Component
public class MyAspectAdvice {
  // 后置通知
  @AfterReturning("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public void myAfterReturning(JoinPoint joinPoint) {
    System.out.println("切点方法名:" + joinPoint.getSignature().getName());
    System.out.println("目标对象:" + joinPoint.getTarget());
    System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
   }


  // 前置通知
  @Before("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public void myBefore() {
    System.out.println("前置通知...");
   }


  // 异常通知
  @AfterThrowing(value = "execution(* com.itbaizhan.dao.UserDao.*(..))",throwing = "ex")
  public void myAfterThrowing(Exception ex) {
    System.out.println("异常通知...");
    System.err.println(ex.getMessage());
   }


  // 最终通知
  @After("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public void myAfter() {
    System.out.println("最终通知");
   }


  // 环绕通知
  @Around("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕前");
    Object obj = proceedingJoinPoint.proceed(); // 执行方法
    System.out.println("环绕后");
    return obj;
   }
}

注解配置AOP

Spring可以使用注解代替配置文件配置切面:

  1. 在xml中开启AOP注解支持
<!-- 开启注解配置Aop -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  1. 在通知类上方加入注解@Aspect

  2. 在通知方法上方加入注解@Before/@AfterReturning/@AfterThrowing/@After/@Around

@Aspect
@Component
public class MyAspectAdvice {
  // 后置通知
  @AfterReturning("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public void myAfterReturning(JoinPoint joinPoint) {
    System.out.println("切点方法名:" + joinPoint.getSignature().getName());
    System.out.println("目标对象:" + joinPoint.getTarget());
    System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
   }


  // 前置通知
  @Before("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public void myBefore() {
    System.out.println("前置通知...");
   }


  // 异常通知
  @AfterThrowing(value = "execution(* com.itbaizhan.dao.UserDao.*(..))",throwing = "ex")
  public void myAfterThrowing(Exception ex) {
    System.out.println("异常通知...");
    System.err.println(ex.getMessage());
   }


  // 最终通知
  @After("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public void myAfter() {
    System.out.println("最终通知");
   }


  // 环绕通知
  @Around("execution(* com.itbaizhan.dao.UserDao.*(..))")
  public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕前");
    Object obj = proceedingJoinPoint.proceed(); // 执行方法
    System.out.println("环绕后");
    return obj;
   }
}

  1. 测试:
@Test
public void testAdd2(){
  ApplicationContext ac = new ClassPathXmlApplicationContext("bean1.xml");
  UserDao userDao = (UserDao) ac.getBean("userDao");
  userDao.update();
}

滴滴:
如何为一个类下的所有方法统一配置切点:
在通知类中添加方法配置切点

@Pointcut("execution(* com.itbaizhan.dao.UserDao.*(..))")
public void pointCut(){}

在通知方法上使用定义好的切点

@Before("pointCut()")
public void myBefore(JoinPoint joinPoint) {
  System.out.println("前置通知...");
}


@AfterReturning("pointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
  System.out.println("后置通知...");
}

配置类如何代替xml中AOP注解支持?
在配置类上方添加@EnableAspectJAutoProxy即可

@Configuration
@ComponentScan("com.itbaizhan")
@EnableAspectJAutoProxy
public class SpringConfig {


}

原生Spring实现AOP

除了AspectJ,Spring支持原生方式实现AOP(只有四种通知方式)。
在这里插入图片描述引入依赖

<!-- AOP -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.3.13</version>
</dependency>

编写通知类

// Spring原生Aop的通知类
public class SpringAop implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor {
  /**
   * 前置通知
   * @param method 目标方法
   * @param args 目标方法的参数列表
   * @param target 目标对象
   * @throws Throwable
   */
  @Override
  public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("前置通知");
   }


  /**
   * 后置通知
   * @param returnValue 目标方法的返回值
   * @param method 目标方法
   * @param args 目标方法的参数列表
   * @param target 目标对象
   * @throws Throwable
   */
  @Override
  public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
    System.out.println("后置通知");
   }


  /**
   * 环绕通知
   * @param invocation 目标方法
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    System.out.println("环绕前");
    Object proceed = invocation.proceed();
    System.out.println("环绕后");
    return proceed;
   }


  /**
   * 异常通知
   * @param ex 异常对象
   */
  public void afterThrowing(Exception ex){
    System.out.println("发生异常了!");
   }
}

编写配置类

<!-- 通知对象 -->
<bean id="springAop" class="com.itbaizhan.advice.SpringAop"></bean>


<!-- 配置代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <!-- 配置目标对象 -->
  <property name="target" ref="userDao"></property>
  <!-- 配置通知 -->
  <property name="interceptorNames">
    <list>
      <value>springAop</value>
    </list>
  </property>
  <!-- 代理对象的生成方式  true:使用CGLib false:使用原生JDK生成-->
  <property name="proxyTargetClass" value="true"></property>
</bean>

测试类

public class UserDaoTest2 {
  @Test
  public void testAdd(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
    UserDao userDao = (UserDao) ac.getBean("userDaoProxy"); // 获取的是代理对象
    userDao.update();
   }
}

SchemaBased实现AOP

SchemaBased(基础模式)配置方式是指使用Spring原生方式定义通知,而使用AspectJ框架配置切面。

编写通知类

public class SpringAop implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor {
  /**
   * 前置通知
   * @param method 目标方法
   * @param args 目标方法的参数列表
   * @param target 目标对象
   * @throws Throwable
   */
  @Override
  public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("前置通知");
   }


  /**
   * 后置通知
   * @param returnValue 目标方法的返回值
   * @param method 目标方法
   * @param args 目标方法的参数列表
   * @param target 目标对象
   * @throws Throwable
   */
  @Override
  public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
    System.out.println("后置通知");
   }


  /**
   * 环绕通知
   * @param invocation 目标方法
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    System.out.println("环绕前");
    Object proceed = invocation.proceed();
    System.out.println("环绕后");
    return proceed;
   }


  /**
   * 异常通知
   * @param ex 异常对象
   */
  public void afterThrowing(Exception ex){
    System.out.println("发生异常了!");
   }
}

配置切面

<!-- 通知对象 -->
<bean id="springAop2" class="com.itbaizhan.aop.SpringAop2"/>


<!-- 配置切面 -->
<aop:config>
  <!-- 配置切点-->
  <aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/>
  <!-- 配置切面:advice-ref:通知对象 pointcut-ref:切点 -->
  <aop:advisor advice-ref="springAop2" pointcut-ref="myPointcut"/>
</aop:config>

测试

@Test
public void t6(){
  ApplicationContext ac = new ClassPathXmlApplicationContext("aop3.xml");
  UserDao userDao = (UserDao) ac.getBean("userDao");
  userDao.add();
}

Spring事务

事务简介

事务:不可分割的原子操作。即一系列的操作要么同时成功,要么同时失败。
开发过程中,事务管理一般在service层,service层中可能会操作多次数据库,这些操作是不可分割的。否则当程序报错时,可能会造成数据异常。
如:张三给李四转账时,需要两次操作数据库:张三存款减少、李四存款增加。如果这两次数据库操作间出现异常,则会造成数据错误。

准备数据库

CREATE DATABASE `spring` ;
USE `spring`;
DROP TABLE IF EXISTS `account`;


CREATE TABLE `account` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `username` varchar(255) DEFAULT NULL,
 `balance` double DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


insert into `account`(`id`,`username`,`balance`) values (1,'张三',1000),(2,'李四',1000);

创建maven项目,引入依赖

<dependencies>
  <!--  mybatis  -->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
  </dependency>
  <!--  mysql驱动包  -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
  </dependency>
  <!--  druid连接池  -->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
  </dependency>
  <!-- spring -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.13</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.13</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.13</version>
  </dependency>
  <!-- MyBatis与Spring的整合包,该包可以让Spring创建MyBatis的对象 -->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
  </dependency>


  <!-- junit,如果Spring5整合junit,则junit版本至少在4.12以上 -->
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>
  <!-- spring整合测试模块 -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.13</version>
    <scope>test</scope>
  </dependency>
</dependencies>

创建配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd">
  <!-- 包扫描 -->
  <context:component-scan base-package="com.itbaizhan"></context:component-scan>
  <!-- 创建druid数据源对象 -->
  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql:///spring"></property>
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
  </bean>


  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
  </bean>


  <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.itbaizhan.dao"></property>
  </bean>
</beans>

编写代码

// 账户
public class Account {
  private int id; // 账号
  private String username; // 用户名
  private double balance; // 余额
  
  // 省略getter/setter/tostring/构造方法
}


@Repository
public interface AccountDao {
  // 根据id查找用户
  @Select("select * from account where id = #{id}")
  Account findById(int id);


  // 修改用户
  @Update("update account set balance = #{balance} where id = #{id}")
  void update(Account account);
}


@Service
public class AccountService {
  @Autowired
  private AccountDao accountDao;


  /**
   * 转账
   * @param id1 转出人id
   * @param id2 转入人id
   * @param price 金额
   */
  public void transfer(int id1, int id2, double price) {
    // 转出人减少余额
    Account account1 = accountDao.findById(id1);
    account1.setBalance(account1.getBalance()-price);
    accountDao.update(account1);


    int i = 1/0; // 模拟程序出错


    // 转入人增加余额
    Account account2 = accountDao.findById(id2);
    account2.setBalance(account2.getBalance()+price);
    accountDao.update(account2);
   }
}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class AccountServiceTest {
  @Autowired
  private AccountService accountService;


  @Test
  public void testTransfer(){
    accountService.transfer(1,2,500);
   }
}

Spring事务管理方案

声明式事务底层采用AOP
在service层手动添加事务可以解决该问题

@Autowired
private SqlSessionTemplate sqlSession;


public void transfer(int id1, int id2, double price) {
  try{
    // account1修改余额
    Account account1 = accountDao.findById(id1);
    account1.setBalance(account1.getBalance()-price);
    accountDao.update(account1);


    int i = 1/0; // 模拟转账出错


    // account2修改余额
    Account account2 = accountDao.findById(id2);
    account2.setBalance(account2.getBalance()+price);
    accountDao.update(account2); 
    sqlSession.commit();
   }catch(Exception ex){
    sqlSession.rollback();
   }
}

但在Spring管理下不允许手动提交和回滚事务。此时我们需要使用Spring的事务管理方案,在Spring框架中提供了两种事务管理方案:
编程式事务:通过编写代码实现事务管理。
声明式事务:基于AOP技术实现事务管理。
在Spring框架中,编程式事务管理很少使用,我们对声明式事务管理进行详细学习。
Spring的声明式事务管理在底层采用了AOP技术,其最大的优点在于无需通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。
使用AOP技术为service方法添加如下通知:
在这里插入图片描述

Spring事务管理器

Spring依赖事务管理器进行事务管理,事务管理器即一个通知类,我们为该通知类设置切点为service层方法即可完成事务自动管理。由于不同技术操作数据库,进行事务操作的方法不同。如:JDBC提交事务是connection.commit(),MyBatis提交事务是sqlSession.commit(),所以Spring提供了多个事务管理器。
在这里插入图片描述
我们使用MyBatis操作数据库,接下来使用DataSourceTransactionManager进行事务管理。
引入依赖

<!-- 事务管理 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.3.13</version>
</dependency>
<!-- AspectJ -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.7</version>
</dependency>

在配置文件中引入约束

xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"


http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
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>


<!-- 进行事务相关配置 -->
<tx:advice id = "txAdvice">
  <tx:attributes>
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice>


<!-- 配置切面 -->
<aop:config>
  <!-- 配置切点 -->
  <aop:pointcut id="pointcut" expression="execution(* com.itbaizhan.service.*.*(..))"/>
  <!-- 配置通知 -->
  <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>

事务控制的API

Spring进行事务控制的功能是由三个接口提供的,这三个接口是Spring实现的,在开发中我们很少使用到,只需要了解他们的作用即可:

PlatformTransactionManager接口

PlatformTransactionManager是Spring提供的事务管理器接口,所有事务管理器都实现了该接口。该接口中提供了三个事务操作方法:

TransactionStatus getTransaction(TransactionDefinition definition):获取事务状态信息。
void commit(TransactionStatus status):事务提交
void rollback(TransactionStatus status):事务回滚

TransactionDefinition接口

TransactionDefinition是事务的定义信息对象,它有如下方法:

String getName():获取事务对象名称。
int getIsolationLevel():获取事务的隔离级别。
int getPropagationBehavior():获取事务的传播行为。
int getTimeout():获取事务的超时时间。
boolean isReadOnly():获取事务是否只读。

TransactionStatus接口

TransactionStatus是事务的状态接口,它描述了某一时间点上事务的状态信息。它有如下方法:

void flush() 刷新事务
boolean hasSavepoint() 获取是否存在保存点
boolean isCompleted() 获取事务是否完成
boolean isNewTransaction() 获取是否是新事务
boolean isRollbackOnly() 获取是否回滚
void setRollbackOnly() 设置事务回滚

事务的相关配置

在tx:advice中可以进行事务的相关配置:

<tx:advice id="txAdvice">
  <tx:attributes>
    <tx:method name="*"/>
    <tx:method name="find*" read-only="true"/>
  </tx:attributes>
</tx:advice>

tx:method>中的属性:

name:指定配置的方法。表示所有方法,find表示所有以find开头的方法。
read-only:是否是只读事务,只读事务不存在数据的修改,数据库将会为只读事务提供一些优化手段,会对性能有一定提升,建议在查询中开启只读事务。
timeout:指定超时时间,在限定的时间内不能完成所有操作就会抛异常。默认永不超时
rollback-for:指定某个异常事务回滚,其他异常不回滚。默认所有异常回滚。
no-rollback-for:指定某个异常不回滚,其他异常回滚。默认所有异常回滚。
propagation:事务的传播行为
isolation:事务的隔离级别

事务的传播行为

事务传播行为是指多个含有事务的方法相互调用时,事务如何在这些方法间传播。
如果在service层的方法中调用了其他的service方法,假设每次执行service方法都要开启事务,此时就无法保证外层方法和内层方法处于同一个事务当中。

// method1的所有方法在同一个事务中
public void method1(){
  // 此时会开启一个新事务,这就无法保证method1()中所有的代码是在同一个事务中
  method2();
  System.out.println("method1");
}


public void method2(){
  System.out.println("method2");
}

事务的传播特性就是解决这个问题的,Spring帮助我们将外层方法和内层方法放入同一事务中。
在这里插入图片描述

事务的隔离级别

事务隔离级别反映事务提交并发访问时的处理态度,隔离级别越高,数据出问题的可能性越低,但效率也会越低。
在这里插入图片描述
如果设置为DEFAULT会使用数据库的隔离级别。

SqlServer , Oracle默认的事务隔离级别是READ_COMMITED
Mysql的默认隔离级别是REPEATABLE_READ

注解配置声明式事务

注册事务注解驱动

<!-- 注册事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在需要事务支持的方法或类上加@Transactional

@Service
// 作用于类上时,该类的所有public方法将都具有该类型的事务属性
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)
public class AccountService {
  @Autowired
  private AccountDao accountDao;


  /**
   * 转账
   * @param id1 转出人id
   * @param id2 转入人id
   * @param price 金额
   */
  // 作用于方法上时,该方法将都具有该类型的事务属性
  @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)
  public void transfer(int id1, int id2, double price) {
    // account1修改余额
    Account account1 = accountDao.findById(id1);
    account1.setBalance(account1.getBalance()-price);
    accountDao.update(account1);


    int i = 1/0; // 模拟转账出错


    // account2修改余额
    Account account2 = accountDao.findById(id2);
    account2.setBalance(account2.getBalance()+price);
    accountDao.update(account2);
   }
}

}
配置类代替xml中的注解事务支持:在配置类上方写@EnableTranscationManagement

@Configuration
@ComponentScan("com.itbaizhan")
@EnableTransactionManagement
public class SpringConfig {


  @Bean
  public DataSource getDataSource(){
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
    druidDataSource.setUrl("jdbc:mysql:///spring");
    druidDataSource.setUsername("root");
    druidDataSource.setPassword("root");
    return druidDataSource;
   }


  @Bean
  public SqlSessionFactoryBean getSqlSession(DataSource dataSource){
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    return sqlSessionFactoryBean;
   }


  @Bean
  public MapperScannerConfigurer getMapperScanner(){
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setBasePackage("com.itbaizhan.dao");
    return mapperScannerConfigurer;
   }


  @Bean
  public DataSourceTransactionManager getTransactionManager(DataSource dataSource){
    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
    dataSourceTransactionManager.setDataSource(dataSource);
    return dataSourceTransactionManager;
   }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值