Spring

框架的作用:解耦和(降低耦性)

形成生态(让更多的人用或者使用的人很多,说明这个东西很好)

一、spring初识

1.1、框架

人话:

举个例子,现在的楼盘都相当于毛坯房,我们买到的毛坯房都是一模一样的,这个毛坯房就相当于框架,我们可以添加东西,让他具备一定的风格。不同的人有不同的装修风格,所以毛坯房都一样,但是装修风格不同,所得到的成品就不同,简单来说,就相当一件房子的模型已经固定好了,你可以随意往里面添加东西。看自己具体怎么设计。

切换到我们所编写的应用来说,所依赖的固定的东西,别人已经给定义好了,给提供好了架子,自己只需要拿这个架子来往里面添加东西,实现我们的功能即可。

1.2、技术架构演变

  1. 单一应用架构

    当访问量很小,只有几十人或者几百人来访问这个应用的话,我们可以把所有的功能都部署在一起,比如初学Javase时写的小demo。耦合度很高。

    ​ 这种架构只适合小规模的访问量,不适合大型的互联网架构,当访问量变大时,就不适合,这种架构单点容错低,并发能力差。代码耦合度高,开发维护困难。

  2. 垂直应用架构

    当访问量变大时,就需要分不同的层级,来解耦合,让耦合度降低,方便增改程序,例如:SSM

  1. 分布式架构

    当垂直应用越来越多,应用之间交互越发频繁,把核心的业务抽取出来作为独立的服务,逐渐形成稳定的服务中心

  1. 流动计算架构(SOA)

    服务越来越多、容量的评估和小服务资源浪费的问题逐渐出现,此时需要增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。

    用户提高机器利用率的资源调度和治理中心(SOA)是关键。

    以前出现了什么问题?

    • 服务越来越多,需要管理每个服务的地址。
    • 调用关系错综复杂,难以理清依赖关系。
    • 服务过多,服务状态难以理解,无法根据服务情况动态管理。

    服务治理要做什么?

    • 服务注册中心:实现服务自动注册和发现,无需人为记录服务地址。
    • 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系。
    • 动态监控服务状态监控报告,人为控制服务状态。

    缺点:

    • 服务间会有依赖关系,一旦某个环节出错会影响很大。(形成雪崩
    • 服务关系复杂,运维、测试部署困难,不符合DevOps思想。
  2. 微服务架构

微服务特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责。
  • :微服务的服务拆分粒度很小。例如:一个用户管理就可以作为一个服务。
  • 面向服务:每个服务都要对外暴露Rest风格服务接口API。不关心服务的技术实现,做到与平台和语言无关,不限定技术实现,只提供Rest的接口即可。

1.3、java主流框架演变之路

  1. JSP+Servlet+JavaBean(实体类)
  2. MVC三层架构

model view controller

browser:浏览器,发送http请求,和接收处理完的响应

controller:控制层,接收浏览器发送的请求,交由model层进行处理

model:主要是用来操作数据库,具体是dao层data access object 数据访问对象

view:视图层,前端的东西html,css,js把返回的结果数据集合进行装饰一下,变得好看,再返回给controller,controller返回响应给浏览器

  1. SSH

  2. SSM (spring + SpringMVC + mybatis)

  3. springboot(约定大于配置)

二、Spring官网及版本

1.官网

  https://spring.io/

2.版本命名规则

spring版本命名规则

标识 说明 含义
Snapshot 快照版 尚不稳定、处于开发中的版本
Release 稳定版 功能相对稳定,可以对外发行,但有时间限制
GA 正式版 代表广泛可用的稳定版
M 里程碑版 具有一些全新的功能或者是具有里程碑意义的版本
RC 终测版 作为正式版发布


附:通用命名规则,如 10.0.1

序号 说明
x 表示主版本号(Major),当API的兼容性变化时(参数个数、类型变化,返回值改变等),x递增
y 表示次版本号(Minor ),增加功能时(不影响原有API的兼容性),y递增
z 表示修订号(Patch),修复现有API的bug或优化性能时(不影响API的兼容性),z递增

补充:CURRENT 表示当前最新的版本

官网地址:https://spring.io/projects/spring-framework#overview

压缩包下载地址:https://repo.spring.io/release/org/springframework/spring/

源码地址:https://github.com/spring-projects/spring-framework

3.核心解释

Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs. As of Spring Framework 5.1, Spring requires JDK 8+ (Java SE 8+) and provides out-of-the-box support for JDK 11 LTS. Java SE 8 update 60 is suggested as the minimum patch release for Java 8, but it is generally recommended to use a recent patch release.Spring supports a wide range of application scenarios. In a large enterprise, applications often exist for a long time and have to run on a JDK and application server whose upgrade cycle is beyond developer control. Others may run as a single jar with the server embedded, possibly in a cloud environment. Yet others may be standalone applications (such as batch or integration workloads) that do not need a server.Spring is open source. It has a large and active community that provides continuous feedback based on a diverse range of real-world use cases. This has helped Spring to successfully evolve over a very long time.Spring 使创建 Java 企业应用程序变得更加容易。它提供了在企业环境中接受 Java 语言所需的一切,,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并可根据应用程序的需要灵活地创建多种体系结构。 从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8+),并且已经为 JDK 9 提供了现成的支持。Spring支持各种应用场景, 在大型企业中, 应用程序通常需要运行很长时间,而且必须运行在 jdk 和应用服务器上,这种场景开发人员无法控制其升级周期。 其他可能作为一个单独的jar嵌入到服务器去运行,也有可能在云环境中。还有一些可能是不需要服务器的独立应用程序(如批处理或集成的工作任务)。Spring 是开源的。它拥有一个庞大而且活跃的社区,提供不同范围的,真实用户的持续反馈。这也帮助Spring不断地改进,不断发展。
  • spring是一个IOC和AOP的容器框架

    • IOC 控制反转
    • AOP 面向切面编程
    • 容器: 类似装水的桶,只不过这个桶里装的不再是水,而是对象(bean)

三、模块概览

一个大绿框是一个模块,大绿框里的小黑框是所依赖的核心jar包

Aspects要用,上图标错了,Transactions事务也要用

模块解释
Test:Spring的单元测试模块
Core Container:核心容器模块
AOP+Aspects:面向切面编程模块
Instrumentation:提供了class instrumentation支持和类加载器的实现来在特定的应用服务器上使用,几乎不用
Messaging:包括一系列的用来映射消息到方法的注解,几乎不用
Data Access/Integration:数据的获取/整合模块,包括了JDBC,ORM,OXM,JMS和事务模块
Web:提供面向web整合特性

1.核心模块

模块名称 主要功能
spring-core 依赖注入IOC与DI的最基本实现
spring-beans Bean工厂与Bean的装配 。
spring-context 定义基础的Spring的Context上下文,即IOC容器。
spring-context-support 对Spring IOC容器的扩展支持,以及IOC子容器。
spring-context-indexer Spring的类管理组件和ClassPath扫描。
spring-expression Spring表达式语言。spEl

2.切面编程模块

模块名称 主要功能
spring-aop 面向切面编程的应用模块,整合Asm,CGLib,JDKProxy 。
spring-aspects 集成AspectJ,AOP应用框架。
spring-instrument 动态Class Loading模块。

3.数据访问与集成模块

模块名称 主要功能
spring-jdbc Spring 提供的JDBC抽象框架的主要实现模块,用于简化Spring对JDBC的操作。
spring-tx Spring JDBC事务控制实现模块。
spring-orm 主要集成Hibernate,java Persistence API(JPA)和Java Data Objects(JDO)。
spring-oxm 将Java对象映射成XML数据,或将XML数据映射成java对象。
spring-jms Java Message Service能够发送和接收信息。

4.Web模块

模块名称 主要功能
spring-web 提供了最基础的Web支持,主要建立于核心容器之上,通过Servlet或者Listeners来初始化IOC容器。
spring-webmvc 实现了spring MVC(model-view-controller)的Web应用。
spring-websocket 主要是与Web前端的双工通讯协议。
spring-webflux 一个新的非阻塞函数式Reactive Web框架,可以用来建立异步的,非阻塞,事件驱动的服务。

5.通信报文模块

几乎不用

模块名称 主要功能
spring-messaging 从spring4开始新加入的一个模块,主要职责是为spring框架集成一些基础的报文传送应用。

6.测试模块

模块名称 主要功能
spring-test 为测试提供支持

7.兼容模块

模块名称 主要功能
spring-framework-bom bill of Materials.解决Spring不同模块依赖版本不同问题。

8.模块关系图

9.Spring各个jar包作用

Spring AOP:Spring的面向切面编程,提供AOP(面向切面编程)的实现
Spring Aspects:Spring提供的对AspectJ框架的整合
Spring Beans:Spring IOC的基础实现,包含访问配置文件、创建和管理bean等。
Spring Context:在基础IOC功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持。
Spring Context Support:Spring context的扩展支持,用于MVC方面。
Spring Core:Spring的核心工具包
Spring expression:Spring表达式语言
Spring Framework Bom:
Spring Instrument:Spring对服务器的代理接口
Spring Instrument Tomcat:Spring对tomcat连接池的集成
Spring JDBC:对JDBC 的简单封装
Spring JMS:为简化jms api的使用而做的简单封装
Spring Messaging:
Spring orm:整合第三方的orm实现,如hibernate,ibatis,jdo以及spring 的jpa实现
Spring oxm:Spring对于object/xml映射的支持,可以让JAVA与XML之间来回切换
Spring test:对JUNIT等测试框架的简单封装
Spring tx:为JDBC、Hibernate、JDO、JPA等提供的一致的声明式和编程式事务管理。
Spring web:包含Web应用开发时,用到Spring框架时所需的核心类,包括自动载入WebApplicationContext特性的类、Struts与JSF集成类、文件上传的支持类、Filter类和大量工具辅助类。
Spring webmvc:包含SpringMVC框架相关的所有类。包含国际化、标签、Theme、视图展现的FreeMarker、JasperReports、 Tiles、Velocity、XSLT相关类。当然,如果你的应用使用了独立的MVC框架,则无需这个JAR文件里的任何类。
Spring webmvc portlet:Spring MVC的增强
Spring websocket:提供 Socket通信, web端的推送功能

四、IOC(控制反转)

4.1、为什么要引入IOC

先看一个小例子:

创建一个普通的java项目,完成下述功能

UserDao.java

public interface UserDao {public void getUser();
}

UserDaoImpl.java

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

UserService.java

public interface UserService {public void getUser();
}

UserServiceImpl.java

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

SpringDemoTest.java

public class SpringDemoTest {public static void main(String[] args) {UserService service = new UserServiceImpl();service.getUser();}
}

在之前的代码编写过程中,我们都是这么完成我们的功能的,但是如果增加一个UserDao的实现类呢?

UserDaoMysqlImpl.java

public class UserDaoMysqlImpl implements UserDao {@Overridepublic void getUser() {System.out.println("mysql");}
}

如果我们想要使用mysql的话,那么就必须要修改UserServiceImpl.java的代码:

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

但是如果我们再增加一个oracle的类呢?

UserDaoOracleImpl.java

public class UserDaoOracleImpl implements UserDao {@Overridepublic void getUser() {System.out.println("oracle");}
}

此时UserService还是要继续修改,很显然这样的方式已经不适用于我们的需求了,那么怎么解决呢,可以使用如下的方式

UserServiceImpl.java

public class UserServiceImpl implements UserService {private UserDao userDao;//通过set方法来实现想调用UserDao接口的哪个实现类就set哪个实现类public void setUserDao(UserDao userDao){this.userDao = userDao;}@Overridepublic void getUser() {userDao.getUser();}}

测试类SpringDemoTest.java

public class SpringDemoTest {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();//把UserDaoMysql的实现类set进去userService.setUserDao(new UserDaoMysqlImpl());//set进去来进行调用userService.getUser();userService.setUserDao(new UserDaoOracleImpl());userService.getUser();}
}

其实从刚刚的代码中,大家应该能体会解耦的重要性了,下面我们就开始学习Spring的IOC。

IOC叫控制反转

首先需要搞清楚如下几个问题:

1.谁控制谁
2.控制的是什么
3.什么是反转
4.谁/哪些方面被反转

IOC基本概念

官方解释:

IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.IOC与大家熟知的依赖注入同理,. 这是一个通过依赖注入对象的过程 也就是说,它们所使用的对象,是通过构造函数参数,工厂方法的参数或者是从工厂方法的构造函数或返回值的对象实例设置的属性,然后容器在创建bean时注入这些需要的依赖。 这个过程相对普通创建对象的过程是反向的(因此称之为IoC),bean本身通过直接构造类来控制依赖关系的实例化或位置,或提供诸如服务定位器模式之类的机制。

人话:

说大白话就是对象的创建不再需要我们new了,让别人new好,我们直接调用。
举个例子:找女朋友,之前的办法是我们自己找,按照自己的标准,要什么样的,去找,找到了结婚,类似我们的new操作,我们现在不用自己找了,找个中介,婚介所去找,我们给婚介所提出我们所需要的条件(白的,黑的,胖的,瘦的)这些就相当于是属性,让婚介所给我们找好,直接结婚就行。这个中介(婚介所)就相当于是容器

这就是控制反转,不用自己new对象,提出自己的条件(就是类里的属性),类就是我们要找什么东西(比如:女人),由IOC容器创建好,放到容器里,我们直接取就行。

控制反转(IOC)是一种思想

现在回答上面的问题

1.谁控制谁  IOC容器控制对象(bean) (婚介所控制女孩)
2.控制的是什么  对象(bean) (女孩)
3.什么是反转 我们之前都是主动得去new,现在不用了,别人创建好了,我们直接拿,或者说叫被动的接受。 (婚介所帮我们找好)
4.谁/哪些方面被反转  对象的创建不用自己创建了  (女孩不用自己找了)

DI与IOC

DI:依赖注入,利用set方法来进行注入,IOC:控制反转,可以理解为一种思想,两者本质上还是有区别

IOC是一种思想,DI是这种思想的具体实现方式

总结

​ 在此处总结中,希望大家能够能够明白两件事: 解耦 和 生态

1、解耦

​ 在面向对象设计的软件系统中,底层的实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。

​ 需要注意的是,在这样的组合关系中,一旦某一个对象出现了问题,那么其他对象肯定回有所影响,这就是耦合性太高的缘故,但是对象的耦合关系是无法避免的,也是必要的。随着应用程序越来越庞大,对象的耦合关系可能越来越复杂,经常需要多重依赖关系,因此,无论是架构师还是程序员,在面临这样的场景的时候,都需要减少这些对象的耦合性。

​ 耦合的关系不仅仅是对象与对象之间,也会出现在软件系统的各个模块之间,是我们需要重点解决的问题。而为了解决对象之间的耦合度过高的问题,我们就可以通过IOC来实现对象之间的解耦,spring框架就是IOC理论最最广泛的应用。

​ 从上图中可以看到,当引入了第三方的容器之后,几个对象之间就没有了耦合关系,全部对象都交由容器来控制,这个容器就相当于粘合剂,将系统的对象粘合在一起发挥作用。

2、生态

​ 任何一个语言或者任何一个框架想要立于不败之地,那么很重要的就是它的生态。就是让更多的人用它。

4.2、使用

4.2.1、原始的使用

使用手动加载jar包的方式实现,分为三个步骤,现在几乎不用

  • 手动导入以下五个包

    commons-logging-1.2.jar
    spring-beans-5.2.3.RELEASE.jar
    spring-context-5.2.3.RELEASE.jar
    spring-core-5.2.3.RELEASE.jar
    spring-expression-5.2.3.RELEASE.jar

  • 写配置

    Person.java

    public class Person {private int id;private String name;private int age;private String gender;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", gender='" + gender + '\'' +'}';}
    }

    ioc.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"><!--注册一个对象,spring会自动创建这个对象--><!--一个bean标签就表示一个对象id:这个对象的唯一标识class:注册对象的完全限定名--><bean id="person" class="com.king.bean.Person"><!--使用property标签给对象的属性赋值name:表示属性的名称value:表示属性的值--><property name="id" value="1"></property><property name="name" value="zhangsan"></property><property name="age" value="18"></property><property name="gender" value=""></property></bean>
    </beans>
    
  • 测试

    SpringDemoTest.java

    //容器中的person对象是什么时候创建的?
    //容器中的对象在容器创建完成之前就已经把对象创建好了
    public class MyTest {public static void main(String[] args) {/** applicationContext:表示IOC容器的入口,想要获取对象的话,必须要创建该类*   该类有两个读取配置文件的实现类*       ClassPathXmlApplicationContext:表示从classpath中读取数据 放的是spring配置文件的位置*       FileSystemXmlApplicationContext:表示从当前文件系统读取数据,*** * */ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");//获取具体的bean实例对象,需要进行强制类型转换
    //        Person person = (Person) context.getBean("person");//获取对象的时候不需要强制类型转换
    //        Person person = context.getBean("person", Person.class);
    //        Person person = context.getBean(Person.class); 这种获取bean的方法适合容器中只有一个bean对象,如果有多个相同bean的对象,那么这种方法来获取就会报错
    //        System.out.println(person);}
    }

    容器中的person对象是什么时候创建的?

​ 容器中的对象在容器创建完成之前就已经把对象创建好了

4.2.2、使用maven来创建spring项目

  • 创建maven项目

  • 添加spring-context的依赖

    只需要导入spring-context的核心依赖包即可,因为ClassPathXmlApplicationContext是spring框架的入口,我们只需要导入这个类所属的包,其余所依赖的包会自动导入

    <dependencies>
    <!--        引入context依赖后,spring装配bean的核心包就全部被导进来了,因为context是spring框架的入口--><!-- https://mvnrepository.com/artifact/org.springframework/spring-context --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.2</version></dependency></dependencies>
    
  • 编写一个小demo,例子同上,此处就不再显示了

注意

  • 配置文件需要放在resources包下

总结

  • ApplicationContext是IOC容器的入口(接口),可以通过获取ApplicationContext的实体化对象(容器),来获取实体类对象。
  • 实体类bean什么时候被创建,默认是在IOC容器创建好之前就被创建好了,放在容器中。
  • bean的创建默认是单例的,就是只创建一个对象,谁用谁取,想要修改需要在xml文件中修改属性
  • bean属性值的注入,依赖set方法,所以,在bean中必须编写set方法
  • 对象的属性是由set方法后面的大写字母开头的那个单词决定的,而不是成员变量来决定
解释:property 属性  name的值是由set方法名后面的大写字母开头的单词决定的 例如:setAdddr
<property name="adddr" value="北京"/>

4.3、Spring对象的获取

两种方式

方式一:

通过bean的id来获取IOC容器中的对象

ApplicationContext context = new ClassPathXmlApplicationContext ( "springApplicationo.xml" );
//通过id来获取IOC容器对象,此种方法指明了获取的对象的类型
User user = context.getBean ( "user",User.class );
//这种方式需要进行强制类型转换,没有指明获取对象的类型
User user1 = (User)context.getBean ( "user" );

方式二:

通过bean的类型来获取对象

ApplicationContext context = new ClassPathXmlApplicationContext ( "springApplicationo.xml" );
//直接通过实体类的.class类型来获取对象
User user = context.getBean ( User.class );

但这种方法容易产生问题:

当我们在spring的配置文件中相同的bean注册了多次,例如:

 <bean id="user" class="com.king.pojo.User"><property name="age" value="20"/><property name="name" value="胡宗宪"/><property name="adddr" value="北京"/></bean><bean id="user2" class="com.king.pojo.User"><property name="age" value="22"/><property name="name" value="嘉靖"/><property name="adddr" value="南京"/></bean>

这时使用第二种方式来获取对象,就会出现问题,context容器不知道该取哪一个,由于有两个属性值完全不同的对象,但类型是相同的,容器不知道如何进行分辨。

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

(期望找到一个,但是出现了两个)

4.4、属性值注入

1、最常用的属性值的注入方式

通过无参构造方法来创建

<bean id="user" class="com.king.pojo.User"><!--这里property标签里的属性 name 指的是set方法名上紧跟着set之后的那部分单词,首字母小写--><property name="age" value="20"/><property name="name" value="胡宗宪"/><property name="adddr" value="北京"/>
</bean>
@Test
public void test(){ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");//在执行getBean的时候, user已经创建好了, 通过无参构造User user = context.getBean("user",User.class);System.out.println(user);
}

2、通过构造器给属性赋值

一定要记得给实体类bean添加构造方法

<!--    通过构造方法来给属性赋值 -->
<bean id="user3" class="com.king.pojo.User"><!--   name 填写构造方法中的参数名,而不是实体类中的属性名字  value填写要赋给属性的值--><constructor-arg name="name" value="李斯"></constructor-arg><constructor-arg name="age" value="123"></constructor-arg><constructor-arg name="addr" value="成都"></constructor-arg>
</bean><!--    简写:name标签属性可以省略,只要value的值和实体类中的构造方法参数顺序一致,依然可以赋上值-->
<bean id="user3" class="com.king.pojo.User"><constructor-arg  value="李斯"></constructor-arg><constructor-arg  value="123"></constructor-arg><constructor-arg  value="成都"></constructor-arg>
</bean><!--如果想不按照顺序来添加参数值,那么可以搭配index属性来使用 -->
<bean id="person4" class="com.king.bean.Person"><constructor-arg value="lisi" index="1"></constructor-arg><constructor-arg value="1" index="0"></constructor-arg><constructor-arg value="" index="3"></constructor-arg><constructor-arg value="20" index="2"></constructor-arg>
</bean><!--当有多个参数个数相同,不同类型的构造器的时候,可以通过type来强制类型-->将person的age类型设置为Integer类型
public Person(int id, String name, Integer age) {this.id = id;this.name = name;this.age = age;System.out.println("Age");
}public Person(int id, String name, String gender) {this.id = id;this.name = name;this.gender = gender;System.out.println("gender");
}<bean id="person5" class="com.king.bean.Person"><constructor-arg value="1"></constructor-arg><constructor-arg value="lisi"></constructor-arg><constructor-arg value="20" type="java.lang.Integer"></constructor-arg>
</bean><!--如果不修改为integer类型,那么需要type跟index组合使用-->
<bean id="person5" class="com.king.bean.Person"><constructor-arg value="1"></constructor-arg><constructor-arg value="lisi"></constructor-arg><constructor-arg value="20" type="int" index="2"></constructor-arg>
</bean><bean id="user6" class="com.king.pojo.User"><constructor-arg value="儒圣"></constructor-arg><constructor-arg value="深圳"></constructor-arg><constructor-arg value="45798" type="java.lang.Integer"></constructor-arg><!--Date类型指定value的时候,用/分开,spring可以自动识别--><constructor-arg value="2021/1/6" type="java.util.Date"></constructor-arg>
</bean>

3、通过命名空间来为bean赋值

3.1、通过p命名空间来注入

User.java :【注意:这里有或没有构造器都不影响p命名空间的注入!】

 public class User {private String name;private int age;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}

p命名空间的注入需要在头文件中加入约束文件

 导入约束 : xmlns:p="http://www.springframework.org/schema/p"<!--P(属性: properties)命名空间 , 直接注入属性--><bean id="user" class="com.pojo.User" p:name="king" p:age="18"/>

3.2、通过c命名空间注入

 导入约束 : xmlns:c="http://www.springframework.org/schema/c"<!--C(构造: Constructor)命名空间 , 使用构造器注入--><bean id="user" class="com.pojo.User" c:name="alibab" c:age="18"/>

发现问题:爆红,因为没有有参构造

解决:把有参构造加上就可以解决,这里我们就知道了,c就是所谓的构造器注入

4、为复杂类型进行赋值

要求被注入的属性必须具有set方法,set方法的方法名需要由IDEA编译器自动生成(set+属性首字母大写),如果属性是boolean类型,没有set方法,只有is

测试类pojo类:

Person.java

public class Person {private String name;private Integer age;private Boolean gender;private String[] hobbies;private ArrayList<Book> books;private Set<String> set;private Address address;private HashMap<String, Object> map;private Properties pro;public String getName ( ) {return name;}public void setName ( String name ) {this.name = name;}public Integer getAge ( ) {return age;}public void setAge ( Integer age ) {this.age = age;}public Boolean getGender ( ) {return gender;}public void setGender ( Boolean gender ) {this.gender = gender;}public String[] getHobbies ( ) {return hobbies;}public void setHobbies ( String[] hobbies ) {this.hobbies = hobbies;}public void setBooks ( ArrayList<Book> books ) {this.books = books;}public Set<String> getSet ( ) {return set;}public void setSet ( Set<String> set ) {this.set = set;}public Address getAddress ( ) {return address;}public void setAddress ( Address address ) {this.address = address;}public HashMap<String, Object> getMap ( ) {return map;}public void setMap ( HashMap<String, Object> map ) {this.map = map;}public Properties getPro ( ) {return pro;}public void setPro ( Properties pro ) {this.pro = pro;}@Overridepublic String toString ( ) {return "Person{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +", hobbies=" + Arrays.toString ( hobbies ) +", books=" + books +", set=" + set +", address=" + address +", map=" + map +", pro=" + pro +'}';}
}

Address.java

public class Address {private String city;private String country;private String province;public String getCity ( ) {return city;}public void setCity ( String city ) {this.city = city;}public String getCountry ( ) {return country;}public void setCountry ( String country ) {this.country = country;}public String getProvince ( ) {return province;}public void setProvince ( String province ) {this.province = province;}@Overridepublic String toString ( ) {return "Address{" +"city='" + city + '\'' +", country='" + country + '\'' +", province='" + province + '\'' +'}';}
}

Book.java

public class Book {private String author;private String book_name;public String getAuthor ( ) {return author;}public void setAuthor ( String author ) {this.author = author;}public String getBook_name ( ) {return book_name;}public void setBook_name ( String book_name ) {this.book_name = book_name;}@Overridepublic String toString ( ) {return "Book{" +"author='" + author + '\'' +", book_name='" + book_name + '\'' +'}';}
}

1.常量注入

<property name="name" value="曾国藩"></property>   String
<property name="age" value="100"></property>   Integer
<property name="gender" value="false"></property>  Boolean

2.引用类型注入

三种方式:

<!--        注入引用类型-->
===========================================================
<!--        使用ref属性来引入外部的bean--><property name="address" ref="add"></property>
=========================================================
<!--        和上面的方式一样,只不过换了一种写法--><property name="address"><ref bean="add"></ref></property>
====================================================================<property name="address">
<!--            使用内部bean标签来注入引用类型 内部bean无法从IOC容器中获取对象--><bean id="add2" class="com.king.pojo.Address"><property name="province" value="河北"></property><property name="country" value="日本"/><property name="city" value="洛杉矶"/></bean></property>

3.数组的注入

<!--        注入数组类型-->
<!--        <property name="hobbies" value="篮球,羽毛球,足球"></property>--><property name="hobbies"><array><value>篮球</value><value>足球</value><value>羽毛球</value></array></property>

4.List的注入

<!--        List类型注入--><property name="books"><list>
<!--                引入外部的bean--><ref bean="book"></ref>
<!--                创建内部bean--><bean class="com.king.pojo.Book"><property name="author" value="king"/><property name="book_name" value="阿里巴巴"/></bean><bean id="b2" class="com.king.pojo.Book"><property name="book_name" value="武汉"/><property name="author" value="中国"/></bean></list></property>

5.set注入

这里虽然写了三个相同的value,但是还是要遵守set的特点,有序不可重复

<!--        set注入--><property name="set"><set><value>king</value><value>king</value><value>king</value><value>joye</value></set></property>

6.map注入

<!--        map注入-->
<property name="map"><map><entry><key><value>1</value></key><value></value></entry><entry key="2" value=""/><entry key="book" value-ref="book"/><entry key="list"><list><bean class="com.king.pojo.Address"><property name="city" value="日本"></property><property name="country" value="韩国"/><property name="province" value="山西"/></bean><value>足球</value><ref bean="book"></ref></list></entry><entry key="add"><bean class="com.king.pojo.Address"><property name="province" value="美国"/><property name="country" value="加拿大"/><property name="city" value="西安"/></bean></entry></map>
</property>

7.properties注入

<property name="pro"><props><prop key="a">asd</prop><prop key="b">asdasd</prop><prop key="c">asfdsad</prop></props>
</property>

4.5、继承关系bean的配置

ioc.xml

<!--    继承关系bean的配置--><bean id="user" class="com.king.pojo.User"><property name="age" value="20"/><property name="name" value="胡宗宪"/><property name="adddr" value="北京"/><property name="date" value="2022/12/5"/></bean><!--parent属性可以指定该bean的属性继承自哪个bean,换句话说,就是获取父类bean中的一些属性值--><bean id="user1" class="com.king.pojo.User" parent="user"><property name="age" value="946546"/></bean>

结果

User被创建
User被创建
User{age=946546, name='胡宗宪', addr='北京', date=Mon Dec 05 00:00:00 CST 2022}

初始化了两个bean实体类

如果想实现Java文件的抽象类,不需要将当前bean实例化的话,可以使用abstract属性

<!--    继承关系bean的配置  abstract属性可以设置不让这个bean被初始化--><bean id="user" class="com.king.pojo.User" abstract="true"><property name="age" value="20"/><property name="name" value="胡宗宪"/><property name="adddr" value="北京"/><property name="date" value="2022/12/5"/></bean><!--parent属性可以指定该bean的属性继承自哪个bean,换句话说,就是获取父类bean中的一些属性值--><bean id="user1" class="com.king.pojo.User" parent="user"><property name="age" value="946546"/></bean>

结果

User被创建
User{age=946546, name='胡宗宪', addr='北京', date=Mon Dec 05 00:00:00 CST 2022}

可以看出只有一个bean被初始化

4.6、bean对象创建的依赖关系

bean对象在创建的时候是按照bean在配置文件的顺序决定的,也可以使用depend-on标签来决定顺序

ioc.xml

<!--创建bean的时候依赖关系当bean对象被创建的时候,是按照xml配置文件定义的顺序创建的,谁在前,谁就先被创建如果需要干扰创建的顺序,可以使用depends-on属性一般在实际工作中不必在意bean创建的顺序,无论谁先创建,需要依赖的对象在创建完成之后都会进行赋值操作-->
<bean id="book" class="com.bean.Book" depends-on="person,address"></bean>
<bean id="address" class="com.bean.Address"></bean>
<bean id="person" class="com.bean.Person"></bean>

4.7、bean的作用域控制

bean在创建的时候可以规定是不是单例的,有两个选择:单例的和多例的

单例情况

<!--scope = "singleton"  单例 默认就是单例的-->
<bean id="user" class="com.king.pojo.User" scope="singleton"><property name="age" value="5461231"/>
</bean>

测试:

 public static void main ( String[] args ) {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );User user = context.getBean ( "user" , User.class );User user1 = context.getBean ( "user" , User.class );System.out.println ( user == user1 );}//结果:User被创建     调用了两个bean,但是只创建了一个对象true

由此可以看出,单例模式下,bean对象IOC容器创建完成之前(或者说创建完IOC容器,bean对象也跟着创建好了),就已经创建好了。

多例情况

<!--当scope属性值设置为prototype-->
<bean id="user" class="com.king.pojo.User" scope="prototype"><property name="age" value="5461231"/>
</bean>

测试:

public static void main ( String[] args ) {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );User user = context.getBean ( "user" , User.class );User user1 = context.getBean ( "user" , User.class );System.out.println ( user == user1 );
}//结果:
User被创建   调用了两次无参构造方法
User被创建
false      创建出来的两个对象是不同的

有以上程序运行结果看出,啥都没有,说明多例模式下,bean对象的创建,是在用到的时候才会被创建

4.8、利用工厂模式创建bean对象

此处可以适当复习一波工厂模式

在之前的案例中,所有bean对象的创建底层都是通过反射得到对应的bean实例,其实在spring中还包含另外一种创建bean实例的方式,就是通过工厂模式进行对象的创建

​ 在利用工厂模式创建bean实例的时候有两种方式,分别是静态工厂和实例工厂。

​ 静态工厂:工厂本身不需要创建对象,但是可以通过静态方法调用,对象=工厂类.静态工厂方法名();

​ 实例工厂(抽象工厂):工厂本身需要创建对象,工厂类 工厂对象=new 工厂类;工厂对象.get对象名();

实体类bean:

public class User {private Integer age;private String name;private String addr;private Date date;public User ( Integer age , String name , String addr , Date date ) {this.age = age;this.name = name;this.addr = addr;this.date = date;}public Date getDate ( ) {return date;}public void setDate ( Date date ) {this.date = date;}public User ( ) {System.out.println ("User被创建");}//    public Integer getAge ( ) {
//        return age;
//    }public void setAge ( Integer age ) {this.age = age;}//    public String getName ( ) {
//        return name;
//    }public void setName ( String name ) {this.name = name;}
//
//    public String getAddr ( ) {
//        return addr;
//    }public void setAdddr ( String addr ) {this.addr = addr;}@Overridepublic String toString ( ) {return "User{" +"age=" + age +", name='" + name + '\'' +", addr='" + addr + '\'' +", date=" + date +'}';}
}

静态工厂

类名.静态方法

直接创建静态工厂的实例对象,在bean标签中指定工厂方法就可以了

静态工厂类

MyStaticFactory.java

public class MyStaticFactory {public static User getUser(String name){User user = new User ( );user.setName (name);user.setAdddr ( "英格兰" );return user;}
}

ioc.xml

<!--    利用静态工厂来创建对象  -->
<!--    class:指定静态工厂类的完全限定名factory-method:指定调用静态工厂里的哪个方法-->
<bean id="user1" class="com.king.factory.MyStaticFactory" factory-method="getUser"><!--        constructor-arg:可以为方法指定参数--><constructor-arg name="name" value="胡宗宪"/>
</bean>

测试:

public static void main ( String[] args ) {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );User user = context.getBean ( "user1" , User.class );System.out.println ( user );
}//结果:
User被创建
User{age=null, name='胡宗宪', addr='英格兰', date=null}

实例工厂(抽象工厂)

先创建工厂实例,再创建需要的bean实例,指定需要的工厂实例和工厂方法

MyAbstractFactory.java

public class MyAbstractFactory {public User getUser(String name){User user = new User ( );user.setAge ( 1231564 );user.setDate ( new Date (  ) );user.setName ( name );return user;}
}

ioc.xml

<!--    利用抽象工厂来创建bean-->
<!--    factory-bean:指定是哪个实例工厂对象fatory-method:指定哪个工厂方法-->
<bean id="user2" class="com.king.pojo.User" factory-bean="fa" factory-method="getUser"><constructor-arg name="name" value="嘉靖"/>
</bean><!--    实例工厂来创建bean需要先初始化实例工厂类-->
<bean id="fa" class="com.king.factory.MyAbstractFactory"></bean>

测试:

public class Test1 {public static void main ( String[] args ) {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );User user = context.getBean ( "user2" , User.class );System.out.println ( user );}
}//结果:
User被创建
User{age=1231564, name='嘉靖', addr='null', date=Thu Jan 07 11:49:04 CST 2021}

4.9、继承FactoryBean来创建对象

​ FactoryBean是Spring规定的一个接口,当前接口的实现类,Spring都会将其作为一个工厂,但是在ioc容器启动的时候不会创建实例,只有在使用的时候才会创建对象

/*** 实现了FactoryBean接口的类是Spring中可以识别的工厂类,spring会自动调用工厂方法创建实例* 在实现时,需要指定泛型类*/
public class MyFactoryBean implements FactoryBean<User> {/*** 工厂方法,返回需要创建的对象* @return* @throws Exception*/@Overridepublic User getObject ( ) throws Exception {User user = new User ();user.setName ( "king" );user.setDate ( new Date (  ) );user.setAge ( 799999 );return user;}/*** 返回创建对象的类型,spring会自动调用该方法返回对象的类型* @return*/@Overridepublic Class<?> getObjectType() {return User.class;}/*** 创建的对象是否是单例对象* @return*/@Overridepublic boolean isSingleton() {return false;}
}

ioc.xml

<bean id="myFactoryBean" class="com.king.factory.MyFactoryBean"/>

测试:

public static void main ( String[] args ) {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );User user = context.getBean ( "myFactoryBean" , User.class );User user1 = context.getBean ( "myFactoryBean" , User.class );System.out.println ( user == user1);
}//结果:
User被创建
User{age=799999, name='king', addr='null', date=Thu Jan 07 13:37:52 CST 2021}

4.10、bean对象的初始化和销毁方法

​ 在创建对象的时候,我们可以根据需要调用初始化和销毁的方法

User.java bean

public class User {private Integer age;private String name;private String addr;private Date date;public User ( Integer age , String name , String addr , Date date ) {this.age = age;this.name = name;this.addr = addr;this.date = date;}public Date getDate ( ) {return date;}public void setDate ( Date date ) {this.date = date;}public User ( ) {System.out.println ("User被创建");}public void setAge ( Integer age ) {this.age = age;}public void setName ( String name ) {this.name = name;}public void setAdddr ( String addr ) {this.addr = addr;}/***初始化方法*/public void init(){System.out.println ("User进行初始化" );}/***销毁方法*/public void destory(){System.out.println ("User被销毁" );}@Overridepublic String toString ( ) {return "User{" +"age=" + age +", name='" + name + '\'' +", addr='" + addr + '\'' +", date=" + date +'}';}
}

ioc.xml

<!--spring容器在创建对象的时候可以指定具体的初始化和销毁方法init-method: 在对象创建完成之后调用初始化方法destory_method: 销毁方法  在容器关闭的时候会调用  -->
<bean id="user" class="com.king.pojo.User" init-method="init" destroy-method="destory" />

测试:

public class Test1 {public static void main ( String[] args ) {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );User user = context.getBean ( "user" , User.class );System.out.println ( user );//applicationContext没有close方法,需要使用具体的子类((ClassPathXmlApplicationContext)context).close();}
}//结果:
User被创建
User进行初始化  //init方法在user构造方法调用之后调用(在user对象创建完毕被调用)
User{age=null, name='null', addr='null', date=null}
User被销毁

注意

初始化和销毁的方法和scope属性也是有关联的

当scope属性为singleton时,单例,初始化和销毁方法都会被调用

但是当scope属性为prototype时,多例,不管关闭不关闭容器,只会调用初始化方法,不会调用销毁方法

ioc.xml

<!--初始化和销毁的方法跟scope属性也是相关联的如果是singleton的话,初始化和销毁的方法都存在如果是prototype的话,初始化方法会调用,但是销毁的方法不会调用-->
<bean id="user" class="com.king.pojo.User" init-method="init" destroy-method="destory" scope="prototype"/>

测试:

public static void main ( String[] args ) {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );User user = context.getBean ( "user" , User.class );System.out.println ( user );//applicationContext没有close方法,需要使用具体的子类((ClassPathXmlApplicationContext)context).close();
}//结果:
User被创建
User进行初始化
User{age=null, name='null', addr='null', date=null}

1、配置bean对象初始化方法的前后处理方法

spring中包含一个BeanPostProcessor的接口,可以在bean的初始化方法的前后调用该方法,如果配置了初始化方法的前置和后置处理器,无论是否包含初始化方法,都会进行调用

MyBeanPostProcesser.java

public class MyBeanPostProcesser implements BeanPostProcessor {/*** 在初始化方法之前调用* @param bean* @param beanName* @return* @throws BeansException*/@Overridepublic Object postProcessBeforeInitialization ( Object bean , String beanName ) throws BeansException {System.out.println ( "在初始化方法之前调用" + beanName );return bean;}/*** 在初始化方法之后被调用* @param bean* @param beanName* @return* @throws BeansException*/@Overridepublic Object postProcessAfterInitialization ( Object bean , String beanName ) throws BeansException {System.out.println ("在初始化方法之后调用" + beanName );return bean;}
}

这个方法不管有多少个bean,只要在xml文件中注册过,那么所有的bean都会生效

ioc.xml

<!--    注册MyBeanPostProcesser,这个类相当于扩展功能的-->
<bean id="myBeanPostProcesser" class="com.king.factory.MyBeanPostProcesser"/><!--注册user-->
<bean id="user" class="com.king.pojo.User" init-method="init" destroy-method="destory"/><!--注册book-->
<bean id="book" class="com.king.pojo.Book"/>

测试:

public static void main ( String[] args ) {//初始化容器ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc1.xml" );//applicationContext没有close方法,需要使用具体的子类  关闭容器((ClassPathXmlApplicationContext)context).close();
}//结果: BOOK 和 User 都生效
User被创建
在初始化方法之前调用user
User进行初始化
在初始化方法之后调用userBook被创建
在初始化方法之前调用book
在初始化方法之后调用bookUser被销毁

4.11、spring创建第三方bean对象

在Spring中,很多对象都是单实例的,在日常的开发中,我们经常需要使用某些外部的单实例对象,例如数据库连接池,下面我们来讲解下如何在spring中创建第三方bean实例。

1、使用spring的配置文件

首先需要引入相关的外部依赖 我们导入数据库连接池,创建durid连接池

<!--        导入数据库连接池德鲁伊的jar包-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.4</version>
</dependency><!--        导入mysql8.0.20版的JDBC驱动 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.20</version>
</dependency>

ioc2.xml

<!--    创建第三方bean对象  spring管理第三方bean-->
<bean id="durid" class="com.alibaba.druid.pool.DruidDataSource"><!--  填写数据库连接池的参数--><property name="username" value="root"/><property name="password" value="root"/><property name="url" value="jdbc:mysql//localhost:3306/test?serverTimezone=UTC"/><!--驱动名要写成driverClassName 不然的话会报错--><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>

测试:

public static void main ( String[] args ) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc2.xml" );DruidDataSource durid = context.getBean ( "durid" , DruidDataSource.class );System.out.println ( durid );
}//结果:
{CreateTime:"2021-01-07 22:23:07",ActiveCount:0,PoolingCount:0,CreateCount:0,DestroyCount:0,CloseCount:0,ConnectCount:0,Connections:[]
}

总结

  • 只要导入jar包,在xml的配置文件里进行注册,就可以使用

2、引入外部配置文件

在resource中添加dbconfig.properties

jdbc.username=root
password=root
url=jdbc:mysql//localhost:3306/test?serverTimezone=UTC
driverClassName=com.mysql.cj.jdbc.Driver

当需要引入外部的配置文件的时候,需要导入一些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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--利用context命名空间来引入外部配置文件,利用location属性来指定配置文件的位置--><context:property-placeholder location="classpath:dbconfig.properties"/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${username}"/><property name="password" value="${password}"/><property name="url" value="${url}"/><property name="driverClassName" value="${driverClassName}"/></bean><!--这里再注册一个User,调用外部的配置文件的username来给user的属性赋值--><bean id="user" class="com.king.pojo.User"><property name="name" value="${username}"/></bean></beans>

注意:可以直接输入<context: IDEA会提示出property-placeholder,当敲完这段代码之后,命名空间就会自动补全,IDEA很智能

测试:

public static void main ( String[] args ) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext ( "ioc2.xml" );DruidDataSource durid = context.getBean ( "dataSource" , DruidDataSource.class );User user = context.getBean ( "user" , User.class );System.out.println ( durid );System.out.println ( user );
}//结果:
User被创建
{CreateTime:"2021-01-07 22:55:41",ActiveCount:0,PoolingCount:0,CreateCount:0,DestroyCount:0,CloseCount:0,ConnectCount:0,Connections:[]
}User{age=null, name='骑着驴杀猪', addr='null', date=null}

这里结果发现,name的值和外部配置文件的值不一样

原因

添加一个前缀来进行区分

jdbc.username=root
<bean id="user" class="com.king.pojo.User"><property name="name" value="${jdbc.username}"/>
</bean>

再看测试结果:

User{age=null, name='root', addr='null', date=null}

完美

4.13、spring基于xml文件的自动装配

仅适合当前类中引用了其他对象

当一个对象中需要引用另外一个对象的时候,在之前的配置中我们都是通过property标签来进行手动配置的,其实在spring中还提供了一个非常强大的功能就是自动装配。

如下几个选项:

no/default: 不装配byType: 按照bean的类型来装配,当注册了多个相同的类型时,就会报错,因为spring不知道该具体选则哪个beanbyName: 按照名字进行装配,这里的名字是set方法名上set后面首字母小写的那个名字当作id来选择对应的bean,找不到,装配nullconstructor:按照构造器进行装配,这里需要给引用了的对象单独造一个构造方法,否则会报错,当注册了多个相同类型的bean时,按照名字进行判断,这里的名字指的是构造方法上参数列表指定的名字

其中byName是最常用的

Person.java 注意这里有有参构造(单独针对引用类型创建的构造方法) 不提供无参构造方法

public class Student {private Address address;//此处一定要有无参构造,因为spring底层通过反射来创建实例对象public Student ( ) {}public Person ( Address address2 ) {this.address = address2;}public void setAddress ( Address address ) {this.address = address;}@Overridepublic String toString ( ) {return "Person{" +", address=" + address +'}';}
}

ioc3.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"><!--    注册一个Address的实例对象--><bean id="address" class="com.king.pojo.Address"><property name="city" value="中国"/><property name="country" value="日本"/><property name="province" value="北京"/></bean><bean id="address2" class="com.king.pojo.Address"><property name="city" value="英格兰"/><property name="country" value="洛杉矶"/><property name="province" value="美国"/></bean><!--    使用autowire属性来进行自动装配  注意:自动装配装配的是引用类型--><!--    使用autowire属性来进行自动装配  注意:自动装配装配的是引用类型--><!--    default 和 no 表示不装配 测试结果会为空--><bean id="stu" class="com.king.pojo.Student" autowire="default"/><bean id="stu1" class="com.king.pojo.Student" autowire="no"/><!--最常用得是byName--><bean id="student" class="com.king.pojo.Student" autowire="byName"/><bean id="student1" class="com.king.pojo.Student" autowire="byType"/><bean id="student2" class="com.king.pojo.Student" autowire="constructor"/>
</beans>

注意:

  • 实例类中一定要加上无参构造,因为spring底层通过反射来创建实例对象,必须要通过无参构造
  • 通过byName来进行自动装配时,是通过set方法名后面得首字母小写当做id来查找对应得bean
  • 通过byType来进行自动装配时,必须要确定不能出现相同类型得bean,否则会报错,这里还会出现一种情况是使用bytype的时候会加载环境变量出来。
  • 通过constructor构造器来进行自动装配时,必须要确保实例类中具有单独的针对该引用对象的构造方法,否则会报错。当出现多个相同类型的bean时,通过构造方法参数名当作id来查找对应的bean

为什么使用bytype的时候会加载环境变量出来

原因:因为在pojo类中用到了map类型,map类型定义的 泛型为<String,Object>,由于有Object则,spring在加载时,会自动装配environment这个类,并把对应的属性都赋上值,只要把Object换成String,或者其他具体的类型就可以恢复正常

4.14、SpEL的使用

这种东西很少使用

​ SpEL:Spring Expression Language,spring的表达式语言,支持运行时查询操作对象

​ 使用#{…}作为语法规则,所有的大括号中的字符都认为是SpEL.

ioc.xml

<!--SpEL表达式语言的使用-->
<bean id="person2" class="com.mashibing.bean.Person"><!--可以引入bean对象的属性--><property name="name" value="#{address.province}"></property><!--可以支持运算符的所有操作--><property name="age" value="#{12*2}"></property><!--可以引入外部bean--><property name="address" value="#{address}"></property><!--可以调用静态方法--><property name="gender" value="#{T(java.util.UUID).randomUUID().toString().substring(0,5)}"></property><!--调用非静态方法--><property name="hobbies" value="#{address.getCity()}"></property>
</bean>

4.15、IOC的注解应用

在之前的项目中,我们都是通过xml文件进行bean或者某些属性的赋值,其实还有另外一种注解的方式,在企业开发中使用的很多,在bean上添加注解,可以快速的将bean注册到ioc容器。

1、使用注解的方式注册bean到IOC容器中

如果想要将自定义的类注册到IOC容器中,我们可以使用注解的方式来操作,spring为我们提供了如下四个注解来使用:

@Controller:放置在控制层@Service:放置在service层@Repository:放置在数据访问层@Component:组件 放哪都可以,在任意类上都可以进行添加,在扫描的时候会自动完成bean的注册

原本只需要提供一个@Component注解就可以一劳永逸,但是不好进行区分,不知道具体注册的是哪个类,还需要点开该类,查看该类的名字才能知道,所以spring官方为了更加的便于区分,怎加了其余三个注解,来使用,提高可读性。

定义四个类

PersonController.java

@Controller
public class PersonController {
}

PesonService.java

@Service
public class PersonService {}

PersonDao.java

@Repository
public class PersonDao {
}

Person.java

@Component
public class Person {
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--如果想要将自定义的bean对象添加到IOC容器中,需要在类上添加某些注解Spring中包含4个主要的组件添加注解:@Controller:控制器,推荐给controller层添加此注解@Service:业务逻辑,推荐给业务逻辑层添加此注解@Repository:仓库管理,推荐给数据访问层添加此注解@Component:给不属于以上基层的组件添加此注解注意:我们虽然人为的给不同的层添加不同的注解,但是在spring看来,可以在任意层添加任意注解spring底层是不会给具体的层次验证注解,这样写的目的只是为了提高可读性,最偷懒的方式就是给所有想交由IOC容器管理的bean对象添加component注解使用注解需要如下步骤:1、添加上述四个注解中的任意一个2、添加自动扫描注解的组件,此操作需要依赖context命名空间3、添加自动扫描的标签context:component-scan这四个注解写在类上面的时候都可以完成注册bean的功能,但是这些规定并不是spring识别的标识在spring程序运行过程中,不会对这四个注解做任何区分,看起来是一样的,都会完成bean的注册功能在实际的开发过程中,最好能分清楚,提高代码的可读性。所以,最偷懒的方式是,给所有需要注册的bean类上添加@Component注解--><!--定义自动扫描的基础包:base-package:指定扫描的基础包,spring在启动的时候会将基础包及子包下所有加了注解的类都自动扫描进IOC容器spring会将com.king这个包下所有的类全部都注册进IOC容器中去--><context:component-scan base-package="com.king"/></beans>

测试:

@Test
public void test01(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ( "applicationContext.xml" );PersonController personController = context.getBean ( "personController" , PersonController.class );System.out.println ( personController );
}//结果:
com.king.controller.PersonController@1d76aeea  

发现注册成功

注意: 当使用注解注册组件和使用配置文件注册组件是一样的,但是要注意:

1、 在使用注解的时候没有定义id和class,那么如何根据id来进行识别。默认是把当前类的名称的首字母小写之后作为id,如果需要改变名称,那么需要在注解添加参数值value来完成修改名字的目的。(一般情况下不会这么干,采用默认的即可

示例:@Controller(value = "controller") 测试的时候也要改PersonController personController = context.getBean ( "controller" , PersonController.class );

2、组件默认情况下都是单例的,如果需要配置多例模式的话,可以在注解下添加@Scope注解 ,@Scope注解可以声明当前类是单例还是多例

//示例:@Controller(value = "controller")
@Scope(value = "prototype")
public class PersonController {
}//测试:
@Test
public void test01(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ( "applicationContext.xml" );PersonController personController = context.getBean ( "controller" , PersonController.class );PersonController personController1 = context.getBean ( "controller" , PersonController.class );System.out.println ( personController == personController1 );
}//结果:
false

2.定义扫描包时要包含的类和不要包含的类

当定义好基础的扫描包后,在某些情况下可能要有选择性的配置是否要注册bean到IOC容器中,此时可以通过如下的方式进行配置。

applicationContext.xml

<context:component-scan base-package="com.mashibing" ><!--当定义好注解的扫描路径之后,可以做更细粒度的控制,可以选择扫描哪个注解,也可以选择不扫描哪个注解include-filter:表示要包含扫描的注解,一般不会定义此规则,但是如果引入的第三方包中包含注解,此时就需要使用此标签来进行标识。exclude-filter:表示要排除扫描的注解,使用较多type:规则的类型 指定类型的规则expression:表达式assignable:可以指定对应的类的名称。但是表达式必须是类的完全限定名annotation:按照注解来进行排序,但是表达式中必须是注解的完全限定名regex:使用正则表达式的方式,一般不用aspectj:使用切面的方式,一般不用custom:使用自定义的方式,可以自己定义自己的筛选规则,一般不用--><!--        <context:exclude-filter type="assignable" expression="com.mashibing.controller.PersonController"/>--><!--        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>--><context:include-filter type="assignable" expression="com.king.service.PersonService"/>

3.使用@AutoWired进行自动装配

1、定义在属性上

使用@autoWired注解可是自动注册bean实例

Personontroller.java

@Controller
public class PersonController {@Autowiredprivate PersonService personService;public PersonController() {System.out.println("创建对象");}public void getPerson(){personService.getPerson();}
}

PersonService.java

@Service
public class PersonService {@Autowiredprivate PersonDao personDao;public void getPerson(){personDao.getPerson();}
}

PersonDao.java

@Repository
public class PersonDao {public void getPerson(){System.out.println("PersonDao:getPerson");}
}

测试:

@Test
public void test01() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");PersonController personController = context.getBean ( "personController" , PersonController.class );personController.getPerson();
}//结果:
创建对象
PersonDao:getPerson

注意:当使用AutoWired注解的时候,自动装配的时候是根据类型实现的。

​ 1、如果只找到一个,则直接进行赋值,

​ 2、如果没有找到,则直接抛出异常,

​ 3、如果找到多个,那么会按照变量名作为id继续匹配,

​ 1、匹配上直接进行装配

​ 2、如果匹配不上则直接报异常

示例:

写一个共同的service接口

public interface IPersonService {void save();
}

写两个实现类

PersonService.java

@Service
public class PersonService implements IPersonService{@Autowiredprivate PersonDao personDao;@Overridepublic void save(){System.out.println("保存personService");personDao.save();}}

PersonService2.java

@Service
public class PersonService2 implements IPersonService {@Autowiredprivate PersonDao personDao;@Overridepublic void save(){System.out.println("PersonService2保存");personDao.save();}}

PersonDao.java

@Repository
public class PersonDao {public void save(){System.out.println("保存personDao");}}

PersonController.java

@Controller
public class PersonController {@Autowiredprivate IPersonService personService232;public void save(){personService232.save();}}

测试:

@Test
public void test01() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");PersonController personController = context.getBean ( "personController" , PersonController.class );personController.save();
}//结果:
报错,原因因为IPersonService有两个实现类,当类型相同时,把属性名首字母大写当作id来查找对应的bean来进行注册,但是注意此时属性名为personService232,由于根据id来查找,没有此类,所以报错

解决:

还可以使用@Qualifier注解来指定id的名称,让spring不要使用变量名,当使用@Qualifier注解的时候也会有两种情况:

​ 1、找到,则直接装配

​ 2、找不到,就会报错

示例:

@Controller
public class PersonController {@Autowired@Qualifier(value = "personService2")  //使用此注解会强制来进行id来匹配private IPersonService personService232;public void save(){personService232.save();}}//测试结果:
PersonService2保存
保存personDao

还可以更改属性名,为personService2或者personService,这样spring根据id来查找时就会找到对应的类

@Controller
public class PersonController {@Autowiredprivate IPersonService personService2;public void save(){personService2.save();}
}

测试结果:

PersonService2保存
保存personDao

2、定义在方法上

当使用@AutoWired注解作用在方法上时,此方法在创建对象时会自动被调用

PersonController.java

@Controller
public class PersonController {@Autowiredprivate IPersonService personService2;public void save(){personService2.save();}/*** 当方法上有@AutoWired注解时:*  1、此方法在bean创建的时候会自动调用*  2、这个方法的每一个参数都会自动注入值* @param personDao*/@Autowiredpublic void test(PersonDao personDao){System.out.println("此方法被调用:"+personDao);}}

测试:

@Test
public void test01() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");PersonController personController = context.getBean ( "personController" , PersonController.class );personController.save();
}//结果:
此方法被调用:com.king.dao.PersonDao@2805c96b
PersonService2保存
保存personDao

@Qualifier注解可以作用在属性参数上

/*** @Qualifier注解可以作用在属性参数上,用来当做id去匹配容器中的对象* 如果没有此注解,则按照类型去匹配,如果是相同类型,则按照属性名当作id去匹配* 如果还是匹配不到,报错* @param personService*/
@Autowired
public void test2(@Qualifier(value = "personService2") IPersonService personService){System.out.println("此方法被调用" + personService);
}

测试结果:

此方法被调用com.king.service.PersonService2@37918c79

可以看出调用的是PersonService2这个bean

3、自动装配的注解@AutoWired,@Resource

@Resource和@AutoWired作用是一样的,但是还是有区别

区别:

  1. @Resource注解是JDK提供的功能,@AutoWired注解是spring提供的功能
  2. @Resource注解可以在其他框架中使用,而@AutoWired注解只能在spring框架中使用,换句话说:@Resource扩展性好,而@AutoWired支持的框架比较单一
  3. @Resource是按照属性的名字当作id来进行匹配的,当名字匹配不到,再按照属性来进行匹配,而@AutoWired恰恰相反。
  4. 两者都可以作用在属性和方法上,功能是一样的
  5. 两者也都可以搭配@Qulifired注解来使用
@Controller
public class PersonController {@Resource@Qualifier("personService2") //此处建议该注解的值和类型相匹配(意思是:这里的id是属性名首字母小写)不然会报错private PersonService2 personService256;public void save(){personService256.save();}/*** 当方法上有@Resource注解时:*  1、此方法在bean创建的时候会自动调用*  2、这个方法的每一个参数都会自动注入值* @param personDao*/@Resourcepublic void test(PersonDao personDao){System.out.println("此方法被调用:"+personDao);}/*** @Qualifier注解可以作用在属性参数上,用来当做id去匹配容器中的对象* 如果没有此注解,则按照类型去匹配,如果是相同类型,则按照属性名当作id去匹配* 如果还是匹配不到,报错* @param personService*/@Resourcepublic void test2(@Qualifier(value = "personService2") IPersonService personService){System.out.println("此方法被调用" + personService);}}

总结:

养成习惯,使用IDEA自动生成的默认名字即可,不要瞎取,@Qualifier注解也尽量不要使用

4.泛型依赖注入

为了讲解泛型依赖注入,首先我们需要先写一个基本的案例,按照我们之前学习的知识:

Student.java

public class Student {
}

Teacher.java

public class Teacher {
}

BaseDao.java

@Repository
public abstract class BaseDao<T> {public abstract void save();
}

StudentDao.java

@Repository
public class StudentDao extends BaseDao<Student>{public void save() {System.out.println("保存学生");}
}

TeacherDao.java

@Repository
public class TeacherDao extends BaseDao<Teacher> {public void save() {System.out.println("保存老师");}
}

StudentService.java

@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;public void save(){studentDao.save();}
}

TeacherService.java

@Service
public class TeacherService {@Autowiredprivate TeacherDao teacherDao;public void save(){teacherDao.save();}
}

测试:

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");StudentService studentService = context.getBean("studentService",StudentService.class);studentService.save();TeacherService teacherService = context.getBean("teacherService",TeacherService.class);teacherService.save();}
}//结果:
保存老师
保存学生

​ 上述代码是我们之前的可以完成的功能,但是可以思考,Service层的代码是否能够改写:

BaseService.java

public class BaseService<T> {@AutowiredBaseDao<T> baseDao;public void save(){System.out.println("自动注入的对象:"+baseDao);baseDao.save();}
}

StudentService.java

//继承时指定泛型类,这个泛型会传给BaseDao,来指定泛型类,就会调用到对应的bean
@Service
public class StudentService extends BaseService<Student> {}

TeacherService.java

@Service
public class TeacherService extends BaseService<Teacher>{}

再次进行测试:

@Test
public void test02(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");TeacherService teacherService = context.getBean("teacherService", TeacherService.class);teacherService.save();StudentService studentService = context.getBean("studentService", StudentService.class);studentService.save();}//结果:
自动注入对象:com.king.dao.TeacherDao@d4342c2
保存老师
自动注入对象:com.king.dao.StudentDao@2bbf180e
保存学生

五、AOP(面向切面编程)

5.1、AOP的概念

5.1.1、引入

AOP:Aspect Oriented Programming 面向切面编程

OOP:Object Oriented Programming 面向对象编程

​ 面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的对象主要是切面,在处理日志,安全管理,事务管理等方面有非常重要的作用。AOP是spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC进行补充。通俗点说:就是在程序运行期间,将某段代码动态切入到指定方法的指定位置进行运行的这种编程方式。

为什么要引入AOP?

看一个例子:

Calculator.java

public interface Calculator {public int add(int i,int j);public int sub(int i,int j);public int mult(int i,int j);public int div(int i,int j);
}

MyCalculator.java

public class MyCalculator implements Calculator {public int add(int i, int j) {int result = i + j;return result;}public int sub(int i, int j) {int result = i - j;return result;}public int mult(int i, int j) {int result = i * j;return result;}public int div(int i, int j) {int result = i / j;return result;}
}

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {MyCalculator myCalculator = new MyCalculator();System.out.println(myCalculator.add(1, 2));}
}

​ 此代码非常简单,就是基础的javase的代码实现,此时如果需要添加日志功能应该怎么做呢,只能在每个方法中添加日志输出,同时如果需要修改的话会变得非常麻烦。

MyCalculator.java

public class MyCalculator implements Calculator {public int add(int i, int j) {System.out.println("add 方法开始执行,参数为:"+i+","+j);int result = i + j;System.out.println("add 方法开始完成结果为:"+result);return result;}public int sub(int i, int j) {System.out.println("sub 方法开始执行,参数为:"+i+","+j);int result = i - j;System.out.println("sub 方法开始完成结果为:"+result);return result;}public int mult(int i, int j) {System.out.println("mult 方法开始执行,参数为:"+i+","+j);int result = i * j;System.out.println("mult 方法开始完成结果为:"+result);return result;}public int div(int i, int j) {System.out.println("div 方法开始执行,参数为:"+i+","+j);int result = i / j;System.out.println("div 方法开始完成结果为:"+result);return result;}
}

​ 可以考虑将日志的处理抽象出来,变成工具类来进行实现:

LogUtil.java

public class LogUtil {public static void start(Method method ,Object ... objects){System.out.println(method.getName() + "方法开始执行,参数为:" + Arrays.asList(objects));}public static void stop(Method method ,Object ... result){System.out.println(method.getName() + "方法执行完毕,结果为:" + Arrays.asList(result));}}

MyCalculator.java

public class MyCalculator implements Calculator {@Overridepublic Integer add(Integer i, Integer j) throws NoSuchMethodException {Method method = MyCalculator.class.getMethod("add",Integer.class,Integer.class);LogUtil.start(method,i,j);Integer result = i + j;LogUtil.stop(method,result);return result;}@Overridepublic Integer sub(Integer i, Integer j) throws NoSuchMethodException {Method sub = MyCalculator.class.getMethod("sub", Integer.class, Integer.class);LogUtil.start(sub,i,j);Integer result = i - j;LogUtil.stop(sub,result);return result;}@Overridepublic Integer div(Integer i, Integer j) throws NoSuchMethodException {Method div = MyCalculator.class.getMethod("div", Integer.class, Integer.class);LogUtil.start(div,i,j);Integer result = i / j;LogUtil.stop(div,result);return result;}@Overridepublic Integer mul(Integer i, Integer j) throws NoSuchMethodException {Method mul = MyCalculator.class.getMethod("mul", Integer.class, Integer.class);LogUtil.start(mul,i,j);Integer result = i * j;LogUtil.stop(mul,result);return result;}
}

​ 按照上述方式抽象之后,代码确实简单很多,我们利用反射技术,获取了对应的方法名称,但是这种写法,几乎和第一种写法没有区别,甚至还多出来一行代码,原先的两行代码只不过长度上变短了,我们换一种方法使用动态代理的方式来进行实现。

代码:我们这里使用JDK代理来实现

Proxy.java

/*** 帮助Calculator生成代理类对象* @Package: com.king.bean* @ClassName: CalculatorProxy* @Author: 骑着驴杀猪* @CreateTime: 2021/1/11 23:50* @Description:*/
public class Proxy {/*** 为传入的参数对象创建一个动态代理对象* @param o 被代理对象* @return          */public static Object getProxy(Object o){//获取被代理对象的类加载器ClassLoader loader = o.getClass().getClassLoader();//被代理对象的接口Class<?>[] interfaces = o.getClass().getInterfaces();//方法执行器,执行被代理对象的目标方法InvocationHandler h = new InvocationHandler() {/*** 执行目标方法* @param proxy  代理对象 给JDK使用,任何时候都不要操作此对象* @param method 当前要执行的目标对象的方法* @param args 这个方法调用时外界传入的参数值* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//利用反射执行目标方法,目标方法执行后的返回值System.out.println("这是动态代理执行的方法");Object result = null;try {LogUtil.start(method,args);result = method.invoke(o,args);LogUtil.stop(method,args);} catch (Exception e) {LogUtil.logException(method,e);} finally {LogUtil.end(method);}//返回方法的执行结果return result;}};Object proxy = Proxy.newProxyInstance(loader, interfaces, h);return proxy;}}

​ 动态代理类会自动的为我们的被代理类生成代理对象,动态的调用被代理类的方法,我们可以看到这种方式更加灵活,而且不需要在业务方法中添加额外的代码,这才是常用的方式。如果想追求完美的同学,还可以使用上述的日志工具类来完善。

LogUtil.java

public class LogUtil {public static void start(Method method, Object ... objects){System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));}public static void stop(Method method,Object ... objects){System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));}public static void logException(Method method,Exception e){System.out.println(method.getName()+"方法出现异常:"+ e.getMessage());}public static void end(Method method){System.out.println(method.getName()+"方法执行结束了......");}
}

测试:

@Test
public void test02() throws Exception {//被代理的对象必须要实现接口,否则无法创建代理对象Calculator calculator = new MyCalculator();Calculator proxy = (Calculator) CalculatorProxy.getProxy(calculator);proxy.add(100,200);System.out.println(proxy.getClass());
}//结果:
这是动态代理执行的方法
add方法开始执行,参数为:[100, 200]
add方法执行完毕,结果为:[300]
add方法执行结束了。。。
class com.sun.proxy.$Proxy4    //$Proxy就是代理对象

​ 很多同学看到上述代码之后可能感觉已经非常完美了,但是要说明的是,这种动态代理的实现方式调用的是jdk的基本实现,**如果需要代理的目标对象没有实现任何接口,那么是无法为他创建代理对象的,这也是致命的缺陷。**而在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。

5.1.2、AOP的核心概念及术语

  • 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。
  • 连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
  • 通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
  • 切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
  • 引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
  • 目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
  • 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。

5.1.3、AOP的通知类型

  • 前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
  • 后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
  • 后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
  • 后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
  • 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。

5.1.4、AOP的应用场景

  • 日志管理
  • 权限认证
  • 安全检查
  • 事务控制

5.2、Spring AOP基于注解使用

​ 使用springAOP实现上述功能

1、添加pom依赖

<!--        导入cglib动态代理功能,AOP要用到-->
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency><!--        导入springaspectjrt:简单理解,支持aop相关注解等等aspectjweaver:简单理解,支持切入点表达式等等-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version>
</dependency><!--     是AOP联盟的API包,里面包含了针对面向切面的接口。 通常Spring等其它具备动态织入功能的框架依赖此包-->
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version>
</dependency><!--        Spring提供的对AspectJ框架的整合-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.3.RELEASE</version>
</dependency>

2、编写配置

  • 接口类上不需要添加任何注解

    public interface Calculator {public Integer add(Integer i ,Integer j) throws NoSuchMethodException, Exception;public Integer sub(Integer i ,Integer j) throws NoSuchMethodException;public Integer div(Integer i , Integer j) throws NoSuchMethodException;public Integer mul(Integer i , Integer j) throws NoSuchMethodException;}
    
  • 将目标类和切面类加入到IOC容器中,在对应的类上添加组件注解

    • 给LogUtil添加@Component注解

    • 给MyCalculator添加@Service注解

      @Service
      public class MyCalculator implements Calculator {@Overridepublic Integer add(Integer i, Integer j) throws NoSuchMethodException {Integer result = i + j;return result;}@Overridepublic Integer sub(Integer i, Integer j) throws NoSuchMethodException {Integer result = i - j;return result;}@Overridepublic Integer div(Integer i, Integer j) throws NoSuchMethodException {Integer result = i / j;return result;}@Overridepublic Integer mul(Integer i, Integer j) throws NoSuchMethodException {Integer result = i * j;return result;}
      }
      
    • 添加自动扫描的配置

      <!--别忘了添加context命名空间-->
      <context:component-scan base-package="com.king"></context:component-scan>
      
  • 设置程序中的切面类

    • 在LogUtil.java中添加@Aspect注解
  • 设置切面类中的方法是什么时候在哪里执行

    @Component
    @Aspect
    public class LogUtil {/*设置下面方法在什么时候运行@Before:在目标方法之前运行:前置通知@After:在目标方法之后运行:后置通知@AfterReturning:在目标方法正常返回之后:返回通知@AfterThrowing:在目标方法抛出异常后开始运行:异常通知@Around:环绕:环绕通知当编写完注解之后还需要设置在哪些方法上执行,使用表达式execution(访问修饰符  返回值类型 方法全称)*/@Before("execution( public Integer com.king.bean.MyCalculator.*(Integer,Integer))")public static void start(){System.out.println("方法开始执行,参数是:");}@AfterReturning("execution( public Integer com.king.bean.MyCalculator.*(Integer,Integer))")public static void stop(){System.out.println("方法执行完成,结果是:");}@AfterThrowing("execution( public Integer com.king.bean.MyCalculator.*(Integer,Integer))")public static void logException(){System.out.println("方法出现异常:");}@After("execution( public Integer com.king.bean.MyCalculator.*(Integer,Integer))")public static void end(){System.out.println("方法执行结束了......");}
    }
    
  • 开启基于注解的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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd
    "><!--开启包扫描,切记--><context:component-scan base-package="com.mashibing"></context:component-scan><!--自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>
    
  • 测试

    MyTest.java

    public class MyTest {public static void main(String[] args){ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");//此处是用来获取代理对象,因为容器中保存的是代理对象Calculator bean = context.getBean(Calculator.class);bean.add(1,1);System.out.println(calculator.getClass());}
    }//结果:
    方法开始执行,参数为:
    方法执行结束了。。。
    方法执行完毕,结果为:
    class com.sun.proxy.$Proxy18
    

注意:

​ 由于当前bean对象有接口实现,spring AOP的动态代理方式是jdk自带的方式,容器中保存的组件是代理对象com.sun.proxy.$Proxy对象,而不是具体的实现类的对象,所以,在获取bean时,不能指定具体的实现类的类型,只能指定接口的类型,也不能指定任何id

//以下几种都是错误的获取代理对象的方式
context.getBean(MyCalculator.class);
context.getBean("myCalculator",MyCalculator.class);
context.getBean("calculator",Calculator.class);

通过cglib来创建代理对象

​ 当前bean没有接口实现时,springAOP的动态代理方式是cjlb

测试:我们注掉MyCalculator后面的实现

@Service
public class MyCalculator{public Integer add(Integer i, Integer j) throws NoSuchMethodException {Integer result = i + j;return result;}public Integer sub(Integer i, Integer j) throws NoSuchMethodException {Integer result = i - j;return result;}public Integer div(Integer i, Integer j) throws NoSuchMethodException {Integer result = i / j;return result;}public Integer mul(Integer i, Integer j) throws NoSuchMethodException {Integer result = i * j;return result;}
}
@Test
public void test03() throws Exception {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");MyCalculator calculator = context.getBean(MyCalculator.class);calculator.div(100, 10);System.out.println(calculator.getClass());
}//结果:
方法开始执行,参数为:
方法执行结束了。。。
方法执行完毕,结果为:
class com.king.bean.MyCalculator$$EnhancerBySpringCGLIB$$82845b39

可以通过cglib的方式来创建代理对象,此时不需要实现任何接口,代理对象是

class com.king.bean.MyCalculatorEnhancerBySpringCGLIBEnhancerBySpringCGLIBEnhancerBySpringCGLIB82845b39类型

综上所述:在spring容器中,如果有接口,那么会使用jdk自带的动态代理,如果没有接口,那么会使用cglib的动态代理。

3、使用注解AOP注意:

1、切入点表达式

  1. 最精确的匹配方式:

​ execution(public Integer com.king.MyCalculator.add(Integer,Integer))

​ 在使用表达式的时候,除了上述的写法之外,还可以使用通配符的方式:

​ *:

​ 1、匹配一个或者多个字符

​ execution( public int com.king.My*alculator.*(int,int))

​ 2、匹配任意一个参数

​ execution( public int com.mashibing.inter.MyCalculator.*(int,*))

​ 3、只能匹配一层路径,如果项目路径下有多层目录,那么*只能匹配一层路径

​ 4、权限位置不能使用*,如果想表示全部权限,那么不写即可

​ execution( * com.mashibing.inter.MyCalculator.*(int,*))

​ …:

​ 1、匹配多个参数,任意类型参数

​ execution( * com.king.inter.MyCalculator.*(…))

​ 2、匹配任意多层路径

​ execution( * com.king…MyCalculator.*(…))

​ 在写表达式的时候,可以有N多种写法,但是有一种最偷懒和最精确的方式:

​ 最偷懒的方式:execution(* *(…)) 或者 execution(* *.*(…))

这种方式所有的方法都有可能被织入切面,包括toString()方法,当打印对象时,会默认调用toString()方法

​ 最精确的方式:execution( public int com.king.MyCalculator.add(int,int))

​ 除此之外,在表达式中还支持 &&、||、!的方式

​ &&:两个表达式同时

​ execution( public int com.king.MyCalculator.*(…)) && execution(* *.*(int,int) )

​ ||:任意满足一个表达式即可

​ execution( public int com.king.MyCalculator.*(…)) && execution(* *.*(int,int) )

​ !:只要不是这个位置都可以进行切入

​ !execution(public Integer com.king.service.MyCalculator.add(Integer,Integer))

​ &&:两个表达式同时

​ execution( public int com.king.MyCalculator.*(…))

2、通知方法的执行顺序

​ 在之前的代码中大家一直对通知的执行顺序有疑问,其实执行的结果并没有错,大家需要注意:

​ 1、正常执行:@Before—>@AfterReturning—>@After

​ 2、异常执行:@Before—>@AfterThrowing—>@After

3、获取方法的详细信息

​ 在上面的案例中,我们并没有获取Method的详细信息,例如方法名、参数列表等信息,想要获取的话其实非常简单,只需要添加JoinPoint参数即可。

LogUtil.java

@Component
@Aspect
public class LogUtil {@Before("execution( public int com.king.MyCalculator.*(int,int))")public static void start(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();String name = joinPoint.getSignature().getName();System.out.println(name+"方法开始执行,参数是:"+ Arrays.asList(args));}@AfterReturning("execution( public int com.king.MyCalculator.*(int,int))")public static void stop(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println(name+"方法执行完成,结果是:");}@AfterThrowing("execution( public int com.king.MyCalculator.*(int,int))")public static void logException(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println(name+"方法出现异常:");}@After("execution( public int com.king.MyCalculator.*(int,int))")public static void end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println(name+"方法执行结束了......");}
}

​ 刚刚只是获取了方法的信息,但是如果需要获取结果,还需要添加另外一个方法参数,并且告诉spring使用哪个参数来进行结果接收

LogUtil.java

    @AfterReturning(value = "execution( public int com.king.MyCalculator.*(int,int))",returning = "result")public static void stop(JoinPoint joinPoint,Object result){//获取方法签名  通过签名再获取.getNameString name = joinPoint.getSignature().getName();System.out.println(name+"方法执行完成,结果是:"+result);}

​ 也可以通过相同的方式来获取异常的信息

LogUtil.java

    @AfterThrowing(value = "execution( public int com.king.MyCalculator.*(int,int))",throwing = "exception")public static void logException(JoinPoint joinPoint,Exception exception){String name = joinPoint.getSignature().getName();System.out.println(name+"方法出现异常:"+exception);}

4、spring对通过方法的要求

​ spring对于通知方法的要求并不是很高,你可以任意改变方法的返回值和方法的访问修饰符,但是唯一不能修改的就是方法的参数,不能增加参数或删除参数,会出现参数绑定的错误,原因在于通知方法是spring利用反射调用的,每次方法调用得确定这个方法的参数的值。

LogUtil.java

    @After("execution( public int com.king.MyCalculator.*(int,int))")private int end(JoinPoint joinPoint,String aa){
//        System.out.println(method.getName()+"方法执行结束了......");String name = joinPoint.getSignature().getName();System.out.println(name+"方法执行结束了......");return 0;}

5、表达式的抽取

如果在实际使用过程中,多个方法的表达式是一致的话,那么可以考虑将切入点表达式抽取出来:

​ a、随便生命一个没有实现的返回void的空方法

​ b、给方法上标注@Potintcut注解

@Component
@Aspect
public class LogUtil {@Pointcut("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")public void myPoint(){}@Before("myPoint()")public static void start(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();String name = joinPoint.getSignature().getName();System.out.println(name+"方法开始执行,参数是:"+ Arrays.asList(args));}@AfterReturning(value = "myPoint()",returning = "result")public static void stop(JoinPoint joinPoint,Object result){String name = joinPoint.getSignature().getName();System.out.println(name+"方法执行完成,结果是:"+result);}@AfterThrowing(value = "myPoint()",throwing = "exception")public static void logException(JoinPoint joinPoint,Exception exception){String name = joinPoint.getSignature().getName();System.out.println(name+"方法出现异常:"+exception.getMessage());}@After("myPoint()")private int end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println(name+"方法执行结束了......");return 0;}
}

6、环绕通知的使用

LogUtil.java

@Component
@Aspect
public class LogUtil {@Pointcut("execution( public int com.king.MyCalculator.*(int,int))")public void myPoint(){}/*** 环绕通知是spring中功能最强大的通知* @param proceedingJoinPoint* @return*/@Around("myPoint()")public Object myAround(ProceedingJoinPoint proceedingJoinPoint){Object[] args = proceedingJoinPoint.getArgs();String name = proceedingJoinPoint.getSignature().getName();Object proceed = null;try {System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));//利用反射调用目标方法,就是method.invoke()proceed = proceedingJoinPoint.proceed(args);System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);} catch (Throwable e) {System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);}finally {System.out.println("环绕后置通知"+name+"方法结束");}return proceed;}
}

​ 总结:环绕通知的执行顺序是优于普通通知的,具体的执行顺序如下:

环绕前置–>普通前置–>目标方法执行–>环绕正常结束/出现异常–>环绕后置–>普通后置–>普通返回或者异常。

但是需要注意的是,如果出现了异常,那么环绕通知会处理或者捕获异常,普通异常通知是接收不到的,因此最好的方式是在环绕异常通知中向外抛出异常。5.3.3版本的spring中普通通知和环绕通知都可以接收到异常信息了。

7、多切面运行的顺序

​ 如果有多个切面要进行执行,那么顺序是什么样的呢?

LogUtil.java

@Component
@Aspect
public class LogUtil {@Pointcut("execution( public int com.king.MyCalculator.*(int,int))")public void myPoint(){}@Before("myPoint()")public static void start(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法开始执行,参数是:"+ Arrays.asList(args));}@AfterReturning(value = "myPoint()",returning = "result")public static void stop(JoinPoint joinPoint,Object result){String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法执行完成,结果是:"+result);}@AfterThrowing(value = "myPoint()",throwing = "exception")public static void logException(JoinPoint joinPoint,Exception exception){String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法出现异常:"+exception.getMessage());}@After("myPoint()")private int end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法执行结束了......");return 0;}/*** 环绕通知是spring中功能最强大的通知* @param proceedingJoinPoint* @return*///@Around("myPoint()")public Object myAround(ProceedingJoinPoint proceedingJoinPoint){Object[] args = proceedingJoinPoint.getArgs();String name = proceedingJoinPoint.getSignature().getName();Object proceed = null;try {System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));//利用反射调用目标方法,就是method.invoke()proceed = proceedingJoinPoint.proceed(args);System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);} catch (Throwable e) {System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);}finally {System.out.println("环绕后置通知"+name+"方法结束");}return proceed;}
}

SecurityAspect.java

@Component
@Aspect
public class SecurityAspect {@Before("com.king.util.LogUtil.myPoint()")public static void start(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法开始执行,参数是:"+ Arrays.asList(args));}@AfterReturning(value = "com.king.util.LogUtil.myPoint()",returning = "result")public static void stop(JoinPoint joinPoint,Object result){String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法执行完成,结果是:"+result);}@AfterThrowing(value = "com.king.util.LogUtil.myPoint()",throwing = "exception")public static void logException(JoinPoint joinPoint,Exception exception){String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法出现异常:"+exception.getMessage());}@After("com.king.util.LogUtil.myPoint()")private int end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法执行结束了......");return 0;}}

​ 在spring中,默认是按照切面名称的字典顺序进行执行的,但是如果想自己改变具体的执行顺序的话,可以使用@Order注解来解决,数值越小,优先级越高。Order的取值范围为int类型的取值范围

LogUtil.java

@Component
@Aspect
@Order(2)
public class LogUtil {@Pointcut("execution( public int com.king.inter.MyCalculator.*(int,int))")public void myPoint(){}@Before("myPoint()")public static void start(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法开始执行,参数是:"+ Arrays.asList(args));}@AfterReturning(value = "myPoint()",returning = "result")public static void stop(JoinPoint joinPoint,Object result){String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法执行完成,结果是:"+result);}@AfterThrowing(value = "myPoint()",throwing = "exception")public static void logException(JoinPoint joinPoint,Exception exception){String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法出现异常:"+exception.getMessage());}@After("myPoint()")private int end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println("Log:"+name+"方法执行结束了......");return 0;}/*** 环绕通知是spring中功能最强大的通知* @param proceedingJoinPoint* @return*///@Around("myPoint()")public Object myAround(ProceedingJoinPoint proceedingJoinPoint){Object[] args = proceedingJoinPoint.getArgs();String name = proceedingJoinPoint.getSignature().getName();Object proceed = null;try {System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));//利用反射调用目标方法,就是method.invoke()proceed = proceedingJoinPoint.proceed(args);System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+proceed);} catch (Throwable e) {System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);}finally {System.out.println("环绕后置通知"+name+"方法结束");}return proceed;}
}

SecurityAspect.java

@Component
@Aspect
@Order(1)
public class SecurityAspect {@Before("com.king.util.LogUtil.myPoint()")public static void start(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法开始执行,参数是:"+ Arrays.asList(args));}@AfterReturning(value = "com.king.util.LogUtil.myPoint()",returning = "result")public static void stop(JoinPoint joinPoint,Object result){String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法执行完成,结果是:"+result);}@AfterThrowing(value = "com.king.util.LogUtil.myPoint()",throwing = "exception")public static void logException(JoinPoint joinPoint,Exception exception){String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法出现异常:"+exception.getMessage());}@After("com.king.util.LogUtil.myPoint()")private int end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println("Security:"+name+"方法执行结束了......");return 0;}/*** 环绕通知是spring中功能最强大的通知* @param proceedingJoinPoint* @return*///@Around("myPoint()")public Object myAround(ProceedingJoinPoint proceedingJoinPoint){Object[] args = proceedingJoinPoint.getArgs();String name = proceedingJoinPoint.getSignature().getName();Object result = null;try {System.out.println("环绕前置通知:"+name+"方法开始,参数是"+Arrays.asList(args));//利用反射调用目标方法,就是method.invoke()result = proceedingJoinPoint.proceed(args);System.out.println("环绕返回通知:"+name+"方法返回,返回值是"+result);} catch (Throwable e) {System.out.println("环绕异常通知"+name+"方法出现异常,异常信息是:"+e);}finally {System.out.println("环绕后置通知"+name+"方法结束");}return proceed;}
}

​ 如果需要添加环绕通知呢,具体的执行顺序又会是什么顺序呢?

​ 因为环绕通知在进行添加的时候,是在切面层引入的,所以在哪个切面添加环绕通知,那么就会在哪个切面执行。

5.3、SpringAOP基于xml的使用

​ 之前我们讲解了基于注解的AOP配置方式,下面我们开始讲一下基于xml的配置方式,虽然在现在的企业级开发中使用注解的方式比较多,但是你不能不会,因此需要简单的进行配置,注解配置快速简单,与xml配置的方式共同使用会更完善。

切面类:

LogUtil

@Component
@Aspect
public class LogUtil {public void start(JoinPoint joinPoint) {String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println(name + "方法开始执行,参数为:" + Arrays.asList(args));}public void stop(JoinPoint joinPoint ,Integer result){String name = joinPoint.getSignature().getName();System.out.println(name + "方法执行结束,结果为:" + result);}public void logException(JoinPoint joinPoint,Exception exception) {String name = joinPoint.getSignature().getName();System.out.println(name + "方法出现异常" + exception);}public void end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println(name + "方法运行结束");}}

SeciUtil

public class SeciUtil {public void start(JoinPoint joinPoint) {String name = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println(name + "Sci方法开始执行,参数为:" + Arrays.asList(args));}public void stop(JoinPoint joinPoint , Integer result){String name = joinPoint.getSignature().getName();System.out.println(name + "Sci方法执行结束,结果为:" + result);}public void logException(JoinPoint joinPoint,Exception exception) {String name = joinPoint.getSignature().getName();System.out.println(name + "Sci方法出现异常" + exception);}public void end(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();System.out.println(name + "Sci方法运行结束");}
}

被织入的类

Calcultor

public interface Calcultor {Integer add(Integer i,Integer j);Integer div(Integer i,Integer j);}

MyCalcultor

@Service
public class MyCalcultor implements Calcultor{@Overridepublic Integer add(Integer i, Integer j) {return i + j;}@Overridepublic Integer div(Integer i, Integer j) {return i - j;}
}

aop.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!--    配置包扫描--><context:component-scan base-package="com.king"/><!--    织入切面--><aop:aspectj-autoproxy/><bean id="logUtil" class="com.king.util.LogUtil"/><bean id="myCalcultor" class="com.king.MyCalcultor"/><bean id="sciUtil" class="com.king.util.SeciUtil"/><!--配置AOP--><aop:config>
<!--        将通用的表达式给抽取出来全局的切面,哪个切面类都可以使用--><aop:pointcut id="myPoint" expression="execution(* com.king.MyCalcultor.*(..))"/><!--定义切入到哪个类--><aop:aspect ref="logUtil" order="0"><!--定义通知在哪些方法上使用--><aop:before method="start" pointcut-ref="myPoint"/><aop:after method="end" pointcut-ref="myPoint"/><aop:after-returning method="stop" pointcut-ref="myPoint" returning="result"/><aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="exception"/></aop:aspect><aop:aspect ref="sciUtil" order="-1">
<!--            自己的切面,只有自己可用,其他切面类不可以用--><aop:pointcut id="mypoint2" expression="execution(Integer com.king.MyCalcultor.add(Integer,Integer))"/><aop:before method="start" pointcut="execution(* com.king.MyCalcultor.*(..))"/><aop:after method="end" pointcut-ref="myPoint"/><aop:after-throwing method="logException" pointcut-ref="mypoint2" throwing="exception"/><aop:after-returning method="stop" pointcut-ref="mypoint2" returning="result"/></aop:aspect></aop:config>
</beans>

测试

@Test
public void test01(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");Calcultor bean = context.getBean(Calcultor.class);bean.add(1, 1);
}//结果:
addSci方法开始执行,参数为:[1, 1]
add方法开始执行,参数为:[1, 1]
add方法运行结束
add方法执行结束,结果为:2
addSci方法运行结束
addSci方法执行结束,结果为:2

5.4、Spring AOP的应用配置

1、Spring JdbcTemplate

由于有了mybatis,这个东西很少使用,但是我们还是需要知道,还是要敲一遍

​ 在spring中为了更加方便的操作JDBC,在JDBC的基础之上定义了一个抽象层,此设计的目的是为不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式,可以尽可能保留灵活性,将数据库存取的工作量降到最低。

1、导入pom依赖

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>com.king</groupId><artifactId>spring_demo</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--spring核心包所需的依赖--><!-- https://mvnrepository.com/artifact/org.springframework/spring-context --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.3.RELEASE</version></dependency><!--数据库连接池--><!-- https://mvnrepository.com/artifact/com.alibaba/druid  --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency><!--java通过jdbc操作mysql数据库的依赖  mysql依赖--><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><!--cglib动态代理依赖--><!-- https://mvnrepository.com/artifact/cglib/cglib --><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency><!--        导入springspring-aop:AOP核心功能,例如代理工厂等等aspectjweaver:简单理解,支持切入点表达式等等aspectjrt:简单理解,支持aop相关注解等等说明:aspectjweaver包含aspectjrt,所以我们只需要引入aspectjweaver依赖包就可以了--><!--支持切入点表达式,和支持AOP相关注解的依赖包--><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency><!--spring切面Bean所需要的包--><!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!--提供对AspectJ的支持,可以方便的将面向切面的功能集成进IDE中--><!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.3.RELEASE</version></dependency></dependencies></project>

dbconfig.properties

jdbc.username=root
password=root
url=jdbc:mysql://localhost:3306/user?serverTimezone=UTC
driverClassName=com.mysql.cj.jdbc.Driver

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd
"><!--引入外部配置文件--><context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder><!--注入第三方bean--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driverClassName}"></property></bean>
</beans>

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);System.out.println(dataSource);System.out.println(dataSource.getConnection());}
}//结果:
{CreateTime:"2021-01-18 23:10:46",ActiveCount:0,PoolingCount:0,CreateCount:0,DestroyCount:0,CloseCount:0,ConnectCount:0,Connections:[]
}
一月 18, 2021 11:10:47 下午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
信息: {dataSource-1} inited
com.mysql.cj.jdbc.ConnectionImpl@37918c79
2、给spring容器添加JdbcTemplate

​ spring容器提供了一个JdbcTemplate类,用来方便操作数据库。

1、添加pom依赖

pom.xml

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

jdbcTemplate.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd
"><context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driverClassName}"></property></bean><!--注入spring中jdbc模板类依赖--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg></bean>
</beans>

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);System.out.println(jdbcTemplate);}
}//结果:
org.springframework.jdbc.core.JdbcTemplate@2aece37d
3、插入数据

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);String sql = "insert into login(id,uname,pwd) values(?,?,?)";int result = jdbcTemplate.update(sql, 3,"胡雪岩", "120");System.out.println(result);}
}
4、批量插入数据

MyTest.java

/*** 批量插入数据*/
@Test
public void test04(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");JdbcTemplate template = context.getBean("jdbcTemplate", JdbcTemplate.class);String sql = "insert into login(id,uname,pwd) values(?,?,?)";ArrayList<Object[]> list = new ArrayList<>();list.add(new Object[]{4,"曾国藩","879"});list.add(new Object[]{5,"李鸿章","456"});list.add(new Object[]{6,"黑石","741"});int[] ints = template.batchUpdate(sql, list);for (int anInt : ints) {System.out.println(anInt);}
}
5、查询某个值,并以对象的方式返回

MyTest.java

/*** 查询某个值,以对象的方式返回*/
@Test
public void test05(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");JdbcTemplate template = context.getBean("jdbcTemplate", JdbcTemplate.class);String sql = "select * from login where id = ?";List<Login> result = template.query(sql, new BeanPropertyRowMapper<>(Login.class), 1);System.out.println(result);
}//结果:
[Login{id=1, uname='zhangsan', pwd='123'}]
6、查询返回集合对象

MyTest.java

@Test
public void test05(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");JdbcTemplate template = context.getBean("jdbcTemplate", JdbcTemplate.class);String sql = "select * from login ";List<Login> result = template.query(sql, new BeanPropertyRowMapper<>(Login.class));for (Login login : result) {System.out.println(login);}
}//结果:
Login{id=1, uname='zhangsan', pwd='123'}
Login{id=2, uname='lisi', pwd='456'}
Login{id=3, uname='胡雪岩', pwd='120'}
Login{id=4, uname='曾国藩', pwd='879'}
Login{id=5, uname='李鸿章', pwd='456'}
Login{id=6, uname='黑石', pwd='741'}
7、返回组合函数的值

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);String sql = "select max(sal) from emp";Double aDouble = jdbcTemplate.queryForObject(sql, Double.class);System.out.println(aDouble);}
}@Test
public void test05(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");JdbcTemplate template = context.getBean("jdbcTemplate", JdbcTemplate.class);String sql = "select max(id) from login ";Integer integer = template.queryForObject(sql, Integer.class);System.out.println(integer);
}
8、使用具备命名函数的JdbcTemplate

Spring JDBC包提供了JdbcTemplate和它的两个兄弟SimpleJdbcTemplate和NamedParameterJdbcTemplate。

NamedParameterJdbcTemplate类是基于JdbcTemplate类,并对它进行了封装从而支持命名参数特性。

NamedParameterJdbcTemplate主要提供以下三类方法:execute方法、query及queryForXXX方法、update及batchUpdate方法。

命名参数设值有两种方式:java.util.Map和SqlParameterSource:

1)Map:使用Map键数据来对于命名参数,而Map值数据用于设值;

2)SqlParameterSource:可以使用SqlParameterSource实现作为来实现为命名参数设值,默认有MapSqlParameterSource和BeanPropertySqlParameterSource实现;MapSqlParameterSource实现非常简单,只是封装了java.util.Map;而BeanPropertySqlParameterSource封装了一个JavaBean对象,通过JavaBean对象属性来决定命名参数的值。

其中有两个比较实用的两个类,分别是BeanPropertySqlParameterSource、BeanPropertyRowMapper。

BeanPropertySqlParameterSource:可以把实例转化成SqlParameterSource

例,new BeanPropertySqlParameterSource(new User);

BeanPropertyRowMapper:可以把返回的每一行转化成对应的对象

例,new BeanPropertyRowMapper<>(CustomSearchVo.class);

jdbcTemplate.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd
"><context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driverClassName}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg></bean><!--注入命名函数的jdbc模板--><bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg></bean>
</beans>

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");NamedParameterJdbcTemplate jdbcTemplate = context.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);String sql = "insert into emp(empno,ename) values(:empno,:ename)";Map<String,Object> map = new HashMap<>();map.put("empno",2222);map.put("ename","sili");int update = jdbcTemplate.update(sql, map);System.out.println(update);}
}@Test
public void test06(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");NamedParameterJdbcTemplate bean = context.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);String sql = "insert into login(id,uname,pwd) values(:id,:uname,:pwd)";Login wangwu = new Login(7, "wangwu", "7845");Login liliu = new Login(8, "liliu", "5123");BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(liliu);int update = bean.update(sql,source);System.out.println(update);
}
9、整合EmpDao

jdbcTemplate.xml

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

EmpDao.java

@Repository
public class LoginDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void save(Login login){String sql = "insert into login(id,uname,pwd) values(?,?,?)";int king = jdbcTemplate.update(sql, login.getId(), login.getUname(), login.getPwd());System.out.println(king);}}

MyTest.java

@Test
public void test07(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");LoginDao loginDao = context.getBean("loginDao", LoginDao.class);loginDao.save(new Login(10,"joye","78965231"));
}

2、声明式事务

1、环境准备

tx.sql

/*
Navicat MySQL Data TransferSource Server         : localhost
Source Server Version : 50528
Source Host           : localhost:3306
Source Database       : txTarget Server Type    : MYSQL
Target Server Version : 50528
File Encoding         : 65001Date: 2020-02-13 19:19:32
*/SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `account`
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (`username` varchar(10) NOT NULL DEFAULT '',`balance` double DEFAULT NULL,PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO account VALUES ('lisi', '1000');
INSERT INTO account VALUES ('zhangsan', '1000');-- ----------------------------
-- Table structure for `book`
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (`id` int(10) NOT NULL,`book_name` varchar(10) DEFAULT NULL,`price` double DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO book VALUES ('1', '西游记', '100');
INSERT INTO book VALUES ('2', '水浒传', '100');
INSERT INTO book VALUES ('3', '三国演义', '100');
INSERT INTO book VALUES ('4', '红楼梦', '100');-- ----------------------------
-- Table structure for `book_stock`
-- ----------------------------
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (`id` int(255) NOT NULL DEFAULT '0',`stock` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- ----------------------------
-- Records of book_stock
-- ----------------------------
INSERT INTO book_stock VALUES ('1', '1000');
INSERT INTO book_stock VALUES ('2', '1000');
INSERT INTO book_stock VALUES ('3', '1000');
INSERT INTO book_stock VALUES ('4', '1000');

BookDao.java

@Repository
public class BookDao {@Autowiredprivate JdbcTemplate jdbcTemplate;/*** 减去某个用户的余额* @param userName* @param price*/public void updateBalance(String userName,int price){String sql = "update account set balance=balance-? where username=?";jdbcTemplate.update(sql,price,userName);}/*** 按照图书的id来获取图书的价格* @param id* @return*/public int getPrice(int id){String sql = "select price from book where id=?";return jdbcTemplate.queryForObject(sql,Integer.class,id);}/*** 减库存,减去某本书的库存* @param id*/public void updateStock(int id){String sql = "update book_stock set stock=stock-1 where id=?";jdbcTemplate.update(sql,id);}
}

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/public void checkout(String username,int id){bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}
}

MyTest.java

public class MyTest {public static void main(String[] args) throws SQLException {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");BookService bookService = context.getBean("bookService", BookService.class);bookService.checkout("zhangsan","1");}
}//结果:
zhangsan的余额减少100

总结:在事务控制方面,主要有两个分类:

编程式事务:在代码中直接加入处理事务的逻辑,可能需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法

声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。

2、声明式事务的简单配置

​ Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

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

​ 事务管理器可以以普通的bean的形式声明在Spring IOC容器中。下图是spring提供的事务管理器

1、在配置文件中添加事务管理器

jdbcTemplate.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd
"><context:component-scan base-package="com.king"></context:component-scan><context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driverClassName}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg></bean><!--事务控制--><!--配置事务管理器的bean--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!--开启基于注解的事务控制模式,依赖tx名称空间--><tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

BookService.java

@Service
public class BookService {@AutowiredBookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactionalpublic void checkout(String username,int id){bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}
}
3、事务配置的属性

​ isolation:设置事务的隔离级别

​ propagation:事务的传播行为

​ noRollbackFor:那些异常事务可以不回滚

​ noRollbackForClassName:填写的参数是全类名

​ rollbackFor:哪些异常事务需要回滚

​ rollbackForClassName:填写的参数是全类名

​ readOnly:设置事务是否为只读事务

​ timeout:事务超出指定执行时长后自动终止并回滚,单位是秒

4、测试超时属性

定义一个超时时间,当程序运行超过这个超时时间,则回滚程序不运行,否则运行成功

BookService.java

@Service
public class BookService {@AutowiredBookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(timeout = 3)public void checkout(String username,int id){bookDao.updateStock(id);try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}
}
5、设置事务只读

​ 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;

​ 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

​ 对于只读查询,可以指定事务类型为readonly,即只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段

BookService.java

@Service
public class BookService {@AutowiredBookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(timeout = 3,readOnly = true)public void checkout(String username,int id){bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}
}
6、设置哪些异常不回滚

注意:运行时异常默认回滚,编译时异常默认不回滚

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username 用户名* @param id 编号* 设置除数为0异常 和 空指针异常 不回滚*/@Transactional(timeout = 3,noRollbackFor = {ArithmeticException.class,NullPointerException.class})public void checkout(String username,int id){//int i = 1 / 0; 异常出现在这里 结果不变 原因:这里出现异常 程序终止运行 出现异常之前的代码不回滚bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);int i = 1/0; //异常放到这里 ,结果回会变 原因上面的程序已经执行完毕,出现异常不回滚,所以结果会变}//同上面作用一样,两种不同的写法@Transactional(timeout = 3,noRollbackForClassName = {"java.lang.ArithmeticException"})public void checkout2(String username,int id){bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);int i = 1/0;}@Transactional(timeout = 3,noRollbackForClassName = {"java.lang.ArithmeticException"})public void checkout4(String username , int id){//更新书的库存bookDao.updateStock(id);//拿到书的价格int price = bookDao.getPrice(id);//异常出现在此处,说明程序运行到这里就结束了,这里出现的异常不回滚,之前执行的程序结果就是已经敲定了,不回滚 所以 用户的余额不会变//但异常语句之后的代码不会执行int i = 1/0;//更新用户的余额bookDao.updateBalance(username,price);}}

特别的地方:

@Transactional(timeout = 3,noRollbackForClassName = {"java.lang.ArithmeticException"})
public void checkout4(String username , int id){//更新书的库存bookDao.updateStock(id);//拿到书的价格int price = bookDao.getPrice(id);//当异常在这里被捕获之后,结果还是会变,异常被捕获后,之后的代码还是要运行的,所以用户的余额还是会被更新try {int i = 1/0;} catch (Exception e) {e.printStackTrace();}//更新用户的余额bookDao.updateBalance(username,price);
}
7、设置哪些异常回滚

BookService.java

@Service
public class BookService {@AutowiredBookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id* 文件没有找到异常 ,发生这个异常回滚*/@Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class})public void checkout(String username,int id) throws FileNotFoundException {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);
//        int i = 1/0;new FileInputStream("aaa.txt"); //这里出现异常,之前的运行的程序全部不作数,回滚}
}

注意:

    /*** 设置哪些异常回滚* 设置文件找不到异常回滚* @param username* @param id*/@Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class})public void checkout3(String username , int id) throws FileNotFoundException {//更新书的库存bookDao.updateStock(id);//拿到书的价格int price = bookDao.getPrice(id);//更新用户的余额bookDao.updateBalance(username,price);//try {new FileInputStream("aa.txt");} catch (FileNotFoundException e) {e.printStackTrace();}}
8、设置隔离级别

​ 事务隔离级别,就是为了解决上面几种问题而诞生的。为什么要有事务隔离级别,因为事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别,对于在事务隔离级别之外会产生的并发问题,在代码中做补偿。

事务隔离级别有4种,但是像Spring会提供给用户5种,来看一下:

1、DEFAULT

默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。顺便说一句,如果使用的MySQL,可以使用"select @@tx_isolation"来查看默认的事务隔离级别

2、READ_UNCOMMITTED

读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用

3、READ_COMMITED

读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读

4、REPEATABLE_READ

重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决

5、SERLALIZABLE

串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

网上专门有图用表格的形式列出了事务隔离级别解决的并发问题:

再必须强调一遍,不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,因此实际开发中往往要在效率和并发正确性之间做一个取舍,一般情况下会设置为READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了。

BookService.java

@Service
public class BookService {@AutowiredBookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(timeout = 3,isolation = Isolation.READ_COMMITTED)public void checkout(String username,int id) throws FileNotFoundException {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);
//        int i = 1/0;new FileInputStream("aaa.txt");}
}
9、事务的传播特性

事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?

spring的事务传播行为一共有7种:

10、测试事务的传播特性
1、REQUIRED

默认传播特性:REQUIRED

BookDao.java

@Repository
public class BookDao {@Autowiredprivate JdbcTemplate jdbcTemplate;/*** 减去某个用户的余额* @param userName* @param price*/public void updateBalance(String userName,int price){String sql = "update account set balance=balance-? where username=?";jdbcTemplate.update(sql,price,userName);}/*** 按照图书的id来获取图书的价格* @param id* @return*/public int getPrice(int id){String sql = "select price from book where id=?";return jdbcTemplate.queryForObject(sql,Integer.class,id);}/*** 减库存,减去某本书的库存* @param id*/public void updateStock(int id){String sql = "update book_stock set stock=stock-1 where id=?";jdbcTemplate.update(sql,id);}/*** 修改图书价格* @param id* @param price*/public void updatePrice(int id,int price){String sql = "update book set price=? where id =?";jdbcTemplate.update(sql,price,id);}}

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.REQUIRED)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}@Transactional(propagation = Propagation.REQUIRED)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);//这里出现异常,由于这个事务方法被外部的事务调用,由外部的事务方法进行统一管理,所以,出现异常全部回滚int i = 1/0;}}

MulService.java

@Service
public class MulService {@Autowiredprivate BookService bookService;@Transactional(propagation = Propagation.REQUIRED)public void mulTx(){bookService.checkout("zhangsan",1);bookService.updatePrice(1,1000);}
}

MyTest.java

public class MyTest {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("jdbcTemplate.xml");MulService mulService = context.getBean("mulService", MulService.class);mulService.mulTx();}
}

​ 通过上图的结果发现,如果设置的传播特性是Required,那么所有的事务都会统一成一个事务,一旦发生错误,所有的数据都要进行回滚。类似这样:

一个大的事务套着若干个小事务,由大的事务统一管理,当任意一个事务中出现异常或者错误,都回滚

注意:

当只有两个单独的事务的时候,只有事务2和事务3,没有事务1,则两个事务单独管理,两个事务的 执行,互不干扰。哪个事务里出现了异常,哪个事务就回滚,当然,捕获异常之后就不会回滚。

情况一

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.REQUIRED)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}@Transactional(propagation = Propagation.REQUIRED)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);}
}

MulService.java

@Service
public class MulService {@Autowiredprivate BookService bookService;@Transactionalpublic void mulTx(){bookService.checkout("zhangsan",1);bookService.updatePrice(1,1000);int i = 1/0;}
}

​ 将bookservice方法的传播行为为Required,并且将报错设置在MulService中,发现会都进行回滚,当然当MulService中的异常被捕获之后值会发生改变,不会回滚。


情况二

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.REQUIRED)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);int i = 1/0;}@Transactional(propagation = Propagation.REQUIRED)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);}
}

MulService.java

@Service
public class MulService {@Autowiredprivate BookService bookService;@Transactional(propagation = Propagation.REQUIRED)public void mulTx(){try {bookService.checkout("zhangsan",1);} catch (Exception e) {e.printStackTrace();}bookService.updatePrice(1,1000);}
}

​ 当BookService中出现异常,在MulService中捕获之后,会发生回滚,所有的值都不会被修改


2、REQUIRES_NEW

REQUIRES_NEW:开启一个新的事务,单独运行

情况一

BookService.java

@Service
public class BookService{@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.REQUIRES_NEW)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}@Transactional(propagation = Propagation.REQUIRED)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);//这里出现异常 这里进行回滚int i = 1/0;}
}

​ 通过修改checkout方法的传播特性为Required_new,发现价格进行了回滚,而其他的数据没有进行回滚。

​ Required_new的事务方法正常运行,这是一个独立的事务,出现异常try-catch之后也是正常运行


情况二

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.REQUIRES_NEW)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}@Transactional(propagation = Propagation.REQUIRES_NEW)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);}
}

MulService.java

@Service
public class MulService {@Autowiredprivate BookService bookService;@Transactionalpublic void mulTx(){bookService.checkout("zhangsan",1);bookService.updatePrice(1,1000);int i = 1/0;}
}

​ 将bookservice方法的传播行为为Requires_new,并且将报错设置在MulService中,发现都不会进行回滚。因为REQUIRES_NEW是单独事务,不受外部的事务方法的管控。


情况三

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.REQUIRES_NEW)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);int i = 1/0;}@Transactional(propagation = Propagation.REQUIRED)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);}
}

MulService.java

@Service
public class MulService {@Autowiredprivate BookService bookService;@Transactional(propagation = Propagation.REQUIRED)public void mulTx(){try {bookService.checkout("zhangsan",1);} catch (Exception e) {e.printStackTrace();}bookService.updatePrice(1,1000);}
}

​ 当BookService中出现异常,在MulService中捕获之后,余额和库存会回滚,价格会被修改。原因REQUIRES_NEW

修饰是一个单独的事务,被外部事务调用,出现异常,也只影响自己。跟其他的事务无关。


BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.REQUIRES_NEW)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);}@Transactional(propagation = Propagation.REQUIRES_NEW)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);}@Transactionalpublic void mulTx(){checkout("zhangsan",1);updatePrice(1,1000);int i = 1/0;}
}

​ 如果在bookservice执行的话,会发现刚刚的效果就没有了,原因是外层调用的时候使用的AOP,但是本类方法自己的调用就是最最普通的调用,就是同一个事务。

总结:

1、事务传播级别是REQUIRED,当checkout()被调用时(假定被另一类中commit()调用),如果checkout()中的代码抛出异常,即便被捕获,commit()中的其他代码都会roll back2、是REQUIRES_NEW,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,不会导致checkout()回滚3、是NESTED,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,会导致checkout()回滚PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. 另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务,  它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. 由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
3、NESTED

如果有事务在运行,就必须在其嵌套的事务内运行,否则单独开启一个新的事务。

情况一

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.NESTED)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);int i = 1/0;}@Transactional(propagation = Propagation.REQUIRED)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);}
}

MulService.java

@Service
public class MulService {@Autowiredprivate BookService bookService;@Transactional(propagation = Propagation.REQUIRED)public void mulTx(){try {bookService.checkout("zhangsan",1);} catch (Exception e) {e.printStackTrace();}bookService.updatePrice(1,1000);}
}

​ 当BookService中出现异常,在MulService中捕获之后,余额和库存会回滚,价格会被修改,看上去Requires_new没啥区别。


情况二

BookService.java

@Service
public class BookService {@Autowiredprivate BookDao bookDao;/*** 结账:传入哪个用户买了哪本书* @param username* @param id*/@Transactional(propagation = Propagation.NESTED)public void checkout(String username,int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username,price);int i = 1/0;}@Transactional(propagation = Propagation.REQUIRED)public void updatePrice(int id,int price){bookDao.updatePrice(id,price);}
}

MulService.java

@Service
public class MulService {@Autowiredprivate BookService bookService;//@Transactional(propagation = Propagation.REQUIRED)public void mulTx(){bookService.checkout("zhangsan",1);bookService.updatePrice(1,1000);}
}

​ 当BookService中出现异常,在MulService中没有事务控制,结果全部都会回滚,谁都不会发生改变。

4、SUPPORTS

这个东西没啥用,如果有事务,就运行在事务里面,如果没事务,就不运行在事务里面

BookService.java

@Transactional(propagation = Propagation.SUPPORTS)
public void checkout(String username, int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username, price);int i = 1/0;
}

MulService.java

//@Transactional
public void checkout(){bookService.checkout("zhangsan",1);
}

当下面的事务方法不是事务方法时

不受下面的事务方法的管控,上面SUPPORTS修饰的就是普通方法,不受事务管控,值会修改,不会进行回滚。

当下面的事务方法是事务方法时

会进行回滚

5、NOT_SUPPORTED

不在事务里面运行,不受事务的管控

BookService.java

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void checkout(String username, int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username, price);int i = 1/0;
}

MulService.java

@Transactional
public void checkout(){bookService.checkout("zhangsan",1);
}

发现结果会改变,这和没有事务时是一样的,不受事务的管控

6、MANDATORY

必须运行在事务内部,不运行在事务内部就抛异常

BookService.java

@Transactional(propagation = Propagation.MANDATORY)
public void checkout(String username, int id) {bookDao.updateStock(id);int price = bookDao.getPrice(id);bookDao.updateBalance(username, price);int i = 1/0;
}

MulService.java

//@Transactional
public void checkout(){bookService.checkout("zhangsan",1);
}

当没有被其他事务方法调用时,我们发现报的不是除数为0的异常

结果也不会进行修改


如果被其他事务调用,结果也不会修改,会发生回滚

7、NEVER

与MANDATORY相反,不能运行在事务中,如果运行在事务中就抛出异常

这里不再演示

3、基于xml的事务配置

jdbcTemplate.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd
"><context:component-scan base-package="com.mashibing"></context:component-scan><context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driverClassName}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg></bean><bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg></bean><!--事务控制--><!--配置事务管理器的bean--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!--基于xml配置的事务:依赖tx名称空间和aop名称空间1、spring中提供事务管理器(切面),配置这个事务管理器2、配置出事务方法3、告诉spring哪些方法是事务方法(事务切面按照我们的切入点表达式去切入事务方法)--><bean id="bookService" class="com.mashibing.service.BookService"></bean><aop:config><aop:pointcut id="txPoint" expression="execution(* com.mashibing.service.*.*(..))"/><!--事务建议:advice-ref:指向事务管理器的配置--><aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"></aop:advisor></aop:config><tx:advice id="myAdvice" transaction-manager="transactionManager"><!--事务属性--><tx:attributes><!--指明哪些方法是事务方法--><tx:method name="*"/><tx:method name="checkout" propagation="REQUIRED"/><tx:method name="get*" read-only="true"></tx:method></tx:attributes></tx:advice>
</beans>

spring基于5.3.0GA版本-笔记相关推荐

  1. Spring 5 详细教程 IDEA版本 复习笔记 狂神笔记 面试宝典

    文章目录 一.Spring 简介 1.简介 2.特点 3.组成 二.Spring IOC 项目准备 1.IOC本质 分析 IOC本质 2. IOC 容器 官方解读 3.Spring IOC 创建对象过 ...

  2. 厉害了,Spring Cloud Alibaba 发布 GA 版本!

    小马哥 & Josh Long 喜欢写一首诗一般的代码,更喜欢和你共同 code review,英雄的相惜,犹如时间沉淀下来的对话,历久方弥新. 相见如故,@杭州. 4 月 18 日,Josh ...

  3. spring cloud Alibaba 的 Nacos学习笔记

    spring cloud Alibaba 的 Nacos学习笔记 文章目录 spring cloud Alibaba 的 Nacos学习笔记 下载nacos spring cloud Alibaba依 ...

  4. spring 基于注解的控制器配置

    http://ttaale.iteye.com/blog/787586 spring 基于注解的控制器配置 博客分类: spring SpringBeanServletMVCWeb 13.12. 基于 ...

  5. Spring Cloud 升级最新 Finchley 版本,踩了所有的坑

    转载自   Spring Cloud 升级最新 Finchley 版本,踩了所有的坑 Spring Boot 2.x 已经发布了很久,现在 Spring Cloud 也发布了 基于 Spring Bo ...

  6. spring基于注释的配置_基于注释的Spring MVC Web应用程序入门

    spring基于注释的配置 这是使Maven启动Spring 3 MVC项目的最小方法. 首先创建spring-web-annotation/pom.xml文件,并包含Spring依赖项: <? ...

  7. Spring Data ElasticSearch 3.2版本发布,相关新特性说明

    由于ElasticSearch更新的速度非常的快,那么就造成了一些常见的Java交互API更新速度无法匹配最新的版本等情况,比如Spring Data ElasticSearch.对于习惯了使用其他类 ...

  8. Spring 实战 第4版 读书笔记

    第一部分:Spring的核心 1.第一章:Spring之旅 1.1.简化Java开发 创建Spring的主要目的是用来替代更加重量级的企业级Java技术,尤其是EJB.相对EJB来说,Spring提供 ...

  9. Spring基于注解TestContext 测试框架使用详解

    原创整理不易,转载请注明出处:Spring基于注解TestContext 测试框架使用详解 代码下载地址:http://www.zuidaima.com/share/1775574182939648. ...

最新文章

  1. MindSpore算子支持类
  2. Deep Learning(深度学习)相关网站
  3. SweetAlert插件 弹框插件
  4. python 2.7 简单模拟登陆网站
  5. hdu oj1092题解
  6. Memcached 缓存个体,对象,泛型,表
  7. Java中<? super T>和List<? extends T>的区别
  8. 09.07 jQuery 随意整理
  9. Java dom4j解析RESTFull风格发布的WebService的xml文件
  10. 使用feof()判断文件结束时会多输出内容的原因
  11. 整理自己的.net工具库
  12. c语言中初始值的作用,初始C语言学习
  13. Transformers实战系列 之 文本生成
  14. 汇编@data_macOS上的汇编入门(十三)——从编译到执行
  15. python循环习题
  16. 嵌入式软件工程师是前端还是后端_【一线】当嵌入式软件工程师有什么感受
  17. 常用的计算机辅助存储器有,重学大学计算机教程--辅助存储器(磁盘、磁带、固态硬盘)...
  18. 华为ADS高阶自动驾驶视频和技术方案曝光!
  19. LeetCode: 872. Leaf-Similar Trees
  20. 国外Windows主机的特点

热门文章

  1. 安卓Android 7.1.1 shortcut实现桌面图标快捷方式跳转,类似IOS 3d touch
  2. 杀毒软件不能为了一己私欲剥夺用户选择权
  3. SQL注入 安全狗apache3.5.12048版本绕过
  4. kali linux安装到U盘 无法启动,vmware 安装 kali linux 系统到U盘 启动错误(initramfs:) 修复方法...
  5. 一款HTML5网页播放器全功能版的界面
  6. Web前端发展方向有哪些?可以做什么岗位?
  7. 设计科目类Subject,其类的实例表示大学课程科目。一门课程包含课程名(String)、课程编码(code)为六位字符串,前三位字符为字母代表学科领域,后三位字符为数字,课程编码是唯一的。
  8. 关系,关系模式,关系模型区别和联系
  9. Android 光芒四射的View
  10. 100天精通Python(可视化篇)——第94天:Pyecharts绘制多种炫酷散点图(参数说明+代码实战)