1-Spring基础


参考
https://www.processon.com/view/5bbac847e4b015327afb89dc?fromnew=1

一、Spring 概述

Spring 简介

Spring : 春天 —>给软件行业带来了春天
2002 年,Rod Jahnson 首次推出了 Spring 框架雏形 interface21 框架。
2004 年 3 月 24 日,Spring 框架以 interface21 框架为基础,经过重新设计,发布了 1.0 正式版。
很难想象 Rod Johnson 的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring 理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects

Spring 优点

1、Spring 是一个开源免费的框架 (容器 )
2、Spring 是一个轻量级的框架 , 非侵入式的
3、控制反转 IOC
4、面向切面 Aop
5、对事物的支持 , 对其他框架的支持

一句话概括:
Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)

Spring 体系结构

image.png
Spring 由 20 多个模块组成,它们可以分为:数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、提供 JVM 的代理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。

二、IOC 容器

1、理解容器

控制反转(IOC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。 IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行 应用广泛的实现方式是依赖注入。
依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA 对 classB 有依赖。
Ioc 的实现:
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理。
Spring 框架使用依赖注入(DI)实现 IoC。
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。

2、快速入门-第一个 Spring 程序

1、创建 maven 项目

选择 maven 的 quickstart,创建一个标准的 maven 项目
image.png

2、pom.xml 引入 maven 依赖

<!--spring依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

<build>
  <plugins>
    <!--maven编译插件-->
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>

3、定义接口与实体类

public interface SomeService {
    void doSome();
}

public class SomeServiceImpl implements SomeService {
    public SomeServiceImpl() {
        super();
        System.out.println("SomeServiceImpl无参数构造方法");
    }
    @Override
    public void doSome() {
        System.out.println("====业务方法doSome()===");
    }
}

4、创建 Spring 配置文件

在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml。
spring 配置中需要加入约束文件才能正常使用,约束文件是 xsd 扩展名。

<?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">

    <!--告诉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.bjpowernode.service.impl.SomeServiceImpl" />

    <bean id="someService1" class="com.bjpowernode.service.impl.SomeServiceImpl" scope="prototype"/>

    <!--
       spring能创建一个非自定义类的对象吗, 创建一个存在的某个类的对象。
    -->
    <bean id="mydate" class="java.util.Date" />

</beans>
<!--
   spring的配置文件
   1.beans : 是根标签,spring把java对象成为bean。
   2.spring-beans.xsd 是约束文件,和mybatis指定  dtd是一样的。
-->

5、定义测试类

String config="applicationContext.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);
}

//从容器中获取某个对象, 你要调用对象的方法
//getBean("配置文件中的bean的id值")
SomeService service = (SomeService) ac.getBean("someService");
service.doSome();

//获取一个非自定义的类对象
Date my = (Date) ac.getBean("mydate");
System.out.println("Date:"+my);

6、容器接口和实现类

ApplicationContext 用于加载 Spring 的配置文件,在程序中充当“容器”的角色
image.png
我们一般有两种方式获取容器对象,
1、ClassPathXmlApplicationContext(基于配置文件的)

新建一个配置文件(一般放在 resources/applicationContext.xml),在配置文件中通过标签(如 )向容器中注入实例对象

2、AnnotationConfigApplicationContext(基于配置类注解的)

新建一个配置类 MyConfig.java(注解@Configuration),在配置类文件中通过注解向容器中注入实例对象

//配置类
@Configuration
public class MyBeanConfig {
    @Bean
    public Object myDate(){
        return new Date();
    }
}


//测试类
@Test
public void test05(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBeanConfig.class);
    Object myDate = context.getBean("myDate");
    System.out.println(myDate.getClass().getName());		//java.util.Date
}

创建容器并注入对象原理
image.png
核心源码:
image.png

3、依赖注入 DI-基于 XML

DI(dependence injection)依赖注入

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
根据注入方式的不同,常用的有三种方式:构造器注入、Set 方式注入(重点)、扩展方式注入

1、构造器注入

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系。
举例 1:
image.png
标签中用于指定参数的属性有:
➢ name:指定参数名称。
➢ value:给参数注入的属性值
➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。

举例 2:
使用构造注入创建一个系统类 File 对象
image.png

2、Set 方式注入

set 注入也叫设值注入,是指通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。
1、创建 Bean 实体类
注意:实体类必须要有 get set 方法(因为 Spring 容器 DI 时依赖 setter 注入的)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String,String> score;
    private Set<String> games;
    private String wife;
    private Properties info;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    private String addr;
}

2、在 xxx.xml 中配置 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="address" class="org.xjt.di.domain.Address">
        <property name="addr" value="shenzhen"></property>
    </bean>

    <bean id="student" class="org.xjt.di.domain.Student">
        <property name="name" value="xiong"></property>
        <property name="address" ref="address"></property>
        <property name="books">
            <array>
                <value>语文</value>
                <value>数学</value>
                <value>英语</value>
            </array>
        </property>
        <property name="hobbys">
            <list>
                <value>足球</value>
                <value>篮球</value>
                <value>羽毛球</value>
            </list>
        </property>
        <property name="score">
            <map>
                <entry key="chinese" value="86"/>
                <entry key="math" value="96"/>
                <entry key="english" value="88"/>
            </map>
        </property>
        <property name="games">
            <set>
                <value>三国杀</value>
                <value>街头霸王</value>
                <value>魂斗罗</value>
            </set>
        </property>
        <property name="wife">
            <null></null>
        </property>
        <property name="info">
            <props>
                <prop key="学号">2018112233</prop>
                <prop key="班级">机械3班</prop>
                <prop key="性别"></prop>
            </props>
        </property>
     </bean>
</beans>

3、扩展方式注入

p-命名空间
需要先导入约束:xmlns:p="http://www.springframework.org/schema/p"

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- set方法注入-->
    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>
    <!-- p-命名空间方法注入(必须先导入依赖)-->
    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

c-命名空间
需要先导入约束:xmlns:c="http://www.springframework.org/schema/c"

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- 必须要有构造方法 -->
    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>

4、依赖注入 DI-基于注解(重要)

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解需要在原有 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解
指定多个包的三种方式:
(1)使用多个 context:component-scan 指定不同的包路径
image.png
**(2)指定 base-package 的值使用分隔符 **
分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。
image.png
**(3)base-package 是指定到父包名 **
base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。
image.png
但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合适的。也就是注解所在包全路径。例如注解的类在 com.bjpowernode.beans 包中
image.png

定义 Bean 的注解_@_Component

@Component 有几个衍生的注解,分别为*@_Controller 、@Service、*@_Repository
这 4 个注解功能是一样的,都是代表将某个类注入到 Spring 容器中 装配 Bean

需要在 bean 类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值
image.png
另外@Component 有几个衍生的注解 分别为:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解
这 4 个注解功能是一样的,都是代表将某个类注入到 Spring 容器中 装配 Bean
但这三个注解还有其他的含义:
@Service 创建业务层对象,业务层对象可以加入事务功能,
@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
image.png

简单类型属性注入_@_Value

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

自动注入-byType

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

byType 自动注入@Autowired

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

自动注入-byName

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

byName 自动注入@Autowired 与@Qualifier

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

JDK 自动注入注解-_@_Resource

Spring 提供了对 jdk 中@Resource 注解的支持。@Resource 注解既可以按名称匹配 Bean,也可以按类型匹配 Bean,默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。
(1)byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean,则会按照类型进行 Bean 的匹配注入。
举例:
image.png
**(2)byName 注入引用类型属性 **
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
举例:
image.png

注解与 XML 的对比

注解优点是:

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

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

XML 方式优点是:

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

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

5、依赖注入 DI-使用 javaConfig

javaConfig,是在 Spring 3.0 开始从一个独立的项目并入到 Spring 中的。javaConfig 可以看成一个用于完成 Bean 装配的 Spring 配置文件,一个类中只要标注了@Configuration 注解,这个类就可以为 spring 容器提供 Bean 定义的信息了。标注了@Configuration 和标注了@Component 的类一样是一个 Bean,可以被 Spring 的 context:component-scan 标签扫描到。类中的每个标注了@Bean 的方法都相当于提供了一个 Bean 的定义信息。

@Configuration
public class AppConfig {
    @Bean
    public UserDao userDao() {
        return new UserDaoImpl();
    }
    @Bean
    public UserService userService() {
        //这里不能声明接口类型
        UserServiceImpl userService = new UserServiceImpl();
        //配置依赖关系(需要set方法)
        userService.setUserDao(userDao());
        return userService;
    }
}

测试类:

//基于xml
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService service = (UserService)context.getBean("userService");
User user = service.getUser();
System.out.println(user);

//基于javaConfig
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService service = (UserService)context.getBean("userService");
User user = service.getUser();
System.out.println(user);

6、为应用指定多个 Spring 配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
包含关系的配置文件:
多个配置文件中有一个总文件,总配置文件将各其它子文件通过引入。在 Java 代码中只需要使用总配置文件对容器进行初始化即可。
举例:
image.png
image.png

7、Bean 作用域(Scopes)

  • 单例模式

<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
  • 原型模式

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
  • Request, Session, Application, and WebSocket

三、面向切面编程 AOP

1、不使用 AOP 的开发方式

Step1:项目 aop_leadin1

先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非业务方法。非业务方法也称为交叉业务逻辑:
➢ doTransaction():用于事务处理
➢ doLog():用于日志处理
然后,再使接口方法调用它们。接口方法也称为主业务逻辑。
接口和实现类:
image.png

Step2:项目 aop_leadin2

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

Step3:项目 aop_leadin3

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

2、代理模式

http://c.biancheng.net/view/1359.html
代理模式的作用:

  1. 功能增强:在原有功能上 增加额外的功能(不改变原有功能的代码)
  2. 控制访问:代理类不让你访问目标,例如商家不让顾客直接访问厂家

实现代理的方式有两种:

  1. 静态代理:代理类是自己手动实现的 自己创建一个 Java 代理类,同时所要代理的目标类是确定的(实现简单 容易理解)
  2. 动态代理:在程序执行过程中,使用 jdk 的反射机制,创建代理类对象, 并动态的指定要代理目标类。换句话说: 动态代理是一种创
    建 java 对象的能力,让你不用创建 TaoBao 类,就能创建代理类对象。

动态代理(原理): 基于反射机制
1、什么是动态代理 ?
使用 jdk 的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建类文件。不用写 java 文件。
动态:在程序执行时,调用 jdk 提供的方法才能创建代理类的对象。
jdk 动态代理,必须有接口,目标类必须实现接口
没有接口时,需要使用 cglib 动态代理

2、动态代理能做什么 ?
可以在不改变原来目标方法功能的前提下, 可以在代理中增强自己的功能代码。
程序开发中的意思。
比如:你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用。
GoNong.class , GoNong gn = new GoNong(), gn.print();
你发现这个功能,现在还缺点, 不能完全满足我项目的需要。 我需要在 gn.print()执行后,需要自己在增加代码。
用代理实现 gn.print()调用时, 增加自己代码, 而不用去改原来的 GoNong 文件。


1.代理
代购, 中介,换 ip,商家等等
比如有一家美国的大学, 可以对全世界招生。 留学中介(代理)
留学中介(代理): 帮助这家美国的学校招生, 中介是学校的代理, 中介是代替学校完成招生功能。
代理特点:

  1. 中介和代理他们要做的事情是一致的: 招生。
  2. 中介是学校代理, 学校是目标。
  3. 家长—中介(学校介绍,办入学手续)—-美国学校。
  4. 中介是代理,不能白干活,需要收取费用。
  5. 代理不让你访问到目标。
为什么要找中介 ?
1. 中介是专业的, 方便
2. 家长现在不能自己去找学校。 家长没有能力访问学校。 或者美国学校不接收个人来访。买东西都是商家卖, 商家是某个商品的代理, 你个人买东西, 肯定不会让你接触到厂家的。
  1. 在开发中也会有这样的情况, 你有 a 类, 本来是调用 c 类的方法, 完成某个功能。 但是 c 不让 a 调用。
    a —–不能调用 c 的方法。
    在 a 和 c 直接 创建一个 b 代理, c 让 b 访问。
    a –访问 b—访问 c
    实际的例子: 登录,注册有验证码, 验证码是手机短信。
    中国移动, 联通能发短信。
    中国移动, 联通能有子公司,或者关联公司,他们面向社会提供短信的发送功能
    张三项目发送短信—-子公司,或者关联公司—–中国移动, 联通

3.使用代理模式的作用 1.功能增强: 在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。 2.控制访问: 代理类不让你访问目标,例如商家不让用户访问厂家。 4.实现代理的方式 1.静态代理 :
1) 代理类是自己手工实现的,自己创建一个 java 类,表示代理类。
2)同时你所要代理的目标类是确定的。
特点: 1)实现简单 2)容易理解。
缺点:
当你的项目中,目标类和代理类很多时候,有以下的缺点:
1)当目标类增加了, 代理类可能也需要成倍的增加。 代理类数量过多。
2) 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改。影响比较多。
模拟一个用户购买 u 盘的行为。
用户是客户端类
商家:代理,代理某个品牌的 u 盘。
厂家:目标类。
三者的关系: 用户(客户端)—商家(代理)—厂家(目标)
商家和厂家都是卖 u 盘的,他们完成的功能是一致的,都是卖 u 盘。
实现步骤:

  1. 创建一个接口,定义卖 u 盘的方法, 表示你的厂家和商家做的事情。
  2. 创建厂家类,实现 1 步骤的接口
  3. 创建商家,就是代理,也需要实现 1 步骤中的接口。
  4. 创建客户端类,调用商家的方法买一个 u 盘。
    代理类完成的功能:
  5. 目标类中方法的调用
  6. 功能增强 2.动态代理
    在静态代理中目标类很多时候,可以使用动态代理,避免静态代理的缺点。 动态代理中目标类即使很多, 1)代理类数量可以很少, 2)当你修改了接口中的方法时,不会影响代理类。
    动态代理: 在程序执行过程中,使用 jdk 的反射机制,创建代理类对象, 并动态的指定要代理目标类。
    换句话说: 动态代理是一种创建 java 对象的能力,让你不用创建 TaoBao 类,就能创建代理类对象。
    在 java 中,要想创建对象: 1.创建类文件, java 文件编译为 class 2.使用构造方法,创建类的对象。
    动态代理的实现:
  7. jdk 动态代理(理解): 使用 java 反射包中的类和接口实现动态代理的功能。反射包 java.lang.reflect , 里面有三个类 :InvocationHandler , Method, Proxy.
  8. cglib 动态代理(了解): cglib 是第三方的工具库, 创建代理对象。
    cglib 的原理是继承, cglib 通过继承目标类,创建它的子类,在子类中
    重写父类中同名的方法, 实现功能的修改。
因为cglib是继承,重写方法,所以要求目标类不能是final的, 方法也不能是final的。
  cglib的要求目标类比较宽松, 只要能继承就可以了。cglib在很多的框架中使用,
  比如 mybatis ,spring框架中都有使用。

jdk 动态代理:

  1. 反射, Method 类,表示方法。类中的方法。 通过 Method 可以执行某个方法。
  2. jdk 动态代理的实现
    反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy.
    1)InvocationHandler 接口(调用处理器):就一个方法 invoke()
    invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在
    invoke()方法中。

代理类完成的功能:

  1. 调用目标方法,执行目标方法的功能
  2. 功能增强,在目标方法调用时,增加功能。
方法原型:
参数: Object proxy:jdk创建的代理对象,无需赋值。
Method method:目标类中的方法,jdk提供method对象的
Object[] args:目标类中方法的参数, jdk提供的。
public Object invoke(Object proxy, Method method, Object[] args)
InvocationHandler 接口:表示你的代理要干什么。
怎么用: 1.创建类实现接口InvocationHandler
2.重写invoke()方法, 把原来静态代理中代理类要完成的功能,写在这。
2)Method类:表示方法的, 确切的说就是目标类中的方法。
作用:通过Method可以执行某个目标类的方法,Method.invoke();
method.invoke(目标对象,方法的参数)
Object ret = method.invoke(service2, "李四");

说明: method.invoke()就是用来执行目标方法的,等同于静态代理中的
//向厂家发送订单,告诉厂家,我买了u盘,厂家发货
float price = factory.sell(amount); //厂家的价格。

3)Proxy 类:核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()
现在我们是使用 Proxy 类的方法,代替 new 的使用。
方法: 静态方法 newProxyInstance()
作用是: 创建代理对象, 等同于静态代理中的 TaoBao taoBao = new TaoBao();

参数:
1. ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader
类a , a.getCalss().getClassLoader(),  目标对象的类加载器
2. Class<?>[] interfaces: 接口, 目标对象实现的接口,也是反射获取的。
3. InvocationHandler h : 我们自己写的,代理类要完成的功能。
返回值:就是代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
  1. 实现动态代理的步骤:
    1. 创建接口,定义目标类要完成的功能
  2. 创建目标类实现接口
  3. 创建 InvocationHandler 接口的实现类,在 invoke 方法中完成代理类的功能 1.调用目标方法 2.增强功能

4.使用 Proxy 类的静态方法,创建代理对象。 并把返回值转为接口类型。

示例:

com.xjt.handler.MyInvocationHandler.java

目标类和接口


基于 Spring 配置文件的 aop

  • 方法一:

    使用原生是 sping-api 接口 在目标方法前置后置增加功能
    该方法需要定义前置方法实现 org.springframework.aop.MethodBeforeAdvice
    定义后置方法实现 org.springframework.aop.AfterReturningAdvice
    优点:前后置方法中可获取到目标对象、执行的方法、参数,自定制程度化高

前置方法

package org.example.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"类的"+method.getName()+"方法被执行了,返回结果为:"+returnValue);
    }
}

后置方法

package org.example.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"类的"+method.getName()+"方法被执行了");
    }
}

Spring 的配置:applicationContext.xml

<bean id="userService" class="org.example.service.UserServiceImpl"/>
<bean id="log" class="org.example.log.Log"/>
<bean id="afterLog" class="org.example.log.AfterLog"/>
<aop:config>
    <!--切入点 表达式-->
    <aop:pointcut id="pointcut" expression="execution(* org.example.service.UserServiceImpl.*(..))"/>
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
  • 方法二:

前后置方法可以定义在一个类中

package org.example.diy;
public class Diy {
    void before(){
        System.out.println("before===============目标方法执行前");
    }
    void after(){
        System.out.println("after===============目标方法执行后");
    }
}

Spring 的配置:applicationContext.xml

<bean id="userService" class="org.example.service.UserServiceImpl"/>
<bean id="diy" class="org.example.diy.Diy"/>
<aop:config>
    <aop:aspect ref="diy">
        <aop:pointcut id="pointcut" expression="execution(* org.example.service.UserServiceImpl.*(..))"/>
        <aop:before method="before" pointcut-ref="pointcut"/>
        <aop:after method="after" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

执行效果:

public static void main( String[] args ){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) applicationContext.getBean("userService");
    userService.add();
}
before===============目标方法执行前
执行了UserServiceImpl::add()
after===============目标方法执行后

基于注解的 aop

切面类

package org.example.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect         //声明当前类是切面类
public class AnnotationAop {
    @Before("execution(* org.example.service.UserServiceImpl.*(..))")
    void doBefore(){
        System.out.println("==========方法执行前==========");
    }
    @After("execution(* org.example.service.UserServiceImpl.*(..))")
    void doAfter(){
        System.out.println("==========方法执行前==========");
    }
    @Around("execution(* org.example.service.UserServiceImpl.*(..))")
    void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前。。。");
        joinPoint.proceed();
        System.out.println("环绕后。。。");
    }
}

applicationContext.xml 配置

<bean id="userService" class="org.example.service.UserServiceImpl"/>
<!--方法二:基于注解的实现-->
<bean id="annotationAop" class="org.example.anno.AnnotationAop"/>
<!--proxy-target-class 默认false 使用jdk动态代理,true使用cglib-->
<aop:aspectj-autoproxy proxy-target-class="false"/>

打印结果:

public static void main( String[] args ){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) applicationContext.getBean("userService");
    userService.add();
}
环绕前。。。
==========方法执行前==========
执行了UserServiceImpl::add()
==========方法执行前==========
环绕后。。。

3、AOP 概述

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理。

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

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。

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

面向切面编程对有什么好处?

1.减少重复; 2.专注业务;
注意:面向切面编程只是面向对象编程的一种补充。
使用 AOP 减少重复代码,专注业务实现:
image.png

4、AOP 编程术语

**(1)切面(Aspect) **
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice) 。实际就是对主业务逻辑的一种增强。
**(2)连接点(JoinPoint) **
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
**(3)切入点(Pointcut) **
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
**(4)目标对象(Target) **
目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上例中 的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然不被增强,也就无所谓目标不目标了。
**(5)通知(Advice) **
通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 切入点定义切入的位置,通知定义切入的时间。

5、AspectJ 对 AOP 的实现

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

AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。官网地址:http://www.eclipse.org/aspectj/
AspetJ 是 Eclipse 的开源项目,官网介绍如下:
a seamless aspect-oriented extension to the Javatm programming language(一种基于 Java 平台的面向切面编程的语言) Java platform compatible(兼容 Java 平台,可以无缝扩展) easy to learn and use(易学易用)

AspectJ 的通知类型

AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知

AspectJ 的切入点表达式

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(访问权限 方法返回值 方法声明(参数) 异常类型)

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
image.png
举例:
**execution(public * *(..)) **
指定切入点为:任意公共方法。
**execution(_ set_(..)) **
指定切入点为:任何一个以“set”开始的方法。
**execution(_ com.xyz.service..*(..)) **
指定切入点为:定义在 service 包里的任意类的任意方法。
**execution(
com.xyz.service...*(..))**
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“_”,表示包、子包下的所有类。
**execution(
..service..(..)) **
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
**execution(
.service..(..)) **
指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
**execution(
.ISomeService.(..)) **
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
**execution(* ..ISomeService.(..)) **
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
**execution(_ com.xyz.service.IAccountService.(..)) **
指定切入点为:IAccountService 接口中的任意方法。
**execution(
com.xyz.service.IAccountService+.(..)) **
指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
**execution(
joke(String,int)))**
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
**execution(_ joke(String,))) **
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)都是,但 joke(String s1,double d2,String s3)不是。
**execution(
joke(String,..))) **
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
**execution(_ joke(Object)) **
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
**execution(_ joke(Object+))) **
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

6、AspectJ 基于注解的 AOP 实现

基于注解的 AOP 实现步骤

1、maven 依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

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

3、定义业务接口与实现类
image.png
4、定义切面类
类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。
image.png
5、声明目标对象切面类对象
image.png
**6、注册 AspectJ 的自动代理 **
在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。
image.png
<aop:aspectj-autoproxy/>的底层是由AnnotationAwareAspectJAutoProxyCreator实现的。
从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。 其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。

7、测试类中使用目标对象的 id
image.png

@Before 前置通知-方法有 JoinPoint 参数

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

@AfterReturning 后置通知-注解有 returning 属性

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

接口增加方法:
image.png
实现方法:
image.png
定义切面:
image.png

@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

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

接口增加方法:
image.png
接口方法的实现:
image.png
定义切面:
image.png

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

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

增加业务方法:
image.png
方法实现:
image.png
定义切面:
image.png

@After 最终通知

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

增加方法:
image.png
方法实现:
image.png
定义切面:
image.png

@Pointcut 定义切入点

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

四、Spring 集成 MyBatis

将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring 来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。 实现 Spring 与 MyBatis 的整合常用的方式:**扫描的 Mapper 动态代理 **

Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插上 mybatis,两个框架就是一个整体。
所谓的 Spring 集成 MyBatis,就是由 Spring 给 MyBatis 注入需要的组件。比如:DataSource(数据源)、SqlSessionFactory、sqlSession、事务管理对象等。

1、添加集成 MyBatis 的依赖

<dependencies>
  	<!--spring核心 IOC容器-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

  	<!--spring事务-->
  	<dependency>
   		<groupId>org.springframework</groupId>
   		<artifactId>spring-tx</artifactId>
   		<version>5.2.5.RELEASE</version>
  	</dependency>
    <!--spring对jdbc的支持依赖包(关联spring-tx包) -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- mybatis-spring 依赖包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.0</version>
    </dependency>
    <!--mybatis 依赖包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
    </dependency>

    <!-- mysql驱动 依赖包 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
  	<!-- alibaba数据源 -->
  	<dependency>
   		<groupId>com.alibaba</groupId>
   		<artifactId>druid</artifactId>
   		<version>1.1.12</version>
  	</dependency>
</dependencies>

2、创建 Mapper

xxxMapper.java 和 xxxMapper.xml 一般放在同一个目录中
下面是采用注解的方式写的 sql

@Mapper
public interface EmpMapper {
    @Select("select * from emp where empno=#{empno}")
    public Emp getEmpById(int empno);
}

在 Mapper 接口上添加@Mapper 注解,Spring 容器扫描到此注解后,就会将 SqlSession 注入给 Mapper 组件。

3、添加 db.properties 文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/emp?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123

4、Spring 容器中添加组件

applicationContext.xml

<!--开启注解扫描,设置需要扫描的包(用于扫描测试时用的Service组件)  -->
<context:component-scan base-package="com.neusoft.ms"/>
<!-- 引入db配置文件  -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 配置dataSource数据源 -->
<bean id="dataSource"
       class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 创建SqlSessionFactory,并配置实体对象别名 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="typeAliasesPackage" value="com.neusoft.ms.po" />
</bean>
<!-- 配置Mapper,自动扫描Mapper接口,并为其注入SqlSessionFactory -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.neusoft.ms.mapper"></property>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

这里,配置了 sqlSessionFactory 组件,并将 sqlSessionFactory 组件注入给 Mapper 组件。

5、测试

创建 Service 组件,然后测试

@Service("empService")
public class EmpServiceImpl implements EmpService{
    @Autowired
    private EmpMapper empMapper;
    @Override
    public Emp getEmpById(int empno) {
        return empMapper.getEmpById(empno);
    }
}
public class MsTest {
    public static void main(String[] args) {
        ApplicationContext context = new  ClassPathXmlApplicationContext("spring.xml");
        EmpService service = (EmpService)context.getBean("empService");
        Emp emp = service.getEmpById(7369);
        System.out.println(emp);
    }
}

6、添加事务管理

Spring 为了支持事务管理,专门封装了事务管理对象。我们只要在 Spring 容器中配置这个对象,即可使用。
在 Spring 容器中添加事务管理的配置:

<!-- 配置Spring提供的事务管理器 -->
<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />

在 Service 组件中,使用@Transactional 注解,就可以给业务方法添加事务管理。

@Transactional
public int transferAccounts(Emp emp1,Emp emp2) {
    //需要事务管理的业务
}

注意:

  1. 需要事务管理的 service,在方法上加上@Transactional 注解即可。
  2. 必须为 public 方法才行,不要捕捉异常,要让异常自动抛出,否则不能进行事务回滚。

7、事务传播行为

@Transactional 注解中的 propagation 属性,可以设置事务传播行为。属性值为:

  1. REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中。这是最常见的选择。
  2. SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  3. MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  4. REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  5. NOT_SUPPORTED:以非事务方式执行操作,如果存在事务,就把当前事务挂起。
  6. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
@Override
@Transactional(propagation=Propagation.SUPPORTS)
public Dept getDeptById(Integer deptno) {
    return deptMapper.getDeptById(deptno);
}

[

](https://null_688_6639.gitee.io/javase/23spring/)


文章作者: CoderXiong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CoderXiong !
  目录