Spring 框架概述
- Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中使用 Java 语言所需的一切,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种体系结构。从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8),并且已经为 JDK 9 提供了现成的支持。
- Spring 是分层的 Java SE/EE full-stack 轻量级开源框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,使用基本的 JavaBean 完成以前只可能由 EJB 完成的工作,取代了 EJB 臃肿和低效的开发模式。
- Spring 是开源的。它拥有一个庞大而活跃的社区,可以根据各种实际用例提供持续的反馈。这帮助 Spring 在很长一段时间内成功地 Developing 了。
Spring优点
- 方便解耦,简化开发
- Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。
- 方便集成各大优秀框架
- Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。
- 方便程序的测试
- Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。
- AOP 编程的支持
- Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。
- 声明式事务的支持
- 只需要通过配置就可以完成对事务的管理,而无须手动编程。
Spring体系结构
Spring 框架采用分层架构,根据不同的功能被划分成了多个模块,这些模块大体可分为 Data Access/Integration、Web、AOP、Aspects、Messaging、Instrumentation、Core Container 和 Test,具体如下图所示:
Data Access/Integration(数据访问/集成)
数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。 ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。 OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。 JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。 Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。
Web 模块
Spring 的 Web 层包括 Web、Servlet、Struts 和 Portlet 组件,具体介绍如下。
Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。 Servlet模块:包括 Spring 模型—视图—控制器(MVC)实现 Web 应用程序。 Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。 Portlet 模块:提供了在 Portlet 环境中使用 MV C实现,类似 Web-Servlet 模块的功能。
Core Container(核心容器)
Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言模块组成,具体介绍如下。
Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。 Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。 Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。 Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。
其他模块
Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块,具体介绍如下。
AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。 Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。 Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。 Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。
Spring拓展
Spring Boot与Spring Cloud
- Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务。
- Spring Cloud是基于Spring Boot实现的。
- Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架。
- Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
- SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。
Spring IoC 容器 (IoC 也称为依赖项注入(DI),或DI是实现IoC的一种方法)
IoC容器概述
- 控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入。
- Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
- Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext。
1.BeanFactory
beanFactory是一个Factory,用于管理bean的,有了一个Spring的beanFactory,我们就可以从spring中获取注册到其中的bean来使用。
2.ApplicationContext
ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。该接口的全路径为:
org.springframework.context.ApplicationContext,它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。
ApplicationContext 接口有两个常用的实现类:ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。 ClassPathXmlApplicationContext从类路径 ClassPath 中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,具体如下所示。ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
configLocation 参数用于指定 Spring 配置文件的名称和位置,如 applicationContext.xml。
FileSystemXmlApplicationContext从指定的文件系统路径中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,具体如下所示。ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,它可以获取类路径之外的资源,如“D:/workspaces/applicationContext.xml”。
3.BeanFactory 和 ApplicationContext区别:
BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean 时才实例目标Bean;而ApplicationContext 则在初始化应用上下文时就实例化所有单实例的Bean 。
在实际开发中,通常都选择使用 ApplicationContext,而只有在系统资源较少时,才考虑使用 BeanFactory。(但是,它们都是通过 XML 配置文件加载 Bean 的。)
Spring入门程序
1.创建maven项目
2.在pom.xml导入jar包依赖
<dependencies> <!--导入spring,maven依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.12.RELEASE</version> </dependency> <!--导入junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
3.编写接口
package com.xxx.mapper; /** * @author shkstart * @create 2021-06-11 15:50 */ public interface UserMapper { public void hello(); }
4.编写接口实现类
package com.xxx.mapper;/** * @author shkstart * @create 2021-06-11 15:50 */ /** *@program: springTest *@description: *@author: XieXianXin *@create: 2021-06-11 15:50 */ public class UserMapperImpl implements UserMapper{ @Override public void hello() { System.out.println("Spring入门程序!"); } }
编写Spring核心配置文件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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 使用Spring来创建对象,在Spring这些都称为Bean 类型 变量名 = new 类型(); Hello hello = new Hello(); id = 变量名 class = new 的对象 --> <beans> <bean id="hello" class="com.xxx.mapper.UserMapperImpl"> </bean> </beans> </beans>
测试
package com.xxx.mapper;/** * @author shkstart * @create 2021-06-11 15:57 */ import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** *@program: springTest *@description: *@author: XieXianXin *@create: 2021-06-11 15:57 */ public class helloTest { @Test public void helloTest1(){ // 1. 初始化Spring容器,加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过容器获取userMapper实例 UserMapper hello = context.getBean("hello", UserMapper.class); // 3.调用实例中的hello()方法 hello.hello(); } }
测试结果
IoC创建对象的三种方式
通过无参构造(要提供set方法)
编写实体类User:
public class User { private String name; // set方法 public void setName(String name) { this.name=name; } public User() { System.out.println("无参构造方法执行了!"); } public void print(){ System.out.println("学生名字为:"+name); } }
编写Spring核心配置文件:
<!--有参构造,但是要有get方法--> <bean id="user" class="com.xxx.pojo.User"> <constructor-arg value="小新2" index="0"/> </bean>
测试以及结果:
@Test public void helloTest2(){ // 1. 初始化Spring容器,加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过容器获取userMapper实例 User user = context.getBean("user", User.class); // 3.调用实例中的print()方法 user.print(); }
通过有参构造(要提供get方法)
编写实体类User:
public class User { private String name; //get方法 public String getName() { return name; } public User(String name) { System.out.println("有参构造方法执行了!"); this.name = name; } public void print(){ System.out.println("学生名字为:"+name); } }
编写Spring核心配置文件:
<!--有参构造,但是要有get方法--> <bean id="user" class="com.xxx.pojo.User"> <constructor-arg value="小新2" index="0"/> </bean>
测试以及结果:
@Test public void helloTest2(){ // 1. 初始化Spring容器,加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过容器获取userMapper实例 User user = context.getBean("user", User.class); // 3.调用实例中的print()方法 user.print(); }
拓展:Spring核心配置文件有三种写法:
<!--有参构造,但是要有get方法--> <bean id="user" class="com.xxx.pojo.User"> <constructor-arg index="0" value="小新-index属性(0开始,按顺序)"/> <constructor-arg name="name" value="小新-name属性"/> <constructor-arg type="java.lang.String" value="小新-参数类型"/> </bean>
结果展示:
通过工厂类
编写工厂类:
public class Factory { //方法一,静态方法 public static User getStaticInstance(){ return new User("小新2——静态方法创建对象"); } //方法二,实例方法 public User getInstance(){ return new User("小新3-实例方法创建对象"); } }
编写Spring核心配置文件:
<!--工厂类创建对象--> <!--创建工厂--> <bean id="factory" class="com.xxx.mapper.Factory"/> <!--静态方法对象--> <bean id="staticFactory-user" class="com.xxx.mapper.Factory" factory-method="getStaticInstance"/> <!--实例方法对象--> <bean id="factory-user" factory-bean="factory" factory-method="getInstance"/>
测试以及结果:静态方法:
@Test public void helloTest4(){ // 1. 初始化Spring容器,加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过容器获取userMapper实例 User user = context.getBean("staticFactory-user", User.class); // 3.调用实例中的print()方法 user.print(); }
实例方法:
@Test public void helloTest3(){ // 1. 初始化Spring容器,加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过容器获取userMapper实例 User user = context.getBean("factory-user", User.class); // 3.调用实例中的print()方法 user.print(); }
Spring依赖注入(DI)和Bean的作用域
什么是依赖注入:Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过 Spring 容器获得被调用者实例。
依赖注入主要有两种实现方式,分别是属性 setter 注入和构造方法注入,其中setter注入要求重点掌握。
- 属性 setter 注入(重点展开讲解)
- 指 IoC 容器使用 setter 方法注入被依赖的实例。通过调用无参构造器或无参 static 工厂方法实例化 bean 后,调用该 bean 的 setter 方法,即可实现基于 setter 的 DI。
- 构造方法注入
- 指 IoC 容器使用构造方法注入被依赖的实例。基于构造器的 DI 通过调用带参数的构造方法实现,每个参数代表一个依赖。
属性 setter 注入讲解:
环境搭建:(创建一个Student和Book类):
Student
package com.xxx.pojo;/** * @author shkstart * @create 2021-06-11 17:45 */ import java.util.*; /** *@program: Spring_study *@description: *@author: XieXianXin *@create: 2021-06-11 17:45 */ public class Student { private String name; private Book book; private String[] course; private List<String> hobbies; private Map<String,String> card; private Set<String> fruit; private String marriage; private Properties info; public Student() { } public Student(String name, Book book, String[] course, List<String> hobbies, Map<String, String> card, Set<String> fruit, String marriage, Properties info) { this.name = name; this.book = book; this.course = course; this.hobbies = hobbies; this.card = card; this.fruit = fruit; this.marriage = marriage; this.info = info; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", book=" + book + ", course=" + Arrays.toString(course) + ", hobbies=" + hobbies + ", card=" + card + ", fruit=" + fruit + ", marriage='" + marriage + '\'' + ", info=" + info + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Book getBook() { return book; } public void setBook(Book book) { this.book = book; } public String[] getCourse() { return course; } public void setCourse(String[] course) { this.course = course; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } public Map<String, String> getCard() { return card; } public void setCard(Map<String, String> card) { this.card = card; } public Set<String> getFruit() { return fruit; } public void setFruit(Set<String> fruit) { this.fruit = fruit; } public String getMarriage() { return marriage; } public void setMarriage(String marriage) { this.marriage = marriage; } public Properties getInfo() { return info; } public void setInfo(Properties info) { this.info = info; } }
Book
package com.xxx.pojo;/** * @author shkstart * @create 2021-06-11 17:45 */ /** *@program: Spring_study *@description: *@author: XieXianXin *@create: 2021-06-11 17:45 */ public class Book { private String name; private int id; public Book() { } @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", id=" + id + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public Book(String name, int id) { this.name = name; this.id = id; } }
常量注入:
<bean class="com.xxx.pojo.Student" id="student"> <!--常量注入--> <property name="name" value="小新"/> </bean>
Bean注入:
<bean class="com.xxx.pojo.Book" id="book"> <property name="name" value="Java放弃"/> <property name="id" value="100"/> </bean> <bean class="com.xxx.pojo.Student" id="student"> <!--Bean注入--> <property name="book" ref="book"/> </bean>
数组注入:
<property name="course"> <array> <value>高数</value> <value>计算机网络</value> <value>数据库</value> </array> </property>
List注入:
<property name="hobbies"> <list> <value>唱</value> <value>跳</value> <value>Rap</value> </list> </property>
Map注入:
<property name="card"> <map> <entry key="银行卡:" value="2501314"/> <entry key="身份证:" value="1314520"/> </map> </property>
Set注入:
<property name="fruit"> <set> <value>香蕉</value> <value>苹果</value> <value>雪梨</value> </set> </property>
Null注入:
<property name="marriage"> <null/> </property>
Properties注入:
<property name="info"> <props> <prop key="username">小新</prop> <prop key="password">520</prop> </props> </property>
测试及结果展示:
public class BeanTest { @Test public void beanTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = context.getBean("student", Student.class); System.out.println(student); } }
Student{name=‘小新', book=Book{name=‘Java放弃', id=100}, course=[高数, 计算机网络, 数据库], hobbies=[唱, 跳, Rap], card={银行卡:=2501314, 身份证:=1314520}, fruit=[香蕉, 苹果, 雪梨], marriage=‘null', info={password=520, username=小新}}
Process finished with exit code 0
p命名空间(以Book类举例)导入约束 xmlns:p=“http://www.springframework.org/schema/p”
<bean id="pBook" class="com.xxx.pojo.Book" p:name="Java懵懂" p:id="250"/>
测试及结果:
@Test public void cpTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Book pBook = context.getBean("pBook", Book.class); System.out.println(pBook); }
c命名空间导入约束 xmlns:c=“http://www.springframework.org/schema/c”
<bean id="cBook" class="com.xxx.pojo.Book" c:id="520" c:name="Java入坑"/>
测试及结果:
@Test public void cpTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Book cBook = context.getBean("cBook", Book.class); System.out.println(cBook); }
作用域种类
singleton(以Book举例)单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。
<bean class="com.xxx.pojo.Book" id="scopeBook" scope="singleton"> <property name="id" value="1"/> </bean>
@Test public void scopeTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Book book1 = context.getBean("scopeBook", Book.class); Book book2 = context.getBean("scopeBook", Book.class); System.out.println(book1.hashCode()); System.out.println(book2.hashCode()); System.out.println(book1==book2); } }
prototype 原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例,即每次调用getBean()时,相当于执行了一次new XxxBean()。
<bean class="com.xxx.pojo.Book" id="scopeBook" scope="prototype"> <property name="id" value="1"/> </bean>
@Test public void scopeTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Book book1 = context.getBean("scopeBook", Book.class); Book book2 = context.getBean("scopeBook", Book.class); System.out.println(book1.hashCode()); System.out.println(book2.hashCode()); System.out.println(book1==book2); } }
- request
在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
- session
在同一个 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
- global Session
在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。
Spring 常用配置及属性
Spring自动装配
- Bean 的装配可以理解为依赖关系注入,Bean 的装配方式也就是 Bean 的依赖注入方式。Spring 容器支持多种形式的 Bean的装配方式,如基于 XML 的 Bean 装配、基于Annotation 的 Bean 装配和自动装配等。之前的举例是通过XML的Bean装配的。接下来讲解自动装配。
- 自动装配就是指 Spring 容器可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。
- Spring的自动装配需要从两个角度来实现:
1.组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
2.自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
autowire 的属性和作用
- 环境搭建:(分别创建一个Student和Student2类,再创建一个Teacher类)
public class Student { public void study(){ System.out.println("Student类的方法study执行了"); } }
public class Student2 { public void study(){ System.out.println("Student2类的方法study执行了"); } }
public class Teacher { private Student student; private Student2 student2; private String teach; public Teacher() { } @Override public String toString() { return "Teacher{" + "student=" + student + ", student2=" + student2 + ", teach='" + teach + '\'' + '}'; } public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public Student2 getStudent2() { return student2; } public void setStudent2(Student2 student2) { this.student2 = student2; } public String getTeach() { return teach; } public void setTeach(String teach) { this.teach = teach; } public Teacher(Student student, Student2 student2, String teach) { this.student = student; this.student2 = student2; this.teach = teach; } }
配置Spring核心配置文件
使用autowire=“byName”:
<bean class="com.xxx.pojo.Student" id="student"/> <bean class="com.xxx.pojo.Student" id="student"/> <bean class="com.xxx.pojo.Student2" id="student2"/> <bean class="com.xxx.pojo.Teacher" id="teacher" autowire="byName"> <property name="teach" value="Java"/> </bean>
测试及结果:
public class BeanTest { @Test public void beanTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Teacher teacher = context.getBean("teacher", Teacher.class); teacher.getStudent().study(); teacher.getStudent2().study(); } }
若修改Student的bean id值不为student,如:<bean class="com.xxx.pojo.Student" id="s"/>
则会报空指针异常java.lang.NullPointerException at BeanTest.beanTest(BeanTest.java:24)
。因为按byName规则找不对应set方法,真正的setStudent就没执行,对象就没有初始化,所以调用时就会报空指针错误。
当一个bean节点带有 autowire byName的属性时:
1.将查找其类中所有的set方法名,例如setStudent,获得将set去掉并且首字母小写的字符串,即student。
2.去spring容器中寻找是否有此字符串名称id的对象,如果有,就取出注入;如果没有,就报空指针异常。
Spring注解开发
环境搭建
1.在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
开启属性注解支持!
<context:annotation-config/>
编写一个 Student类
public class Student { private String name; @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Student() { } public Student(String name) { this.name = name; } }
编写Spring核心配置文件:
<bean class="com.xxx.pojo.Student" id="student"> <property name="name" value="小新"/> </bean>
测试及结果:
@Test public void beanTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = context.getBean("student",Student.class); System.out.println(student);
使用@Configuration和@Bean给容器中注册组件 编写一个配置类
/** *@program: springTest *@description: 在类上添加@Configuration注解使得该类成为Spring配置类,通过@Bean注解将该类注入到IoC容器,此时配置类==配置文件 *@author: XieXianXin *@create: 2021-06-12 23:06 */ // 这个配置类也是一个组件 @Configuration// 告诉Spring这是一个配置类 public class AnnotationStudent { @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id public Student student(){ return new Student("小新"); } }
测试及结果:
@Test public void beanTest(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationStudent.class); Student bean = context.getBean(Student.class); //返回Student类在IoC容器中的id值 String[] namesForType = context.getBeanNamesForType(Student.class); for (String s : namesForType) { System.out.println(s); } System.out.println(bean); } }
若在配置类中给@Bean设置一个value值,如@Bean("stu")
则测试结果为:
则我们在使用注解方式向Spring的IOC容器中注入JavaBean时,如果没有在@Bean注解中明确指定bean的名称,那么就会使用当前方法的名称来作为bean的名称;如果在@Bean注解中明确指定了bean的名称,那么就会使用@Bean注解中指定的名称来作为bean的名称。
使用@ComponentScan自动扫描组件并指定扫描规则
开启注解扫描,并删除之前配置文件中的bean
<context:component-scan base-package="com.xxx"/>
在原有环境下创建一个com.xxx.service包,并创建一个Teacher类,并在类上添加一个@Service注解,同时,之前的Student类上也添加一个@Component注解
@Service public class Teacher { private Student student; public void teach(){ System.out.println("教授的学生是"+student); } @Override public String toString() { return "Teacher{" + "student=" + student + '}'; } public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public Teacher(Student student) { this.student = student; } public Teacher() { } }
测试及结果:
public class BeanTest { @Test public void beanTest() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String definitionName : beanDefinitionNames) { System.out.println(definitionName); } } }
以上可以看到:在配置注解扫描后,只要在com.xxx包下的所有子包中,加上了@Repository(Dao)、@Service(service)、@Controller、(web)@Component注解的类都会被扫描到,并自动注入到Spring容器中。(其实上面四个功能,目前为止是一样的)
- 使用注解配置XML包扫描
我们可以在配置类中(前面的AnnotationStudent)使用@ComponentScan注解配置包扫描,由此代替xml中的<context:component-scan base-package="com.xxx"/>
。先注释掉之前的xml方式的注解扫描,接着
@Configuration// 告诉Spring这是一个配置类@ComponentScan(value = "com.xxx")public class AnnotationStudent { @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id public Student student(){ return new Student("小新"); }}
测试结果跟之前一样。因此,推荐以后都使用注解扫描就好了,Spring还是尽量用注解开发,MyBatis中还是用xml配置文件。
- ComponentScan方法使用
excludeFilters()不包含哪些包、includeFilters()包含哪些包,使用includeFilters时,需要在XML配置文件中先配置use-default-filters="false"
,即禁用默认的扫描所有包过滤规则才能生效。另外,ComponentScan还是一个可重复注解的注解,因此可以在一个类上重复使用这个注解。
使用@Scope注解设置组件的作用域
通过在类中添加注解@scope注解设置作用域,如:
// 这个配置类也是一个组件 @Configuration// 告诉Spring这是一个配置类 public class AnnotationStudent { @Scope("prototype") @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id public Student student(){ return new Student("小新"); } }
- 结果:
如果为false。
- @Scope注解中的取值如下所示:
注解自动装配组件(@Resource是JDK自带的)
- @Autowired
@Autowired注解可以对类成员变量、方法和构造函数进行标注,完成自动装配的工作。@Autowired注解可以放在类、接口以及方法上。等价于<property name="属性名" value=" 属性值"/>
@Autowired注解默认是优先按照类型去容器中找对应的组件,即:context.getBean(类名.class);,
如果找到多个相同类型的组件,那么是将属性名称作为组件的id,到IOC容器中进行查找,即:context.getBean("组件的id");
- @Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配,且Qualifier不能单独使用。
- @Resource
是JDK自带的注解 可以按名称注入也可以按类型注入,默认是按名称注入,没有显式指定名称时,在spring容器中匹配与需要注入的bean属性名相同的bean,如果还不同,@Resource会找到一个主类型匹配而不是一个特定的命名bean。
懒加载@Lazy
懒加载就是Spring容器启动的时候,先不创建对象,在第一次使用(获取)bean的时候Xxx xxx = context.getBean(Xxx.class);
再来创建对象,并进行一些初始化。使用时,只需要在配置类的方法上加上@Lazy注解即可。
public class AnnotationStudent { @Lazy @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id public Student student(){ System.out.println("在容器中添加对象!"); return new Student("小新"); } }
public class BeanTest { @Test public void beanTest() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println("容器创建完成!"); Student student = context.getBean(Student.class); Student student1 = context.getBean(Student.class); System.out.println(student==student1); } }
- 非懒加载模式(默认情况):bean在Spring容器启动的时候ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");就会被创建,并且还加载到Spring容器中去了。
@Configuration// 告诉Spring这是一个配置类 public class AnnotationStudent { @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id public Student student(){ System.out.println("在容器中添加对象!"); return new Student("小新"); } }
public class BeanTest { @Test public void beanTest() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println("容器创建完成!"); } }
使用@Import注解给容器中快速导入一个组件
注册bean的方式通常有以下几种:
1.包扫描+给组件标注注解(@Controller、@Servcie、@Repository、@Component
2.@Bean注解
3.@Import注解(只作用在类上,可以在实际开发项目中导入别人的类并注册到容器中,这是两外两种无法做到的)例如在AnnotationStudent配置类上导入Teacher类对应的bean实例(id默认是组件的全类名)
4.使用FactoryBean接口(支持泛式)向Spring容器中注册bean
// 这个配置类也是一个组件 @Configuration// 告诉Spring这是一个配置类 @Import(Teacher.class) public class AnnotationStudent { @Bean// @Bean注解是给IOC容器中注册一个bean,id默认是用方法名作为id public Student student(){ return new Student("小新"); } }
public class BeanTest { @Test public void beanTest() { /* ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); */ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationStudent.class); String[] beanNamesForType = applicationContext.getBeanDefinitionNames(); for (String s : beanNamesForType) { System.out.println(s); } } }
当去除@Import后,输出结果为:
Bean生命周期
常意义上讲的bean的生命周期,指的是bean从创建到初始化,经过一系列的流程,最终销毁的过程,如下图所示。在Spring中,我们可以自己来指定bean的初始化和销毁的方法@Bean(initMethod = "自定义的初始化方法名",destroyMethod = "自定义的销毁方法名")。
当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。
自定义一个Life类:
public class Life { public Life(){ System.out.println("Life构造方法执行了!"); } public void init(){ System.out.println("Life初始化方法执行了!"); } public void destroy(){ System.out.println("Life销毁方法执行了!"); } }
配置类中注册bean:
@Configuration// 告诉Spring这是一个配置类 public class AnnotationStudent { @Bean(initMethod = "init",destroyMethod = "destroy") public Life life(){ return new Life(); } }
测试及结果:
public class BeanTest { @Test public void beanTest() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationStudent.class); System.out.println("容器创建完成!"); Life bean = applicationContext.getBean(Life.class); } }
可以看到,对于单实例对象,先执行构造方法,再到初始化方法,而销毁方法执行需要显式关闭容器时候才执行applicationContext.close();
因此,我们可以自定义初始化方法和销毁方法处理配置数据源问题,在初始化的时候,会对很多的数据源的属性进行赋值操作;在销毁的时候,我们需要对数据源的连接等信息进行关闭和清理。
@Value注解为属性赋值
在Student类中的name属性上加上@Value注解,等价于配置文件中的<bean id="student" class="com.xxx.pojo.Student"> <property name="name" value="xiaoxin"/> </bean>
里的<property name="name" value="xiaoxin"/>,外面的bean是@Component
注解作用。
@Component public class Student { @Value("xiaoxin") private String name; @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Student() { } public Student(String name) { this.name = name; } }
测试及结果:
public class BeanTest { @Test public void beanTest() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = context.getBean("student", Student.class); System.out.println(student); } }
使用@PropertySource加载配置文件
- 原始xml方式:
- 在resources包下创建一个applicationContext.properties配置文件,内容为键值对形式:
name=xiaoxin password=888888
- 编写一个Property类,用于测试:
public class Property { private String username; private Integer password; @Override public String toString() { return "Property{" + "username='" + username + '\'' + ", password=" + password + '}'; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getPassword() { return password; } public void setPassword(Integer password) { this.password = password; } public Property(String username, Integer password) { this.username = username; this.password = password; } public Property() { } }
Spring核心配置文件内容为:
<context:annotation-config /> <context:component-scan base-package="com.xxx"/> <context:property-placeholder location="applicationContext.properties"/> <bean class="com.xxx.pojo.Property" id="property"> <property name="username" value="${name}"/> <property name="password" value="${password}"/> </bean>
测试及结果:
public class BeanTest { @Test public void beanTest() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Property property = context.getBean("property", Property.class); System.out.println(property.toString()); } }
注解方式:
- 保留原applicationContext.properties配置文件
- 将Spring核心配置文件内容删除:只保留开启注解:<
context:annotation-config />
- Property类完全使用注解代替:
@Configuration//表示该类是配置类,等价于核心配置文件 @ComponentScan(value = "com.xxx")//等价于<context:component-scan base-package="com.xxx"/> @Component//注册bean,默认id为类名(首字母小写),等价于<bean class="com.xxx.pojo.Property" id="property"></bean> @PropertySource("classpath:applicationContext.properties")//等价于<context:property-placeholder location="applicationContext.properties"/> public class Property { @Value("${name}")//等价于<property name="username" value="${name}"/> private String username; @Value("${password}")//等价于<property name="password" value="${password}"/> private Integer password; @Override public String toString() { return "Property{" + "username='" + username + '\'' + ", password=" + password + '}'; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getPassword() { return password; } public void setPassword(Integer password) { this.password = password; } public Property(String username, Integer password) { this.username = username; this.password = password; } public Property() { } }
测试及结果:
public class BeanTest { @Test public void beanTest() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Property.class); Property bean = applicationContext.getBean(Property.class); System.out.println(bean.toString()); } }
代理模式
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
静态代理
案例:男孩相亲,想找女孩结婚,于是男孩找媒婆进行代理,媒婆代理介绍女孩同时,还要收取一定的介绍费。
接口类
/** *@program: springTest *@description: 相亲接口 *@author: XieXianXin *@create: 2021-06-13 20:36 */ public interface Marry { //相亲 void marry(); }
女孩(目标对象)
/** *@program: springTest *@description: 目标对象 *@author: XieXianXin *@create: 2021-06-13 20:32 */ public class Girl { private String name; @Override public String toString() { return "Girl{" + "name='" + name + '\'' + '}'; } public Girl(String name) { this.name = name; } public Girl() { } public String getName() { return name; } public void setName(String name) { this.name = name; } }
男孩(被代理对象)
/** *@program: springTest *@description: 被代理对象 *@author: XieXianXin *@create: 2021-06-13 20:33 */ public class Boy implements Marry { private Girl girl; public Boy(Girl girl) { this.girl = girl; } @Override public void marry() { System.out.println("想跟"+girl.getName()+"认识!"); } }
媒婆(代理对象)
/** *@program: springTest *@description: 代理类 *@author: XieXianXin *@create: 2021-06-13 20:33 */ public class Proxy implements Marry { private Boy boy; public Proxy(Girl girl){ boy = new Boy(girl); } @Override public void marry() { boy.marry(); } public void earn(){ System.out.println("媒婆收取介绍费"); } }
测试及结果
public class ProxyTest { @Test public void proxyTest(){ Girl girl = new Girl(); girl.setName("美女!"); Proxy proxy = new Proxy(girl); proxy.marry(); proxy.earn(); } }
- 静态代理的好处:
可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情。
公共的业务由代理来完成 . 实现了业务的分工。
公共业务发生扩展时变得更加集中和方便。
- 静态代理缺点:
冗余,由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
不易维护,一旦接口增加方法,目标对象与代理对象都要进行修改。
Spring AOP AOP
AOP
(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总的来说,AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。
AOP中相关概念
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知 执行的 “地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中支持5种类型的Advice
Spring AOP的实现(3种)
- 导入依赖
在原有的maven的pom.xml文件中加上AOP织入依赖包
<!--使用Spring实现Aop,使用AOP织入,需要导入一个依赖包!--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
通过 Spring API 实现
编写业务接口及其实现类
/** * @author shkstart 第一种,有接口方式,通过 Spring API 实现,要实现Uservice接口,具体看advice包 * 第二种,通过自定义类实现,运用的是AOP定义,不需要实现接口,具体看diy包 * 第三种,使用注解实现,具体看annotation包 * @create 2021-06-04 16:12 */ public interface UserService { public void add(); public void delete(); public void update(); public void select(); }
/** *@program: Spring_study *@description: *@author: XieXianXin *@create: 2021-06-04 16:14 */ public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void select() { System.out.println("查询用户"); } }
编写增强类(分别有前置通知、后置通知和环绕通知)
/** *@program: Spring_study *@description: 前置通知,在方法前增强,实现MethodBeforeAdvice接口 *@author: XieXianXin *@create: 2021-06-04 16:21 */ public class BeforeAdvice implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //args : 被调用的方法的参数 //target : 目标对象 @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("前置通知的"+target.getClass().getName()+"的"+method.getName()+"方法被执行了"); } }
/** *@program: Spring_study *@description: 后置通知,在方法后执行,实现AfterReturningAdvice接口 *@author: XieXianXin *@create: 2021-06-04 17:00 */ public class AfterAdvice implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("后置通知的"+target.getClass().getName()+"的"+method.getName()+"执行了,返回值为:"+returnValue); } }
/** *@program: Spring_study *@description: 环绕通知,在方法前后执行,实现MethodInterceptor接口 *@author: XieXianXin *@create: 2021-06-04 17:07 */ public class InterceptAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { try { System.out.println("环绕通知"+invocation.getMethod().getName()+"——方法前执行的"); Method invocationMethod = (Method) invocation.proceed(); System.out.println("环绕通知"+invocation.getMethod().getName()+"——方法后执行的"); return invocationMethod; } catch (Throwable throwable) { throwable.printStackTrace(); } return invocation; } }
配置Spring核心配置文件,实现AOP切入
<!--第一种方式,通过接口实现--> <!--1.注册bean--> <bean id="userService" class="com.xxx.service.UserServiceImpl"/> <bean id="beforeAdvice" class="com.xxx.advice.BeforeAdvice"/> <bean id="afterAdvice" class="com.xxx.advice.AfterAdvice"/> <bean id="interceptAdvice" class="com.xxx.advice.InterceptAdvice"/> <bean id="throwAdvice" class="com.xxx.advice.ThrowAdvice"/> <!--2.aop的配置--> <aop:config> <!--切入点 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointCut" expression="execution(* com.xxx.service.UserServiceImpl.*(..))"/> <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--> <!--前置通知--> <aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointCut"/> <!--后置通知--> <aop:advisor advice-ref="afterAdvice" pointcut-ref="pointCut"/> <!--环绕通知--> <aop:advisor advice-ref="interceptAdvice" pointcut-ref="pointCut"/> <!--异常抛出通知--> <aop:advisor advice-ref="throwAdvice" pointcut-ref="pointCut"/> </aop:config>
测试及结果
public class UserServiceImplTest { @Test public void myTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //动态代理的是接口,不是实体类,因此不是UserServiceImpl.class UserService userService = context.getBean("userService", UserService.class); userService.delete(); System.out.println("=============================="); UserService userService1 = context.getBean("userService", UserService.class); userService1.add(); System.out.println("=============================="); UserService userService2 = context.getBean("userService", UserService.class); userService2.select(); System.out.println("=============================="); UserService userService3 = context.getBean("userService", UserService.class); userService3.update(); } }
通过自定义类来实现 保留之前的业务类UserServiceImpl编写自定义类DiyPointcut
/** *@program: Spring_study *@description: 自定义类实现AOP,一个类相当于一个切面,类的方法相当于通知 *@author: XieXianXin *@create: 2021-06-04 21:16 */ public class DiyPointcut { public void beforeAdvice(){ System.out.println("前置通知"); } public void afterAdvice(){ System.out.println("后置通知"); } public void interceptAdvice(ProceedingJoinPoint joinPoint){//环绕通知要有ProceedingJoinPoint joinPoint参数 System.out.println("方法"+joinPoint.getSignature().getName()+"环绕通知前执行的语句"); Object[] args = joinPoint.getArgs(); try { Object proceed = joinPoint.proceed(args); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("方法"+joinPoint.getSignature().getName()+"环绕通知后执行的语句"); } }
配置Spring核心配置文件
<!--第二种方式,通过自定义类实现--> <!--1.注册bean--> <bean id="userService" class="com.xxx.service.UserServiceImpl"/> <bean id="diyPointcut" class="com.xxx.diy.DiyPointcut"/> <aop:config> <!--2.使用AOP标签--> <aop:aspect ref="diyPointcut"> <!--3.切入点--> <aop:pointcut id="pointcut" expression="execution(* com.xxx.service.UserServiceImpl.*(..))"/> <!--4.通知--> <!--前置通知--> <aop:before method="beforeAdvice" pointcut-ref="pointcut"/> <!--后置通知--> <aop:after method="afterAdvice" pointcut-ref="pointcut"/> <!--环绕通知--> <aop:around method="interceptAdvice" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
测试及结果
public class UserServiceImplTest { @Test public void myTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //动态代理的是接口,不是实体类,因此不是UserServiceImpl.class UserService userService = context.getBean("userService", UserService.class); userService.delete(); System.out.println("=============================="); UserService userService1 = context.getBean("userService", UserService.class); userService1.add(); System.out.println("=============================="); UserService userService2 = context.getBean("userService", UserService.class); userService2.select(); System.out.println("=============================="); UserService userService3 = context.getBean("userService", UserService.class); userService3.update(); } }
通过自定义类来实现 编写注解实现的增强类AnnotationAdvice
/** *@program: Spring_study *@description: 使用注解进行AOP设计 *@author: XieXianXin *@create: 2021-06-04 22:18 */ @Aspect public class AnnotationAdvice { @Before("execution(* com.xxx.service.UserServiceImpl.*(..))")//表达式中写要被增强的类 public void before(){ System.out.println("前置通知"); } @After("execution(* com.xxx.service.UserServiceImpl.*(..))") public void after(){ System.out.println("后置通知"); } @Around("execution(* com.xxx.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知执行前"); System.out.println("签名:"+joinPoint.getSignature()); //执行目标方法proceed Object proceed = joinPoint.proceed(); System.out.println("环绕通知执行后"); System.out.println(proceed); } }
开启注解扫描和注册bean
<!--指定要扫描的包,这个包下的注解就会生效--> <context:component-scan base-package="com.xxx.service"/> <context:annotation-config/>
<aop:aspectj-autoproxy proxy-target-class="false"/> <!--2.注册bean,只需要注册增强的那个类--> <bean class="com.xxx.service.UserServiceImpl" id="userService"/> <bean id="annotationAdvice" class="com.xxx.annotation.AnnotationAdvice"/>
测试及结果
public class UserServiceImplTest { @Test public void myTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //动态代理的是接口,不是实体类,因此不是UserServiceImpl.class UserService userService = context.getBean("userService", UserService.class); userService.delete(); System.out.println("=============================="); UserService userService1 = context.getBean("userService", UserService.class); userService1.add(); System.out.println("=============================="); UserService userService2 = context.getBean("userService", UserService.class); userService2.select(); System.out.println("=============================="); UserService userService3 = context.getBean("userService", UserService.class); userService3.update(); } }
Spring事务管理及Spring整合MyBatis代码示例
Spring事务管理
- 什么是事务:事务就是把一系列的动作当成一个独立的工作单元,这些动作要么都执行,要么都不执行。
- 事务四个特性-ACID:
原子性(atomicity)
事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
一致性(consistency)
一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
隔离性(isolation)
可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
持久性(durability)
事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中Spring支持编程
- 式事务管理和声明式的事务管理:
声明式事务管理
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
使用Spring管理事务,注意头文件的约束导入:
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
声明式事务配置拓展:
JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
自动代理的配置
!-- Spring事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置事务的传播特性 --> <bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true" > <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="add*">PROPAGATION_REQUIRED</prop> <prop key="edit*">PROPAGATION_REQUIRED</prop> <prop key="remove*">PROPAGATION_REQUIRED</prop> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="del*">PROPAGATION_REQUIRED</prop> <prop key="*">readOnly</prop> </props> </property> </bean>
基于 命名空间的声明式事务管理
<beans......> ...... <bean id="bankService" class="footmark.spring.core.tx.declare.namespace.BankServiceImpl"> <property name="bankDao" ref="bankDao"/> </bean> <tx:advice id="bankAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/> <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/> </aop:config> ...... </beans>
- @Transactional 的声明式事务管理
启用tx的annotation:
<tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
- Spring事务的传播行为:
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
- 事务的隔离级别:
事务的第二个维度就是隔离级别(isolation level)。
脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
Spring结合事务整合MyBatis示例
1.导入相关Jar包
<!--Spring整合Mybatis需要如下包,都是放在dependencies内--> <dependencies> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--导入spring,maven依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.12.RELEASE</version> </dependency> <!--使用Spring实现Aop,使用AOP织入--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <!--spring操作数据库也需要一个spring-jdbc包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.7</version> </dependency> <!--整合必要的一个包,mybatis-spring,使用2.0以上版本--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.5</version> </dependency> <!--LOG4J--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> <scope>compile</scope> </dependency> </dependencies> <!--需要解决的乱码以及maven静态资源过滤问题等在build内完成--> <!--解决单元测试中文乱码--> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.12.4</version> <configuration> <argLine> -Dfile.encoding=UTF-8 </argLine> </configuration> </plugin> </plugins> <!--可能出现问题说明:Maven静态资源过滤(导出)问题 Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/xxx/dao/UserMapper.xml 原因是idea默认不编译src目录下的xml文件,所以加载不到 解决办法在pom文件中的build标签内加入如下配置,则可以找到java和resources下的所有properties和xml文件了 --> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
2.编写配置文件及加入日志
mybatis-config.xml
<configuration> <!-- configuration" 里的标签顺序如下:(否则报错如下信息) "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory? objectWrapperFactory?,reflectorFactory?,plugins?,environments?, databaseIdProvider?,mappers?)". --> <!--标准的日志工厂实现(常用:STDOUT_LOGGING,LOG4J),下面的value值建议去mybaits文档复制 日志就是记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。 --> <settings> <!--下划线驼峰自动转换--> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="LOG4J"/> </settings> <!--给这个包下的类起别名--> <typeAliases> <package name="com.xxx.pojo"/> </typeAliases> <mappers> <mapper resource="com/xxx/mapper/UserMapper.xml"/> </mappers> </configuration>
spring-mybatis.xml
<!-- spring整合mybatis,根据mybatis-spring文档可以,需要一个数据源获取SqlSessionFactory 和至少一个数据映射器类 具体查看文档:http://mybatis.org/spring/zh/getting-started.html --> <!-- DataSource:使用Spring的数据源替换Mybatis的配置:druid c3p0,dbcp 这里使用Speing提供的JDBC:org.springframework.jdbc.datasource.DriverManagerDataSource 前提是要导入:spring-jdbc 包 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybaits?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!--MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!--跟在mybatis中学习一样,需要在mybatis核心配置文件绑定xxxmapper.xml文件 这里也需要绑定mybatis核心配置文件,绑定后,mybatis核心配置文件可以完成的这里也都可以完成,则mybatis-config文件可以不要也行 --> <!--绑定mybatis--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--注册映射器,等价于mybatis核心配置文件中的: <mappers> <mapper resource="com/xxx/mapper/UserMapper.xml"/> </mappers> --> <!--<property name="mapperLocations" value="classpath:com/xxx/mapper/*.xml"/>--> </bean> <!--注册SqlSessionTemplate,相当于我们使用的sqlSession,因此可将id命名为此好记--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--因为sqlSessionTemplate只有构造方法而无set方法,只能使用构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> <!--配置声明式事务(AOP原理,不改变源代码条件下增加事务),而编程式事务要在源代码上自动try catch 具体可查看文档:http://mybatis.org/spring/zh/transactions.html --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--结合AOP实现事务的织入--> <!--配置事务的通知--> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <!--给具体方法配置事务和传播新特性(propagation=REQUIRED是默认的,即会自动创建事务) 具体查看:https://blog.csdn.net/edward0830ly/article/details/7569954 name="*"表示给所有方法配置事务,也可给具体方法,给出方法名即可 --> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--配置事务切入--> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.xxx.mapper.*.*(..))"/> <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/> </aop:config>
applicationContext.xml
<import resource="spring-mybatis.xml"/> <bean id="userMapperImpl_2" class="com.xxx.mapper.UserMapperImpl_2"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>
log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/xxx.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
3.编写接口及其实现类和配置对应的mapper.xml文件
UserMapper接口
public interface UserMapper { //查询所有用户 public List<User> queryUser(); //添加一个用户 int addUser(User user); //根据id删除用户 int deleteUser(int id); }
UserMapperImpl_2实现类
/** *@program: Spring_study *@description: spring-mybatis整合方式二:继承SqlSessionDaoSupport实现接口 *@author: XieXianXin *@create: 2021-06-05 22:08 */ public class UserMapperImpl_2 extends SqlSessionDaoSupport implements UserMapper { @Override public List<User> queryUser() { return getSqlSession().getMapper(UserMapper.class).queryUser(); } @Override public int addUser(User user) { return getSqlSession().getMapper(UserMapper.class).addUser(user); } @Override public int deleteUser(int id) { return getSqlSession().getMapper(UserMapper.class).deleteUser(id); } }
UserMapper.xml
<!--namespace==绑定一个对应的Dao/Mapper接口,以后Mapper.xml文件都放在resourse下, 但是要建立一个跟Mapper接口相对应得包 注意!!这里有一个坑,当在resources下建立包时候,不要写为:com.xxx.dao 应该为:com/xxx/dao --> <!--诡异事件,在学习mabatis适合,写UTF-8没错,但是整合这里的所有XML却报错:1 字节的 UTF-8 序列的字节 1 无效。 解决方法:将所有的XML文件UTF-8改为UTF8即可--> <mapper namespace="com.xxx.mapper.UserMapper"> <!-- last_name已经进行自动驼峰转换,则这里不用resultMap进行不同名的映射 resultType中也起了别名,不用再写com.xxx.pojo了 --> <select id="queryUser" resultType="User"> select * from user </select> <insert id="addUser" parameterType="User"> insert into user (id,last_name,email) values (#{id},#{lastName},#{email}) </insert> <delete id="deleteUser" parameterType="_int"> delete from user where id = #{id} </delete> </mapper>
测试及结果
手动设置错误,如在插入语句上写错insert为inserts
<insert id="addUser" parameterType="User"> inserts into user (id,last_name,email) values (#{id},#{lastName},#{email}) </insert>
public class UserMapperTest { static Logger logger = Logger.getLogger(UserMapperTest.class); @Test @Test public void userMapperImpl_2(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapperImpl_2 = context.getBean("userMapperImpl_2", UserMapper.class); userMapperImpl_2.addUser(new User(12,"xiaoxin","com@xiaoxin")); userMapperImpl_2.deleteUser(8); for (User user : userMapperImpl_2.queryUser()) { System.out.println(user); } } }
如果为插入语句错误,则项目不能正常插入,事务会回滚。
查看并刷新数据库表,没有变化。
接着将错误改正后,再次测试结果为:
成功添加和删除,事务保证了数据的一致性。查看数据库表为:
总结
本篇文章的内容就到这了,希望大家可以喜欢,也希望大家可以多多关注自学编程网的其他精彩内容!
- 本文固定链接: https://zxbcw.cn/post/215652/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)