Spring

1、Spring

1.1.简介

  • Spring是一个分层的轻量级框架,以IOC(Inversion of Control控制反转)和AOP(面向切片编程)为内核,使用简单的JavaBean来完成以前只能由EJB(Enterprise Java Beans)完成的工作,取代了臃肿,低效的EJB。
  • Spring致力于JavaEE应用各层的解决方案,是企业应用一站式开发很好的选择,在表现层它提供了Spring MVC以及整合Struts的功能,在业务逻辑层可以管理事务、记录日志等,在持久层可以整合Hibernate、Mybatis等框架。虽然Spring贯穿表现层、业务逻辑层、持久层,但Spring并不是要取代那些已有的优秀框架,而是可以高度开放的与其它优秀框架无缝整合。
  • 2002,首次推出了Spring框架的雏形,interface21框架。Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其中内涵于2004年3月24是发布了1.0正式版。
  • Rod Johnson,Spring Framework创始人,著名作者。很难想象Rod Johnson的学历,真的让多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
  • Spring设计理念:使用现有的技术更加容易使用,本身就是一个大杂烩,整合了现有的技术框架。
  • SSH:Struct2 + Spring + Hibernate
  • SSM:Spring + SpringMVC + Mybatis

官网:Spring Framework

Github:Spring · GitHub

官方下载地址:repo.spring.io

Maven依赖:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

1.2 优点

  1. 非侵入式设计Spring是一种非侵入式(no-invasive)框架,它可以使应用程序代码对框架的依赖最小化。
  2. 方便解耦,简化开发Spring是一个大工厂,可以将所有对象的创建,依赖关系的维护交给Spring容器管理,大大降低了组建之间的的耦合。
  3. 支持AOP允许将一些通用任务,比如安全,事务,日志等,进行集中式管理,从而提高程序的复用性。
  4. 支持声明式事务处理通过配置就可以完成对事务的管理,无需手动编程。
  5. 方便整合其他优秀框架Spring可以与大多数框架无缝整合。
  6. 测试方便Spring支持Junit4,可通过注解测试程序,非常方便。
  7. 降低了使用JavaEE API的难度Spring对Java EE开发中駗的一些API进行了封装,降低了这些API的使用难度。
  8. 开源,免费

1.3 组成

img

核心容器(Spring Core)

  核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。

应用上下文(Spring Context)

  Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。

面向切面编程(Spring AOP)

  AOP(Aspect Oriented Programming)

  通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

JDBC和DAO模块(Spring DAO)

  JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。

对象实体映射(Spring ORM)

  ORM(Object Relational Mapping)

  Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。

Web模块(Spring Web)

  Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

MVC模块(Spring Web MVC)

  MVC(Model View Controller)

  MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。

1.4 扩展

  • Spring Boot
    • 基于SpringBoot可以快速开发单个微服务。
    • 约定大于配置。
  • Spring Cloud
    • SpringCloud是基于SpringBoot实现的。

学习SpringBoot的前提,需要完全掌握Spring及SpringMVC。承上启下的学习。

Spring弊端:发展了太久之后违背了原来的理念配置十分繁琐

2、IOC理论推导

2.1 IOC原型

  1. UserDao接口public interface UserDao {
    void getUsr();
    }
  2. UserDaoImpl实现类public class UserDaoImpl implements UserDao{
    @Override
    public void getUsr() {
    System.out.println(“默认实现”);
    }
    }
  3. UserService业务接口public interface UserService {
    void getUser();
    }
  4. UserServiceImpl业务实现类public class UserServiceImpl implements UserService{
    private UserDao userDao = new UserServiceImpl;
    @Override
    public void getUser() {
    userDao.getUsr();
    }
    }

在前面的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码,如果程序代码量十分在,修改一次的成本代码十分昂贵。

为此,我们使用一个set接口实现,此刻代码已经发生革命性的变化。

private UserDao userDao;

public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

之前,程序是要主动创建对象,控制权在程序员手上。

使用了set注入后,程序不再具有主动性,而是变成了被用的接收对象。

这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上,这就是IOC的原型。

2.2 IOC的本质

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

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者全为一体。

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

3、HelloSpring

定义一个实体类

User.java

public class Hello {
private String name;

public Hello() {

}

// get/set方法

@Override
public String toString() {
return "Hello{" +
"name='" + name + '\'' +
'}';
}
}

在Spring配置文件中配置我们的实体类

  • bean标签:将类实例化,并由Spring容器管理
    • id属性:从容器中获得对象时需要的标识,可以理解为bean的名称
    • class属性:指定该bean实例化的是哪个实体类
  • property标签:为实体类中的某个属性注入值,嵌套在bean中使用。
    • name属性:为类中哪个属性注入值
    • ref:将某个bean作为属性值注入到该name指定的属性
    • value:直接为name指定的属性赋值

Beans.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-4.2.xsd">
<bean id="hello" class="org.example.pojo.Hello">
<property name="name" value="Spring"/>
</bean>

<bean id="user" class="org.example.pojo.User">

<!--
类型 变量名 = new 类型();
Hello hello = new Hello();

id = 变量名
class = new 全限定名对象
property 相当于给对象中的属性设置一个值,可以理解为调用set方法。
-->
<property name="name" value="年少有为"/>
<!--
ref:引用Spring容器中创建好的对象
values:具体的值,基本数据类型
-->
<property name="hello" ref="hello"/>
</bean>
</beans>

解析一个或多个配置文件,并从容器中通过bean的id获取bean对象,默认情况下是以单例模式创建的。

MyTest.java


public class MyTest {
public static void main(String[] args) {
// 解析beans.xml文件,生成管理相应的bean对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "bean.xml");
// getBean:参数即为spring配置文件中的bean标签的id
// Hello hello = (Hello) applicationContext.getBean("hello");
// System.out.println("hello.toString() = " + hello.toString());

User user = (User) applicationContext.getBean("user");
System.out.println("user = " + user);
}
}

思考问题?

  • Hello对象是谁创建的?Hello对象是由spring创建的
  • Hello对象的属性是怎么设置的?Hello对象的属性是由Spring容器设置的

这个过程就叫控制反转。

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

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

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

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

OK,到目前为止,我们彻底不用在程序中去发动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话概括:对象由Spring来创建,管理,装配。

4、Spring创建对象的方式

  1. 默认使用无参构造创建对象,所以我们的类如何没有提供无参构造在获取bean的时候是会报错的
  2. 假设我们要使用有参构造创建对象
    1. 下标赋值,下标从0开始<bean id=”user” class=”org.example.pojo.User”>
      <constructor-arg index=”0″ value=”年少有为”/>
      </bean>
    2. 类型赋值,符合类型的构造器将被赋值<bean id=”user” class=”org.example.pojo.User”>
      <constructor-arg type=”java.lang.String” value=”年少有为”/>
      </bean>
    3. 直接通过参数名<bean id=”user” class=”org.example.pojo.User”>
      <constructor-arg name=”name” value=”年少有为”/>
      </bean>

注意:

  1. 在<constructor-arg>和<property>一并存在时,<property>会覆盖<constructor-arg>设置的值
  2. 在配置文件加载的时候,容器中管理的对象已经被初始化了(并不是获取bean时才创建的),并且每个对象都是以单例模式创建的。

5、Spring配置说明

5.1 别名

<!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
<!--
name:bean中的某一个id
alias:别名
注意:
1,一个bean可以使用多个alias标签配置多个别名
-->
<alias name="user" alias="user2"/>

5.2 Bean的配置

<!--
id:bean的唯一标识符,相当于我们学的对象名
class:bean对象对应的全限定名:包名 + 类型
name:别名,相比alias标签,该属性可以同时取多个别名,多个别名以逗号或空格分隔
scope:创建对象的方式:prototype原型,singleton单例,默认是以单例模式创建的
-->
<bean id="user" class="org.example.pojo.User" name="u1 u2 u3">
<constructor-arg name="name" value="年少有为"/>
</bean>

注意:如果alias标签和bean的name同时存在并且别名一致,最终结果是只配置了一个别名,如果同时存在并且别名不一致,那么会存在alias别名和bean标签name属性配置两个别名。

5.3 import

这个import,一般用于团队开发使用,它可以将多个配置文件,导入合并为一个

假设,现在项目中有三个人在开发,这三个个负责不同的类开发,不同的类需要注册在不同的bean中,我们可以用import标签将所有人的beans.xml合并为一个总的。

  1. 老王 beans1.xml
  2. 老李 beans2.xml
  3. 老四 beans3.xml

applicationContext.xml

<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

使用的时候,使用总的配置就可以了,并且如果多个配置文件中有重复的内容,也会被合并,所以除了一些特殊情况外理论上不会有冲突的问题,如果配置文件中有重复配置的内容,后来者居上。

6、依赖注入

6.1 构造器注入

前面已经说过了。

6.2 Set方法注入(重点)

依赖注入:Set注入。

依赖:bean对象的创建依赖于容器。

注入:bean对象中的所有属性,由容器来注入。

【环境搭建】

  1. 复杂类型public class Address {
       private String address;

       public Address(String address) {
           this.address = address;
      }

       public String getAddress() {
           return address;
      }

       public void setAddress(String address) {
           this.address = address;
      }

       @Override
       public String toString() {
           return “Address{” +
                   “address='” + address + ‘\” +
                   ‘}’;
      }
    }
  2. 真实测试类型public class Data {
       private String name;
       private Address address;
       private String[] strings;
       private List<String> stringList;
       private Map<String, String> map;
       private Set<String> stringSet;
       private String wife;
       private Properties properties;
    }    
  3. beans.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-4.2.xsd”>
       <bean id=”data” class=”org.example.pojo.Data”>
           <property name=”name” value=”年少有为”/>
       </bean>
    </beans>
  4. 测试类public class MyTest {
    public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext( “bean.xml”);
    Data data = (Data) applicationContext.getBean(“data”);
    System.out.println(“data = ” + data);
    }
    }
  5. 完善注入<?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-4.2.xsd”>
    <bean id=”hello” class=”org.example.pojo.Hello”>
    <property name=”name” value=”Spring”/>
    </bean>

    <bean id=”address” class=”org.example.pojo.Address”>
    <property name=”address” value=”中国”/>
    </bean>

    <bean id=”data” class=”org.example.pojo.Data”>
    <!– String 注入 values –>
    <property name=”name” value=”年少有为”/>

    <!– Bean注入,ref,通过bean的id去引用某个bean –>
    <property name=”address” ref=”address”/>

    <!– Array,注入数组,这个不难理解 –>
    <property name=”strings”>
    <array>
    <value>第一行数据</value>
    <value>第二行数据</value>
    <value>第三行数据</value>
    <value>第四行数据</value>
    </array>
    </property>

    <!– List,List集合注入,和数组注入有点类似 –>
    <property name=”stringList”>
    <list>
    <value>第一行数据</value>
    <value>第二行数据</value>
    <value>第三行数据</value>
    <value>第四行数据</value>
    </list>
    </property>

    <!– null,如果property的value指定的是一个空串,如果想指定一个null,加上<null/>即可。 –>
    <property name=”wife”>
    <null/>
    </property>

    <!– Map,不同的是map使用的不是value标签来注入值,而是使用entry来注入 –>
    <property name=”map”>
    <map>
    <entry key=”name” value=”姓名”/>
    <entry key=”age” value=”18″/>
    <entry key=”sex” value=”男”/>
    </map>
    </property>

    <!– Set,不难理解,大同小异 –>
    <property name=”stringSet”>
    <set>
    <value>年少有为</value>
    <value>嘿嘿</value>
    <value>哈哈</value>
    </set>
    </property>

    <!– Properties –>
    <property name=”properties”>
    <props>
    <prop key=”name”>姓名</prop>
    <prop key=”age”>年龄</prop>
    <prop key=”sex”>性别</prop>
    </props>
    </property>
    </bean>
    </beans>
  6. 注入结果data = Data{name=’年少有为’, address=Address{address=’中国’}, strings=[第一行数据, 第二行数据, 第三行数据, 第四行数据], stringList=[第一行数据, 第二行数据, 第三行数据, 第四行数据], map={name=姓名, age=18, sex=男}, stringSet=[年少有为, 嘿嘿, 哈哈], wife=’null’, properties={age=年龄, name=姓名, sex=性别}}

6.3 扩展方式注入

6.3.1 在beans标签属性中导入对应的xml约束

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

6.3.2 p命名注入及c命名注入

  • 自定义bean类public class Hello {
    private String name;

    public Hello() {
    System.out.println(“hello”);
    }

    public Hello(String name) {
    this.name = name;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    @Override
    public String toString() {
    return “Hello{” +
    “name='” + name + ‘\” +
    ‘}’;
    }
    }

    public class User {
    private String name;
    private Hello hello;

    public User(Hello hello) {
    this.hello = hello;
    }

    public Hello getHello() {
    return hello;
    }

    public void setHello(Hello hello) {
    this.hello = hello;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }


    @Override
    public String toString() {
    return “User{” +
    “name='” + name + ‘\” +
    “, hello=” + hello +
    ‘}’;
    }
    }
  • beanx.xml 使用p与c命名注入
    • p:注入的是属性,可以代替<property>标签直接在bean标签的属性上注入属性值
    • c:注入的是构造函数,可以代替<constructor-arg>标签直接在bean标签的属性上注入属性值
    <?xml version=”1.0″ encoding=”UTF-8″?>
    <beans xmlns=”http://www.springframework.org/schema/beans”
    xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
    xmlns:p=”http://www.springframework.org/schema/p”
    xmlns:c=”http://www.springframework.org/schema/c”
    xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd”>
    <bean id=”user” class=”org.example.pojo.User” p:name=”注入property” c:hello=”注入构造器”/>
    </beans>
  • 测试类public class MyTest {
    public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext( “bean.xml”);
    User user = (User) applicationContext.getBean(“user”);
    System.out.println(“user = ” + user);
    }
    }
  • 结果user = User{name=’注入property’, hello=Hello{name=’注入构造器’}}

6.4 Bean作用域

image-20210710095758023
  1. singleton 单例模式(Spring默认的机制)<bean id=”user” class=”org.example.pojo.User” scope=”singleton”/>
  2. prototype 原型模式(每次从容器中get对象的时候,都会创建一个新的对象)<bean id=”user” class=”org.example.pojo.User” scope=”prototype”/>
  3. 其余的request、session、application、websocket,这些只能在web开发中使用到,学过Servlet的应该不陌生。

7、bean的自动装配

JDK1.5支持的注解,Spring2.5就支持了。

自动装配是Spring满足bean依赖的一种方式,Spring会在上下文中自动寻找,并自动给bean装配属性。

在Spring中有三种装配的方式

  1. 在xml中显式的配置
  2. 在java中显式配置
  3. 隐式的自动装配bean(重要)

环境搭建

public class Cat {
public void echo() {
System.out.println("Cat.echo");
}
}

public class Dog {
public void echo() {
System.out.println("Dog.echo");
}
}

public class People {
private Dog dog;
private Cat cat;

public Dog getDog() {
return dog;
}

public void setDog(Dog dog) {
this.dog = dog;
}

public Cat getCat() {
return cat;
}

public void setCat(Cat cat) {
this.cat = cat;
}

@Override
public String toString() {
return "People{" +
"dog=" + dog +
", cat=" + cat +
'}';
}
}

7.1 byName自动装配

在bean标签中指定autowire=”byName”属性后,我们可以不手动注入值,让Spring容器根据属性名从一下文中推断。

<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
<bean id="dog" class="com.ziyia.pojo.Dog"/>
<bean id="cat" class="com.ziyia.pojo.Cat"/>

<bean id="people" class="com.ziyia.pojo.People" autowire="byName">
<property name="name" value="年少有为/"/>
</bean>
</beans>

7.2 byType自动装配

在bean标签中指定autowire=”byType”属性后,我们可以不手动注入值,让Spring容器根据类型从一下文中推断。

<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
<bean id="dog" class="com.ziyia.pojo.Dog"/>
<bean id="cat" class="com.ziyia.pojo.Cat"/>

<bean id="people" class="com.ziyia.pojo.People" autowire="byType">
<property name="name" value="年少有为/"/>
</bean>
</beans>

小结:

  • byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致。
  • byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性类型一致。
  • 使用byType和byName的时候,属性必须提供set方法,不然是装配不成功的。

7.4、使用注解实现自动装配

使用注解须知:

  1. 导入约束:context约束xmlns:context=”http://www.springframework.org/schema/context”
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd”
  2. 配置注解的支持:context:annotation-config/
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置注解的支持 -->
<context:annotation-config/>
</beans>

@Autowired

  • 使用直接在成员变量上使用即可,也可以在属性的set方法上使用。
  • required属性如果我们不确定属性是否可装配成功,可以把该属性设置为false

使用Autowired后我们可以不用编写set方法了,前提是该自动装配的属性在iIOC(Spring)容器中存在,且唯一。

装配策略:默认使用byType来装配,如果发现多个实例,则用byName装配,如果还没有装配成功,会判断bean标签中是否配置了primary=true属性,有配置则返回,否则,还可以用@Qualifier注解来指定装配的bean。另外如果@ Autowired和@Qualifier同时存在的话最终是以@Qualifier来装配的。

注意:

  • 如果是直接给属性装配,请不要在构造方法中直接使用装配的属性,因为装配时机是在构造方法执行之后,在构造方法执行期间,属性并没有被装配,直接使用就会异常。解决办法是在构造器中注入。
  • 特别注意,当一个类只有一个有参构造器(唯一构造器),且该构造器不一定需要是public修饰的, 组件注入的时候不需要指定在构造器方法上或者构造器参数上指定@Autowired,只需要声明构造器即可
  • 一般推荐注入位置放在构造器上,因为不管字段还是方法的方式注入,都是先创建组件,再注入依赖的组件,如果在构造方法上就需要使用依赖的组件,那么只有在构造器上注入才是可以实现的,因为执行顺序的问题;
@Autowired
private Dog dog;

@Autowired
private Cat cat;

@Resource

该注解和@Autowired是一样的,区别在于

  • Resource是先按照byName装配然后再按照byType来装配
  • Resource可以指定name属性去装配指定名称的bean,@Resource如果定义了name属性的值, 就只按照name值匹配
  • Resource 只能注入方法和属性,不能注入构造方法,而@Autowired可以构造构造方法

后面的情况和@Autowired是一样的。

@Resource
private Dog dog;

@Resource(name = "mycat")
private Cat cat;

@Nullable

注解可以标注在方法、字段、参数之上,表示对应的值可以为空

@NonNull

可以标注在方法、字段、参数之上,表示对应的值不可以为空

8、使用注解开发

在Spring4之后,要使使用注解开发,必须要保证aop的包导入了

image-20210710191020579

添加约束及包路径,配置之后会根据标记和注解来进行装配

<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置注解的支持 -->
<context:annotation-config/>
<!-- 指定要扫描的包,使该包下的注解生效 -->
<context:component-scan base-package="cn.ziyia.pojo"/>
</beans>

<context:component-scan base-package=”cn.ziyia.pojo”/>的作用是使用该包下的注解生效

8.1 bean如何注入?

// 相当于在application.xml中手动装配:<bean id="user" class="cn.ziyia.pojo.User"/>
// 意思就是说,该类已经被Spring窗口所管理了
@Component
public class User {
private String name = "default";

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

我们是可以直接通过getBean来取得的

image-20210710192344644

8.2 属性如何注入?、

@Value注解用于向属性中注入值,该注解可以使用在属性上也可以使用在set方法上。

// 相当于在application.xml中手动装配
@Component
public class User {
// 相当于在application.xml中使用property给属性进行了赋值
@Value(value = "年少有为")
private String name = "default";

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

此时属性已经被注入值了

image-20210710192434270

8.3 @Component衍生的注解

@Component有几个衍生注解,我们在web开发中,会按照MVC三层架构分层。

  • dao 层则使用 @Repository 注解
  • service层则使用 @Service 注解
  • controller层则使用 @Controller 注解

这四个注解功能都是一样的,都是代表将某个类注册到Spring容器中,装配Bean

8.4 自动装配配置

前面已经说过了。

8.5 作用域

@Scope注解指定该组件创建对象的方法,默认是以单例模式创建的

@Component
// singleton单例模式 / prototype原型模式
@Scope("prototype")
public class User {
// 相当于在application.xml中使用property给属性进行了赋值
@Value(value = "年少有为")
private String name = "default";

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

8.6 小结

xml与注解

  • xml更加万能,适用于任何场合,维护简单方便。
  • 注解不是自己的类使用不了,维护相对复杂。

8.3 xml与注解的最佳实践

  • xml用来管理bean
  • 注解只负责完成属性注入
  • 我们在使用的过程中,只需要注意一个问题:必须主注解生效,就需要开启注解的支持。‘<context:annotation-config/>
    <context:component-scan base-package=”cn.ziyia.pojo”/>

9、使用JavaConfig实现配置

我们现在完全不使用Spring的xml配置了,全权交给java来做。

javaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。

使用该方式可以完全代替xml,也就是说,我们可以不用创建xml文件。

  • 实体类@Component
    public class User {

    private String name;

    public User(String name) {
    this.name = name;
    }

    @Override
    public String toString() {
    return “User{” +
    “name='” + name + ‘\” +
    ‘}’;
    }
    }这部分内容和前面是一样的,使用@Component使用其自动注入到容器。注意:@Configuration注解的配置类有如下要求:
  • @Configuration不可以是final类型;
  • @Configuration不可以是匿名类;
  • 嵌套的configuration必须是静态类。、配置文件
    @Configuration
    @Import({AppConfig3.class, AppConfig2.class})
    public class AppConfig {
    public User getUser() {
    return new User(“config”);
    }
    }

    @Configuration
    public class AppConfig2 {

    /**
    * 方法名对应bean的id
    * 方法返回值对应bean的class
    */
    @Bean
    @Scope(“prototype”)
    public User getUser() {
    return new User(“config-2”);
    }
    }

    @Configuration
    public class AppConfig3 {

    @Bean
    public User getUser() {
    return new User(“config-3”);
    }
    }@Configuration注解中proxyBeanMethods属性是为了让使用@Bean注解的方法被代理而实现bean的生命周期的行为。
    • 设置为true,那么直接调用方法获取bean,不会创建新的bean,而是会走bean的生命周期的行为。
    • 设置为false, 那么直接调用方法获取bean,会创建新的bean,且不会走bean的生命周期的行为。
    需要知道:
    • 我们使用@Configuration注解来标记AppConfig类,表示该类是一个配置类,该注解是必须的。
    • 默认情况下我们通过getBean获取的还是单例模式的对象,如果想获得原型对象,可以在配置类上使用@Scope注解。
    • @Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同,方法的返回值对应Bean中的class
    • 在类上使用@Import注解可以导入一个或多个AppConfig,如果多个配置文件中有内容冲突,则后者覆盖前者,如果总配置类中也和子配置类内容有冲突,那么最终以总配置的内容为准。
    • 我们的AppConfig也会被Spring托管,因为它本身也是一个被@Component标识的类。
    • @Configuation等价于<Beans></Beans>
    • @Bean等价于<Bean></Bean>
    • @ComponentScan等价于<context:component-scan base-package="com.xxx"/>
  • 测试类public class AppTest {
    @Test
    public void test1() {

    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    User user1 = (User) context.getBean(“getUser”);
    User user2 = (User) context.getBean(“getUser”);

    System.out.println(“user1 = ” + user1);
    System.out.println(“user2 = ” + user2);
    System.out.println(user1 == user2);
    }
    } 如果完全使用了配置类方式代替xml文件去做,我们就只通过AnnotationConfig上下文来获取窗口,通过配置类的class对象加载。

10、代理模式

为什么要学习代理模式?因为这就是SpringAop的底层【SpringAOP 和 SpringMVC】

代理模式的分类:

  • 静态代理
  • 动态代理
image-20210711213250967

10.1 静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理的人

代码步骤:

  1. 接口// 租房
    public interface Rent {
    void rent();
    }
  2. 真实角色// 房东
    public class Host implements Rent{

    @Override
    public void rent() {
    System.out.println(“房东租房”);
    }
    }
  3. 代理角色// 代理房东
    public class Proxy implements Rent{

    Host host;

    public Proxy(Host host) {
    this.host = host;
    }

    @Override
    public void rent() {
    log(“收中介费”);
    log(“看房”);
    log(“签合同”);
    log(“中介帮房东租房”);
    host.rent();
    }

    private void log(String msg) {
    System.out.println(“[debug] ” + msg);
    }

    @Override
    public String toString() {
    return “Proxy{” +
    “host=” + host +
    ‘}’;
    }
    }
  4. 客户端访问代理角色public class Client {
    public static void main(String[] args) {
    Host host = new Host();

    Proxy proxy = new Proxy(host);
    proxy.rent();
    }
    }

代理的好处:

  1. 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务。
  2. 公共也就交给代理角色,实现的业务的分工。
  3. 公共业务发生扩展的时候,方便集中管理。

10.2 动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是直接写好的。
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口:JDK提供的动态代理
    • 基于类:cglib
    • java字节码实现:javasisst

需要了解两个类:

  • Proxy:代理
  • InvocationHandler:调用处理程序

代理的好处:

  1. 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务。
  2. 公共也就交给代理角色,实现的业务的分工。
  3. 公共业务发生扩展的时候,方便集中管理。
  4. 一个动态代理类代理的是一个接口,一般就是对应的一类业务。
  5. 一个动态代理类可以代理多个类,只要是实现了同时一个接口即可。

实现例子:所有类通用

public class ProxyInvocationHandler implements InvocationHandler {

/**
* 需要被代理的接口
*/
private Object singer;

/**
* 构造函数中接收一个接口的具体实现类
* @param singer
*/
public ProxyInvocationHandler(Singer singer) {
this.singer = singer;
}

/**
* 创建接口的代理对象
* @return
*/
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), singer.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在具体的方法执行前做一些处理
// 数据加密...
// 我们并不需要去改动原有的代码
Object invoke = method.invoke(singer, args);
// 在具体的方法执行之后做一些处理
return invoke;
}

}

11、AOP

11.1 什么是AOP

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

image-20210717080530678

11.2 AOP在Spring中的作用

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

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

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

image-20210712155407162

即Aop在不改变原有代码的情况下,去增加新功能。

11.3 使用Spring实现AOP

使用AOP之前,需要导入以下依赖:

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>

准备工作 :

Service.java

public interface Service {
void insert();
void delete();
void select();
void update();
}

ServiceImp.java

public class ServiceImp implements Service{
@Override
public void insert() {
System.out.println("添加了一个用户");
}

@Override
public void delete() {
System.out.println("删除了一个用户");
}

@Override
public void select() {
System.out.println("查询了一个用户");
}

@Override
public void update() {
System.out.println("修改了一个用户");
}
}

Log.java

public class Log implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println("【" + target.getClass().getSimpleName() +"】的[" + method.getName() + "]被执行了");
}
}

AfterLog.java

public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了【" + method.getName() + "】方法,返回了[" + o + "]结果");
}
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>Spring-aop</artifactId>
<version>1.0-SNAPSHOT</version>

<name>Spring-aop</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.7</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>

<build>

</build>
</project>
  • 方式一:使用Spring的Api接口application.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”
    xmlns:p=”http://www.springframework.org/schema/p”
    xmlns:c=”http://www.springframework.org/schema/c”

    xmlns:context=”http://www.springframework.org/schema/context”
    xmlns:aop=”http://www.springframework.org/schema/aop”
    xsi:schemaLocation=”http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd”>
    <context:annotation-config/>
    <!– 注册bean –>
    <bean id=”userService” class=”org.example.ServiceImp”/>
    <bean id=”beforeLog” class=”org.example.Log”/>
    <bean id=”afterLog” class=”org.example.AfterLog”/>
    <!– 配置aop,需要导入aop的约束 –>
    <aop:config>
    <!– 方式一 –>
    <!– 切入点:expression:表达式,execution(需要执行的位置 * * * * *) –>
    <aop:pointcut id=”pointcut” expression=”execution(* org.example.ServiceImp.*(..))”/>
    <aop:advisor advice-ref=”beforeLog” pointcut-ref=”pointcut”/>
    <aop:advisor advice-ref=”afterLog” pointcut-ref=”pointcut”/>
    </aop:config>
    </beans>image-20210717090355763
  • 方式二:使用自定义类实现Diy.javapublic class Diy {

    public void before() {
    System.out.println(“==== before ====”);
    }

    public void after() {
    System.out.println(“==== after ====”);
    }
    }application.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”
    xmlns:p=”http://www.springframework.org/schema/p”
    xmlns:c=”http://www.springframework.org/schema/c”

    xmlns:context=”http://www.springframework.org/schema/context”
    xmlns:aop=”http://www.springframework.org/schema/aop”
    xsi:schemaLocation=”http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd”>
    <context:annotation-config/>


    <!– 注册bean –>
    <bean id=”userService” class=”org.example.ServiceImp”/>
    <bean id=”beforeLog” class=”org.example.Log”/>
    <bean id=”afterLog” class=”org.example.AfterLog”/>

    <bean id=”diy” class=”org.example.diy.Diy”/>
    <!– 配置aop,需要导入aop的约束 –>
    <aop:config>
    <!– 方式二:使用自定义类实现 –>
    <aop:aspect ref=”diy”>
    <aop:pointcut id=”pointcut” expression=”execution(* org.example.ServiceImp.*(..))”/>
    <aop:before method=”before” pointcut-ref=”pointcut”/>
    <aop:after method=”after” pointcut-ref=”pointcut”/>
    </aop:aspect>
    </aop:config>
    </beans>image-20210717090341859
  • 方式三:使用注解实现AnnotationPointcut.java@Aspect
    public class AnnotationPointcut {

    @Before(“execution(* org.example.ServiceImp.*(..))”)
    public void before() {
    System.out.println(“==== 使用注解实现的before ====”);
    }

    @After(“execution(* org.example.ServiceImp.*(..))”)
    public void after() {
    System.out.println(“==== 使用注解实现的after ====”);
    }
    }application.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”
    xmlns:p=”http://www.springframework.org/schema/p”
    xmlns:c=”http://www.springframework.org/schema/c”

    xmlns:context=”http://www.springframework.org/schema/context”
    xmlns:aop=”http://www.springframework.org/schema/aop”
    xsi:schemaLocation=”http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd”>
    <context:annotation-config/>


    <!– 注册bean –>
    <bean id=”userService” class=”org.example.ServiceImp”/>
    <bean id=”beforeLog” class=”org.example.Log”/>
    <bean id=”afterLog” class=”org.example.AfterLog”/>


    <!– 使用注解实现 –>
    <bean id=”annotationPointCut” class=”org.example.diy.AnnotationPointcut”/>
    <aop:aspectj-autoproxy/>



    </beans>
  • 优先级:同时实现了多种方式时方式一 > 方式二 > 方式三image-20210717091444707补充:<aop:aspectj-autoproxy proxy-target-class=”true”/>如果proxyy-target-class属性设置为true。则表示使用cglib的实现方式,默认是为false,表示使用jdk默认的实现。

12、整合Mybatis

步骤:

  1. 导入依赖
    • junit
    • mybatis
    • mybatis-spring
    • spring-jdbc
    • aspectjweaver
    • mysql数据库
    • spring-webmvc
    • pom.xml<?xml version=”1.0″ encoding=”UTF-8″?>

      <project xmlns=”http://maven.apache.org/POM/4.0.0″ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
      xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>
      <modelVersion>4.0.0</modelVersion>

      <groupId>org.example</groupId>
      <artifactId>Spring-mybatis-Test</artifactId>
      <version>1.0-SNAPSHOT</version>

      <name>Spring-mybatis-Test</name>
      <!– FIXME change it to the project’s website –>
      <url>http://www.example.com</url>

      <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.source>1.7</maven.compiler.source>
      <maven.compiler.target>1.7</maven.compiler.target>
      </properties>

      <dependencies>
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.1</version>
      <scope>test</scope>
      </dependency>

      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.16</version>
      </dependency>


      <!– https://mvnrepository.com/artifact/org.springframework/spring-jdbc –>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.9</version>
      </dependency>


      <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.7</version>
      </dependency>

      <!– https://mvnrepository.com/artifact/org.mybatis/mybatis –>
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7</version>
      </dependency>

      <!– https://mvnrepository.com/artifact/org.mybatis/mybatis-spring –>
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
      </dependency>


      <!– https://mvnrepository.com/artifact/org.springframework/spring-webmvc –>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.9</version>
      </dependency>


      </dependencies>

      <build>
      <resources>
      <!– 如果不加,那么打包的时候mapper文件不会被加载进来 –>
      <resource>
      <directory>src/main/java</directory>
      <includes>
      <include>**/*.properties</include>
      <include>**/*.xml</include>
      </includes>
      <filtering>false</filtering>
      </resource>
      </resources>
      </build>
      </project>
  2. 编写配置文件
  3. 测试

12.1 Mybatis-Spring

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要——因为本手册中不会提供二者的基本内容,安装和配置教程。

MyBatis-Spring 需要以下版本:

整合步骤:

  1. 编写数据源配置<!– DriverManagerDataSource –>
    <bean id=”resource” class=”org.springframework.jdbc.datasource.DriverManagerDataSource”>
    <property name=”driverClassName” value=”com.mysql.cj.jdbc.Driver”/>
    <property name=”url” value=”jdbc:mysql://localhost:3306/SpringMybatisTest?serverTimezone=GMT%2B8&amp;characterEncoding=utf8″/>
    <property name=”username” value=”root”/>
    <property name=”password” value=”root”/>
    </bean>
  2. SqlSessionFactory<bean id=”SqlSessionFactory” class=”org.mybatis.spring.SqlSessionFactoryBean”>
    <property name=”dataSource” ref=”resource”/>
    <property name=”mapperLocations” value=”classpath:org/example/demo1/mapper/*.xml”/>
    </bean>
  3. SqlSessionTemplate<bean id=”SqlSession” class=”org.mybatis.spring.SqlSessionTemplate”>
    <constructor-arg index=”0″ ref=”SqlSessionFactory”/>
    </bean>
  4. 需要给接口加实现类public class UserMapperTemplate implements UserMapper {
    private SqlSessionTemplate SqlSession;

    public SqlSessionTemplate getSqlSession() {
    return SqlSession;
    }

    public void setSqlSession(SqlSessionTemplate sqlSession) {
    SqlSession = sqlSession;
    }

    @Override
    public List<User> select() {
    return SqlSession.getMapper(UserMapper.class).select();
    }
    }<bean id=”userMapper” class=”org.example.demo2.mapper.UserMapperTemplate”>
    <property name=”sqlSession” ref=”SqlSession”/>
    </bean>
  5. 测试public void test02() {
    ApplicationContext context = new ClassPathXmlApplicationContext(“application.xml”);
    UserMapper userMapper = (UserMapper) context.getBean(“userMapper”);
    List<User> select = userMapper.select();
    for (User user : select) {
    System.out.println(user);
    }
    }

SqlSessionDaoSupper

上方步骤中的第三步是可选的,我们可以让UserMapperTemplate继承SqlSessionDaoSupport然后为SqlSessionDaoSupport的父类注入SqlSessionFacatory,我们就可以在UserMapperTemplate中直接调用getSqlSession()方法来获得SqlSession对象。

public class UserMapperTemplate extends SqlSessionDaoSupport implements UserMapper {

@Override
public List<User> select() {
return getSqlSession().getMapper(UserMapper.class).select();
// return SqlSession.getMapper(UserMapper.class).select();
}
}

为其父类配置SqlSessionFactory。

<bean id="userMapper" class="org.example.demo2.mapper.UserMapperTemplate">
<property name="sqlSessionFactory" ref="SqlSessionFactory"/>
</bean>

不配置Spring的情况下是这样配置使用的

public static void main(String[] args) throws Exception {
// dataSource
DriverManagerDataSource dataSource = new DriverManagerDataSource(
"jdbc:mysql://localhost:3306/SpringMybatisTest?serverTimezone=GMT%2B8&characterEncoding=utf8",
"root",
"root"
);

// SqlSessionFactory
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:org/example/mapper/*.xml");
sqlSessionFactoryBean.setMapperLocations(resources);
sqlSessionFactoryBean.setDataSource(dataSource);
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();

// UserMapperTemplate
UserMapperTemplate userMapperTemplate = new UserMapperTemplate();
userMapperTemplate.setSqlSessionFactory(sqlSessionFactory);
List<User> select = userMapperTemplate.select();
for (User user : select) {
System.out.println(user);

}
}

13、声明式事务

事务:

  • 把一组业务当成一个业务去处理,要么都成功,要么都失败。
  • 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能有一点马虎。
  • 确保数据的完整性和一致性。

事务的ACID原则:

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

13.1 Spring事务的配置方式

Spring支持编程式事务管理以及声明式事务管理两种方式。

1. 编程式事务管理

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

2. 声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。 编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。 显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。

3. 事务超时

为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。

假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。

由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。

13.2 事务的传播机制

事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。 常用的事务传播机制如下:

  • PROPAGATION_REQUIRED Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
  • PROPAGATION_REQUES_NEW 该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
  • PROPAGATION_SUPPORT 如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
  • PROPAGATION_NOT_SUPPORT 该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
  • PROPAGATION_NEVER 该传播机制不支持外层事务,即如果外层有事务就抛出异常
  • PROPAGATION_MANDATORY 与NEVER相反,如果外层没有事务,则抛出异常
  • PROPAGATION_NESTED 该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

13.3 回滚规则

在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚(这一行为和EJB中的回滚行为是一致的)。 不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。

13.4 事务的隔离级别

事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度,可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。

在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:

  1. 脏读(Dirty read) 脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。
  2. 不可重复读(Nonrepeatable read) 不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。

不可重复读重点在修改。

  1. 幻读(Phantom reads) 幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。

幻读重点在新增或删除。

在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。

13.5 事务的配置及应用

环境配置:

  • 数据表image-20210718145923081image-20210718212226304
  • 实体类public class User {
    private int id;
    private String username;
    private String password;
    private String email;
    }
  • 接口public interface UserMapper {
    List<User> select();
    int insert(User user);

    int delete(int id);
    }
  • 接口的映射<?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=”org.example.mapper.UserMapper”>
    <select id=”select” resultType=”org.example.User”>
    SELECT * from `user`;
    </select>
    <insert id=”insert” parameterType=”org.example.User”>
    insert into `user` values(null, #{username}, #{password}, #{email});
    </insert>

    <delete id=”delete” parameterType=”_int”>
    delete from `user` where id = #{id};
    </delete>
    </mapper>
  • UserMapperTemplate.javapublic class UserMapperTemplate extends SqlSessionDaoSupport implements UserMapper {

    @Override
    public List<User> select() {
    User userObj = new User(“nsyw”, “nsyw”, “nsyw@qq.com”);
    UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
    mapper.insert(userObj);
    mapper.delete(15);
    return mapper.select();
    }

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

    @Override
    public int delete(int id) {
    return getSqlSession().getMapper(UserMapper.class).delete(id);
    }
    }
  • application.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”
    xmlns:p=”http://www.springframework.org/schema/p”
    xmlns:c=”http://www.springframework.org/schema/c”

    xmlns:context=”http://www.springframework.org/schema/context”
    xmlns:tx=”http://www.springframework.org/schema/tx”
    xmlns:aop=”http://www.springframework.org/schema/aop”
    xsi:schemaLocation=”http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd”>
    <context:annotation-config/>

    <!– DriverManagerDataSource –>
    <bean id=”resource” class=”org.springframework.jdbc.datasource.DriverManagerDataSource”>
    <property name=”driverClassName” value=”com.mysql.cj.jdbc.Driver”/>
    <property name=”url” value=”jdbc:mysql://localhost:3306/SpringMybatisTest?serverTimezone=GMT%2B8&amp;characterEncoding=utf8″/>
    <property name=”username” value=”root”/>
    <property name=”password” value=”root”/>
    </bean>

    <bean id=”SqlSessionFactory” class=”org.mybatis.spring.SqlSessionFactoryBean”>
    <property name=”dataSource” ref=”resource”/>
    <property name=”mapperLocations” value=”classpath:org/example/mapper/*.xml”/>
    </bean>
    <bean id=”SqlSession” class=”org.mybatis.spring.SqlSessionTemplate”>
    <constructor-arg index=”0″ ref=”SqlSessionFactory”/>
    </bean>
    <bean id=”userMapper” class=”org.example.UserMapperTemplate”>
    <property name=”sqlSessionFactory” ref=”SqlSessionFactory”/>
    </bean>

    </beans>

我们修改接口映射文件,让其中一组业务异常

image-20210718150757672

修改业务代码,如果delete()异常,insert()会成功插入吗?

image-20210718150652761

编写测试代码

image-20210718150926088

执行结果:程序异常了。

image-20210718150933728

我们去查询数据库,看看insert()有没有成功插入数据。

image-20210718151205310

数据被插入了,显示是不合理的。

接来来我们配置事务,修改application.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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<context:annotation-config/>


<!-- DriverManagerDataSource -->
<bean id="resource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/SpringMybatisTest?serverTimezone=GMT%2B8&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>

<bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="resource"/>
<property name="mapperLocations" value="classpath:org/example/mapper/*.xml"/>
</bean>

<bean id="SqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="SqlSessionFactory"/>
</bean>

<!-- <bean id="userMapper" class="org.example.UserMapperTemplate.UserMapperTemplate">-->
<!-- <property name="sqlSession" ref="SqlSession"/>-->
<!-- </bean>-->

<bean id="userMapper" class="org.example.UserMapperTemplate">
<property name="sqlSessionFactory" ref="SqlSessionFactory"/>
</bean>

<!-- 第一步:Transaction,事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 需要用到数据库的连接 -->
<property name="dataSource" ref="resource"/>
</bean>

<!-- 为事务管理器,配置生效的方法。在哪些方法上生效 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 所以方法均生效 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!-- 配置事务切入 -->
<aop:config>
<!-- 配置切入点:org.example.UserMapper.Template中的所有方法都切入事务-->
<aop:pointcut id="txPointcut" expression="execution(* org.example.UserMapperTemplate.*(..))"/>
<!-- 在切入点中注入事务 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

</beans>

为了更加直观的测试,我们测试前先把数据库表清空,如果事务生效,是不会插入数据的。

image-20210718152019550

并再次执行测试,效果如下:

image-20210718152220385
image-20210718153158088

可见,配置的事务已经生效了,由于 delete()方法抛出了异常,所以在事务的角度来说,insert()也不应该被执行成功。

发表回复

相关

浙ICP备2021031744号-3