spring

Spring

1 Spring概述

1.1 什么是Spring

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CZT3SOAL-1676534158949)(assets/wps1.jpg)]

​ [Rod Johnson](http://baike.baidu.com/item/Rod Johnson)在2002年编著的《Expert one on one J2EE design and development》一书中,对Java EE 系统框架臃肿、低效、脱离现实的种种现状提出了质疑,并积极寻求探索革新之道。以此书为指导思想,他编写了interface21框架,这是一个力图冲破J2EE传统开发的困境,从实际需求出发,着眼于轻便、灵巧,易于开发、测试和部署的轻量级开发框架。Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。同年他又推出了一部堪称经典的力作《Expert one-on-one J2EE Development without EJB》,该书在Java世界掀起了轩然大波,不断改变着Java开发者程序设计和开发的思考方式。在该书中,作者根据自己多年丰富的实践经验,对EJB的各种笨重臃肿的结构进行了逐一的分析和否定,并分别以简洁实用的方式替换之。至此一战功成,Rod Johnson成为一个改变Java世界的大师级人物。

​ 传统J2EE应用的开发效率低,应用服务器厂商对各种技术的支持并没有真正统一,导致J2EE的应用没有真正实现Write Once及Run Anywhere的承诺。Spring作为开源的中间件,独立于各种应用服务器,甚至无须应用服务器的支持,也能提供应用服务器的功能,如声明式事务、事务处理等。

​ Spring致力于J2EE应用的各层的解决方案,而不是仅仅专注于某一层的方案。可以说Spring是企业应用开发的“一站式”选择,并贯穿表现层、业务层及持久层。然而,Spring并不想取代那些已有的框架,而是与它们无缝地整合。

1.2 Spring框架的结构体系

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YOkAIC4t-1676534158950)(assets/wps2.jpg)]

组成Spring框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  1. 内核容器(Core Container)

​ 内核容器是由spring-core、spring-beans、spring-context、spring-context-sup和spring-expression(Spring Expression Language)模块组成。 Spring-core和spring-beans模块提供了框架的基本功能,包括IoC和依赖注入特性。BeanFactory是工场模式的精美实现。它不需要单独的编程逻辑就可以把所依赖的配置和规范从程序的实际逻辑中分离开。

​ Context(spring-context)模块是在Core和Beans模块的基础之上构建的。Context模块继承了Beans模块的特性,并添加了国际化(i18n)(使用资源绑定的方式)、事件传播、资源加载、以及通过Servlet容器透明创建的上下文环境。Context模块也支持*Java EE*特性,如EJB、JMX和基本的远程调用。ApplicationContext接口是Context模块的入口.

​ spring-context-support提供了把常用的第三方类库(如缓存(EhCache、Guava、JCache)、邮件(JavaMail)、计划(CommonJ、Quartz)、以及模板引擎(FreeMarker、JasperReports、Velocity))集成到Spring应用场景中的支持。
spring-expression模块提供了可用于运行时查询和操作对象的强大的表达式语言。它是JSP2.1规范中指定的统一表达式语言的一个扩展。它支持设置和获取属性值、属性匹配、方法调用、访问数组内容、集合和索引、逻辑和算术运算、命名变量、以及使用对象名从Spring容器中检索对象等功能。它还支持列表的投射和选择以及常用的列表聚合。

  1. AOP和监测器(Instrumentation)

​ spring-aop提供了可定义的符合AOP联盟标准的面向切面编程的实现。例如,应该把拦截器和切入点的方法与实现功能的代码进行清晰的解耦分离。使用代码级的元数据功能,也可以像.NET的属性那样把行为信息合并到代码中。

​ 独立的spring-aspects模块提供AspectJ的集成。

​ spring-instrument模块提供了类的检测支持,并且类的加载器实现可以被用于特定应用服务中。spring-instrument-tomcat模块包含了Spring给Tomcat提供的监测代理。

  1. 消息传递

    ​ Spring 框架包含的spring-messaging模块带有一些来自诸如Message、MessageChannel、MessageHandler等Spring Integration对象的关键抽象,它们被用于基于消息传递应用的服务基础。这个模块映射包含了一组用于消息映射的方法注释,类似于基于编程模式的Spring MVC注释。

  2. 数据访问/集成

    ​ 数据访问/集成层是由JDBC、ORM、OXM、JMS和事务模块组成。

    ​ spring-jdbc模块提供了一个JDBC抽象层,它剔除了繁琐的JDBC编码和数据库供应商指定的错误代码的解析。

    ​ 对于实现指定接口的类和所有的POJO对象,spring-tx模块提供的编程和声明式事务的支持。

    ​ spring-orm模块提供了通用的对象-关系映射API的集成层,包括对JPA、JDO和*hibernate*集成。使用spring-orm模块可以使用跟Spring提供的其他功能合并到一起的所有的O/R映射框架,例如前面提到的简单的声明式事务管理功能。

    ​ spring-oxm提供了一个支持Object/XML映射实现带的抽象层,如JAXB、Castor、XMLBeans、JiBX和XStream等。

    ​ spring-jms模块(Java消息服务)包含了制造和使用消息的功能,从 Spring框架4.1开始,它就集成了spring-messaging模块。

  3. Web

    Web层由spring-web、spring-webmvc、spring-websocket和spring-webmvc-portlet模块组成。

    ​ spring-web模块提供了基本的面向web的基础功能,例如分段上传文件功能、使用Servlet监听器来初始化IoC容器,以及面向web的应用程序环境。它还包含了一个HTTP客户端和Spring远程支持的web相关的部分。

    ​ spring-webmvc模块(也被叫做Web-Servlet模块)包含了Spring的MVC框架和用于web应用程序的REST web服务的实现。Spring的MVC框架在域模型代码和web表达之间提供了清晰的边界,并且还集成了Spring框架的所有其他功能。

    ​ spring-webmvc-portlet模块(也被叫做Web-Portlet模块)提供了用于Portlet环境和映射spring-webmvc模块功能的MVC实现。

  4. 测试

    spring-test模块支持使用带有JUnit或TestNG的Spring 组件进行单元测试和集成测试。它提供了固定的Spring的ApplicationContests加载以及这些环境的缓存。它还提供了mock对象,可以让你使用隔离的方式来测试你的代码。

1.3 Spring的核心

  • IoC (Inverse of control )反转控制 或者DI (Dependency Injection,依赖注入

  • AOP (Aspect Oriented Programming)面向切面编程

Spring的官网:http://spring.io

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VlKjlWKL-1676534158951)(assets/image-20220729151126656.png)]

2.Spring的IoC之Hello World

我们创建一个maven项目,导入Spring的依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DtKk7qYa-1676534158952)(assets/image-20220729152329530.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-60T0RAkX-1676534158952)(assets/image-20220729152420187.png)]

添加spring相关依赖,对应的pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring-helloworld</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <spring.version>5.2.15.RELEASE</spring.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--spring-test-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
		<!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>
</project>

编写IHello接口:

package com.suke.hello;

public interface IHello {
    public String sayHello(String word);
}

编写IHello的实现类:

package com.suke.hello.impl;

import com.suke.hello.IHello;

public class HelloImpl implements IHello {
    public String sayHello(String word) {
        return "hello:"+word;
    }
}

在src/main/resources目录下创建一个spring的配置文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E1KHnQMW-1676534158953)(assets/image-20220731101755324.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZoRzrb2D-1676534158954)(assets/image-20220731101814724.png)]

在applicationContext.xml文件中,把HelloImpl交给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 class="com.suke.hello.impl.HelloImpl" id="hello"></bean>
</beans>

编写测试类,测试IHello的sayHello()方法

package com.suke.hello;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

public class IHelloTest {
    
    @Test
    public void sayHello() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        IHello hello = applicationContext.getBean("hello", IHello.class);
        System.out.println(hello.sayHello("spring"));
    }
}

ApplicationContext的继承体系

**applicationContext:**接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fMlcXypY-1676534158954)(assets/image-20220731182029042.png)]

ApplicationContext的实现类

1)ClassPathXmlApplicationContext

它是从类的根路径下加载配置文件 推荐使用这种

2)FileSystemXmlApplicationContext

它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

3)AnnotationConfigApplicationContext

当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLCBSIXZ-1676534158955)(assets/image-20220731102104626.png)]

如果想看到Spring运行的日志,我们可以添加log4j日志框架,并在src/main/resources目录添加log4j的配置文件:

 <!--log4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.36</version>
</dependency>

log4j.properties

log4j.rootCategory=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yC6o8z3m-1676534158956)(assets/image-20220731102559290.png)]

IOC的理解

​ 1996年,Michael Mattson(迈克尔·马特森)在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。

​ IoC(Inversion of Control,即“控制反转”)不是什么技术,而是一种设计思想。在Java开发中,IoC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好IoC呢?理解好IoC的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由IoC容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

用图例说明一下,传统程序设计如图,都是主动去创建相关对象然后再组合起来:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAaLcNWl-1676534158957)(assets/wps1.png)]

图 传统应用程序示意图

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pmfFBIOb-1676534158957)(assets/wps2-1659234402175.jpg)]

​ 图: IoC/DI容器后程序结构示意图

​ **IoC(控制反转)就是将程序中原来 new 对象,交给spring创建,从spring工厂获取对象,使用spring来负责控制对象的生命周期和对象间的关系.**这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像范冰冰,身材像林熙雷,唱歌像张学友,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。

Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

3. Spring的DI入门程序

在前面的案例中,我们再创建UserDao接口和UserDaoImpl的实现类:

package com.suke.dao;

public interface UserDao {
    public void insert();
    public void delete();
}


package com.suke.dao.impl;

import com.suke.dao.UserDao;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UserDaoImpl implements UserDao {
    @Override
    public void insert() {
        log.info("保存用户成功...");
    }

    @Override
    public void delete() {
        log.info("删除用户成功...");
    }
}

接下来我们来创建UserService接口和对应的实现类, 然后调用UserDao的方法完成相关的业务功能:

package com.suke.service;

public interface UserService {
    public void addUser();

    public void deleteUser();
}


package com.suke.service.impl;

import com.suke.dao.UserDao;
import com.suke.service.UserService;

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void addUser() {
        userDao.insert();

    }

    @Override
    public void deleteUser() {
        userDao.delete();
    }
}

在applicationContext.xml文件中配置UserDao和UserService的bean:

<bean class="com.suke.dao.impl.UserDaoImpl" id="userDao"></bean>
<bean class="com.suke.service.impl.UserServiceImpl" id="userService">
    <property name="userDao" ref="userDao"></property>
</bean>

编写对应的测试类:

package com.suke.service;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

public class UserServiceTest {
    private  UserService userService;

    @Before
    public void setUp(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        userService = applicationContext.getBean("userService",UserService.class);
    }
    @Test
    public void addUser() {
        userService.addUser();
    }

    @Test
    public void deleteUser() {
        userService.deleteUser();
    }
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRXchMQj-1676534158958)(assets/image-20220731104049740.png)]

DI的理解

​ DI(Dependency Injection)即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁:当然是应用程序依赖于IoC容器;

  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

  • 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

​ IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(依赖注入)来实现的**。**比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢?Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

IoC和DI是什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。 理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

4. Bean的管理

​ Spring的基本Bean管理包括Bean配置,Bean实例化和Bean的依赖注入。这些管理可以通过手工编码的方式把每个Bean注册到容器中,也可以通过properties文件和xml文件配置Bean和Bean之间的依赖关系。通常我们的配置方式是XML作为配置文件。

4.1 Bean的配置

​ 我们可以看一看前面例子的配置文件:

<bean id="dog" class="org.suke.Dog" ></bean>
<bean id="cat" class="org.suke.Cat" ></bean>
<bean id="boy" class="org.suke.Boy" >
   <property name="pet" ref="dog"></property>
</bean>

配置一个Bean,id为Bean的标识,在配置Bean时,可以使用name属性为Bean指定标识。id 和name 属性都是用来指定bean的标识符。id具有唯一性,并且是XML中真正的id属性,XML解析器可以验证其的合法性,在使用中必须和Java中命名变量一样去命名id的值,比如不能以数字开始等约束。name属性值则没有要求,甚至在name中可以使用特殊字符(如等)。

​ 如果在配置文件中既没有配置id,也没有配置name,Spring会默认使用类的全名来标识,如果需要配置多个类名相同的对象,则spirng会使用 类名+#+数字的形式来标识。即如果配置了三个<bean class="org.suke.Dog">,那么标识分别是 “org.suke.Dog”” org.suke.Dog #1”,和” org.suke.Dog #2”

​ name属性其实就是为该Bean指定的别名,多个别名之间使用”,”进行分隔。还可以使用< alias >来指定别名,比如上面的dog使用alias来配置别名可以做如下配置:

<bean id="dog" name="dog1,dog2,dog3" class="org.suke.Dog" />

<alias name="dog" alias="dog4"/>

​ 这样dog对象就被被标识为 dog,dog1,dog2,dog3,dog4这4个标识名,<alias>标记中的name可以是id的值,也可以是name的值(也就是说使用别名还可以再次重新命名),那么在程序中我们通过ApplicationContext对象或者BeanFactory的get方法获取bean对象的时候,就可以使用dog,dog1,dog2,dog3,dog4这4中标识中的任意一个来获取bean对象,获取到的bean对象都是同一个对象。

class是Bean的全限定名。

配置Bean的属性,name表示属性名,这个属性实际上是javaBean的setter方法,所以配置的类必须符合javaBean的规范。

④<property>节点可以通过ref属性引用其他已经配置的Bean,ref的值是其他已经配置Bean的标识。如果这个属性的值是基本数据类型或者是String类型,只要该属性具有setter访问器,就可以使用value直接设置值。

4.2 Bean的作用域(scope)

​ 配置文件中的Bean实例化后,该如何保存,就是Bean的作用域问题。比如:默认的作用域是singleton,表示对应Bean在容器中是单例的,整个系统只保存一份实例,实例化后即保存起来,直到系统结束才销毁,期间所有线程共享一份实例。Bean的作用域使用<bean>节点的scope属性来表示。

​ scope:指对象的作用范围,取值如下:

取值范围说明
singleton默认值,单例的
prototype多例的
requestWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
sessionWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
global sessionWEB 项目中,应用在 Portlet 环境,没有 Portlet 环境globalSession 相当于session

重点是singleton与prototype两个作用域:

1)当scope的取值为singleton时

Bean的实例化个数:1个

Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例

Bean的生命周期:

  • 对象创建:当应用加载,创建容器时,对象就被创建了

  • 对象运行:只要容器在,对象一直活着

  • 对象销毁:当应用卸载,销毁容器时,对象就被销毁了

2)当scope的取值为prototype时

Bean的实例化个数:多个

Bean的实例化时机:当调用getBean()方法时实例化Bean

  • 对象创建:当使用对象时,创建新的对象实例

  • 对象运行:只要对象在使用中,就一直活着

  • 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

public class HelloImpl implements IHello {
    public String sayHello(String word) {
        return "hello:"+word;
    }

    public HelloImpl() {
        System.out.println("HelloImpl实例进行初始化....");
    }
}
<bean class="com.suke.hello.impl.HelloImpl" id="hello" scope="singleton"></bean>

测试代码:

 @Test
    public void testScope(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        IHello hello = applicationContext.getBean("hello", IHello.class);

        IHello hello2 = applicationContext.getBean("hello", IHello.class);
        System.out.println("是否是同一个对象:"+(hello == hello2));
    }

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uhUUQd0L-1676534158959)(assets/image-20220731133248507.png)]

现在我们把HelloImpl的bean设置为prototype,再来测试,看控制台的效果:

<bean class="com.suke.hello.impl.HelloImpl" id="hello" scope="prototype"></bean>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fH2FC8ZF-1676534158959)(assets/image-20220731133403626.png)]

4.3 Bean的生命周期的配置

我们可以在Bean标签中使用init-method属性和destroy-method属性对bean的生命周期进行相关配置:

  • init-method:指定类中的初始化方法名称

init-method用于指定bean的初始化方法。 我们知道spring会帮我们实例化对象,实例化对象之后,spring就会查找我们是否配置了init-method如果配置了,spring就会调用我们配置的initmethod方法,进行bean的初始化。

  • destroy-method:指定类中销毁方法名称

destroy-methodinit-method一样,只是它是用来配置释放资源的方法,spring会在销毁当前bean对象之前调用destroy-method制定的方法。

package com.suke.hello.impl;

import com.suke.hello.IHello;

public class HelloImpl implements IHello {
    public String sayHello(String word) {
        return "hello:"+word;
    }

    public HelloImpl() {
        System.out.println("HelloImpl实例进行初始化....");
    }

    public void init(){
        System.out.println("哈哈,我出生了...");
    }

    public void destroy(){
        System.out.println("555,我要走了...");
    }
}

<bean class="com.suke.hello.impl.HelloImpl" id="hello"  init-method="init" destroy-method="destroy"></bean>

测试代码:

@Test
public void testLife(){
    AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    IHello hello = applicationContext.getBean("hello", IHello.class);
    hello.sayHello("spring");
    applicationContext.close();
}

测试效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mHha4Tu3-1676534158960)(assets/image-20220731134337719.png)]

4.4 Spring Bean 的加载流程概述

​ Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。

​ springBean的加载过程大致分为两个过程:解析注册,实例化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FShArxHw-1676534158960)(assets/image-20220731140023990.png)]

​ spring先解析xml文件或者注解配置,读取所有要加载类信息。根据类信息创建对应的BeanDefinition对象,再根据Beandefination对象创建实例对象。

​ BeanDefinition是SpringBean的描述对象,主要封装了如下信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJRloiw8-1676534158961)(assets/image-20220731140253260.png)]

Spring通过BeanDefinition来进行bean的实例化, 实例化的bean存在BeanFactory的singletonObjects中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kuyD4QNP-1676534158961)(assets/image-20220731140511584.png)]

4.5 依赖注入属性配置

4.5.1 依赖注入二种方式

​ 在DI的案例中,我们在UserService中是怎么把UserDao注入进来的呢?其实在Spring中提供了两种注入方式:

  • Set方法的注入
  • 构造方法的注入
4.5.1.1 属性setter方法注入

通过 <property>元素,实现属性setter方法注入

package com.suke.injection;

public class Dog {
    private String name;

    public String getName() {
        return name;
    }

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

package com.suke.injection;

public class Person {
    private String name;
    private Dog dog;

    public String getName() {
        return name;
    }

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

    public Dog getDog() {
        return dog;
    }

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

配置:

 <bean id="d1" class="com.suke.injection.Dog">
     <property name="name" value="旺财"/>
 </bean>

我们在使用property和constructor-arg为bean注入属性时,如果属性是简单类型,我们可以通过value直接注入。

这里简单类型主要是指java的基本类型和String型

<bean id="p1" class="com.suke.injection.Person">
    <property name="name" value="张三"/>
    <property name="dog" ref="d1"/>
</bean>

当我们通知spring帮我们注入某个引用对象时,我们可以使用ref通知spring注入bean的beanName

4.5.1.2 P命名空间使用

​ Spring2.5 版本之后,为了简化属性setter依赖注入,提供虚拟名称空间 p ,使用步骤:

  1. 在spring的配置文件中<beans>标签引入p命令空间

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qEueiE0-1676534158962)(assets/image-20220731172649959.png)]

  2. 使用p命令空间

    <bean id="d2" class="com.suke.injection.Dog">
            <property name="name" value="来福"/>
    </bean>
    <bean id="p2" class="com.suke.injection.Person" p:name="李四" p:dog-ref="d2"></bean>
    
    
4.5.1.3 构造方法注入

​ 通过 <constructor-arg> 进行构造器参数注入

​ 我们分别在Dog类和Person类提供了无参和有参的构造方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A28sJicV-1676534158962)(assets/image-20220731173131884.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-atUbG7e6-1676534158963)(assets/image-20220731173159237.png)]

配置:

<bean id="d3" class="com.suke.injection.Dog">
    <constructor-arg name="name" value="大黄"/>
</bean>

<bean id="p3" class="com.suke.injection.Person">
    <constructor-arg name="name" value="王五"/>
    <constructor-arg name="dog" type="com.suke.injection.Dog" index="1" ref="d3"/>
</bean>

<constructor-arg>标签属性说明:

  • name: 属性名
  • type: 属性类型
  • index: 参数索引(从0开始)
4.5.2 集合属性的注入

​ 某些类的属性是可能是集合,包括:数组LISTMAPSETPROPERTIES等集合,在Spring中同样可以使用XML配置文件的方式对属性进行注入。

主要用于 参数配置 !

  • 数组或者List ----- <list>或者 <array>注入

  • Set ---- <set> 注入

  • Map — <map> <entry> 注入

  • Properties — <props> <prop> 注入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nZ1caIew-1676534158963)(assets/image-20220731181126728.png)]

配置:

  <bean id="p4" class="com.suke.injection.Person">
        <property name="list">
            <list>
                <value>aaa</value>
                <value>bbb</value>
                <value>aaa</value>
                <value>ccc</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>aaa</value>
                <value>bbb</value>
                <value>aaa</value>
                <value>ccc</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="abc" value="123"></entry>
                <entry key="bcd" value="456"></entry>
                <entry key="efg" value="567"></entry>
            </map>
        </property>
        <property name="props">
            <props>
                <prop key="zhangsan">张三</prop>
                <prop key="lisi">李四</prop>
                <prop key="wangwu">王五</prop>
            </props>
        </property>
    </bean>

5. Spring IoC 容器配置-注解方式

5.1 注解定义Bean对象

  1. 在Bean class 添加 注解

    Spring2.5 提供 @Component 效果相当于 <bean> 元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2jBwWUXr-1676534158964)(assets/image-20220731182606575.png)]

  1. 配置包扫描,通知spring 注解Bean 在哪个包下面

    使用 <context> 命名空间 ,在spring的配置文件中添加context命令空间

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQoaohgC-1676534158964)(assets/image-20220731182821091.png)]

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

    Spring 容器提供@Component 等效三个衍生注解

    @Repository 用于注册DAO(持久层 )

    @Service 用于注册 Service(业务层)

    @Controller 用于注册 Action (表现层)

5.2 注解实现属性依赖注入

通过上一步,我们已经把UserDaoImpl和UserServiceImpl使用对应的注解,交给Spring管理了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X7IqemiE-1676534158964)(assets/image-20220731183102098.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bSq7GTBl-1676534158965)(assets/image-20220731183120024.png)]

但是在我们的UserServiceImpl类中依赖UserDao, 那我们需要使用属性注入的注解把UserDao注入到UserServiceImpl类:

5.2.1 注入基本类型属性

简单类型属性注入,只有变量才有意义 !

Spring3.0之后,通过@Value注解 结合 spEL 表达式实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wHsOSZ21-1676534158965)(assets/image-20220731183354510.png)]

5.2.2 注入类类型的属性
  • Spring3.0提供@Value注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arq82yfb-1676534158966)(assets/image-20220731183503395.png)]

  • Spring2.0 提供@Autowired 注解 结合 @Qualifier 注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqgJWIZ2-1676534158966)(assets/image-20220731183639589.png)]

或者:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XakLwo0r-1676534158967)(assets/image-20220731183706389.png)]

  • 单独使用@Autowired,表示按照类型匹配注入
  • @Autowired结合@Qualifier(“bean名称”),表示按照bean的name进行匹配注入
  • JSR-250规范 提供 @Resource 注解实现注入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0vmOZbI-1676534158967)(assets/image-20220731183920943.png)]

或者:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQNZOrT9-1676534158967)(assets/image-20220731183958350.png)]

  • @Resource没有写name,表示按照类型匹配注入
  • @Resource写name,表示按照bean的name进行匹配注入
  • JSR-330规范 提供 @Inject @Named 注解

    需要导入javax.inject 的依赖:

javax.inject javax.inject 1 ```

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KxsfGHdX-1676534158968)(assets/image-20220731184238641.png)]

或者:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZeakdD0Q-1676534158968)(assets/image-20220731184301148.png)]

  • 只使用@Inject注解,表示按照类型匹配注入
  • @Inject配合@Named,表示按照bean的name进行匹配注入

5.3 Bean的作用域注解

<bean> 提供 scope属性,用于描述Bean的作用域

Spring提供@Scope注解,描述Bean的作用域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TCSPOAhy-1676534158968)(assets/image-20220731184532326.png)]

默认scope作用域 singleton !

6. 在web开发中使用spring框架

  1. 创建一个web项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAgRkWgb-1676534158969)(assets/image-20220801192406700.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1l7BMiR-1676534158969)(assets/image-20220801192505321.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7JL9dpb9-1676534158969)(assets/image-20220801192525606.png)]

  1. 导入相关依赖
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>spring-webdemo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>spring-webdemo Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.3.21</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!--spring-web-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!-- servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!-- jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <!--log4j-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.36</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
    </dependency>

    <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
    </dependency>
  </dependencies>


  <build>
    <finalName>spring-webdemo</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
  1. 在web.xml文件中配置监听器,用于监听web服务器的启动, 创建Spring容器

    <!--spring监听器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    

    如果没有配置监听器,那么Spring默认加载 WEB-INF/applicationContext.xml,而我们的spring的配置文件是位于

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5azpNQWj-1676534158970)(assets/image-20220801194632314.png)]

​ 使用<context-param>配置spring的配置文件路径:

<!--指定spring的配置文件-->
<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>classpath:applicationContext.xml</param-value>
</context-param>

通过上面配置的spring的监听器,那么在tomcat启动时,spring工厂进行创建 !

  1. 创建Servlet,在Servlet中获取Spring容器对象,通过getBean()获取service层的对象
package com.suke.web;

import com.suke.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "UserServlet",value = "/userServlet")
public class UserServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.第一种方式:
        //ApplicationContext applicationContext = (ApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        //2.第二种方式
        ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());

        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.addUser();
    }
}

7. AOP 面向切面编程

7.1 AOP概述

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

​ 面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5mHLsPYl-1676534158970)(assets/image-20220801200813598.png)]

​ AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。

7.2 AOP的作用

​ AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

​ 主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。

​ 简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wAvP7ssM-1676534158971)(assets/aop.png)]

7.3 AOP应用场景

应用场景:

  • 事务管理

  • 记录日志

  • 监测性能(统计方法运行时间)

  • 权限控制

  • 缓存

主要目的:

​ 将日志记录,性能统计,安全控制,事务处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

7.4 Spring AOP的术语

  • **Joinpoint(连接点)😗*所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点.

  • **Pointcut(切入点)😗*所谓的切入点是指我们要对哪些Joinpoint进行拦截的定义

  • **Advice(通知/增强)😗*所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的事情)

  • **Introduction(引介)😗*引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field

  • Target代理的目标对象

  • **Weaving(织rget(目标):入)😗*是指把增强应用到目标对象来创建的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

  • **Proxy(代理)😗*一个类被AOP织入增强后,就产生一个结果代理类

  • **Aspect(切面)😗*是切入点和通知(引介)的结合

建议采用图解方式理解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdrl8RSB-1676534158971)(assets/image-20220801201457318.png)]

7.5 基于AspectJ的AOP编程

​ Spring AOP通知分类:

通知类型说明
before(前置通知)通知方法在目标方法调用之前执行
after(后置通知)通知方法在目标方法返回或异常后调用
after-returning(返回后通知)通知方法会在目标方法正常执行,返回后调用
after-throwing(抛出异常通知)通知方法会在目标方法抛出异常后调用
around(环绕通知)通知方法会将目标方法包装起来
7.5.1 AspectJ各种通知编程案例(xml方式 )
  1. 导入依赖 spring-aop spring-aspects (整合AspectJ) AspectJ的框架依赖
 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.15.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.15.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
  1. 编码

    编写各种通知的日志记录类

    在所有增强方法中,都可以接受JoinPoint类型连接点参数对象,可以获取当前增强是哪个类的哪个方法,还可以获取调用目标方法传递参数

package org.aop.aspectJ.logger;

import java.sql.SQLException;
import java.util.Arrays;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
@Slf4j
public class UserBizLogger {
	/**
	 * 前置增强
	 * @param jp
	 */
	public void before(JoinPoint jp){
		log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
				+"方法,传递的参数:"+Arrays.toString(jp.getArgs()));
	}
	
	/**
	 * 后置增强
	 * @param jp
	 * @param result  返回值
	 */
	public void afterReturning(JoinPoint jp,Object result){
		log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
				+"方法,方法的返回值:"+result);
	}
	
	/**
	 * 异常增强
	 * @param jp
	 * @param e 增强的异常类型
	 */
	public void afterThrowing(JoinPoint jp,SQLException e){
		log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
				+"方法发生异常"+e);
	}
	
	/**
	 * 最终增强
	 * @param jp
	 */
	public void after(JoinPoint jp){
		log.info(jp.getSignature().getName()+"方法执行结束");
	}
	
	/**
	 * 环绕增强
	 * @param jp
	 * @throws Throwable
	 */
	public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable{
		log.info(jp.getSignature().getName()+"方法开始执行");
		try {
			//执行目标方法
			Object rs = jp.proceed();
			log.info(jp.getSignature().getName()+"方法正常执行完");
			return rs;
		} catch (SQLException e) {
			log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
					+"方法发生异常"+e);
			throw e;
		}
	}
}

  1. 配置切入点和切面

    引入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:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        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/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FEWGtwYp-1676534158972)(assets/image-20220801202632184.png)]

    配置:

    <!-- 配置增强方法所在的Bean -->
    	<bean id="userBizLogger" class="org.aop.aspectJ.logger.UserBizLogger"></bean>
    	
    	<!-- 配置AspectJ的AOP -->
    	<aop:config>
    		<aop:pointcut expression="execution( * org.aop.service..*.*(..))" id="pointcut"/>
    		<!-- 引用增强方法的bean -->
    		<aop:aspect ref="userBizLogger">
    			<!-- 
    				前置增强, method: 前置增强的方法
    			 -->
    			<aop:before method="before" pointcut-ref="pointcut"/>
    			<!-- 
    				后置增强
    					returning: 返回值,对应方法参数
    			 -->
    			<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
    			<!-- 
    				异常增强
    					throwing: 异常的类型, 对应方法的异常参数
    			 -->
    			<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
    			<!-- 
    				最终增强
    			 -->
    			<aop:after method="after" pointcut-ref="pointcut"/>
    			<!-- 
    				环绕增强
    			 -->
    			<aop:around method="aroundLogger" pointcut-ref="pointcut"/>
    		</aop:aspect>
    	</aop:config>
    

    切入点说明

    execution(修饰符? 返回值 方法名(参数) 异常?)

    • execution(* *(…)) 匹配所有spring管理对象所有方法, 第一个*任意返回值 ,第二个*任意方法, … 任意参数

    • execution(* org.suke.spring…*.*(…) ):匹配org.suke.spring包及其子包所有类的所有方法

    • execution(* org.suke.spring.*.*(…)) 匹配org.suke.spring包中所有对象所有方法

    • execution(* org.suke.spring.UserService.s*(…)) 匹配org.suke.spring包UserService中s开头方法

    • execute( public * addUser(entity.User)) 匹配addUser方法,返回值任意,参数为entity包User对象

    • execute( public void *(entity.User)) 匹配返回值为void,参数为entity包User的所有方法

    • execute( public void addUser(…)) 匹配返回值为void,参数任意的addUser方法

    测试效果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mFWrif3t-1676534158972)(assets/image-20220801202908182.png)]

7.5.2 AspectJ AOP注解编程

编写增强类:

package org.aop.aspectJ.logger;

import java.sql.SQLException;
import java.util.Arrays;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect  //把该类定义为切面类
public class UserBizLogger {
	private static final Logger log =Logger.getLogger(UserBizLogger.class);
	
	/**
	 * 前置增强
	 * @param jp
	 */
	@Before("execution( * org.aop.service..*.*(..))")  //使用@Before 定义该方法为前置增强
	public void before(JoinPoint jp){
		log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
				+"方法,传递的参数:"+Arrays.toString(jp.getArgs()));
	}
	
	/**
	 * 后置增强
	 * @param jp
	 * @param result  返回值
	 */
	//使用@AfterReturning 定义该方法为后置增强方法
	@AfterReturning(pointcut="execution( * org.aop.service..*.*(..))",returning="result")
	public void afterReturning(JoinPoint jp,Object result){
		log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
				+"方法,方法的返回值:"+result);
	}
	
	/**
	 * 异常增强
	 * @param jp
	 * @param e 增强的异常类型
	 */
	//使用@AfterThrowing 定义该方法为异常增强
	@AfterThrowing(pointcut="execution( * org.aop.service..*.*(..))",throwing="e")
	public void afterThrowing(JoinPoint jp,SQLException e){
		log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
				+"方法发生异常"+e);
	}
	
	/**
	 * 最终增强
	 * @param jp
	 */
	//使用@After 定义该方法为最终增强
	@After("execution( * org.aop.service..*.*(..))")
	public void after(JoinPoint jp){
		log.info(jp.getSignature().getName()+"方法执行结束");
	}
	
	/**
	 * 环绕增强
	 * @param jp
	 * @throws Throwable
	 */
	//使用@Around 定义该方法为环绕增强
	@Around("execution( * org.aop.service..*.*(..))")
	public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable{
		log.info(jp.getSignature().getName()+"方法开始执行");
		try {
			//执行目标方法
			Object rs = jp.proceed();
			log.info(jp.getSignature().getName()+"方法正常执行完");
			return rs;
		} catch (SQLException e) {
			log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
					+"方法发生异常"+e);
			throw e;
		}
	}
}

配置:

<!-- 配置增强方法所在的Bean -->
<bean id="userBizLogger" class="org.aop.aspectJ.logger.UserBizLogger"></bean>
<!-- 启动对于AspectJ的注解支持 -->
<aop:aspectj-autoproxy/>

8. Spring与Mybatis整合

​ 由于Spring框架提供了对象的管理,切面编程等非常实用的功能,如果把mybatis的对象交给Spring容器进行解偶合管理,不仅能大大增强系统的灵活性,便于功能扩展,还能通过Spring提供的服务简化编码,减小开发工作量,提高开发效率.实现整合的主要工作就是把Mybatis中的对象配置到Spring容器中,交给Spring来管理.比如说Mapper对象,以及业务层的事物管理。

8.1 基于xml的配置方式

  1. 导入相关依赖

    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>org.example</groupId>
      <artifactId>spring-webdemo</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
    
      <name>spring-webdemo Maven Webapp</name>
      <!-- FIXME change it to the project's website -->
      <url>http://www.example.com</url>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.2.15.RELEASE</spring.version>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>${spring.version}</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>${spring.version}</version>
        </dependency>
        <!--spring-web-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>${spring.version}</version>
        </dependency>
        <!-- servlet-api -->
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <!-- jsp-api -->
        <dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>jsp-api</artifactId>
          <version>2.1</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
        </dependency>
    
        <!--log4j-->
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>1.7.36</version>
        </dependency>
    
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.24</version>
        </dependency>
    
        <dependency>
          <groupId>javax.inject</groupId>
          <artifactId>javax.inject</artifactId>
          <version>1</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aop</artifactId>
          <version>5.3.21</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.3.21</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.3.21</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>5.3.21</version>
        </dependency>
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.7</version>
        </dependency>
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>2.0.6</version>
        </dependency>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.29</version>
        </dependency>
        <dependency>
          <groupId>com.mchange</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.5.2</version>
        </dependency>
    
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.6</version>
        </dependency>
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.75</version>
        </dependency>
      </dependencies>
    
    
      <build>
        <finalName>spring-webdemo</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
          <plugins>
            <plugin>
              <artifactId>maven-clean-plugin</artifactId>
              <version>3.1.0</version>
            </plugin>
            <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
            <plugin>
              <artifactId>maven-resources-plugin</artifactId>
              <version>3.0.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.0</version>
            </plugin>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>2.22.1</version>
            </plugin>
            <plugin>
              <artifactId>maven-war-plugin</artifactId>
              <version>3.2.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-install-plugin</artifactId>
              <version>2.5.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-deploy-plugin</artifactId>
              <version>2.8.2</version>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </project>
    
    1. 编写User实体类和UserMapper接口,以及UserService,UserServlet

      package com.suke.pojo;
      import lombok.Data;
      @Data
      public class User {
          private Integer id;
          private String name;
          private String gender;
          private Integer age;
          private String address;
          private String email;
          private String qq;
          private String photo;
      
      }
      
      package com.suke.mapper;
      
      import com.suke.pojo.User;
      
      public interface UserMapper {
          public void insert(User user);
          public void delete(int id);
          public User selectById(int id);
      }
      
      package com.suke.service;
      
      import com.suke.pojo.User;
      
      public interface UserService {
          public void addUser(User user);
      
          public void deleteUser(Integer uid);
      
          public User queryById(Integer uid);
      }
      
      
      package com.suke.service.impl;
      
      import com.suke.mapper.UserMapper;
      import com.suke.pojo.User;
      import com.suke.service.UserService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Scope;
      import org.springframework.stereotype.Service;
      
      @Service("userService")
      public class UserServiceImpl implements UserService {
          @Autowired
          private UserMapper userMapper;
      
          @Override
          public void addUser(User user) {
              userMapper.insert(user);
          }
      
          @Override
          public void deleteUser(Integer uid) {
              if(uid == null){
                 throw new RuntimeException("id不能为空!");
              }
              userMapper.delete(uid);
          }
      
          @Override
          public User queryById(Integer uid) {
              if(uid == null){
                  throw new RuntimeException("id不能为空!");
              }
              return userMapper.selectById(uid);
          }
      }
      
      package com.suke.web;
      
      import com.alibaba.fastjson.JSON;
      import com.suke.pojo.User;
      import com.suke.service.UserService;
      import org.springframework.context.ApplicationContext;
      import org.springframework.web.context.WebApplicationContext;
      import org.springframework.web.context.support.WebApplicationContextUtils;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.io.PrintWriter;
      
      @WebServlet(name = "UserServlet",value = "/userServlet")
      public class UserServlet extends HttpServlet {
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
          }
      
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
      
              UserService userService = applicationContext.getBean("userService", UserService.class);
              int id = Integer.parseInt(request.getParameter("id"));
              User user = userService.queryById(id);
              response.setContentType("application/json;charset=UTF-8");
              PrintWriter out = response.getWriter();
              String json = JSON.toJSONString(user);
              out.print(json);
              out.flush();
              out.close();
      
          }
      }
      
      1. 在src/main/resource导入spring,log4j等配置文件

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DzKWZ1p9-1676534158973)(assets/image-20220804222021994.png)]

log4j.properties

log4j.rootCategory=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
  1. 配置SqlSessionFactory

    ​ 使用Spring整合Mybatis,首先应将诸如JDBC DataSource 或者MyBatis的SqlSessionFactory等数据访问资源以Bean的形式定义在Spring容器中,交由Spring容器进行管理.

​ 1)把jdbc的参数配置到一个properties的文件,便于修改:

db.properties

jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.user=root
jdbc.password=123

#当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3
jdbc.acquireIncrement=5
#初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3
jdbc.initialPoolSize=5
#连接池中保留的最小连接数。
jdbc.minPoolSize=3
#连接池中保留的最大连接数。Default: 15
jdbc.maxPoolSize=10

​ 因为我们在spring的配置文件需要配置大量的bean,为了方便管理,我们把spring的配置文件拆分成几个文件: applicationnContext-mapper.xml和applicationnContext-tx.xml, 然后在applicationnContext.xml使用import导入上面两个配置文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0SK113go-1676534158973)(assets/image-20220804222705747.png)]

<import resource="applicationContext-mapper.xml"/>
<import resource="applicationContext-tx.xml"/>

然后在 applicationnContext-mapper.xml配置与mybatis相关的配置:

<?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:p="http://www.springframework.org/schema/p"
       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">


    <!--加载properties
      如果是多个properties文件, 文件路径与文件路径使用逗号分割   classpath:db.properties,classpath:log4j.properties
    -->
    <context:property-placeholder location="classpath:db.properties" file-encoding="UTF-8"/>
      <!--配置一个C3p0数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--数据库四大参数-->
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <!--连接池相关的参数-->
        <!--初始连接数-->
        <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
        <!--增量, 每次创建几个连接-->
        <property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
        <!--最大连接数-->
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
        <!--最小连接数 默认值等于初始连接数-->
        <property name="minPoolSize" value="${jdbc.minPoolSize}"/>
    </bean>

    <!--配置SqlSessionFactoryBean-->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--加载mybatis全局配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--配置别名-->
        <property name="typeAliasesPackage" value="com.suke.pojo"/>
        <!--加载sql映射文件  * 任意  一定要注意: classpath*: 如果不写, 后面*无效-->
        <property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"/>
    </bean>


    <!--配置mapper扫描器 扫描指定包下所有的Mapper接口
       创建Mapper接口代理对象, sqlSession.getMapper()并且保存到Spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--扫描那个包下Mapper接口-->
         <property name="basePackage" value="com.suke.mapper"/>
        <!--注入SqlSessionFactory-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
    </bean>

</beans>

我们导入mybatis的主配置文件,虽然我们不会在该配置文件添加配置,但是还是推荐保留:

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration SYSTEM "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration>
 		<!-- 全局的setting配置 -->
 		
 		<!-- 全局的别名配置 -->
	
		<!--映射文件  -->
</configuration>
  1. 编写UserMapper.xml的sql映射文件

    <?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.suke.mapper.UserMapper">
        <select id="selectById" resultType="User">
            select * from tb_userinfo where id = #{id}
        </select>
        
        <insert id="insert">
            <selectKey resultType="int" order="AFTER" keyProperty="id">
                SELECT LAST_INSERT_ID()
            </selectKey>
            insert into tb_userinfo(name,gender,age,address,email,qq,photo)
            values(#{name,jdbcType=VARCHAR},#{gender,jdbcType=VARCHAR},#{age,jdbcType=INTEGER},#{address,jdbcType=VARCHAR}
            ,#{email,jdbcType=VARCHAR},#{qq,jdbcType=VARCHAR},#{photo,jdbcType=VARCHAR})
        </insert>
    
        <delete id="delete">
            delete from tb_userinfo where id = #{id}
        </delete>
    
    </mapper>
    
    1. 在applicationContext-tx.xml配置声明式事务管理

      • PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24IzpkcM-1676534158974)(assets/image-20220820113609386.png)]

      注意:

      PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc

      或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager

      Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

      • TransactionDefinition

      TransactionDefinition 是事务的定义信息对象,里面有如下方法:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OohUMt1Q-1676534158974)(assets/image-20220820113751749.png)]

      • TransactionStatus

      ​ TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。

      ​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z3V20Hko-1676534158974)(assets/image-20220820113851176.png)]

      基于XML的声明式事务控制

      Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 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:p="http://www.springframework.org/schema/p"
             xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
      
      
          <!--开始扫描注解-->
          <context:component-scan base-package="com.suke"/>
      
          <!--配置声明式事务管理-->
          <!--配置一个事务管理器-->
          <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="dataSource"/>
          </bean>
      
          <!--配置事务通知-->
          <tx:advice id="txAdvice" transaction-manager="txManager">
              <!--配置事务策略: 对业务层的方法进行怎么样事务管理-->
              <tx:attributes>
                  <!-- 对业务层类的具体的方法的设置事务策略
                     name: 业务层类的方法名, 支持通配符 *
                     要求你业务层类的方法必须按照你配置和名字起名
                     查询方法的策略
                       read-only: 是否只读: true只读, false不只读 默认值, 查询效率,对查询方法设置只读,
                                  增删改方法,一定不能设置只读
                       propagation: 事务传播机制:
                          	REQUIRED:默认值,表示如果存在一个事务,则支持当前事务,如果当前没有事务,则开启一个新的事务.  增删改
                          	SUPPORTS:表示如果存在一个事务,则支持当前事务,如果当前没有事务,则按非事务方式执行, 查询
      
                      rollback-for="在那些异常下进行回滚"  默认值 RuntimeException
                                    rollback-for="java.lang.Exception" 所有异常都回滚
      
                      no-rollback-for="那些异常不回滚"
      
                      timeout: 超时 默认 -1 永不超时
                  -->
                  <tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
                  <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
                  <tx:method name="select*" read-only="true" propagation="SUPPORTS"/>
                  <tx:method name="add*" rollback-for="java.lang.Exception"/>
                  <tx:method name="update*" rollback-for="java.lang.Exception"/>
                  <tx:method name="del*" rollback-for="java.lang.Exception"/>
                  <tx:method name="*" rollback-for="java.lang.Exception"/>
              </tx:attributes>
          </tx:advice>
      
          <!--把事务通知类txAdvice与业务层的类方法进行织入: AOP的技术-->
           <aop:config>
               <!--配置切入点-->
               <aop:pointcut id="mypointcut" expression="execution(* com.suke.service..*.*(..))"/>
               <aop:advisor advice-ref="txAdvice" pointcut-ref="mypointcut"/>
           </aop:config>
      
      
      </beans>
      

      ​ 注意:我们可以通过tx:attributes子标签定制事务属性.事务属性通过tx:method标签进行设置,Spring支持对不同的方法设置不同的事务属性, tx:method标签中的name是必须的用于指定匹配的方法,这里需要对方法名进行约定,可以使用通配符 ( * ).其他属性均可选,用于指定具体的事务机制,这些属性解释如下:

      • propagation:事务传播机制:该属性可选值如下:
        • REQUIRED:默认值,表示如果存在一个事务,则支持当前事务,如果当前没有事务,则开启一个新的事务. 增删改
        • SUPPORTS:表示如果存在一个事务,则支持当前事务,如果当前没有事务,则按非事务方式执行, 查询
        • MANDATORY:表示如果存在一个事务,则支持当前事务,如果当前没有事务,则抛出异常
        • REQUIRES_NEW:表示总是开启一个新的事务,如果当前有一个事务,则将当前事务挂起,开启新事物执行方法.
        • NOT_SUPPORTED:表示总是以非事务方式执行.如果一个事务已经存在,则将这个存在的事务挂起,然后执行方法.
        • NEVER:表示总是以非事务方式执行.如果当前存在一个活动的事务,则抛出异常
        • NESTED:表示如果当前存在一个活动的事务,则创建一个事务作为当前事务的嵌套事务运行,如果没有当前事务,该取值与REQUIRED相同.

      注意:在实际开发中, REQUIRED能够满足大多数的事务需求,可以作为首选的事务传播行为.

      • isolation:事务隔离级别.即当前事务和其他事务的隔离程度,在并发事务处理的情况下需要考虑它的设置,该属性可选的值如下:
        • DEFAULT:默认值,表示使用数据库默认的事务隔离级别
        • READ_UNCOMMITTED:读未提交
        • READ_COMMITTED:读已提交 oracle
        • REPEATABLE_READ: 可重复读 mysql
        • SERIALIZABLE: 串行读
      • timeout:事务的超时时间,允许事务运行的最长时间,以秒作为单位,超过给定的时间自动回滚,防止事务执行时间过长而影响到系统性能.该属性需要底层的实现支持,默认为-1,表示不超时.
      • rollback-for: 回滚控制, 默认是只对RuntimeException异常进行回滚
      • read-only: 是否只读, 默认值:false, 如果是true: 表示只读
      1. 编写测试类

        package com.suke.service;
        
        import com.suke.pojo.User;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.test.context.ContextConfiguration;
        import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
        
        import static org.junit.Assert.*;
        
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration("classpath:applicationContext.xml")
        public class UserServiceTest {
            @Autowired
            private UserService userService;
        
            @Test
            public void addUser() {
                User user = new User();
                user.setName("李琦");
                user.setGender("男");
                user.setAge(21);
                user.setAddress("北京");
                user.setEmail("liqi@163.com");
                user.setQq("2312311");
                userService.addUser(user);
                System.out.println(user.getId());
        
            }
        
            @Test
            public void deleteUser() {
                userService.deleteUser(6);
            }
        }
        
基于注解的声明式事务控制
  1. 在业务实现类的方法上添加@Transactional注解进行事务控制

    package com.suke.service.impl;
    
    import com.suke.mapper.UserMapper;
    import com.suke.pojo.User;
    import com.suke.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Service;
    
    @Service("userService")
    @Transactional
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void addUser(User user) {
            userMapper.insert(user);
        }
    
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void deleteUser(Integer uid) {
            if(uid == null){
               throw new RuntimeException("id不能为空!");
            }
            userMapper.delete(uid);
        }
    
        @Override
        @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
        public User queryById(Integer uid) {
            if(uid == null){
                throw new RuntimeException("id不能为空!");
            }
            return userMapper.selectById(uid);
        }
    }
    
  2. 编写 applicationContext.xml 配置文件

    <!--事务的注解驱动-->
    <tx:annotation-driven/>
     <!--配置事务管理器-->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    

e(“李琦”);
user.setGender(“男”);
user.setAge(21);
user.setAddress(“北京”);
user.setEmail(“liqi@163.com”);
user.setQq(“2312311”);
userService.addUser(user);
System.out.println(user.getId());

         }
     
         @Test
         public void deleteUser() {
             userService.deleteUser(6);
         }
     }
     ```
基于注解的声明式事务控制
  1. 在业务实现类的方法上添加@Transactional注解进行事务控制

    package com.suke.service.impl;
    
    import com.suke.mapper.UserMapper;
    import com.suke.pojo.User;
    import com.suke.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Service;
    
    @Service("userService")
    @Transactional
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void addUser(User user) {
            userMapper.insert(user);
        }
    
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void deleteUser(Integer uid) {
            if(uid == null){
               throw new RuntimeException("id不能为空!");
            }
            userMapper.delete(uid);
        }
    
        @Override
        @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
        public User queryById(Integer uid) {
            if(uid == null){
                throw new RuntimeException("id不能为空!");
            }
            return userMapper.selectById(uid);
        }
    }
    
  2. 编写 applicationContext.xml 配置文件

    <!--事务的注解驱动-->
    <tx:annotation-driven/>
     <!--配置事务管理器-->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值