一. Spring框架整理
Spring全家桶 : Spring、SpringMVC、Spring Boot、Spring Cloud
1. Spring-认识框架
出现原因 : 解决企业开发的难度。减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
Spring核心技术 : IOC、AOP。能够实现模块之间、类之间的解耦合。
依赖 : Class A中使用了Class B的属性或方法,叫做Class A依赖Class B。
Spring能做什么 : Microservices(微服务)、Reactive(反应式编程)、Cloud(云服务)、Web apps、Event Driven(事件驱动)、Serverless、Batch……
Projects : Spring Boot、Spring Framework、Spring Data、Spring Cloud、Spring Cloud Data Flow、Spring Security、Spring Session、Spring Integration、Spring HATEOAS、Spring REST Docs、Spring Batch、Spring AMQP、Spring for Android、Spring CredHub、 Spring Flo、Spring for Apache Kafka、Spring LDAP、Spring Mobile、Spring Roo、Spring Shell、Spring Statemachine、Spring Vault、Spring Web Flow、Spring Web Services
2.Spring-框架内部模块
Spring 由20多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP、Aspects)、提供JVM的代理(Instrument)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)
每个模块的主要功能 :
- Spring Core : Core封装包是框架的最基础的部分,提供IOC和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正的允许你从程序逻辑中分离出依赖关系和配置。
- Spring Context : 构建于Core封装包基础上的Context封装包,提供了一种框架式的对象访问方法,有些像JNDI注册器。Context封装包的特性得自于Beans封装包,并添加了对国际化(I18N)的支持(例如资源绑定),事件传播,资源装载的方式和Context的透明创建,比如通过Servlet容器。
- Spring DAO :DAO(Data Access Object)提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码。并且,JDBC封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的POJOs都适用。
- Spring ORM :ORM封装包提供了常用的"对象/关系"映射APIs的集成层,其中包括JPA、JDO、Hibernate和iBatis。利用ORM封装包,可以混合使用所有Spring提供的特性进行"对象/关系"映射,如前面提到的简单声明性事务管理。
- Spring AOP :Spring的AOP封装包提供了符合AOP Alliance规范的面向切面的编程实现,让你可以定义,例如方法拦截器(method-intercreptors)和切点(pointcuts),从逻辑上讲,从而减弱代码的功能耦合,清晰的被分离开。而且,利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中。
- Spring Web :Spring中的Web包提供了基础的针对Web开发的集成特性,例如多方文件上传,利用Servlet listeners进行IOC容器初始化和针对Web的ApplicationContext。与WebWork或Struts一起使用Spring时,这个包使Spring可与其他框架结合。
- Spring Web MVC : Spring中的MVC封装包提供了Web应用的Model-View-Controller(MVC)实现。Spring的MVC框架并不仅仅提供一种传统的实现,它提供了一种清晰的分离模型,在领域模型代码和Web Form之间。并且,还可以借助Spring框架的其他特性。
3.Spring-IOC介绍
概述 :
控制反转(IOC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通 过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了容器外部。通过容器实现对象 的创建、属性赋值,依赖的管理。
控制 : 创建对象,对象之间的属性赋值,对象之间的关系管理。
反转 : 把原来开发人员管理,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象、创建对象、给对 象赋值。
使用IOC的目的 :
目的就是减少对代码的改动,也能实现不同的功能。实现解耦合。
创建对象的方式 :
1. 构造方法, new Student();
2. 反射
3. 序列化
4. 克隆
5. IOC : 容器创建对象
IOC的体现 :
Servlet =>
1.创建类继承HttpServlet
2.在web.xml中注册Servlet,使用myServlet
com.wisedu.controller.MyServlet
3.没有创建 Servlet对象
4.Servlet对象是Tomcat服务器创建的。Tomcat也称为容器
Spring实现IOC的思路 :
- 应用程序提供类,提供依赖关系(属性或构造方法)
- 把需要交给容器管理的类通过配置信息告诉容器
- 把各个类之间的依赖关系通过配置信息告诉容器
IOC的技术实现(DI) :
DI是IOC的技术实现,
DI(Dependency Injection) : 依赖注入,只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建,赋值, 查找都由容器内部实现。
Spring是使用的DI实现了IOC功能,底层创建对象使用的是反射机制
4.Spring-第一个例子创建对象
⚪ 实现步骤 :
1. 创建maven项目
2. 加入maven的依赖
Spring的依赖,版本5.2.5版本
junit依赖
3. 创建类(接口和它的实现类)
4. 创建Spring需要使用的配置文件,声明类的信息,由spring统一创建、装配
⚪ 创建和管理Spring编程风格 :
- schemal-based -------->xml ==>通过bean标签创建对象指定依赖关系(p和c名称空间)
- annotation-based ----->annotation ==>通过引入context名称空间,开启注解扫描组件和扫描包的位置
- java-based ------------->java Configuration ==>完全注解开发,创建一个类 类名上引用注解
@Configuration
@ComponentScan("包名")
@ImportResource("classpath: ")
⚪ Spring的配置文件 :
-
beans: 是根标签,spring把java对象称为bean
-
spring-beans.xsd是约束文件,和mybatis指定的dtd是一样的。
-
bean标签去声明bean,告诉spring要创建某个类的对象
id : 对象的自定义名称,唯一值。spring通过这个名称找到对象
class : 类的全限定名称 ( 不能是接口,因为spring是反射机制,必须使用类)
-
spring创建好对象以后放入到容器中,容器可以简单的理解为一个Map集合
springMap.put(id的值,对象);
例如 : springMap.put("apiImp",new ApiImp());
⚪ 创建容器对象ApplicationContext :
方式一 : BeanFactory
IOC容器基本实现,只能提供基本的DI功能。加载配置文件的时候不会创建对象,而在我们获 取对象的时候才创建对象
方式二 : ApplicationContext
继承了BeanFactory后派生而来的应用上下文,它能提供更多企业级的应用,例如解析文本信 息等。对于上下文抽象接口,Spring也为我们提供了多种类型的容器实现,供我们在不同的应用场景 选择 :
- AnnotationConfigApplicationContext : 从一个或多个基于Java的配置类中加载上下文定义,适用于Java注解的方式;
- ClassPathXmlApplicationContext : 从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;
- FilesSystemXmlApplicationContext : 从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件
- AnnotationConfigWebApplicationContext : 专门为web应用准备的,适用于注解方式;
- XmlWebApplicationContext : 从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式
5. Spring-Bean管理方式(Xml方式)
⚪ IOC操作、Bean管理 : Bean管理指的是两个操作
- Spring 创建对象
- Spring 注入属性
⚪ 实现Bean管理操作的两种方式
-
基于xml配置文件方式实现
-
基于xml配置文件方式创建对象
<bean id="",class="">
在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建,默认执行无参构造方法。
id : bean标签的唯一标识
class : 类的全路径(包含类名)
ref : 其他bean标签的id值
-
基于xml配置文件方式注入属性
DI : 依赖注入,就是注入属性。
-
-
基于注解的方式实现
<!--第一种方式 : 通过setter方式赋值-->
<!--常规注入普通类型-->
<bean id="user" class="com.wisedu.entity.User">
<property name="userName" value="任雨杰"/>
<property name="password" value="123456"/>
</bean>
<!--也可以使用p名称空间注入-->
<bean id="user" class="com.wisedu.entity.User" p:userName="任雨杰" p:password="123456"/>
<!--注入空值或特殊字符-->
<!--设置null值-->
<bean id="user" class="com.wisedu.entity.User">
<property name="userName" value="任雨杰"/>
<property name="password">
<null/>
</property>
</bean>
<!--属性包含特殊字符 <<、>>
可以使用转义字符 >的转义字符是> <的转义字符<
也可以使用<!CDATA[[]]> 通过CDATA进行设置
-->
<bean id="book" class="com.wisedu.Book">
<property name="bookName" value="<<南京>>"/>
</bean>
<bean id="book" class="com.wisedu.Book">
<property name="bookName">
<value><![CDATA[[<<南京>>]]></value>
</property>
</bean>
<!--注入集合类型属性-->
<!--集合中注入普通类型属性-->
<bean id="student" class="com.wisedu.Student">
<property name="courses">
<array>
<value>java课程</value>
……
<value>c++课程</value>
</array>
</property>
<property name="lists">
<list>
<value>张三</value>
……
<value>李四</value>
</list>
</property>
<property name="maps">
<map>
<enrty key="任雨杰" value="123456"></enrty>
……
<entry key="柴静" value="987654"></entry>
</map>
</property>
<property name="sets">
<set>
<value>Mysql</value>
……
<value>Oracle</value>
</set>
</property>
</bean>
<!-- 把集合过程抽取出来
使用util名称空间,完成集合注入
-->
<util:list id="bookList">
<vlue>张三</vlue>
……
<value>李四</value>
</util:list>
<bean id="book" class="com.wisedu.Book">
<property name="lists" ref="bookList"></property>
</bean>
<!--在集合中注入对象类型属性-->
<list>
<ref bean=""></ref>
</list>
<!--注入外部bean-->
<bean id="userService" class="com.wisedu.UserService">
<property name="userDao" ref="userDaoImp"></property>
</bean>
<bean id="userDaoImp" class="com.wisedu.UserDaoImp"/>
<!--注入内部bean和级联赋值-->
<!--注入内部bean-->
<bean id="emp" class="com.wisedu.Emp">
<peoperty name="ename" value="lucy"></peoperty>
<property name="gender" value="女"></property>
<property name="dept">
<bean id="dept" class="com.wisedu.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
<!--通过有参构造方法赋值-->
<bean id="book" class="com.wisedu.Book">
<constructor-arg name="bookName" value="柴静" ></constructor-arg>
<constructor-arg name="password" value="245"></constructor-arg>
</bean>
⚪ 自动装配
什么是手动装配?
上述需要我们自行将Bean中的特殊属性类型(其他Bean)去手动的注入或级联赋值,这就是自动装配。
什么是自动装配?
根据指定装配规则(属性名称或类型名称),Spring自动将匹配的值注入
关键字 : autowire 配置属性自动装配
常用值 : byName 根据setter方法后面的名字注入(例如SetDogs,则setter方法后面的名字为Dog),注入值bean的id值和该名字一致[1]
byType 根据属性类型注入(xml文件中只能有一个该类型的Bean标签,否则会报错),只能有一个Bean标签
[1] byName中匹配规则和属性名称无关,和setter方法后面的名字有关
⚪ 引入外部属性文件
应用场景 :
Spring整合Mybatis的时候需要设置数据库连接池,我们可以直接创建相关的Bean实例为其属性赋值。通过setter方式注入属性值,例如 :
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.driver"/>
<property name="url" value="jdbc:mysql//localhost:3306/smbms"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--
缺点 : 耦合度高,修改繁琐
-->
我们可以使用引入外部文件的方式为其赋值,创建一个单独的.properties文件记录其属性值,再引入到spring配置文件中
① 创建.properties文件
jdbc.DriverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc.mysql://localhost:3306/smbms?useSSL=false
jdbc.username=root
jdbc.passwrod=123456
② 引入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 名称空间 指定外部文件位置
<context:property-placeholder location="classpath:jdbc.properties"/>
④ 创建数据库连接池Bean实例
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.DriverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
6. Spring-Bean管理方式(注解方式)
🐇 什么是注解?
- 注解是特殊代码标记,格式 : @注解名称(属性名称=值,属性名称=值……)
- 使用注解,注解作用在类上、方法上、属性上、参数上
- 使用注解的目的 : 简化xml
🐇 Spring针对Bean管理创建的对象提供的注解 :2
- @Component ==> 普通注解
- @Service ==>业务逻辑层
- @Controller ==>web控制层
- @Repository ==> Dao层
6.1 开启注解的使用步骤
-
引入context 名称空间,开启注解组件扫描
<context:component-scan base-package="com.wisedu"/>
-
扫描多个包,多个包之间可以用( , )逗号隔开,也可以扫描上级目录
-
针对不同的Bean实例使用对应的注解@Component、@Repository……
-
开启注解扫描组件细节配置
⚪ 可以配置仅扫描对应的注解所在的Bean,示例 :
<context:component-scan base-package="com.wisedu">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
use-default-filters="" 表示不适用默认的filter,自己配置filter
context:include-filter="" 设置哪些扫描内容annotation,根据注解扫描
expressios="org.springframework.stereotype.Controller" 仅扫描带@Controller注解的内容
另外context:conponent-scan节点还有<context:exclude-filter>可以用来指定要排除的类,其用法和include-filter一致。
⚪ context:include-filter标签的type属性解析
Filter Type Examples Expression Description annotation org.example.SomeAnnotation 符合SomeAnnoation的target class assignable org.example.SomeClass 指定class或interface的全名 aspectj org.example..*Service+ AspectJ語法 regex org.example.Default.* Regelar Expression custom org.example.MyTypeFilter Spring3新增自定义Type,读作org.springframework.core.type.TypeFilter
6.2 注入属性
基于注解实现属性注入 :
- @Autowired 根据属性类型自动装配
- 创建Service或Dao实体类,在类上添加对应的注解@Service | @Repository
- 在Service中注入Dao对象,在Service类中添加Dao对象属性,在属性上使用注解
- @Qualifier 根据属性类型自动装配
- 这个注解需要和@Autowired一起使用
- 接口中有多个实现类时,需要指定注入的接口类型和具体的实现类名称
- @Resources 可以根据类型注入,也可以根据名称注入
- @Value 注入普通类型属性
- 注意细节 : @Autowired默认是按照类型注入Bean,如果有多个类型相同的Bean实例,
- 当@Autowired放在属性上面时,Spring根据属性名称来查找Bean实例
- 当@Autowired放在setter上面时,Spring按照参数名称来查找Bean实例^3^
- ==当@Autowired放在构造方法上时,Spring会按照有参构造注册该类的Bean实例对象,同时会根据参数名称来查找对应的Bean实例==
7. Spring-Bean管理方式(完全注解开发方式)
🌳 创建配置类,替代xml配置文件
🌳 @Configuration标注作为配置类,代替xml配置文件
🌳 @ComponentScan(basePackage="") 指定扫描的包名称
🌳 连接上下文 ApplicationContext context=new AnnotationConfigApplication(config.class);
@Configuration
@Component-scan(basepackage="com.rikc")
public class AppConfig(){
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(Datasource datasource) {
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFacotoryBean.setDataSource(datasource);
return SqlSessionFactoryBean;
}
@Bean
public DataSource dataSource(){
Properties prop=new Properties();
proper.load(TestDataSource.class.getResourceAsStream("jdbc-config.properties"));
DruidDataSource dataSource=DruidDataSourceFactory.createDataSource(prop);
return dataSource;
}
}
8. Spring-工厂方法与FactoryBean(实例化Bean)
- 理解工厂方法和FactoryBean是干什么的
- 静态工厂方法实例化Bean
- 工厂方法实例化Bena
- FactoryBean和实例化Bean(工厂Bean)
Spring的Bean管理(Xml方式)向IOC容器中实例化bean的方式 :
反射模式
静态工厂模式
FactoryBean模式
📕反射模式主要通过在Bean的装配中,声明类全名,第五节主要采用的反射模式下的Bean装配机制
8.1 静态工厂模式
静态工厂模式应用场景 : 单例模式中实体类无法通过new 关键字来创建对象,只能通过静态工厂方 法来创建
//computer类,这是一个单例实体类
public class Computer{
private static Computer instance;
private Computer(){}
public static Computer getInstance(){
if(instance==null){
instance=new Computer();
}
return instance;
}
}
//电脑工厂
public class FactoryComputer{
private static Computer singleton;
public static Computer getComputer(){
if(singleton==null){
singleton=Computer.getInstance();
}
return singleton;
}
}
<bean id="factoryComputer" class="com.wisedu.FactoryComputer" factory-method="getComputer"/>
<!--参数解释 :
factory-method : 允许我们调用一个指定的静态工厂方法,从而代替构造方法来创建一个类的实例(即指定静态工厂方法名)
class : 不再是bean实例,而是Bean实例的静态工厂类,
如果静态工厂方法需要参数,使用为其配置
-->
Test测试类:
public class Atest {
@Test
public void test(){
ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml");
//获取的是电脑工厂Bean实例,返回值确实电脑Bean实例
Computer computer=(Computer) context.getBean("factoryComputer");
System.out.println(computer);
}
}
输出打印结果为 : com.wisedu.Computer@58c1670b
8.2 工厂方法实例化Bean(Instance Factroy Method)
public class Computers implements FactoryBean<Computer> {
@Override
public Computer getObject() throws Exception {
return Computer.getInstance();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<bean id="computers" class="com.wisedu.Computers"/>
<!--属性参数
factory-bean : 指定工厂方法所在的工厂类实例(即工厂方法bean的id,与ref类似)
factory-mehtod:指定工厂方法名
-->
测试类 :
public class Atest {
@Test
public void test(){
ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml");
Computer computer=(Computer) context.getBean("computers");
System.out.println(computer);
}
}
打印结果为 : com.wisedu.Computer@3d921e20
FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,FactoryBean,本身和其他注册到容器中的对象一样,只是一个Bean,只不过这里类型的Bean本身就是生产对象的工厂,调用了其实现方法getObject();
应用场景 :
当某些对象的实例话过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个 实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器中的时候,就可以实现org.spring- framework.beans.factory.FactoryBean接口,给出自己的对象实例化代码。当然实现自定义工厂也是可 以的。但是FactoryBean是Spring的标准。例如 : spring往IOC容器中添加Bean的方式有很多种,例如在 xml中使用bean标签,使用 @Bean 、 @Component 等注解,那为什么还要设计这么一种方式给容器中 添加Bean呢?主要原因是为了服务第三方类。我们在一个项目中除了使用Spring框架以外,可能还要 用到其他很多第三方框架,例如 ORM 框架 mybatis ,为了使用IOC容器进行解耦,我们需要将第三方 框架中的核心类加入到spring的IOC容器中去,那怎么加进去呢?我们不能修改第三方框架的代码,因 此在类上加入 @Compontent 或其他注解肯定是不行的,于是我们还可以使用 bean 标签在xml文件中 定义,这种方式行不通,原因是如果我们需要添加的 Bean有很多属性,则 bean标签的数量会让任何 一个程序员崩溃。当然还可以在配置类中使用 @Bean 注解,自己new一个这个对象,然后加入到IO C容器中去,这种方式固然可以,但是局限也很大,如果我们还想使用 @Component 或者Service 等注 解灵活在添加到容器中,就必须借助 FactoryBean
FactoryBean和BeanFactory区别
- FactoryBean:就是笔者上文阐述的,以Bean结尾,表示它是个Bean,它并不是简单的Bean,而是一个能生产对象或者修饰对象的工厂Bean
- 基础类型IoC容器,提供完整的IoC服务支持,如果没有特殊指定,默认采用延迟初始化策略。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入。所以相对来说,容器启动初期速度较快(因为延迟初始化了),所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较适合的。但话说回来,Bean工厂对大多数应用来说往往太低级了,所以一般不使用
9. Spring-Bean管理(Bean的作用域和声明周期)
9.1 Bean的作用域
- singleton
- prototype
- request
- session
在Spring里面,默认情况下,bean实例是单例对象。在Spring里面,可以设置bean实例是单例还是多例
⚪ 在Spring配置文件bean标签里面有属性scope 可以设置单例还是多例
⚪ scope属性值 :
第一个值,默认值singleton,表示单例对象
【singleton作用域】
singleton是默认的作用域,当定义Bean时,如果没有指定scope配置项,Bean的作用域被默认为singleton。singleton属于单例模式,在整个系统上下文环境中,仅有一个Bean实例。也就是说,在整个系统上下文环境中,你通过Spring IOC获取的都是同一个实例。
第二个值,prototype,表示多实例对象
【prototype作用域】
当一个Bean的作用域被定义prototype时,意味着程序每次从IOC容器获取的Bean都是一个新的实例。因此,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
⚪ singleton和prototype区别 :
singleton表示单实例,prototype表示多实例
设置scope的值是singleton时,加载spring配置文件会立即创建对象 (立即加载)
设置scope的值是prototype时,不是在加载spring配置文件的时候创建对象,在调用getBean的 时候创建多实例对象。
⚪ request
该属性仅对Http请求产生作用,使该属性定义bean,每次Http请求都会创建一个新的bean,适用 于WebApplicationContext环境。
⚪ session
同一个session共享一个bean实例,不同seesion使用不同的实例。
9.2 Bean的声明周期
【加上Bean的后置处理器有7步,否则是5步】
- 通过构造器创建Bean实例(无参数构造)
- 为Bean的属性设置和对其他Bean的引用调用(调用setter方法)
- 把Bean实例传递给Bean后置处理器的方法【postProcessBeforeInitialization】
- 调用Bean里面的初始化的方法(需要进行配置) init-method
- 把Bean的实例传递给Bean后置处理器的方法【postProcessAfterInitialization】
- 获取Bean对象
- 当容器关闭的时候,调用bean的销毁方法(需要配置销毁的方法)destroy-method
各种接口方法分类 :
- Bean自身的方法 : 这个包括了Bean本身调用的方法和通过配置文件中的init-method和destroy-method指定的方法
- Bean级生命周期接口方法 : 这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法。
- 容器级生命周期接口方法 : 这个包括了InstantiationAwareBeanPostProcessor和BeanPostProcessor这两个接口实现,一般称它们的实现类为后处理器。
- 工厂后处理器接口方法:这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
10. Spring-AOP基本概念
10.1 什么是AOP?
与OOP相比,面向切面,传统的OOP开发中的代码逻辑是至上而下的,在这些至上而下的过程中会产生一些横切性的问题,这些横切性的问题和我们的主业务逻辑关系不大,会散落在代码的各个地方,造成难以维护,AOP的编程思想就是把业务逻辑和横切的问题进行分离,从而达到解耦的目的,使代码的重用性和开发效率高。
- JDK动态代理
- CGLIB代理
- 编译时期的织入还是运行时的织入?两者都是在运行时期织入
- 初始化时期织入还是获取对象时期织入?通过源码分析,可以知道是在初始化时期织入
AOP的应用场景: 1.日志记录 2.权限验证 3.效率检查 4.事务管理
Spring-Aop和AspectJ的关系 :
Spring AOP提供两种编程风格
- @AspectJ support ------利用AspectJ的注解
- Schema-based AOP suppert --------xml方式
10.2 AOP动态代理的两种方式
第一种 :
-
有接口情况,使用JDK动态代理
- 创建接口实现类代理对象
- 增强类的方法
/**
JDK动态代理
(1)使用JDK动态代理,使用Proxy类里面的方法创建代理对象
(2)public static Object newProxyInstance(ClassLoader lodaer,Class<?>[] interface,InvocationHanler handler)
参数解析 :
ClassLoader loader : 目标对象的类加载器,通过反射得到
Class<?>[] interface : 目标对象实现的接口,通过反射得到
InvocationHandler handler : 方法调用处理器的是实现类
(3)第三个方法参数是一个InvacationHandler接口的实现类,需要重写invoke()方法,以增强目前对象的功能
public Object invoke(Object target,Method method,Object[] args)
参数解析 :
Object target : 真实主题的目标对象
Method method : 要执行的目标方法对象
Object[] args : 执行方法时所需要的参数
method.invoke()方法表示调用真实主题对象的方法,写在此语句前的代码是预处理,写在此语句后的代码是处理操作。
*/
@Configuration
@ComponentScan(basePackages = {"com.rikc.pojo"})
@Repository
public class UserDaoProxy {
@Autowired
@Qualifier("userDaoImp")
private UserDao userDao;
public UserDao getUserDao(){
UserDao userDao1 =(UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(), UserDaoImp.class.getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法的名字:" + method.getName());
Object invoke = method.invoke(userDao, args);
System.out.println("方法增强之后");
return invoke;
}
});
return userDao1;
}
}
第二种 :
-
没有接口情况,使用CGLIB动态代码
- 创建当前类子类的代理对象
- 增强类的方法
/**
Ⅰ.添加CGLIB的jar包依赖
<!-- CGLIB -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.11</version>
</dependency>
Ⅱ.创建Enhancer对象
Ⅲ.调用Enhancer对象的setSuperClass()方法设置父类,传入真实主题对象的class;
Ⅳ.调用Enhancer对象的setCallback()对象,该方法的参数是一个MethodInterceptor接口的实现类,需要重写intercept()方法,以增强目标对象的功能。
public Ojbect intercept(Object target,Method method,Object[] arg,MethodProxy methodProxy)
methodProxy.invokeSuper()方法表示调用真实主题对象的方法,写在此语句前的代码是预处理操作,写在此语句后的代码是后处理操作。
Ⅴ.调用create()创建代理对象
*/
public class UserServiceCGLIBProxy{
@Autowired
private UserService userService;
public UserService getProxy(){
Enhancer enhangcer=new Enhancer();
enhancer.setSuperClass(userService.getClass());
enhancer.setCallBack(new MethodIntercepter(){
public Ojbect intercept(Object target,Method method,Object[] arg,Method methodProxy) throw Throwable{
System.out.print("获取Session");
System.out.print("开启事务");
Object obj=null;
try{
//调用真实主题对象的方法
obj=methodProxy.invokeSuper(target,arg);
Symtem.out.print("提交事务");
}catch(Exception e){
System.out.print("回滚事务");
}finally{
System.out.print("关闭Session");
}
return obj;
}
});
UserService proxy=(UserService)enhancer.create();
return proxy;
}
}
10.3 AOP相关的术语名词
🌳 操作术语
-
Target --- 真实主题
Target是指被代理的目标对象
-
Proxy --- 代理主题
Proxy是指Target被AOP动态拦截,并织入增强代码后产生的结果集
-
Weaving --- 织入
Weaving是指把增强的功能应用到真实主题上,产生新的代理对象的过程
-
JointPoint --- 连接点
JoinPoint是指在真实主题的哪些位置进行动态织入,Spring的拦截粒度只支持到方法级别上,无法细化到代码块级别,所以连接点就指的是要拦截哪些方法
-
PointCut --- 切入点
PointCut是指连接点的具体描述和定义,需要定义拦截方法所在的包名、类名、方法名以及方法参数类型。
-
Advice --- 通知
Advice是指拦截到方法需要增强的代码。这些代码封装在一个方法中,每一个方法就是一个通知。
📕 Spring的通知分为5种
- 前置通知 --- 在目标方法之前执行
- 返回后通知 --- 在目标方法返回后执行,如果抛出异常则不执行
- 后置通知/最终通知 --- 在目标方法之后执行,无论是否抛出异常都会执行
- 异常通知 --- 在目标方法抛出异常之后执行
- 环绕通知 --- 在目标方法之前之后都执行
-
AspectJ --- 切面
AspectJ是一个类,类中定义了多个通知方法
🌳 切入点表达式
用来告诉Spring为哪些目标类生成代理对象,并指定拦截该目标类的方法
-
语法
【格式】 execution(方法返回类型 包名.类名.方法名(方法参数列表))
【通配符 * 】 包名、类名、方法名、返回类型都支持通配符*,表示任意多个字符
【通配符 .. 】 ..用在包名后,表示当前及其子包路径
..用在方法参数列表中,表示任意个数和类型的参数列表
-
常见的切入点表达式
🎯 execution(* *(..))
拦截所有包下的所有类的所有方法,参数个数和类型任意,返回类型任意
🎯 execution(* com.wisedu..(..))
拦截com.wisedu包下的所有类的所有方法
🎯 execution(* com.wisedu.UserServiceImp.*(..))
拦截com.wisedu包下UserServiceImp类的所有方法
🎯 execution(* com.wisedu.UserServiceImp.add*(..))
拦截com.wisedu包下的UserServiceImp类中所有以add开头的方法
🎯 execution(* com.wisedu.UserServiceImp.add(..))
拦截com.wisedu包下的UserServiceImp类中的add方法
🎯 execution(String com.wisedu.UserServiceImp.add(int,int))
拦截com.wisedu包下的UserServiceImp类中,返回类型是String,参数类型也是两个int的add方法
🌳 切点Pointcut的参数详解
【execution与within的区别】
·execution 由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型、类名、
方法名和参数名等与方法相关的信息,并且在Spring中,大部分需要使用AOP的业务场景也只需达到方法级别即可。
example: execution(public * com.rikc.dao.*.*(..))
·within 最小粒度仅仅只能定义到类上 example: within(com.rikc.dao.*)
【args】
args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
example: @Pointcut("args(java.lang.Integer)")
应用场景: @Pointcut("pointCutWithin()&&!pointCutArgs()")
【重点 this和target的区别】
· this 匹配代理对象是否匹配指定的类
example: @Pointcut(this(com.rikc.dao.UserDao))
proxyTargetClass=false 使用JDK动态代理,则可以匹配 否则不匹配
· target 目标对象是否匹配当前指定的类
example: @Pointcut(com.rikc.dao.UserDao)
【Proceedingjoinpoint 和 JoinPoint的区别】
ProceeddingJoinPoint继承了JoinPoint,
proceed()这是aop代理链执行的方法
JoinPoint的方法
→java.lang.Object[] getArgs(); 获取连接点运行时的参数列表
→Signature getSignature(); 获取连接点的方法签名对象
→java.lang.Object.getTarget(); 获取连接点所在的目标对象
→java.lang.Object.getThis(); 获取代理对象本身
→proceed() 有重载,有个带参数的方法,可以修改目标方法的参数
【Introductions】
通过注解@DeclareParents(value="com.rikc.dao.*",defaultImp=IndexDao.class)引入
目标类实现指定的接口,默认为指定的类中的方法
【切面的作用域或模型】
【perthis】
perthis:每个切入点表达式匹配的连接点对应的AOP对象都会创建一个新的切面实例,使用 @Aspect("perthis(切入点表达式)")指定切入点表达式;
如@Aspect("perthis(this(com.rikc.dao.userDao))")将对每个匹配 "this(com.rikc.dao.userDao)"切入点表达式的AOP代理对象创建一个切面实例。
🌳 AOP操作准备工作
-
Spring框架一般基于AspectJ实现AOP操作
-
AspectJ不是Spring组成部分,独立的AOP框架,一般将AspectJ和Spring框架一起使用,进行AOP操作。
-
基于AspectJ实现AOP操作
- 基于Xml配置文件
- 基于注解方式实现
-
引入AOP依赖Spring-aspects
-
定制切入点表达式
10.4 AOP管理方式(Xml方式)
-
在pom.xml文件中添加AOP相关的jar包依赖
-
Spring-context --- Spring的核心基础包,自动导入了aop的功能
-
aspectj --- AspectJ是AOP思想的一种具体体现, 通知、连接点、切入点、切面,都是AspectJ提出的概念
<!-- Spring Context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- Aspectj 声明式事务管理 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
-
-
Spring.xml配置文件中 --- 添加aop名称空间的声明
xmlns:aop="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
-
编写切面类,将要织入真实主题类中的代码封装成通知方法。
-
在Spring.xml配置文件中使用标签配置真实主题类和切面类
-
在Spring.xml文件中使用aop:config标签配置切面类
-
aop:config标签中包括两个子标签 : aop:pointcut和aop:aspect
-
使用aop:pointcut子标签配置切入点,告诉Spring框架 : 切面类要拦截目标对象的哪些业务方法
<!--配置切入点 expression属性指定切入点表达式,即切面的拦截范围 id属性指定切入点的唯一标识 --> <aop:pointcut expression="切入点表达式" id="切入点的id"/>
-
使用aop:aspect子标签配置切面通知,告诉Spring框架 : 切面类拦截到业务后,应该调用哪些通知方法
<!---配置切面通知 ref属性指定切面类的id--> <aop:aspect ref="切面类的id"> <!--前置通知,在目标方法之前执行 method 属性指定切面类中的方法名, pointcut-ref属性指定切入点的id --> <aop:before method="前置通知方法名" pointcut-ref="切入点的id"/> <!--后置通知,在目标方法正确返回无异常抛出时执行 returning 属性指定方法中的结果参数名称 --> <aop:after-returning method="返回后通知方法名" pointcut-ref="切入点的id" returning="返回参数名"/> <!--异常通知,在目标方法抛出异常后执行 throwing 属性指定方法中的异常参数名称--> <aop:after-throwing method="异常通知方法名" pointcut-ref="切入点的id" throwing="异常参数名"/> <!--后置通知/最终通知,在目标方法之后执行,无论是否抛出异常最终都会 执行下--> <aop:after method="后置通知方法名" pointcut-ref="切入点的id"/> <!--环绕通知,在目标方法之前之后都执行--> <aop:around method="环绕通知方法名" pointcut-ref="切入点的id"/> </aop:aspect>
-
通知方法中的连接点参数 :
🐷 JoinPoint 参数 --- 连接点,封装了执行目标方法时的相关信息
getArgs()方法 --- 返回目标方法参数的对象数组
getSignature()方法 --- 返回目标方法签名信息
🐷 返回后通知方法中的结果参数 --- 目标方法执行后,方法的返回值自动注入到结果参数中,结果参数要和配置中的returning属性值保持一致。
🐷 异常通知方法中的异常参数 --- 目标方法执行时,抛出的异常对象自动注入到异常参数中,异常参数名要和配置文件中的throwing属性值保持一致
🐷 环绕通知方法中的参数类型是ProceedingJoinPoint,是JoinPoint的子类,调用proceed()方法保证流程继续执行。
public Ojbect aound(ProceedingJoinPoint proceeding) throws Throwable{ System.out.println("环绕通知之前"); //调用目标方法,类似于chain.doFilter() Object result=proceeding.proceed(); System.out.println("环绕通知之后"); return result; }
-
10.5 AOP管理方式(注解方式)
⭕️ 在Pom文件中添加context和aspectj的jar包依赖
⭕️ 在Spring.xml配置文件中添加context和aop名称空间的声明,并添加
<!--包扫描组件-->
<context:component-scan base-package="com.wisedu"/>
<!--AOP注解开启-->
<aop:aspectj-antoproxy/>
⭕️ 在切面类上添加AOP的注解
-
@Aspect --- 指定一个类为切面类,相当于aop:aspect的标签
-
@Ponintcut --- 指定切入点表达式,方法名对应切入点表达式的id,相当于aop:pointcut
@Pointcut("execution(* com.wisedu.service.imp.UserServiceImp.*(..))") public void pointCut(){}
-
@Before --- 前置通知,在目标方法之前执行,相当于aop:before标签
@Before("ponintCut()") [注意]: 切点表达式要加()
-
@AfterReturning --- 返回后通知,在目标方法返回后执行,如果抛出异常则不执行,相当于 aop:after-returning标签
@AfterReturning(value="ponintCut()" returning="result")
-
@AfterThrowing --- 异常通知,在目标方法抛出异常后执行,相当于aop:after-throwing标签
@AfterThrowing(value="pointCut()" throwing="e")
-
@After --- 后置最终通知,在目标方法之后执行,无论是否抛出异常最终都会执行,相当于 aop:after标签
@After(value="pointCut()")
-
@Around --- 环绕通知,在目标方法之前之后都执行,相当于aop:around标签
@Around(value="pointCut()")
10.6 AOP管理方式(完全注解开发方法)
@Configuration
@ComponentScan(basePackages={""})
@EnableAspectJAutoProxy(proxyTargetClass=true)[4]==>代表JDK动态代理
[4] 与10.3AOP操作参数详解中this和target有关
11. Spring-整合Mybatis
11.1 三个关键点
- 将Mybatis的DataSource交给Spring IOC容器创建并管理,使用第三方数据库连接池(Druid,C3P0等)代替Mybatis内置的数据库连接池
- 将Mybatis的SqlSessionFactory交给SpringIOC容器创建并管理,使用Spring-Mybatis整合jar包中提供的SqlSessionFactoryBean类代替项目中的MybatisUtil工具类
- 将Mybatis的接口代理方式生成的实现类,交给SpringIOC容器管理并创建
11.2 具体步骤
-
创建基于Java的Maven项目TestSpringMybatis
-
在Spring.xml配置文件中添加jar包的依赖
Spring核心、Mybatis核心、Mysql JDBC驱动、log4j日志、JUnit单元测试、Lombok插件、Druid数据库连接池、Spring和Mybatis整合包、Spring和ORM框架的整合包--此依赖会自动导入spring-orm、spring-jdbc、spring-tx三个jar包、Spring和JUnit的整合包
<dependencies> <!-- Spring核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- MyBatis核心 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <!-- MySQL JDBC驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!-- JUnit单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- Lombok插件 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> <scope>provided</scope> </dependency> <!-- Druid数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- Druid数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- Spring整合ORM框架 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- Spring整合JUnit --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> </dependencies>
-
创建Spring.xml配置文件,添加相应的Bean实例
-
配置Druid数据源的Bean
<!-- 配置Druid数据源的Bean --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
-
配置SqlSessionFactory的Bean,并注入Datasource
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--注入数据源--> <property name="datasource" ref="dataSource"/> <!--指定Mybatis配置文件的位置--> <property name="configLocation" value="classpath:mybatis.xml"/> <!--给实体类起别名--> <peoperty name="typeAliasePackage" value="com.wisedu.entity"/> </bean>
-
配置自动扫描Mapper的Bean --- MapperScannerCo'n'fi'gu'rer
<!--配置mapper接口的扫描器,将Mapper接口的是实现类自动注入到IOC容器中, 实现类Bean的名称默认为接口类名的首字母小写--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--basePackage属性指定自动扫描Mapper接口所在的包--> <property name="basePackage" value="com.wisedu.mapper"/> </bean>
-
-
注意事项 : Spring整合Mybatis后,每一个CRUD操作都会获取session,自动提交事务,最终关闭session,因此目前的整合还不足以满足实际项目对事务处理的需要,还需要继续整合业务层
11.3 DruidDataSource配置属性列表
配置 | 缺省值 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错。详情-点此处。 | |
url | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里 | |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxPoolPreparedStatement PerConnectionSize | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
validationQueryTimeout | 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 1分钟(1.0.14) | 有两个含义:1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
numTestsPerEvictionRun | 30分钟(1.0.14) | 不再使用,一个DruidDataSource只支持一个EvictionRun |
minEvictableIdleTimeMillis | 连接保持空闲而不被驱逐的最长时间 | |
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
12. Spring-整合PageHelper
🏐 在pom文件中添加PageHelper的jar包依赖
<!-- PageHelper分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.2.1</version>
</dependency>
🏐 修改spring.xml --- 在SessionFactory 的标签中注入plugins属性
<!-- 配置MyBatis的插件 -->
<property name="plugins">
<array>
<!-- PageHelper分页插件 -->
<bean id="pageHelper" class="com.github.pagehelper.PageHelper">
<property name="properties">
<props>
<prop key="dialect">mysql</prop>
</props>
</property>
</bean>
</array>
</property>
🏐 修改myabtis.xml --- 去掉标签,由IOC容器管理插件
<!-- 配置MyBatis的插件 -->
<plugins>
<!-- 配置分页插件 interceptor属性指定拦截器的完整类名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- dialect属性指定数据库方言 分页插件支持Oracle,MySQL,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库类型 -->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
13. Spring-整合JUnit
应用场景 :
单独使用JUnit时,必须通过代码创建IOC容器,并调用getBean()方法获取Bean对象
将JUnit整合到Spring中以后,由Spring框架自动创建IOC容器,使得测试更加方便
整合步骤 :
-
在pom文件中添加Spring和JUnit的整合jar包的依赖
<!-- Spring整合JUnit --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency>
-
在测试类上添加@Runwith注解和@ContextConfiguration注解
@Runwith注解 --- 指定Spring提供的单元测试类SpringJUnit4ClassRunner,使用它代理原生的JUnit
@ContextConfiguration --- 指定Spring配置文件的位置
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:spring.xml") public class TestSpringJUnit{...}
-
在测试类中使用@Autowired注解注入IOC容器中的Bean直接测试
14. Spring-整合声明式事务管理
14.1 什么是事务
【概述】
事务是数据库操作的最基本单元,逻辑上的一组数据,要么都成功,如果有一个失败所有操作都失败
【事务的四个特性ACID】
原子性 : 事务中的操作要么都成功,要么都时报
一致性 : 事务操作前后的数据操作总量不变
隔离性 : 多事务操作中,不会产生影响
持久性 : 事务提交数据以后,影响的结果是永久性的,如果没有另一个事务去修改那么就不会发送改变。
【Spring事务管理说明】
- 事务要添加到JavaEE三层架构里面Service层(业务逻辑层)
- Spring进行事务管理操作,有两种方式 : 编程式事务管理和声明式事务管理
- 声明式事务管理实现方式 : ① 基于注解方式和② 基于xml配置文件方式
- 在Spring进行声明式事务管理,底层使用AOP
【关键点】
📕 使用Spring提供的事务管理器DataSourceTransactionManager接管项目中的事务操作
📕 使用Spring基于AOP的声明式事务管理,动态为项目的业务层的方法添加事务控制代码
14.2 事务管理的两种方式--编程式VS声明式
🌳 编程式事务控制 --- 在业务层中手工编写控制事务的代码
//JDBC
Conntection conn=DriverManager.getConnection(...);//获取数据库连接
conn.setAutoCommit(false); //设置为手动提交事务模式
conn.commit();//提交事务
conn.rollback();//回滚事务
//Hibernate
Session session=sessionFacatory.openSession();//获取session
Transaction tx=session.beginTransaction();//开启事务
tx.commit();//提交事务
tx.rollback();//回滚事务
//Mybatis
SqlSession session=SqlSessionFactory.openSession();//获取session
session.commit();//提交事务
session.rollback();//回滚事务
🌳 声明式事务控制 --- 使用Spring提供的基于AOP的事务配置
Spring没有直接管理事务,而是将管理事务的工作委托给相应的持久性机制去实现,Spring只提供相应的事务管理器,相当于一个切面
-
spring-tx.jar中的事务管理器接口
org.springframework.transaction.PlatformTransactionManager
TransactionStatus getTransaction(@Nullable TransactionDefinition definition); void commit(TransactionStatus status); void rollback(TransactionStatus status);
-
spring-jdbc.jar中的JDBC事务管理器是西安类
org.springframework.jdbc.datasource.DataSourceTransactionManager
-
spring-orm.jar中的Hibernate事务管理器实现类
org.springframework.orm.hibernate5.HibernateTransactionManager
14.3 使用XML实现声明式事务管理的开发步骤
-
在pom.xml文件中添加spring整合orm框架的jar包依赖
spring-orm自动导入spring-jdbc、spring-orm、spring-tx三个jar包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.0.2.RELEASE</version> </dependency>
-
在pom文件中添加aspectj的jar包依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
-
修改spring.xml---添加aop和tx名称空间
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/aop http://www.springframework.org/schema/tx/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
-
配置事务管理器
<!--配置事务管理器--> <bean id="txManager" class="org.sprngframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入DataSource--> <property name="datasource" ref="datasource"/> </bean>
-
配置事务通知
<!--配置事务通知transaction-manager属性指定事务管理器的id--> <tx:advice id="taAdvice" transaction-manager="txManager"> <!--指定事务属性,即事务管理器如何控制事务--> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*" read-only="false"/> </tx:attributes> </tx:advice>
-
配置切入点表达式 并根据切入点表达式织入事务通知
<!--基于AOP配置声明事务管理,即指定在哪些类的哪些方法上使用事务管理通知--> <aop:config> <!--配置切入点表达式--> <aop:pointcut express="execution(* com.wisedu.service.imp.UserServiceImp.*(..))" id="pointCut"/> <!--根据切入点--> <aop:advisor advice-ref="txAdvice" pointCut-ref="pointCut"/> </aop:config>
14.4 事务属性详解
1. 通过配置事务属性,可以精准定义事务管理器如何控制事务的细节
<tx:attributes>
<tx:method name="业务方法名,可以使用*通配符"
read-only="是否设置为只读事务"
propagation="事务的传播行为"
timeout="超时时间"
rollback-for="需要事务回滚的异常"
no-rollback-for="不需要事务回滚的异常"
isolation="事务的隔离级别" />
</tx:attributes>
(1)read-only属性---指定事务是否只读,默认为false。取值为true表示只读事务,因为只读事务不修改数据,因此可以进行优化操作
(2)propagation属性---指定事务的传播行为,即设置业务方法是否需要事务环境
取值为REQUIRED | REQUIRES_NEW | SUPPORTS | NOT_SUPPORTED | MANDATORY | NEVER | NESTED,默认为REQUIRED
(3)timeout属性---指定事务的最长持续时间,如果事务一直没有提交或回滚,那么超出该时间后,系统将自动回滚事务。单位为秒,默认为-1,表示不限制超时时间
(4)rollback-for属性---指定哪些异常发生时回滚事务,取值为异常的完整类名
【注意】默认所有RuntimeException都会回滚,所有非RuntimeException都不回滚
(5)no-rollback-for属性---指定哪些异常发生时不回滚事务,取值为异常的完整类名。
(6)isolation属性---指定事务的隔离级别,取值为DEFAULT(默认,取决于数据库的设置) | READ_UNCOMMITTED | READ_COMMITTED | REPEATABLE_READ | SERIALIZABLE 2. 事务的传播行为
(1)Propagation.REQUIRED
指定当前的方法必须在一个事务中运行
如果方法运行时已存在事务,则加入到该事务中,否则创建一个新事务
(2)Propagation.REQUIRED_NEW
指定当前的方法必须在一个新的事务中运行,而不管当前是否存在事务
如果方法运行时已经存在事务,则挂起该事务,并创建一个新事务,方法执行完后,原先挂起的事务才继续恢复运行
3. 事务的隔离级别
(1)READ-UNCOMMITTED---读未提交---不加锁
事务A对数据做了修改,但尚未提交时,事务B读取数据,读取到的是事务A尚未提交的脏数据,会导致脏读现象,隔离性最低,但并发性最高
(2)READ-COMMITTED---读已提交---锁住当前正在读取的记录行
事务A对数据做了修改,并提交,事务B读取数据,虽然数据是正确的,但会导致两次读取的数据不一致现象,是Oracle和SQL Server数据库默认的隔离级别 常用
(3)REPEATABLE-READ---可重复读---锁住读取到的所有记录行
事务A对数据做了修改,并提交,事务B读取数据,读取到的是事务B修改之前的数据,即两次读取到的是相同的数据,只有提交后才能读取到事务A修改后的最新数据
当前数据被加锁,所以事务修改此条数据;但其它事务依然可以往表中插入或删除数据,会导致幻读现象,即两次读取的数据记录条数不一致,是MySQL数据库默认的隔离级别 常用
(4)SERIERLIZED---串行化---锁住整个表
事务A读取数据,并对数据做了修改,但尚未提交时,此时事务B需要读取数据,只能等待(加锁),当事务A提交后(解锁),事务B才能读取到已经提交的数据
两个事务完全独立,事务A操作完毕而且提交后,事务B才能开始操作,两个事务完全独立,互不影响。对整个表加锁,会导致所有事务串行执行,隔离性最高,但并发性最差
14.5 使用注解实现声明式事务管理的开发步骤
-
修改spring.xml --- 开启事务管理的注解配置,并指定事务管理器
<!-- 开启注解方式事务管理的配置,并指定事务管理器 --> <tx:annotation-driven transaction-manager="txManager"/>
-
在需要添加事务控制的地方,添加@Transactional注解
(1)将@Transactional注解定义到类上---类中的所有业务方法都使用相同的事务配置
(2)将@Transactional注解定义到方法上---不同业务方法上设置不同的事务配置处理规则
@Transactional( //指定事务是否只读,true表示只读事务,因为只读事务不修改数据,因此可以进行查询优化 timeout = -1, //指定事务的最长持续时间,如果事务一直没有提交或回滚,那么超出该时间后,系统将自动回滚事务。单位为秒,-1表示不限制超时时间 rollbackFor = ArithmeticException.class, //指定哪些异常发生时回滚事务 noRollbackFor = ArithmeticException.class, //指定哪些异常发生时不回滚事务 isolation = Isolation.DEFAULT, //指定事务的隔离级别为数据库的默认隔离级别 propagation = Propagation.REQUIRED //指定事务的传播行为 )