SSM使用之Spring

181 阅读30分钟

1. Spring是什么以及Spring的特点

  • Spring是什么
    • Spring 始于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的
    • Spring 的核心是控制反转(IoC)面向切面编程(AOP)
    • Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。
    • Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不使用代码关联,而是通过配置来说明。即在 Spring 中说明对象(模块)的关系。
    • Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度
    • IoC 使得主业务在相互调用过程中,不用自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”,注入即赋值
    • AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。
  • Spring的特点
    • Spring是一个存储对象的容器
    • 轻量 --- Spring的核心功能所需的jar很小(3M左右),运行占用资源少,运行效率高,不依赖于其他jar
    • 针对接口编程,解耦合 --- Spring提供Ioc控制反转,由容器来完成对对象的创建,实现对象之间的以来解耦合
    • 具有面向切面编程(AOP) --- 可以把一些不容易实现的功能如对事务的管理通过声明的方式即可实现,无需在自己手写代码,提高了开发的效率
    • 集成了各种优秀的框架 --- Spring提供对各种优秀框架的支持,简化框架的使用。

2. IoC(Inversion of Control)控制反转

控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。

IoC的实现: 依赖注入(Dependency Injection)

  • 依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
  • Spring框架使用依赖注入(DI)来实现IoC:Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为Bean。Spring 容器管理者容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
2.1 容器接口和实现类
  • ApplicationContext 接口

    • 用于加载Spring的配置文件,在程序中充当“容器”角色

    • 两个实现类

      • ClassPathXmlApplicationContext
      • FileSystemXmlApplicationContext
    • 若 Spring 配置文件存放在项目的类路径下,则使用 ClassPathXmlApplicationContext 实现类进行加载。

    • ApplicationContext容器中的对象的装配时机

      • ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
    • 使用Spring容器创建的java对象

Snipaste_2022-07-10_16-01-08.png

2.2 基于XML的依赖注入(DI)

​ bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。根据注入方式的不同,常用的有两类:set 注入构造注入

2.2.1 set注入(需要掌握的)
  • set 注入也叫设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。

  • 简单类型注入方式

    • 通过bean的property属性为对象的属性进行初始化(附上例子)
    public class Student{
    	private String name;
    	private int age;
    	
        // 使用Setter进行DI
        // Getter和Setter方法(这里要写 由于篇幅问题此处省略)
    }
    
    <bean id="myStudent" class="com.tbabs.pojo.Student">
        <!--简单类型属性赋值-->
    	<property name="name" value="tbabs"/>
    	<property name="age" value="18" />
    </bean>
    
  • 引用类型注入方式

    当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref的值必须为某 bean 的 id 值(附上例子)

    // Student类
    public class Student{
    	private String name;
    	private School myschool;
    }
    
    // School类
    public class School{
    	private String name;
    }
    
    <!--School注入-->
    <bean id="mySchool" class="com.tbabs.pojo.School">
        <!--简单类型属性赋值-->
    	<property name="name" value="XXX"/>
    </bean>
    
    <!--Student注入-->
    <bean id="myStudent" class="com.tbabs.pojo.Student">
        <!--简单类型属性赋值-->
    	<property name="name" value="tbabs"/>
        <!--引用类型属性赋值 使用ref作为属性-->
    	<property name="myschool" ref="mySchool" />
    </bean>
    
2.2.2 构造注入(理解即可)

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系

public Student{
	private String name;
	private School school;
	
	// 使用构造器进行DI
	public Student(String name, School school){
		this.name = name;
		tihs.school = school;
	}
}
<!--School注入-->
<bean id="mySchool" class="com.tbabs.pojo.School">
    <!--简单类型属性赋值-->
	<constructor-arg name="name" value="XXX"/>
</bean>
<!--Student注入-->
<bean id="myStudent" class="com.tbabs.pojo.Student">
	<constructor-arg name="name" value="tbabs"/>
	<constructor-arg name="school" ref="mySchool"/>
<bean/>
<!--
<constructor-arg />标签中用于指定参数的属性有:
    ➢ name:指定参数名称。
    ➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若		 参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
-->
2.2.3 引用类型属性自动注入

​ 对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为<bean/>标签设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。根据自动注入判断标准的不同,可以分为两种:①byName:根据名称自动注入 ②byType: 根据类型自动注入

  • byName方式自动注入

    当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的 属性名 相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。

image-20220710202555349.png

  • byType方式注入

    使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。

image-20220710202538925.png

2.2.4 指定多个Spring配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。

包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>引入。在 Java代码中只需要使用总配置文件对容器进行初始化即可。也可使用通配符*。但,此时要求父配置文件名不能满足*所能匹配的格式,否则将出现循环递归包含。

<!--
假如当前配置文件有多个分别为
	application.xml
	application-spring.xml
	application-shiro.xml
-->
<!--当前application.xml为主配置文件,导入剩下的两个配置文件-->
<import resource="classpath:com/tbabs/application-spring.xml"/>
<import resource="classpath:com/tbabs/application-shiro.xml"/>
<!--使用通配符-->
<import resource="classpath:com/tbabs/application-*.xml"/>
2.3 基于注解的依赖注入(DI)

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变。需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。

<!--声明组件扫描器:指定注解所在的包名-->
<context:component-scan base-package="com.tbabs.service"/>
<!--
	指定多个包的三种方式:
-->
<!--方式一:使用多个 context:component-scan 指定不同的包路径-->
<context:component-scan base-package="com.tbabs.service"/>
<context:component-scan base-package="com.tbabs.vo"/>
<!--方式二:指定 base-package 的值使用分隔符-->
<!--逗号分隔-->
<context:component-scan base-package="com.tbabs.service,com.tbabs.vo"/>
<!--分号分隔-->
<context:component-scan base-package="com.tbabs.service;com.tbabs.vo"/>
<!--方式三:base-package 是指定到父包名-->
<!--
base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到
子包下级的子包。所以 base-package 可以指定一个父包就可以。
但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合
适的。也就是注解所在包全路径。例如注解的类在 com.tbabs.service包中
-->
<context:component-scan base-package="com.tbabs"/>
<!--或-->
<context:component-scan base-package="com"/>
2.3.1 定义Bean的注解@Component(掌握)

需要在类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值。(可以省略value属性)

另外,Spring 还提供了 3 个创建对象的注解:

  • @Repository 用于对 DAO 实现类进行注解
  • @Service 用于对 Service 实现类进行注解
  • @Controller 用于对 Controller 实现类进行注解

这三个注解与 @Component 都可以创建对象,但这三个注解还有其他的含义,

  • @Service 创建业务层对象,业务层对象可以加入事务功能
  • @Controller 注解创建的对象可以作为处理器接收用户的请求
  • @Repository@Service@Controller 是对 @Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。@Component 不指定 value 属性,bean 的 id 是类名的首字母小写
2.3.2 简单类型属性注入@Value(掌握)

需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter上。

image-20220711172410687.png

2.3.3 byType 自动注入@Autowired(掌握)

需要在引用属性上使用注解@Autowired,该注解默认使用 按类型自动装配 Bean 的方式。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

image-20220711173616513.png

2.3.4 byName 自动注入@Autowired与@Qualifier(掌握)

需要在引用属性上联合使用注解 @Autowired@Qualifier@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。

@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。

image-20220711173850232.png

2.3.5 JDK注解@Resource自动注入(掌握)

Spring提供了对 jdk中 @Resource 注解的支持。@Resource 注解既可以按名称匹配Bean,也可以按类型匹配 Bean。默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。

  • byType 注入引用类型属性

    @Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean, 则会按照类型进行 Bean 的匹配注入。

image-20220711174318098.png

  • byName 注入引用类型属性

    @Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

image-20220711174359659.png

2.4 注解与XML的对比

注解

  • 优点是: 方便、直观、高效(代码少,没有配置文件的书写那么复杂)。

  • 其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的

XML 方式

  • 优点是: 配置和代码是分离的、在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。

  • xml 的缺点是:编写麻烦,效率低,大型项目过于复杂


3. AOP(Aspect Orient Programming)面向切面编程

代入例子一:

image-20220712141142219.png

代入例子二:

当然,也可以有另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或处理类中,由主业务逻辑调用。

image-20220712141231770.png

代入例子三(最终决解方案):

以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑的可读性,降低了代码的可维护性,同时也增加了开发难度。所以,可以采用动态代理方式。在不修改主业务逻辑的前提下,扩展和增强其功能

功能增强:

image-20220712141410725.png

3.1 AOP简介
  • AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。

  • AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。

  • AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。

  • AOP 是 Spring 框架中的一个重要内容。

  • 利用 AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

  • 面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。

  • 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。

  • 例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑(转账功能)。

3.2 面向切面编程的好处
  • 减少重复
  • 专注业务
  • 面向切面编程只是面向对象编程的一种补充

image-20220712142759974.png

3.3 AOP编程术语(掌握)
  • 切面(Aspect)

    切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。

  • 连接点(JoinPoint)

    连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

  • 切面点(Ponitcut)

    切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

  • 目标对象(Target)

    目标对象指将要被增强的对象 。 即包含主业务逻辑的类的对象。上例中 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。

  • 通知(Advice)

    通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间

3.4 AspectJ的通知类型(理解)
  • 对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式

  • AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。

  • AspectJ 中常用的通知有五种类型:

    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern)
/*
    execution(访问权限 方法返回值 方法声明(参数) 异常类型)
    modifiers-pattern 访问权限类型(可选)
    ret-type-pattern 返回值类型
    declaring-type-pattern 包名类名(可选)
    name-pattern(param-pattern) 方法名(参数类型和参数个数)
    throws-pattern 抛出异常类型(可选)
*/

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

image-20220712172340485.png

execution(public * *(..)) 
指定切入点为:任意公共方法。
execution(* set*(..)) 
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service.*.*(..)) 
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *.service.*.*(..))
指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *.ISomeService.*(..))
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
execution(* *..ISomeService.*(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
execution(* com.xyz.service.IAccountService.*(..)) 
指定切入点为:IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..)) 
指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
execution(* joke(String,int)))
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
execution(* joke(String,*))) 
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是。
execution(* joke(String,..))) 
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
execution(* joke(Object))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
execution(* joke(Object+))) 
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是
3.5 AspectJ的开发环境(掌握)
  • maven依赖

    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
    </dependency>
    <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version>
    </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version>
    </dependency>
    <!--插件-->
    <build>
    <plugins>
     <plugin>
     <artifactId>maven-compiler-plugin</artifactId>
     <version>3.1</version>
     <configuration>
     <source>1.8</source>
     <target>1.8</target>
     </configuration>
     </plugin>
    </plugins> 
    </build>
    
  • 引入AOP约束

    在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。

3.6 AspectJ基于注解的AOP实现(掌握)

AspectJ 提供了以注解方式对于 AOP 的实现。

  • 实现步骤

    1. 定义业务接口与实现类

image-20220712173726339.png

  1. 定义切面类

    类中定义了若干普通方法,将作为不同的通知方法,用来增强功能

Snipaste_2022-07-12_17-38-31.png

  1. 声明目标对象切面类对象

image-20220712174250619.png

  1. 注册AspectJ的自动代理

    在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到 @Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。

image-20220712174610938.png

<aop:aspectj-autoproxy/> 底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。其工作原理是,<aop:aspectj-autoproxy/> 通过扫描找到 @Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。

  • @Before 前置通知-方法有JoinPoint参数(掌握)

    在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。

image-20220713133704323.png

  • @AfterReturning 后置通知-注解有returning属性(掌握)

    在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

image-20220713133929278.png

  • @Around 环绕通知-增强方法有ProceedingJoinPoint参数(掌握)

    在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

image-20220713135819970.png

  • @AfterThrowing 异常通知-注解中有throwing属性

    在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

image-20220713135908373.png

image-20220713135920500.png

  • @After 最终通知

    无论目标方法是否抛出异常,该增强均会被执行

image-20220713135949276.png

  • @Pointcut定义切入点

    当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

image-20220713140022914.png

3.7 AspectJ基于XML配置文件的AOP实现

基于XML配置是指通过XML配置文件定义切面、切入点及通知,所有的定义必须在<aop:config>元素内完成。

元素名称用途
<aop:config>开发AspectJ的顶层配置元素,在配置文件的<beans>可以包含多个该元素
<aop:aspect>配置一个切面,是<aop:config>的子元素,属性ref指定定义切面的bean
<aop:pointcut>配置切入点,<aop:aspect>的子元素,属性expression指定通知增强的哪些方法
<aop:before>配置前置通知,<aop:aspect>的子元素 ,实现method指定前置通知方法,属性pointcut-ref指定关联的切入点
<aop:after-returning>配置后置返回通知,<aop:aspect>的子元素 ,实现method指定后置返回通知方法,属性pointcut-ref指定关联的切入点,目标方法成功执行后执行
<aop:around>配置环绕通知,<aop:aspect>的子元素 ,实现method指定环绕通知方法,属性pointcut-ref指定关联的切入点
<aop:after-throwing>配置异常通知,<aop:aspect>的子元素 ,实现method指定异常通知方法,属性pointcut-ref指定关联的切入点 ,没有异常时不会执行
<aop:after>配置最终通知,<aop:aspect>的子元素 ,实现method指定最终通知方法,属性pointcut-ref指定关联的切入点,不管是否发生异常都要执行
  • <aop:advisor>大多用于事务管理。
  • <aop:aspect>大多用于日志、缓存。
<!--配置事务管理器-->
<!-- 以下是事务的配置信息 -->
    <!-- 声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 添加事物的切面-->
    <tx:advice id="myadvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*select*" read-only="true"/>
            <tx:method name="*find*" read-only="true"/>
            <tx:method name="*get*" read-only="true"/>
            <tx:method name="*search*" read-only="true"/>
            <tx:method name="*insert*" propagation="REQUIRED"/>
            <tx:method name="*save*" propagation="REQUIRED"/>
            <tx:method name="*add*" propagation="REQUIRED"/>
            <tx:method name="*delete*" propagation="REQUIRED"/>
            <tx:method name="*remove*" propagation="REQUIRED"/>
            <tx:method name="*clear*" propagation="REQUIRED"/>
            <tx:method name="*update*" propagation="REQUIRED"/>
            <tx:method name="*modify*" propagation="REQUIRED"/>
            <tx:method name="*change*" propagation="REQUIRED"/>
            <tx:method name="*set*" propagation="REQUIRED"/>
            <tx:method name="*" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

    <!--通知应用的切入点-->
    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(* *..service..*.*(..))"/>
        <aop:advisor advice-ref="myadvice" pointcut-ref="mypointcut"/>
    </aop:config>

4. Spring集成Mybatis

  • 将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。
  • 实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插 上 mybatis,两个框架就是一个整体。
4.1 准备

image-20220713230719354.png

image-20220713231158447.png

image-20220713231216474.png

image-20220713231532372.png

image-20220713231544240.png

4.2 定义Mybatis主配置文件

在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml

这里有两点需要注意:

(1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器来管理了。

(2)这里对 mapper 映射文件的注册,使用<package/>标签,即只需给出 mapper 映射文件所在的包即可。因为 mapper 的名称与 Dao 接口名相同,可以使用这种简单注册方式。这种方式的好处是,若有多个映射文件,这里的配置也是不用改变的。当然,也可使用原来的<resource/>标签方式。

image-20220713232107910.png

4.3 修改Spring配置文件
4.3.1 数据源的配置(掌握)

使用 JDBC 模板,首先需要配置好数据源,数据源直接以 Bean 的形式配置在 Spring 配置文件中。根据数据源的不同,其配置方式不同

  • Druid 数据源 DruidDataSource

    Druid 官网:github.com/alibaba/dru…

    Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接池。Druid 能够提供强大的监控和扩展功能。Druid 与其他数据库连接池的最大区别是提供数据库的配置连接池

image-20220713232722083.png

4.3.2 从属性文件读取数据库连接信息

为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置文件从中读取数据。

image-20220713232956668.png

Spring 配置文件从属性文件中读取数据时,需要在<property/>的 value 属性中使用${ },将在属性文件中定义的 key 括起来,以引用指定属性的值。

image-20220713233015165.png

该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。使用<context>标签。

<context:property-placeholder/>方式

该方式要求在 Spring 配置文件头部加入 spring-context.xsd 约束文件

<context:property-placeholder/>标签中有一个属性 location,用于指定属性文件的位置。

image-20220713233406529.png

4.3.3 注册SqlSessionFactoryBean

image-20220713233455226.png

4.3.4 定义Mapper扫描配置器MapperScannerConfigurer

Mapper 扫描配置器 MapperScannerConfigurer 会自动生成指定的基本包中 mapper 的代理对象。该 Bean 无需设置 id 属性。basePackage设置dao层接口包名,使用分号或逗号设置多个包。

image-20220713233536464.png

4.4 向Service注入接口名

向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器MapperScannerConfigurer生成的 Mapper 代理对象没有名称,所以在向 Service 注入 Mapper代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口的对象。

image-20220713233819606.png

4.5 Spring最终配置文件

image-20220713234546729.png


5. Spring事务

5.1 Spring的事务管理

事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。

在Spring 中通常可以通过以下两种方式来实现对事务的管理:

(1)使用 Spring 的事务注解管理事务

(2)使用 AspectJ 的 AOP 配置管理事务

5.2 Spring事务管理API

Spring 的事务管理,主要用到两个事务相关的接口。

image-20220714115924391.png

5.2.1 事务管理器接口(重点)

事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。

  • 常用的两个实现类

    PlaformTransactionManager接口有两个常用的实现类:

    • DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。

    • HibernateTransactionManager :使用 Hibernate 进行持久化数据时使用。

  • Spring的回顾公式(理解)

    Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

5.2.2 事务定义接口

事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。

image-20220714130337119.png

  • 定义了五个事务隔离级别常量

    这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

    • DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。

    • READ_UNCOMMITTED:读未提交。未解决任何并发问题。

    • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。

    • REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读

    • SERIALIZABLE:串行化。不存在并发问题。

  • 定义了七个事务传播行为常量

    所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

    事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

    • PROPAGATION_REQUIRED

    • PROPAGATION_REQUIRES_NEW

    • PROPAGATION_SUPPORTS

    • PROPAGATION_MANDATORY

    • PROPAGATION_NESTED

    • PROPAGATION_NEVER

    • PROPAGATION_NOT_SUPPORTED

    a、 PROPAGATION_REQUIRED

    指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 ==Spring 默认的事务传播行为==。如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

    image-20220714131008968.png

    b、PROPAGATION_SUPPORTS

    指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

    image-20220714131111422.png

    c、 PROPAGATION_REQUIRES_NEW

    总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

    image-20220714131135800.png

  • 定义了默认事务超时时限

    常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。

    注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

5.3 使用Spring的事务注解管理事务(掌握)

通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。

@Transactional 的所有可选属性如下所示:

  • propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。

  • isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。

  • readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。

  • timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。

  • rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

  • rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

  • noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

  • noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。

@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务

image-20220714135041405.png

image-20220714135055074.png

5.4 使用AspectJ的AOP配置管理事务(掌握)

使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。

使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。

image-20220714135509775.png


6. Spring与Web

在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet)中获取到 Spring容器的问题。只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。

  • 使用 Spring 的监听器 ContextLoaderListener(掌握)

    问题引入:对于一个应用,Spring容器只需要一个即可,怎么做到一个Web只对应一个Spring容器

    解决方案:对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个ServletContext 对象,该对象是在 Web 应用装载时初始化的若将 Spring 容器的创建时机,放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了Spring 容器在整个应用中的唯一性。当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就保证了 Spring 容器的全局性。上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中:spring-web-5.2.5.RELEASE

    具体步骤:

    • maven依赖pom.xml

      <dependency> 
          <groupId>org.springframework</groupId> 
          <artifactId>spring-web</artifactId> 
          <version>5.2.5.RELEASE</version>
      </dependency>
      
    • 注册监听器ContextLoaderListener

      若要在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。

      image-20220714141309765.png

      Spring 为该监听器接口定义了一个实现类 ContextLoaderListener,完成了两个很重要的工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。

      打开 ContextLoaderListener 的源码。看到一共四个方法,两个是构造方法,一个初始化方法,一个销毁方法。

      image-20220714141347436.png

      所以,在这四个方法中较重要的方法应该就是 contextInitialized(),context 初始化方法。

      image-20220714141415498.png

      跟踪 initWebApplicationContext()方法,可以看到,在其中创建了容器对象。

      image-20220714141435085.png

      并且,将创建好的容器对象放入到了 ServletContext 的空间中,key 为一个常量:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。

      image-20220714141503813.png

    • 指定 Spring 配置文件的位置<context-param>

      ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认的 Spring 配置文件位置与名称为:WEB-INF/applicationContext.xml。但,一般会将该配置文件放置于项目的 classpath 下,即 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及名称进行指定。

      image-20220714141607126.png

      从监听器 ContextLoaderListener 的父类 ContextLoader 的源码中可以看到其要读取的配置文件位置参数名称 contextConfigLocation。

      image-20220714141626300.png

    • 获取 Spring 容器对象

      在 Servlet 中获取容器对象的常用方式有两种:

      • 直接从 ServletContext 中获取

        从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 中存放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。所以,可以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到

      image-20220714141732227.png

      • 通过 WebApplicationContextUtils 获取

        工具类 WebApplicationContextUtils 有一个方法专门用于从 ServletContext 中获取 Spring容器对象:getRequiredWebApplicationContext(ServletContext sc)

      image-20220714141823963.png