Spring

Spring

Spring概述

  • Spring是分层的JavaSE/EE应用full-stack轻量级开源框架,以IoC和AOP为内核,提供了展现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术。
  • Spring的优点

    • 方便解耦,简化开发
    • AOP编程支持
    • 声明式事务支持
    • 方便程序的测试
    • 方便集成各种优秀的框架
    • 降低JavaEE API的使用难度
  • Spring体系结构

    • 核心容器:Beans、Core、Context、SpEL
  • Spring Bean:被Spring IoC容器初始化、装配、管理的Java对象

程序耦合及解耦

  • 耦合(Coupling):模块及关联程度的度量。程序之间的依赖性。
  • 内聚:同一个模块内的各个元素之间彼此结合的紧密程度
  • (1)内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
  • (2)公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
  • (3) 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
  • (4) 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合
  • (5)标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
  • (6) 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
  • (7) 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
  • 解耦的思路
    • 使用反射来创建对象,避免使用new关键字
    • 通过读取配置文件,获取要创建对象的全限定类名
    • 工厂模式解耦:

IoC

  • Inverse Of Control:控制反转,把创建对象的权利交给框架。它包括依赖注入和依赖查找。
  • IoC:可以消减计算机程序的耦合

Spring快速入门

  • 第一步:配置依赖坐标
  • 第二步:在类的跟路径创建xml配置文件

    1
    2
    3
    4
    5
    <?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
    https://www.springframework.org/schema/beans/spring-beans.xsd">
  • 第三步:让spring管理资源

    • bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中

      • id 属性:对象的唯一标识。
      • class 属性:指定要创建对象的全限定类名
      1
      2
      <bean id="accountService" class="com.nogizaka.service.AccountServiceImpl"></bean>
      <bean id="accountDao" class="com.nogizaka.dao.AccountDaoImpl"></bean>
  • 第四步:从容器中获取对象

    • 获取Spring的IoC核心容器,并根据id获取对象

      1
      2
      3
      4
      5
      // 1. 获取核心容器
      ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
      // 2. 获取Bean对象
      AccountService accountService = ac.getBean("accountService", AccountService.class);
      AccountDao accountDao = (AccountDao) ac.getBean("accountDao");

ApplicationContext接口的实现类

  • ApplicationContext三个常用实现类

    • ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的化,加载不了

    • FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件(必须有访问权限)

    • AnnotationConfigApplicationContext;用于读取注解,创建容器。

BeanFactory 和 ApplicationContext 的区别

  • BeanFactory:是Spring容器中的顶层接口。

    • 它在构建核心容器时,创建对象采取的策略是延迟加载的方式,什么时候用什么时候创建
    • 用多例对象
  • ApplicationContext:是BeanFactory的子接口。

    • 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。即只要一读取配置文件,默认情况下就会创建对象。
    • 适用单例对象

Spring对Bean的管理

  • bean标签

    • 作用:用于配置对象让spring来创建
    • 属性:

      • id:给对象在容器中提供一个唯一表示符,用于获取对象

      • class:指定类的全限定类名,用于反射创建对象,默认情况下调用无参构造函数

      • scope:指定对象的作用范围

        • singleton:默认值,单例的
        • prototype:多例的
        • request:Web项目中,Spring创建一个Bean的对象,将对象存入到request域中
        • session:Web项目中,Spring创建一个Bean的对象,将对象存入到session域中
        • global session:WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session.
      • init-method:指定类中的初始化方法名称

      • destroy-method:指定类中销毁方法名称

创建bean的三种方式

  • 第一种:使用默认构造函数创建,在spring的配置文件中使用bean标签,配以id和class属性之后,且没用其他属性和标签时。采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。

  • 第二种:使用普通工厂中的方法创建对象(实例工厂)

    • 先把工厂的创建交给 spring 来管理。
    • 然后在使用工厂的 bean 来调用里面的方法
      • factory-bean 属性:用于指定实例工厂 bean 的 id。
      • factory-method 属性:用于指定实例工厂中创建对象的方法。
1
2
<bean id="工厂类" class="工厂类全限定类名"></bean>  
<bean id="要被工厂创建的对象" factory-bean="工厂类" factory-method="工厂类的生产方法"></bean>
  • 第三种:使用工厂中的静态方法创建对象(静态工厂)
1
<bean id="要被工厂创建的对象" class="工厂类全限定类名" factory-method="生产对象的静态方法"></bean>

bean对象的作用范围

  • bean标签的scope属性:用于指定bean的作用范围
    • singleton:单例的,默认值
    • prototype:多例的
    • request:作用于web应用的请求范围
    • session:作用于web应用的会话范围
    • global-session:作用于集群环境的会话范围(全局会话范围)

bean对象的生命周期

  • 单例对象

    • 出生:当容器创建时对象出生(加载完配置文件立即创建)
    • 活着:只要容器在,对象存活
    • 死亡:容器销毁,对象消亡
    • 单例对象的生命周期和容器相同
  • 多例对象

    • 出生:当我们使用对象时spring框架创建
    • 活着:对象只要时在使用过程就一直存活。
    • 死亡:当对象长时间不用,且没用别的对象引用时。由Java的垃圾回收器回收

依赖注入

  • 概念:Dependency Injection 。它是 spring 框架核心 ioc 的具体实现。

    • 我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,当前类需要用到其他类的对象时,由spring为我们提供,我们只需要在配置文件中说明。
    • ioc 解耦只是降低他们的依赖关系,但不会消除。
    • 依赖关系的维护,称为依赖注入
      • 例子:在Spring框架下,当Bean实例 A运行过程中需要引用另外一个Bean实例B时,Spring框架会创建Bean的实例B,并将实例B通过实例A的构造函数、set方法、自动装配和注解方式注入到实例A,这种注入实例Bean到另外一个实例Bean的过程称为依赖注入。
  • 能注入的数据:

    • 基本类型和String
    • 其他bean类型(在配置文件或注解配置过的bean)
    • 复杂类型/集合类型
  • 注入的方式:

    • 第一种:使用构造函数注入
    • 第二种:使用set方法注入
    • 第三种:使用注解注入

构造函数注入

  • 使用的标签:constructor-arg
  • 标签出现的位置;bean标签内部
  • 标签中的属性:

    • type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
    • index:指定要注入的数据在构造函数参数列表的索引位置
    • name:指定要注入的数据在构造函数中的名称
    • value:用于提供基本类型和String 类型的数据
    • ref:用于指定其他的bean类型数据。它指的就是在spring的IoC核心容器中出现过的bean对象
  • 优势:在注入数据时,注入数据是必须的操作,否则对象无法创建成功(因为没用无参构造函数)

  • 缺点:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,为了创建对象,也必须提供
1
2
3
4
5
6
<bean id="accountService" class="com.nogizaka.service.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>

set方法注入

  • 使用的标签:property
  • 标签出现的位置;bean标签内部
  • 标签中的属性:

    • name:指定要注入时所调用的set方法名称(属性名)
    • value:用于提供基本类型和String 类型的数据
    • ref:用于指定其他的bean类型数据。
  • 优点:创建对象时没用明确的限制,可以直接使用默认构造函数

  • 缺点:如果由某个成员必须有值,则获取对象时有可能set方法没有执行。
1
2
3
4
5
6
<bean id="accountService" class="com.nogizaka.service.AccountServiceImpl">
<property name="name" value="张三"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
  • 复杂类型/集合的注入
    • 用于给List结构集合注入的标签:list array set
    • 用于给Map结构集合注入的标签:map props
    • 在注入集合数据时,只要结构相同,标签可以互换
1
2
3
4
5
6
7
8
9
10
<bean id="accountService" class="com.nogizaka.service.AccountServiceImpl">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
</bean>
<bean id="now" class="java.util.Date"></bean>

Spring注解

Spring IoC的常用注解

1
2
3
4
<!--xml配置-->
<bean id="" class="全限定类名" scope="作用范围" init-method="" destroy-method="">
<property name="" value="" | ref=""></property>
</bean>
  • 创建对象:和XML配置文件中编写一个<bean>标签实现的功能一样
  • 注入数据:和在XML配置文件中的bean标签中写一个<property>标签的作用一样
  • 改变作用范围:和在bean标签中使用scope属性实现功能一样
  • 生命周期:和在bean标签中使用init-methoddestroy-method的作用一样

使用注解准备

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--使用注解时,需要告诉spring在创建容器时需要扫描的保,配置所需要的标签不是在beans的约束中,而是在一个名称为context名称空间和约束中-->
<?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
https://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="要扫描的全限定包名"/></context:component-scan>

</beans>

一、创建对象的注解

@Component

  • 作用:用于把当前类对象存入spring容器中。相当于在 xml 中配置一个 bean。
  • 属性:
    • value:用于指定bean的id。不写时默认是当前类名(首字母小写)

@Controller、@Service、@Repository

  • 功能和属性和@Component一样,可相互替换
  • @Controller:一般用在表现层
  • @Service:一般用在业务层
  • @Repository:一般用在持久层

二、依赖注入的注解

@Autowired

  • 作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
  • 当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
  • 出现位置:可以是变量上,也可以是方法上
  • 注意:在使用注解注入时,set方法就不是必需。如果IoC容器中没有任何bean的类型和要注入的变量类型匹配,则报错。

@Qualifier

  • 作用:在按照类型注入的基础之上,再按照名称注入。它给类成员注入时不能单独使用(配合@Autowired使用)。但是在给方法参数注入时可以单独使用。
  • 属性:
    • value:指定注入bean的id

@Resource

  • 作用:直接按照bean的id注入。可以独立使用
  • 属性:

    • name:用于指定bean的id
  • 注意:

    • 省略name
      • 注解标注set方法时省略name属性,如setXXX(),则name值默认为xXX,去掉“set”,首字母小写。
      • 注解直接标注变量时省略name属性,则那么name值默认与所标注变量名相同。
    • @Resource省略name属性后,按其默认规则没有找到所需要注入的Bean时,则采用
      byType的方式寻找,即寻找与所标注set方法参数类型或所标注变量类型相匹配的Bean,如有唯一匹配则直接注入,如有多个匹配,则抛出异常。

@Autowired、@Qulifier、@Resource只能注入其他bean类型的数据,基本数据类型和String类型的数据无法使用该注解。集合类型的注解只能由XML配置文件实现。

@Value

  • 作用:用于注入基本类型和String类型的数据
  • 属性:
    • value:用于指定数据的值,它可以使用Spring中的SpEL(Spring的EL表达式${表达式})

三、作用范围的注解

@Scope

  • 作用:用于指定bean的作用范围
  • 位置:类上、方法上
  • 属性:
    • value;指定范围的取值。常用取值:singleton、prototype

四、生命周期的注解

@PostConstruct

  • 作用:用于指定初始化方法

@PreDestroy

  • 作用:用于指定销毁方法

新注解

@Configuration

  • 作用:指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)
  • 位置:类上
  • 属性:
    • value:用于指定配置类的字节码

@ComponentScan

  • 作用:用于通过注解指定spring在创建容器时要扫描的包
  • 位置:类上
  • 属性:
    • value:它和basePackages的作用一样,都是用于指定创建容器时要扫描的包。
    • 等价于:<context:component-scan base-package="com.noizaka"/>

@Bean

  • 作用:用于把当前方法的返回值作为bean对象存入spring的IoC容器中
  • 位置;方法上
  • 属性:
    • name:用于指定bean的id,当不屑时,默认值时当前方法的名称

当我们使用注解配置时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired注解的作用一样

@Import

  • 作用:用于导入其他的配置类
  • 属性:
    • value:用于指定其他配置类的字节码,当我们使用Import的注解后,有Import注解的累就是父配置类,导入的都是子配置类

@PropertySource

  • 作用:用于指定properties文件的位置
  • 属性:
    • value:指定文件的名称和路径。关键字:classpath,表示类路径下@PropertySource("classpath:jdbcConfig.properties")

Spring整合Junit

  • 导入spring整合junit坐标

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.3.RELEASE</version>
    </dependency>
  • 使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的(@RunWith(SpringJuni4ClassRunner.class)

  • 告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
    • @ContextConfiguration
      • locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
      • classes:指定注解类所在地位置

AOP

概念

  • AOP:Aspect Oriented Programming。面向切面编程
  • 简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

作用及优势

  • 作用:在程序运行期间,不修改源码对已有方法进行增强
  • 优势:
    • 减少重复代码
    • 提高开发效率
    • 维护方便

AOP的实现

  • 两种动态代理技术
  • 开发阶段(我们做的)
    • 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求
    • 把公用代码抽取出来,制作成通知。(开发阶段最后再做)
    • 在配置文件中,声明切入点与通知间的关系,即切面。
  • 运行阶段(Spring 框架完成的)
    • Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

动态代理

  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法的增强
  • 分类
    • 基于接口的动态代理
    • 基于子类的动态代理

基于接口的动态代理

  • 涉及的类:Proxy
  • 提供者:JDK官方
  • 如何创建代理对象
    • 使用Proxy类中的newProxyInstance方法
    • 创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用
  • newProxyInstance方法的参数:
    • ClassLoader:类加载器。用于加载代理对象字节码,和被代理对象使用相同的类加载器。被代理对象.getClass().getInterfaces()
    • Class[]:字节码数组。它是让代理对象和被代理对象有相同的方法。被代理对象.getClass().getInterfaces()
    • InvocationHandler:提供增强的代码。它是让我们写如何代理。一般都是一个该接口的实现类,通常情况下是匿名内部类,但不是必须。
      • invoke方法:执行被代理对象的任何接口方法都会经过该方法
        • proxy:代理对象的引用
        • method:当前执行的方法
        • args:当前执行方法所需的参数
        • return:和被代理对象有相同的返回值
        • 匿名内部类访问外部成员,外部成员必须被final修饰

基于子类的动态代理

  • 涉及的类:Enhancer
  • 提供者:第三方CGLib库
  • 如何创建代理对象
    • 使用Enhancer类中的create方法
    • 创建代理对象的要求:被代理类不能是最终类
  • create方法的参数:
    • Class:字节码。它是用于指定被代理对象的字节码
    • Callback:用于提供增强的代码。我们一般写的都是该接口的子接口实现类:MethodInterceptor(方法拦截器)
      • intercept方法:执行被代理对象的任何方法都会经过该方法
        • proxy:代理对象的引用
        • method:当前执行的方法
        • args:当前执行方法所需的参数
        • methodProxy:当前执行方法的代理对象

Spring中的AOP

  • 代理的选择:在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。使用了接口,则用Proxy,没有使用接口,则用Cglib库
  • 术语
    • Joinpoint:连接点。指那些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
    • Pointcut:切入点。指我们要对哪些Joinpoint进行拦截的定义
    • Advice:通知/增强。指拦截到Joinpoint之后所要做的事情就是通知
      • 通知的类型:
        • 前置通知:在某个方法之前执行 实现MethodBeforeAdvice接口
        • 后置通知:在某个方法之后执行 实现AfterReturningAdvice接口
        • 异常通知:在某个方法发生异常时执行 实现ThrowsAdvice接口
        • 环绕通知:可以在方法之前、之后、发生异常时执行!MethodInterceptor接口
        • 最终通知:不论目标方法是否发生异常都会执行
    • Introduction:引介,引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法Field
    • Target:目标对象。代理的目标对象,即被代理的对象
    • Weaving:织入。把增强应用到目标对象来创建新的代理对象的过程
      • Spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入
    • Proxy:代理。一个类被AOP织入增强后,就产生一个结果代理类。
    • Aspect:切面。是切入点和通知(引介)的结合

基于XML的AOP配置

  • 第一步:导入依赖坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--spring-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
    </dependency>
    <!--解析切入点表达式-->
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
    </dependency>
  • 第二步:创建业务接口

  • 第三步:实现业务接口实现类
  • 第四步:创建通知类
  • 第五步:配置xml
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
  • spring中基于XML的AOP配置步骤

    1. 把通知的Bean也交给spring管理
    2. 使用aop:config标签表明开始AOP的配置
    3. 使用aop:aspect标签表明配置切面

      • id : 给切面提供一个唯一标志
      • ref : 指定通知类bean的id
    4. aop:aspect标签的内部使用对应的标签来配置通知的类型

      • aop:before : 表示配置前置通知

        • method属性 : 指定哪个方法使用前置通知
        • pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
      • 切入点表达式的写法

        • 关键字:execution(表达式)
        • 标准表达式:访问修饰符 返回值 全类名.方法名(参数列表)
        • 全通配写法:* *..*.*(..)
        • 访问修饰符可以省略
        • 返回值可以使用通配符,表示任意返回值
        • 包名可以使用通配符,表示任意包。但是,有几级包,就需要写几个*.
        • 包名可以使用..表示当前包及其子包
        • 类名和方法名都可以使用通配符
        • 参数列表:

          • 基本类型直接写名称
          • 引用类型写包名.类名的方式
          • 可以使用通配符表示任意类型,但必须有参数
          • 可以使用..表示有无参数均可,有参数可以是任意类型

          • 实际项目中一般切入到业务层实现类下的所有方法* com.nogillc.service.impl.*.*(.. )

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--配置Spring的IOC。把Service对象配置进来-->
<bean id="accountService" class="com.nogillc.service.impl.AccountServiceImpl"></bean>
<!--配置logger类-->
<bean id="logger" class="com.nogillc.utils.Logger"></bean>

<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知的方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public void com.nogillc.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
  • 其他几种通知类型

    • 前置通知:<aop:before>
    • 后置通知:<aop:returning>
    • 异常通知:<aop:throwing>
    • 最终通知:<aop:after>
    • 环绕通知:<aop:around>
  • 通用化切入点表达式

    • 配置切入点表达式 id属性用于指定表达式唯一的标识,expression属性用于指定表达式内容
    • 此标签写在aop:aspect标签内部,只能当前切面使用
    • 它还可以写在aop:aspect标签外部(但必须在aop:aspect切面标签之前),此时就变成了所有切面可用
1
2
3
4
5
6
7
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知的方法和切入点方法的关联-->
<aop:before method="printLog" pointcut-ref="pc"></aop:before>
<!--配置切入点表达式 id属性用于指定表达式唯一的标识,expression属性用于指定表达式内容-->
<aop:pointcut id="pc" expression="execution(public void com.nogillc.service.impl.AccountServiceImpl.saveAccount())"/>
</aop:aspect>
  • 环绕通知
    • aop:around:作用:用于配置环绕通知属性:
      • method:指定通知中方法的名称。
      • pointct:定义切入点表达式
      • pointcut-ref:指定切入点表达式的引用说明:它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
      • 注意:通常情况下,环绕通知都是独立使用的
      • spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
      • 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。

基于注解的AOP配置

  • 配置spring创建容器要扫描的包
  • 配置spring开启注解AOP的支持
1
2
3
4
<!--配置spring创建容器要扫描的包-->
<context:component-scan base-package="com.nogillc"></context:component-scan>
<!--配置spring开启注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • @Aspect :声明当前类是一个切面类
  • @Before
    • 作用:把当前方法看成是前置通知。
    • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。
  • @AfterReturning
    • 作用:把当前方法看成是后置通知。
    • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。
  • @AfterThrowing
    • 作用:把当前方法看成是异常通知。
    • 属性:value:用于指r定切入点表达式,还可以指定切入点表达式的引用
  • @After
    • 作用:把当前方法看成是最终通知。
    • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用
  • @Around
    • 作用:把当前方法看成是环绕通知。
    • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用
  • @Pointcut

    • 作用:指定切入点表达式
    • 属性:指定表达式的内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Pointcut("execution(* com.nogillc.service.impl.*.*(..))")
    private void pt1() {}


    /*环绕通知*/
    @Around("pt1()")//注意:千万别忘了写括号
    public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object rtValue = null;
    try {
    //获取方法执行所需的参数
    Object[] args = pjp.getArgs();
    //前置通知:开启事务
    beginTransaction();
    //执行方法
    rtValue = pjp.proceed(args);
    //后置通知:提交事务
    commit();
    }catch(Throwable e) {
    //异常通知:回滚事务
    rollback();
    e.printStackTrace();
    }finally {
    //最终通知:释放资源
    release();
    }
    return rtValue;
  • 不使用 XML 的配置方式 @EnableAspectJAutoProxypublic

    1
    2
    3
    4
    @Configuration
    @ComponentScan(basePackages="com.nogillc")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {}

Spring中的JdbcTemplate

  • 作用:用于和数据库交互,实现对表的CRUD操作
  • 常用方法:
    • 查询:query
    • 更新:update

环境搭建

  • 导入坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--spring jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>

<!--spring tx事务管理-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>

配置xml

  • 使用spring内置数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--配置账户的持久层-->
<bean id="accountDao" class="com.nogibingo.dao.impl.AccountDao">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>

<!--jdbc template-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>

</bean>

<!--dataSource spring内置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>

抽取持久层重复代码

方法一:在Dao类中定义JdbcTemplate

  • 将JdbcTemplate声明为私有变量,然后进行自动注入
  • 该方法适合注解配置,用xml进行配置较为麻烦

方法二:让Dao类继承JdbcDaoSupport

  • JdbcDaoSupport 是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源
  • 包:import org.springframework.jdbc.core.support.JdbcDaoSupport;
  • 该方法不适合注解配置,适合xml配置

Spring中的事务控制

  • JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案。
  • spring 框架为我们提供了一组事务控制的接口。这组接口是在spring-tx中。
  • spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。

事务控制API

  • PlatformTransactionManager

    • 获取事务状态信息:TransactionStatus getTransaction(TransactionDefinition definition)
    • 提交事务:void commit(TransactionStatus status)
    • 回滚事务:void rollback(TransactionStatus status)
    • 在开发种使用它的实现类
      • DataSourceTransactionManager:使用SpringJDBC或iBatis进行持久化数据
      • HibernateTransactionManager:使用Hibernate进行持久化数据
  • TransactionDefinition

    • 获取事务对象名称:String getName()
    • 获取事务隔离级别:int getIsolationLevel()
    • 获取事务传播行为:int getPropagationBehavior()
    • 获取事务超时时间:int getTimeout()
    • 获取事务是否只读:boolean isReadOnly()
  • 事务隔离级别:默认使用数据库的隔离级别,4种

    • 读未提交
    • 读已提交,解决脏读
    • 重复读,解决脏读、不可重复读
    • 串行化,解决脏读、不可重复读、幻读
  • 事务的传播行为

    • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
    • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
    • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
    • REQUES_NEW:新建事务,如果当前在事务中,把当前事务挂起
    • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
    • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
    • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
  • 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。

  • 是否是只读事务:建议查询时设置为只读。

  • TransactionStatus:此接口提供的是事务具体的运行状态

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

基于XML的AOP实现事务控制

  1. 配置事务管理器
  2. 配置事务通知:导入事务的约束xmlns:tx,同时也需要xmlns:aop
  3. 使用tx:advice标签配置事务通知
    • 属性:
      • id:事务通知的唯一标识
      • transaction-manager:给事务通知提供一个事务管理器引用
  4. 配置AOP中的通用切入点表达式
  5. 建立事务通知h和切入点表达式的对应关系
  6. 配置事务的属性:是在事务的通知tx:advice标签内部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--事务的属性
isolation: 指定事务的隔离级别 默认值是DEFAULT.表示使用数据库的默认隔离级别
propagation(传播): 用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询可以用SUPPORTS
read-only:用于指定事务是否只读。只有查询方法才能设置为true,默认值是false,表示读写
timeout:用于指定事务的超时时间,默认值-1,永不超时
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
no-rollback-for :用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.nogillc.service.impl.*.*(..))"/>
<!--建立切入点表达式h和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>

基于注解的AOP实现事务控制

  1. 导入约束
    • xmlns:aop
    • xmlns:tx
    • xmlns:context
  2. 配置事务管理器
  3. 开启Spring对注解事务的支持
  4. 在需要事务支持的地方使用@Transactional
1
2
3
4
5
6
<!--配置事务管理器-->
<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>
  • 不使用xml的配置
1
2
3
4
5
6
7
@Configuration//注解类
@ComponentScan("com.nogi")//要扫描的包
@Import({JdbcConfig.class,TransactionConfig.class})//导入其他类
@PropertySource("jdbcConfig.properties")//指定配置文件
@EnableTransactionManagement//开启事务注解支持
public class SpringConfiguration {
}
Knowledge is priceless, thanks for your support !