Spring基础

Spring基础

1、Spring

1.1 简介

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

  • SSH:Struct2 + Spring + Hibernate
  • SSM: SpringMVC + Spring + Mybatis

官网 : http://spring.io/

官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/

GitHub : https://github.com/spring-projects

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

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

1.2 优点

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

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

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

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

总结:spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。

1.3 组成

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

image-20220317122507251

1.4 扩展

Spring Boot与Spring Cloud

  • Spring Boot
  • Spring Boot 是 Spring 的一套快速配置脚手架;
  • 基于Spring Boot 可以快速开发单个微服务;
  • 约定大于配置
  • Spring Cloud
  • Spring Cloud 是基于Spring Boot实现的;

因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用!

2、IoC(控制反转)理论推导

2.1 IoC基础

新建一个空白的maven项目

2.1.1 分析实现

我们先用我们原来的方式写一段代码 .

1、先写一个UserDao接口

public interface UserDao {
    public void getUser();
}

2、再去写Dao的实现类

public class UserDaoImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("获取用户数据");
    }
}

3、然后去写UserService的接口

public interface UserService {
    public void getUser();
}

4、最后写Service的实现类

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

5、测试一下(这里每次更换不同接口都需要在service层更改new的接口)

@Test
public void test(){
    // 用户实际调用的是业务层,dao层它们不需要接触
    UserService service = new UserServiceImpl();
    service.getUser();
}

这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下 .

把Userdao的实现类增加一个

public class UserDaoMysqlImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("MySql获取用户数据");
    }
}

紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoMySqlImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

再假设, 我们再增加一个Userdao的实现类 .

public class UserDaoMysqlImpl implements UserDao{
    @Override
    public void getUser() {
        System.out.println("MySql获取用户数据");
    }
}

那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .

那我们如何去解决呢 ?

我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .

public class UserServiceImpl implements UserService {
   private UserDao userDao;
    //在Service层的实现类(UserServiceImpl)增加一个Set()方法
    //利用set动态实现值的注入!
    //DAO层并不写死固定调用哪一个UserDao的实现类
    //而是通过Service层调用方法设置实现类!
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

现在去我们的测试类里 , 进行测试 ;(有了set方法就可以在调用的时候由用户选择调用的接口)

@Test
public void test(){
    UserServiceImpl service = new UserServiceImpl();
    service.setUserDao( new UserDaoMySqlImpl() );
    service.getUser();
    //那我们现在又想用Oracle去实现呢
    service.setUserDao( new UserDaoOracleImpl() );
    service.getUser();
}
  • 之前,程序是主动创建对象!控制权在程序猿手上
  • 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象!(主动权在客户手上

本质上解决了问题,程序员不用再去管理对象的创建;系统的耦合性大大降低,可以更专注在业务的实现上

这是IoC(控制反转)的原型,反转(理解):主动权交给了用户

2.1.2 IoC本质

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

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

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

container magic

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

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

3、HelloSpring

3.1 导入Jar包

注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项

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

3.2 编写代码

1、编写一个Hello实体类

public class Hello {
    private String name;

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

    public void show(){
        System.out.println("Hello,"+ name );
    }
}

2、编写我们的spring文件 , 这里我们命名为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.xsd">

    <!--bean就是java对象 , 由Spring创建和管理-->
    <bean id="hello" class="com.example.pojo.Hello">
        <property name="name" value="Spring"/>
    </bean>

</beans>

使用Spring来创建对象,在Spring这些都称为Bean

  • id = 变量名
  • class = new 的对象
  • property相当于给对象中的属性设置一个值!

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

@Test
public void test(){
    //解析beans.xml文件 , 生成管理相应的Bean对象
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //getBean : 参数即为spring配置文件中bean的id .
    Hello hello = (Hello) context.getBean("hello");
    hello.show();
}

3.3 思考

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

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

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

3.4 修改案例一

我们在案例一中, 新增一个Spring配置文件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.xsd">

    <bean id="mysqlImpl" class="com.example.dao.UserDaoMysqlImpl"/>
    <bean id="oracleImpl" class="com.example.dao.UserDaoOracleImpl"/>

    <bean id="UserServiceImpl" class="com.example.service.UserServiceImpl">
        <!--ref引用spring中已经创建很好的对象-->
        <!--value是一个具体的值,基本数据类型-->
        <property name="userDao" ref="mysqlImpl"/>
    </bean>
</beans>

测试!

@Test
public void test2(){
    // 获取ApplicationContext;拿到Spring容器
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    // 需要什么就直接get什么!
    UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
    userServiceImpl.getUser();
}

假若这时候需要OracleImpl对象,则只需在xml配置文件中设置 ref="oracleImpl"即可,无需去改动代码。

总结:

  • 所有的类都要装配的beans.xml 里面;
  • 所有的bean 都要通过容器去取;
  • 容器里面取得的bean,拿出来就是一个对象,用对象调用方法即可;

所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !

4、IoC创建对象方式

4.1 通过无参构造方法来创建

1、User.java

public class User {
    private String name;

    public User() {
        System.out.println("user无参构造方法");
   }

    public String getName() {
        return name;
    }

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

    public void show(){
        System.out.println("name="+ name );
    }
}

2、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.xsd">

    <bean id="user" class="com.example.pojo.User">
        <property name="name" value="John"/>
    </bean>
</beans>

3、测试类

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //在执行getBean的时候, user已经创建好了 , 通过无参构造
    User user = (User) context.getBean("user");
    //调用对象的方法 .
    user.show();
}
image-20220317210732654

结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!

4.2 通过有参构造方法来创建

1、User.java

public class User {
    private String name;

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

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

    public void show(){
        System.out.println("name="+ name );
    }
}

2、beans.xml 有三种方式编写

  • 下标赋值
<bean id="user" class="com.example.pojo.User">
    <!-- index指构造方法 , 下标从0开始 -->
    <constructor-arg index="0" value="John"/>
</bean>
  • 类型赋值(不建议使用)
<bean id="user" class="com.example.pojo.User">
    <constructor-arg type="java.lang.String" value="John"/>
</bean>
  • 直接通过参数名(掌握)
<bean id="user" class="com.example.pojo.User">
    <!-- name指参数名 -->
    <constructor-arg name="name" value="John"/>
</bean>

结论:在配置文件加载的时候。其中管理的对象都已经初始化了(并且是单例模式,取到的对象是全局唯一)!

User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
system.out.println(user == user2)    //结果为true

5、Spring配置

5.1 别名

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

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

5.2 Bean的配置

  • id:bean的唯一标识符,也就是相当于我们学的对象名
  • class:bean对象所对应的会限定名:包名+类型
  • name:也是别名,而且name可以同时取多个别名
<bean id="user" class="com.example.pojo.User" name="u1 u2,u3;u4">
    <property name="name" value="chen"/>
</bean>

5.3 import

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

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

  • 张三(beans.xm1)
  • 李四(beans2.xm1)
  • 王五(beans3.xm1)
  • applicationContext.xml
  <import resource="beans.xm1"/>
  <import resource="beans2.xml"/>
  <import resource="beans3.xm1"/>

使用的时候,直接使用总的配置就可以了

有重名的情况出现时,按照在总的xml中的导入顺序来进行创建,后导入的会重写先导入的,最终实例化的对象会是后导入xml中的那个

6、依赖注入(DI)

6.1 构造器注入

第4点已有提到

6.2 set方式注入【重点】

依赖注入:set注入!

  • 依赖:bean对象的创建依赖于容器
  • 注入:bean对象中的所有属性,由容器来注入

环境搭建:

测试pojo类 :

Address.java

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

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

    // toString
}

Student.java

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

    // ... ...
    // get set 
    // toString
}

测试类

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

}

常量注入

<bean id="student" class="com.example.pojo.Student">
    <!--普通值注入 -->
    <property name="name" value="John"/>
</bean>

Bean注入

注意点:这里的值是一个引用,ref

<bean id="addr" class="com.example.pojo.Address">
    <property name="address" value="China"/>
</bean>

<bean id="student" class="com.example.pojo.Student">
    <!--Bean注入,ref -->
    <property name="address" ref="addr"/>
</bean>

数组注入

<!--数组注入 -->
<property name="books">
    <array>
        <value>西游记</value>
        <value>红楼梦</value>
        <value>水浒传</value>
    </array>
</property>

List注入

<!--list列表注入 -->
<property name="hobbies">
    <list>
        <value>唱</value>
        <value>跳</value>
        <value>rap</value>
        <value>篮球</value>
    </list>
</property>

Map注入

<!--map键值对注入 -->
<property name="card">
    <map>
        <entry key="username" value="root" />
        <entry key="password" value="root" />
    </map>
</property>

set注入

<!--set(可去重)注入 -->
<property name="games">
    <set>
        <value>wangzhe</value>
        <value>lol</value>
        <value>galname</value>
    </set>
</property>

空指针null注入

<!--空指针null注入 -->
<property name="wife">
    <null></null>
</property>

properties常量注入

<!--properties常量注入 -->
<property name="info">
    <props>
        <prop key="id">20200802</prop>
        <prop key="name">cbh</prop>
    </props>
</property>

测试结果:

image-20220317225815811

6.3 拓展方式注入

p命名空间注入

User.java 【注意:这里没有有参构造器!】

package com.example.pojo;

public class User {
    private String name;
    private int age;

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

    public void setAge(int age) {
        this.age = age;
    }

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

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

 导入约束 : xmlns:p="http://www.springframework.org/schema/p"

 <!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="com.example.pojo.User" p:name="John" p:age="18"/>

c命名空间注入

需要在头文件中加入约束文件

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

发现问题:爆红了,刚才我们没有写有参构造!

解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!

6.4 Bean的作用域

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

image-20220318211156500

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

  • 单例模式(singleton)

默认的模式

<bean id="user" class="com.example.pojo.User"" c:name="cxk" c:age="19" scope="singleton"></bean>
  • 原型模式(Prototype)

每次从容器中get的时候,都产生一个新对象!

<bean id="user" class="com.example.pojo.User"" c:name="cxk" c:age="19" scope="prototype"></bean>
  • 其余的request、session、application这些只能在web开放中使用!

7、Bean的自动装配

7.1 自动装配说明

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。

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

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

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

7.2 测试环境

1、新建一个项目

2、新建两个实体类,Cat Dog 都有一个叫的方法

pojo的Cat类

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

pojo的Dog类

public class Dog {

    public void shout(){
        System.out.println("wow");
    }

}

3、新建一个用户类 People

pojo的People类

package pojo;
public class People {

    private Cat cat;
    private Dog dog;
    private String name;

    public Cat getCat() {
        return cat;
    }

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

    public Dog getDog() {
        return dog;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + name + '\'' +
                '}';
    }
}

4、编写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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

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

    <bean id="people" class="com.example.pojo.People">
        <property name="name" value="John"/>
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
    </bean>
</beans>

5、测试

@Test
public void testMethodAutowire() {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    People people = (People) context.getBean("user");
    people.getCat().shout();
    people.getDog().shout();
}

结果正常输出,环境OK。

7.3 byName

autowire byName (按名称自动装配)

byname会在容器自动查找,和自己对象set方法的set后面的值对应的id

例如:setCat(),取set后面的字符作为id,则要id = cat 才可以进行自动装配

测试:

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

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

2、再次测试,结果依旧成功输出!

3、我们将 cat 的bean id修改为 catXXX

4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

小结:

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

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。

7.4 byType

autowire byType (按类型自动装配)

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

测试:

1、将user的bean配置修改一下 : autowire="byType"

2、测试,正常输出

3、在注册一个cat 的bean对象!

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

<bean id="user" class="com.kuang.pojo.User" autowire="byType">
   <property name="str" value="qinjiang"/>
</bean>

4、测试,报错:NoUniqueBeanDefinitionException

5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

小结:

  • byName的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致。
  • byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性类型一致。

7.4 使用注解实现自动装配

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

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

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

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

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

2、开启属性注解支持!

<context:annotation-config/>

@Autowired

默认是byType方式,如果匹配不上,就会byName

在属性上个使用,也可以在set上使用

我们可以不用编写set方法了,前提是自动装配的属性在Spring容器里,且要符合ByName 自动装配

测试:

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

public class People {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;

    // get
    // toString
}

2、此时配置文件内容

<context:annotation-config/>

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

Tips1:

@Nullable 字段标记了这个注解,说明该字段可以为空

public People(@Nullable String name){ this.name = name; }

Tips2:

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

//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;

@Autowired+@Qualifier

@Autowired不能唯一装配时,需要@Autowired+@Qualifier

如果@Autowired自动装配环境比较复杂。自动装配无法通过一个注解完成的时候,可以使用@Qualifier(value = “xxx”)去配合 @Autowired 使用,指定一个唯一的id对象

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

<bean id="dog2" class="com.example.pojo.Dog"/>
<bean id="cat2" class="com.example.pojo.Cat"/>

2、没有加Qualifier测试,直接报错

3、在属性上添加Qualifier注解

@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;

测试,成功输出!

@Resource

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

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

<bean id="dog1" class="com.example.pojo.Dog"/>
<bean id="dog2" class="com.example.pojo.Dog"/>
<bean id="cat1" class="com.example.pojo.Cat"/>
<bean id="cat2" class="com.example.pojo.Cat"/>

2、没有加Qualifier测试,直接报错

3、在属性上添加Qualifier注解

public class People {
    @Resource(name="cat2")
    private Cat cat;
    @Resource(name="dog2")
    private Dog dog;
    private String name;
}

测试,成功输出!

小结

@Autowired与@Resource异同:

  • @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在set方法上。
  • @Autowired通过byType的方式实现,而且必须要求这个对象存在!【常用】
  • @Resource默认通过byname的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】
  • 它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

8、使用注解开发

8.1 说明

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

image-20220319104556686

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

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

    <context:annotation-config/>
</beans>

8.2 Bean的实现

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

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

<!--指定注解扫描包-->
<context:component-scan base-package="com.example.pojo"/>

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

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

3、测试

@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}

8.3 属性注入

使用注解注入属性

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

@Component
public class User {
    // 相当于<property name="name" value="John"/>
    @Value("John")
    public String name;
}

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

@Component
public class User {
    public String name;

    // 也可以放在set方法上面
    @Value("John")
    public void setName(String name) {
        this.name = name;
    }
}

8.4 衍生的注解

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

@Component三个衍生注解:

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

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

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

8.5 自动装配注解

@Autowired:默认是byType方式,如果匹配不上,就会byName

@Nullable:字段标记了这个注解,说明该字段可以为空

@Resource:默认是byName方式,如果匹配不上,就会byType

8.6 作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
// scope("prototype")相当于<bean scope="prototype"></bean>
@Component
@Scope("prototype")
public class User {
   @Value("John")
   public String name;
}

8.7 小结

xml与注解:

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

最佳实践:

  • xml用来管理Bean
  • 注解只用来完成属性的注入
  • 可以不用扫描,扫描是为了类上的注解,但是要开启注解支持 <context:annotation-config/>

9、使用Java的方式配置Spring

完全不使用xml配置

1、编写一个实体类 User

package com.example.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// 这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中 
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }

    // 属性注入值
    @Value("John")
    public void setName(String name) {
        this.name = name;
    }

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

2、新建一个config配置包,编写一个MyConfig配置类

package com.example.config;

import com.example.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 这个也会Spring容器托管,注册到容器中,因为他本米就是一个@Component 
// @Configuration表这是一个配置类,就像我们之前看的beans.xml,类似于<beans>标签
@Configuration
@componentScan("com.example.pojo")  // 开启扫描
@Import(MyConfig2.class)    // 导入别的bean配置
public class MyConfig {

    // 注册一个bean , 就相当于我们之前写的一个bean 标签 
    // 这个方法的名字,就相当于bean 标签中的 id 属性 ->getUser
    // 这个方法的返同值,就相当于bean 标签中的class 属性 ->User
    @Bean
    public User getUser(){
        return new User();    // 就是返回要注入到bean的对象!
    }
}

3、测试

@Test
public void test(){
    // 如果完全使用了配置类方式去做,我们就只能通过 Annotationconfig 上下文来获取容器,通过配置类的class对象加载!
    ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(MyConfig.class);    // class对象
    User user = (User) applicationContext.getBean("getUser");    // 方法名getUser
    System.out.println(user.getName());
}

关于这种Java类的配置方式,在 SpringBoot 和 SpringCloud中随处可见

10、代理模式

AOP的底层机制就是动态代理!【SpringAOP和SpringMVC】

代理模式:

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

10.1 静态代理

角色分析

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

代码实现

Rent.java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host.java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
    @Override
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy.java 即代理角色

//代理角色:中介
public class Proxy implements Rent {

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

   //租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
  }
}

Client.java 即客户

//客户类,一般客户都会去找代理!
public class Client {
   public static void main(String[] args) {
       //房东要租房
       Host host = new Host();
       //中介帮助房东
       Proxy proxy = new Proxy(host);

       //你去找中介!
       proxy.rent();
  }
}

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式。

优缺点

优点:

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

缺点 :

  • 一个真是类对应一个代理角色,代码量翻倍,开发效率降低 .

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

image-20220319124400235

10.2 动态代理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
  • 基于接口的动态代理—-JDK动态代理 【我们这里使用】
  • 基于类的动态代理—-cglib
  • java字节码实现—- javasist 来生成动态代理

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

核心 : InvocationHandler 调用处理程序类和 Proxy 代理类

InvocationHandler:调用处理程序

public interface InvocationHandler

InvocationHandler是由代理实例的调用处理程序实现的接口

每个代理实例都有一个关联的调用处理程序。

Object invoke(Object proxy, 方法 method, Object[] args);

当在代理实例上调用方法的时候,方法调用将被编码并分派到其调用处理程序的invoke()方法。

参数

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

Proxy : 代理

public class Proxy extends Object implements Serializable

Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。

public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

参数

  • loader – 类加载器来定义代理类
  • interfaces – 代理类实现的接口列表
  • h – 调度方法调用的调用处理函数

代码实现

接口 Rent.java

//接口
public interface Rent {
    public void rent();

}

接口Host实现类 Host.java

//接口实现类
public class Host implements Rent{    
    public void rent() {
        System.out.println("房东要租房子");
    }
}

代理角色的处理程序类 ProxyInvocationHandler.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//用这个类,自动生成代理
public class ProxyInvocationHandler implements InvocationHandler {
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),this);
    }

    // 处理代理实例并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        // 动态代理的本质,就是使用反射机制实现的
        // invoke()执行它真正要执行的方法
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }

    //看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    //收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}

用户Client.java

public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host = new Host();
        //代理实例的调用处理程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setRent(host); //将真实角色放置进去!
        Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
        proxy.rent();
    }
}

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

优点

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的类只要实现了同一个接口即可!

11、AOP

11.1 什么是AOP

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

image-20220321114425483

11.2 AOP在Spring中的使用

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

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

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

通知类型连接点实现接口
前置通知方法前org.springframework.aop.MethodBeforeAdvice
后置通知方法后org.springframework.aop.AfterReturningAdvice
环绕通知方法前后org.aopalliance.interface.MethodInterceptor
异常抛出通知方法抛出异常org.springframework.aop.ThrowsAdvice
引介通知类中新增新的方法属性org.springframework.aop.IntroductionInterceptor

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

11.3 使用Spring实现AOP

使用AOP织入,需要导入一个依赖包!

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

方法一:通过 Spring API 实现

主要是Spring API 接口实现

  • 首先编写我们的业务接口和实现类

UserService.java

package service;
public interface UserService {   
        public void add() ;
        public void delete() ;
        public void query() ;
        public void update();
}

UserService 的实现类 UserServiceImpl.java

package service;

public class UserServiceImpl implements UserService {

    public void add() {
        System.out.println("add增");
    }
    public void delete() {
        System.out.println("delete删");
    }
    public void update() {
        System.out.println("update改");
    }
    public void query() {
        System.out.println("query查");
    }
}
  • 然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice {
   //method : 要执行的目标对象的方法
   //objects : 被调用的方法的参数
   //Object : 目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
    //returnValue 返回值
    //method被调用的方法
    //args 被调用的方法的对象的参数
    //target 被调用的目标对象
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,返回的结果为"+returnValue);
    }
}
  • 最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userService" class="com.example.service.UserServiceImpl"/>
    <bean id="log" class="com.example.log.Log"/>
    <bean id="afterLog" class="com.example.log.AfterLog"/>

    <!--aop的配置-->
    <aop:config>
        <!--切入点 expression:表达式匹配要执行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.example.service.UserServiceImpl.*(..))"/>
        <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>
  • 测试
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 动态代理代理的是接口!!
    UserService userService = (UserService) context.getBean("userService");
    userService.search();
}

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

方法二:自定义类来实现Aop

主要是切面定义

目标业务类不变依旧是userServiceImpl

  • 写我们自己的一个切入类
public class DiyPointCut {
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}
  • 去spring中配置
<!--注册bean-->
<bean id="diy" class="com.example.diy.DiyPointCut"/>

<!--aop的配置-->
<aop:config>
    <!--第二种方式:使用AOP的标签实现-->
    <aop:aspect ref="diy">
        <aop:pointcut id="diyPonitCut" expression="execution(* com.example.service.UserServiceImpl.*(..))"/>
        <aop:before pointcut-ref="diyPonitCut" method="before"/>
        <aop:after pointcut-ref="diyPonitCut" method="after"/>
    </aop:aspect>
</aop:config>
  • 测试
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 动态代理代理的是接口!!
    UserService userService = (UserService) context.getBean("userService");
    userService.add();
}

方法三:使用注解实现

  • 编写一个注解实现的增强类
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.example.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }

    @After("execution(* com.example.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
    }

    //在环绕增强中,我们可以给定一个参数,代表我们要获取切入的点
    @Around("execution(* com.example.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());    // 获取签名
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}
  • 在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.example.diy.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>
  • 测试
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 动态代理代理的是接口!!
    UserService userService = (UserService) context.getBean("userService");
    userService.add();
}
image-20220321153612407

12、整合MyBatis

12.1 步骤

  1. 导入相关jar包

junit

<dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.12</version>
</dependency>

mybatis

<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.2</version>
</dependency>

mysql-connector-java

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

spring相关

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.1.10.RELEASE</version>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>5.1.10.RELEASE</version>
</dependency>

aspectJ AOP 织入器

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

mybatis-spring整合包 【重点】

<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>2.0.2</version>
</dependency>

配置Maven静态资源过滤问题!

<build>
   <resources>
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>true</filtering>
       </resource>
   </resources>
</build>
  1. 编写配置文件
  2. 代码实现

12.2 回忆MyBatis

  • 编写实体类
package com.example.pojo;

public class User {
    private int id;  //id
    private String name;   //姓名
    private String pwd;   //密码

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  • 编写核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

   <typeAliases>
       <package name="com.example.pojo"/>
   </typeAliases>

   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
               <property name="username" value="root"/>
               <property name="password" value=""/>
           </dataSource>
       </environment>
   </environments>

   <mappers>
       <package name="com.example.mapper"/>
   </mappers>
</configuration>
  • 编写接口
package com.example.mapper;

import com.example.pojo.User;

import java.util.List;

public interface UserMapper {
    public List<User> selectUser();
}
  • 编写接口对应的Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">

    <select id="selectUser" resultType="User">
        select * from mybatis.user
    </select>

</mapper>
  • 测试
import com.example.mapper.UserMapper;
import com.example.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyTest {
    @Test
    public void selectUser() throws IOException {

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> userList = mapper.selectUser();
        for (User user: userList){
            System.out.println(user);
        }

        sqlSession.close();
    }

}

12.3 MyBatis-Spring

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

知识基础

在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要

MyBatis-Spring 需要以下版本:

MyBatis-SpringMyBatisSpring 框架Spring BatchJava
2.03.5+5.0+4.0+Java 8+
1.33.4+3.2.2+2.1+Java 6+

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>2.0.2</version>
</dependency>

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

SqlSessionFactory

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource" />
</bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 <settings><typeAliases>元素。

SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

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

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

public class UserDaoImpl implements UserDao {

 private SqlSession sqlSession;

 public void setSqlSession(SqlSession sqlSession) {
   this.sqlSession = sqlSession;
}

 public User getUser(String userId) {
   return sqlSession.getMapper...;
}
}

按下面这样,注入 SqlSessionTemplate:

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
 <property name="sqlSession" ref="sqlSession" />
</bean>

整合实现一

1、引入Spring配置文件applicationContext.xml

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

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

<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
   <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
   <property name="username" value="root"/>
   <property name="password" value=""/>
</bean>

3、配置SqlSessionFactory,关联MyBatis

<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource"/>
   <!--关联Mybatis-->
   <property name="configLocation" value="classpath:mybatis-config.xml"/>
   <property name="mapperLocations" value="classpath:com/example/dao/*.xml"/>
</bean>

4、注册sqlSessionTemplate,关联sqlSessionFactory;

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

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

public class UserMapperImpl implements UserMapper {

   //sqlSession不用我们自己创建了,Spring来管理
   private SqlSessionTemplate sqlSession;

   public void setSqlSession(SqlSessionTemplate sqlSession) {
       this.sqlSession = sqlSession;
  }

   public List<User> selectUser() {
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       return mapper.selectUser();
  }

}

6、注册bean实现

<bean id="userMapper" class="com.example.dao.userMapperImpl">
   <property name="sqlSession" ref="sqlSession"/>
</bean>

7、测试

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper mapper = (UserMapper) context.getBean("userMapper");
    List<User> user = mapper.selectUser();
    System.out.println(user);
}

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

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

实现方式二

dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式一 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好

SqlSessionDaoSupport

SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法,就像下面这样:

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
  public User getUser(String userId) {
    return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
  }
}

在这个类里面,通常更倾向于使用 MapperFactoryBean,因为它不需要额外的代码。但是,如果你需要在 DAO 中做其它非 MyBatis 的工作或需要一个非抽象的实现类,那么这个类就很有用了。

SqlSessionDaoSupport 需要通过属性设置一个 sqlSessionFactorySqlSessionTemplate。如果两个属性都被设置了,那么 SqlSessionFactory 将被忽略。

假设类 UserMapperImplSqlSessionDaoSupport 的子类,可以编写如下的 Spring 配置来执行设置:

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

测试:

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

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

2、修改bean的配置

<bean id="userMapper" class="com.example.mapper.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

3、测试

@Test
public void test() throws IOException{
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }

总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现。

13、声明式事务

13.1 回顾事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  • 原子性(atomicity)

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

  • 一致性(consistency)

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

  • 隔离性(isolation)

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

  • 持久性(durability)

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

13.2 测试

在之前的案例中,我们给userMapper接口新增三个方法,删除和增加用户;

//添加一个用户
int addUser(User user);

//根据id删除用户
int deleteUser(int id);

public List<User> test();

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

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {

   //增加一些操作
   public List<>User test() {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       User user = new User(5,"小明","123456");
       mapper.addUser(user);
       mapper.deleteUser(5);
       return mapper.selectUser();
   } 

   public List<User> selectUser() {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.selectUser();
  }

   //新增
   public int addUser(User user) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.addUser(user);
  }
   //删除
   public int deleteUser(int id) {
       UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
       return mapper.deleteUser(id);
  }

}

到UserMapper.xml配置文件中添加映射,并且故意把 deletes 写错

<insert id="addUser" resultType="User">
    insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd})
</insert>

<delete id="deleteUser" resultType="User">
    deletes from mybatis.user where id=#{id}
</delete>

<select id="test" resultType="User"/>

测试

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper mapper = (UserMapper) context.getBean("userMapper");
    List<User> userlist = mapper.test();
    for (User user : userlist) {
        System.out.println(user);
    }
}

报错:sql异常,delete写错了

结果 :插入成功!

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

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

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

13.3 Spring中的事务管理

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

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

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

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

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

事务管理器

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

JDBC事务

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

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

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

配置AOP

导入aop的头文件!

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

最终我们的spring-dao.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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/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">

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value=""/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--绑定MyBatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/example/mapper/UserMapper.xml"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource" />
    </bean>

    <!--结合AOP实现事务的织入-->
    <!--配置事务的类:-->
    <!--配置事务通知:-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务-->
        <!--配置事务的传播特性-->
        <tx:attributes>
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.example.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
</beans>

进行测试

删掉刚才插入的数据,再次测试!

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper mapper = (UserMapper) context.getBean("userMapper");
    List<User> userlist = mapper.test();
    for (User user : userlist) {
        System.out.println(user);
    }
}

这时候仍然报错但是插入不成功了!

思考问题?

为什么需要配置事务?

  • 如果不配置事务,可能存在数据提交不一致的情况下;
  • 如果不在spring中去配置声明式事务,我们就需要在代码中手动配置事务
  • 事务在项目的开发中非常重要,涉及到数据的一致性和完整性问题!

参考资料

【狂神说Java】Spring5最新完整教程IDEA版通俗易懂

Spring学习笔记-狂神版

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇