亲宝软件园·资讯

展开

spring学习总结 spring框架学习总结

我实在是想不出什么好听的昵称了啊 人气:0
想了解spring框架学习总结的相关内容吗,我实在是想不出什么好听的昵称了啊在本文为您仔细讲解spring学习总结的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:spring框架,学习总结,下面大家一起来学习吧。

Spring 框架概述

Spring优点

Spring体系结构

Spring 框架采用分层架构,根据不同的功能被划分成了多个模块,这些模块大体可分为 Data Access/Integration、Web、AOP、Aspects、Messaging、Instrumentation、Core Container 和 Test,具体如下图所示:

在这里插入图片描述

Data Access/Integration(数据访问/集成)

数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。

JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。 ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。 OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。 JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。 Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。

Web 模块

Spring 的 Web 层包括 Web、Servlet、Struts 和 Portlet 组件,具体介绍如下。

Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。 Servlet模块:包括 Spring 模型—视图—控制器(MVC)实现 Web 应用程序。 Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。 Portlet 模块:提供了在 Portlet 环境中使用 MV C实现,类似 Web-Servlet 模块的功能。

Core Container(核心容器)

Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言模块组成,具体介绍如下。

Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。 Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。 Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。 Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。

其他模块

Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块,具体介绍如下。

AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。 Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。 Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。 Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。

Spring拓展

Spring Boot与Spring Cloud

Spring IoC 容器 (IoC 也称为依赖项注入(DI),或DI是实现IoC的一种方法)

IoC容器概述

在这里插入图片描述

1.BeanFactory

beanFactory是一个Factory,用于管理bean的,有了一个Spring的beanFactory,我们就可以从spring中获取注册到其中的bean来使用。

2.ApplicationContext

ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。该接口的全路径为:

org.springframework.context.ApplicationContext,它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。

ApplicationContext 接口有两个常用的实现类:ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。 ClassPathXmlApplicationContext从类路径 ClassPath 中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,具体如下所示。ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);configLocation 参数用于指定 Spring 配置文件的名称和位置,如 applicationContext.xml。

FileSystemXmlApplicationContext从指定的文件系统路径中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,具体如下所示。ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,它可以获取类路径之外的资源,如“D:/workspaces/applicationContext.xml”。

3.BeanFactory 和 ApplicationContext区别:

BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean 时才实例目标Bean;而ApplicationContext 则在初始化应用上下文时就实例化所有单实例的Bean 。

在实际开发中,通常都选择使用 ApplicationContext,而只有在系统资源较少时,才考虑使用 BeanFactory。(但是,它们都是通过 XML 配置文件加载 Bean 的。)

Spring入门程序

1.创建maven项目

2.在pom.xml导入jar包依赖

    <dependencies>
        <!--导入spring,maven依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.12.RELEASE</version>
        </dependency>
        <!--导入junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

3.编写接口

package com.xxx.mapper;
/**
 * @author shkstart
 * @create 2021-06-11 15:50
 */
public interface UserMapper {
    public void hello();
}

4.编写接口实现类

package com.xxx.mapper;/**
 * @author shkstart
 * @create 2021-06-11 15:50
 */
/**
 *@program: springTest
 *@description:
 *@author: XieXianXin
 *@create: 2021-06-11 15:50
 */
public class UserMapperImpl implements UserMapper{
    @Override
    public void hello() {
        System.out.println("Spring入门程序!");
    }
}

编写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">
    <!--
    使用Spring来创建对象,在Spring这些都称为Bean
    类型 变量名 = new 类型();
    Hello hello = new Hello();
    id = 变量名
    class = new 的对象
    -->
    <beans>
        <bean id="hello" class="com.xxx.mapper.UserMapperImpl">
        </bean>
    </beans>
</beans>

测试

package com.xxx.mapper;/**
 * @author shkstart
 * @create 2021-06-11 15:57
 */
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 *@program: springTest
 *@description:
 *@author: XieXianXin
 *@create: 2021-06-11 15:57
 */
public class helloTest {
    @Test
    public void helloTest1(){
        // 1. 初始化Spring容器,加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 通过容器获取userMapper实例
        UserMapper hello = context.getBean("hello", UserMapper.class);
        // 3.调用实例中的hello()方法
        hello.hello();

    }
}

测试结果

在这里插入图片描述

IoC创建对象的三种方式

通过无参构造(要提供set方法)

编写实体类User:

public class User {
    private String name;
    // set方法
    public void setName(String name) {
        this.name=name;
    }
    public User() {
        System.out.println("无参构造方法执行了!");
    }
    public void print(){
        System.out.println("学生名字为:"+name);
    }
}

编写Spring核心配置文件:

<!--无参构造,但是要有set方法-->
        <bean id="user" class="com.xxx.pojo.User">
            <property name="name" value="小新"/>
        </bean>

测试以及结果:

 @Test
    public void helloTest2(){
        // 1. 初始化Spring容器,加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 通过容器获取userMapper实例
        User user = context.getBean("user", User.class);
        // 3.调用实例中的print()方法
        user.print();
    }

在这里插入图片描述

通过有参构造(要提供get方法)

编写实体类User:

public class User {
    private String name;
    //get方法
    public String getName() {
        return name;
    }
    public User(String name) {
        System.out.println("有参构造方法执行了!");
        this.name = name;
    }
    public void print(){
        System.out.println("学生名字为:"+name);
    }
}

编写Spring核心配置文件:

 <!--有参构造,但是要有get方法-->
        <bean id="user" class="com.xxx.pojo.User">
            <constructor-arg value="小新2" index="0"/>
        </bean>

测试以及结果:

  @Test
    public void helloTest2(){
        // 1. 初始化Spring容器,加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 通过容器获取userMapper实例
        User user = context.getBean("user", User.class);
        // 3.调用实例中的print()方法
        user.print();
    }

在这里插入图片描述

拓展:Spring核心配置文件有三种写法:

<!--有参构造,但是要有get方法-->
        <bean id="user" class="com.xxx.pojo.User">
            <constructor-arg index="0" value="小新-index属性(0开始,按顺序)"/>
            <constructor-arg name="name" value="小新-name属性"/>
            <constructor-arg type="java.lang.String" value="小新-参数类型"/>
        </bean>

结果展示:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过工厂类

编写工厂类:

public class Factory {
    //方法一,静态方法
    public static User getStaticInstance(){
        return new User("小新2——静态方法创建对象");
    }
    //方法二,实例方法
    public User getInstance(){
        return new User("小新3-实例方法创建对象");
    }
}

编写Spring核心配置文件:

<!--工厂类创建对象-->
        <!--创建工厂-->
        <bean id="factory" class="com.xxx.mapper.Factory"/>
        <!--静态方法对象-->
        <bean id="staticFactory-user" class="com.xxx.mapper.Factory" factory-method="getStaticInstance"/>
        <!--实例方法对象-->
        <bean id="factory-user" factory-bean="factory" factory-method="getInstance"/>

测试以及结果:静态方法:

 @Test
    public void helloTest4(){
        // 1. 初始化Spring容器,加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 通过容器获取userMapper实例
        User user = context.getBean("staticFactory-user", User.class);
        // 3.调用实例中的print()方法
        user.print();
    }

在这里插入图片描述

实例方法:

@Test
    public void helloTest3(){
        // 1. 初始化Spring容器,加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 通过容器获取userMapper实例
        User user = context.getBean("factory-user", User.class);
        // 3.调用实例中的print()方法
        user.print();
    }

在这里插入图片描述

Spring依赖注入(DI)和Bean的作用域

什么是依赖注入:Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过 Spring 容器获得被调用者实例。

依赖注入主要有两种实现方式,分别是属性 setter 注入和构造方法注入,其中setter注入要求重点掌握。

属性 setter 注入讲解:

环境搭建:(创建一个Student和Book类):

Student

package com.xxx.pojo;/**
 * @author shkstart
 * @create 2021-06-11 17:45
 */
import java.util.*;
/**
 *@program: Spring_study
 *@description:
 *@author: XieXianXin
 *@create: 2021-06-11 17:45
 */
public class Student {
    private String name;
    private Book book;
    private String[] course;
    private List<String> hobbies;
    private Map<String,String> card;
    private Set<String> fruit;
    private String marriage;
    private Properties info;
    public Student() {
    }
    public Student(String name, Book book, String[] course, List<String> hobbies, Map<String, String> card, Set<String> fruit, String marriage, Properties info) {
        this.name = name;
        this.book = book;
        this.course = course;
        this.hobbies = hobbies;
        this.card = card;
        this.fruit = fruit;
        this.marriage = marriage;
        this.info = info;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", book=" + book +
                ", course=" + Arrays.toString(course) +
                ", hobbies=" + hobbies +
                ", card=" + card +
                ", fruit=" + fruit +
                ", marriage='" + marriage + '\'' +
                ", info=" + info +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Book getBook() {
        return book;
    }
    public void setBook(Book book) {
        this.book = book;
    }
    public String[] getCourse() {
        return course;
    }
    public void setCourse(String[] course) {
        this.course = course;
    }
    public List<String> getHobbies() {
        return hobbies;
    }
    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }
    public Map<String, String> getCard() {
        return card;
    }
    public void setCard(Map<String, String> card) {
        this.card = card;
    }
    public Set<String> getFruit() {
        return fruit;
    }
    public void setFruit(Set<String> fruit) {
        this.fruit = fruit;
    }
    public String getMarriage() {
        return marriage;
    }
    public void setMarriage(String marriage) {
        this.marriage = marriage;
    }
    public Properties getInfo() {
        return info;
    }
    public void setInfo(Properties info) {
        this.info = info;
    }
}

Book

package com.xxx.pojo;/**
 * @author shkstart
 * @create 2021-06-11 17:45
 */
/**
 *@program: Spring_study
 *@description:
 *@author: XieXianXin
 *@create: 2021-06-11 17:45
 */
public class Book {
    private String name;
    private int id;
    public Book() {
    }
    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public Book(String name, int id) {
        this.name = name;
        this.id = id;
    }
}

常量注入:

<bean class="com.xxx.pojo.Student" id="student">
            <!--常量注入-->
            <property name="name" value="小新"/>
</bean>

Bean注入:

<bean class="com.xxx.pojo.Book" id="book">
           <property name="name" value="Java放弃"/>
           <property name="id" value="100"/>
       </bean>
       <bean class="com.xxx.pojo.Student" id="student">
       	 <!--Bean注入-->
          <property name="book" ref="book"/>
   	 </bean>

数组注入:

<property name="course">
              <array>
                  <value>高数</value>
                  <value>计算机网络</value>
                  <value>数据库</value>
              </array>
           </property>

List注入:

<property name="hobbies">
               <list>
                   <value>唱</value>
                   <value>跳</value>
                   <value>Rap</value>
               </list>
           </property>

Map注入:

<property name="card">
               <map>
                   <entry key="银行卡:" value="2501314"/>
                   <entry key="SFZ:" value="1314520"/>
               </map>
           </property>

Set注入:

 <property name="fruit">
               <set>
                   <value>香蕉</value>
                   <value>苹果</value>
                   <value>雪梨</value>
               </set>
           </property>

Null注入:

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

Properties注入:

<property name="info">
               <props>
                   <prop key="username">小新</prop>
                   <prop key="password">520</prop>
               </props>
           </property>

测试及结果展示:

public class BeanTest {
    @Test
    public void beanTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println(student);
    }
}

在这里插入图片描述

Student{name=‘小新', book=Book{name=‘Java放弃', id=100}, course=[高数, 计算机网络, 数据库], hobbies=[唱, 跳, Rap], card={银行卡:=2501314, SFZ:=1314520}, fruit=[香蕉, 苹果, 雪梨], marriage=‘null', info={password=520, username=小新}}

Process finished with exit code 0

p命名空间(以Book类举例)导入约束 xmlns:p=“http://www.springframework.org/schema/p”

<bean id="pBook" class="com.xxx.pojo.Book" p:name="Java懵懂" p:id="250"/>

测试及结果:

 @Test
    public void cpTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book pBook = context.getBean("pBook", Book.class);
        System.out.println(pBook);
    }

在这里插入图片描述

c命名空间导入约束 xmlns:c=“http://www.springframework.org/schema/c”

<bean id="cBook" class="com.xxx.pojo.Book" c:id="520" c:name="Java入坑"/>

测试及结果:

  @Test
    public void cpTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book cBook = context.getBean("cBook", Book.class);
        System.out.println(cBook);
    }

在这里插入图片描述

作用域种类

singleton(以Book举例)单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。

<bean class="com.xxx.pojo.Book" id="scopeBook" scope="singleton">
            <property name="id" value="1"/>
        </bean>
@Test
    public void scopeTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book book1 = context.getBean("scopeBook", Book.class);
        Book book2 = context.getBean("scopeBook", Book.class);
        System.out.println(book1.hashCode());
        System.out.println(book2.hashCode());
        System.out.println(book1==book2);
    }
}

在这里插入图片描述

prototype 原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例,即每次调用getBean()时,相当于执行了一次new XxxBean()。

<bean class="com.xxx.pojo.Book" id="scopeBook" scope="prototype">
            <property name="id" value="1"/>
        </bean>
@Test
    public void scopeTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book book1 = context.getBean("scopeBook", Book.class);
        Book book2 = context.getBean("scopeBook", Book.class);
        System.out.println(book1.hashCode());
        System.out.println(book2.hashCode());
        System.out.println(book1==book2);
    }
}

在这里插入图片描述

在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。

在同一个 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。

在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

Spring 常用配置及属性

在这里插入图片描述

Spring自动装配

1.组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;

2.自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

autowire 的属性和作用

在这里插入图片描述

public class Student {
   public void study(){
       System.out.println("Student类的方法study执行了");
   }
}
public class Student2 {
    public void study(){
        System.out.println("Student2类的方法study执行了");
    }
}
public class Teacher {
    private Student student;
    private Student2 student2;
    private String teach;
    public Teacher() {
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "student=" + student +
                ", student2=" + student2 +
                ", teach='" + teach + '\'' +
                '}';
    }
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    public Student2 getStudent2() {
        return student2;
    }
    public void setStudent2(Student2 student2) {
        this.student2 = student2;
    }
    public String getTeach() {
        return teach;
    }
    public void setTeach(String teach) {
        this.teach = teach;
    }
    public Teacher(Student student, Student2 student2, String teach) {
        this.student = student;
        this.student2 = student2;
        this.teach = teach;
    }
}

配置Spring核心配置文件

使用autowire=“byName”:

   <bean class="com.xxx.pojo.Student" id="student"/>
 <bean class="com.xxx.pojo.Student" id="student"/>
    <bean class="com.xxx.pojo.Student2" id="student2"/>
    <bean class="com.xxx.pojo.Teacher" id="teacher" autowire="byName">
        <property name="teach" value="Java"/>
    </bean>

测试及结果:

public class BeanTest {
    @Test
    public void beanTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Teacher teacher = context.getBean("teacher", Teacher.class);
        teacher.getStudent().study();
        teacher.getStudent2().study();
    }
}

在这里插入图片描述

若修改Student的bean id值不为student,如:<bean class="com.xxx.pojo.Student" id="s"/>则会报空指针异常java.lang.NullPointerException at BeanTest.beanTest(BeanTest.java:24)。因为按byName规则找不对应set方法,真正的setStudent就没执行,对象就没有初始化,所以调用时就会报空指针错误。

当一个bean节点带有 autowire byName的属性时:

1.将查找其类中所有的set方法名,例如setStudent,获得将set去掉并且首字母小写的字符串,即student。

2.去spring容器中寻找是否有此字符串名称id的对象,如果有,就取出注入;如果没有,就报空指针异常。

Spring注解开发

环境搭建

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

开启属性注解支持!

<context:annotation-config/>

编写一个 Student类

public class Student {
    private String name;
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student() {
    }
    public Student(String name) {
        this.name = name;
    }
}

编写Spring核心配置文件:

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

测试及结果:

@Test
    public void beanTest(){
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = context.getBean("student",Student.class);
        System.out.println(student);

在这里插入图片描述

使用@Configuration和@Bean给容器中注册组件 编写一个配置类

/**
 *@program: springTest
 *@description: 在类上添加@Configuration注解使得该类成为Spring配置类,通过@Bean注解将该类注入到IoC容器,此时配置类==配置文件
 *@author: XieXianXin
 *@create: 2021-06-12 23:06
 */
// 这个配置类也是一个组件
@Configuration// 告诉Spring这是一个配置类
public class AnnotationStudent {
    @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id
    public Student student(){
        return new Student("小新");
    }
}

测试及结果:

    @Test
    public void beanTest(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationStudent.class);
        Student bean = context.getBean(Student.class);
        //返回Student类在IoC容器中的id值
        String[] namesForType = context.getBeanNamesForType(Student.class);
        for (String s : namesForType) {
            System.out.println(s);
        }
        System.out.println(bean);
    }
}

在这里插入图片描述

若在配置类中给@Bean设置一个value值,如@Bean("stu")则测试结果为:

在这里插入图片描述

则我们在使用注解方式向Spring的IOC容器中注入JavaBean时,如果没有在@Bean注解中明确指定bean的名称,那么就会使用当前方法的名称来作为bean的名称;如果在@Bean注解中明确指定了bean的名称,那么就会使用@Bean注解中指定的名称来作为bean的名称。

使用@ComponentScan自动扫描组件并指定扫描规则

开启注解扫描,并删除之前配置文件中的bean

<context:component-scan base-package="com.xxx"/>

在原有环境下创建一个com.xxx.service包,并创建一个Teacher类,并在类上添加一个@Service注解,同时,之前的Student类上也添加一个@Component注解

@Service
public class Teacher {
   private Student student;
   public void teach(){
       System.out.println("教授的学生是"+student);
   }
    @Override
    public String toString() {
        return "Teacher{" +
                "student=" + student +
                '}';
    }
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    public Teacher(Student student) {
        this.student = student;
    }
    public Teacher() {
    }
}

测试及结果:

public class BeanTest {
        @Test
        public void beanTest() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            String[] beanDefinitionNames = context.getBeanDefinitionNames();
            for (String definitionName : beanDefinitionNames) {
                System.out.println(definitionName);
            }
        }
}

在这里插入图片描述

以上可以看到:在配置注解扫描后,只要在com.xxx包下的所有子包中,加上了@Repository(Dao)、@Service(service)、@Controller、(web)@Component注解的类都会被扫描到,并自动注入到Spring容器中。(其实上面四个功能,目前为止是一样的)

我们可以在配置类中(前面的AnnotationStudent)使用@ComponentScan注解配置包扫描,由此代替xml中的<context:component-scan base-package="com.xxx"/>。先注释掉之前的xml方式的注解扫描,接着

@Configuration// 告诉Spring这是一个配置类@ComponentScan(value = "com.xxx")public class AnnotationStudent {    @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id    public Student student(){        return new Student("小新");    }}

测试结果跟之前一样。因此,推荐以后都使用注解扫描就好了,Spring还是尽量用注解开发,MyBatis中还是用xml配置文件。

excludeFilters()不包含哪些包、includeFilters()包含哪些包,使用includeFilters时,需要在XML配置文件中先配置use-default-filters="false",即禁用默认的扫描所有包过滤规则才能生效。另外,ComponentScan还是一个可重复注解的注解,因此可以在一个类上重复使用这个注解。

使用@Scope注解设置组件的作用域

通过在类中添加注解@scope注解设置作用域,如:

// 这个配置类也是一个组件
@Configuration// 告诉Spring这是一个配置类
public class AnnotationStudent {
    @Scope("prototype")
    @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id
    public Student student(){
        return new Student("小新");
    }
}

如果为false。

在这里插入图片描述

注解自动装配组件(@Resource是JDK自带的)

@Autowired注解可以对类成员变量、方法和构造函数进行标注,完成自动装配的工作。@Autowired注解可以放在类、接口以及方法上。等价于<property name="属性名" value=" 属性值"/>@Autowired注解默认是优先按照类型去容器中找对应的组件,即:context.getBean(类名.class);,如果找到多个相同类型的组件,那么是将属性名称作为组件的id,到IOC容器中进行查找,即:context.getBean("组件的id");

@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配,且Qualifier不能单独使用。

是JDK自带的注解 可以按名称注入也可以按类型注入,默认是按名称注入,没有显式指定名称时,在spring容器中匹配与需要注入的bean属性名相同的bean,如果还不同,@Resource会找到一个主类型匹配而不是一个特定的命名bean。

懒加载@Lazy

懒加载就是Spring容器启动的时候,先不创建对象,在第一次使用(获取)bean的时候Xxx xxx = context.getBean(Xxx.class);再来创建对象,并进行一些初始化。使用时,只需要在配置类的方法上加上@Lazy注解即可。

public class AnnotationStudent {
    @Lazy
    @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id
    public Student student(){
        System.out.println("在容器中添加对象!");
        return new Student("小新");
    }
}
public class BeanTest {
        @Test
        public void beanTest() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            System.out.println("容器创建完成!");
            Student student = context.getBean(Student.class);
            Student student1 = context.getBean(Student.class);
            System.out.println(student==student1);
        }
}

在这里插入图片描述

@Configuration// 告诉Spring这是一个配置类
public class AnnotationStudent {
    @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id
    public Student student(){
        System.out.println("在容器中添加对象!");
        return new Student("小新");
    }
}
public class BeanTest {
        @Test
        public void beanTest() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            System.out.println("容器创建完成!");
        }
}

在这里插入图片描述

使用@Import注解给容器中快速导入一个组件

注册bean的方式通常有以下几种:

1.包扫描+给组件标注注解(@Controller、@Servcie、@Repository、@Component

2.@Bean注解

3.@Import注解(只作用在类上,可以在实际开发项目中导入别人的类并注册到容器中,这是两外两种无法做到的)例如在AnnotationStudent配置类上导入Teacher类对应的bean实例(id默认是组件的全类名)

4.使用FactoryBean接口(支持泛式)向Spring容器中注册bean

// 这个配置类也是一个组件
    @Configuration// 告诉Spring这是一个配置类
    @Import(Teacher.class)
    public class AnnotationStudent {
        @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id
        public Student student(){
            return new Student("小新");
        }
    }
public class BeanTest {
        @Test
        public void beanTest() {
           /* ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            */
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationStudent.class);
            String[] beanNamesForType = applicationContext.getBeanDefinitionNames();
            for (String s : beanNamesForType) {
                System.out.println(s);
            }
        }
}

在这里插入图片描述

当去除@Import后,输出结果为:

在这里插入图片描述

Bean生命周期

常意义上讲的bean的生命周期,指的是bean从创建到初始化,经过一系列的流程,最终销毁的过程,如下图所示。在Spring中,我们可以自己来指定bean的初始化和销毁的方法@Bean(initMethod = "自定义的初始化方法名",destroyMethod = "自定义的销毁方法名")。当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。

在这里插入图片描述

自定义一个Life类:

public class Life {
    public Life(){
        System.out.println("Life构造方法执行了!");
    }
    public void init(){
        System.out.println("Life初始化方法执行了!");
    }
    public void destroy(){
        System.out.println("Life销毁方法执行了!");
    }
}

配置类中注册bean:

@Configuration// 告诉Spring这是一个配置类
    public class AnnotationStudent {
        @Bean(initMethod = "init",destroyMethod = "destroy")
        public Life life(){
            return new Life();
        }
    }

测试及结果:

public class BeanTest {
        @Test
        public void beanTest() {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationStudent.class);
            System.out.println("容器创建完成!");
            Life bean = applicationContext.getBean(Life.class);
        }
}

在这里插入图片描述

可以看到,对于单实例对象,先执行构造方法,再到初始化方法,而销毁方法执行需要显式关闭容器时候才执行applicationContext.close();

在这里插入图片描述

因此,我们可以自定义初始化方法和销毁方法处理配置数据源问题,在初始化的时候,会对很多的数据源的属性进行赋值操作;在销毁的时候,我们需要对数据源的连接等信息进行关闭和清理。

@Value注解为属性赋值

在Student类中的name属性上加上@Value注解,等价于配置文件中的<bean id="student" class="com.xxx.pojo.Student"> <property name="name" value="xiaoxin"/> </bean>里的<property name="name" value="xiaoxin"/>,外面的bean是@Component注解作用。

@Component
public class Student {
    @Value("xiaoxin")
    private String name;
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student() {
    }
    public Student(String name) {
        this.name = name;
    }
}

测试及结果:

public class BeanTest {
        @Test
        public void beanTest() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Student student = context.getBean("student", Student.class);
            System.out.println(student);
        }
}

在这里插入图片描述

使用@PropertySource加载配置文件

public class Property {
    private String username;
    private Integer password;
    @Override
    public String toString() {
        return "Property{" +
                "username='" + username + '\'' +
                ", password=" + password +
                '}';
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Integer getPassword() {
        return password;
    }
    public void setPassword(Integer password) {
        this.password = password;
    }
    public Property(String username, Integer password) {
        this.username = username;
        this.password = password;
    }
    public Property() {
    }
}

Spring核心配置文件内容为:

    <context:annotation-config />
    <context:component-scan base-package="com.xxx"/>
    <context:property-placeholder location="applicationContext.properties"/>
    <bean class="com.xxx.pojo.Property" id="property">
        <property name="username" value="${name}"/>
        <property name="password" value="${password}"/>
    </bean>

测试及结果:

public class BeanTest {
        @Test
        public void beanTest() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Property property = context.getBean("property", Property.class);
            System.out.println(property.toString());
        }
}

在这里插入图片描述

注解方式:

@Configuration//表示该类是配置类,等价于核心配置文件
@ComponentScan(value = "com.xxx")//等价于<context:component-scan base-package="com.xxx"/>
@Component//注册bean,默认id为类名(首字母小写),等价于<bean class="com.xxx.pojo.Property" id="property"></bean>
@PropertySource("classpath:applicationContext.properties")//等价于<context:property-placeholder location="applicationContext.properties"/>
public class Property {
    @Value("${name}")//等价于<property name="username" value="${name}"/>
    private String username;
    @Value("${password}")//等价于<property name="password" value="${password}"/>
    private Integer password;
    @Override
    public String toString() {
        return "Property{" +
                "username='" + username + '\'' +
                ", password=" + password +
                '}';
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Integer getPassword() {
        return password;
    }
    public void setPassword(Integer password) {
        this.password = password;
    }
    public Property(String username, Integer password) {
        this.username = username;
        this.password = password;
    }
    public Property() {
    }
}

测试及结果:

public class BeanTest {
        @Test
        public void beanTest() {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Property.class);
            Property bean = applicationContext.getBean(Property.class);
            System.out.println(bean.toString());
        }
}

在这里插入图片描述

代理模式

代理模式:为其他对象提供一种代理以控制对这个对象的访问。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

静态代理

案例:男孩相亲,想找女孩结婚,于是男孩找媒婆进行代理,媒婆代理介绍女孩同时,还要收取一定的介绍费。

接口类

/**
 *@program: springTest
 *@description: 相亲接口
 *@author: XieXianXin
 *@create: 2021-06-13 20:36
 */
public interface Marry {
    //相亲
    void marry();
}

女孩(目标对象)

/**
 *@program: springTest
 *@description: 目标对象
 *@author: XieXianXin
 *@create: 2021-06-13 20:32
 */
public class Girl {
    private String name;
    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                '}';
    }
    public Girl(String name) {
        this.name = name;
    }
    public Girl() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

男孩(被代理对象)

/**
 *@program: springTest
 *@description: 被代理对象
 *@author: XieXianXin
 *@create: 2021-06-13 20:33
 */
public class Boy implements Marry {
    private Girl girl;
    public Boy(Girl girl) {
        this.girl = girl;
    }
    @Override
    public void marry() {
        System.out.println("想跟"+girl.getName()+"认识!");
    }
}

媒婆(代理对象)

/**
 *@program: springTest
 *@description: 代理类
 *@author: XieXianXin
 *@create: 2021-06-13 20:33
 */
public class Proxy implements Marry {
    private Boy boy;
    public Proxy(Girl girl){
         boy = new Boy(girl);
    }
    @Override
    public void marry() {
        boy.marry();
    }
    public void earn(){
        System.out.println("媒婆收取介绍费");
    }
}

测试及结果

public class ProxyTest {
    @Test
    public void proxyTest(){
        Girl girl = new Girl();
        girl.setName("美女!");
        Proxy proxy = new Proxy(girl);
        proxy.marry();
        proxy.earn();
    }
}

在这里插入图片描述

可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情。

公共的业务由代理来完成 . 实现了业务的分工。

公共业务发生扩展时变得更加集中和方便。

冗余,由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。

不易维护,一旦接口增加方法,目标对象与代理对象都要进行修改。

Spring AOP AOP

AOP

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

在这里插入图片描述

总的来说,AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。

AOP中相关概念

横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …

切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

目标(Target):被通知对象。

代理(Proxy):向目标对象应用通知之后创建的对象。

切入点(PointCut):切面通知 执行的 “地点”的定义。

连接点(JointPoint):与切入点匹配的执行点。

SpringAOP中支持5种类型的Advice

在这里插入图片描述

Spring AOP的实现(3种)

在原有的maven的pom.xml文件中加上AOP织入依赖包

<!--使用Spring实现Aop,使用AOP织入,需要导入一个依赖包!-->
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.4</version>
      </dependency>

通过 Spring API 实现

编写业务接口及其实现类

/**
 * @author shkstart 第一种,有接口方式,通过 Spring API 实现,要实现Uservice接口,具体看advice包
 *                  第二种,通过自定义类实现,运用的是AOP定义,不需要实现接口,具体看diy包
 *                  第三种,使用注解实现,具体看annotation包
 * @create 2021-06-04 16:12
 */
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
/**
 *@program: Spring_study
 *@description:
 *@author: XieXianXin
 *@create: 2021-06-04 16:14
 */
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加用户");
    }
    @Override
    public void delete() {
        System.out.println("删除用户");
    }
    @Override
    public void update() {
        System.out.println("更新用户");
    }
    @Override
    public void select() {
        System.out.println("查询用户");
    }
}

编写增强类(分别有前置通知、后置通知和环绕通知)

/**
 *@program: Spring_study
 *@description: 前置通知,在方法前增强,实现MethodBeforeAdvice接口
 *@author: XieXianXin
 *@create: 2021-06-04 16:21
 */
public class BeforeAdvice implements MethodBeforeAdvice {
    //method : 要执行的目标对象的方法
    //args : 被调用的方法的参数
    //target : 目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知的"+target.getClass().getName()+"的"+method.getName()+"方法被执行了");
    }
}
/**
 *@program: Spring_study
 *@description: 后置通知,在方法后执行,实现AfterReturningAdvice接口
 *@author: XieXianXin
 *@create: 2021-06-04 17:00
 */
public class AfterAdvice implements AfterReturningAdvice {
    //returnValue 返回值
    //method被调用的方法
    //args 被调用的方法的对象的参数
    //target 被调用的目标对象
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置通知的"+target.getClass().getName()+"的"+method.getName()+"执行了,返回值为:"+returnValue);
    }
}
/**
 *@program: Spring_study
 *@description: 环绕通知,在方法前后执行,实现MethodInterceptor接口
 *@author: XieXianXin
 *@create: 2021-06-04 17:07
 */
public class InterceptAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            System.out.println("环绕通知"+invocation.getMethod().getName()+"——方法前执行的");
            Method invocationMethod = (Method) invocation.proceed();
            System.out.println("环绕通知"+invocation.getMethod().getName()+"——方法后执行的");
            return invocationMethod;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return invocation;
    }
}

配置Spring核心配置文件,实现AOP切入

    <!--第一种方式,通过接口实现-->
    <!--1.注册bean-->
    <bean id="userService" class="com.xxx.service.UserServiceImpl"/>
    <bean id="beforeAdvice" class="com.xxx.advice.BeforeAdvice"/>
    <bean id="afterAdvice" class="com.xxx.advice.AfterAdvice"/>
    <bean id="interceptAdvice" class="com.xxx.advice.InterceptAdvice"/>
    <bean id="throwAdvice" class="com.xxx.advice.ThrowAdvice"/>
    <!--2.aop的配置-->
    <aop:config>
        <!--切入点 expression:表达式匹配要执行的方法-->
        <aop:pointcut id="pointCut" expression="execution(* com.xxx.service.UserServiceImpl.*(..))"/>
        <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
        <!--前置通知-->
        <aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointCut"/>
        <!--后置通知-->
        <aop:advisor advice-ref="afterAdvice" pointcut-ref="pointCut"/>
        <!--环绕通知-->
        <aop:advisor advice-ref="interceptAdvice" pointcut-ref="pointCut"/>
        <!--异常抛出通知-->
        <aop:advisor advice-ref="throwAdvice" pointcut-ref="pointCut"/>
    </aop:config>

测试及结果

public class UserServiceImplTest {
    @Test
    public void myTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //动态代理的是接口,不是实体类,因此不是UserServiceImpl.class
        UserService userService = context.getBean("userService", UserService.class);
        userService.delete();
        System.out.println("==============================");
        UserService userService1 = context.getBean("userService", UserService.class);
        userService1.add();
        System.out.println("==============================");
        UserService userService2 = context.getBean("userService", UserService.class);
        userService2.select();
        System.out.println("==============================");
        UserService userService3 = context.getBean("userService", UserService.class);
        userService3.update();
    }
}

在这里插入图片描述

通过自定义类来实现 保留之前的业务类UserServiceImpl编写自定义类DiyPointcut

/**
 *@program: Spring_study
 *@description: 自定义类实现AOP,一个类相当于一个切面,类的方法相当于通知
 *@author: XieXianXin
 *@create: 2021-06-04 21:16
 */
public class DiyPointcut {
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    public void afterAdvice(){
        System.out.println("后置通知");
    }
    public void interceptAdvice(ProceedingJoinPoint joinPoint){//环绕通知要有ProceedingJoinPoint joinPoint参数
        System.out.println("方法"+joinPoint.getSignature().getName()+"环绕通知前执行的语句");
        Object[] args = joinPoint.getArgs();
        try {
            Object proceed = joinPoint.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("方法"+joinPoint.getSignature().getName()+"环绕通知后执行的语句");
    }
}

配置Spring核心配置文件

    <!--第二种方式,通过自定义类实现-->
    <!--1.注册bean-->
    <bean id="userService" class="com.xxx.service.UserServiceImpl"/>
    <bean id="diyPointcut" class="com.xxx.diy.DiyPointcut"/>
    <aop:config>
        <!--2.使用AOP标签-->
        <aop:aspect ref="diyPointcut">
            <!--3.切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.xxx.service.UserServiceImpl.*(..))"/>
            <!--4.通知-->
            <!--前置通知-->
            <aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
            <!--后置通知-->
            <aop:after method="afterAdvice" pointcut-ref="pointcut"/>
            <!--环绕通知-->
            <aop:around method="interceptAdvice" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

测试及结果

public class UserServiceImplTest {
    @Test
    public void myTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //动态代理的是接口,不是实体类,因此不是UserServiceImpl.class
        UserService userService = context.getBean("userService", UserService.class);
        userService.delete();
        System.out.println("==============================");
        UserService userService1 = context.getBean("userService", UserService.class);
        userService1.add();
        System.out.println("==============================");
        UserService userService2 = context.getBean("userService", UserService.class);
        userService2.select();
        System.out.println("==============================");
        UserService userService3 = context.getBean("userService", UserService.class);
        userService3.update();
    }
}

在这里插入图片描述

通过自定义类来实现 编写注解实现的增强类AnnotationAdvice

/**
 *@program: Spring_study
 *@description: 使用注解进行AOP设计
 *@author: XieXianXin
 *@create: 2021-06-04 22:18
 */
@Aspect
public class AnnotationAdvice {
    @Before("execution(* com.xxx.service.UserServiceImpl.*(..))")//表达式中写要被增强的类
    public void before(){
        System.out.println("前置通知");
    }
    @After("execution(* com.xxx.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("后置通知");
    }
    @Around("execution(* com.xxx.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知执行前");
        System.out.println("签名:"+joinPoint.getSignature());
        //执行目标方法proceed
        Object proceed = joinPoint.proceed();
        System.out.println("环绕通知执行后");
        System.out.println(proceed);
    }
}

开启注解扫描和注册bean

<!--指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.xxx.service"/>
    <context:annotation-config/>
 <aop:aspectj-autoproxy proxy-target-class="false"/>
    <!--2.注册bean,只需要注册增强的那个类-->
    <bean class="com.xxx.service.UserServiceImpl" id="userService"/>
    <bean id="annotationAdvice" class="com.xxx.annotation.AnnotationAdvice"/>

测试及结果

public class UserServiceImplTest {
    @Test
    public void myTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //动态代理的是接口,不是实体类,因此不是UserServiceImpl.class
        UserService userService = context.getBean("userService", UserService.class);
        userService.delete();
        System.out.println("==============================");
        UserService userService1 = context.getBean("userService", UserService.class);
        userService1.add();
        System.out.println("==============================");
        UserService userService2 = context.getBean("userService", UserService.class);
        userService2.select();
        System.out.println("==============================");
        UserService userService3 = context.getBean("userService", UserService.class);
        userService3.update();
    }
}

在这里插入图片描述

Spring事务管理及Spring整合MyBatis代码示例

Spring事务管理

原子性(atomicity)

事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用

一致性(consistency)

一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中

隔离性(isolation)

可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏

持久性(durability)

事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中Spring支持编程

声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。

显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。

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

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

声明式事务配置拓展:

JDBC事务

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

自动代理的配置

!-- Spring事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务的传播特性 -->
<bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true" >
  <property name="transactionManager" ref="transactionManager" />
  <property name="transactionAttributes">
    <props>
      <prop key="add*">PROPAGATION_REQUIRED</prop>
      <prop key="edit*">PROPAGATION_REQUIRED</prop>
      <prop key="remove*">PROPAGATION_REQUIRED</prop>
      <prop key="insert*">PROPAGATION_REQUIRED</prop>
      <prop key="update*">PROPAGATION_REQUIRED</prop>
      <prop key="del*">PROPAGATION_REQUIRED</prop>
      <prop key="*">readOnly</prop>
    </props>
  </property>
</bean>

基于 命名空间的声明式事务管理

<beans......>
  ......
  <bean id="bankService" 
  class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
  </bean>
  <tx:advice id="bankAdvice" transaction-manager="transactionManager">
    <tx:attributes>
      <tx:method name="transfer" propagation="REQUIRED"/>
    </tx:attributes>
  </tx:advice>
  <aop:config>
    <aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
    <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
  </aop:config>
  ......
</beans>

启用tx的annotation:

<tx:annotation-driven transaction-manager="transactionManager"/>

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

编程式事务管理

编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。

事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

在这里插入图片描述

事务的第二个维度就是隔离级别(isolation level)。

脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

Spring结合事务整合MyBatis示例

1.导入相关Jar包

<!--Spring整合Mybatis需要如下包,都是放在dependencies内-->
    <dependencies>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--导入spring,maven依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.12.RELEASE</version>
        </dependency>
        <!--使用Spring实现Aop,使用AOP织入-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <!--spring操作数据库也需要一个spring-jdbc包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.7</version>
        </dependency>
        <!--整合必要的一个包,mybatis-spring,使用2.0以上版本-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>
        <!--LOG4J-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <!--需要解决的乱码以及maven静态资源过滤问题等在build内完成-->
    <!--解决单元测试中文乱码-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.4</version>
                <configuration>
                    <argLine>
                        -Dfile.encoding=UTF-8
                    </argLine>
                </configuration>
            </plugin>
        </plugins>
        <!--可能出现问题说明:Maven静态资源过滤(导出)问题
         Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration.
         Cause: java.io.IOException: Could not find resource com/xxx/dao/UserMapper.xml
         原因是idea默认不编译src目录下的xml文件,所以加载不到
         解决办法在pom文件中的build标签内加入如下配置,则可以找到java和resources下的所有properties和xml文件了
     -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

2.编写配置文件及加入日志

mybatis-config.xml

<configuration>
    <!--
    configuration" 里的标签顺序如下:(否则报错如下信息)
     "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?
     objectWrapperFactory?,reflectorFactory?,plugins?,environments?,
     databaseIdProvider?,mappers?)".
    -->
    <!--标准的日志工厂实现(常用:STDOUT_LOGGING,LOG4J),下面的value值建议去mybaits文档复制
        日志就是记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。
    -->
    <settings>
        <!--下划线驼峰自动转换-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <!--给这个包下的类起别名-->
    <typeAliases>
        <package name="com.xxx.pojo"/>
    </typeAliases>
    <mappers>
        <mapper resource="com/xxx/mapper/UserMapper.xml"/>
    </mappers>
    
</configuration>

spring-mybatis.xml

<!--
spring整合mybatis,根据mybatis-spring文档可以,需要一个数据源获取SqlSessionFactory 和至少一个数据映射器类
具体查看文档:http://mybatis.org/spring/zh/getting-started.html
-->
    <!--
    DataSource:使用Spring的数据源替换Mybatis的配置:druid c3p0,dbcp
    这里使用Speing提供的JDBC:org.springframework.jdbc.datasource.DriverManagerDataSource
    前提是要导入:spring-jdbc 包
    -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybaits?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!--MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    <!--跟在mybatis中学习一样,需要在mybatis核心配置文件绑定xxxmapper.xml文件
        这里也需要绑定mybatis核心配置文件,绑定后,mybatis核心配置文件可以完成的这里也都可以完成,则mybatis-config文件可以不要也行
    -->
        <!--绑定mybatis-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--注册映射器,等价于mybatis核心配置文件中的:
            <mappers>
            <mapper resource="com/xxx/mapper/UserMapper.xml"/>
            </mappers>
        -->
        <!--<property name="mapperLocations" value="classpath:com/xxx/mapper/*.xml"/>-->
    </bean>
    <!--注册SqlSessionTemplate,相当于我们使用的sqlSession,因此可将id命名为此好记-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--因为sqlSessionTemplate只有构造方法而无set方法,只能使用构造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    <!--配置声明式事务(AOP原理,不改变源代码条件下增加事务),而编程式事务要在源代码上自动try catch
        具体可查看文档:http://mybatis.org/spring/zh/transactions.html
    -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--结合AOP实现事务的织入-->
    <!--配置事务的通知-->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <!--给具体方法配置事务和传播新特性(propagation=REQUIRED是默认的,即会自动创建事务)
             具体查看:https://blog.csdn.net/edward0830ly/article/details/7569954
             name="*"表示给所有方法配置事务,也可给具体方法,给出方法名即可
             -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
   <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="transactionPointcut" expression="execution(* com.xxx.mapper.*.*(..))"/>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
    </aop:config>

applicationContext.xml

 <import resource="spring-mybatis.xml"/>
    <bean id="userMapperImpl_2" class="com.xxx.mapper.UserMapperImpl_2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/xxx.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3.编写接口及其实现类和配置对应的mapper.xml文件

UserMapper接口

public interface UserMapper {
    //查询所有用户
    public List<User> queryUser();
    //添加一个用户
    int addUser(User user);
    //根据id删除用户
    int deleteUser(int id);
}

UserMapperImpl_2实现类

/**
 *@program: Spring_study
 *@description: spring-mybatis整合方式二:继承SqlSessionDaoSupport实现接口
 *@author: XieXianXin
 *@create: 2021-06-05 22:08
 */
public class UserMapperImpl_2 extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> queryUser() {
        return getSqlSession().getMapper(UserMapper.class).queryUser();
    }
    @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);
    }
}

UserMapper.xml

<!--namespace==绑定一个对应的Dao/Mapper接口,以后Mapper.xml文件都放在resourse下,
                                            但是要建立一个跟Mapper接口相对应得包
    注意!!这里有一个坑,当在resources下建立包时候,不要写为:com.xxx.dao
                                                      应该为:com/xxx/dao
                                           -->
<!--诡异事件,在学习mabatis适合,写UTF-8没错,但是整合这里的所有XML却报错:1 字节的 UTF-8 序列的字节 1 无效。
    解决方法:将所有的XML文件UTF-8改为UTF8即可-->
<mapper namespace="com.xxx.mapper.UserMapper">
    <!--
    last_name已经进行自动驼峰转换,则这里不用resultMap进行不同名的映射
    resultType中也起了别名,不用再写com.xxx.pojo了
    -->
    <select id="queryUser" resultType="User">
        select * from user
    </select>
    <insert id="addUser" parameterType="User">
        insert into user (id,last_name,email) values (#{id},#{lastName},#{email})
    </insert>
    <delete id="deleteUser" parameterType="_int">
        delete from user where id = #{id}
    </delete>
</mapper>

测试及结果

手动设置错误,如在插入语句上写错insert为inserts

  <insert id="addUser" parameterType="User">
        inserts into user (id,last_name,email) values (#{id},#{lastName},#{email})
    </insert>
public class UserMapperTest {
    static Logger logger = Logger.getLogger(UserMapperTest.class);
    @Test
   @Test
    public void userMapperImpl_2(){
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapperImpl_2 = context.getBean("userMapperImpl_2", UserMapper.class);
        userMapperImpl_2.addUser(new User(12,"xiaoxin","com@xiaoxin"));
        userMapperImpl_2.deleteUser(8);
        for (User user : userMapperImpl_2.queryUser()) {
            System.out.println(user);
        }
    }
}

如果为插入语句错误,则项目不能正常插入,事务会回滚。

在这里插入图片描述

查看并刷新数据库表,没有变化。

在这里插入图片描述

接着将错误改正后,再次测试结果为:

在这里插入图片描述

成功添加和删除,事务保证了数据的一致性。查看数据库表为:

在这里插入图片描述

总结

本篇文章的内容就到这了,希望大家可以喜欢,也希望大家可以多多关注的其他精彩内容!

加载全部内容

相关教程
猜你喜欢
用户评论