Spring入门

Spring

概述及IOC理论推导

2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。

2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。

Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

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

优点

1、Spring是一个开源免费的框架 , 容器 .

2、Spring是一个轻量级的框架 , 非侵入式的 .

3、控制反转 IoC , 面向切面 Aop

4、对事物的支持 , 对框架的支持

一句话概括:

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

组成

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
在这里插入图片描述

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

快速上手Spring

导入Jar包
<!-- 这一个才是spring的 这个包是一个汇总 -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.1.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>RELEASE</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>RELEASE</version>
    <scope>test</scope>
</dependency>
编写代码

1、编写一个Hello实体类

@Data
@NoArgsConstructor
@AllArgConstructor
public class Hello {
   private String name;
}

2、编写我们的spring文件 , 这里我们命名为ApplicationContext.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就是java对象  由Spring创建和管理-->
   <bean id="hello" class="com.kuang.pojo.Hello">
       <property name="name" value="数据"/>
   </bean>
</beans>

3、我们可以去进行测试了

@Test
public void test(){
   //ApplicationContext.xml文件 , 生成管理相应的Bean对象
   ApplicationContext c = new ClassPathXmlApplicationContext("ApplicationContext.xml");
   //getBean : 参数即为spring配置文件中bean的id
   Hello hello = c.getBean("hello",Hello.class);
   hello.show();
}
思考
  • Hello 对象是谁创建的 ? hello 对象是由Spring创建的

  • Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的

  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

依赖注入 : 就是利用set方法来进行注入的.

IOC是一种编程思想,由主动的编程变成被动的接收

可以通过newClassPathXmlApplicationContext去浏览一下底层源码

IOC创建对象方式

1、通过无参构造方法来创建

<!-- 这个其实一开始是通过无参构造方法构造了这个对象 -->
<bean id="hello" class="com.kuang.pojo.Hello">
    <!-- 然后使用反射进行set注入 -->
    <property name="name" value="lm"/>
</bean>

2、通过有参构造方法来创建

​ 有参构造方法来创建bean有三种方法

<!-- 第一种根据index参数下标设置 -->
<bean id="hello" class="com.kuang.pojo.Hello">
    <!-- index指构造方法 , 下标从0开始 -->
	<constructor-arg index="0" value="lm"/>
</bean>

<!-- 第二种根据参数名字设置 -->
<bean id="hello" class="com.kuang.pojo.Hello">
    <!-- name指参数名 -->
	<constructor-arg name="name" value="lm"/>
</bean>

<!-- 第三种根据参数类型设置 -->
<bean id="hello" class="com.kuang.pojo.Hello">
    <!-- 参数类型  这种不推荐使用 -->
	<constructor-arg type="java.lang.String" value="lm"/>
</bean>
Spring配置

1、别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名

<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="hello" alias="helloNew"/>

2、Bean的配置

<!--
   id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
   如果配置id,又配置了name,那么name是别名
   name可以设置多个别名,可以用逗号,分号,空格隔开
   如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;

class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
   <property name="name" value="Spring"/>
</bean>

3、import

团队的合作通过import来实现 .

<import resource="{path}/beans.xml"/>

依赖注入

概念

  • 依赖注入(Dependency Injection,DI)。
  • 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
构造器注入

前面写过 在bean里面使用constructor这一些列的就是构造器注入

<bean id="hello" class="com.kuang.pojo.Hello">
	<constructor-arg type="java.lang.String" value="lm"/>
</bean>
Set注入

要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .

1、注入常量

<bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
 </bean>

2、注入Bean

<bean id="addr" class="com.kuang.pojo.Address">
    <property name="address" value="重庆"/>
</bean>

<bean id="student" class="com.kuang.pojo.Student">
    <property name="name" value="小明"/>
    <!-- ref引用了上面的bean 这个就是bena注入 -->
    <property name="address" ref="addr"/>
</bean>

3、注入数组

<bean id="student" class="com.kuang.pojo.Student">
    ...............
    <property name="books">
        <!-- array代表是个数组 -->
        <array>
            <value>西游记</value>
            <value>红楼梦</value>
            <value>水浒传</value>
        </array>
    </property>
</bean>

4、注入List

<property name="hobbys">
    <list>
        <value>听歌</value>
        <value>看电影</value>
        <value>爬山</value>
    </list>
</property>

5、注入Map

<property name="card">
    <map>
        <entry key="中国邮政" value="456456456465456"/>
        <entry key="建设" value="1456682255511"/>
    </map>
</property>

6、注入Set

<property name="games">
    <set>
        <value>LOL</value>
        <value>BOB</value>
        <value>COC</value>
    </set>
</property>

7、注入Null

<property name="wife"><null/></property>

8、注入Properties

<property name="info">
    <props>
        <prop key="学号">20190604</prop>
        <prop key="性别"></prop>
        <prop key="姓名">小明</prop>
    </props>
</property>
p命名注入和c命名注入

1、P命名空间注入 : 需要在头文件中加入约束文件

 导入约束 : xmlns:p="http://www.springframework.org/schema/p"
 
 <!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>

2、c 命名空间注入 : 需要在头文件中加入约束文件

 导入约束 : xmlns:c="http://www.springframework.org/schema/c"
 <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>

注意:c命名注入要有所对应的构造方法

Bean的作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象

几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

1、Singleton 单列

当设置bean的作用域为Singleton,那么这个IOC容器中只会存在一个bean的共享实例,所以对bean的请求需要获取使用bean的时候只会返回那一个bean(只是相对于它所在的容器单列,如果有多个容器都有这个bean的话,就会有多个)。创建容器的时候就会直接进行创建这个bean的对象,不管你使不使用,都会存在

<!-- scope设置bean的作用域  远古版本可以singleton="true"来设置(现版本只能用scope) -->
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

2、Prototype 原型

当一个bean的作用域为Prototype,表示这个bean会定义多个对象实例,每次获取(getBean())都会创建一个bean。 在我们创建容器的时候不会创建这个bean实例,而是当我们去获取bean的时候才会去创建一个bean实例

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 

3、Request 请求

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例 即每个HTTP请求都会有各自的bean实例。该作用域仅在基于web的Spring ApplicationContext情形下有效

<bean id="loginAction" class="cn.csdn.LoginAction" scope="request"/>

每次HTTP请求,Spring容器会根据LoginAction bean的定义创建一个全新的LoginAction Bean实例。这个实例仅在当前请求内有效。当请求结束的时候,request作用域的bean实例将被销毁

4、Session 会话

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

当然Bean作用域不止这四种,重点了解这四种就好了

自动装配

自动装配说明

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean有三种装配机制,分别是:

  1. 在xml中显式配置;
  2. 在java中显式配置;
  3. 隐式的bean发现机制和自动装配。

Spring的自动装配需要从两个角度来实现,或者说是两个操作:

  1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

推荐不使用自动装配xml配置 , 而使用注解 .

1、新建两个实体类

public class Cat {
   public void shout() {
       System.out.println("miao~");
  }
}
public class Dog {
   public void shout() {
       System.out.println("wang~");
  }
}

2、新建一个人员类

@Data
@NoArgsConstructor
@AllArgConstructor
public class People {
   private Cat cat;
   private Dog dog;
   private String name;
}

3、编写Spring配置文件

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>

<!-- 正常的注入方式 -->
<bean id="people" class="com.kuang.pojo.People">
    <property name="cat" ref="cat"/>
    <property name="dog" ref="dog"/>
    <property name="name" value="小红"/>
</bean>
使用自动装配

autowire byName (按名称自动装配)

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

1、修改bean配置,增加一个属性 autowire=“byName”

<bean id="people" class="com.kuang.pojo.People" autowire="byName">
   <property name="name" value="qinjiang"/>
</bean>

autowire byType (按类型自动装配)

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

<!-- 它会到容器里面找是否有所对于类型的的bean 然后进行装配进来  注意不要有重复的 -->
<bean id="people" class="com.kuang.pojo.People" autowire="byType">
   <property name="name" value="qinjiang"/>
</bean>
使用注解

jdk1.5开始支持注解,spring2.5开始全面支持注解。

准备工作:利用注解的方式注入属性。

1、在spring配置文件中引入context文件头

xmlns:context="http://www.springframework.org/schema/context"

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

2、开启属性注解支持!

<context:annotation-config/>

@Autowired

  • @Autowired是按类型自动转配的,不支持id匹配。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启注解支持 -->
    <context:annotation-config/>
    <bean id="cat" class="com.lm.spring.Cat" p:name="加菲猫"/>
    <bean id="cat01" class="com.lm.spring.Cat" p:name="折耳猫"/>
    <bean id="cat02" class="com.lm.spring.Cat" p:name="短腿猫"/>
    <bean id="dog" class="com.lm.spring.Dog" p:name="哈巴狗"/>
    <bean id="people" class="com.lm.spring.People" p:name="小紫"/>
    <bean id="people02" class="com.lm.spring.People02"/>
</beans>

1、将People类中的set方法去掉,使用@Autowired注解

@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {
    private String name;
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    public void show() {
        System.out.println(name + "说:人猿泰山~");
    }
}

@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

@Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
  • @Qualifier不能单独使用。
@Autowired
@Qualifier(value = "cat02")
private Cat cat;

@Resource

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上不成功,则按byType的方式自动装配。(不能设置name值)
  • 都不成功,则报异常。

实体类:

public class User {
   //如果允许对象为null,设置required = false,默认为true
   @Resource(name = "cat01")
   private Cat cat;
   @Resource
   private Dog dog;
   private String str;
}
小节

@Autowired与@Resource异同:

1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

2、@Autowired默认按类型装配(属于pring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。然后根据类型进行装配。如果name属性一旦指定,就只会按照名称进行装配。

4、它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。 @Autowired是Spring的注解,@Resource是java的注解

使用注解开发

说明

1、在spring4之后,想要使用注解形式,必须得要引入aop的包

在这里插入图片描述

2、在配置文件当中,还得要引入一个context约束

<!-- xmlns:context="http://www.springframework.org/schema/context" -->
<!-- http://www.springframework.org/schema/context -->
<!-- http://www.springframework.org/schema/context/spring-context.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"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
Bean的实现

我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!

1、配置扫描哪些包下的注解

<!--指定注解扫描包-->
<context:component-scan base-package="com.kuang.pojo"/>
<!-- 开启使用注解 -->
<context:annotation-config />

2、在指定包下编写类,增加注解

@Component(value = "user01")
//相当于配置文件中<bean id="user01" class="com.lm.pojo.User"/>
public class User {
    public String name = "小红";
}

3、测试

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    User user = context.getBean("user01",User.class);
    System.out.println(user.name);
}
属性注入

使用注解注入属性

1、可以不用提供set方法,直接在直接名上添加@value(“值”)

@Component
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
   @Value("秦疆")
   // 相当于配置文件中 <property name="name" value="秦疆"/>
   public String name;
}

2、如果提供了set方法,在set方法上添加@value(“值”);

@Component
public class User {
   	public String name;
	
    //注意别导错包
   	@Value("秦疆")
   	public void setName(String name) {
       	this.name = name;
  	}
}
衍生注解

我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!

@Component三个衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层

写上这些注解,就相当于将这个类交给Spring管理装配了

作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
   @Value("秦疆")
   public String name;
}
纯注解开发

@Configurable相当于定义这个类为一个ioc容器 就像xml配置里面的Beans标签

@Bean就是定义一个Bean对象 相当于xml配置里面的Bean标签

@Configurable
public class ConfigUser {
    @Bean
    public User user() {
        return new User();
    }
    @Bean
    public UserDao userDao() {
        return new UserDao();
    }
}

测试

@Test
public void test02() {
    //AnnotationConfigApplicationContext 读取注解配置的ioc容器
    ApplicationContext context = new AnnotationConfigApplicationContext(ConfigUser.class);
    UserDao userDao = context.getBean("userDao", UserDao.class);
    System.out.println(userDao);
}

导入其他配置如何做呢?

1、我们再编写一个配置类!

@Configuration  //代表这是一个配置类
//扫描 com.lm下的所有类 看有没有加注解的 然后配置进来
@ComponentScan(value = "com.lm")	
public class MyConfig2 {
}

2、在之前的配置类中我们来选择导入这个配置类

@Configuration
@Import(MyConfig2.class)  //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {
    @Bean
    public UserDao userDao() {
        return new UserDao();
    }
}
小节

1、XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

2、xml与注解整合开发 :

推荐最佳实践

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>  

3、作用:

  • 进行注解驱动注册,从而使注解生效
  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
  • 如果不扫描包,就需要手动配置bean
  • 如果不加注解驱动,则注入的值为null!

4、常用注解

  • @Component:吧自己当作bean注册到容器里面
  • @Controller:和上面一样
  • @Service:和上面一样
  • @Repository:和上面一样
  • @Scope:定义对象的作用域
  • @Configurable:定义为一个容器配置类 就如xml配置里面的beans
  • @Bean:就如xml配置里面bean
  • @ComponentScan:定义扫描范围
  • @Import:导入其他的@Configurable定义的类
  • @Autowired:自动装配
  • @Resource:自动装配java自带的

静态/动态代理模式

为什么要学习代理模式,因为AOP的底层机制就是动态代理!

代理模式:

  • 静态代理
  • 动态代理

学习aop之前 , 我们要先了解一下代理模式!

静态代理

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
  • 客户 : 使用代理角色来进行一些操作 .

1、抽象角色:租房

public interface Renting {
    void getHouse();
}

2、真实角色:房东

public class HouseOwner implements Renting{
    @Override
    public void getHouse() {
        System.out.println("进行房屋出租");
    }
}

3、代理角色:中介

public class HouseProxy implements  Renting{
    private Renting renting = new HouseOwner();
    @Override
    public void getHouse() {
        System.out.println("带领客户看房子");
        renting.getHouse();
        System.out.println("带领客户签证");
    }
}

4、客户:我们

public class Client {
    //进行静态代理测试
    public static void main(String[] args) {
        Renting renting = new HouseProxy();
        renting.getHouse();
    }
}

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

静态代理的好处

  • 可以使用我们的真实角色更加纯粹,不用去关注一些公共的事情。
  • 公共的业务由代理来完成,实现了业务的分工。
  • 公共业务发生扩展时变得更加集中和方便

缺点:

  • 类多了,多了代理类,工作量变大了 开发效率降低。

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

在这里插入图片描述

动态代理
  • 动态代理的角色和静态代理的一样 .

  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!、

JDK的动态代理需要了解两个类

核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看

【InvocationHandler:调用处理程序】

在这里插入图片描述

Object invoke(Object proxy, 方法 method, Object[] args)//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 

【Proxy : 代理】

代码实现

抽象角色和真实角色和之前的一样!

1、抽象角色

public interface UserService{
    void add();
    void remove();
    void update();
    void getUser();
}

2、真实角色

public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("add方法");
    }

    @Override
    public void remove() {
        System.out.println("remove方法");
    }

    @Override
    public void update() {
        System.out.println("update方法");
    }

    @Override
    public void getUser() {
        System.out.println("getUser方法");
    }
}

3、ProxyInvocationHandler. java 即代理角色

//待会我们会用这个类,自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }

    //生成代理类
    public Object getProxy() {
//        fare();
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }

    //处理代理实例 并返回结果	使用它调用方法是通过这个实现
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质就是使用反射机制实现
        log(method.getName());
        Object result = method.invoke(target,args);
        return result;
    }

    public void fare() {
        System.out.println("收取中介费");
    }
	//打印日志
    public void log(String methodName) {
        System.out.println("我们执行了"+methodName+"方法");
    }
}

4、Client客户

public class UserClient {
    public static void main(String[] args) {
        //真实角色
        UserService userService = new UserServiceImpl();

        //代理角色 : 现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象
        pih.setTarget(userService);

        //这个Proxy就是自动生成的,我们并没有去写它
        UserService proxy = (UserService) pih.getProxy();
        proxy.add();
        System.out.println("========================");
        proxy.update();
    }
}

AOP理论与上手

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

Aop在Spring中的作用

提供声明式事务;允许用户自定义切面

以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
  • 切面(Aspect):横切关注点 被模块化 的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知 执行的 “地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。
    在这里插入图片描述

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
在这里插入图片描述

使用Spring实现AOP

【重点】使用AOP织入,需要导入一个依赖包!

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

一)通过XML配置实现

1、首先编写业务层接口和实现类

public interface UserService01 {	//之前写了重名的 所以我加了01
    void add();
    void delete();
    void update();
    String query();
}
public class UserService01Impl implements UserService01 {
    @Override
    public void add() {
        System.out.println("add一条数据");
    }
    @Override
    public void delete() {
        System.out.println("delete一条数据");
    }
    @Override
    public void update() {
        System.out.println("update一条数据");
    }
    @Override
    public String query() {
        System.out.println("query一条数据");
        return "方法值";
    }
}

2、然后编写增强类,我编写了两个 前置增强和后置增强

//前置增强 进行日志打印
public class UserBeforeLog implements MethodBeforeAdvice {
    @Override		//method:要执行的目标方法	args:调用时的参数		target:目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("开始执行"+target.getClass().getName()+"的"+method.getName());
    }
}
//后置增强 继续进行日志打印
public class UserAfterLog implements AfterReturningAdvice {
    @Override	//returnValue:返回值  method:被调方法  args:调用时参数  target:被调目标对象
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行");
        System.out.println("并返回:"+returnValue);
    }
}

3、XML配置注册,实现aop切入

<?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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 注册Bean -->
    <bean id="userAfter" class="com.lm.service.aop.UserAfterLog"/>
    <bean id="userBefore" class="com.lm.service.aop.UserBeforeLog"/>
    <bean id="userService01" class="com.lm.service.impl.UserService01Impl"/>

    <!-- aop的配置 -->
    <aop:config>
        <!-- 切入点 expression:表达式匹配要执行的方法 
			 第一个*代表适配所有项目
			 第二个*代表适配对应类下的所有方法
			 (..)代表匹配所有方法-->
        <aop:pointcut id="userPointcut" expression="execution(* com.lm.service.impl.UserService01Impl.*(..))"/>

        <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
        <aop:advisor advice-ref="userAfter" pointcut-ref="userPointcut"/>
        <aop:advisor advice-ref="userBefore" pointcut-ref="userPointcut"/>
    </aop:config>
</beans>

4、测试

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("Beans01.xml");
    //注意aop切入配置的对象要是接口,不能是具体实现类,所有我写的是 UserService01.class
    UserService01 userService = context.getBean("userService01",UserService01.class);
    userService.query();
}

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

二)通过XML配置简单版

1、写真实角色

public class UserService01Impl02 implements UserService01 {
    @Override
    public void add() {
        System.out.println("这个是02的add方法");
    }
    @Override
    public void delete() {
        System.out.println("这个是02的delete方法");
    }
    @Override
    public void update() {
        System.out.println("这个是02的update方法");
    }
    @Override
    public String query() {
        System.out.println("这个是02的query方法");
        return "02的返回值";
    }
}

2、写我们的切入类

public class User02Log {
    public void after() {
        System.out.println("执行后日志");
    }
    public void before() {
        System.out.println("执行前日志");
    }
}

3、去Spring中配置

<bean id="userService02" class="com.lm.service.impl.UserService01Impl02"/>
<bean id="user02Log" class="com.lm.service.aop.User02Log"/>
<aop:config>
    <aop:pointcut id="userPointcut02" expression="execution(* com.lm.service.impl.UserService01Impl02.*(..))"/>
    <aop:aspect ref="user02Log">
        <!-- 前置日志打印 -->
        <aop:before method="before" pointcut-ref="userPointcut02"/>
        <!-- 后置日志打印 -->
        <aop:after method="after" pointcut-ref="userPointcut02"/>
    </aop:aspect>
</aop:config>

4、进行测试

@Test
public void test02() {
    ApplicationContext context = new ClassPathXmlApplicationContext("Beans01.xml");
    UserService01 userService = context.getBean("userService02",UserService01.class);
    userService.query();
}

三)通过注解实现
1、编写一个注解实现的增强类

@Aspect
public class AnnotationPointcut {
    //execution里面的是连接点
    @Before("execution(* com.lm.service.impl.UserService01Impl03.*(..))")
    public void before() {
        System.out.println("===============方法执行前=================");
    }

    @After("execution(* com.lm.service.impl.UserService01Impl03.*(..))")
    public void after() {
        System.out.println("===============方法执行后=================");
    }

    @Around("execution(* com.lm.service.impl.UserService01Impl03.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();

        System.out.println("环绕后");
        System.out.println(proceed);
    }
}

2、在Spring配置文件中,注册bean,并增加支持注解的配置

<aop:aspectj-autoproxy/>
<bean id="annotationPointcut" class="com.lm.service.aop.AnnotationPointcut"/>

aop:aspectj-autoproxy:说明

通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy  poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

3、测试 并查看结果

@Test
public void test03() {
    ApplicationContext context = new ClassPathXmlApplicationContext("Beans01.xml");
    UserService01 userService = context.getBean("userService03",UserService01.class);
    userService.query();
}

在这里插入图片描述

整合MyBatis

搭建一个普通的MyBatis

1、导入jar包

<dependency><!-- 单元测试的包 -->
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.12</version>
</dependency>
<dependency><!-- mybatis -->
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.2</version>
</dependency>
<dependency><!-- mysql连接器 注意如果你的mysql是5版本的就用5开头的,如果是8版本的就用8开头的 -->
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.47</version>
</dependency>
<dependency><!-- Spring相关 -->
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.1.10.RELEASE</version>
</dependency>
<dependency><!-- Spring相关 -->
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>5.1.10.RELEASE</version>
</dependency>
<dependency><!-- AOP织入包 -->
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>
<dependency><!-- mybatis-spring整合包 -->
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>2.0.2</version>
</dependency>

2、配置Maven静态资源过滤问题

<build>
   <resources>
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>true</filtering>
       </resource>
   </resources>
</build>

3、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--类型别名-->
    <typeAliases>
        <!--指定包配置-->
        <package name="com.lm.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mysqlby1?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    
    <!--mapper的位置-->
    <mappers>
        <mapper class="com.lm.mapper.UserMapper"/>
    </mappers>
</configuration>

4、代码实现

4.1 编写pojo实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
}

4.2 接口编写

public interface UserMapper {
   public List<User> selectUser();
}

4.3 接口对应的Mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper">
   <select id="selectUser" resultType="User">
    select * from user
   </select>
</mapper>

4.4 测试

@Test
public void selectUser() throws IOException {
	//Mybatis的配置文件
   	String resource = "mybatis-config.xml";
   	//加载配置文件到输入流
    InputStream inputStream = Resources.getResourceAsStream(resource);
   	//用会话工厂构建器根据配置文件的输入流进行构建一个会话工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //用会话工厂对象获取到会话
   	SqlSession sqlSession = sqlSessionFactory.openSession();

    //进行操作
   	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
   	List<User> userList = mapper.selectUser();
   	for (User user: userList){
       	System.out.println(user);
  	}

   	sqlSession.close();
}
整合实现一

1、引入Spring配置文件ApplicationContext.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">

2、配置数据源替换mybaits的数据源

<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <!-- 如果执行成功的话也有错误日志 那就把value的数值改为 com.mysql.cj.jdbc.Driver -->
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
   <property name="url" value="jdbc:mysql://localhost:3306/mysqlby1?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
   <property name="username" value="root"/>
   <property name="password" value="123456"/>
</bean>

3、配置SqlSessionFactory,关联MyBatis

<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   	<property name="dataSource" ref="dataSource"/>
   	<!--关联Mybatis value指向的Mybatis的配置文件 -->
   	<property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:com/lm/mapper/*.xml"/>
</bean>

4、注册sqlSessionTemplate,关联sqlSessionFactory;

<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
   <!--利用构造器注入-->
   <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

5、增加Dao接口的实现类;私有化sqlSessionTemplate

public class UserDaoImpl implements UserMapper {
    //sqlSession不用我们自己创建了,Spring来管理  使用set注入
    private SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }
    @Override
    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
<!-- 注册Bean -->
<bean id="userDao" class="com.lm.mapper.impl.UserDaoImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>

6、测试进行数据查询

@Test
public void test02() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    UserMapper userMapper = context.getBean("userDao",UserMapper.class);
    List<User> users = userMapper.selectUser();
    users.forEach(user -> {
        System.out.println(user);
    });
}

结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
   <typeAliases>
       <package name="com.kuang.pojo"/>
   </typeAliases>
</configuration>

整合实现二

mybatis-spring1.2.3版以上的才有这个 .

官方文档截图 :

dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
在这里插入图片描述

1、将我们上面写的UserDaoImpl修改一下

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

2、修改bean的配置

<bean id="userDao" class="com.kuang.dao.UserDaoImpl">
   <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

3、测试

@Test
public void test02() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    UserMapper userMapper = context.getBean("userDao",UserMapper.class);
    List<User> users = userMapper.selectUser();
    users.forEach(user -> {
        System.out.println(user);
    });
}

总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!

声明式事务

1、回顾事务

  • 要么都成功,要么都失败
  • 事务在项目中,十分重要,涉及到数据的一致性问题,不能马虎
  • 确保完整性和一致性。

2、事务ACID原则

  • 原子性
    • 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
  • 一致性
    • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  • 隔离性
    • 多个业务可能操作同一个资源,防止数据损坏
  • 持久性
    • 事务一旦提交,无论系统发生什么问题,结果都不会被影响,被持久化的写到存储器中!
没有事务错误操作

1、先给UserDao接口新增两个方法,删除和增加用户;

//添加一个用户
int addUser(User user);
//根据id删除用户
int deleteUser(int id);

2、mapper的xml文件,我们故意把delete多加个s

<insert id="addUser" parameterType="com.kuang.pojo.User">
	insert into user (id,name) values (#{id},#{name})
</insert>

<!-- 这个方法的delete是故意写错的 用于测试 -->
<delete id="deleteUser" parameterType="int">
	deletes from user where id = #{id}
</delete>

3、接口的实现类,在实现类中,我们去操作一波

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> selectUser() {
        User user = new User(0,"小绿");
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        mapper.addUser(user);
        mapper.deleteUser(6);

        return mapper.selectUser();
    }

    @Override
    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    @Override
    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }
}

4、测试

@Test
public void test01() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    UserMapper userMapper = context.getBean("userDao",UserMapper.class);
    List<User> users = userMapper.selectUser();
    users.forEach(user -> {
        System.out.println(user);
    });

结果会报错,当前一步的插入却成功了

没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!

一起我们都需要自己手动管理事务,十分麻烦!

当时Spring给我们提供了事务管理,我们只需要配置即可;

spring中的事务管理

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

  • 声明式事务:AOP
    • 一般情况下比编程式事务好用
    • 将事务管理代码重业务方法中分离出来,以声明的方式来实现事务管理。
    • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过SpringAOP框架支持声明式事管理
  • 编程式事务:需要在代码中,进行事务的管理
    • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
    • 确定:必须在每个事务操作业务逻辑中保护额外的事务管理代码

使用Spring管理事务,注意头文件的约束导入:tx

xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="dataSource" />
</bean>

配置好事务管理器后我们需要去配置事务的通知

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
       <!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
       <tx:method name="add" propagation="REQUIRED"/>
       <tx:method name="delete" propagation="REQUIRED"/>
       <tx:method name="update" propagation="REQUIRED"/>
       <tx:method name="search*" propagation="REQUIRED"/>
       <tx:method name="get" read-only="true"/>
       <tx:method name="*" propagation="REQUIRED"/>
   </tx:attributes>
</tx:advice>

配置AOP

<!--配置aop织入事务-->
<aop:config>
   <aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/>
   <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

进行测试

@Test
public void test01() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    UserMapper userMapper = context.getBean("userDao",UserMapper.class);
    List<User> users = userMapper.selectUser();
    users.forEach(user -> {
        System.out.println(user);
    });
}
spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
  • Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

Git源码:https://gitee.com/LM720/Spring_S
本文参考来之【【狂神说Java】Spring5最新完整教程IDEA版通俗易懂-哔哩哔哩】 https://b23.tv/uqlchoC

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值