Spring学习(为了快速入门)

Spring

Spring快速入门

  • spring框架的概述,以及spring中基于XML的IOC配置

  • 1、spring的概述

    • 1.1、spring是什么
      • spring是分层的java SE/EE 应用 full-stack 轻量级开源框架,以IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核
    • 1.2、spring的两大核心
      • IOC
      • AOP
    • 1.3、spring的发展历程和优势
      • 方便解耦,简化开发
      • AOP编程的支持
      • 声明式事务的支持
      • 方便程序的测试
      • 方便集成各种优势框架
      • 降低JavaEE API 的使用难度
        • Spring对JavaEE API(JDBC、JavaMail、远程调用等)进行了薄薄的封装层
      • Java源码式经典学习范例
    • 1.4、spring体系结构
  • 2、程序的耦合及解耦

    • 程序的耦合
      • 耦合:程序间的依赖关系
        • 包括
          • 类之间的依赖
          • 方法间的依赖
      • 解耦:
        • 降低程序间的依赖关系
      • 实际开发中
        • 应该做到:编译器不依赖,运行时才依赖。
      • 解耦的思路:
        • 第一步:使用反射来创建对象,从而避免使用new关键字。
        • 第二部:通过读取配置文件来获取要创建的对象全限定类名
    • 2.1、曾经案例中问题
    • 2.2、工厂模式解耦
  • 3、IOC概念和spring中的IOC

    • 3.1、spring中基于XML的IOC环境搭建

      • <?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">
        
            <bean id="..." class="...">  
                <!--
        		该id属性是标识单个bean定义的字符串。
        		该class属性定义bean的类型,并使用完全限定的类名。
        -->
                <!-- collaborators and configuration for this bean go here -->
            </bean>
        
            <bean id="..." class="...">
                <!-- collaborators and configuration for this bean go here -->
            </bean>
        
            <!-- more bean definitions go here -->
        
        </beans>
        
    • IOC:对象由Spring来创建,管理,装配。

  • 4、依赖注入(Dependency Injection)

  • spring中基于注解的IOC 和ioc的案例‘

  • spring中的aop和基于XML以及注解的AOP配置

  • spring中的jdbcTemlate以及Spring事务控制

Spring能做什么?

​ 1、Spring能帮我们根据配置文件创建及组装对象之间的依赖关系。

​ 2、Spring面向切面编程能帮助我们无耦合的实现日志记录,性能统计,安全控制。

​ 3、Spring能非常简单的帮我们管理数据库事务

Spring框架结构

  • Data Access/Integration 层包含有JDBC、ORM、OXM、JMS和Transaction模块。
  • Web层包含了Web、Web-Servlet、WebSocket、Web-Porlet模块。
  • AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现。
  • Core Container(核心容器):包含有BEans、Core、Context、SpEl模块
  • Test模块支持使用JUnit和TestNG对Spring组件进行测试

Spring IoC 和 DI 简介

IoC:Inverse of Control (控制反转)

或者读作“反转控制”,是一种设计思想,将原本在程序中手动创建对象的控制器,交由Spring框架来管理。

  • 正控:使用某个对象,需要自己去负责对象的创建
  • 反控:使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给Spring框架,,交给Spring来创建对象

主动创造:

去奶茶店

总结:

  • 传统方式:

    ​ 通过new关键字主动创建对象

  • IOC方式:

    ​ 让Spring去创建对象管理,我们直接去Spring那里去获取对象,控制器交给Spring

DI:Dependency Injection(依赖注入)

  • Spring在创建对象的过程中,将对象依赖属性(简单值,集合,对象)通过配置设值给该对象

IOC如何实现的?

1、读取标注或者配置文件,看看 JuiceMaker依赖的是哪个Source ,拿到类名

2、使用反射的API,基于类名实例化对应的对象实例。

3、将对象实例,通过构造函数或者setter,传递给JuiceMaker

IOC升级版的工厂模式

Spring AOP简介

在数据库事务中切面编程被广泛使用

AOP:Aspect Oriented Program 面向切面编程

  • 在面向切面编程思想里,把功能划分为核心业务功能,和周边功能。

  • 核心业务:登录、增加数据、删除数据。。。。

  • 周边功能:性能统计、日志、事务管理等等。。

周边功能在 Spring 的面向切面编程AOP思想里被定义为切面。

核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能“编织”在一起,这就叫AOP

AOP的目的?

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(事务控制,日志管理,权限控制)封装起来,便于减少系统的重复代码,降低模块间的耦合,有利于未来的可扩展性和可维护性

AOP概念:

  • 切入点(Pointcut)

    ​ 在哪些类,哪些方法上切入(where)

  • 通知(Advice)

    ​ 在方法执行的什么时机(where:方法前/方法后/方法前后)做什么(what:增强的功能)

  • 切面(Aspect)

    ​ 切面 = 切入点 + 通知,简言之:在什么时机,什么地方,做什么增强

  • 织入(Weaving)

    ​ 把切面加入到对象,并创建出代理对象的过程(由Spring完成)

AOP入门概念代码

package com.yuanwu.service;

/**
 * @Author YuanWu
 * @ClassName ProductService
 * @Date 2020/8/7
 **/
public class ProductService {
    public void doSomeService(){
        System.out.println("doSomeService");
    }
}

在xml文件中装配bean

<bean name="productService" class="com.yuanwu.service.ProductService"/>

测试

package com.yuanwu.Test;

import com.yuanwu.service.ProductService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author YuanWu
 * @ClassName TeseSpring
 * @Date 2020/8/7
 **/
public class TestSpring {

    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        ProductService ps = (ProductService) context.getBean("productService");
        ps.doSomeService();
    }
}

日志切面

package com.yuanwu.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
/**
 * @Author YuanWu
 * @ClassName LoggerAspect
 * @Date 2020/8/7
 **/
public class LoggerAspect {

    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return  object;
    }
}

在xml文件中声明业务对象和日志切面

<?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">-->
<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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean name="productService" class="com.yuanwu.service.ProductService"/>
    <bean name="loggerAspect" class="com.yuanwu.aspect.LoggerAspect" />

    <!-- 配置AOP -->
    <aop:config>
<!--        where: 在那些地方(包.类.方法)做增加-->
        <aop:pointcut id="loggerCutpoint" expression="execution(* com.yuanwu.service.ProductService.*(..))"/>
<!--        what: 做什么增强-->
        <aop:aspect id="logAspect" ref="loggerAspect">
<!--            when: 在什么时机(方法前/方法后)-->
            <aop:around pointcut-ref="loggerCutpoint" method="log"/>
        </aop:aspect>
    </aop:config>
</beans>

再次测试

start log:doSomeService
doSomeService
end log:doSomeService

Spring IOC详解

Spring IOC 容器的设计

Spring IOC 容器的设计主要是基于两个接口

  • BeanFactory
  • ApplicationContext

ApplicationContext 是 BeanFactory的子接口之一

BeanFactory

位于设计的 最底层,提供了Spring IOC 最顶层的设计

  • 【getBean】对应了多个方法来获取配置给Spring IOC容器的bean

    ​ 1、按照类型拿bean:

    bean = (Bean)factory.getBean(Bean.class);
    

    要求在Spring中只配置了一个这种类型的实例,否则报错

    ​ 2、按照bean的名字拿bean:

    bean = (Bean)factory.getBean("beanName");
    

    这种方法不太安全

    ​ 3、按照名字和类型拿bean:推荐

    bean = (Bean)factory.getBean("beanName",Bean.class);
    

ApplicationContext

常见实现类:

1、 ClassPathXmlApplicationContext

读取classpath中的资源

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

2、 FileSystemXmlApplicationContext

读取指定路径的资源

ApplicationContext ac = new FileSystemXmlApplicationContext("c:/applicationContext.xml");

3、 XmlWebApplicationContext

需要在Web环境下才可以运行

XmlWebApplicationContext ac = new XmlWebApplicationContext(); // 这时并没有初始化容器
ac.setServletContext(servletContext); // 需要指定ServletContext对象
ac.setConfigLocation("/WEB-INF/applicationContext.xml"); // 指定配置文件路径,开头的斜线表示Web应用的根目录
ac.refresh(); // 初始化容器


Spring IOC 的容器初始化和依赖注入

Bean的定义 和 初始化 在Spring IOC 容器中是两大步骤,先定义,然后初始化和依赖注入

  • Bean的定义分三步:

    1、Resource定位:

    Spring IOC容器先根据开发者的配置,进行资源的定位,在Spring开发中,通过XML或者注解都是常见的方式,定位的内容由开发者提供。

    2、BeanDefinition的载入:

    这个时候只是将Resource定位到的信息,保存到Bean定义(BeanDefinition)中,此时并不会创建Bean的实例。

    3、BeanDefinition的注册

    这个过程就是将BeanDefinition的信息发布到Spring IOC 容器中

    但是此时仍然没有对应的Bean实例

对于初始化和依赖注入,Spring Bean还有一个配置选项——【lazy-init】,其含义为是否初始化Spring Bean。在没有任何配置的情况下,它是默认值default,false,即Spring IOC 默认会自动初始化Bean,如果设置为true,则只有当我们使用Spring IOC 容器的getBean方法获取它时,这时才会进行Bean初始化,完成依赖注入

Spring Bean 详解

方式选择原则

1、最优先:通过隐式Bean 的发现机制和自动装配的原则。

​ 基于约定大于配置的原则,这种方式应该是最优先的。

  • 优点:减少程序开发者的决定权,简单,灵活

2、其次:Java 接口和类中配置实现配置

  • 优点:避免XML配置的泛滥,更为容易

3、XML方式配置

  • 对于初学者易懂

装配简易值

<bean id="c" class="pojo.Category">
    <property name="name" value="测试" />
</bean>

  • id:id属性是Spring能找到当前Bean的一个依赖的编号,唯一标识符
  • class:class属性是一个类的全限定类名
  • property:property元素是定义类的属性,name是定义属性的名称,value是值

例子::

<!-- 配置 srouce 原料 -->
<bean name="source" class="pojo.Source">
    <property name="fruit" value="橙子"/>
    <property name="sugar" value="多糖"/>
    <property name="size" value="超大杯"/>
</bean>

<bean name="juickMaker" class="pojo.JuiceMaker">
    <!-- 注入上面配置的id为srouce的Srouce对象 -->
    <property name="source" ref="source"/>
</bean>

  • 注入对象:使用ref属性

..............................................未完待续


面向切面编程【AOP模块】

AOP的一个思想:让关注点代码与业务代码分离

使用注解来开发 Spring AOP

  • 🎃 第一步:选择连接点

    ​ Spring是方法级别的AOP框架,选择哪一个类的哪一个方法用以增强功能

    package com.yuanwu.pojo;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @Author YuanWu
     * @ClassName Landlord
     * @Date 2020/8/8
     **/
    @Component("landlord")
    public class Landlord {
        public void service(){
            System.out.println("签合同");
            System.out.println("收房租");
        }
    }
    

    选择Landlord类中的service()方法作为连接点

    配置xml

    在bean.xml中配置自动注入,并告诉Spring IOC容器去哪里扫描这两个Bean:

     <?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"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        
    	<context:component-scan base-package="com.yuanwu.aspect"/>
        <context:component-scan base-package="com.yuanwu.pojo"/>
    
        <aop:aspectj-autoproxy/>
        
    </beans>
    
    
  • 🎃 第二步:创建切面

在Spring 中只需要使用 @Aspect 注解类,Spring IOC容器就会认为这是一个切面

package com.yuanwu.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Author YuanWu
 * @ClassName Broker
 * @Date 2020/8/8
 **/
@Component
@Aspect
public class Broker {

    @Before("execution(* com.yuanwu.pojo.Landlord.service())")
    public void before(){
        System.out.println("带租客看房子");
        System.out.println("谈价格");
    }

    @After("execution(* com.yuanwu.pojo.Landlord.service())")
    public void after(){
        System.out.println("交钥匙");
    }
}

  • 这里需要注意一下: 被定义为切面的类仍然是一个Bean,需要 @Component 注解标注

Spring 中的AspectJ注解:

注解 说明
@Before 前置通知,在连接点方法前调用
@Around 环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法
@After 后置通知,在连接点方法后调用
@AfterReturning 返货通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常
@AfterThrowing 异常通知,当连接点方法异常时调用

注解中间使用了定义切点的正则式,告诉SpringAOP需要拦截说明对象的说明方法

  • 🎃第三步:定义切点

    Spring通过这个正则表达式判断具体要拦截的是哪一个类的哪一个方法

execution(* com.yuanwu.pojo.Landlord.service())

  • execution :代表执行方法的时候会触发
  • ***** : 代表任意返回类型的反法
  • com.yuanwu.pojo.Landlord : 代表全限定类名
  • service() :被拦截的方法名称

可以使用 @Pointcut 注解来定义一个切点,来避免很多重复的表达式

package com.yuanwu.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Author YuanWu
 * @ClassName Broker
 * @Date 2020/8/8
 **/
@Component
@Aspect
public class Broker {

    @Pointcut("execution(* com.yuanwu.pojo.Landlord.service())")
    public void lService(){
       
    }
    
    //@Before("execution(* com.yuanwu.pojo.Landlord.service())")
    @Before("lService()")
    public void before(){
        System.out.println("带租客看房子");
        System.out.println("谈价格");
    }

    //@After("execution(* com.yuanwu.pojo.Landlord.service())")
    @After("lService()")
    public void after(){
        System.out.println("交钥匙");
    }
}
  • 🎃第四步:测试

  • 带租客看房子
    谈价格
    签合同
    收房租
    交钥匙
    

🥨 环绕通知

Spring AOP 中最强大的通知,因为它集成了前置和后置通知,保留了连接点原有方法的功能

使用 @Around 注解来同时完成前置和后置通知

@Around("execution(* com.yuanwu.pojo.Landlord.service())")
    public void around(ProceedingJoinPoint joinPoint){
        System.out.println("带租客看房子");
        System.out.println("谈价格");

        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        System.out.println("交钥匙");
    }

使用XML配置开发SpringIOC

AOP 配置元素 用途 备注
aop:advisor 定义 AOP 的通知 一种很古老的方式,很少使用
aop:aspect 定义一个切面 ——
aop:before 定义前置通知 ——
aop:after 定义后置通知 ——
aop:around 定义环绕通知 ——
aop:after-returning 定义返回通知 ——
aop:after-throwing 定义异常通知 ——
aop:config 顶层的 AOP 配置元素 AOP 的配置是以它为开始的
aop:declare-parents 给通知引入新的额外接口,增强功能 ——
aop:pointcut 定义切点 ——
<!-- 装配Bean -->
<bean name="productService" class="com.yuanwu.service.ProductService"/>
<bean name="loggerAspect" class="com.yuanwu.aspect.LoggerAspect" />

    <!-- 配置AOP -->
    <aop:config>
<!--        where: 在那些地方(包.类.方法)做增加-->
        <aop:pointcut id="loggerCutpoint" expression="execution(* com.yuanwu.service.ProductService.*(..))"/>
<!--        what: 做什么增强-->
        <aop:aspect id="logAspect" ref="loggerAspect">
<!--            when: 在什么时机(方法前/方法后)-->
            <aop:around pointcut-ref="loggerCutpoint" method="log"/>
        </aop:aspect>
    </aop:config>

在Spring 中使用 AspectJ注解支持

1、导入jar 包

头文件

<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"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task-3.1.xsd">
    
    <!-- 组件扫描 -->
    <context:component-scan base-package="包名"></context:component-scan>
    
    <!-- 基于注解使用AspectJ 	主要作用是为切面中通知能作用到目标类生成代理-->
    <aop:aspectJ-autoproxy/>
    
</beans>

定义一个日志切面类

前置通知

目标方法执行之前执行

@Component //标识为一个组件
@Aspect		//标识为一个切面
public class LoggingAspect{
    @Before("execution(public int 全类名.方法名(参数类型,参数类型))")
    public void beforeMethod(){
        System.out.println("日志记录");
    }
}

后置通知

目标方法执行之后执行;不管目标方法有没有抛出异常都会执行不能获取方法的结果

execution(* com.yuanwu.anntion.类名.*(..))

*:任意修饰符

*:任意类

*:任意方法

..:任意参数

获取方法名字

连接点对象 Joinpoint

getSignature()===》方法签名

public void afterMethod(Joinpoint joinpoinr){

​ String methodName = joinpoint.getSignature().getName();

​ System.out.println("日志记录");
}

@After("execution(* 全限定类名.*(..))")
public void afterMethod(){
    System.out.println("日志记录");
}

返回通知

在目标方法正常执行结束后执行;可以获取到返回值

@AfterReturning(value="execution(* 全限定类名.*(..))" ,returning="result")
												//指定名字必须与参数名一致
public void afterRetueningMethod(Object result){
    System.out.println("日志记录" + result);
}

异常通知

在目标方法抛出异常后执行

可以通过形参异常的类型,去设置抛出指定异常,才执行异常通知;比如指定空指针异常,才会执行异常通知

public void afterThrowingMethod(NullPointerExecption EX){
    System.out.println("日志记录" + EX);
}
@AfterThrowing(value="execution(* 全限定类名.*(..))" , throwing="EX")
												//指定名字必须与参数名一致 
public void afterThrowingMethod(Execption EX){
    System.out.println("日志记录" + EX);
}

环绕通知

环绕目标方法执行,可以理解为:前置后置返回异常,四个通知的组合体,更像是动态代理的整个过程

@Around("execution(* 全限定类名.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjp){
    try{
        //前置通知
        ...
    	//执行目标方法
    	pjp.proceed();
        //返回通知
        ...
    } catch(Throwable e){
        //异常通知
        ...
        e.printStackTrace();
    }finally {
        //后置通知
        ...
    }
    return null;
}

@Oreder();

优先级

==重用切入点表达式==

Spring 事务管理

事务的概念:

以转账为例,必定要有两个操作:先将A账户的金额减去要转账的数目,然后将B账户加上响应的金额数目。这两个操作必定要全部完成,才表示当前转账操作成功。若有任何一方失败,则另一方必须回滚(即全部失败)。即事务:这组操作是不可分割的,要么全部成功,要么全部失败。

所谓事务管理,就是“按照给定的事务规则来执行提交或者回滚操作”;

事务的特性:

具有ACID四个特性:

原子性(Atomicity):事务是不可分割的工作单位,要么都发生,要么都不发生。

一致性(Consistency):事务在完成后数据的完整性必须保持一致。

隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发事务之间的数据要相互隔离;

持久性(Durability):一个事务被提交,它对数据库数据的改变应该是永久性的,即使数据库发生故障也不应该对其有任何影响;

Spring 事务管理接口:

三个高层抽象接口:PlatformTransactionManager,TransactionDefinition,TransactionStatus

1、PlatformTransactionManager事务管理器

org.springframework.transaction.PlatformTransactionManager 事务管理器接口;事实上,Spring 框架并不直接管理事务,而是通过这个接口为不同的持久层框架提供了不同的PlatformTransactionManager接口实现类,将事务管理的职责 委托给Hibernate 或者iBatis等持久化框架的事务来实现;

PlatformTransactionManager源码:

public interface PlatformTransactionManager {
    //事务管理器通过TransactionStatus获得"事务状态",从而管理事务
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    //根据状态提交
    void commit(TransactionStatus status) throws TransactionException;
    //根据状态回滚
    void rollback(TransactionStatus status) throws TransactionException;
}

在使用JDBCC或者MyBatis进行数据持久化操作时,xml配置通常:

    <!-- 事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

2、TransactionDefinition 定义事务基本属性

包: org.springframework.transaction.TransactionDefinition ;用于定义一个事务。

它定义了 Spring 事务管理的五大属性:隔离级别、传播行为、是否只读、事务超时、回滚规则

2.1、隔离级别

隔离级别是用来描述并发事务之间隔离程度的大小;

脏读: 一个事务读到了另一个事务的未提交的数据;

不可重复读: 一个事务读到了另一个事务已经提交的 update 的数据,导致多次查询结果不一致;

幻读: 一个事务读到了另一个事务已经提交的 insert 的数据,导致多次查询结果不一致;

Spring 事务管理定义的隔离级别::

ISOLATION_DEFAULT:使用数据库默认的隔离级别;

ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读;

ISOLATION_READ_COMMITTED:允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生;

ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生;

ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读、以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的;

2.2、传播行为

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

Spring 事务管理传播机制规定了事务方法和事务方法 发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间发生调用时的事务上下文的规则;

Spring 定义了七种传播行为:方法之间发生嵌套调用时如何传播事务?

PROPAGATION_REQUIRED: A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务;

PROPAGATION_SUPPORTS: A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行;

PROPAGATION_MANDATORY: A如果有事务,B将使用该事务;如果A没有事务,B将抛出异常;

PROPAGATION_REQUIRES_NEW: A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务;

PROPAGATION_NOT_SUPPORTED: A如果有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行;

PROPAGATION_NEVER:A如果有事务,B将抛出异常;A如果没有事务,B将以非事务执行;

PROPAGATION_NESTED:A和B底层采用保存点机制,形成嵌套事务;

2.3、是否只读

如果将事务设置为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务;

2.4、事务超时

事务超时就是事务的一个定时器,在特定的时间内事务如果没有执行完毕,就会自动回滚,而不是一致等待其结束。在TransactionDefinition 中以 int 的值表示超时时机,默认值是 -1 ,单位秒

2.5、回滚规则

回滚规则定义了哪些异常会导致事务回滚或哪些不会。默认情况下,事务只有遇到运行期异常才会回滚;

3、TransactionStatus事务状态

org.springframework.transaction.TransactionStatus 接口是用来记录事务的状态,用来获取或判断事务的相应状态信息:

TransactionStatus源码:

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();		//是否是新的事务
    boolean hasSavepoint();		//是否有回复点
    void setRollbackOnly();		//设置为只回滚
    boolean isRollbackOnly();		//是否为只回滚
    void flush();		//刷新
    boolean isCompleted();		//是否已完成
}

Spring 事务管理实现方式

Spring 事务管理有两种方式:编程式事务管理;声明式事务管理;

编程式事务管理:通过TransactionTemplate 手动管理事务;

声明式事务管理:基于TransactionProxyFactoryBean的方式、基于Aspect的XML方式、基于注解(@Transactional)的方式;代码侵入性最小,实际是通过AOP实现的;(推荐使用)

配置事务管理器

数据源 让 事务管理器 进行管理

ref="dataSource" ==>>引用数据源 id名字

<!-- 事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transactionManager-manager="dataSourceTransactionManager"/>

xml方式配置事务

<!-- 基于xml配置事务管理, -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="toAdvice">
	<!-- 配置事务属性 -->
    <tx:attributes>
    	<!-- 具体方法使用的事务属性 -->
        <tx:method name="方法名" ...事务属性/>
        
        <!-- 约定方法的名字 -->
        <!-- select开头的方法 -->
        <tx:method name="select*"/>
        
        <!-- 上述指定之外的所有方法 -->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<aop:config>
	<!-- 切入点表达式 -->
    <aop:pointcut expression="executiom(* 全限定类名.*.*(..))" id="toPointCut"/>
    <aop:advisor advice-ref="toAdvice" pointcut-ref="toPointCut"/>
</aop:config>

动态代理

动态代理原理:

代理设计模式原理: 使用一个代理类将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。

动态代理的方式:

基于接口实现动态代理:JDK动态代理

基于继承实现动态代理:Cglib、javassist动态代理

1、接口:ArithmeticCalculator

package com.yuanwu.spring.before;

public interface ArithmeticCalculator {
    int add(int x, int y);
    int sub(int x, int y);
    int nub(int x, int y);
    int del(int x, int y);

}

2、接口实现类:AritheticCalculatorImpl

package com.yuanwu.spring.before;

/**
 * @Author YuanWu
 * @ClassName AritheticCalculatorImpl
 * @Date 2020/9/28
 **/
public class AritheticCalculatorImpl implements ArithmeticCalculator{
    @Override
    public int add(int x, int y) {
        return x + y;
    }

    @Override
    public int sub(int x, int y) {
        return x - y;
    }

    @Override
    public int nub(int x, int y) {
        return x * y;
    }

    @Override
    public int del(int x, int y) {
        return x / y;
    }
}

3、AritheticCalculatorImpl 代理类

package com.yuanwu.spring.before;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * @Author YuanWu
 * @ClassName ArithmeticCalculatorProxy
 * @Date 2020/9/28
 *
 * 生成代理对象。
 *
 * JDK动态代理
 *      1、Proxy :是所有动态代理类的父类,专门用于生成代理类或者是代理对象
 *          1、用于生成代理类的Class对象
 *          public static Class<?> getProxyClass(ClassLoader loader,
 *                                          Class<?>... interfaces)
 *
 *          2、用于生成代理对象
 *          public static Object newProxyInstance(ClassLoader loader,
 *                                           Class<?>[] interfaces,
 *                                           InvocationHandler h)
 *      2、InvocationHandler :完成动态代理的整个过程
 *          public Object invoke(Object proxy, Method method, Object[] args)
 *                  throws Throwable;
 **/
public class ArithmeticCalculatorProxy {
    //动态代理 :目标对象(是谁)    如何获取代理对象    代理要做什么

    //目标对象
    private ArithmeticCalculator target;

    public ArithmeticCalculatorProxy(ArithmeticCalculator target) {
        this.target = target ;
    }

    //获取代理对象的方法
    public Object getProxy(){

        //代理对象

        /**
         * loader: ClassLoader对象,类加载器。帮我们加载动态生成的代理类
         *
         * interfaces: 多个接口。提供目标对象的所有接口,
         *                 目的是让代理对象保证与目标对象都有接口中相同的方法。
         *
         * h:InvocationHandler类型的对象
         * */
        ClassLoader loader = target.getClass().getClassLoader();

        Class<?>[] interfaces = target.getClass().getInterfaces();

        //返回代理类的一个实例,返回的代理类可以当作被代理类使用
        Object proxy = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {

            /**
             * invoke : 代理对象调用代理方法,会回来调用invoke方法。
             *
             * proxy : 代理对象,在invoke方法中一般不会使用
             * method: 正在被调用的方法对象
             * args : 正在被调用的方法参数
             * */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //将方法的调用转回到目标对象上。

                String methodName = method.getName();
                System.out.println("LoggingProxy==>" + methodName + "begin with" + Arrays.asList(args));

                //目标对象执行目标方法,相当于执行ArithmeticCalculator - + * / 方法
                Object result = method.invoke(target, args);

                System.out.println("LoggingProxy==>" + methodName + "ends with" + Arrays.asList(args));
                return result;
            }
        });
        return proxy ;
    }
}

JDK动态代理的一般实现步骤如下:

(1)创建一个实现InvocationHandler接口的类,它必须实现invoke方法

(2)创建被代理的类以及接口

(3)调用Proxy的静态方法newProxyInstance,创建一个代理类

(4)通过代理调用方法

动态代理具体步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
posted @ 2020-10-26 23:37  san只松鼠  阅读(76)  评论(0)    收藏  举报