Spring

Spring

1、概述

1.1 简介

为了简化开发,整合了现有的框架技术,是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器(框架)。有了它之后不需要再new对象了,所有的对象都可以托管到spring中,需要用的时候通过spring来取即可

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

1.2 优点

  • 开源免费的框架,容器
  • 轻量级,非侵入式(项目中引入它不会影响到原本的代码运行)
  • 控制反转(IOC):依赖注入(DI)
  • 面向切面编程(AOP)
  • 支持事务,整合了其他框架的支持

1.3 七大模块

2、控制反转(IOC)

2.1 概念

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做**依赖注入(Dependency Injection,简称DI**),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中

2.2 实践

  1. 创建新项目,然后按照传统步骤编写dao层和Service层

image-20220323195140650

  • Dao接口

    public interface UserDao {
        public void getUser();
    }
    
  • Dao实现

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

    public interface UserService {
        public void getUser();
    }
    
  • Service实现

    public class UserServiceImpl implements UserService{
    
        private UserDao userDao = new UserDaoImpl();
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    
  • Test

    public class IocTest {
        @Test
        public void test(){
            UserService userService = new UserServiceImpl();
            userService.getUser();
        }
    }
    

    image-20220323195842119

  1. 如果UserDao有两个实现类,而用户想要换一种UserDao的实现方式,按照传统思路,此时就需要修改UserService的实现类了
  • UserDaoMysqlImpl

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

    public class UserServiceImpl implements UserService{
    
        private UserDao userDao = new UserDaoMysqlImpl();
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    

    image-20220323200625191

    如上所述,客户需求一旦发生变化,服务端的代码就需要进行相应的修改,假设这种需求非常大,这种方式就根本不适用了,因为它的耦合性太高了,牵一发而动全身,因此就需要引入控制反转的概念,将控制权移交到客户手上。

  1. 改造代码

    根据上面的代码不难发现,修改前后的UserServiceImpl其实变化并不大,只是参数userDao的实现方式发生了变化而已,那么我们可以考虑在需要用到userDao的地方,不去实现它,而是留出一个接口,利用set方法,让调用UserService的人来决定他到底要使用哪种userDao的实现方式。

    public class UserServiceImpl implements UserService{
    
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    
    public class IocTest {
        @Test
        public void test(){
            UserServiceImpl userService = new UserServiceImpl();
            userService.setUserDao(new UserDaoImpl());
            userService.getUser();
            userService.setUserDao(new UserDaoMysqlImpl());
            userService.getUser();
        }
    }
    
    

    image-20220323201839995

  2. 改变的意义

    改进后的代码可能目前看起来也没比之前的方式简单多少,但是实际上已经发生了根本性的变化,以前所有的东西都是由程序自己去控制创建(我要用什么,我就自己创建什么),而现在是由调用者自行控制创建对象,把主动权交给调用者,程序不用去管怎么创建,它只负责提供一个接口。

    这种思想,从本质上解决了问题,我们程序员不再去管理对象的创建了,更多的去关注对象的实现,耦合性大大降低,这就是IOC的原型。

    流程变化:

    原本:调用者告诉开发者我要用这个账号登录,开发者根据调用者需求修改程序代码,把原来写进程序里的账号改成新的。

    现在:开发者告诉调用者,我给你弄了个登录界面,你要用哪个账号自己把账号输进去就好了,不要再来找我了。

2.3 本质

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

Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。

采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。

image-20220323203307573

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解, 新版本的Spring也可以零配置实现IoC。 Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用 时再从Ioc容器中取出需要的对象。

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

3、HelloSpring

  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配置文件

    <?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="hello" class="Hello">
            <property name="name" value="Spring"/>
        </bean>
    </beans>
    
  3. 测试

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

  4. 思考

    • Hello时谁创建的

      Hello由Spring创建

    • Hello中的name属性是怎么设置的

      由Spring容器创建Hello对象时设置的

由此可见,Spring容器就是控制反转中的第三方,当对象在Spring中注册了bean时,该对象就相当于托管给了Spring,对象由Spring创建,程序本身不创建对象,而变成被动的接收对象,而所谓的依赖注入本质上就是利用set方法来注入的。

4、IOC案例修改

由HelloSpring我们初步了解到,Spring可以作为第三方帮助我们new对象,那么我们就可以对控制反转中的案例进行如下改造:

  1. 新增Spring的配置文件,将UserDao的两种实现类和Service的实现类在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="userDaoImpl" class="com.yirui.dao.UserDaoImpl"/>
        <bean id="userDaoMysqlImpl" class="com.yirui.dao.UserDaoMysqlImpl"/>
        <bean id="serviceImpl" class="com.yirui.Service.UserServiceImpl">
            <property name="userDao" ref="userDaoImpl"/>
        </bean>
    </beans>
    
  2. 修改测试代码

    public class IocTest {
        @Test
        public void test(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            UserServiceImpl userService = context.getBean("serviceImpl",UserServiceImpl.class);
            userService.getUser();
        }
    }
    

image-20220323211625239

经过如上改造我们就彻底不需要对程序做出改动了,要实现不同的操作只需要在xml配置文件中进行相应的修改即可,IOC到这里就可简单理解为:对象都有Spring来创建,管理,装配。

5、Spring创建对象方式

5.1 无参构造

当实体类有无参构造方式时,Spring可以直接通过bean来创建,在创建对象的同时也可以通过标签给它的属性赋值,前提是有该属性的set方法,因为依赖注入的本质就是通过set来进行注入的。

package com.yirui.dao;

public class User {
    private String name;
    public User(){
        System.out.println("User的无参构造");
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public void show(){
        System.out.println("name:"+name);
    }
}
<bean id="user" class="com.yirui.dao.User">
    <property name="name" value="yirui"/>
</bean>

5.2 有参构造

package com.yirui.dao;

public class UserT {
    private String name;
    public UserT(String name){
        System.out.println("User的有参构造,name:"+name);
    }

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

    public void show(){
        System.out.println("name:"+name);
    }
}
  • 通过index注入构造参数

    <bean id="userT" class="com.yirui.dao.UserT">
        <constructor-arg index="0" value="yiruiT"/>
    </bean>
    
  • 通过name注入构造参数

    <bean id="userT" class="com.yirui.dao.UserT">
        <constructor-arg name="name" value="yirui"/>
    </bean>
    
  • 通过type注入构造参数(不推荐,因为只有当每个参数的类型都不一致的时候才能使用,而且必须指定全限定类名)

    <bean id="userT" class="com.yirui.dao.UserT">
        <constructor-arg type="java.lang.String" value="yirui"/>
    </bean>
    

5.3 测试

public class DiTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = context.getBean("user",User.class);
        user.show();
        UserT userT = context.getBean("userT",UserT.class);
        userT.show();
    }
}

image-20220323213838823

观察结果,不难发现当ClassPathXmlApplicationContext被创建时,在Spring中注册的bean会被一次性全部实例化

6、Spring配置和依赖注入(DI)

6.1 配置介绍

  1. 别名

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

    <!--设置别名:在获取Bean的时候可以使用别名获取-->
    <alias name="userT" alias="userNew"/>
    
  2. bean标签

    <!--bean就是java对象,由Spring创建和管理-->
    <!--
    id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
    如果配置id,又配置了name,那么name是别名
    name可以设置多个别名,可以用逗号,分号,空格隔开
    如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
    class是bean的全限定名=包名+类名
    -->
    <bean id="hello" name="hello2 h2,h3;h4" class="com.yirui.pojo.Hello">
    	<property name="name" value="Spring"/>
    </bean>
    
    
  3. import

    团队的合作通过import来实现,可以将多个spring配置文件整合到一起

    <import resource="{path}/beans.xml"/>
    

6.2 依赖注入(DI)

  • 依赖:一指bean对象的创建依赖于spring容器,二指bean对象创建时的依赖资源
  • 注入:指bean对象创建时的依赖资源,由spring容器来设置和装配

构造器注入和简单的常量注入已经在前面阐述了,这里主要描述set注入的一些复杂情况

  1. 新建Address和Student对象
public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
@Data
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobby;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                "\n address=" + address +
                "\n books=" + Arrays.toString(books) +
                "\n hobby=" + hobby +
                "\n card=" + card +
                "\n games=" + games +
                "\n wife='" + wife + '\'' +
                "\n info=" + info +
                '}';
    }
}
  1. 在beans.xml文件完成address和student的属性注入
<bean id="address" class="com.yirui.pojo.Address"/>
<bean id="student" class="com.yirui.pojo.Student">
    <!--单值注入-->
    <property name="name" value="yirui"/>
    <!--bean注入,ref指向引用的bean的id-->
    <property name="address" ref="address"/>
    <!--数组注入-->
    <property name="books">
        <array>
            <value>西游记</value>
            <value>红楼梦</value>
            <value>水浒传</value>
            <value>三国演义</value>
        </array>
    </property>
    <!--List注入-->
    <property name="hobby">
        <list>
            <value>打游戏</value>
            <value>听音乐</value>
            <value>看电影</value>
        </list>
    </property>
    <!--map注入-->
    <property name="card">
        <map>
            <entry key="身份证" value="123456789123456789"/>
            <entry key="学号" value="123456789"/>
        </map>
    </property>
    <!--set注入-->
    <property name="games">
        <set>
            <value>LOL</value>
            <value>BOB</value>
            <value>COC</value>
        </set>
    </property>
    <!--Null注入-->
    <property name="wife"><null/></property>
    <!--properties注入-->
    <property name="info">
        <props>
            <prop key="电话">13400000000</prop>
            <prop key="性别"></prop>
            <prop key="年龄">22</prop>
        </props>
    </property>
</bean>
  1. 结果测试
public class DiTest02 {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println(student.toString());
    }
}

image-20220324095335052

6.3 拓展注入

  1. P命名空间注入(相当于)

    头文件中加入约束文件:xmlns:p=“http://www.springframework.org/schema/p”

  2. C命名空间注入(相当于)

    头文件中加入约束文件:xmlns:c=“http://www.springframework.org/schema/c”

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--<bean id="user" class="com.yirui.dao.User">-->
    <!--    <property name="name" value="yirui"/>-->
    <!--</bean>-->

    <bean id="user" class="com.yirui.dao.User" p:name="yirui"/>
    <!--<bean id="userT" class="com.yirui.dao.UserT">-->
    <!--    <constructor-arg index="0" value="yiruiT"/>-->
    <!--</bean>-->
    <bean id="userT" class="com.yirui.dao.UserT" c:name="yirui"/>
</beans>

6.4 bean作用域

img

  1. singleton

    当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对 bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象 都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成 singleton,可以这样配置:

    <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
    
  2. prototype

    当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会 导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法) 时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是 当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在 XML中将bean定义成prototype,可以这样配置:

    <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
    <!--或者-->
    <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
    
  3. request

    当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP 请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

    <bean id="loginAction" class="cn.csdn.LoginAction" scope="request"/>
    

    针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实 例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例 的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的 状态变化。当处理请求结束,request作用域的bean实例将被销毁。

  4. session

    当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域 仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
    

    针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的 userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作 用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据 userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session 最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

7、bean的自动装配

除去上述在xml中对bean进行显式配置的方式外,还有另外两种方式可以对bean进行装配:

  • 在java中进行显式配置
  • 隐式的bean发现机制和自动装配
    • 组件扫描:自动发现应用上下文中所创建的bean
    • 自动装配:自动满足bean之间的依赖,寻找符合条件的bean自动装入

使用自动装配可以大幅度减少xml配置,此外自动装配也有两种方式:

  • 注解(推荐)
  • xml中配置(不推荐)

7.1测试环境搭建

  1. 创建实体类

    public class Cat {
        public void shout(){
            System.out.println("猫叫了一声");
        }
    }
    
    public class Dog {
        public void shout(){
            System.out.println("狗叫了一声");
        }
    }
    
  2. 创建用户类

    @Data
    public class User {
        private Cat cat;
        private Dog dog;
        private String str;
    }
    
  3. 编写spring配置文件

    <bean id="dog" class="com.yirui.pojo.Dog"/>
    <bean id="cat" class="com.yirui.pojo.Cat"/>
    
    <bean id="user" class="com.yirui.pojo.User">
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
        <property name="str" value="yirui"/>
    </bean>
    
  4. 测试

    public class AutoWiredTest {
        @Test
        public void test(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            User user = context.getBean("user", User.class);
            user.getCat().shout();
            user.getDog().shout();
            System.out.println(user.getStr());
        }
    }
    

    image-20220324105622895

7.2 byName自动装配

按名字自动装配,spring会按照需要配置的属性名自动搜索上下文中的bean id

  1. 在初始环境上修改user bean,查看测试结果

    <bean id="user" class="com.yirui.pojo.User" autowire="byName">
        <property name="str" value="yirui"/>
    </bean>
    

    image-20220324110234750

  2. 修改cat bean的id,查看测试结果

    <bean id="cat1" class="com.yirui.pojo.Cat"/>
    

    image-20220324110321072

  3. user中添加setCat1方法,查看测试结果

    @Data
    public class User {
        private Cat cat;
        private Dog dog;
        private String str;
    
        public void setCat1(Cat cat) {
            this.cat = cat;
        }
    }
    

    image-20220324110416349

由上诉测试结果不难推测出byname自动装配的运行过程:

  1. 查找自动装配对象中的所有的set方法名,例如setCat,setDog,setCat1,获得将set去掉并且首字母小写的字符串cat,dog,cat1。
  2. 在spring容器中寻找是否有id=这些字符串的对象。
  3. 如果有,就自动注入,没有就不注入。

7.3 byType自动装配

在spring中按照类型自动匹配装配对象,使用bytype自动装配必须保证同一类型的对象在spring容器中只存在一个,要不然就会报不唯一异常。

  1. 在初始环境上修改bean配置

    <bean id="dog" class="com.yirui.pojo.Dog"/>
    <bean id="cat1" class="com.yirui.pojo.Cat"/>
    <bean id="user" class="com.yirui.pojo.User" autowire="byType">
        <property name="str" value="yirui"/>
    </bean>
    
  2. 测试,正常输出,说明此时id不匹配已经不影响装配,因为现在式按照类型自动匹配的

  3. 增加一个bean,再测试

    <bean id="cat2" class="com.yirui.pojo.Cat"/>
    

    image-20220324111956000

  4. 由上结果可知,使用bytype自动装配时必须保持每个对象的类型唯一

7.4 使用注解自动装配

  1. 引入配置

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context 
           http://www.springframework.org/schema/context/spring-context.xsd
    ">
    
  2. 开启注解支持

    • 开启支持

      <context:annotation-config/>
      
    • 指定注解扫描包(只有指定包下的注解才能生效)

      <context:component-scan base-package="com.yirui.pojo"/>
      
  3. @Autowired

    • 按类型自动装配,不支持id匹配
    • 需要导入spring-aop包
    • @Autowired(required = false):对象可以为null,required默认为false,当将其指定为true时,则spring中必须要存在能够匹配的对象,否则会报错。

    基础环境上修改代码:

    • User类

      @Data
      public class User {
          @Autowired
          private Cat cat;
          @Autowired
          private Dog dog;
          private String str;
      }
      
    • beans.xml

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

    测试结果:

    image-20220324113827503

  4. @Qualifier

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

    上述代码基础上修改:

    1. 修改beans.xml并测试

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

      image-20220324114152641

    2. 加上@Qualifier并测试

      @Data
      public class User {
          @Autowired
          @Qualifier(value = "cat2")
          private Cat cat;
          @Autowired
          @Qualifier(value = "dog2")
          private Dog dog;
          private String str;
      }
      

      image-20220324114317042

  5. @Resource

    • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
    • 其次再进行默认的byName方式进行装配;
    • 如果以上都不成功,则按byType的方式自动装配。
    • 都不成功,则报异常。
    @Data
    public class User {
        @Resource(name = "cat1")
        private Cat cat;
        @Resource(name = "dog2")
        private Dog dog;
        private String str;
    }
    

7.5 小结

@Autowired与@Resource异同:

  1. @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
  2. @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果 要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我 们想使用名称装配可以结合@Qualifier注解进行使用
  3. @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

它们的作用相同:都是用注解方式注入对象

但执行顺序不同:@Autowired先byType,@Resource先 byName。

8、注解开发

在spring4之后,想要使用注解形式,需要先做以下三件事情:

  • 引入aop的包(这里直接在maven中导入了spring-webmvc依赖)

    image-20220324115855873

  • 在配置文件当中,要引入context约束

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context 
           http://www.springframework.org/schema/context/spring-context.xsd
    ">
    
  • 开启注解支持和指定注解扫描包

        <context:component-scan base-package="com.yirui.pojo"/>
        <context:annotation-config/>
    

8.1 bean的注解实现

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

@Data
@Component("user")
public class User {
    @Resource(name = "cat1")
    private Cat cat;
    @Resource(name = "dog2")
    private Dog dog;
    private String str;
}

比如对7中的User实体类加上如上注解,就可以删除掉beans.xml中的:

<bean id="user" class="com.yirui.pojo.User"/>

8.2 简单属性注入

使用@Value注解可以注入简单属性,复杂属性如列表,数组等还是推荐使用传统xml配置方式注入

@Data
@Component("user")
public class User {
    @Resource(name = "cat1")
    private Cat cat;
    @Resource(name = "dog2")
    private Dog dog;
    @Value("yirui")
    private String str;
}

image-20220324121121232

注意:

  • 可以不用提供set方法,直接在直接名上添加@value(“值”)
  • 如果提供了set方法,也可以在set方法上添加@value(“值”);

8.3 衍生注解

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

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

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层写上这些注解,就相当于将这个类交给Spring管理装配了!

8.4 作用域注解

使用@Scope可以指定作用域:

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Data
@Component("user")
@Scope("prototype")
public class User {
    @Resource(name = "cat1")
    private Cat cat;
    @Resource(name = "dog2")
    private Dog dog;
    @Value("yirui")
    private String str;
}

8.5 小结

XML与注解比较:

  • XML:可以适用任何场景 ,结构清晰,维护方便
  • 注解:不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解

作用:

  • 进行注解驱动注册,从而使注解生效

  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册

  • 如果不扫描包,就需要手动配置bean

  • 如果不加注解驱动,则注入的值为null!

9、基于java类配置

在7中提到,bean装配除了xml配置外,还有自动装配和java配置两种方式,到目前自动装配和注解介绍完了,开始介绍基于java类来配置:JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的 版本, JavaConfig 已正式成为 Spring4 的核心功能 。

  1. 新建实体类

    @Component//将这个类注册为Spring的一个组件,放到容器里
    public class Dog {
        @Value("dog")
        private String name;
    }
    
  2. 新建配置类

    @Configuration //代表这是一个配置类
    public class MyConfig {
        @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
        public Dog dog(){
            return new Dog();
        }
    }
    
  3. 测试

    public class JavaConfigTest {
        @Test
        public void test(){
            ApplicationContext applicationContext =
                    new AnnotationConfigApplicationContext(MyConfig.class);
            Dog dog = (Dog) applicationContext.getBean("dog");
            System.out.println(dog.getName());
        }
    }
    

image-20220324125633103

注意:使用java类配置时,获取spring的方式也要发生相应的变化

10、代理模式

10.1 简介

代理模式分为静态代理动态代理,所谓代理,顾名思义就是代替别人做一些事情,专业的事情交给专业的人来做。

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

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

例子:房东要出租房子,但他平时有很多其他的事要忙,比如:吃饭,上班,睡觉。因此他委托房产中介帮他出租,要租房子的人直接联系房产中介,房产中介就专门负责带人去看房,签合同。

在这个例子中,房东就是真实角色,要访问他的开销很大,因此他把出租房子这件事情抽取出来(抽象角色),房东和中介(代理角色)共同实现这件事情(抽象角色的接口),与此同时,中介负责一些额外的事情(带客户看房子,签合同)

10.2 静态代理

  1. 创建租房接口(抽象主题角色),房东和中介都要实现这个接口

    public interface Rent {
        public void rent();
    }
    
  2. 创建房东类(真实主题角色)

    public class Host implements Rent{
        @Override
        public void rent() {
            System.out.println("房东出租房子");
        }
    }
    
  3. 创建房产中介类(代理主题角色)

    public class Proxy implements Rent{
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent() {
            seeHouse();
            host.rent();
            fare();
        }
    
        public void seeHouse() {
            System.out.println("中介带客户看房");
        }
    
        public void fare(){
            System.out.println("收中介费");
        }
    }
    
  4. 创建客户类

    public class Client {
        public static void main(String[] args) {
            //房东要出租房子
            Host host = new Host();
            //中介帮助房东
            Proxy proxy = new Proxy(host);
            //你去找中介
            proxy.rent();
        }
    }
    

    image-20220324155648853

  • 优点:
    • 真实角色专注于自己的事情,其他的事情交给专业的人(代理)去做
    • 公共业务交给代理完成,实现了业务的分工
    • 公共业务拓展时更加集中,方便管理
  • 缺点:
    • 真实角色变多了,代理也会变多,工作量变大,降低开发效率

因此有了动态代理

10.3 动态代理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javassist

实现JDK动态代理的一个核心接口和一个核心类:InvocationHandlerProxy

  1. InvocationHandler(调用处理程序接口)

    InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

    每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:

    /**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
    
  2. Proxy(代理)

    Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

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

    这个方法的作用就是创建一个代理类对象,它接收三个参数:

    • loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
    • interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
    • h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

代码实现:

  1. 抽象接口
public interface Rent {
    public void rent();
    public void buy();
}
  1. 真实对象
public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }

    @Override
    public void buy() {
        System.out.println("房东买房子");
    }
}
  1. 动态代理
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);
    }

    /**
     * 
     * @param proxy 代理类
     * @param method 代理类的调用处理程序的方法对象
     * @param args 代理类调用处理程序方法所需要的参数
     * @return 处理代理实例上的方法并返回结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        Object result = method.invoke(rent,args);
        fare();
        return result;
    }
    
    public void seeHouse(){
        System.out.println("带客户看房");
    }
    
    public void fare(){
        System.out.println("收中介费");
    }
}
  1. 客户
public class Client {
    public static void main(String[] args) {
        //真实对象
        Host host = new Host();
        //代理实例的调用处理程序,用来设置具体要代理的人,代理的事情,动态生成对应代理对象
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        proxyInvocationHandler.setRent(host);
        Rent proxy = (Rent) proxyInvocationHandler.getProxy();
        //
        proxy.rent();
        proxy.buy();
    }
}
  1. 结果

image-20220324173759460

结果分析:由上述结果可以看出,当调用代理角色来做租房和买房操作时,都会转发到invoke方法中,按照invoke方法中的顺序来执行。

总结:动态代理的调用处理程序主要做一件事-----生成动态代理对象实例(getProxy),但是在生成动态代理对象的时候要考虑两个问题:

  1. 你要代理谁(setRent)
  2. 你代理他之后要帮他做哪些额外的操作(invoke)

好处:

  • 一个动态代理类可以代理一类业务(不管有多少个房东,出租房子只需要一个中介就可以了)
  • 一个动态代理可以代理多个类,代理的是接口(假设代理的是结婚这件事,同一个婚庆公司即可以代理医生,也可以代理老师)

11、面向切面编程(AOP)

为什么在之前要花那么大篇幅介绍代理模式,因为AOP实现的原理就是代理,所谓面向切面编程,就是在不改变原来代码的情况下,实现对原有功能的增强。

11.1 什么是AOP

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

image-20220324175947532

如上图,原本的顺序是:验证—>业务逻辑–>下一层工作,而我们想要在不改变业务逻辑代码的情况下,在它的前后加上日志功能,这就相当于把业务逻辑这一层单独抠出来,作为一个切面,使用代理的方式对它进行包裹,这就是所谓的面向切面编程。

11.2 AOP在Spring中的作用

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

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要 关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法(房地产例子中的:带客户看房,收中介费等)。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的 “地点”的定义(动态代理中的method.invoke)。
  • 连接点(JointPoint):与切入点匹配的执行点。

11.3 使用spring实现AOP

springAOP中,支持5种类型的Adivice:

  • 前置通知:在切入点之前执行,org.springframework.aop.MethodBeforeAdvice
  • 后置通知:在切入点之后执行,org.springframework.aop.AfterReturningAdrvice
  • 环绕通知:在切入点前后都执行,org.springframework.aop.MethodInterceptor
  • 异常抛出通知:切入点抛出异常执行,org.springframework.aop.ThrowsAdvice
  • 引介通知:类中增加新的方法属性,org.springframework.aop.IntroductionInterceptor

在使用spring实现AOP时,需要导入一个依赖包:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
第一种方式:接口实现
  1. 编写service接口和Service实现类

    public interface UserService {
        public void add();
    
        public void delete();
    
        public void update();
    
        public void search();
    
    }
    
    public class UserServiceImpl implements UserService {
        @Override
        public void add() {
            System.out.println("增加用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除用户");
        }
    
        @Override
        public void update() {
            System.out.println("更新用户");
        }
    
        @Override
        public void search() {
            System.out.println("查询用户");
        }
    }
    
  2. 编写前置增强类和后置增强类

    public class Log implements MethodBeforeAdvice {
        //method : 要执行的目标对象的方法
        //objects : 被调用的方法的参数
        //Object : 目标对象
        @Override
        public void before(Method method, Object[] objects, Object o) throws
                Throwable {
            System.out.println(o.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("执行了" + target.getClass().getName()
                    + "的" + method.getName() + "方法,"
                    + "返回值:" + returnValue);
        }
    }
    
  3. 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 https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--注册bean-->
        <bean id="userService" class="com.yirui.service.UserServiceImpl"/>
        <bean id="log" class="com.yirui.log.Log"/>
        <bean id="afterLog" class="com.yirui.log.AfterLog"/>
        <!--aop的配置-->
        <aop:config>
            <!--切入点 expression:表达式匹配要执行的方法-->
            <aop:pointcut id="pointcut" expression="execution(*
    com.yirui.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>
    

    com.yirui.service.UserServiceImpl.*(…)指的是UserServiceImpl类下的所有方法,参数任意。

  4. 测试

    public class aopTest01 {
        @Test
        public void test(){
           
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            UserService userService = context.getBean("userService",UserService.class);
            userService.add();
            userService.delete();
            userService.update();
            userService.search();
        }
    }
    

    image-20220324191010920

第二种方式:自定义实现
  1. 接口和其实现类不变

  2. 自定义切入类

    public class DiyPointcut {
        public void before(){
            System.out.println("---------方法执行前---------");
        }
        public void after(){
            System.out.println("---------方法执行后---------");
        }
    }
    
  3. spring中配置

        <bean id="diy" class="com.yirui.log.DiyPointcut"/>
        <!--aop的配置-->
        <aop:config>
            <!--第二种方式:使用AOP的标签实现-->
            <aop:aspect ref="diy">
                <aop:pointcut id="diyPonitcut" expression="execution(*
    com.yirui.service.UserServiceImpl.*(..))"/>
                <aop:before pointcut-ref="diyPonitcut" method="before"/>
                <aop:after pointcut-ref="diyPonitcut" method="after"/>
            </aop:aspect>
        </aop:config>
    

    image-20220324192936178

第三种方式:注解实现
  1. 接口和其实现类不变

  2. 自定义注解实现的切入类

    package com.yirui.log;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    @Aspect
    public class AnnotationPointCut {
        @Before("execution(* com.yirui.service.UserServiceImpl.*(..))")
        public void before(){
            System.out.println("---------方法执行前---------");
        }
        @After("execution(* com.yirui.service.UserServiceImpl.*(..))")
        public void after(){
            System.out.println("---------方法执行后---------");
        }
        @Around("execution(* com.yirui.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);
        }
    }
    
  3. spring中注册bean,并开启注解支持

    <!--第三种方式:注解实现-->
    <bean id="annotationPointcut" class="com.yirui.log.AnnotationPointCut"/>
    <aop:aspectj-autoproxy/>
    

    image-20220324193622107

由上图可知,环绕通知执行的顺序在前置通知和后置通知之前

aop:aspectj-autoproxy:说明

通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面
的bean创建代理,织入切面。当然,spring 在内部依旧采用
AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被
<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态
代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用
CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接
口,则spring将自动使用CGLib动态代理。

12、整合Mybatis

  1. 创建新项目

  2. 导入依赖

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.2</version>
    </dependency>
    
  3. maven静态资源过滤问题

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
    
  4. 构建原始mybatis项目

    1. mybatis配置文件

      driver = com.mysql.jdbc.Driver
      url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8
      username = root
      password = 123456
      
      <?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>
          <properties resource="db.properties"/>
          <typeAliases>
              <package name="com.yirui.pojo"/>
          </typeAliases>
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"/>
                  <dataSource type="POOLED">
                      <property name="driver" value="${driver}"/>
                      <property name="url" value="${url}"/>
                      <property name="username" value="${username}"/>
                      <property name="password" value="${password}"/>
                  </dataSource>
              </environment>
          </environments>
          <!--    每一个mapper.xml都需要在mybatis核心配置文件中注册-->
          <mappers>
              <package name="com.yirui.dao"/>
          </mappers>
      </configuration>
      
    2. 工具类

      public class MybatisUtils {
          private static SqlSessionFactory sqlSessionFactory;
      
          static {
              try {
                  String resource = "mybatis-config.xml";
                  InputStream inputStream = Resources.getResourceAsStream(resource);
                  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          //通过工厂获取sqlSession
          public static SqlSession getSession(){
              return sqlSessionFactory.openSession();
          }
      }
      
    3. 实体类

      @Data
      public class User {
          private int id; //id
          private String name; //姓名
          private String pwd; //密码
      }
      
    4. Dao接口

      public interface UserDao {
          public List<User> selectUser();
      }
      
    5. 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.yirui.dao.UserMapper">
          <select id="selectUser" resultType="User">
              select * from user
          </select>
      </mapper>
      
    6. 测试类

      public class springMybatisTest {
          @Test
          public void test(){
              SqlSession sqlSession = MybatisUtils.getSession();
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              List<User> userList = mapper.selectUser();
              for (User user : userList) {
                  System.out.println(user);
              }
              sqlSession.close();
          }
      }
      

      image-20220324205419569

  5. MyBatis-Spring改造代码

    要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean 来创建 SqlSessionFactory 。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

    <!--导入配置文件-->
    <context:property-placeholder location="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>
    <!--配置sqlSessionFactory-->
    <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--也可以直接引入mybatis的配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--引入mapper文件-->
        <property name="mapperLocations" value="classpath:com/yirui/dao/*.xml"/>
    </bean>
    

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

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

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

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

    • 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>
      

注意点:

  • 无论导入的mybatis-config.xml中有没有数据源,在spring中配置sqlSessionFactorybean的时候都必须要注入dataSource属性,否则一定会报错,所以在Spring中一定要有一个dataSource。

  • 在sqlSessionFactorybean中如果注入了mapperLocations属性,就不需要再mybatis-config.xml中注册mapper,否则会有如下报错。

    image-20220325162406320

问题:

在测试整合Mybatis实现时,报了如下错误,排查发现name取错了,目前不知到什么原因,直接在datasource里面写死:

image-20220325162456385

image-20220325162534684

image-20220325162641790

image-20220325162703323

image-20220325162730408

第二种获得sqlSessionTemplate的方式

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
<bean id="uerMapper2" class="com.yirui.dao.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

继承了SqlSessionDaoSupport的方法可以直接通过getSqlSession()来获得sqlSessionTemplate,但是需要注入sqlSessionFactory。

13、事务管理

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>

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这 个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。 假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的 调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。 就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

配置AOP

导入aop的头文件!

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

事务管理器

  • 无论使用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>

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这 个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。 假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的 调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。 就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

配置AOP

导入aop的头文件!

<!--配置aop织入事务-->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*
    (..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值