Spring入门

一、基本配置需要

环境:
①jdk1.8
③maven3.6.3
④IDEA
需要的基层知识
①JDBC
③Java基础
④Maven
⑤Junit单元测试
学习spring最好方法:看官网:
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
上面是官方文档,全英文,下面还有一份中文文档:
https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference

二、简介

2.1什么是spring

·Spring是一个开源框架, 使创建 Java 企业应用程序变得容易。它提供了在企业环境中使用 Java 语言所需的一切
·spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架。

2.2如何获取spring

①maven仓库:(这里以5.2.0版本为例)

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

②github:
https://github.com/spring-projects/spring-framework

2.3优点

·Spring是一个开源的免费的框架(容器)
·Spring是一个轻量级、非入侵式的(引入这个不会对你的项目结构有任何影响)框架
·控制反转(IOC),面向切面编程(AOP)
·支持事务的处理,对框架整合的支持
总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

2.4构成

在这里插入图片描述

只需掌握由什么构成,比方Spring ORM就是支持Mybatis层的对象实体映射,SpringDao提供了有意义的异常层次结构,可用该结构来管理异常处理。

2.5拓展

在这里插入图片描述

·Spring Boot
。一个快速开发的脚手架(框架)
。基于SpringBoot可以快速的开发单个微服务
·Spring Cloud
。Spring Cloud是基于Spring Boot实现的
因为现在大多数公司都在使用Spring Boot进行快速开发,学习Spring Boot的前提,需要完全掌握Spring和Spring MVC
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称“配置地狱”

三、IOC理论推导

3.1:原来项目结构的漏洞

①UserDao接口
②UserDao Impl实现类
③UserService接口
④UserService Impl实现类
通过UserDaoImpl去实现UserDao接口
通过UserServiceImpl去实现UserService接口,并调用UserDaoImpl
漏洞:在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
在这里插入图片描述
在这里插入图片描述

所以我们必须通过set进行动态实现值的注入思想才能解决这个问题
·之前程序是主动创建对象,控制权在程序员手上!所以需求一改,程序员就得自己手动改
·使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象

public class UserServiceImpl implements UserService{
    //利用Java组合特性调Dao层
    private UserDao userDao;
   //利用set进行动态实现值的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void getUser() {
        userDao.getUser();
    }
}

在这里插入图片描述

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

3.2 IOC本质

控制反转IOC是一种设计思想, DI(依赖注入)是实现IOC的一种方法,没有IOC的程序中,我们使用面向对象的编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方(用户自己),用户想调用谁就调用谁,程序员只需专注业务逻辑即可!个人认为所谓 控制反转就是:获得依赖对象的方式反转了!
IOC是Spring框架的核心内容,使用多种方式完美的实现了IOC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建于组织对象存入容器中程序使用时再从IOC容器中取出需要的对象
在这里插入图片描述

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到零配置的目的.
总结:控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(DI)

3.3HelloSpring

①建立一个新模块spring-02-helloSpring
②建立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">
    <parent>
        <artifactId>Spring_study</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>Spring_02_helloSpring</artifactId>
    <build>
        <!--在build中配置resources,来防止我们资源导出的问题-->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</project>

③在src/main/java下建立com/mbw/pojo包,并在pojo包下建立hello类

package com.mbw.pojo;

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);
    }

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

④在resource文件夹下建立beans.xml文件,Spring配置容器是由bean组成的,基于XML的配置元数据将这些bean配置为顶级元素内的元素。这些bean定义对应于组成应用程序的实际对象。
通过xml文件,我们在beans标签下建立bean标签
其中bean标签有两个属性,分别是id和class,id就是你对这个bean的一个取名,Class对应你的类的全路径
在bean之下通过propertie标签我们可以设定我们属性的值,name对应属性名,value对应设置的值使用Spring来创建对象,在Spring这都称为bean对比正常的java编写:
类型 变量名=new 类型()
比如Hello hello =new Hello()
而在Spring这边,id=变量名,class=new的对象
property相当于给对象中的属性设置一个值

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    使用Spring来创建对象,在Spring这些都成为bean-->
    <bean id="hello" class="com.mbw.pojo.Hello">
        <property name="name" value="Spring"/>
    </bean>
</beans>

⑤测试:
通过ClassPathXmlApplicationContext获取context上下文对象,这行代码是死的,只要用spring,就必须得先通过这行代码
然后通过context.getBean方法获取hello对象,然后就可以调用Hello类里的方法

@Test
public void TestHelloSpring(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Hello hello = (Hello) context.getBean("hello");
    hello.show();
}

在这里插入图片描述

通过上面的例子:
我们知道hello对象是由Spring创建的
Hello对象的属性也是由Spring创建的
这个过程就叫做控制反转
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring创建的
反转:程序本身不创建对象,而变成被动的接收对象
依赖注入:就是利用pojo的set方法来进行注入的,如果你的实体类没有set方法,那么你的配置文件属性赋值那里就会报错
IOC是一种编程思想,由主动的编程变成被动的接收,总结就是,对象由Spring去创建,管理,装配!

OK.到现在。我们彻底不用再到程序中去做任何改动了。要实现不同的操作,只需要在xml配置文件中进行修改。

四、IOC创建对象的方式

①我们新建一个模块mybatis_03_IOC2
②然后创建实体类User,我们想要验证IOC创建对象的方式,通过构造器是直接明了的,所以我们在User类里添加一个无参构造器并打印一句话
正常来说,User user=new User()就会打印出那句话
我们要验证的就是通过Spring是否也会打印出这句话,如果是,那么就说明IOC创建对象默认方式走的是无参构造

package com.mbw.pojo;

public class User {
    private String name;

    public User() {
        System.out.println("调用了无参构造");
    }

    public String getName() {
        return name;
    }

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

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

③编写bean.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="User" class="com.mbw.pojo.User">
        <property name="name" value="mbw"/>
    </bean>

</beans>

④Test

@Test
public void TestIOC(){
    ApplicationContext context= new ClassPathXmlApplicationContext("Beans.xml");
    User user = (User) context.getBean("User");
    user.show();
}

在这里插入图片描述

通过结果,我们发现确实IOC是默认通过无参构造器去new对象的
如果我只在pojo类中只有有参构造器,那么直接运行就会报错,再一次证明了上述结果
结论:①默认使用无参构造创建对象
②假设我们要使用有参构造创建对象,IOC也有自己的一些方法
》通过参数下标给指定参数赋值去构造对象,0代表第一个参数

<constructor-arg index="0" value="mbw"/>

测试后:发现IOC通过有参构造方式创建对象
在这里插入图片描述

》通过type类型指定参数类型,基本类型可以直接用,包装类型得用限定名

<constructor-arg type="java.lang.String" value="mbw"/>

测试:
在这里插入图片描述

但是,这个不建议使用,首先类型易写错
其次也是最大的弊端:万一我的pojo类里有两个相同类型属性,这个就会出问题
》直接通过参数名构造对象,推荐使用,name=参数名,value给参数赋值

<constructor-arg name="name" value="mbw"/>

拓展:
假如我在pojo再创建一个对象User2,并加入一个无参构造器,在构造器方法体内打印一句话,然后在beans.xml去创建这个对象
然后测试,我仍然只get User,并调用User的方法,测试后会发现User2无参构造的那句话也被打印出来了,这说明在你通过context去get对象前,Spring已经把所有对象创建好放到容器内,你想用哪个get哪个就好
就和交友网站一样,交友网站已经把你感兴趣的类型全部放到网上(前提时他们注册该网站),你对哪个有好感,直接去“get”它就好.在配置文件加载的时候,容器中管理的对象就已经初始化了!

五、Spring配置说明

5.1别名

<alias name="User" alias="userNew"></alias>

有点像myBatis的typeAlias,但我觉得没有人家好用,mybatis可以直接给你的包换别名,这个只能把对象(bean)给换个名字,你取对象的时候,既可以用对象名,也可以用别名取得
在这里,我将Bean的id改为userNew
测试通过别名去取对象并执行

@Test
public void TestIOC(){
    ApplicationContext context= new ClassPathXmlApplicationContext("Beans.xml");
    User user = (User) context.getBean("userNew");
    user.show();
}

在这里插入图片描述

要搞清楚的是,别名只是给你的bean的id改个名字,你没有这个bean取别名就会报错,别名也不能改变你类的结构,例如类名,属性名等

5.2Bean的配置

<bean id="User" class="com.mbw.pojo.User">
        <constructor-arg name="name" value="mbw"/>
</bean>

id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean对象所对应的全限定名
name:也是别名,而且name可同时取多个别名,完爆alias,别名之间用逗号,空格分隔

5.3 import

一般用于团队开发使用,因为不同人使用不同的类注定使用不同的bean,import可以将多个配置文件,导入合并到一个总的配置文件中!

我们现在创建一个bean.xml的”老大”,它叫做applicationContext.xml,这个名称才是bean.xml的全程,然后创建bean2.xml和bean3.xml,将beans和bean2,bean3合并到applicationContext.xml.
这里我们就要在applicationContext.xml通过import将这三个bean.xml合并到一块儿,使用的时候,直接使用总的配置就可以了

<import resource="Beans.xml"/>
<import resource="bean2.xml"/>
<import resource="bean3.xml"/>

import合并到applicationContext.xml后通过applicationContext.xml去获取context
测试仍然能创建对象,说明合并成功!

@Test
public void TestIOC(){
    ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User) context.getBean("userNew");
    user.show();
}

在这里插入图片描述

注意点:如果多个bean中有重名现象,那么当通过同一个名字取对象时,spring会随机选择其中一个创建

六、DI依赖注入环境

6.1构造器注入

之前已经讲过了

<constructor-arg name="name" value="mbw"/>

这里指定name属性并给name属性赋值

6.2 SET方式注入

·依赖注入:Set注入
。依赖:bean对象的创建依赖于容器
。注入:bean对象中的所有属性,由容器来注入
【环境搭建】
①复杂类型
地址类Address

package com.mbw.pojo;

public class Address {
    private String address;

    public String getA() {
        return address;
    }

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

2、学生类Student

package com.mbw.pojo;

import java.util.*;

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 Properties info;

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

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

    public String[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address=" + address +
                ", books=" + Arrays.toString(books) +
                ", hobbies=" + hobbies +
                ", card=" + card +
                ", games=" + games +
                ", info=" + info +
                '}';
    }
}

②bean.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="student" class="com.mbw.pojo.Student">
     <property name="name" value="mbw"/>
 </bean>
</beans>

③真实测试对象
在Test/java文件夹下创建MyTest
④不同类型的注入
1、普通的基本类型注入,直接使用value赋值即可

<property name="name" value="mbw"/>

测试:

System.out.println(student.getName());

在这里插入图片描述

2、引用类型的值注入
我们在bean.xml中创建address对象

<bean id="address" class="com.mbw.pojo.Address">
    <property name="address" value="南昌"/>
</bean>

之后在StudentBean注入,由于是引用类型,所以得用ref

<property name="address" ref="address"/>

测试:

System.out.println(student.getAddress().getA());

在这里插入图片描述

3、数组的注入,通过property标签下的array标签去对数组注入
然后通过value标签去赋值,可以有多副value标签

<property name="books">
    <array>
        <value>水浒传</value>
        <value>三国演义</value>
        <value>西游记</value>
        <value>红楼梦</value>
    </array>
</property>

测试:

String[] books = student.getBooks();
for (String book : books) {
    System.out.println(book);
}

在这里插入图片描述

4、List数组的注入
和Array方式一样,只是标签换成了list

<list>
    <value>音乐</value>
    <value>运动</value>
    <value>打游戏</value>
    <value>购物</value>
</list>

测试:
在这里插入图片描述

List<String> hobbies = student.getHobbies();
for (String hobby : hobbies) {
    System.out.println(hobby);
}

5、Map数组的注入,首先property标签下的map标签去注入一个map数组
然后在map数组下通过entry标签去设置key-value属性

<property name="card">
    <map>
        <entry key="身份证" value="233333333333333333"/>
        <entry key="银行卡号" value="666666666666666"/>
        <entry key="学生证" value="1802212022"/>
    </map>
</property>

测试:

Map<String, String> card = student.getCard();
System.out.println(card.get("身份证"));

在这里插入图片描述

6、Set数组的注入
和之前的array数组和list数组一样,标签换成set

<property name="games">
    <set>
        <value>LOL</value>
        <value>原神</value>
        <value>王者荣耀</value>
        <value>吃鸡</value>
    </set>
</property>

测试:

Set<String> games = student.getGames();
for (String game : games) {
    System.out.println(game);
}

在这里插入图片描述

7、Property类的注入
Property类主要是用来读取配置文件信息用的,同样是采用键值对存储,但它在注入上和Map有所不同,我们通过标签去注入property类,通过标签去赋值,但注意的是,这里的key的确是键,但是值不再是通过value(没value属性压根),值是写在prop标签中间

<property name="info">
    <props>
        <prop key="url">localhost/8080</prop>
        <prop key="username">root</prop>
        <prop key="password">123456</prop>
    </props>
</property>

测试:

Properties info = student.getInfo();
System.out.println(info);

在这里插入图片描述

8、Null值注入

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

或者

<property name="girlfriend" value=""/>

测试:

System.out.println(student.getGirlfriend());

在这里插入图片描述

6.3拓展方式注入

*我们可以使用p命令空间和c命令空间进行注入

环境搭建:
建立一个User类

package com.mbw.pojo;

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

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("调用了有参构造");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

建立一个UserBean.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     https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.mbw.pojo.User">

    </bean>

</beans>

1、c命名空间注入,通过构造器注入,相当于标签作用
①在beans标签内添加xmlns:p="http://www.springframework.org/schema/c,就可以使用c标签
②根据自己的有参构造器给构造器里的属性赋值,比方我的pojo里只有两个属性的有参构造器。所以必须得给两属性赋值

<bean id="user2" class="com.mbw.pojo.User" c:name="mbw" c:age="22"/>

③测试

User user=(User)Context.getBean("user2");

在这里插入图片描述

2、p命名空间注入,可以直接注入属性的值
①在beans标签内添加xmlns:p="http://www.springframework.org/schema/p,就可以使用p标签
②使用方法:直接在bean标签中加入p属性对类属性直接赋值。

<bean id="user" class="com.mbw.pojo.User" p:name="mbw" p:age="22"/>

③测试:

@Test
public void TestUser(){
    ApplicationContext Context = new ClassPathXmlApplicationContext("UserBean.xml");
    User user=(User)Context.getBean("user");
    System.out.println(user.toString());
    
}

在这里插入图片描述

注意点:p命名和c命名空间不能直接使用,需要导入xml约束

6.4 Bean的作用域

在这里插入图片描述

①单例模式(Spring默认机制)
scope=“singleton”
无论用几个对象去get,它们始终用的同一个bean的实例这些对象本质上是相等的,甚至地址也相等!即被这个bean单例出的对象!
测试:用两个对象去get同一个bean实例,判断这两个对象是否相等

User user=(User)Context.getBean("user2");
User user2=(User)Context.getBean("user2");
System.out.println(user==user2);

在这里插入图片描述

②原型模式
就是和单例模式反着的,你的每一个对象去get同一个bean的实例,这每一个被bean创建的对象都是单独的实例,彼此不相干!即每次从容器中get的时候,都会产生一个新对象
比方我现在在bean标签中加入

 <bean id="user" class="com.mbw.pojo.User" p:name="mbw" p:age="22" scope="prototype"/>

仍然测试之前的两个对象

User user=(User)Context.getBean("user");
User user2=(User)Context.getBean("user");
System.out.println(user==user2);

在这里插入图片描述

③其余的request、session、application,这些个只能在web开发中使用到!

七、Bean的自动装配

·自动装配是Apring满足bean依赖的一种方式
·Spring会在上下文中自动寻找,并自动给bean装配属性
在Spring中有三种装配的方式:
①在xml中显示的配置
②在java中显示配置
③隐式的自动装配bean【重点】

7.1测试

1、环境搭建:
一个人有两个宠物
①构建一个新模块Spring_05_Autowired
②创建com.mbw.pojo包,在包下创建Dog,Cat,Person类
Dog

package com.mbw.pojo;

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

Cat

package com.mbw.pojo;

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

Person

package com.mbw.pojo;

public class Person {
    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 "Person{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + name + '\'' +
                '}';
    }
}

③创建一个beans.xml
④测试类

原来:
我们手动通过xml显式装配bean
在这里插入图片描述

现在我们自动装配!
7.2 ByName自动装配
①实况举例

<property name="dog" ref="dog"/>
<property name="cat" ref="cat"/>

比方这两行赋值代码,其实有点多余,因为我们本来就有cat和dog,而且创建并放到容器里了,现在又要通过ref去手动注入,所以ByName出现了,它可以为我们自动装配这些引用对象!
用法:在bean标签内添加autowire=”byname”

<bean  autowire="byName">

②测试:
我们现在删掉手动注入两行引用对象的代码
然后在bean标签内添加autowire=”byname”

<bean id="person" class="com.mbw.pojo.Person" autowire="byName">
    <property name="name" value="mbw"/>
</bean>

Test一下

@Test
public void test1(){
    ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
    Person person =context.getBean("person",Person.class);
    person.getCat().shout();
    person.getDog().shout();
}

在这里插入图片描述

发现不手动赋值也能运行成功。
③ByName原理:
ByName会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean id(自动转化为小写)
什么意思?

<bean id="dog1" class="com.mbw.pojo.Dog"/>

比方我现在将dog bean的id改为dog1
而pojo类里的setDog不变(此时set后的值为dog)

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

测试发现报错,没找到dog的id
此时我将setDog改为setDog1

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

发现运行成功
说明ByName查找自己对象set方法后面的值对应的bean id

7.3 ByType

byType:会自动在容器上下文查找,和自己对象属性类型相同的bean!
测试:
这个时候我就把id改为dog111,setdog不变

<bean id="dog111" class="com.mbw.pojo.Dog"/>
<bean id="person" class="com.mbw.pojo.Person" autowire="byType">
    <property name="name" value="mbw"/>
</bean>

如果是刚才的byname,一定会运行失败
但是改为bytype
测试后发现运行成功,你甚至连id都不用,它也能给你自动装配
在这里插入图片描述

可是它有自己的弊端:就是得保证类型全局唯一
如果我现在创建两个dog对象
在这里插入图片描述

byType就无法使用!所以我们更多的还是使用byname!
因为byname只需保证id唯一且和set后面的字段名保持一致即可,其他约束没有。

7.4使用注解实现自动装配

Jdk1.5开始支持注解,而Spring2.5就开始支持注解了!
要使用注解须知:
1、导入约束:context约束

2、配置注解的支持:

<context:annotation-config/>

拓展:以后这样的配置会越来越多,如果每一个都到官网上粘贴复制会很繁琐
所以我们可以用一些小技巧简化操作
比如xmlns:context="http://www.springframework.org/schema/context"
我们用它和xmlns="http://www.springframework.org/schema/beans"
是不是就是把beans改为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">

我们把蓝色的和绿色的对比一下,是不是及其相似,和刚才一样,就是把beans换成context
那么后面比方AOP也是一样,我们把beans换成aop即可,这样就避免从官网拷贝的麻烦了!

①@Autowired
直接在属性上或者set方法前使用即可
测试:
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:context="http://www.springframework.org/schema/context"
       xmlns:https="http://www.springframework.org/schema/c"
       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/>
   <bean id="cat1" class="com.mbw.pojo.Cat"/>
   <bean id="dog" class="com.mbw.pojo.Dog"/>
   <bean id="person" class="com.mbw.pojo.Person"/>
</beans>

记住配置注解的支持:<context:annotation-config/>
person类:

@Autowired
private Cat cat;
@Autowired
private Dog dog;

我也可以在set方法前加入注解

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

测试通过,说明自动装配成功!
在这里插入图片描述

注意点:使用@Autowired我们连set方法都不用写了,前提是你这个自动装配的属性在IOC(Spring)容器中存在,它会先按bytype去寻找,如果byType不满足再按byName去找,如果都不满足就会报错!
我现在比方把pojo类中的set方法全部删去,并且在beans.xml中catbean的id改为cat1,并且创建两个dog对象
pojo:

package com.mbw.pojo;

import org.springframework.beans.factory.annotation.Autowired;

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

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + name + '\'' +
                '}';
    }
}
<bean id="cat1" class="com.mbw.pojo.Cat"/>
<bean id="dog" class="com.mbw.pojo.Dog"/>
<bean id="dog1" class="com.mbw.pojo.Dog"/>

测试:
在这里插入图片描述

仍然能通过,那么意味着我cat就算不符合byname但符合bytype,它也能通过,dog不符合bytype但其中有一个id符合byname,它也能通过,假如我现在把id=dog改为dog1

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'person': Unsatisfied dependency expressed through field 'dog'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.mbw.pojo.Dog' available: expected single matching bean but found 2: dog3,dog1

报错。因为此时dog对象不再满足byname或者byType
这就说明**@AutoWired只需满足byType或者byName其中之一即可**!且先byType再byName,即当注入容器存在多个同一个类型的对象时,就是根据byName进行装配,当注入IOC容器中该类型只有一个时,Autowired就通过byType装配
而连set方法都不用的原因-----反射

7.5拓展

①@Nullable 字段标记了这个注解,说明这个字段可以为null;
②@Autowired ,它的源码中有一个required()值为布尔值,且默认为true

public @interface Autowired {
    boolean required() default true;
}

如果显式地定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空!

@Autowired(required = false)

③@Qualifier
拿刚才的两个dog举例,我两个dog的id均不为dog
在这里插入图片描述

此时@Autowired均不满足byName和byType,所以报错
此时我们还有一个补救方法就是通过@Qualifier,它源码里有一个value属性默认为null
在这里插入图片描述

所以我们可以通过该注解指定一个id去装配,让@Autowired强行符合byName
在这里插入图片描述
在这里插入图片描述

运行成功,证明了@Qualifier的功能,起到的就是配合@Autowired使用的功能
④@Resource
一个java的注解(不是spring的)
这个的功能和@Autowired一样,但是比它好用,因为,它是一个组合注解
它可以囊括@Qualifier的功能,即当两个条件都不满足,我可以直接在@Resource后面跟值
比方,还是拿两只劳模狗为例子

<bean id="dog1" class="com.mbw.pojo.Dog"/>
<bean id="dog2" class="com.mbw.pojo.Dog"/>

如果直接使用@Resource会报错
但是通过它的name属性将它的值变成其中一个id的值就ok了

@Resource(name="dog1")
private Dog dog;

所以它整合了@Autowired和@Qualifier的功能!
不过,由于单例模式的存在,我们一般@Autowired就足够了

八、使用注解开发

在这里插入图片描述

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

②使用注解需要导入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"
       xmlns:https="http://www.springframework.org/schema/c"
       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>

③当然我们也可以通过

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

去扫描指定路径下的包,让包中所有类的注解生效!
④环境搭建
1、创建一个新模块Spring_06
2、创建applicationContext.xml
3、创建service层,dao层,pojo层,controller层
4、在pojo层建立User类

package com.mbw.pojo;
public class User {
    public String name="孟博文";

}

8.1 bean

@conponent:组件,放在类上,说明这个类被Spring管理了,就是bean
比如:

@Component
public class User {
    public String name="孟博文";

}

它等价于<bean id="user" class="com.mbw.pojo.User">
Id默认为类名的小写
测试:

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

}

在这里插入图片描述

8.2属性如何注入

之前我们通过bean标签下的property标签注入
现在我们也同样可以通过注解注入属性
@Value:对属性进行注入

@Value("孟博文")
public String name;

测试结果同上!
注意的是:如果@Value内没赋任何值,那么就按基本类型的默认值处理,比如String默认值为null,那么@Value此时值就是null
当然,这个注解无法注入过于复杂的类型,例如map,list等等,这时候还是推荐使用property标签。
而且,如果该类的属性拥有相应的set方法,也可将@value放到set方法前

@Value("孟博文")
public void setName(String name) {
    this.name = name;
}

8.3衍生的注解

@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层
下面的三个注解和@component功能一样,都是将类注册到Spring托管,装配bean

·dao【@Repository】

比方现在在dao层建立一个UserDao类

@Repository
public class UserDao {

}

·service【@Service】

@Service
public class UserService {
}

·controller【@Controlller】

@Controller
public class UserController {
}

这时候我们发现这些注解还没生效,是因为我们在applicationContext.xml文件的<context:component-scan base-package="com.mbw.pojo"/>,注解的支持标签只扫描了pojo包下的所有类,因此我们要将其改为com.mbw包才行!

8.4自动装配

之前已讲过
①@Nullable字段标记了这个注解,说明这个字段可以为null;
②@Autowired ,实现自动装配通过类型,名字
@Resource:实现自动装配通过类型,名字

8.5作用域

@Scope:声明Bean的作用域,它有很多值,声明不同值意味着该bean为不同的作用域
①单例模式

@Scope("singleton")

②原型模式

@Scope("prototype")

8.6小结

Xml与注解:
·xml更加万能,适用于任何场合,维护简单方便
·注解不是自己的类使用不了,维护相对复杂
Xml与注解最佳实践:
·Xml负责管理bean
·注解只负责完成属性的注入
·我们在是哦也能够的过程中,只需要注意一个问题,必须让注解生效,必须要开启Context约束和注解支持标签

九、使用Java的方式配置Spring

我们现在要完全不使用Spring的xml配置了,全权交给Java来做!
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个一个核心功能!
①环境搭建
建立一个新模块Spring_07_AppConfig
并建立com.mbw.pojo和com.mbw.config两个包
在pojo包下建立User类,并在setName前添加@Value注解,给name属性赋值

package com.mbw.pojo;

import org.springframework.beans.factory.annotation.Value;
public class User {
    private String name;

    public String getName() {
        return name;
    }
   @Value("孟博文")
    public void setName(String name) {
        this.name = name;
    }

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

②在config包下建立MbwConfig类,在类前添加一个**@Configuration注解**
这个注解表明我这是配置类,相当于beans.xml一样!它会在类加载时最先执行一遍包括类里面带有@Bean的方法,然后再是加载类和自动装配注入。这也就意味着我们配置类被先执行了一遍,可以对我们后面的类作出一些控制,故一般被@Configuration注解修饰的类都可以称为配置类!

package com.mbw.config;

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

@Configuration

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

③重头戏来了:现在我们的需求是不建立xml文件的情况下,将我们的User类注册为bean.
做法1:我们在config类添加一个返回值为User的方法,在方法前添加一个注解@Bean,@Bean的作用就和一样,将这个方法的返回类型注册为bean,而方法名相当于bean标签的id,返回值相当于bean标签的class属性,即指定注册的bean类型
注意点:这个与@Component不同,两个的作用虽然都是注册bean,但是@Component还是得基于配置文件/类来进行注解的支持才能使用。而@bean是直接基于方法。
④测试:这里注意的是,因为我们不再使用xml文件去配置,而是通过注解配置
之前,我们通过ClassPathXmlApplicationContext去加载xml文件
这次我们需要使用AnnotationConfigApplicationContext上下文对象去加载我们的配置类,参数内填写我们的配置类

@Test
public void Test1(){
        ApplicationContext context=new AnnotationConfigApplicationContext(MbwConfig.class);
    User user = context.getBean("user", User.class);
    System.out.println(user.getName());

}

在这里插入图片描述

这样感觉是不是有点代码冗余的感觉,我写了一个User类,还要为了将它注册为bean专门写一个方法。所以就有了下一个做法
⑤做法2:
刚才也讲到了@Component,它可以直接放到类前面就代表注册类,但是它有一个前提,就是需要配置文件对注解的支持,比如之前的扫描指定包下的类的所有注解,并给予支持

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

那么我们是否也能在配置类中做到
答案是:能
我们现在将配置类的getUser方法删去,加入一个扫描指定包下的注解@ComponentScan注解,你可以通过对该注解赋值意思就是让它扫描指定包下的
组件,它的作用就相当于<context:component-scan base-package="com.mbw"/>
这样就彻底简化了代码

@ComponentScan("com.mbw.pojo")

测试:
@ComponentScan(“com.mbw.pojo”)
public class MbwConfig {
//@Bean:注册一个Bean,就相当于我们之前写的一个bean标签
//这个方法的名字,就相当于bean标签的id属性
//这个方法的返回值就相当于bean标签的class属性

}
这时只需在User类前加入@Component即可

@Component
public class User {

这时bean id就和之前讲的一样,为类名的小写

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

}

在这里插入图片描述

注意点:如果我同时使用@bean和@component也不会报错,它相当于同一个对象被实例化了两次,不是两个对象,他俩分别通过这个创建的对象的hashcode值也是一样的!所以他俩差别不大,只是得注意两者使用条件!
·在开发当中,注定存在多个bean和多个bean.xml所以在之前我们讲到了import标签
在java-config中,同样也能通过import标签将多个bean合为一体
我们再写一个配置类MbwConfig2
然后再MbwConfig类方法前加上Import标签,标签内参数填你要并入的配置类

@Import(MbwConfig2.class)

十、代理模式

为什么要学习代理模式:因为这就是Spring AOP的底层!【Spring AOP和Spring MVC面试重点】要想学好AOP就先得学会代理模式
代理模式分为静态代理和动态代理
拿日常中租房来说,租房你一般都会通过中介找到房东,而中介和房东都需要去完成一个任务就是租房,这时中介相当于一个代理角色,房东作为一个真正的角色,而你作为一个真实的人。
在这里插入图片描述

代理模式的好处:
·在不修改原有代码的基础上,对原有功能进行增强
·改动原有的代码,在公司是大忌!
·可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
·公共也就交给代理角色!实现了业务的分工
·公共业务发生扩展的时候,方便集中管理!
缺点
·一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会降低,但是可以通过动态代理解决!

10.1静态代理

角色分析:
·抽象角色:一般会使用接口或者抽象类来解决
·真实角色:被代理的角色
·代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
·客户:访问代理对象的人
测试:仍然拿租房来说明
抽象角色:租房,一般是接口或者抽象类

package com.mbw.pojo;
//抽象角色:租房
public interface Rents {
    public  void rent();
}

真实角色:房东,只想把房子出租,那么他就只需要实现租房接口,重写方法即可

package com.mbw.pojo;
//真实角色:房东(他要把房子租出去)
public class Host implements Rents {

    public void rent() {
        System.out.println("房东要出租房子");
    }
}

代理角色:中介,帮助房东出租房子,通同时扩展业务,所以他需要将房东对象封装起来,即组合!这样就可以不用通过继承也可以享受房东的功能,避免了多继承的错误

package com.mbw.pojo;
//代理角色:中介
public class Proxy {
    private Host host;
    public Proxy(Host host){
        this.host=host;
    }
    public  void rent(){
        seeHouse();
        host.rent();
        agree();
        seekMoney();
    }
    //看房
    public void seeHouse(){
        System.out.println("中介带你看房");
    }
    //收中介费
    public void seekMoney(){
        System.out.println("中介收中介费");
    }
    public void agree(){
        System.out.println("中介签合同");
    }

}

客户:你自己,要找中介完成一系列操作,即被代理
那么你就需要在你的类里去建立一个代理对象

package com.mbw.pojo;

public class Client {
    public static void main(String[] args) {

        Host host=new Host();
        //不代理,直接找房东
//        host.rent();
        //代理,找中介,中介帮(代理)房东出租房子,但伴随着一些附属操作
        Proxy proxy=new Proxy(host);
        proxy.rent();

    }
}

测试运行后:
在这里插入图片描述

你会发现,房东确实出租了房子,伴随的是中介的三个操作,都被完成了!说明了代理模式的扩展业务能力!
在我们平常开发中,遵循着controller-service-dao层这么纵向开发的过程的,假设我现在需要在service层原来的增删改查方法基础上加入一个日志功能的实现,我们刚才就知道,动用原来代码结构是大忌,就算公司允许,假设业务千万,你就需要改千万条,这样是很影响开发效率的。这时候代理起的就是这样一个横向开发的作用,在不影响代码原有基础上,去扩展我们原来代码的功能。
在这里插入图片描述

10.2、动态代理

①介绍
·动态代理和静态代理的角色一样
·动态代理类是动态生成的,不是我们直接写好的!
·动态代理分为两大类:基于接口的动态代理,基于类的动态代理
。基于接口–JDK动态代理
。基于类:cglib
。java字节码实现:javassist
需要了解两个类:Proxy:代理,InvocationHandler:调用处理程序
·InvocationHandler:
。它是由代理实例调用处理程序实现的一个接口,位于反射包下
。每个代理实例都有一个关联的调用处理程序,当在代理实例上调用方法时,方法调用将被编码分派到其调用处理程序的invoke方法
·Proxy:
。它提供了动态代理类和实例的静态方法,比方我可以通过它获取InvocationHandler或者我可以用它创建一个动态实例
。它也是由这些方法创建的所有动态代理类的父类。
②动态代理的好处:
·可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
·公共也就交给代理角色!实现了业务的分工
·公共业务发生扩展的时候,方便集中管理!
·一个动态代理类代理的是一个接口,一般就是对应一类业务
·一个动态代理类可以代理多个类,只要是实现了同一个接口即可

③环境搭建:
1、建立一个包demo3
我们的抽象角色还有真实角色都是必须存在的,即接口和被代理类
但是在这里我们对代理类进行改动
在静态代理中,我们是在原有方法基础上进行功能扩展,虽然起到了代理效果,但若是改动需求过多,静态代理就使得代码量变得颇多,因为每有一个被代理类就象征着创建一个代理类,几乎在源代码基础上翻了一番。
所以为了能够更方便灵活的创建代理类去施行代理方法,就有了动态代理
动态代理需要创建一个实现InvocationHandler接口的类,并实现里面的invoke方法

public class ProxyInvocationHandler implements InvocationHandler {
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}

那么这就是InvocationHandler的作用,处理代理对象的方法,并返回一个结果!
但是这还不够,我们仍然没有创建出我们的代理对象
2、所以还需要通过Proxy类的newProxyInstance方法创建一个proxy对象
newProxyInstance需要三个参数,第一个是当前类的加载器,第二个是代理类的接口,第三个是当前对象。

public Object getProxy() {
    return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

·类的加载器很容易,我们可以用this.getClass.getClassLoader获取
·代理类的接口
我们需要在该类对我们的代理接口进行封装组合,且因为我们一般让代理对象引用方法并不直接是接口的方法,而是真实角色(被代理类)的方法,这就需要一个set方法对接口进行注入,这样客户使用就可以直接将接口子类注入接口,让代理类直接使用被代理类的方法了

//被代理的接口
private Rents rents;

public void setRents(Rents rents) {
    this.rents = rents;
}

那么获取了接口,newProxyInstance的参数就很容易了,即rents.getClass().getInterfaces(),
·当前对象:this关键字就行
那么我们就可以获取生成代理类的方法了:
//生成得到代理类

public  Object getProxy(){
    return  Proxy.newProxyInstance(this.getClass().getClassLoader(),rents.getClass().getInterfaces(),this);
}

3、修改Invoke方法
只需将方法中加入一个invoke方法,给方法指定相应的对象和参数,我们这里使用封装的接口rents即可
当然,同之前讲的代理对象一样,我们仍然可以加入一些中间操作
比如加一个中介带看房的方法,这时只要直接将它invoke方法相应的位置即可

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //动态代理的本质就是使用反射实现
        seeHouse();
        Object result = method.invoke(rents, args);
        return result;
    }
    public void seeHouse(){
        System.out.println("中介带你看房");
    }
}

4、测试:通过客户new一个InvocationHandler实例
然后通过实例set注入host这个真实角色对象
再通过刚刚我们手写的实例的getProxy方法得到一个代理类,然后就可以使用方法了!

package com.mbw.pojo.demo3;

public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host=new Host();
        //代理角色:现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象
        pih.setRents(host);
        Rents proxy = (Rents) pih.getProxy();
        proxy.rent();
    }
}

在这里插入图片描述

·看到这里很多人不明白,我在Invocationhandler里的invoke方法为什么会被自动执行?
这里就涉及到底层源码,可能笔者解释的也不是很清楚,详细请去查看这篇博客:
[https://blog.csdn.net/qq_39056197/article/details/102598674]
大致意思就是通过proxy.newInstance出来的对象并不是我们直接的proxy对象,而是Proxy引用对象,这个对象的test方法里有一个this.invoke,然后再返回proxy对象,看到这儿大家应该就大致明白了,只要我的代理对象使用方法,它就会去使用引用对象$Proxy内的test方法去调用我们的handler类的Invoke方法,这样就实现了自动调用invoke方法的功能!

十一、AOP

11.1什么是AOP

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

11.2AOP在Spring中的作用

提供声明式事务:允许用户自定义切面
·横切关注点:跨越应用程序多个模块的方法或功能,既是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,如日志,安全,缓存,事务等等
·切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类,比方日志类log
·通知(Advice):切面必须要完成的工作,即,它是类中的一个方法,Log类中的方法
·目标(Target):被通知对象。其实就是一个接口或者抽象方法。
·代理(Proxy):向目标对象应用通知之后创建的对象,生成的代理类
·切入点(PointCut):切面通知执行的“地点”的定义,即在哪个地方执行
·连接点:(JointPoint):与切入点匹配的执行点

11.3使用Spring实现AOP【重点】

①使用AOP织入,需要向pom.xml导入一个依赖包

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

②环境搭建
构建一个新模块Spring_09_AOP
在src/main/java下新建com.mbw.service包,在service包下新建一个UserService接口和一个UserServiceImpl类
UserService接口

package com.mbw.service;

public interface UserService {
    public  void add();
    public  void delete();
    public  void update();
    public  void register();
}

UserServiceImpl

package com.mbw.service;

public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {

        System.out.println("删除了一个用户");
    }

    public void update() {

        System.out.println("修改了一个用户");
    }

    public void register() {

        System.out.println("查询了一个用户");
    }
}

方式一:使用Spring的API接口【主要SpringAPI接口实现】
①在mbw包下新建一个log包
②在log包分别新建两个类BeforeLog和AfterLog类,分别代表前置日志和后置日志的意思。
③BeforeLog,要想实现前置方法,需要实现一个前置方法通知MethodBeforeAdvice接口,并且重写before方法,里面有三个参数,method代表要执行的方法,args代表参数,target代表被代理接口

package com.mbw.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {
    //method:要执行的目标对象的方法
    //arg:参数
    //target:目标对象
    //
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");

    }
}

④AfterLog,和上面一样,需要实现一个后置返回通知接口AfterReturningAdvice,
并重写afterReturning方法,它比刚才的before方法多了一个参数,就是返回值!

package com.mbw.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    //returnValue:返回值
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+method.getName()+"返回结果为"+returnValue);
    }
}

⑤建立一个配置文件applicationContext.xml,首先我们需要导入aop约束

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

并且在xsi:schemaLocation增加相关配置

http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd

接着把我们三个类注册为bean
⑥在xml文件中使用aop标签完成代理功能
首先需加入<aop:config>标签
在config标签内加入切入点标签,意思是我要在哪里执行方法,id表示切入点id,你可以有多个切入点。expression表示表达式,内容就是在哪里执行方法
那么固定格式为execution(* (…))
**两个星号分别代表返回类型,类名,
(…)代表方法名,代表所有方法,(…)参数*
比方* com.mbw.service.UserServiceImpl.,serviceImpl类下的全部方法,后面的(…)代表剩余的参数

<aop:pointcut id="pointcut" expression="execution(* com.mbw.service.UserServiceImpl.*(..))"/

然后加入方法环绕标签,把我们的前置后置方法加入到要被执行的method内
Advice-ref就代表引用哪个对象,切入点的引用对象就是我们刚才的切入点了

<aop:advisor advice-ref="BeforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="AfterLog" pointcut-ref="pointcut"/>

⑦测试:主要注意的一点就是,动态代理的是接口,不是直接的实现类,实现类对于接口而言只是一种注入关系,相当于之前说动态代理的时候那个注入一样

import com.mbw.service.UserService;
import com.mbw.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext Context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理的是接口:注意点
        UserService userService = Context.getBean("userService", UserService.class);
        userService.add();
    }
}

方式二:自定义来实现AOP【主要是切面定义】
这次我们不再去实现前置方法通知接口和后置返回通知两个接口了。而是通过自定义方法去实现代理
①在com.mbw新建一个DIY包,在包下新建一个DiyPoint类
DIYPoint类,分别写before方法和after方法

package com.mbw.DIY;

public class DiyPointCut {
    public void before(){
        System.out.println("=========方法执行前===========");
    }
    public void after(){
        System.out.println("=========方法执行后===========");
    }
}

②回到刚才配置文件中
我们首先得将DIY类注册为bean
然后就通过aop的切面标签自定义我们的切面,ref表示要引入的类,这里引入diy bean

<aop:aspect ref="diy">

这下我们就可以写切入点,before,after标签了,切入点仍然是之前的UserServiceImpl类下的所有方法
Before标签和after标签都有两个属性
一个是method,它可以指定java的方法和自定义切面内的方法
另一个是切入点引用,那么这边就引入我们的point bean.

<!-- 切入点-->
     <aop:pointcut id="point" expression="execution(* com.mbw.service.UserServiceImpl.*(..))"/>
 <!--通知-->
     <aop:before method="before" pointcut-ref="point"/>
     <aop:after method="after" pointcut-ref="point"/>

③测试

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext Context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理的是接口:注意点
        UserService userService = Context.getBean("userService", UserService.class);
        userService.add();
    }
}

在这里插入图片描述

这个方法虽然比之前利用API接口实现AOP简单很多,但同时代价是实现的功能比较单一,在API内我们可以对类,方法做出相应的操作,而在DIY中只能写一些比较单调的代码进行代理。

11.4注解实现AOP

①在之前DIY包下新建一个AnnotationPointCut类
在类前加入一个@Aspect注解,表示将这个类注册为一个切面,和之前的
<aop:aspect/>的作用是一样的
②然后就可以加入我们的before,after方法,只需在方法前添加一个@before(after)注解即可,注解内参数仍然是之前的执行器即执行地点,表明这是执行方法前(后)的意思
在这里还可以介绍一下方法环绕
③我们再写一个around方法
并给它加上一个@around标签
around毕竟是环绕,所以它肯定与before,after有不同点,至少有一个中间角色
果然around方法可以加入一个参数ProceedingJoinPoint jp,它可以通过proceed方法去执行方法,以此为中间点去控制环绕过程
所以这个方法的核心就三部分
·环绕前
·执行方法
·环绕后

当然ProceedingJoinPoint jp还可以做很多事,比如打印方法签名,
jp.getSignature(),当然对于现在这些都是无关紧要的。玩玩儿就行。

package com.mbw.DIY;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//使用注解实现AOP

@Aspect//标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.mbw.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("=========方法执行前===========");
    }
    @After("execution(* com.mbw.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("=========方法执行后===========");
    }
    //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.mbw.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp){
        System.out.println("环绕前");
        try {
            //获取方法签名
            Signature signature = jp.getSignature();
            System.out.println("签名为"+signature);
            //执行方法
            Object proceed = jp.proceed();
            System.out.println("环绕后");
            System.out.println(proceed);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

④那么将他们和before,after综合起来,他们的执行顺序呢?
待会儿我们通过测试就可以看出来!
在此之前,我们得去配置文件将新建的这个annotationPointCut类注册为bean,当然你直接在类前加入@Component也行,但是做重要的是要加入aop注解的支持

 <bean id="annotationPointCut" class="com.mbw.DIY.AnnotationPointCut"/>
<!--    开启注解支持-->
    <aop:aspectj-autoproxy/>

⑤测试:
仍然是之前的测试代码
在这里插入图片描述

我们发现,是先出现的环绕前,然后jp通过proceed()执行方法,执行方法之前就会先打印before方法的“执行方法前”,然后执行方法,方法执行完后,紧接着打印出环绕后,然后打印after方法“执行方法后”!

十二、整合mybatis

12.1环境搭建

在这之前,大家对mybatis快忘记的人需要赶快复习,笔者就是”受害者之一”
回忆一下mybatis很基础的大致步骤:
①连接数据库
②编写实体类
③编写核心配置文件mybatis-config.xml
④编写mapper(接口和xml文件),记得将mapper注册到核心配置文件中
⑤编写工具类:sqlsessionFactoryBuilder-sqlsessionFactory-SqlSession
⑥测试:sqlsession-mapper-运行测试

步骤1:导入相关依赖
。Junit

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
    。Mysql-connector
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
    。Mybatis相关
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
Spring相关
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
    。Mybatis-Spring【NEW】:整合spring和mybatis用的
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.3</version>
</dependency>
    。AOP织入
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
Spring-JDBC
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

步骤2:编写配置文件
步骤3:测试

12.2 Mybatis-Spring

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中。
一、整合Mybatis方式一
①要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。过去在mybatis中,SqlsessionFactory我们是通过代码(工具类)写死的,而数据映射器类我们在配置文件也已经写死了。这样是非常笨重的,例如一旦出现新的mapper就得全部流程重新走一趟, SqlSessionFactory的代码量也十分庞大,在spring中,这些都不再变得笨重,我们可以在spring的xml文件中非常灵活地配置
②先配置SqlSessionFctory
注意:SqlSessionFactory 需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
1、建立一个spring-dao.xml,先配置我们的数据源datasource
在这里我想通过对比学习来对比一下mybatis和spring的区别
首先mybatis:

<environments default="development">
    <environment id="development">
        <!--事务管理,默认使用JDBC-->
        <transactionManager type="JDBC"/>
         <!--数据源-->
        <dataSource type="POOLED">
            <!--jdbc驱动-->
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="studentm200033"/>
        </dataSource>
    </environment>
</environments>

当然通过db.properties后
我们可以通过<properties resource="db.properties"></properties>引入配置文件简化mybatis的配置
这里的property指定的就是四个属性driver,url,username,password

<environments default="development">
    <environment id="development">
        <!--事务管理,默认使用JDBC-->
        <transactionManager type="JDBC"/>
        <!--数据源-->
        <dataSource type="POOLED">
            <!--jdbc驱动-->
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>

Spring-dao.xml
我们这里就不再采取繁琐的configuration配置,而是通过spring-jdbc的数据源对核心配置文件进行替换,前提是需要对spring-jdbc引入依赖
我们在这里将数据源注册为bean,class引用spring下的DriverManagerDataSource(事务管理数据源),意思其实就是将mybatis的transactionManager和datasource进行融合,默认方式为jdbc和pooled
中间可以对属性进行操作
其实就是mybatis核心配置文件的四个属性,driver,url,uesrname,password
那么问题是,spring如何将db.properties引入呢
通过标签<context:property-placeholder >就可在xml文件引入指定properties文件
这样就完美的替换了mybatis的一大段配置代码

<context:property-placeholder location="classpath:db.properties"/>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</bean>

注意点:在spring的配置文件中,加载数据库的properties文件,利用${username} 获取数据库用户名时,会得到系统当前的用户名;
那么${username}就不是root,而是你的系统的用户名,如果直接测试。会有如下异常:

 nested exception is java.sql.SQLException: Access denied for user '14437'@'localhost' (using password: YES)

我这边系统用户名为14437,所以它并没有获取root,而是获取14437
解决办法①:修改properties文件
既然username获取不到我的root,那么我单独写一个root进去,用别的引用
在properties文件写一个pp=root,避免和${username}发生冲突
然后回到spring配置文件
在这里插入图片描述
在这里插入图片描述

解决办法②:
我就是想让我的${username}生效怎么办
答案是可以做到!
在context:property-placeholder/标签添加local-override=“true”

<context:property-placeholder local-override="true" location="classpath*:jdbc.properties"/>

即本地配置覆盖系统的配置,这样${username}优先获取的是配置文件中的内容,就是root

2、配置sqlsessionfactory
3、在MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。并对我们的SqlSessionFactory注入引用类datasource.

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

4、绑定mybatis配置文件
我们可以在SqlSessionFactoryBean绑定mybatis配置文件
通过property去绑定我们核心配置文件的多个属性
①直接绑定核心配置文件,property标签的name属性为configLocation,value就是类路径下的核心配置文件名

<property name="configLocation" value="classpath:Mybatis-config.xml"/>

②虽然已经绑定了配置文件,但是我们可以使核心配置文件更加”简洁”
我们可以通过value去绑定我们的mapper,property标签的name属性为mapperLocations,value就是类路径下的mapper的xml文件

<property name="mapperLocations" value="com/mbw/mapper/*.xml"/>

配置完spring-dao.xml文件后,回过头看看被我们替换后的mybatis核心配置文件有多干净,虽然可以完全让它毫无存在感(把别名都给配了),但还是要给mybatis点面子,让用户知道你用了mybatis,所以按照个人习惯可在核心配置文件多多少少留一点东西,一般来说会留下别名标签和settings设置标签
在这里插入图片描述

③获取sqlSession:
Spring中,我们就不再使用sqlsession,而是spring给出的SqlSessionTemplate
,template的意思是模板,例如后面会用到的redisTemplate等等,SqlSessionTemplate你暂时和sqlSession划等号即可。
我们在spring-dao.xml引入SqlSessionTemplateBean
至此,这份配置文件就非常固定,我们先在配置文件配置了数据源,紧接着通过SqlSessionFactoryBean建造SqlSessionFactory并注入引用类DataSorce.
最后通过SqlSessionTemplate建造SqlSession并注入引用类SqlSessionFactory。
这份配置非常固定,几乎所有的整合都需要这样一个过程,剩下的就是使用。所以我们完全可以将这份配置文件单独保留为一份,而使用的再通过另外的Spring配置文件去处理,最后通过import将所有的配置文件整合在一起,使配置更加清晰可读。
④你可以有两种方式去使用
先介绍官方的:
你可以去建造一个UserMapperImpl实现类
然后将sqlSession给封装起来,并写一个set方法用来测试注入sqlSession
最后我们通过sqlSession去getMapper获取mapper,通过mapper实现方法即可。

package com.mbw.mapper;

import com.mbw.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl implements UserMapper {
    //我们所有的操作都是使用sqlSession来执行,在原来,现在都使用SqlSessionTemplate
    private SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSession){
        this.sqlSession=sqlSession;
    }
    public List<User> selectUser() {
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

最后还需要将
再来一种我自己研究的:
我觉得再次写一个实现类有一个好处就是可以封装sqlsession.并且因为封装为bean的原因,可以通过context直接获取调用方法,方便测试不用再重复写代码。虽然还是要通过set。。,而且还得写一遍context
但是如果按照上述那样写,有几个不妥
①你把sqlsession封装成bean又有什么意义呢,仍然还是通过sqlTemplate去创建的,为了一时的代码轻松放弃bean的封装?
②实现类,你让mapperDao接口情何以堪,实现的方法返回的结果一模一样不说,即并没有去拓展原来接口的任何功能,反而还要再多注册一个bean,显得累赘!
③测试类其实仍然挺复杂的,还是得通过上下文去获取对象,然后调用方法
下面我来介绍一下我的方法思路
因为我觉得所有的Sql语句都是最终由我们的mapper来处理,而mapper的确是由SqlSession获取没错,所以我决定不写实现类,而是直接冲着getMapper去,并将它封装为静态。
我们先获取上下文对象context,然后通过context.getBean去获取我们SqlSession
并将它们都封装为静态,方便调用,且我们的sqlSession仍然是由SqlSessionTemplate类创建的对象
之后写一个静态方法getMapper,去获取mapper,这样测试类只需到时候通过类名.getMapper方法获取Mapper,最后调用接口方法即可!
当然我的方法相较官方也有一个最大的缺点,就是官方将context对象放到测试上,

package com.mbw.mapper;

import com.mbw.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class GetMapper {
    private static ApplicationContext context = new ClassPathXmlApplicationContext("Spring-dao.xml");
    private static SqlSessionTemplate sqlSession = context.getBean("sqlSession",SqlSessionTemplate.class);
    public static UserMapper getMapper(){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper;
    }

}

⑤测试
先来官方的:累赘挺多的,逃不掉去通过上下文去获取

 @Test
    public void test3(){
ApplicationContext Context = new ClassPathXmlApplicationContext("Spring-dao.xml");
        UserMapperImpl userMapperImpl1 = Context.getBean("UserMapperImpl1", UserMapperImpl.class);
        for (User user : userMapperImpl1.selectUser()) {
            System.out.println(user);
        }
    }

再来我自己的方法:直接通过类名.getMapper去获取mapper
然后就是mybatis的老一套,去使用接口的方法。

@Test
public void test2(){
    UserMapper mapper = GetMapper.getMapper();
    List<User> users = mapper.selectUser();
    for (User user :users) {
        System.out.println(user);
    }
}

二、整合Mybatis方式二
之前实现类封装了sqlSession,但是要想使用还是得用到注入
而方式二就是免除注入,但代价是需要继承一个类sqlSessionDaoSupport,这个类会为你自动创建sqlSession,然后剩下的和方式一同理,记得仍然是实现类以及把它注册成bean

package com.mbw.mapper;

import com.mbw.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

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

注册bean步骤如下:
记住虽然不用注入,但是由于SqlsessionDaoSupport需要sqlSessionFactory的支持,必须得注入sqlSessionFactory引用对象。

<bean id="UserMapperImpl2" class="com.mbw.mapper.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

测试:

 @Test
    public void test4(){
ApplicationContext Context = new ClassPathXmlApplicationContext("Spring-dao.xml");
        UserMapperImpl2 userMapperImpl2 = Context.getBean("UserMapperImpl2", UserMapperImpl2.class);
        for (User user : userMapperImpl2.selectUser()) {
            System.out.println(user);
        }
    }

在这里插入图片描述

十三、声明式事务

13.1回顾事务

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

13.2事务ACID原则

①原子性(A):指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
②一致性(C):事务前后数据的完整性必须保持一致
③隔离性(I):多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
④持久性(D):事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中
为什么需要事务?
·如果不配置事务,可能存在数据提交不一致的情况:
·如果我么不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务,为了安全!
·事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!

13.3事务实例显现

①环境搭建:创建一个新模块spring_11_transaction
并把spring_10的pojo包下的user类,resource包下的jdbc.properties,spring-dao.xml,mybatis-config.xml,mapper包下的UserMapper,UserMapper.xml,UserMapperImpl2全部复制过来。
在这里插入图片描述

②在UserMapper写一个addUser方法用来增加用户,再写一个deleteUser的方法用来删除用户

public int addUser(User user);
public int deleteUser(int id);

③在UserMapper.xml中增加insert标签和delete标签,并写上sql语句
注意,这里我们为了研究事务的同时成功,同时失败的性质,我们insert语句正常写,delete的sql语句故意写错,以验证到时候放到同一个业务中出错了会不会全都失败。
④实现UserMapperImpl2的两个方法,并在selectUser内将add,delete两个方法全放进来,以便凑成一个业务!

package com.mbw.mapper;

import com.mbw.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectUser() {
        User user = new User(7, "钟离", "394");
        UserMapper userMapper = getSqlSession().getMapper(UserMapper.class);
        userMapper.addUser(user);
        userMapper.deleteUser(7);
        return userMapper.selectUser();
    }

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

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

⑤测试:

 @Test
    public void test4(){
ApplicationContext Context = new ClassPathXmlApplicationContext("Spring-dao.xml");
        UserMapperImpl2 userMapperImpl2 = Context.getBean("UserMapperImpl2", UserMapperImpl2.class);
        for (User user : userMapperImpl2.selectUser()) {
            System.out.println(user);
        }
    }

我们知道,由于delete的sql语句错误,它一定会报错,按道理,delete执行失败,由于事务的原子性,addUser也不能成功,但是回看数据库。
在这里插入图片描述

发现添加成功,没有删去,这就不符合事务的性质,下面就让我们来研究Spring声明式事务

13.4、Spring中的事务管理

·声明式事务:AOP
·编程式事务:需要在代码中,进行事务的管理,即比方我在前面insert语句加一个try catch语句,如果出现异常,则回滚操作这样,但是我们在代理模式说过,改变代码结构是大忌,所以我们一般不会去尝试编程式事务,这里也不过多介绍!我们主要在这儿介绍声明式事务!
需要知道的是:Spring 总是为你处理了事务。你不能在 Spring 管理的 SqlSession 上调用 SqlSession.commit(),SqlSession.rollback() 或 SqlSession.close() 方法。如果这样做了,就会抛出 UnsupportedOperationException 异常。
在这里插入图片描述

13.5、Spring声明式事务

①配置声明式事务
Spring-dao.xml中,配置声明式事务,记住它需要注入datasource数据源引用类

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

②结合AOP,实现事务的织入
一、首先,需要配置事务,先得导入事务的约束:

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

并且在XSI:schemaLocation中增加配置:

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

三、配置事务的通知:这个是死代码,spring自动给你配好的

<tx:advice id="txAdvice" transaction-manager="transaction">

四、指定要给哪些方法配置事务,并配置事务的传播特性
首先是指定方法:

<tx:attributes>
    <tx:method name="add" propagation=""/>
    <tx:method name="delete"/>
    <tx:method name="update"/>
    <tx:method name="query"/>
    <tx:method name="*"/>
</tx:attributes>

然后再来看看事务的七大传播特性【new】—propagation(不需要强行记忆)
REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。这也是事务的默认的传播特性
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。这是使用率第二的传播特性

<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>

五、配置事务切入(AOP)
在回顾一遍,首先使用SpringAPI去实现AOP
你需要一个aop:config标签
在这个标签下你需要配置aop:pointCut即切入点,去声明你要在哪里去实行方法
它的参数一个id自己写,另一个expression下有一个execution用来指定具体的方法,第一个代表方法类型,mapper后的代表mapper包下的所有类,再后头的*代表所有方法,(…)代表方法参数。
最后配置方法环绕标签,即在这个切入点用什么方法
那当然就是我们之前配置的事务标签了。

<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.mbw.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

③测试
报错仍然报错,但我们发现id=7没被插进去,说明事务生效
在这里插入图片描述

注意点:这个错误也在之前讲过

Bean named 'userMapper' is expected to be of type 'com.cheng.mapper.UserMapperImpl' but was actually of type 'com.sun.proxy.$Proxy13'

在这里插入图片描述

只需要简单查看一下,就能发现问题是出在获得Bean这里,动态代理所代理的是接口,但我这里转成了接口的实现类,自然就会报错。

UserMapperImpl2 userMapperImpl2 = Context.getBean("UserMapperImpl2", UserMapperImpl2.class);

修改成下面的代码问题就解决了!

UserMapper userMapperImpl2 = Context.getBean("UserMapperImpl2", UserMapper.class);
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐