spring学习笔记

视频地址:https://www.bilibili.com/video/BV1nz4y1d7uy/?vd_source=c9d6648c0583f404d322e2130b32ea96

0.Spring介绍

  • Spring是一个框架,核心技术为IOC和AOP,主要功能在于实现解耦合,同时spring也可以理解为是一个容器(Spring底层使用concurrentHashMap容器存储创建的对象),容器中存放的是Java对象(bean),可以根据需要从容器中取出相应的对象。如何把对象放入spring容器中呢?主要有以下两种方法:
    • 使用xml配置文件,具体来说就是使用<bean>标签。
    • 通过注解将对象放入spring容器中,
  • Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称 为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
  • 存在两种方法将对象放入容器中:(1)通过创建spring容器的配置文件,并在配置文件中利用<bean>声明对象。(2)通过一系列注解(注解的底层原理也是基于配置文件)。而后根据需求通过BeanFactory或者 ApplicationContext中的getBean("id")方法获取容器中的对象。

1.Spring IOC

1.1 概念

Spring IOC也称为控制反转,把对象的创建,赋值,管理工作都交给Spring容器实现,也就是对象的创建是由其它外部资源完成。

  • 控制指的就是创建对象,对对象的属性赋值,对象之间的关系管理的权力。

  • 反转指的就是把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象,创建对象。

  • 正转:开发人员主动管理对象。在代码中,使用new构造方法创建对象。

    public static void main(String args[]){
    	Student student = new Student(); // 在代码中, 创建对象。--正转。
    }
    

IOC的作用:实现解耦合,让对象的管理更加方便(可以在减少代码的改动的基础上,实现不同的功能。)

1.2 技术实现

  • IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式是依赖注入(DI)
  • IOC的技术实现:Spring框架使用依赖注入(DI)实现 IoC。而Ioc拥有创建对象的权力,究其根本,Spring底层创利用反射机制实现对象的创建。
所谓的依赖注入,其实是当一个bean实例引用到了另外一个bean实例时,spring容器帮助我们创建依赖bean实例并注入(传递)到另一个bean中。分析这个过程,可以发现存在着循环依赖等问题。

依赖注入的理解:在调用无参构造函数创建对象后,就要对对象的属性进行初始化。初始化是由容器自动完成的,整个赋值过程称为依赖注入。 【可参考:https://aliyuque.antfin.com/lizixuan.lzx/kczhzp/mpytnlknvxt3cxo6?singleDoc#】

1.3 spring IOC的简单使用

  • 创建maven项目
  • maven项目引入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
  • 定义接口和接口的实现类(创建类)
//接口
package com.sysu.service;
public interface SomeService {
    public void doSome();
}

//接口实现类
package com.sysu.service.impl;
import com.sysu.service.SomeService;
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("执行dosome方法");
    }
}
  • 创建spring配置文件,使用<bean>标签声明对象。

main/resources目录下创建一个beans.xml文件,该文件即为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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--bean标签的作用:告诉spring创建对象
    id:对象的名称,唯一值。spring通过这个名称找到对象
    class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类.

    一个bean标签的作用等同于让spring完成Someservice someService(id值) = new SomeserviceImpl();
	在bean标签内使用<property name="" value=""/>标签可以实现给对象内的属性赋值。
    然后spring会把创建好的对象放入到map中,springMap.put(id的值,对象);
    例如springMap.put("someservice", new SomeserviceImpl())
    一个bean标签声明一个对象。根据id值就能获取唯一对应的对象
    -->
    
    <bean id="someService" class="com.sysu.service.impl.SomeServiceImpl"></bean>

</beans>

<!--
    spring的配置文件
    1.beans: 根标签,spring把java对象成为bean。
    2.spring-beans.xsd是约束文件,和mybatis指定dtd是一样的。
-->
  • 定义测试类,通过ApplicationContext接口和它的实现类ClassPathXmlApplicationContext中的方法getBean完成容器中对象的获取。
@Test
public void test02(){
    String config = "beans.xml";
    //创建spring容器对象,同时会创建配置文件中所有bean标签声明的对象(默认采用无参构造函数)
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    //根据id从spring容器中获取目标对象
    SomeService someService = (SomeService) ac.getBean("someService");
    someService.doSome();
}

1.4 基于XML的DI(了解)

  • 在调用无参构造函数创建对象后,就要对对象的属性进行初始化。初始化是由容器自动完成的,整个赋值过程称为依赖注入。 依赖注入(DI)主要分为两种:基于XML的DI(了解) + 基于注解的DI(重点)
  • 基于XML的DI分为三种:set 注入、构造注入以及自动注入,其中自动注入只负责给引用类型属性赋值,分为byName以及byType(见@Autowired注解)两种。
    • byName:按名称注入,类中引用类型的【属性名】和spring容器中bean的【id】一样、数据类型一样,则把这样的bean赋值给引用类型。
  • set注入以及构造注入既可以给简单类型(8种基本数据类型以及string)赋值也可以给引用类型赋值。
    • set注入底层原理:spring会调用类中属性对应的set方法,在set方法中完成属性赋值。
    • 构造注入底层原理:spring调用类的有参构造函数完成属性赋值。
  • 有关基于XML的DI可以参考视频。

1.5 基于注解的DI(重点)

使用注解的简单步骤(@Component)

  • 使用maven构建项目

  • 在maven配置文件中加入依赖

    • 在加入spring-context依赖的同时,会自动间接加入spring-aop的依赖。使用注解必须使用spring-aop依赖
  • 创建类,在类中加入spring的注解

package com.sysu.entity

//@Component注解的作用
//(1)告诉spring创建一个名为myStudent的对象(默认调用无参构造函数),并放置spring容器中。起作用等同于spring配置文件中的bean标签,该注解有一个value属性,该属性对应的值即为创建的对象的名称,等同于bean Id。
//(2)@Component("自定义创建对象名")【最常用的方式】
//(3)value属性可以省略,直接使用@Component即可。则创建类的名称由spring默认提供:类名首字母小写
//(4)当某个类不属于业务层、持久层、控制层的时候,即使用@Component注解进行对象的创建。
@Component(value = "myStudent")
public class Student(){
    private String name;
    private Integer age;
    //省略get、set方法
}

spring中和@Component注解功能一致、使用语法相同的注解有:
1. @Repository(用在持久层类的上面): 放在dao的实现类上面,表示创建dao对象, dao对象是能访问数据库的。
2. @Service(用在业务层类的上面): 放在service的实现类上面,创建service对象, service对象是做业务处理,可以有事务等功能的。
3. @Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象能够接收用户提交的参数,显示请求的处理结果。
  • 创建spring的配置文件(applicationContext.xml),加入一个组件扫描器的标签,说明注解在你的项目中的位置
<?xml version="1.0" encoding= "UTF-8" ?>
<beans xmIns="http://www.springframework.org/schema/beans”
	   xmlns:xsi="http://www.W3.org/2001/XMschema-instance"
   	   xmLns:context="http://www.springframework.org/schema/context"
	   xsi:schemalocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--声明组件扫描器(Component-scan),组件就是java对象
        basepackage:指定注解在你的项目中的包名。
        component-scan工作方式: spring会扫描遍历base-package指定的包
 		把包中和子包中的所有类中的注解,按照注解的功能创建对象,或给属性赋值。
        -->
        <context:component-scan base-package="com.sysu.entity"/>
</beans>
  • 测试方法
@Test
public void test01(){
    String config = "applicationContext.xml";
    ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
    Student stu = (Student)ctx.getBean("myStudent");
    System.out.println(stu);
}

@Value

  • @Value:给类中的简单类型(8种基本数据类型+string)属性进行赋值
  • 使用位置: 在属性定义的上面,无需set方法。
package com.sysu.entity

@Component("myStudent")
public class Student(){
   
    @Value("张飞")
    private String name;
    @Value("29")
    private Integer age;
}

@Test
public void test01(){
    String config = "applicationContext.xml";
    ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
    Student stu = (Student)ctx.getBean("myStudent");
    System.out.println(stu);
}

@Autowired

  • @Autowired: 给类中的引用类型属性进行赋值。
  • @Autowired使用的是自动注入原理(byName,byType),默认使用的是byType自动注入。
  • 使用的位置:在属性定义的上面,无需set方法。
  • 在本实例中,当扫描到@Autowired注解时,需要提供一个和School类同源的对象,因此还需要先实例化一个School对象mySchool.
  • 同源对象:
    • 由相同类生成的两个对象
    • 类和类之间是继承关系,由这两个类生成的对象也为同源对象
    • 类和类之间是接口与实现类关系,由这两个类生成的对象也为同源对象
package com.sysu.entity

@Component("myStudent")
public class Student(){
   
    @Value("张飞")
    private String name;
    @Value("29")
    private Integer age;
    @Autowired
    private School school;
}
-------------------------------------------------
package com.sysu.entity
    
@Component("mySchool")
public class School{
    @Value("中山大学")
    private String name;
    @Value("广州")
    private String address;
}


@Test
public void test01(){
    String config = "applicationContext.xml";
    ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
    Student stu = (Student)ctx.getBean("myStudent");
    System.out.println(stu);
}
  • @Autowired注解 +@Qualifier注解即可实现采用byName的方式对引用类型属性进行赋值
  • @Qualifier注解使用方法:@Qualifier("给属性赋值的对象的名称")注解
package com.sysu.entity

@Component("myStudent")
public class Student(){
   
    @Value("张飞")
    private String name;
    @Value("29")
    private Integer age;
    @Autowired
    @Qualifier("mySchool")
    private School school;
}
-------------------------------------------------
package com.sysu.entity
    
@Component("mySchool")
public class School{
    @Value("中山大学")
    private String name;
    @Value("广州")
    private String address;
}

@Test
public void test01(){
    String config = "applicationContext.xml";
    ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
    Student stu = (Student)ctx.getBean("myStudent");
    System.out.println(stu);
}

@Resource注解

  • 该注解来自JDK,spring框架提供了对这个注解的功能支持,可以使用该注解给引用类型属性进行赋值,该注解的功能和@Autowired相同,底层原理也是使用自动注入,支持byName(默认)、byType。
  • 使用的位置:在引用类型属性定义的上面,无需set方法。
  • 先采用byName注入,如果失败则采用byType方式注入。
package com.sysu.entity

@Component("myStudent")
public class Student(){
   
    @Value("张飞")
    private String name;
    @Value("29")
    private Integer age;
    @Resource("mySchool")
    private School school;
}
-------------------------------------------------
package com.sysu.entity
    
@Component("mySchool")
public class School{
    @Value("中山大学")
    private String name;
    @Value("广州")
    private String address;
}

@Test
public void test01(){
    String config = "applicationContext.xml";
    ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
    Student stu = (Student)ctx.getBean("myStudent");
    System.out.println(stu);
}

常用注解参考文章

1.6 使用多个Spring配置文件

  • 在实际应用里,随着应用规模的增加,系统中Bean数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring配置文件分解成多个配置文件。
  • 多个配置文件中有一个总配置文件,总配置文件会引入其余配置文件。在 Java 代码中只需要使用总配置文件对容器进行初始化即可。
例如一个项目中存在三个spring配置文件
spring-school.xml
spring-student.xml
total.xml

total.xml配置文件的内容如下:(classpath为类路径,即配置文件相对于target/classes目录的路径)
<import resource="classpath:spring-school.xml"/>
<import resource="classpath:spring-student.xml"/>

1.7 注解与xml的对比

注解优点:

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

注解缺点:

  • 以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

XML优点:

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

XML缺点

  • 编写麻烦,效率低,大型项目过于复杂。

2.AOP

2.1 AOP基本概念

  • 理解角度一:AOP(Aspect Orient Programming),面向切面编程。它是一种思想,常见的实现该思想的框架有Spring以及AspectJ。这两个框架底层都是通过动态代理实现AOP,即AOP是从动态角度考虑程序的运行,所以AOP这种思想的作用可以等价于动态代理的作用。

  • AOP(面向切面编程)的含义:合理的安排切面的执行时间(在目标方法前, 还是目标方法后)、合理的安排切面执行的位置(例如在哪个类或者哪个方法需要进行功能增强)

  • AOP作用

    • 可以在你类源码不变的情况下,给类增加一些额外的功能,减少代码的冗余。同时能够实现业务功能和非业务功能【日志、事务等】的解耦合。
    • 具体来说就是在程序执行过程中,通过创建代理对象,然后让代理对象执行方法,从而实现给目标类的方法增加额外的功能。
  • aop的使用场景

    • 当你需要修改系统中某个类的功能,原有类的功能不完善,而你又没有源代码的情况。
    • 当你需要给项目中多个类增加相同的功能时。
    • 给业务方法增加事务、日志输出等功能时。

2.2 AOP的实现

对于AOP这种编程思想,很多框架都进行了实现。最常见的是Spring框架以及AspectJ框架。

  • spring:spring在内部实现了aop规范,能做aop的工作。spring主要在事务处理时使用aop。然而项目开发中很少使用spring的aop实现。 因为spring的aop比较笨重。
  • aspectJ: 一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。在Spring中使用AOP开发时,一般都使用AspectJ的实现方式。aspectJ框架实现aop有两种方式:使用xml的配置文件以及使用注解

2.3 AOP相关术语以及aspectJ实现原理

Aspect(切面)

给你的目标类增加的功能就可以认为是一个切面,切面一般都是非业务方法且独立使用的。常见的切面有:日志记录、输出执行时间、参数检查、事务等等。

JoinPoint(连接点)

表示切面的执行位置。例如给业务方法A增加切面,则业务方法A就是JoinPoint。JoinPoint一般表示一个业务方法。

Pointcut(切入点)

和JoinPoint的唯一区别在于:JoinPoint表示一个方法,Pointcut表示多个方法(多个连接点的集合)。

目标对象

给类A的方法增加功能,则类A就是目标对象。

Advice(通知)

表示切面执行的时间,比如切面是在业务方法之前执行,还是在业务方法之后执行。AspectJ有5个常用的注解用于表示切面的执行时间,@Before(切入点表达式)@AfterReturning(切入点表达式)@Around(切入点表达式)@AfterThrowing(切入点表达式)@After(切入点表达式)

切面三要素

一个切面有三个关键的要素:

  • (切面)切面的功能代码:切面是干什么的
  • (切入点)切面的执行位置,使用Pointcut表示切面执行的位置
  • (切面执行时间)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

切面三要素在aspectJ框架中的实现

上文提到切面三要素:切面的功能代码(自己书写的)、切面的执行位置、切面的执行时间

  • 在aspectJ框架中,使用5个常见的注解(也称为5个常用的通知[Advice]注解)表示切面的执行时间,切面的执行时间又叫做Advice(通知):

    • @Before(切入点表达式)
    • @AfterReturning(切入点表达式)
    • @Around(切入点表达式)
    • @AfterThrowing(切入点表达式)
    • @After(切入点表达式)
    • 例如:@Before(value = "execution(* com.sysu.serviceImpl..(..))")
  • 在aspectJ框架中,使用切入点表达式表示切面执行的位置

execution(方法访问权限、方法返回值方法声明(参数类型)、异常类型)不加粗部分表示可以省略。

execution(public * *(..)) 
指定切入点为:任意公共方法。

execution(* set*(..)) 
指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..)) 
指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* *..service.*.*(..))
指定所有包下的service包里的任意类的任意方法为切入点

2.4 AspectJ框架在项目中的使用

具体可以见动力节点视频(P52开始)

使用aspectj实现aop的基本步骤:
1.新建maven项目
2.加入依赖
	spring依赖
	aspectj依锁
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.1</version>
        </dependency>
        <dependency>
            <groupId> org.aspectj</groupId >
            <artifactId>aspectjweaver</artifactId >
            <version> 1.6.11</version >
        </dependency>
3.创建目标类:接口和他的实现类。要做的是给类中的方法增加功能
4.创建切面类:普通类
	1)在类的上方加入@Aspect注解,表明该类为切面类
	2)在类中定义方法,方法就是切面要执行的功能代码,然后在方法的上面加入aspectj中的通知注解(5个中的任意一个),例如@Before(切入点表达式)
5.创建spring的配置文件:声明对象,把对象交给容器统一管理.声明对象你可以使用注解或者xml配置文件<bean>标签
	1)声明目标对象
	2)声明切面类对象
	3)声明aspectj框架中的自动代理生成器标签<aop:aspectj-autoproxy/>。自动代理生成器:用来完成代理对象的自动创建功能(将spring容器中所有的目标对象一次性都生成代理对象)。
6.创建测试类,从spring容器中获取目标对象(实际就是代理对象).通过代理执行方法,实现aop的功能增强。

java.lang.ClassCastException: com.sun.proxy.$Proxy* cannot be cast to***问题解决方案:https://www.cnblogs.com/MrSaver/p/6104130.html

2.5 AspectJ5个通知注解和常用注解

@Before

  • @Before注解修饰的方法称为前置通知方法,即在目标方法执行之前执行。
  • @Before(value="切入点表达式")
  • 前置通知方法的定义要求:
    • 公共方法public
    • 方法没有返回值
    • 方法可以没有参数,也可以有参数[JoinPoint]。JoinPoint参数的作用:可以在切面方法中获取业务方法执行时的信息,例如方法名,方法的形参等等。这个参数的值由框架赋予,且必须放在第一个位置。
@Aspect
public class MyAspect {
    @Before("execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore(JoinPoint jp){
        //JoinPoint能够获取到方法的定义,方法的参数等信息
        System.out.println("连接点的方法定义: "+ jp.getsignature());
        System.out.println("连接点方法的参数个数:"+jp.getArgs().length);方法参数的信息
        Object args []= jp.getArgs();
        for(Object arg: args){
            System.out.println(arg);
        }
        //切面代码的功能,例如日志的输出,事务的处理
        System.out.println("前置通知:在目标方法之前先执行,例如输出日志");
    }
}

@AfterReturning

  • @AfterReturning注解修饰的方法称为后置通知方法,即在目标方法执行之后执行。能够获取到目标方法的返回值,可以修改这个返回值或者根据这个返回值进行不同的处理。
  • @AfterReturning(value="切入点表达式",returning=“自定义变量名,表示目标方法的返回值”)。注意自定义变量名必须和 @AfterReturning注解修饰的方法的形参名一样。
  • 后置通知方法的定义
    • 公共方法public
    • 方法没有返回值
    • 该方法除了可以包含 JoinPoint 参数外,还可以包含用于接收目标方法返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
@Aspect
public class MyAspect {
    @AfterReturning(value="execution(* *..SomeServiceImpl.doOther(..))",returnint="result")
    public void myAfterReturning(Object result){
        if(result != null){
            String s = (String)result;
            System.out.println("接收到目标方法的返回值");
        }
        System.out.println("后置通知:在目标方法之后执行的功能增强,例如执行事务处理〈切面〉");
    }
}

@Around

  • @AfterReturning注解修饰的方法称为环绕通知方法,即在目标方法执行之前之后执行。
  • @Around(value="切入点表达式")
  • 环绕通知方法的定义
    • 公共方法public
    • 方法有返回值, 推荐使用Object,该返回值就是目标方法的返回值,是可以被修改的
    • 该方法除了可以包含 JoinPoint 参数外,还有固定的参数ProceedingJoinPoint,该参数用于执行目标方法。
@Aspect
public class MyAspect {
    @Around("execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
        Object obj = null;
        //增强功能
        System.out.print1n("环绕通知:在目标方法之前执行的,例如输出日志");
        //执行目标方法的调用,等同于JDK动态代理中的method.invoke(target,angs)
        obj = pjp.proceed();
        //增强功能
        System.out.print1n("环绕通知:在目标方法之后执行的,例如处理事务");//返回目标方法的执行结果
        return obj;
    }
}

@AfterThrowing

  • @AfterThrowing注解修饰的方法称为异常通知方法,即在目标方法抛出异常之后执行。类似于try/catch
  • @AfterThrowing(value="切入点表达式",throwing="自定义变量名")。注意自定义变量名必须和 @AfterThrowing注解修饰的方法的形参名一样。
  • 异常通知方法的定义
    • 公共方法public
    • 方法没有返回值
    • 该方法除了可以包含 JoinPoint 参数外,还可以包含Throwable对象,用于表示目标方法发生的异常对象。
@AfterThrowing(value="execution(* *..SomeServiceImpl.doSecond(..))",throwing="ex")
public void myAfterThrowing(Throwable ex){
    //把异常发生的时间,位置,原因记录到数据库,日志文件等等,
    //可以在异常发生时,把异常信息通过短信,邮件发送给开发人员。
	System.out.print1n("异常通知:在目标方法抛出异常时执行的,异常原因:" + ex.gethessage());
}

@After

  • @After注解修饰的方法称为最终通知方法,无论目标方法是否抛出异常都会执行。类似于finally
  • @Around(value="切入点表达式")
  • 最终通知方法的定义要求:
    • 公共方法public
    • 方法没有返回值
    • 方法可以没有参数,也可以有参数[JoinPoint]
@After("execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
    System.out.println("最终通知:总是会被执行的方法");
}

@Pointcut

  • 用于定义和管理切入点,如果项目中有多个切入点表达式是重复的,可以复用的。那么可以使用@Pointcut对切入点表达式进行定义。
  • @Pointcut(value="切入点表达式"),使用位置在自定义的方法上面
  • 当在一个通知方法的上面使用@Pointcut,此时这个方法的名称就是切入点表达式的别名。在其它的通知方法中,value属性就可以直接使用这个方法名称代替切入点表达式。
@Aspect
public class MyAspect {
    @Pointcut(value="execution(* *..SomeServiceImpl.doThird(..))")
    private void mypt(){
        //无需代码
    }
}
可以使用value="mypt()"代替value="execution(* *..SomeServiceImpl.doThird(..))"。

2.6 动态代理

  • 动态代理的功能:可以在你类源码不变的情况下,给类增加一些额外的功能,减少代码的冗余。
  • 具体来说就是在程序执行过程中,通过创建代理对象,然后让代理对象执行方法,从而实现给目标类的方法增加额外的功能。动态代理有两种实现方式:JDK动态代理CGLIB动态代理

(1) JDK动态代理

JDK动态代理要求需要进行功能扩展的类必须实现接口,Java语言中通过Java.lang.reflect包提供三个类支持代理模式:Proxy、Method和InovcationHandler。

JDK动态代理实现步骤:
1.创建目标类
2.创建InovacationHandler接口的实现类,在这个类中给目标方法增加功能。
3.使用JDK中的类Proxy,创建代理对象,使用代理对象实现功能增加。
具体看动力节点的动态代理视频。

(2) CGLIB动态代理

  • 如果目标类实现了接口,使用JDK动态代理来生成代理类及代理类实例对象(底层通过反射实现)。
  • 对于没有实现接口的对象,利用cglib为其提供代理,在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
  • CGLIB原理:动态生成一个要代理类的子类,子类继承并重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

3.Spring与MyBatis的整合

使用Spring的IOC核心技术,把MyBatis框架中使用的对象交给Spring统一创建和管理。

参考:https://cloud.tencent.com/developer/article/1935568

1.新建maven项目
2.加入maven的依赖
    1) spring依赖
    2) mybatis依赖
    3) mysql驱动
    4) spring的事务的依赖
    5) mybatis和spring集成的依赖:mybatis官方提供的,用来在spring项目中创建mybatis的sqlsesissonFactory, dao对象的
3.创建实体类
4.创建dao接口和mapper文件
5.创建mybatis主配置文件
6.创建service接口和实现类,属性是dao。
7.创建spring的配置文件:利用spring中的bean标签创建与mybatis相关的对象
    1)创建数据源对象
    2)创建sqlsessionFactory对象
    3)创建Dao对象
    4)声明自定义的service
8.创建测试类,获取Service对象,通过service调用dao完成数据库的访问。
源码见GitHub仓库ch06-Spring-MyBatis

4.Spring事务

4.1 使用事务的时机

  • 当我的操作,涉及得到多个表或者多个sql语句(例如insert,update,delete)时,需要保证这些语句要么全部都执行成功,要么全部都执行失败(事务的原子性),这时候就需要使用事务对多个sql语句进行管理。
  • 在Java代码中,一般都在service类的业务方法上进行事务的操作,因为通常业务方法会调用多个dao方法,执行多个sql语句

4.2 JDBC和Mybatis如何处理事务

JDBC创建连接Connection conn; 利用conn.commit()conn.rollback()进行事务的处理。

mybatis访问数据库,利用SqlSession.commit()SqlSession.rollback()进行事务的处理。

缺点:多种数据库的访问技术,有不同的事务处理的机制,对象,方法。

4.3 Spring事务

Spring提供一种处理事务的统一模型, 能使用统一步骤和方式完成多种不同数据库的事务处理功能。例如使用spring的事务处理机制,既可以完成mybatis访问数据库的事务处理,同时也可以完成hibernate访问数据库的事务处理。

Spring处理事务的模型以及使用的步骤都是固定的。开发人员只需要把事务相关信息(例如使用哪种数据库访问技术对事务进行管理,JDBC、Mybatis还是hibernate)提供给spring就可以了。

(1) 事务管理器对象

事务管理器是一个接口和他的众多实现类。它的作用在于完成commit()、rollback()等操作。同时spring把每一种数据库访问技术对应的事务处理类都创建好了。

  • 如果使用mybatis访问数据库,则spring在内部会创建mybatis对应的事务管理器对象:DataSourceTransactionManager

  • 如果使用hibernate访问数据库,则spring在内部会创建hibernate对应的事务管理器对象:HibernateTransactionManager

    -如何把事务相关信息提供给Spring?
    -在Spring的配置文件中使用标签声明数据库访问技术对应的事务管理器对象,例如,你要使用mybatis访问数据库,你应该在xml配置文件中进行如下的配置:<bean id=“xxx" class="...DataSourceTransactionManager">

(2) 事务定义接口

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

(3) 事务提交、回滚的时机

  • 当你的业务方法,执行成功并且没有抛出异常,在该方法执行完毕之后,spring会自动调用事务管理对象中的commit()方法进行事务的提交。

  • 当你的业务方法抛出运行时异常或ERROR时候,spring会自动调用事务管理器对象中的rollback()方法进行事务的回滚。

  • 当你的业务方法抛出非运行时异常, 主要是受查异常时,spring会自动调用事务管理对象中的commit()方法进行事务的提交。

4.4 事务描述的三要素

(1) 事务隔离级别

事务的隔离级别
DEFAULT:采用 DB 默认的事务隔离级别。MySql的默认为REPEATABLE_READ; Oracle默认为READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。

(2) 事务传播行为

事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如A事务中的方法doSome()调用B事务中的方法oOther(),在调用执行期间,事务的维护情况就称为事务传播行为。事务传播行为是加在方法上的。事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

7个传播行为,表示你的业务方法调用时,事务在方法之间是如何使用的。
PROPAGATION_REQUIRED(掌握)
PROPAGATION_REQUIRES_NEW(掌握)
PROPAGATION_SUPPORTS(掌握)
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

(3) 事务默认超时时限

表示一个方法最长的执行时间,如果方法执行时间超过了默认超时时限,事务就会回滚。单位是秒, 整数值, 默认是-1(不配置). 该值一般就使用默认值即可。

4.5 使用Spring的事务注解管理事务

  • 事务在Spring框架中的实现是依赖AOP思想的,上文说到AOP的实现方式有两种:spring框架自己实现的aop以及借助Aspectj框架实现的AOP,因此事务的管理方式也存在两种:使用Spring的事务注解管理事务、使用AspectJ的AOP配置管理事务。
  • Spring的事务注解适合在中小型项目中使用,spring框架底层采用自己实现的AOP给业务方法增加事务的功能。具体而言采用@Around环绕通知实现。
  • 使用@Transactional注解增加事务。必须将该注解放在public方法的上面,表示当前方法具有事务。并且可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等,不同通常都采用默认值即可。
具体的使用步骤
1.在Spring配置文件中声明事务管理器对象:<bean id="xx" class="DataSourceTransactionManager">
2.开启事务注解驱动,告诉spring框架,我要使用注解的方式进行事务的管理。
<!--声明事务注解驱动-->
<tx: annotation-driven transaction-manager="transactionManager"/>
3.在业务层public方法的上面加入@Trancational注解。
  • Spring的事务注解管理事务的底层原理
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。具体而言就是:在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知。  
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
//开启事务,spring给你开启
try{
    	buy(1001,10);
		//spring的事务管理器.commit();
	}catch(Exception e){
        //spring的事务管理器.rollback();
	}
}								

代码见github

4.6 使用AspectJ的AOP配置管理事务

AspectJ的AOP配置管理事务适合大型项目,当有大量的类、方法需要进行事务管理,则使用aspectj框架功能进行事务的配置与管理。

使用该方法进行事务管理的优势在于:只需要在spring配置文件中声明类,方法需要的事务,可以让业务方法和事务配置解耦合。

实现步骤: 都是在xml配置文件中实现。 
1.加入aspectJ依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
2.声明事务管理器对象
<bean id="xx" class="DataSourceTransactionManager">
3.声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)
4.配置aop:指定哪些哪类要创建代理。

具体代码过程见github

Spring常见面试题

1.BeanFactory 和 ApplicationContext的区别

共同点:BeanFactoryApplicationContext都是Spring IOC的核心接口,都是用于从容器中获取Spring beans的,但是,他们二者有很大不同。

(1) Spring beans就是被Spring容器所管理的Java对象。
(2) Spring容器本质是一个concurrentHashMap,负责实例化,配置和装配Spring beans,然后对象的获取则由接口BeanFactory或ApplicationContext进行获取。
  • (懒加载)BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,

  • (即时加载)ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。

BeanFacotry优缺点:应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;但是这样就不能发现一些存在的Spring的配置问题,例如Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常;

ApplicationContext优缺点:在容器启动时,就可以发现Spring中是否存在配置错误,但是内存空间占用更大,且当应用程序配置Bean较多时,程序启动较慢。

2.Bean的作用域

所谓Bean的作用域是指spring容器创建Bean后的生存周期即由创建到销毁的整个过程。之前我们所创建的所有Bean其作用域都是Singleton,这是Spring默认的,在这样的作用域下,每一个Bean的实例只会被创建一次,而且Spring容器在整个应用程序生存期中都可以使用该实例。因此之前的代码中spring容器创建Bean后,通过代码获取的bean,无论多少次,都是同一个Bean的实例

<!-- 默认情况下无需声明Singleton -->
<bean name="accountDao" scope="singleton"    
class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
  • singleton : 唯一bean实例,Spring 中的 bean 默认都是单例的(单例模式)。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在同一个HTTP request内相同。不同http请求则bean也不相同
  • session :在一个HTTP Session中,一个Bean对应一个实例。同一个浏览器中访问属于同一次会话,因此SessionBean实例都是同一个实例对象。当换一个浏览器则不同。

3.解释一下AOP

参考:https://blog.csdn.net/javazejian/article/details/56267036

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

aop的作用

  • 在目标类不增加代码的情况下,给目标类增加功能。
  • 减少重复的代码
  • 让开发人员更加专注于业务逻辑的实现
  • 解耦合:将业务功能和日志、事务等非业务功能解耦

aop的使用场景

  • 当你需要修改系统中某个类的功能,原有类的功能不完善,而你又没有源代码的情况。
  • 当你需要给项目中多个类增加相同的功能时。
  • 给业务方法增加事务、日志输出等功能时。

AOP可以简单理解为aspect(切面)应用到目标函数(类)的过程。对于这个过程,一般分为动态织入(两种方式)和静态织入,因此AOP有两种实现方式:

  • Spring aop(动态织入,又分为两种方式)
  • aspectj aop(静态织入)

(1)Spring aop

Spring aop底层通过代理实现

  • 如果目标类实现了接口,使用JDK动态代理来生成代理类及代理类实例对象(底层通过反射实现)。
  • 对于没有实现接口的对象,利用cglib为其提供代理,在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
  • CGLIB原理:动态生成一个要代理类的子类,子类继承并重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

代理对象可以看作是目标对象的加强版,它是对目标对象中方法功能的一个扩充。

Joinpoint(连接点) 指那些被拦截到的点,可以理解为是目标类的方法
Pointcut(切入点) 指要对哪些Joinpoint(目标类的方法)进行拦截,即被拦截的连接点
Advice(通知) 指拦截到Joinpoint(目标类的方法)之后要做的事情,对方法增强的内容
Aspect(切面) 切入点和通知的结合

(2)aspectj aop

aspectj aop底层是基于字节码操作实现的

  • ApectJ采用的就是静态织入的方式。 ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
  • Spring 2.0版本之后,spring aop采用了aspectj语法来定义切面,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。
posted @ 2021-06-21 21:47  控球强迫症  阅读(130)  评论(0编辑  收藏  举报