Spring学习笔记(王鹤老师)


一、IOC控制反转

1.1 概述

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

  • IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式是依赖
    注入
    。应用广泛
  • 依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA
    对 classB 有依赖。

1.2 IOC的实现

依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。

  • 依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建
    被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
  • Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系
    的管理。
  • Spring 容器是一个大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。
  • Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。

1.3 关于bean标签

  • bean标签的配置
    <!--告诉spring创建对象
        声明bean , 就是告诉spring要创建某个类的对象
        id:对象的自定义名称,唯一值。 spring通过这个名称找到对象
        class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)

        spring就完成 SomeService someService = new SomeServiceImpl();
        spring是把创建好的对象放入到map中, spring框架有一个map存放对象的。
           springMap.put(id的值, 对象);
           例如 springMap.put("someService", new SomeServiceImpl());

        一个bean标签声明一个对象。
    -->
    <bean id="someService" class="com.jjh.service.impl.SomeServiceImpl" />
    <bean id="someService1" class="com.jjh.service.impl.SomeServiceImpl" scope="prototype"/>
  • 测试类(1)使用
    @Test
    public void test01(){
        SomeService service  = new SomeServiceImpl();
        service.doSome();
   }
  • 测试类(2)使用
    @Test
    public void test03(){
        String config="beans.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //使用spring提供的方法, 获取容器中定义的对象的数量
        int nums  = ac.getBeanDefinitionCount();
        System.out.println("容器中定义的对象数量:"+nums);
        //容器中每个定义的对象的名称
        String names [] = ac.getBeanDefinitionNames();
        for(String name:names){
            System.out.println(name);
        }
    }

1.4 基于XML的DI

1.4.1 set注入(重点)

set 注入也叫设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 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">

    <!--声明student对象
        注入:就是赋值的意思
        简单类型: spring中规定java的基本数据类型和String都是简单类型。
        di:给属性赋值
        1. set注入(设值注入) :spring调用类的set方法, 你可以在set方法中完成属性赋值
         1)简单类型的set注入
            <bean id="xx" class="yyy">
               <property name="属性名字" value="此属性的值"/>
               一个property只能给一个属性赋值
               <property....>
            </bean>
            
         2) 引用类型的set注入 : spring调用类的set方法
           <bean id="xxx" class="yyy">
              <property name="属性名称" ref="bean的id(对象的名称)" />
           </bean>
    -->
    <bean id="myStudent" class="com.jjh.ba02.Student" >
        <property name="name" value="李四" />
        <property name="age" value="26" />
        <!--引用类型-->
        <property name="school" ref="mySchool" /><!--setSchool(mySchool)-->
    </bean>
    
    <!--声明School对象-->
    <bean id="mySchool" class="com.jjh.ba02.School">
        <property name="name" value="北京大学"/>
        <property name="address" value="北京的海淀区" />
    </bean>
</beans>

1.4.2 构造注入

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

<!--
        2.构造注入:spring调用类有参数构造方法,在创建对象的同时,在构造方法中给属性赋值。
          构造注入使用 <constructor-arg> 标签
          <constructor-arg> 标签:一个<constructor-arg>表示构造方法一个参数。
          <constructor-arg> 标签属性:
             name:表示构造方法的形参名
             index:表示构造方法的参数的位置,参数从左往右位置是 0 , 1 ,2的顺序
             value:构造方法的形参类型是简单类型的,使用value
             ref:构造方法的形参类型是引用类型的,使用ref
    -->
		    <!--使用name属性实现构造注入-->
		    <bean id="myStudent" class="com.jjh.ba03.Student" >
		        <constructor-arg name="myage" value="20" />
		        <constructor-arg name="mySchool" ref="myXueXiao" />
		        <constructor-arg name="myname" value="周良"/>
		    </bean>
		
		    <!--使用index属性-->
		    <bean id="myStudent2" class="com.jjh.ba03.Student">
		        <constructor-arg index="1" value="22" />
		        <constructor-arg index="0" value="李四" />
		        <constructor-arg index="2" ref="myXueXiao" />
		    </bean>
		
		    <!--省略index-->
		    <bean id="myStudent3" class="com.jjh.ba03.Student">
		        <constructor-arg  value="张强强" />
		        <constructor-arg  value="22" />
		        <constructor-arg  ref="myXueXiao" />
		    </bean>

1.4.3 引用类型自动注入

  • byName(按名称注入)

java类中引用类型的属性名和spring容器中(配置文件)<bean>的id名称一样,且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。

<!--
		语法:
         <bean id="xx" class="yyy" autowire="byName">
            简单类型属性赋值
         </bean>
         -->
         
    <bean id="myStudent" class="com.jjh.ba04.Student" autowire="byName">
        <property name="name" value="李四" />
        <property name="age" value="26" />
        <!--引用类型-->
        <!--<property name="school" ref="mySchool" />-->
    </bean>

    <!--声明School对象-->
    <bean id="school" class="com.jjh.ba04.School">
        <property name="name" value="清华大学"/>
        <property name="address" value="北京的海淀区" />
    </bean>
  • byType(按类型注入)

java类中引用类型的数据类型和spring容器中(配置文件)的class属性是同源关系的,这样的bean能够赋值给引用类型

<!--
		语法:
         <bean id="xx" class="yyy" autowire="byType">
            简单类型属性赋值
         </bean>

         注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,
              多余一个是错误的
-->
    <!--byType-->
    <bean id="myStudent" class="com.jjh.ba05.Student" autowire="byType">
        <property name="name" value="张飒" />
        <property name="age" value="26" />
        <!--引用类型-->
        <!--<property name="school" ref="mySchool" />-->
    </bean>

    <!--声明School对象-->
    <bean id="mySchool" class="com.jjh.ba05.School">
        <property name="name" value="人民大学"/>
        <property name="address" value="北京的海淀区" />
    </bean>

1.4.4 具有关联关系的配置

  • student类的配置文件
        <!--student 模块bean声明-->
    <bean id="myStudent" class="com.jjh.domain.entity.Student" autowire="byType">
        <property name="name" value="张三"/>
        <property name="age" value="23"/>
        <!--引用类型数据-->
    </bean>
  • School类的配置文件
    <bean id="mySchool" class="com.jjh.domain.entity.School">
        <property name="address" value="松潘"/>
        <property name="name" value="工大"/>
    </bean>
  • total配置文件
    <!--
         包含关系的配置文件:
         spring-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
         语法:<import resource="其他配置文件的路径" />
         关键字:"classpath:" 表示类路径(class文件所在的目录),
               在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
    -->

    <!--加载的是文件列表-->
    <!--
    <import resource="classpath:ba01/applicationContext01.xml" />
    <import resource="classpath:ba01/applicationContext02.xml" />
    -->

    <!--
       在包含关系的配置文件中,可以通配符(*:表示任意字符)
       注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
    -->
    <!--导入配置文件-->
    <import resource="classpath:ba01/applicationContext*"/>

1.5 基于注解的DI

  • 注解配置的约束文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

<!--spring 创建容器时要扫描的包  @ComponentScan --> 
<context:component-scan base-package="com.jjh">
</context:component-scan>
</beans>
  • 实体类
@Data
@Component("myStudent")
public class Student {
    private String name;
    private int age;
    //引用类型
    private School school;
    }
  • 测试类中调用
    @Test
    public void demo01(){
        String config = "applicationContext.xml";
        ApplicationContext app = new ClassPathXmlApplicationContext(config);
        Student myStudent = (Student)app.getBean("myStudent");
        System.out.println(myStudent);
    }

1.5.1 使用注解的步骤

1.加入maven的依赖 spring-context,在你加入spring-context的同时, 间接加入spring-aop的依赖。使用注解必须使用spring-aop依赖

2.在类中加入spring的注解(多个不同功能的注解)

3.在spring的配置文件中,加入一个component-scan组件扫描器的标签,说明注解在你的项目中的位置

    <!--声明组件扫描器(component-scan),组件就是java对象
        base-package:指定注解在你的项目中的包名。
        component-scan工作方式: spring会扫描遍历base-package指定的包,
           把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。

       加入了component-scan标签,配置文件的变化:
        1.加入一个新的约束文件spring-context.xsd
        2.给这个新的约束文件起个命名空间的名称
    -->
    <context:component-scan base-package="com.jjh.ba01" />

1.5.2 多注解项目分层

1.@Component: 创建对象的, 等同于<bean>的功能

  • 属性:value 就是对象的名称,也就是bean的id值,value的值是唯一的,创建的对象在整个spring容器中就一个

2.@Repository(用在持久层类的上面) : 放在dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的。
3.@Service(用在业务层类的上面):放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务等功能的。
4.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象,能够接受用户提交的参数,显示请求的处理结果

  • 以上三个注解的使用语法和@Component一样的。 都能创建对象,但是这三个注解还有额外的功能。

1.5.3 简单类型的赋值

@Value: 简单类型的属性赋值

  • 属性: value 是String类型的,表示简单类型的属性值
  • 使用位置:1.在属性定义的上面,无需set方法,推荐使用。
         2.在set方法的上面
  • 配置文件
    <context:component-scan base-package="com.jjh"/>
    <!--配置属性的properties文件-->
    <context:property-placeholder location="classpath:student.properties"/>
  • properties文件
name=Dick
age=20
@Component("myStudent")
public class Student {

    //@Value("李四" )
    @Value("${myname}") //使用属性配置文件中的数据
    private String name;

    @Value("${myage}")  //使用属性配置文件中的数据
    private Integer age;
    }

1.5.4 引用类型的赋值

  • 默认方式

1.@Autowired: spring框架提供的注解,实现引用类型的赋值
2.spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
3.@Autowired:默认使用的是byType自动注入
4.使用位置:

  • 在属性上面使用
  • 在set方法上面使用
@Component("myStudent")
public class Student {

    @Value("李四" )
    private String name;
    @Value("20")
    private Integer age;

    @Autowired
    private School school;
    }
  • 通过名称赋值

如果要使用byName方式:

  • 在属性上面加入@Autowired
  • 属性上面加入@Qualifier(value="bean的id") :表示使用指定名称的bean完成赋值在
@Component("myStudent")
public class Student {

    @Value("李四" )
    private String name;
    @Value("20")
    private Integer age;

    @Autowired
    @Qualifier("mySchool")
    private School school;
    }

1.5.5 Autowired的required属性

  • required :是一个boolean类型的,默认true
  • required=true:表示引用类型赋值失败,程序报错,并终止执行
  • required=false:引用类型如果赋值失败, 程序正常执行,引用类型是null

1.5.6 JDK注解@Resource自动注入

1.@Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
2.使用的也是自动注入原理,支持byName,byType默认是byName
3.使用位置:

  • 在属性定义的上面,无需set方法,推荐使用
  • 在set方法的上面

4.默认是byName:先使用byName自动注入,如果byName赋值失败,再使用byType
5.@Resource只使用byName方式,需要增加一个属性 name,name的值是bean的id(名称)

  • 指定name
@Component("myStudent")
public class Student {

    @Value("李四" )
    private String name;
    private Integer age;

    //只使用byName
    @Resource(name = "mySchool")
    private School school;
  • 默认配置
@Component("myStudent")
public class Student {

    @Value("李四" )
    private String name;
    private Integer age;

    //只使用byName
    @Resource
    private School school;

二、AOP面向切面编程

2.1 概述

  • AOP(Aspect Orient Programming)。面向切面编程是从动态角度考虑程序运行过程
  • AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理,AOP就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了, 让开发人员用一种统一的方式,使用动态代理
    • Aspect: 切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面。切面的特点:一般都是非业务方法,独立使用的
    • Orient:面向, 对着
    • Programming:编程

2.2 相关术语

1.Aspect:切面,表示增强的功能, 就是一堆代码,完成某个一个功能。非业务功能,常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证,切面用于组织多个Advice,Advice放在切面中定义,实际就是对主业务逻辑的一种增强
*
2.JoinPoint:连接点 ,连接业务方法和切面的位置。就某类中的业务方法,程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用
*
3.Pointcut:切入点 ,指多个连接点方法的集合。多个方法。可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点
*
4.Advice:AOP框架在特定的切入点执行的增强处理。处理有"around"、"before"和"after"等类型,能表示切面功能执行的时间,切入点定义切入的位置,通知定义切入的时间
*
5.Target:目标对象,目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象

2.3 AspectJ

2.3.1 概述

AspectJ是一个基于Java语言的AOP框架,提供了强大的AOP功能,其主要包括两个部分:

  • 一个部分定义了如何表达、定义AOP编程中的语法规范;
  • 另一个部分是工具部分,包括编译、调试工具等

aspectJ框架实现aop的两种方式:

  • 使用xml的配置文件 : 配置全局事务
  • 使用注解,我们在项目中要做aop功能,一般都使用注解,aspectj有5个注解
    • @Before
    • @AfterReturning
    • @Around
    • @AfterThrowing
    • @After

2.3.2 AspectJ的切入点表达式

表达式原型:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
相关解释:

  • modifiers-pattern? 访问权限类型
  • ret-type-pattern 返回值类型
  • declaring-type-pattern? 包名类名
  • name-pattern(param-pattern) 方法名(参数类型和参数个数)
  • throws-pattern 抛出异常类型
  • ?表示可选的部分
  • 以上表达式一共4个部分

execution(访问权限 方法返回值 方法声明(参数) 异常类型)

符号意义
*0至多个任意字符
. .用在方法参数中,表示任意多个参数;用在包名后,表示当前包与子包路径
+用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

相关实例

  • execution(public * *(..)):任意公共方法
  • execution(* set*(..)):任何一个以“set”开始的方法
  • execution(* com.xyz.service.*.*(..)):定义在 service 包里的任意类的任意方法
  • execution(* com.xyz.service..*.*(..)):定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类
  • execution(* *..service.*.*(..)):指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
  • execution(* com.xyz.service.IAccountService+.*(..)):IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法
  • execution(* joke(String,int))):所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int;如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)

2.3.3 前置通知:@Before

1.方法的定义要求:

  • 公共方法 public
  • 方法没有返回值
  • 方法名称自定义
  • 方法可以有参数,也可以没有参数

2.@Before: 前置通知注解

  • 属性:value ,是切入点表达式,表示切面的功能执行的位置
  • 位置:在方法的上面
    1.配置依赖
<!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

2.创建业务接口与实现类对象

  • Service.interface
public interface Service {
    public void doSome();
}
  • ServiceImpl.java
@org.springframework.stereotype.Service("myService")
public class ServiceImpl implements Service {

    @Override
    public void doSome() {
        System.out.println("这是我的业务方法!!!");
    }
}

3.创建切面类:MyAspect.java

@Aspect
@Component("myAspect")
public class MyAspect {
	/**
	*指定通知方法中的参数:JoinPoint
	*
	*/
    @Before(value = "execution(void *..doSome(..))")
    public void before(){
        System.out.println("这是前置通知");
    }
}

4.配置applicationContext.xml文件

    <!--扫描文件-->
    <context:component-scan base-package="com.jjh.*"/>

    <!--声明自动代理生成器-->
    <aop:aspectj-autoproxy/>

5.测试类调用

@org.junit.Test
    public void demo01(){
        String config = "applicationContext.xml";
        ApplicationContext app = new ClassPathXmlApplicationContext(config);
        Service proxy = (Service) app.getBean("myService");
        //输出当前类的信息:com.sun.proxy.$Proxy17
        //证明其使用的是JDK动态代理
        System.out.println(proxy.getClass().getName());
        proxy.doSome();
    }

2.3.4 JoinPoint

  • 指定通知方法中的参数 : JoinPoint
  • JoinPoint:业务方法,要加入切面功能的业务方法
  • 作用:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参
  • 如果需要切面功能中方法的信息,就加入JoinPoint
  • JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数
  • 不止前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数
  • MyAspect.java
@Aspect
@Component("myAspect")
public class MyAspect {

    @Before(value = "execution(void *..doSome(..))")
    public void before(JoinPoint joinPoint){
        //获取方法的定义
        System.out.println("方法的签名(定义):" + joinPoint.getSignature());
        System.out.println("方法的名称:" + joinPoint.getSignature().getName());
        //获取方法的实参
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println("参数:" + arg);
        }
        System.out.println("这是前置通知!!!");
    }
}
  • 测试类
    @org.junit.Test
    public void demo01(){
        String config = "applicationContext.xml";
        ApplicationContext app = new ClassPathXmlApplicationContext(config);
        Service proxy = (Service) app.getBean("myService");
        proxy.doSome("张三",20);
    }
  • 打印结果
    在这里插入图片描述

2.3.5 后置通知:@AfterReturning

  • 在目标方法执行之后执行
  • 由于是目标方法之后执行,所以可以获取到目标方法的返回值
  • 该注解的returning属性就是用于指定接收方法返回值的变量名的
  • 所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量
  • 该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型
  • 业务方法
@Component("myService2")
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("这是业务方法!!!");
    }

    @Override
    public String doOther(String str, int i) {
        return "业务方法doOther的返回值!!!";
    }
}
  • 后置通知

1.该注解的 returning 属性就是用于指定接收方法返回值的变量名的
2.除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量
3.该变量最好为Object 类型,因为目标方法的返回值可能是任何类型
4.方法的定义要求:

  • 公共方法 public
  • 方法没有返回值
  • 方法名称自定义
  • 方法有参数的,推荐是Object ,参数名自定义

5.@AfterReturning:后置通知

  • value:切入点表达式
  • returning:自定义的变量,表示目标方法的返回值的,自定义变量名必须和通知方法的形参名一样
  • 可以根据业务方法的返回值做出相应的操作
@AfterReturning(value = "execution(String *..doOther(..))",returning = "res")
    public void myAfterReturning(JoinPoint joinPoint,Object res){
        //获取方法的签属(定义)
        System.out.println(joinPoint.getSignature());
        //获取方法的参数
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println("目标方法参数:" + arg);
        }
        //目标方法的返回值
        System.out.println(res);
        //后置通知
        System.out.println("后置通知!!!");
    }
  • 测试类
    @Test
    public void demo02(){
        String config = "applicationContext.xml";
        ApplicationContext app = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService)app.getBean("myService2");
        proxy.doOther("Dick",20);
    }

2.3.6 环绕通知:@Around

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

1.环绕通知方法的定义格式:

  • public
  • 必须有一个返回值,推荐使用Object
  • 方法名称自定义
  • 方法有参数,固定的参数 ProceedingJoinPoint

2.特点

  • 在目标方法的前和后都能增强功能
  • 控制目标方法是否被调用执行
  • 修改原来的目标方法的执行结果,影响最后的调用结果
  • 它是功能最强的通知

3.环绕通知等同于jdk动态代理的InvocationHandler接口
4.参数:ProceedingJoinPoint 就等同于 Method,用于执行目标方法
5.返回值: 就是目标方法的执行结果,可以被修改
6.环绕通知:经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务

@Around(value = "execution(String *..do(..))")
    public Object myAround(ProceedingJoinPoint p) throws Throwable {
        Object result = null;
        //前置功能增强
        System.out.println("前置功能增强!!");

        //等同于method.invoke(); Object result = doFirst();
        result = p.proceed();

        //后置功能增强
        System.out.println("后置功能增强");

        return result;
    }
  • 测试类
    @Test
    public void demo01(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        SomeService proxy = (SomeService)app.getBean("Service3");
        String result = proxy.doFirst("Dick", 99);
        //输出返回结果
        System.out.println(result);
    }

2.3.7 异常通知:@AfterThrowing

  • 在目标方法抛出异常后执行
  • 该注解的 throwing 属性用于指定所发生的异常类对象
  • 被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象
  • 业务方法
    @Override
    public void doSecond() {
        System.out.println("执行业务方法doSecond()" + (10/0));
    }
  • 切面类

1.异常通知方法的定义格式:

  • 访问权限public
  • 没有返回值
  • 方法名称自定义
  • 方法有个一个Exception, 也可以使用JoinPoint

2.@AfterThrowing:异常通知

  • 属性:
    • value 切入点表达式
    • throwinng 自定义的变量,表示目标方法抛出的异常对象,变量名必须和方法的参数名一样
  • 特点:
    • 在目标方法抛出异常时执行的
    • 可以做异常的监控程序, 监控目标方法执行时是不是有异常,如果有异常,可以发送邮件,短信进行通知
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
            throwing = "ex")
    public void myAfterThrowing(Exception ex) {
        System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
        //发送邮件,短信,通知开发人员
    }

2.3.8 最终通知:@After

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

  • 业务方法
    @Override
    public void doThird() {
        System.out.println("执行业务方法doThird()");
    }
  • 切面类

1.最终通知的定义格式:

  • 访问权限public
  • 没有返回值
  • 方法名称自定义
  • 方法没有参数,但是可以使用JoinPoint

2.@After :最终通知特点

  • 总是会执行
  • 在目标方法之后执行
       //等同以下执行方式
       try{
           SomeServiceImpl.doThird(..)
       }catch(Exception e){
     
       }finally{
           myAfter()
       }
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public  void  myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }

2.3.9 @Pointcut 定义切入点

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

1.@Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。
2.特点:

  • 当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名
  • 其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
    @After(value = "mypt()")
    public  void  myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }

    @Before(value = "mypt()")
    public  void  myBefore(){
        System.out.println("前置通知,在目标方法之前先执行的");
    }

    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" )
    private void mypt(){
        //无需代码,
    }

2.4 代理方式更换

  • 如果目标类有接口,默认使用jdk动态代理,如果目标类没有接口,则使用CGlib动态代理
  • 如果想让具有接口的目标类使用CGlib的代理方式,需要以下配置文件
<aop:aspectj-autoproxy proxy-target-class="true"/>

三、spring整合MyBatis

  • 将 MyBatis 与 Spring 进行整合,主要解决的问题就是将SqlSessionFactory 对象交由 Spring来管理
  • 只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合

整合思路

  • 需要有需要有Dao接口的代理对象,例如studentDao需要一个他的代理对象,使用SqlSession.getMapper(StudentDao.class),得到dao代理对象
  • 需要有SqlSessionFactory,创建一个SqlSessionFactory对象,使用SqlSessionFactory.open()得到SqlSession对象
  • 数据源DataSource对象,使用连接池对象替换mybatis自己的PooledDataSource

3.1 maven依赖

  • maven依赖
   <dependencies>
    <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-tx</artifactId>
      <version>5.3.8</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.8</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.25</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.1</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.20</version>
    </dependency>
  </dependencies>


	<build>
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

3.2 实体类

  • 定义实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private String email;
    private Integer age;
}

3.3 Dao接口与mapper文件

  • Dao接口
public interface StudentDao {
    int insertStudent(Student student);

    List<Student> selectStudentList();
}
  • mapper文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jjh.dao.StudentDao">
    <insert id="insertStudent">
        insert into student values (#{id},#{name},#{email},#{age})
    </insert>

    <select id="selectStudentList" resultType="com.jjh.domain.entity.Student">
        select * from student order by id desc
    </select>
</mapper>

3.4 service接口与实现类

  • service接口
public interface StudentService {

    int addStudent(Student student);

    List<Student> queryAllStudents();
}
  • service实现类
@Service("myStudentService")
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentDao studentDao;

    @Override
    public int addStudent(Student student) {
        int result = studentDao.insertStudent(student);
        return result;
    }

    @Override
    public List<Student> queryAllStudents() {
        List<Student> students = studentDao.selectStudentList();
        return students;
    }
}

3.5 MyBatis主配置文件

  • 主配置文件中不再需要数据源的配置了,因为数据源要交给 Spring 容器来管理了
  • 这里对 mapper 映射文件的注册,使用<package/>标签,即只需给出 mapper 映射文件所在的包即可,因为 mapper 的名称与 Dao 接口名相同,可以使用这种简单注册方式。这种方式的好处是,若有多个映射文件,这里的配置也是不用改变的。当然,也可使用原来的<resource/>标签方式
<?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>

    <!--settings:控制mybatis全局行为-->
    <settings>
        <!--设置mybatis输出日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--设置别名-->
    <typeAliases>
        <!--实体类别名-->
        <!--
            package:把包下面所有类名作为别名
            name:实体类的包名
        -->
        <package name="com.jjh.domain.entity"/>
    </typeAliases>

    <!-- sql mapper(sql映射文件)的位置-->
    <mappers>
        <!--
            package:指定Dao接口包的位置,表示将包下面的sql映射文件找到
            name:Dao接口的包名
            使用package指定映射文件的要求:
            1.sql映射的文件名与Dao接口名一致
            2.sql映射文件和Dao接口在同一目录
        -->
        <package name="com.jjh.dao"/>
    </mappers>
</configuration>

3.6 spring的配置文件

  • jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis01?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
  • 该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。使用<context>标签
  • <context:property-placeholder/>标签中有一个属性 location,用于指定属性文件的位置
    在这里插入图片描述
  • 注册 SqlSessionFactoryBean
<!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
    SqlSessionFactory  sqlSessionFactory = new ..
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>
  • 定义 Mapper 扫描配置器 MapperScannerConfigurer

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

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.jjh.dao"/>
    </bean>

3.7 向service注入接口名

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

  • 全部配置文件
<context:component-scan base-package="com.jjh.service"/>

    <!--
        把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
        spring知道jdbc.properties文件的位置
    -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--声明数据源DataSource,作用是连接数据库-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="20"/>
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
    SqlSessionFactory  sqlSessionFactory = new ..
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.jjh.dao"/>
    </bean>
    
</beans>

四、Spring事务

4.1 概述

事务就是一些sql序列的集合, 是多条sql, 作为一个整体执行

  • 事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务
  • 在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
    • (1)使用 Spring 的事务注解管理事务
    • (2)使用 AspectJ 的 AOP 配置管理事务

4.2 spring的事务管理

  • 使用spring的事务管理器,管理不同数据库访问技术的事务处理。 开发人员只需要掌握spring的事务处理一个方案, 就可以实现使用不同数据库访问技术的事务管理
  • 管理事务面向的是spring, 有spring管理事务,做事务提交,事务回顾

4.3 Spring事务管理API

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

4.3.1 事务管理器接口

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

  • 常用的两个实现类
  • DataSourceTransactionManager:使用 JDBCMyBatis进行数据库
    操作时使用
  • HibernateTransactionManager:使用Hibernate 进行持久化数据时使
  • Spring的回滚方式
  • Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式

4.3.2 事务定义接口

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

4.3.2.1 五个事务隔离级别常量

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

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

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

  • 事务传播行为常量都是以 PROPAGATION_开头

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

1.PROPAGATION_REQUIRED

指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是Spring默认的事务传播行为

2.PROPAGATION_SUPPORTS

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

3.PROPAGATION_REQUIRES_NEW

  • 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕
  • 常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值