Spring5笔记整理

一、Spring概述

官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/index.html

版本:Version 5.3.19

1.1 什么是Spring?

  • Spring是一个Java的免费开源的框架。
  • Spring使创建Java企业应用程序变得容易。
  • 它是为了解决企业应用开发的复杂性而创建的。
  • 2003年2月,Spring0.9版本发布,它采用了Apache2.0开源协议;2004年4月,Spring1.0版本正式发布。到目前为止,Spring已经步入了第5个大版本,也就是我们常说的Spring5。

1.2 为什么学Spring?

  • 方便解耦,简化开发
    • Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给Spring管理。
  • 方便集成各种优秀框架
    • Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts2、Hibernate、MyBatis等)的直接支持。
  • 降低Java EE API的使用难度
    • Spring对Java EE开发中非常难用的一些API(JDBC、JavaMail、远程调用等)都提供了封装,使这些API应用的难度大大降低。
  • 方便程序的测试
    • Spirng支持JUnit4,可以通过注解方便的测试Spring程序。
  • AOP编程的支持
    • Spring提供面向切面编程,可以方便的实现对程序进行权限拦截和运行监控等功能。
  • 声明式事务的支持
    • 只需要通过配置就可以完成对事务的管理,而无需手动编程。
  • Spring的核心使控制反转(IOC)和面向切面(AOP)。

二、Spring核心

Spring核心是:控制反转(IOC),面向切面(AOP)

2.1 什么是IOC?

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象,而IOC是有专门一个容器来创建这些对象,即由IOC容器来控制对象的创建;

2.2 分析传统项目实现

  1. 创建一个空白的maven项目

  2. 写一个UserDao接口

    package dao;
    public interface UserDao {
        public void getUser();
    }
    
  3. Dao的实现类(模拟MySQL版本的Dao实现)

    package dao.impl;
    import dao.UserDao;
    public class UserDaoMysqlImpl implements UserDao {
        public void getUser() {
            System.out.println("MySql获取用户数据");
        }
    }
    
  4. 然后去写UserService的接口

    package service;
    public interface UserService {
        public void getUser();
    }
    
  5. Service的实现类

    package service.impl;
    import dao.UserDao;
    import dao.impl.UserDaoMysqlImpl;
    import service.UserService;
    public class UserServiceImpl implements UserService {
        //1、传统方法,直接new对象
        private UserDao userDao = new UserDaoMysqlImpl();
        public void getUser() {
            userDao.getUser();
        }
    }
    
  6. 测试

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

结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dqtI45wa-1658195283144)(img/内网通截图20220718163358.png)]

假设, 给UserDao接口添加多一个实现类,UserDaoOracleImpl(模拟Oracle的Dao实现)

package dao.impl;
import dao.UserDao;
public class UserDaoOracleImpl implements UserDao {
    public void getUser() {
        System.out.println("Oracle获取用户数据");
    }
}

修改service实现类里面对应的实现

package service.impl;
import dao.UserDao;
import dao.impl.UserDaoMysqlImpl;
import dao.impl.UserDaoOracleImpl;
import service.UserService;
public class UserServiceImpl implements UserService {
    //1、传统方法,直接new对象
    // private UserDao userDao = new UserDaoMysqlImpl();
    private UserDao userDao = new UserDaoOracleImpl();
    public void getUser() {
        userDao.getUser();
    }
}

如果每次有改动这样改好不好?

我们每次为UserDao接口扩展了实现类,业务层需要修改代码才可以实现切换具体的实现类,这种做法耦合性太高了,扩展性不好! 假设我们的这种需求非常大 , 这种方式就根本不适用了,比较容易出错。

我们如何解决这个问题?

  1. 改造原来实现的Dao的地方,留出一个入参。

    public class UserServiceImpl implements UserService {
        private UserDao userDao;
        // 利用set实现
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    
  2. 现在去我们的测试类里,进行测试;

    @Test
    public void test(){
        UserServiceImpl service = new UserServiceImpl();
        // 1、使用Mysql
        service.setUserDao( new UserDaoMySqlImpl() );
        service.getUser();
        // 2、使用Oracle
        service.setUserDao( new UserDaoOracleImpl() );
        service.getUser();
    }
    

从上面来看,代码灵活了许多。以前代码都是固定的,现在是由我们选择程序去创建对象。选择权是我们手上。

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

2.3 IOC本质

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BRZo3Hgx-1658195283147)(img/内网通截图20220718164456.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcB62wDm-1658195283147)(img/内网通截图20220718164751.png)]

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

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

三、第一个Spring项目

3.1 导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.19</version>
    <scope>test</scope>
    </dependency>

3.2 编写代码

  1. 写实体类

    public class Hello {
        private String name;
        public void sayHello(){
            System.out.println("Hello "+name);
        }
        // get/set toString
    }
    
  2. 在资源目录下编写xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--bean就是java对象 , 由Spring创建和管理-->
        <bean id="hello" class="pojo.Hello">
            <property name="name" value="Spring"/>
        </bean>
    </beans>
    
  3. 测试

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

结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N5Fuod4I-1658195283148)(img/内网通截图20220718200536.png)]

3.3 为什么不用new就可以使用

  • Hello对象是谁创建的?为什么不用new就可以使用?
    • hello对象是由Spring创建的,交给了spring管理了,使用的时候通过spring去获取就能使用了
  • Hello对象的属性是怎么设置的?
    • hello对象的属性是由Spring容器设置的。

这个过程就叫控制反转

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

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

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

可以通过newClassPathXmlApplicationContext去浏览一下底层源码。

3.4 改造案例

回顾之前的例子,根据之前的登录进行改造。

新增一个Spring配置文件beans2.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="MysqlImpl" class="com.dao.impl.UserDaoMySqlImpl"/>
    <bean id="OracleImpl" class="com.dao.impl.UserDaoOracleImpl"/>
    <bean id="ServiceImpl" class="com.service.impl.UserServiceImpl">
        <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
        <!--引用另外一个bean , 不是用value 而是用 ref-->
        <property name="userDao" ref="OracleImpl"/>
    </bean>
</beans>

测试!

@Test
public void test03() {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
    serviceImpl.getUser();
}

到了现在,我们彻底不用在程序中去改程序了,要实现不同的操作,只需要在xml配置文件中进行修改即可,这就是所谓的IOC,简单来说:对象由Spring来创建,管理,装配!

四、IOC创建对象方式

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

  1. User.java

    public class User {
        private String name;
        public void sayHello(){
            System.out.println("Hello "+ this.name);
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    
  2. beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--bean就是java对象 , 由Spring创建和管理-->
        <bean id="user" class="pojo.User">
            <property name="name" value="AdminUser"/>
        </bean>
    </beans>
    
  3. 测试

    public class MyTest {
        @Test
        public void test() {
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            //在执行getBean的时候, user已经创建好了 , 通过无参构造
            User user = (User) context.getBean("user");
            user.sayHello();
        }
    }
    

这个例子其实就是三、第一个Spring项目一样的。

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

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

  1. UserT.java

    public class UserT {
        private String name;
        public UserT(String name) {
            this.name = name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public void showName(){
            System.out.println("name="+ name );
        }
    }
    
  2. beanx.xml有三种方式编写

    <!-- 第一种根据index参数下标设置 -->
    <bean id="userT" class="pojo.UserT">
        <!-- index指构造方法 , 下标从0开始 -->
        <constructor-arg index="0" value="kuangshen2"/>
    </bean>
    
    <!-- 第二种根据参数名字设置 -->
    <bean id="userT" class="pojo.UserT">
        <!-- name指参数名 -->
        <constructor-arg name="name" value="kuangshen2"/>
    </bean>
    
    <!-- 第二种根据参数名字设置 -->
    <bean id="userT" class="pojo.UserT">
        <!-- name指参数名 -->
        <constructor-arg name="name" value="kuangshen2"/>
    </bean>
    
  3. 测试

    @Test
    public void test04(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserT user = (UserT) context.getBean("userT");
        user.show();
    }
    

结论:在配置文件加载的时候,其中管理的对象都已经初始化了。

五、Spring配置

5.1 别名

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

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

5.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.pojo.Hello">
    <property name="name" value="Spring"/>
</bean>

5.3 import

团队的合作通过import来实现

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

六、依赖注入(DI)

  • 依赖注入(Dependency Injection,DI)
  • 依赖:指Bean对象的创建依赖于容器,Bean对象的依赖资源。
  • 注入:指Bean对象所依赖的资源,有容器来配置和装配。

6.1 构造器注入

我们在之前的案例中已经详细讲过了

6.2 set注入(重点)

要求被注入的属性,必须有set方法,set 方法的方法名由set+属性首字母大写,如果属性是boolean类型,没有set 方法,是is。

测试pojo类:

Address.java

public class Address {
    private String address;
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

Student.java

package pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;
    public void setName(String name) {
        this.name = name;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public void setBooks(String[] books) {
        this.books = books;
    }
    public void setHobbys(List<String> hobbys) {
        this.hobbys = hobbys;
    }
    public void setCard(Map<String, String> card) {
        this.card = card;
    }
    public void setGames(Set<String> games) {
        this.games = games;
    }
    public void setWife(String wife) {
        this.wife = wife;
    }
    public void setInfo(Properties info) {
        this.info = info;
    }
    public void show(){
        System.out.println("name="+ name
                + ",address="+ address.getAddress()
                + ",books="
        );
        for (String book:books){
            System.out.print("<<"+book+">>\t");
        }
        System.out.println("\n爱好:"+hobbys);
        System.out.println("card:"+card);
        System.out.println("games:"+games);
        System.out.println("wife:"+wife);
        System.out.println("info:"+info);
    }
}
  1. 常量注入

    <bean id="student" class="pojo.Student">
        <property name="name" value="小明"/>
    </bean>
    

    测试:

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

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

    <bean id="addr" class="pojo.Address">
        <property name="address" value="重庆"/>
    </bean>
    <bean id="student" class="pojo.Student">
        <property name="name" value="小明"/>
        <property name="address" ref="addr"/>
    </bean>
    
  3. 数组注入

    <bean id="student" class="pojo.Student">
        <property name="name" value="小明"/>
        <property name="address" ref="addr"/>
        <property name="books">
            <array>
                <value>西游记</value>
                <value>红楼梦</value>
                <value>水浒传</value>
            </array>
        </property>
    </bean>
    
  4. List注入

    <property name="hobbys">
        <list>
            <value>听歌</value>
            <value>看电影</value>
            <value>爬山</value>
        </list>
    </property>
    
  5. Map注入

    <property name="card">
        <map>
            <entry key="中国邮政" value="456456456465456"/>
            <entry key="建设" value="1456682255511"/>
        </map>
    </property>
    
  6. Set注入

    <property name="games">
        <set>
            <value>LOL</value>
            <value>BOB</value>
            <value>COC</value>
        </set>
    </property>
    
  7. Null注入

    <property name="wife"><null/></property>
    
  8. Properties注入

    <property name="info">
        <props>
            <prop key="学号">20190604</prop>
            <prop key="性别"></prop>
            <prop key="姓名">小明</prop>
        </props>
    </property>
    

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BGX66y3y-1658195283149)(img/内网通截图20220718204123.png)]

6.3 拓展注入实现

  1. p命名空间注入xmlns:p="http://www.springframework.org/schema/p"

  2. c命名空间注入xmlns:c="http://www.springframework.org/schema/c"

    c需要构造器注入

6.4 Bean的作用域

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LwejvtOM-1658195283150)(img/内网通截图20220718204439.png)]

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

6.4.1 Singleton

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

<bean id="ServiceImpl" class="service.ServiceImpl" scope="singleton">

测试:

@Test
public void test03(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User) context.getBean("user");
    User user2 = (User) context.getBean("user");
    System.out.println(user==user2);
}
6.4.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"/>
6.4.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实例将被销毁。

6.4.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也会被废弃掉。

七、Bean的自动装配

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

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

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

Spring的自动装配西药从两个角度来实现,或者说是两个操作:

  1. 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean;
  2. 自动装配(autowiring):Spring自动满足bean之间的依赖,也就是我们说的IOC/DI;

组件扫描和自动装配组合发挥巨大威力,使的显示的配置降低到最少。

推荐不使用自动装配xml配置,而使用注解。

7.1 按名称自动装配(byName)

aitowire byName(按名称自动装配)

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试:

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

    <bean id="user" class="com.pojo.User" autowire="byName">
        <property name="str" value="qinjiang"/>
    </bean>
    
  2. 再次测试,结果依旧成功输出!

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

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

小结:

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

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

7.2 按类型自动装配(byType)

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

NoUniqueBeanDefinitionException

测试:

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

测试,正常输出

在注册一个cat的bean对象!

<bean id="dog" class="com.pojo.Dog"/>
<bean id="cat" class="com.pojo.Cat"/>
<bean id="cat2" class="com.pojo.Cat"/>
<bean id="user" class="com.pojo.User" autowire="byType">
    <property name="str" value="qinjiang"/>
</bean>

测试,报错:NoUniqueBeanDefinitionException

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

这就是按照类型自动装配!

7.3 使用注解

jdk1.5开始支持注解,Spring2.5开始全名支持注解。

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

  1. 在Spring配置文件中引入context文件头

    xmlns:context="http://www.springframework.org/schema/context"
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    
  2. 开启属性注解支持!

    <context:annotation-config/>
    
7.3.1 @Autowired
  • @Autowired是按类型自动装配的,不支持id匹配。
  • 需要导入Spring-aop的包!

测试:

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

    public class User {
        @Autowired
        private Cat cat;
        @Autowired
        private Dog dog;
        private String str;
        public Cat getCat() {
            return cat;
        }
        public Dog getDog() {
            return dog;
        }
        public String getStr() {
            return str;
        }
    }
    
  2. 此时配置文件内容

    <context:annotation-config/>
    <bean id="dog" class="com.pojo.Dog"/>
    <bean id="cat" class="com.pojo.Cat"/>
    <bean id="user" class="com.pojo.User"/>
    
  3. 测试,成功输出结果!

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

实体类:

public class User {
    //如果允许对象为null,设置required = false,默认为true
    @Resource(name = "cat2")
    private Cat cat;
    @Resource
    private Dog dog;
    private String str;
}

beans.xml

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

测试:结果OK

配置文件2:beans.xml , 删掉cat2

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

实体类上只保留注解

@Resource
private Cat cat;
@Resource
private Dog dog;

结果:OK

结论:先进行byName查找,失败;再进行byType查找,成功。

7.4 小结

@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.1 说明

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NaeuijvZ-1658195283151)(img/内网通截图20220719084644.png)]

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

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

8.2 Bean的实现

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

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

    <!--指定注解扫描包-->
    <context:component-scan base-package="com.pojo"/>
    
  2. 在指定包下编写类,增加注解

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

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

8.3 属性注入

使用注解注入属性

  1. 可以不用提供set方法,直接在属性名上添加@value(“值”)

    @Component("user")
    // 相当于配置文件中 <bean id="user" class="当前注解的类"/>
    public class User {
        @Value("小明")
        // 相当于配置文件中 <property name="name" value="小明"/>
        public String name;
    }
    
  2. 如果提供了set方法,在set方法上添加@value(“值”)

    @Component("user")
    public class User {
        public String name;
        @Value("小明")
        public void setName(String name) {
            this.name = name;
        }
    }
    

8.4 衍生注解

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

@Component三个衍生注解

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

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

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

8.5 自动装配注解

在Bean的自动装配已经讲过了,可以回顾!

8.6 作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象,关闭工厂,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂,所有的对象不会销毁,内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
    @Value("秦疆")
    public String name;
}

8.7 小结

XML与注解比较

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

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

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中,可以不用扫描,扫描时为了类上的注解
<context:annotation-config/>

作用:

  • 进行注解驱动注册,从而使注解生效
  • 用于激活那些已经在Spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
  • 如果不扫描包,就需要手动配置bean
  • 如果不加注解驱动,则注入的值为null!

8.8 基于Java类进行配置

JavaConfig原来使Spring的一个子项目,它通过Java类的方式提供Bean的定义信息,在Spring4的版本,JavaConfig已正式成为Spring4的核心功能。

测试:

  1. 编写一个实体类,Dog

    @Component  //将这个类标注为Spring的一个组件,放到容器中!
    public class Dog {
        public String name = "dog";
    }
    
  2. 新建一个config配置包,编写一个MyConfig配置类

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

    @Test
    public void test2(){
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(MyConfig.class);
        Dog dog = (Dog) applicationContext.getBean("dog");
        System.out.println(dog.name);
    }
    
  4. 成功输入结果!

导入其他配置如何做呢?

  1. 我们再编写一个配置类!

    @Configuration  //代表这是一个配置类
    public class MyConfig2 {
    }
    
  2. 在之前的配置类中我们来选择导入这个配置类

    @Configuration
    @Import(MyConfig2.class)  //导入合并其他配置类,类似于配置文件中的 inculde 标签
    public class MyConfig {
        @Bean
        public Dog dog(){
            return new Dog();
        }
    }
    

关于这种Java类的配置方式,我们在之后的SpringBoot和SpringCloud中还会大量看到,我们需要直到这些注解的作用即可!

九、代理模式

学习aop之前,我们要先了解一些代理模式!

AOP的底层机制就是动态代理!

代理模式:

  • 静态代理
  • 动态代理

9.1 静态代理

静态代理角色分析
  • 抽象角色:一般使用接口或者抽象类来实现
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色;代理真实角色后,一般会做一些附属的操作。
  • 客户:使用代理角色来进行一些操作。
代码实现
  1. 创建一个抽象角色,比如平时做的用户业务,抽象起来就是增删改查!

    //抽象角色:增删改查业务
    public interface UserService {
        void add();
        void delete();
        void update();
        void query();
    }
    
  2. 我们需要一个真实对象来完成这些增删改查操作

    //真实对象,完成增删改查操作的人
    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 query() {        
            System.out.println("查询了一个用户");   
        }
    }
    
  3. 需求来了,现在我们需要增加一个日志功能,怎么实现!

    • 思路1:在实现类上增加代码【麻烦!】
    • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
  4. 设置一个代理类来处理日志!代理角色

    //代理角色,在这里面增加日志的实现
    public class UserServiceProxy implements UserService {
        private UserServiceImpl userService;
        public void setUserService(UserServiceImpl userService) {
            this.userService = userService;
        }
        public void add() {
            log("add");
            userService.add();
        }
        public void delete() {
            log("delete");
            userService.delete();
        }
        public void update() {
            log("update");
            userService.update();
        }
        public void query() {
            log("query");
            userService.query();
        }
        public void log(String msg){
            System.out.println("执行了"+msg+"方法");
        }
    }
    
  5. 测试访问类

    public class Client {
        public static void main(String[] args) {
            //真实业务
            UserServiceImpl userService = new UserServiceImpl();
            //代理类
            UserServiceProxy proxy = new UserServiceProxy();
            //使用代理类实现日志功能!
            proxy.setUserService(userService);
            proxy.add();
        }
    }
    

OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;

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

【聊聊AOP:纵向开发,横向开发】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iSOtawhu-1658195283152)(img/内网通截图20220719091304.png)]

9.2 静态代理的好处

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

缺点:

  • 类多了,多了代理类,工作量变大了,开发效率降低。

我们想要静态代理的好处,又不想要静态代理的缺点,所以,就有了动态代理!

9.3 动态代理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理——JDK动态代理
    • 基于类的动态代理—cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

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

核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看

【InvocationHandler:调用处理程序】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eyoAMPm3-1658195283152)(img/内网通截图20220719091612.png)]

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

【Proxy : 代理】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Yg6xofF-1658195283153)(img/内网通截图20220719091655.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSeEjdpY-1658195283154)(img/内网通截图20220719091721.png)]

//生成代理类
public Object getProxy(){
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                  rent.getClass().getInterfaces(),this);
}

代码实现

public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
    // proxy : 代理类
    // method : 代理类的调用处理程序的方法对象.
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
    public void log(String methodName){
        System.out.println("执行了"+methodName+"方法");
    }
}

测试!

public class Test {
    public static void main(String[] args) {
        //真实对象
        UserServiceImpl userService = new UserServiceImpl();
        //代理对象的调用处理程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService); //设置要代理的对象
        UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
        proxy.delete();
    }
}

【测试,增删改查,查看结果】

9.4 动态代理的好处

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

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

十、AOP

10.1 什么是AOP

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A9H8yqBt-1658195283155)(img/内网通截图20220719091908.png)]

10.2 AOP在Spring中的作用

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jZUC6RYm-1658195283155)(img/内网通截图20220719091953.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLnZ16Tv-1658195283156)(img/内网通截图20220719092018.png)]

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

10.3 使用Spring实现AOP

【重点】使用AOP织入,需要导入一个依赖包!

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
第一种方式

通过Spring API实现

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

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("查询用户");
    }
}

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

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() + "方法被执行了");
    }
}

最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--注册bean-->
    <bean id="userService" class="com.service.UserServiceImpl"/>
    <bean id="log" class="com.log.Log"/>
    <bean id="afterLog" class="com.log.AfterLog"/>
    <!--aop的配置-->
    <aop:config>
        <!--切入点  expression:表达式匹配要执行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.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>

测试

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

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

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

第二种方式

自定义来实现AOP

目标业务类不变依旧是userServiceImpl

第一步 : 写我们自己的一个切入类

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

去spring中配置

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

测试:

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}
第三种方式

使用注解实现

第一步:编写一个注解实现的增强类

package com.config;
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.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    @After("execution(* com.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
    }
    @Around("execution(* com.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

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

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动态代理。

十一、声明式事务

11.1 回顾事务

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

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

事务四个属性ACID

  1. 原子性(atomicity)
    • 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
  2. 一致性(consistency)
    • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  3. 隔离性(isolation)
    • 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
  4. 持久性(durability)
    • 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

11.2 测试

将上面的代码拷贝到一个新项目中

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

//添加一个用户
int addUser(User user);
//根据id删除用户
int deleteUser(int id);

mapper文件,我们故意把 deletes 写错,测试!

<insert id="addUser" parameterType="com.pojo.User">
 insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int">
 deletes from user where id = #{id}
</delete>

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

public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
    //增加一些操作
    public List<User> selectUser() {
        User user = new User(4,"小明","123456");
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        mapper.addUser(user);
        mapper.deleteUser(4);
        return mapper.selectUser();
    }
    //新增
    public int addUser(User user) {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.addUser(user);
    }
    //删除
    public int deleteUser(int id) {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.deleteUser(id);
    }
}

测试

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

报错:sql异常,delete写错了

结果 :插入成功!

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

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

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

11.3 Spring中的事务管理

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

编程式事务管理

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

声明式事务管理

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

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

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

事务管理器

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

JDBC事务

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

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

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

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.dao.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

进行测试

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

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

思考问题?

为什么需要配置事务?

  • 如果不配置,就需要我们手动提交控制事务;
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!

总结:

Spring核心:

  1. 控制反转(IOC)
    • 把类交到SpringBean工厂管理,减少耦合。
  2. 面向切面(AOP)
    • 在不影响原来的业务代码上,进行代理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值