Java学习笔记_Spring

5 篇文章 0 订阅
3 篇文章 0 订阅

Java学习笔记_Spring

1. spring 概述

1.1 spring 是什么 ?

Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

1.2 spring 的优势

  • 方便解耦,简化开发
    通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
  • AOP 编程的支持
    通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
  • 声明式事务的支持
    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
  • 方便程序的测试
    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
  • 方便集成各种优秀框架
    Spring 可以降低各种框架的使用难度,提供了对各种优秀框架( Struts、 Hibernate、 Hessian、 Quartz
    等)的直接支持。
  • 降低 JavaEE API 的使用难度
    Spring 对 JavaEE API(如 JDBC、 JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
  • Java 源码是经典学习范例
    Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。

1.3 spring 体系结构spring 体系结构

2. IoC 的概念和作用

2.1 程序的耦合和解耦

	耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。 耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
	在软件工程中, 耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。 划分模块的一个准则就是高内聚低耦合。

它有如下分类:
	(1) 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
	(2) 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
	(3) 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
	(4) 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
	(5) 标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
	(6) 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
	(7) 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
总结:
	耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚与耦合:
	内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。 内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
	内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。  

2.2 解耦思想

解耦即降低不同类之间的关联性

  • 通过反射的方式解耦

    如:

    jdbc通过反射来注册驱动的,代码如下:Class.forName("com.mysql.jdbc.Driver");

    com.mysql.jdbc.Driver在这里只是一个字符串

  • 工厂模式解耦

    在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的。方法通过读取配置文件(xml,properties),把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件, 创建和获取三层对象的类就是工厂。

2.3 IoC 控制反转

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的实现方式:

  • 依赖注入(Dependency Injection,简称DI

    容器提供回调接口和上下文条件给组件。EJB和Apache Avalon 都使用这种方式。这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回调方法上:容器将调用这些回调方法,从而让应用代码获得相关资源。

  • 依赖查找(Dependency Lookup)。

    组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection)

2.4 IoC接口实现的细节

  • BeanFactory 和 ApplicationContext 的区别 :

    BeanFactory 才是 Spring 容器中的顶层接口。ApplicationContext 是它的子接口。

    区别:

    • 创建对象的时间点不一样。
    • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象(立即加载)。
    • BeanFactory:什么使用什么时候创建对象。
  • ApplicationContext 接口的实现类 :

    ClassPathXmlApplicationContext:从类的根路径下加载配置文件 (推荐使用)

    FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

    AnnotationConfigApplicationContext: 用于读取注解,创建 spring 容器。

2.5 IoC 中 bean 标签细节

2.5.1 实例化 Bean 的三种方式

第一种方式:使用默认无参构造函数

<!-- 
	在默认情况下: 
		默认构造函数创建,使用无参构造方式构造。
        默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
-->
<bean id="userService" class="com.cn.fate.impl.UserServiceImpl"/>  

第二种方式: spring 管理静态工厂-使用静态工厂的方法创建对象

/**
* 模拟一个静态工厂,创建业务层实现类
*/
public class StaticFactory {
    public static userService createUserService(){
        return new userServiceImpl();
    }
}
<!-- 
    此种方式是:
    使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
    id 属性:指定 bean 的 id,用于从容器中获取
    class 属性:指定静态工厂的全限定类名
    factory-method 属性:指定生产对象的静态方法
-->
<bean id="userService"
      class="cn.fate.factory.StaticFactory"
      factory-method="createUserService">
</bean>  

第三种方式: spring 管理实例工厂-使用实例工厂的方法创建对象

/**
* 模拟一个实例工厂,创建业务层实现类
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
*/
public class InstanceFactory {
    public UserService createUserService(){
        return new UserServiceImpl();
    }	
}
<!-- 
	此种方式是:
        先把工厂的创建交给 spring 来管理。
        然后在使用工厂的 bean 来调用里面的方法
        factory-bean 属性:用于指定实例工厂 bean 的 id。
        factory-method 属性:用于指定实例工厂中创建对象的方法。
-->
<bean id="instancFactory" class="cn.fate.factory.InstanceFactory"></bean>
<bean id="userService"
      factory-bean="instancFactory"
      factory-method="createUserService">
</bean>  
2.5.2 bean标签属性
  • id: 给对象在容器中提供一个唯一标识。用于获取对象。

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

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

    • singleton : 默认值,单例的.、
    • prototype : 多例的
    • request : WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
    • session : WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
    • global session: 作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
  • init-method: 指定类中的初始化方法名称。

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

2.5.3 bean 的作用范围和生命周期
  • 单例对象: scope=“singleton”

    一个应用只有一个对象的实例。它的作用范围就是整个引用。

    生命周期:

    • 对象出生:当应用加载,创建容器时,对象就被创建了。
    • 对象活着:只要容器在,对象一直活着。
    • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
  • 多例对象: scope=“prototype”

    每次访问对象时,都会重新创建对象实例。

    生命周期:

    • 对象出生:当使用对象时,创建新的对象实例。
    • 对象活着:只要对象在使用中,就一直活着。
    • 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。

2.6 spring 的依赖注入

  • 概念

    依赖注入: Dependency Injection。 它是 spring 框架核心 ioc 的具体实现。我们的程序在编写时, 通过控制反转, 把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。ioc 解耦只是降低他们的依赖关系,但不会消除。 例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系, 在使用 spring 之后, 就让 spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取 。

  • 依赖注入: Dependency Injection

  • IOC的作用:

    • 降低程序间的耦合(依赖关系)
  • 依赖关系的管理:

    • 以后都交给spring来维护在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
  • 依赖关系的维护:就称之为依赖注入。

  • 能注入的数据:有三类

    • 基本类型和String(直接用value)
    • 其他bean类型(在配置文件中或者注解配置过的bean)
    • 复杂类型/集合类型
  • 注入的方式:有三种

    • 第一种:使用构造函数提供
    • 第二种:使用set方法提供
    • 第三种:使用注解提供
2.6.1 构造函数注入
  • 使用的标签:constructor-arg,标签出现的位置:bean标签的内部

  • 标签中的属性()

    • type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
    • index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
    • name:用于指定给构造函数中指定名称的参数赋值 (常用)
    • value:用于提供基本类型和String类型的数据
    • ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
  • 优势:

    • 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
  • 弊端:

    • 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
2.6.2 set方法注入
  • 涉及的标签:property,出现的位置:bean标签的内部,相比构造函数的注入额方式,set注入方式更为常用。

  • 标签的属性

    • name:用于指定注入时所调用的set方法名称
    • value:用于提供基本类型和String类型的数据
    • ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
  • 优势:

    • 创建对象时没有明确的限制,可以直接使用默认构造函数
  • 弊端:

    • 如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
2.6.3 集合类型的数据注入
  • 用于给List结构集合注入的标签:
    • list
    • array
    • set
  • 用于个Map结构集合注入的标签:
    • map
    • props

2.7 Spring 注解配置IoC

2.7.1 用于创建对象

作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的。

@Component:

  • 属性:
    • value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。

@Controller:一般用在表现层

@Service:一般用在业务层

@Repository:一般用在持久层

以上三个注解他们的作用和属性与Component是一模一样。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰.

2.7.2 用于注入数据

作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的。

@Autowired:

  • 作用:自动按照类型注入

    • 只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功

    • 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。

    • 如果Ioc容器中有多个类型匹配时: 如果重复,则会报错。但可以配合@Qualifier注解使用。或者单独是用@Resource注解。

  • 出现位置:可以是变量上,也可以是方法上

  • 细节:在使用注解注入时,set方法就不是必须的了。

    @Autowired
    private UserDao userDao1 = null;

@Qualifier:

  • 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以。
  • 属性:value:用于指定注入bean 的 id。
    @Autowired
    @Qualifier("userDao0")
    private UserDao userDao = null;

@Resource:

  • 作用:直接按照bean的id注入。它可以独立使用。
  • 属性:
    • name:用于指定bean的id。
@Resource("userDao0")
private UserDao userDao = null;

以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。

@Value:

  • 作用:用于注入基本类型和String类型的数据
  • 属性:
    • value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
    • SpEL的写法:${ 表达式 }
2.7.3 用于改变作用范围

他们的作用就和在bean标签中使用scope属性实现的功能是一样的
@Scope:

  • 作用:用于指定bean的作用范围
  • 属性:
    • value:指定范围的取值。
      • singleton 单列
      • prototype 多列
2.7.4 生命周期相关的注解

他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的

@PreDestroy:

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

@PostConstruct:

  • 作用:用于指定初始化方法
2.7.5 用于指定配置类的注解

@Configuration :

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

@ComponentScan :

  • 作用:
    • 用于指定 spring 在初始化容器时要扫描的包。 作用等同于 spring 的 xml 配置文件中的:
      <context:component-scan base-package="com.itheima"/>
    • 属性:
      • basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
@Configuration
@ComponentScan(basePackages = {"cn.fate"}) // 指定创建容器时扫描的包
public class SpringConfiguration {

}

@Bean:

  • 作用:

    • 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
  • 属性:

    • name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
@Configuration
public class JdbcConfig {
    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return (new QueryRunner(dataSource));
    }

    @Bean(name = "dataSource")
    public DataSource getDataSource() {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring_test");
        dataSource.setUser("root");
        dataSource.setPassword("fate");
        return dataSource;
    }
}

@PropertySource

  • 作用
    • 用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到
      properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
  • 属性:
    • value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

@Import

  • 作用:
    • 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。 当然,写上也没问
      题。
  • 属性:
    • value[]:用于指定其他配置类的字节码。
@Configuration
@ComponentScan(basePackages = {"cn.fate"}) // 指定创建容器时扫描的包
@Import({JdbcConfig.class})
public class SpringConfiguration {

}

2.8 Spring 整合 Junit

问题描述:

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);

IAccountService as = ac.getBean(“accountService”,IAccountService.class);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉

解决思路分析 :

​ 针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就无须手动创建了,问题也就解决了。我们都知道, junit 单元测试的原理(在 web 阶段课程中讲过),但显然, junit 是无法实现的,因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在, junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了。

Spring整合junit的配置:

  1. 导入spring整合junit的jar(坐标)
  2. 使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
  • @Runwith
  1. 告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
  • @ContextConfiguration
    • locations:指定xml文件的位置,加上classpath关键字,表示在类路径
    • classes:指定注解类所在地位置

注:当使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上

import cn.fate.domain.Account;
import cn.fate.service.AccountService;
import config.SpringConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
@ContextConfiguration(location: bean.xml)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;


    @Test
    public void TestFindById() {
        int id = 1;
        Account account = accountService.findAccountById(id);
        System.out.println(account);

    }


    @Test
    public void testTransfer() {
        String sourceName = "aaa";
        String targetName = "bbb";
        Float money = (float) 20;
        accountService.transfer(sourceName, targetName, money);

    }

}

3. spring中的动态代理

3.1 动态代理

  • 特点:字节码随用随创建,随用随加载

  • 作用:不修改源码的基础上对方法增强

  • 分类:

    • 基于接口的动态代理

      • 涉及的类:Proxy

      • 提供者:JDK官方

      • 如何创建代理对象:

        • 使用Proxy类中的newProxyInstance方法
  • 基于子类的动态代理

    • 涉及的类:Enhancer
    • 提供者:cglib第三方库

3.2 基于接口的动态代理

  • 涉及的类:Proxy

  • 提供者:JDK官方

  • 如何创建代理对象:

    • 使用Proxy类中的newProxyInstance方法
  • 创建代理对象的要求:

    • 被代理类最少实现一个接口,如果没有则不能使用
  • newProxyInstance方法的参数:

    • ClassLoader:类加载器

      • 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
    • Class[]:字节码数组

      • 它是用于让代理对象和被代理对象有相同方法。固定写法。
    • InvocationHandler:用于提供增强的代码

      • 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
      • 此接口的实现类都是谁用谁写。

实列:经销商和厂家的售卖过程

/**
 * 对生产厂家要求的接口
 */
public interface Producer {

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money);

    /**
     * 售后
     * @param money
     */
    public void afterService(float money);
}
/**
 * 一个生产者
 */
public class ProducerImpl implements Producer{

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 模拟一个消费者
 */
public class Client {

    public static void main(String[] args) {
        final ProducerImpl producer = new ProducerImpl();

        Producer proxyProducer = (Producer) Proxy.newProxyInstance(
                producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     *
                     * @param proxy  代理对象的引用
                     * @param method 当前执行的方法
                     * @param args   当前执行方法所需的参数
                     * @return 被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy,
                                         Method method,
                                         Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
                        //1.获取方法执行的参数
                        Float money = (Float) args[0];
                        //2.判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money * 0.8f);
                        }
                        return returnValue;
                    }
                });

        proxyProducer.saleProduct(10000f);
    }
}

3.2 基于子类的动态代理

  • 涉及的类:Enhancer

  • 提供者:cglib第三方库

  • 如何创建代理对象:

    • 使用Enhancer类中的create方法
  • 创建代理对象的要求:

    • 被代理类不能是最终类
  • newProxyInstance方法的参数:

    • Class:字节码

      • 它是用于指定被代理对象的字节码。
    • Callback:用于提供增强的代码 (通常使用他的借口实现类 MethodInterceptor )

实列:经销商和厂家的售卖过程

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 模拟一个消费者
 */
public class Client {

    public static void main(String[] args) {
        final ProducerImpl producer = new ProducerImpl();


        ProducerImpl cglibProducer = (ProducerImpl) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 作用:执行被代理对象的任何接口方法都会经过该方法
             * 方法参数的含义
             *
             * @param proxy  代理对象的引用
             * @param method 当前执行的方法
             * @param args   当前执行方法所需的参数
             * @return 被代理对象方法有相同的返回值
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float) args[0];
                //2.判断当前方法是不是销售
                if ("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;
            }

        });

        cglibProducer.saleProduct(12000f);
    }
}

4. AOP

4.1 什么是AOP

AOP: 全称是 Aspect Oriented Programming 即: 面向切面编程

​ 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP中的术语:

  • Joinpoint(连接点):

    • 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
      连接点。(就是被代理的对象的所有方法。)
  • Pointcut(切入点):

  • 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 (即:被增强的方法)。

  • Advice(通知/增强):

    • 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

    • 通知的类型: 前置通知, 后置通知, 异常通知, 最终通知, 环绕通知。

      @Override
      // 整个的invoke方法的执行叫环绕通知
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          Object returnValue = null;
      
          try {
              //1.开启事务
              transactionManager.beginTransaction();  // 前置通知
              //2.执行操作
              returnValue = method.invoke(accountService, args);  // 环绕通知中有明确的切入方法调用
              //3.提交事务
              transactionManager.commit();  // 后置通知
      
          } catch (Exception e) {
              //4.回滚操作
              transactionManager.rollback(); // 异常通知
              e.printStackTrace();
              throw new RuntimeException(e);
          } finally {
              //5.释放连接
              transactionManager.release(); // 最终通知
          }
      
          return returnValue;
      }
      });
      
  • Introduction(引介):

    • 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
  • Target(目标对象):

    • 代理的目标对象。
  • Weaving(织入):

    • 是指把增强应用到目标对象来创建新的代理对象的过程。
    • spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
  • Proxy(代理) :

    • 一个类被 AOP 织入增强后,就产生一个结果代理类。
  • Aspect(切面):

    • 是切入点和通知(引介)的结合。

4.2 AOP的作用和优点

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

4.3 AOP 的实现方式和使用

AOP是通过AOP动态代理技术 实现的。

4.4 Spring 中的AOP

4.4.1 关于代理的选择

在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

4.4.2 基于 XML 的 AOP 配置

spring中基于XML的AOP配置步骤:

  1. 把通知Bean也交给spring来管理。
  2. 使用aop:config标签表明开始AOP的配置
  3. 使用aop:aspect标签表明配置切面
    • id属性:是给切面提供一个唯一标识
    • ref属性:是指定通知类bean的Id。
  4. 在aop:aspect标签的内部使用对应标签来配置通知的类型
    • aop:before:表示配置前置通知
      • method属性:用于指定Logger类中哪个方法是前置通知
      • pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

切入点表达式的写法:

关键字:execution(表达式)

表达式:

​ 访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)

标准的表达式写法:

public void cn.fate.service.impl.AccountServiceImpl.saveAccount()

访问修饰符可以省略:

void cn.fate.service.impl.AccountServiceImpl.saveAccount()

返回值可以使用通配符,表示任意返回值:

* cn.fate.service.impl.AccountServiceImpl.saveAccount()

包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*:

* *.*.*.*.AccountServiceImpl.saveAccount())

包名可以使用…表示当前包及其子包:

* *..AccountServiceImpl.saveAccount()

类名和方法名都可以使用*来实现通配:

* *..*.*()

参数列表:

  • 可以直接写数据类型:
    • 基本类型直接写名称 ( int double float)
    • 引用类型写包名.类名的方式 java.lang.String
  • 可以使用通配符*表示任意类型,但是必须有参数
  • 可以使用..表示有无参数均可,有参数可以是任意类型

全通配写法:

  • * *..*.*(..)

在开发中,业务层类的而所有方法通常使用:

* cn.fate.service.impl.*.*(..)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置IoC -->
    <bean id="userService" class="cn.fate.service.impl.UserServiceImpl"></bean>

    <!-- 配置logger类 -->
    <bean id="logger" class="cn.fate.utils.Logger"></bean>

    <!-- 配置AOP -->
    <aop:config>
        <!--     配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
                 此标签写在aop:aspect标签内部只能当前切面使用。
                 它还可以写在aop:aspect外面,此时就变成了所有切面可用(这里由于spring的约束,卸载aop:config 内部的最前面)
          -->
        <aop:pointcut id="pt1" expression="execution(* cn.fate.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知:在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>

            <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

            <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

            <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

            <!-- 配置环绕通知 详细的注释请看Logger类中(但配置了环绕通知,就不会再执行切入点方法就不会执行) -->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>
4.4.3 Spring中的环绕通知

Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调

用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供

我们使用。 spring中的环绕通知作用:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执

行的方式。

public Object aroundPrintLog(ProceedingJoinPoint pjp){
    Object rtValue = null;
    try{
        Object[] args = pjp.getArgs();//得到方法执行所需的参数

        System.out.println("环绕通知开始记录日志了。。。前置");

        rtValue = pjp.proceed(args);  //明确调用业务层方法(切入点方法)

        System.out.println("环绕通知方法开始记录日志了。。。后置");

        return rtValue;
    }catch (Throwable t){
        System.out.println("环绕通知开始记录日志了。。。异常");
        throw new RuntimeException(t);
    }finally {
        System.out.println("环绕通知开始记录日志了。。。最终");
    }
}
4.4.4 Spring AOP基于注解开发

@Aspect:

  • 作用:把当前类声明为切面类。
@Component("logger")
@Aspect  // 表示当前为一个切面类
public class Logger {

    @Pointcut("execution(* cn.fate.service.impl.*.*(..))")
    private void pointCut() {
    }
}

@Before:

  • 作用:把当前方法看成是前置通知。
  • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

@AfterReturning:

  • 作用:把当前方法看成是后置通知。
  • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 。

@AfterThrowing:

  • 作用:把当前方法看成是异常通知。
  • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 。

@After:

  • 作用:把当前方法看成是最终通知。
  • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 。

@Around:

  • 作用:把当前方法看成是环绕通知。
  • 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

@Pointcut:

  • 作用:指定切入点表达式。
  • 属性: value:指定表达式的内容 。
@Component("logger")
@Aspect  // 表示当前为一个切面类
public class Logger {

    @Pointcut("execution(* cn.fate.service.impl.*.*(..))")
    private void pointCut() {
    }

    /**
     * 前置通知
     */
    @Before("pointCut()")
    public void beforePrintLog() {
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pointCut()")
    public void afterReturningPrintLog() {
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("pointCut()")
    public void afterThrowingPrintLog() {
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
    @After("pointCut()")
    public void afterPrintLog() {
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }

    /**
     * 环绕通知
     */
    @Around("pointCut()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);  //明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        } catch (Throwable t) {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        } finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

@EnableAspectJAutoProxy:

  • 作用:开启AOP代理自动配置
@Configuration
@ComponentScan(basePackages = {"cn.fate"}) // 指定创建容器时扫描的包
@EnableAspectJAutoProxy // 开启AOP代理自动配置
public class SpringConfiguration {

}

5. Spring 中的 JdbcTemplate

5.1 JdbcTemplate 概述

JdbcTemplate 它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。 spring 框架为我们提供了很多的操作模板类。

  • 操作关系型数据的:
    • JdbcTemplate
    • HibernateTemplate
  • 操作 nosql 数据库的:
    • RedisTemplate
  • 操作消息队列的:
    • JmsTemplate

5.2 JdbcTemplate 的CRUD

5.2.1 dao 中定义 JdbcTemplate

dao类:

import cn.fate.dao.AccountDao;
import cn.fate.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate;
    
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Account findAccountById(Integer id) {
        List<Account> accounts = jdbcTemplate.query("select *  from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    @Override
    public Account findAccountByName(String name) {
        List<Account> accounts = jdbcTemplate.query("select *  from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), name);
        if (accounts.isEmpty()) {
            return null;
        } else if (accounts.size() > 1) {
            throw new RuntimeException("查询到多个用户");
        } else {
            return accounts.get(0);
        }
    }

    @Override
    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
    }

}

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
        http://www.springframework.org/schema/beans/spring-beans.xsd">


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

    <!-- 配置AccountDao -->
    <bean id="accountDao" class="cn.fate.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

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


</beans>
5.2.1 dao 继承JdbcDaoSupport

dao类:

import cn.fate.dao.AccountDao;
import cn.fate.domain.Account;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;


import java.util.List;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {


    @Override
    public Account findAccountById(Integer id) {
        List<Account> accounts = super.getJdbcTemplate().query("select *  from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    @Override
    public Account findAccountByName(String name) {
        List<Account> accounts = super.getJdbcTemplate().query("select *  from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), name);
        if (accounts.isEmpty()) {
            return null;
        } else if (accounts.size() > 1) {
            throw new RuntimeException("查询到多个用户");
        } else {
            return accounts.get(0);
        }
    }

    @Override
    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
    }

}

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
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao" class="cn.fate.dao.impl.AccountDaoImpl02">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

</beans>

6. Spring 中的事务控制

6.1 Spring 中事务控制的 API

PlatformTransactionManager

此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。

  • 获取事务的状态信息
    • TransactionStatus getTransaction(TransactionDefinition definition)
  • 提交事务
    • viod commit(TransactionStatus status)
  • 回滚事务
    • viod rollback(TransactionStatus status)

我们在开发中都是使用它的实现类 :

  • 使用 SpringJDBC 或 iBatis 进行持久化数据时使用

    • org.springframework.jdbc.datasource.DataSourceTransactionManager
  • 使用Hibernate 版本进行持久化数据时使用

    • org.springframework.orm.hibernate5.HibernateTransactionManager
TransactionDefinition

事务属性通过TransactionDefinition接口实现定义,主要有事务隔离级别、事务传播行为、事务超时时间、事务是否只读。

public interface TransactionDefinition {
    // 事务传播行为
   //事务传播行为类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中(默认)。
    int PROPAGATION_REQUIRED = 0;
    //事务传播行为类型:支持当前事务,如果当前没有事务,就以非事务方式执行(一查询事务一般使用)。 
    int PROPAGATION_SUPPORTS = 1;
    //事务传播行为类型:当前如果有事务,Spring就会使用该事务;否则会抛出异常
    int PROPAGATION_MANDATORY = 2;
    //事务传播行为类型:新建事务,如果当前存在事务,把当前事务挂起。 
    int PROPAGATION_REQUIRES_NEW = 3;
    //事务传播行为类型:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 
    int PROPAGATION_NOT_SUPPORTED = 4;
    //事务传播行为类型:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
    int PROPAGATION_NEVER = 5;
    //事务传播行为类型:如果当前存在事务,则在嵌套事务内执行。
    int PROPAGATION_NESTED = 6;

    
    // 事务隔离级别
   //隔离级别:默认的隔离级别(对mysql数据库来说就是ISOLATION_ READ_COMMITTED,可以重复读)
    int ISOLATION_DEFAULT = -1;
    //隔离级别:读未提交(最低)
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    //隔离级别:读提交
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    //隔离级别:可重复度
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    //隔离级别:序列化操作(最高)
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    //默认事务的超时时间
    int TIMEOUT_DEFAULT = -1;
    


    //获取事务的传播行为
    int getPropagationBehavior();
    //获取事务的隔离级别
    int getIsolationLevel();
    //获取事务超时时间
    int getTimeout();
    //获取事务名称
    String getName();
    //获取事务是否只读 (建议查询时设置为只读。)
    boolean isReadOnly();
    

}
TransactionStatus

TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作。

名称说明
void flush()刷新事务
boolean hasSavepoint()获取是否存在保存点
boolean isCompleted()获取事务是否完成
boolean isNewTransaction()获取是否是新事务
boolean isRollbackOnly()获取是否回滚
void setRollbackOnly()设置事务回滚

6.2 spring中基于XML的事务控制

XML的声明式事务控制配置步骤:

  1. 配置事务管理器
  2. 配置事务的通知
    • 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的,使用tx:advice标签配置事务通知
    • 属性:
      • id:给事务通知起一个唯一标识
      • transaction-manager:给事务通知提供一个事务管理器引用
  3. 配置AOP中的通用切入点表达式
  4. 建立事务通知和切入点表达式的对应关系
  5. 配置事务的属性

是在事务的通知tx:advice标签的内部

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--配置AccountService-->
    <bean id="accountService" class="cn.fate.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>


    <!-- 配置AccountDao -->
    <bean id="accountDao" class="cn.fate.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

    <!-- spring中基于XML的声明式事务控制配置-->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--
        配置事务的属性:
                isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
                propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
                read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
                timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
                rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
                no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
        -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!-- 配置aop -->
    <aop:config>
        <!-- 配置切入点表达式-->
        <aop:pointcut id="pointcut" expression="execution(* cn.fate.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>


</beans>

6.3 spring中基于注解的事务控制

@Transactional 注解

作用:控制事务,该注解可以出现在接口上,类上和方法上。

  • 出现接口上,表示该接口的所有实现类都有事务支持。
  • 出现在类上,表示类中所有方法有事务支持
  • 出现在方法上,表示方法有事务支持。
  • 以上三个位置的优先级:方法>类>接口
@Transactional
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
    @Override
    public Account findAccountById(Integer accountId) {
        Account account = accountDao.findAccountById(accountId);
        //4.返回结果
        return account;
    }


    /**
     * 转账
     *
     * @param sourceName 转成账户名称
     * @param targetName 转入账户名称
     * @param money      转账金额
     */
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer begin....");

        // 执行操作

        //2.1根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3转出账户减钱
        source.setMoney(source.getMoney() - money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney() + money);
        //2.5更新转出账户
        accountDao.updateAccount(source);
        int i = 1 / 0;
        //2.6更新转入账户
        accountDao.updateAccount(target);

    }
}

@EnableTransactionManagement

作用:开启事务控制

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan(basePackages = "cn.fate") // 指定要扫描的包
@Import({JdbcConfig.class, TransactionConfig.class}) // 需要导入的配置类
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement //开启事务控制

public class SpringConfiguration {
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值