5. Spring整理

92 阅读21分钟

一、简介

1.1 基本介绍

  • 目的:解决企业应用开发的复杂性
  • 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
  • 范围:任何Java应用

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

  • 框架

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

1.2 所需要的包

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

1.3 优点

  • Spring是一个开源免费的框架
  • Spring是一个轻量级、非入侵的框架
  • 控制反转(IOC)、面向切面编程(AOP)
  • 支持事务处理,对框架整合的支持

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

1.4 Spring组成

image.png

1.5 Spring全家桶关系

  • SpringBoot(需要学习Spring、SpringMVC)

    • 一个快速开发脚手架
    • 基于SpringBoot可以快速开发单个微服务
  • SpringCloud(需要学习SpringBoot)

    • 基于SpringBoot实现
    • 协调各个微服务

二、IOC

2.1 理论推导

原来实现步骤:

  1. UserDao接口
  2. UserDaoImpl实现类
  3. UserService业务接口
  4. UserServiceImpl业务实现类

image.png

  • 问题:

UserDaoImplUserDao的实现类,如果现在又来了一个实现类UserDaoMysqlImpl,需要修改业务层实现类中组合的代码:new UserDaoMysqlImpl,如果又来一个UserDaoOracleImpl,那业务层又要改。

程序无法适应用户的变更!!

在之前的代码中,用户的需求可能会影响我们原来的代码,我们需要根据用户需求修改源代码,如果程序代码量十分大,代价会很昂贵。

引出控制反转(IOC)

  • 修改

image.png

我们在业务层实现层中不直接写明具体要new的类,而是设置一个set方法,在调用的时候,让用户来决定具体使用哪一个实现类,这样我们不用修改业务层的代码

image.png

我们使用一个set接口实现,已经发生了革命性的变化

  • 之前程序主动创建对象,控制权在程序员手上
  • 使用set注入后,程序不在具有主动性,而是被动的接收对象,控制权在用户手上
  • 这就是控制反转的思想,从本质上解决了问题,程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上。这就是IOC的原型

2.2 IOC本质

  • 控制反转(Inversion of Control, IoC)是一种设计思想
  • 依赖注入(DI)是实现IoC的一种方法

没有IoC的程序中,使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。控制反转后将对象的创建转移给第三方,所谓控制反转就是:获取依赖对象的方式反转了

image.png

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

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

image.png

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

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

2.3 第一个Spring程序

  • 实体类
 @Data
 public class User {
     String name;
 ​
     public User() {
         System.out.println("User的无参构造被调用!!");
     }
 ​
     @Override
     public String toString() {
         return "User{" +
                 "name='" + name + ''' +
                 '}';
     }
 }
  • 通过Beans.xml配置容器
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     https://www.springframework.org/schema/beans/spring-beans.xsd">
     
     <!--使用Spring来创建对象,在Spring中成为Bean
     类型 变量名 = new 类型();
     com.nick.User user = new com.nick.User();
     bean.id = new bean.class();
     -->
     <bean id="user" class="com.nick.pojo.Users">
         <property name="name" value="nickwang"/>
     </bean>
 </beans>
  • 实例化容器
 // 获取Spring的上下文对象,现在对象都在Spring中管理,使用时直接取
 ApplicationContext context = 
     new ClassPathXmlApplicationContext("beans.xml");
 User user = (User) context.getBean("user");
 System.out.println(user.toString());

用XML获取上下文对象的方法:ClassPathXmlApplicationContext

image.png

ApplicationContext是一个接口,下面有很多的实现类,对应不同的容器实例化方法,如图所示,ClassPathXmlApplicationContext是通过配置类实例化的实现类,而AnnotationConfigApplicationContext是通过注解实例化的实现类

image.png

解释:

  • User对象是谁创建的?(Spring创建)
  • User对象的属性是怎么设置的?(是由Spring容器设置的)

这个过程就是控制反转:

  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身创建的;使用Spring之后对象是由Spring创建的
  • 反转:程序本身不创建对象,而变成被动接收对象
  • 依赖注入:就是利用set方法来进行注入(实体类中必须要有set方法,才能被Spring托管)

到现在为止,我们彻底不用在程序中改动了,要实现不同的操作,只需要在xml中修改配置,所谓IoC,就是对象由Spring来创建、管理、装配

2.4 IOC创建对象的方式

  1. ==使用无参构造创建对象==(默认)
<bean id = "user" class = "com.nick.User">
	<property name="name" value="nickwang"/>
</bean>
  1. 使用有参构造创建对象(构造器注入)
  • 下标赋值
<bean id = "user" class = "com.nick.User">
	<constructor-arg index="0" value="wangwk-a"/>
</bean>
  • 类型(不建议使用,同类型不唯一不能使用)
<bean id = "user" class = "com.nick.User">
	<constructor-arg type="java.lang.String" value="wangwk-a"/>
</bean>
  • ==直接通过参数名注入==
<bean id = "user" class = "com.nick.User">
	<constructor-arg name="name" value="wangwk-a"/>
</bean>

==总结:在配置文件加载的时候,容器中管理的bean都会被实例化,即使不使用,也会调用其构造函数==

三、Spring配置

官方推荐的配置文件名称:applicationContext.xml

3.1 别名

<alias name="user" alias="userNickWang"/>
  • name:为已经被配置过的bean的id
  • alias:该bean的别名

3.2 Bean配置

  • bean
    • id:bean的唯一标识符
    • class:bean对象所对应的全限定名(类型)
    • name:别名(可以同时取多个别名,用逗号或者空格分隔,比<alias>更实用)
    • scope:设置bean的限定域
      • session
      • singleton:单例模式(默认)
      • request

3.3 导入

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

四、依赖注入DI

4.1 构造器注入

Constructor-based Dependency Injection

<bean id = "user" class = "com.nick.User">
	<constructor-arg name="name" value="wangwk-a"/>
</bean>

4.2 Setter注入*

Setter-based Dependency Injection

  • 依赖:bean对象的创建依赖于容器
  • 注入:bean对象中的所有属性由容器来注入
  1. 实体类
  • Student
@Data
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String, String> cards;
    private Set<String> games;
    private Properties info;
    private String wife;
}
  • Address
@Data
public class Address {
    private String address;
}
  1. 值注入
  • 普通值(使用value
<bean id="student" class="com.nick.pojo.Student">
    <property name="name" value="王文堃"/>
</bean>
  • 对象(使用ref引用一个bean
<bean id="address" class="com.nick.pojo.Address">
	<property name="address" value="陕西省西安市未央区凤城十二路"/>
</bean>
<bean id="student" class="com.nick.pojo.Student">
    <property name="name" value="王文堃"/>
    <property name="address" ref="address"/>
</bean>
  • 数组(使用非自闭合property并使用**<array>**)
<bean id="student" class="com.nick.pojo.Student">
    <property name="books">
        <array>
            <value>红楼梦</value>
            <value>西游记</value>
            <value>三国演义</value>
            <value>水浒传</value>
        </array>
    </property>
</bean>
  • 队列List(使用非自闭合property并使用**<list>**)

<array>相同

<bean id="student" class="com.nick.pojo.Student">
    <property name="hobbies">
        <list>
            <value>篮球</value>
            <value>代码</value>
            <value>电影</value>
        </list>
    </property>
</bean>
  • 映射Map(使用非自闭合property并使用**<map>**)

<array>不同之处是map要结合**<entry>**使用

<bean id="student" class="com.nick.pojo.Student">
    <property name="cards">
        <map>
            <entry key="身份证" value="610XXX19991122XXXX"/>
            <entry key="银行卡" value="6217XXXX2104XXXX340"/>
        </map>
    </property>
</bean>
  • 集合Set(直接使用<set>
<bean id="student" class="com.nick.pojo.Student">
    <property name="games">
        <set>
            <value>英雄联盟</value>
            <value>三国杀</value>
        </set>
    </property>
</bean>
  • Null(使用**<null/>**)
<bean id="student" class="com.nick.pojo.Student">
    <!--如果要注入空串则直接使用value
	<property name="wife" value=""/>
	-->
    <property name="wife">
        <null/>
    </property>
</bean>
  • Properties(使用<prop>

propmap不同之处是key写在标签内,value在标签外

<bean id="student" class="com.nick.pojo.Student">
    <property name="info">
        <props>
            <prop key="username">NickWang</prop>
            <prop key="password">Wang2994</prop>
        </props>
    </property>
</bean>

结果:

Student{
	name='王文堃', 
	address=Address(address=陕西省西安市未央区凤城十二路), 
	books=[红楼梦, 西游记, 三国演义, 水浒传], 
	hobbies=[篮球, 代码, 电影], 
	cards={身份证=610XXX19991122XXXX, 银行卡=6217XXXX2104XXXX340}, 
	games=[英雄联盟, 三国杀], 
	info={password=Wang2994, username=NickWang}, 
	wife='null'
}

4.3 拓展方式注入

  1. p命名空间注入(属性注入)
  • 命名空间:xmlns:p="http://www.springframework.org/schema/p"
  • 功能:可以直接注入**属性(property)**的值
  • 使用:p:可以自动识别bean中的属性
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="address" class="com.nick.pojo.Address" p:address="文艺路"/>
</beans>
  1. c命名空间注入(构造器注入)
  • 命名空间:xmlns:c="http://www.springframework.org/schema/c"
  • 功能:可以通过构造器注入值
  • 使用:c:可以通过构造器注入
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
	<!--需要在Address中添加有参构造器-->
    <bean id="address" class="com.nick.pojo.Address" c:address="文艺路"/>
</beans>

4.4 Bean的作用域

ScopeDescription
singleton(Default)单例,
prototype非单例,
requestWebMVC使用,作用域在一次请求中
sessionWebMVC使用,在session中
applicationWebMVC使用,application中
websocketWebMVC使用,websocket中
  1. 单例singleton

image.png

<bean id="address" class="com.nick.pojo.Address" scope="singleton">
    <property name="address" value="陕西省西安市未央区凤城十二路"/>
</bean>
  1. 原型prototype

image.png

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

五、自动装配Bean

5.1 什么是自动装配

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

  • 在xml中配置bean
  • 在java中显示配置(JavaConfig)
  • Java隐式自动装配*
    • ByName自动装配
    • ByType自动装配

5.2 使用自动装配

5.2.1 手动装配环境搭建

@Autowired

  1. 实体类
  • Dog
@Data
public class Dog {
    public void shout() {
        System.out.println("汪~~");
    }
}
  • Cat
@Data
public class Cat {
    public void shout() {
        System.out.println("喵~~");
    }
}
  • People
@Data
public class People {
    private Cat cat;
    private Dog dog;
    private String name;

    public People() {
    }
    public People(@Nullable String name) {
        this.name = name;
    }
}
  1. 配置文件
<?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-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <bean id="cat" class="com.nick.pojo.Cat"/>
    <bean id="dog" class="com.nick.pojo.Dog"/>
    <bean id="people" class="com.nick.pojo.People">
        <property name="name" value="wangwk-a"/>
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
    </bean>
</beans>
  1. 测试类
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    People people = context.getBean("people", People.class);
    people.getDog().shout();
    people.getCat().shout();
}

5.2.2 ByName自动装配

  • 自动在容器上下文中查找,和自己对象set方法后面的值对应的beanId
<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People" autowire="byName">
    <property name="name" value="wangwk-a"/>
</bean>

不用手动注入ref相关的两个对象,Spring会根据set方法后面的值去已经配置好的bean中找id相同的bean

==注意:People中Set方法后面的值,一定要与bean中id的名称相同才能够匹配==

image.png

如果修改Dog类bean的id,则byName自动装配失效

5.2.3 ByType自动转配

  • 自动在容器上下文中查找,和自己对象属性类型对应的bean
<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog11" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People" autowire="byType">
    <property name="name" value="wangwk-a"/>
</bean>

类型如果不唯一,则装配不成功

5.3 注解实现自动装配

  • JDK 1.5支持注解,Spring 2.5支持注解
  • 注解的方式比xml要更为灵活

==要是用注解,须知:==

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

配置之后,可以在属性set方法上使用==@Autowired==进行自动装配。使用@Autowired之后就可以不用编写set方法了,前提是这个自动装配的属性在IOC容器中存在,且符合名字ByName,或者ByType。

  • 配置文件
<?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-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <!--开启注解的支持-->
    <context:annotation-config/>

    <bean id="cat" class="com.nick.pojo.Cat"/>
    <bean id="dog" class="com.nick.pojo.Dog"/>
    <bean id="people" class="com.nick.pojo.People">
        <property name="name" value="wangwk-a"/>
    </bean>
</beans>
  • 实体类

image.png

Autowired注解参数:

  • @Autowired(required = false):表示该属性可以为Null

构造函数参数注解:

  • @Nullable:也表示该参数可以为Null

如果IOC中bean的id与实体类中属性名不一样,且类型不唯一,只使用Autowired是不行的,要搭配使用@Qualifier(value="beanName")后,就可以自动装配。例如:

<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog1" class="com.nick.pojo.Dog"/>
<bean id="dog2" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People">
    <property name="name" value="wangwk-a"/>
</bean>
@Data
public class People {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}

此时是不能自动专配的,因为Autowired会去IOC中找dog,然而没有找到,然后根据type找,不唯一。因此使用@Qualifier

@Data
public class People {
    @Autowired
    private Cat cat;
    @Autowired
    @Qualifier("dog1")
    private Dog dog;
    private String name;
}

==@Resource是java的注解,也可以实现自动装配==

  • @Resource(name="beanId"):可以指定bean的id

@Autowired@Autowired的区别:

  • 都是自动装配的,都可以放在属性字段上
  • ==@Autowired:先找type再找name==
  • ==@Resource:先找name再找type==

六、Spring注解开发

  • 在Spring4之后,要是用注解开发,必须要保证AOP的包已导入
  • 使用注解要导入context约束,增加注解支持

6.1 Bean实现

为使用注解开发时,需要在xml中配置bean,而使用注解开发后,则使用context中的扫描包

  • 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <!--    指定要扫描的包,该包下的注解就会生效-->
    <context:component-scan base-package="com.nick"/>
    <!--    开启注解的支持-->
    <context:annotation-config/>
</beans>
  • 实体类

要想让Spring能够自动扫描指定路径的下包,需要在实体类上添加注解@Component

默认bean的id就是实体类的小写名字

// 等价于在bean里面注册了一个bean
// <bean id="user" class="com.nick.pojo.User"/>
@Component
@Scope("singleton")
public class User {
    public String name = "王文堃";
}

6.2 属性如何注入

  • 通过set方法和注解可以对属性进行注入

对于简单的可以这样使用,但是对于复杂类型,还是使用配置文件比较方便

@Component
@Scope("singleton")
public class User {
    public String name;

    // 相当于<property name="name" value="王文堃"/>
    @Value("王文堃")
    public void setName(String name) {
        this.name = name;
    }
}

6.3 衍生注解

@Component有几个衍生注解,在web开发中会按照MVC结构分层:

  • dao:@Repository
  • service:@Service
  • controller:@Controller

上面四个注解是等价的,加上这个注解,含义就是告诉Spring这个类将会被IOC托管,实现自动装配。

6.4 作用域

@Component的下面使用@Scope注解可以对该类的作用域进行设置,其值有singletonprototype

6.5 使用JavaConfig替代配置文件

现在要完全不使用Spring的xml配置,全权交给java来做

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

  • 实体类
@Component
public class User {
    private String name;
    public String getName() {
        return name;
    }
    // 属性注入值
    @Value("王文堃")
    public void setName(String name) {
        this.name = name;
    }
}

之前要在xml中导入context约束,开启注解支持,开启包扫描,这里的实体类User才能够自动装配到IOC中

  • 配置类
  • 建包:com.nick.config

  • 创建配置类:JavaConfig

==所使用的注解:==

  • @Configuration:本身也是一个Component,因此本身也会被IOC托管
  • @ComponentScan("com.nick.pojo"):配置扫描包
  • @Bean:注册Bean,id是方法名,class是返回值
// 这个也会被spring容器托管,注册到容器中,因为它本身就是一个@Component
// @Configuration代表这是一个注册类,就和beans.xml一样
@Configuration
@ComponentScan("com.nick.pojo")
public class JavaConfig {
    // 注册一个bean,相当于我们之前写的bean标签
    // 这个方法的名字就是bean标签中的id属性
    // 方法的返回值相当bean标签的class属性
    @Bean
    public User getUser(){
        return new User(); // 就是返回要注入到bean的对象
    }
}
  • 使用配置类后调用方式也发生了改变
  • 之前使用xml是通过ClassPathXmlApplicationContext方法来返回ApplicationContext实例

  • 现在使用AnnotationConfigApplicationContext来返回实例

image.png

public class MyTest {
    public static void main(String[] args) {
        // 如果完全使用配置类来操作,就要是用AnnotationConfig上下文来获取容器
        // 通过配置类的class对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
        User user = context.getBean("getUser", User.class);
        System.out.println(user.getName());
    }
}

JavaConfig可以通过@Import注解将多个config类合并

@Configuration
@ComponentScan("com.nick.pojo")
@Import(JavaConfig2.class) // 将两个类引入成一个类
public class JavaConfig {
    // ......
}

==说明:现在出现的都是Spring的注解,实际上学习SpringBoot的时候,会出现很多SpringBoot特有的注解,但是点进去底层都是通过spring的注解实现的==

七、AOP

AOP的底层实现是动态代理,动态代理的基础是静态代理,学习这里应该首先要对静态代理和动态代理有一定的了解

7.1 什么是AOP

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

image.png

7.2 AOP在spring中的作用

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

一些名词:

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

image.png

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

image.png

7.3 AOP的使用

7.3.1 环境准备

  1. 使用AOP需要导入织入包
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
  1. 原始业务
  • 接口
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
  • 实现类
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 select() {
        System.out.println("查看");
    }
}

7.3.2 方式一:使用Spring接口实现(切入点)

创建包com.nick.log,并创建Log

  • 在业务方法前调用:==org.springframework.aop.MethodBeforeAdvice==
public class Log implements MethodBeforeAdvice {
    /**
     *
     * @param method 要执行的目标对象的方法
     * @param args 参数
     * @param target 目标对象
     * @throws Throwable
     */
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
    }
}
  • 在业务方法后调用:==org.springframework.aop.AfterReturningAdvice==
public class AfterLog implements AfterReturningAdvice {
    /**
     * @param returnValue 返回值
     * @param method 要执行的目标对象的方法
     * @param args 参数
     * @param target 目标对象
     * @throws Throwable
     */
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果为" + returnValue);
    }
}
  • 配置文件引AOP
  • 导入AOP:xmlns:aop="http://www.springframework.org/schema/aop"
  • 添加对应约束:http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--    注册bean-->
    <bean id="userservice" class="com.nick.service.UserServiceImpl"/>
    <bean id="log" class="com.nick.log.Log"/>
    <bean id="afterlog" class="com.nick.log.AfterLog"/>

	<!--方式一:使用原生Spring API接口-->
    <!--配置AOP:需要导入AOP约束-->
    <aop:config>
        <!-- 切入点:expression表达式,execution要执行的位置-->
        <aop:pointcut id="pointcut" expression="execution(* com.nick.service.UserServiceImpl.*(..))"/>
        <!-- 执行环绕增加-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

==重点:==

  1. 切入点:就是告诉Spring需要在哪里执行AOP
  • 标签:<aop:pointcut>
  • 值:
    • id:切入点的名字
    • expression:切入点表达式,就是表示要执行切入的具体位置
      • execution():要执行的位置
      • *:第一个*表示任意的返回值
      • com.nick.service.UserServiceImpl表示具体包下的具体类
      • .*:表示该类下的所有方法
      • (..):表示方法中任意的参数
  1. 执行环绕增强
  • 标签:<aop:advisor>
  • 值:
    • advice-ref:指明要执行什么
    • pointcut-ref:指明什么时候执行

测试:

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

结果:

com.nick.service.UserServiceImpl的select被执行了
查看
执行了select方法,返回结果为null

Process finished with exit code 0

7.3.3 方式二:使用自定义类实现AOP(切面)

  • 建包:com.nick.diy

  • 创建类:DiyPointCut

public class DiyPointCut {
    public void before() {
        System.out.println("=======方法执行前=======");
    }
    public void after() {
        System.out.println("=======方法执行后=======");
    }
}
  1. 修改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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--    注册bean-->
    <bean id="userservice" class="com.nick.service.UserServiceImpl"/>
    
	<!--方式二:自定义类-->
    <bean id="diy" class="com.nick.diy.DiyPointCut"/>
    <aop:config>
        <!-- 自定义切面,ref要引用的类-->
        <aop:aspect ref="diy">
            <!-- 切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.nick.service.UserServiceImpl.*(..))"/>
            <!-- 通知-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

==重点:==

  • ==第一种实现方式,分别实现了Spring定义的连接点,来实现在业务方法前后调用方法,二者分别是两个类,他们是两个分开的点,因此在配置的时候,使用切入点配置==
  • ==第二种方式,是实现了一个类DiyPointCut,该类中实现了业务前后的方法,这是一个切面(Aspect)【横切关注点被模块化的特殊对象,类】,因此在配置的使用使用切面配置,并使用ref来指明切面所对应的类==
  • ==切面需要被告知应该在什么时候做什么,因此切面配置<aop:aspect>中需要说明:==
    • ==<aop:pointcut>:什么时候==
    • ==<aop:before>:通知【切面要完成的工作,即做什么】==

7.3.4 方式三:使用注解实现AOP

实际上就是上述方法注解来实现

  1. 编写切面类
  • @Aspect:标记该类是是一个切面
  • @Before@After:前置增强、后置增强,相当于<aop:before>,注意包要导入org.aspectj.lang.annotation下的包
  • @Around:环绕增强,可以增加一个==连接点==参数【与切入点匹配的执行点】,通过连接点的proceed方法来执行方法
@Aspect
public class AnnotationPointCut {
    @Before("execution(* com.nick.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("=========方法执行前=========");
    }

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

    // 在环绕增项中,可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.nick.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");

        // 打印信息
        System.out.println("signature:" + jp.getSignature());

        // 执行方法
        Object proceed = jp.proceed();

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

    }
}
  1. 编写xml配置文件
  • 开启注解支持: <aop:aspectj-autoproxy>
  • proxy-target-class的值:
    • false:默认,使用JDK
    • true:使用cglib
<?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-3.0.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--    注册bean-->
    <bean id="userservice" class="com.nick.service.UserServiceImpl"/>
    <bean id="annotationPointCut" class="com.nick.diy.AnnotationPointCut"/>

<!--方式三-->
<!--开启注解支持 JDK(默认 proxy-target-class="false") cglib(proxy-target-class="true")-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>

</beans>
  1. 结果
  • 环绕是在前置后置的外面
  • getSignature是获取业务逻辑中被执行的方法
环绕前
signature:void com.nick.service.UserService.select()
=========方法执行前=========
查看
=========方法执行后=========
环绕后

7.4名词总结说明

  • 方式一

image.png

  • 方式二

image.png

  • 方式三

image.png

八、整合Mybatis

参考文档:Mybatis-Spring

8.1 快速入门

要使用 MyBatis-Spring 模块,只需要在类路径下包含 mybatis-spring-2.0.6.jar 文件和相关依赖即可。

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

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

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

  • xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
  • 配置类
@Configuration
public class MyBatisConfig {
  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
  }
}

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

假设你定义了一个如下的 mapper 接口:

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
}

那么可以通过 MapperFactoryBean 将接口加入到 Spring 中:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。

配置好之后,你就可以像 Spring 中普通的 bean 注入方法那样,将映射器注入到你的业务或服务对象中。MapperFactoryBean 将会负责 SqlSession 的创建和关闭。 如果使用了 Spring 的事务功能,那么当事务完成时,session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException 异常。

使用 Java 代码来配置的方式如下:

@Configuration
public class MyBatisConfig {
  @Bean
  public UserMapper userMapper() throws Exception {
    SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
    return sqlSessionTemplate.getMapper(UserMapper.class);
  }
}

要调用 MyBatis 的数据方法,只需一行代码:

public class FooServiceImpl implements FooService {

  private final UserMapper userMapper;

  public FooServiceImpl(UserMapper userMapper) {
    this.userMapper = userMapper;
  }

  public User doSomeBusinessStuff(String userId) {
    return this.userMapper.getUser(userId);
  }
}

8.2 具体示例

  1. mybatis-config.xml

sqlSessionFactory和数据源的配置放到Spring的配置文件中做,这样在使用的时候就不用手动new对象,而在mybatis-config中保留别名

image.png

  1. spring-dao.xml
  • 数据源
  • sqlSessionFactory
  • sqlSessionTemplate
  • 给接口增加实现类

image.png

  1. application-context.xml

image.png

  1. UserMapperImpl

application-context.xml中需要配置UserMapperImpl,将sqlSessionTemplate通过set方法注入到sqlSession中,因此在我们不必收到通过配置文件new一个SqlSessionFactory然后获取sqlSession了。

image.png

  1. 具体调用
  • 之前

整合Spring之前,调用时需要通过配置文件来创建sqlSessionFactory,然后获取sqlSession

image.png

  • 现在调用

image.png

九、声明式事务

9.1 回顾事务

  • 要么都成功,要么都失败
  • 事务在项目中十分重要,涉及到数据的一致性问题

事务的ACID原则:

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

9.2 spring事务

Spring中事务分为:

  • 声明式事务:AOP
  • 编程式事务:需要代码中进行事务管理

9.2.1 环境准备

  • 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
    private int id;
    private String name;
    private String password;
    private String email;
    private Date birthday;
}
  • 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--dataSource:使用Spring提供的jdbc数据源来替换Mybatis的配置 其他的数据源:c3p0 dbcp druid-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/jdbc?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="wang2995"/>
    </bean>

    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--绑定Mapper-->
        <property name="mapperLocations" value="classpath:com/nick/mapper/*.xml"/>
    </bean>

    <!--SqlSessionTemplate:就是我们使用的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--SqlSessionTemplate类没有set方法,只能使用构造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>
  • 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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <import resource="spring-dao.xml"/>
    <!--注入UserMapper的实现类-->
    <bean id="userMapper" class="com.nick.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
</beans>
  • UserMapper接口
public interface UserMapper {
    List<Users> queryAllUsers();

    int insertUser(Users user);
}
  • UserMapper.xml
<?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.nick.mapper.UserMapper">
    <select id="queryAllUsers" resultType="com.nick.pojo.Users">
        SELECT * FROM users
    </select>
    <insert id="insertUser" parameterType="com.nick.pojo.Users">
        insert into users
        values(#{user.id}, #{user.name}, #{user.password}, #{user.email}, #{user.birthday})
    </insert>
</mapper>
  • UserMapperImpl
public class UserMapperImpl implements UserMapper {
    /**
     * 之前使用sqlSession执行,现在使用sqlSessionTemplate
     */
    private SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSession = sqlSessionTemplate;
    }

    @Override
    public List<Users> queryAllUsers() {
        return sqlSession.getMapper(UserMapper.class).queryAllUsers();
    }

    @Override
    public int insertUser(Users user) {
        return sqlSession.getMapper(UserMapper.class).insertUser(user);
    }
}
  • 测试
public class MybatisTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        List<Users> users = userMapper.queryAllUsers();
        users.stream().forEach(item-> System.out.println(item.toString()));
    }

    @Test
    public void insertTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        Users user = new Users(2, "王文堃", "wang2995", "928458710@qq.com", new Date());
        userMapper.insertUser(user);
    }
}

报错:org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'user' in 'class com.nick.pojo.Users'

是因为在UserMapper接口中没有添加@Param

  • 示例

image.png

  • 结果

image.png

期望是delete出错后,数据回滚,不插入id为5的数据

9.2.2 声明式事务

  1. 要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>
@Configuration
public class DataSourceConfig {
  @Bean
  public DataSourceTransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource());
  }
}

传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource

注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。

  1. ==结合AOP实现事务的织入==

spring-dao.xml中导入事务tx约束

  • 配置事务通知

image.png

spring中其中Propagation类的事物传播特性:

image.png

9.2.3 编程式事物

MyBatis 的 SqlSession 提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession 或映射器。也就是说,Spring 总是为你处理了事务。

你不能在 Spring 管理的 SqlSession 上调用 SqlSession.commit()SqlSession.rollback()SqlSession.close() 方法。如果这样做了,就会抛出 UnsupportedOperationException 异常。在使用注入的映射器时,这些方法也不会暴露出来。

无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。

下面的代码展示了如何使用 PlatformTransactionManager 手工管理事务。

public class UserService {
  private final PlatformTransactionManager transactionManager;
  public UserService(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }
  public void createUser() {
    TransactionStatus txStatus =
        transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
      userMapper.insertUser(user);
    } catch (Exception e) {
      transactionManager.rollback(txStatus);
      throw e;
    }
    transactionManager.commit(txStatus);
  }
}

在使用 TransactionTemplate 的时候,可以省略对 commitrollback 方法的调用。

public class UserService {
  private final PlatformTransactionManager transactionManager;
  public UserService(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }
  public void createUser() {
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.execute(txStatus -> {
      userMapper.insertUser(user);
      return null;
    });
  }
}