文章目录
- 1 SpringFramework核心技术栈
- 2 Spring容器
- 3 基于 xml 的Spring应用
- 4 基于 注解 的Spring应用
- 5 AOP
1 SpringFramework核心技术栈
-
Data Access:数据访问
-
Data Integration:数据集成
-
Web:Web开发
-
AOP:面向切面编程
-
Aspects:AOP思想实现
-
Core container:核心容器
-
Test:单元测试与集成测试
2 Spring容器
2.1 BeanFactory
2.1.1 快速入门
2.1.1.1 SpringIOC思想的体现
开发步骤:
1.导入Spring的jar包或Maven坐标;
<!--Spring核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.7</version>
</dependency>
2.定义UserService接口及其UserServiceImpl实现类;
public interface UserService {}
public class UserServiceImpl implements UserService {}
3.创建beans.xml配置文件,将UserServiceImpl的信息配置到该xml中;
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
4.编写测试代码,创建BeanFactory,加载配置文件,获取UserService实例对象
//创建BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//创建读取器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//加载配置文件
reader.loadBeanDefinitions("beans.xml");
//获取Bean实例对象
UserDao userService = (UserService) beanFactory.getBean("userService");
对应核心容器中的四个部分:
2.1.1.2 Spring中DI依赖注入
1.定义UserDao接口及其UserDaoImpl实现类;
public interface UserDao {}
public class UserDaoImpl implements UserDao {}
2.修改UserServiceImpl代码,添加一个setUserDao(UserDao userDao)用于接收注入的对象;
public class UserServiceImpl implements UserService {
public void setUserDao(UserDao userDao) {
System.out.println(userDao);
}
}
3.修改beans.xml配置文件,在UserDaoImpl的中嵌入配置注入;
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
4.修改测试代码,获得UserService时,setUserService方法执行了注入操作。
//创建BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//创建读取器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//加载配置文件
reader.loadBeanDefinitions("beans.xml");
//获取Bean实例对象
UserDao userService = (UserService) beanFactory.getBean("userService");
name:指的是UserServiceImpl里面UserDao的set方法名,首字母小写
ref:指的是配置文件里面引用的实例的id
2.2 ApplicationContext
2.2.1 快速入门
ApplicationContext 称为Spring容器,内部封装了BeanFactory,比BeanFactory功能更丰富更强大,使用ApplicationContext 进行开发时,xml配置文件的名称习惯写成applicationContext.xml
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
System.out.println(userService);
2.3 BeanFactory与ApplicationContext的关系
-
1.BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring 容器;
-
2.ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;
-
3.Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
-
4.Bean的初始化时机不同,原始
BeanFactory
是在首次调用getBean时才进行Bean的创建(延迟加载)
,而ApplicationContext
则是配置文件加载,容器一创建就将Bean都实例化并初始化好
。
2.4 BeanFactory的继承体系
BeanFactory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是DefaultListableBeanFactory,而ApplicationContext内部维护的Beanfactory的实现类也是它
2.5 ApplicationContext的继承体系
只在Spring基础环境
下,即只导入spring-context坐标时,此时ApplicationContext的继承体系
只在Spring基础环境下,常用的三个ApplicationContext作用如下:
实现类 | 功能描述 |
---|---|
ClassPathXmlApplicationContext | 加载类路径下的xml配置的ApplicationContext |
FileSystemXmlApplicationContext | 加载磁盘路径下的xml配置的ApplicationContext |
AnnotationConfigApplicationContext | 加载注解配置类的ApplicationContext |
3 基于 xml 的Spring应用
Xml配置方式 | 功能描述 |
---|---|
< bean id=“” class=“”> | Bean的id和全限定名配置 |
< bean name=“”> | 通过name设置Bean的别名,通过别名也能直接获取到Bean实例 |
< bean scope=“”> | Bean的作用范围,BeanFactory作为容器时取值singleton(单例)和prototype(原型),singleton是在加载配置文件的时候就创建好了对象放在容器中 ,prototype是在调用getBean()方法时才会创建对象 |
< bean lazy-init=“”> | Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效 |
< bean init-method=“”> | Bean实例化后自动执行的初始化方法,method指定方法名 |
< bean destroy-method=“”> | Bean实例销毁前的方法,method指定方法名 |
< bean autowire=“byType”> | 设置自动注入模式,常用的有按照类型byType,按照名字byName |
< bean factory-bean=“” factory-method=“”/> | 指定哪个工厂Bean的哪个方法完成Bean的创建 |
3.1 bean的基础配置
配置UserDaoImpl由Spring容器负责管理
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
3.2 bean的别名配置
可以为当前Bean指定多个别名,根据别名也可以获得Bean对象
<bean id="userDao" name="aaa,bbb" class="com.itheima.dao.impl.UserDaoImpl"/>
此时多个名称都可以获得UserDaoImpl实例对象
applicationContext.getBean("userDao");
applicationContext.getBean("aaa");
applicationContext.getBean("bbb");
3.3 bean的范围配置
默认情况下,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype
- singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
- prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
当scope设置为singleton时,获得两次对象打印结果是一样的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton"/>
当scope设置为prototype时,获得两次对象打印结果是不一样的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype"/>
3.4 Bean的延迟加载
当 lazy-init 设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" lazy-init="true"/>
3.5 Bean的初始化和销毁方法配置
Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" init-method="init"
destroy-method="destroy"/>
public class UserDaoImpl implements UserDao {
public UserDaoImpl() { System.out.println("UserDaoImpl创建了..."); }
public void init(){ System.out.println("初始化方法..."); }
public void destroy(){ System.out.println("销毁方法..."); }
}
3.6 Bean的实例化配置
Spring的实例化方式主要如下两种:
- 构造方式实例化:底层通过构造方法对Bean进行实例化
- 工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化
3.6.1 构造方式实例化Bean
构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的几乎都是无参构造方式
,此处不在赘述。下面讲解有参构造方法实例化Bean
//有参构造方法
public UserDaoImpl(String name){
}
有参构造在实例化Bean时,需要参数的注入,通过标签,嵌入在标签内部提供构造参数,如下:
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<constructor-arg name="name" value="haohao"/>
</bean>
3.6.2 工厂方式实例化Bean
- 静态工厂方法实例化Bean
- 实例工厂方法实例化Bean
- 实现FactoryBean规范延迟实例化Bean
静态工厂方法
实例化Bean,其实就是定义一个工厂类,提供一个静态方法用于生产Bean实例,在将该工厂类及其静态方法配置给Spring即可
//工厂类
public class UserDaoFactoryBean {
//非静态工厂方法
public static UserDao getUserDao(String name){
//可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean" factory-method="getUserDao">
<constructor-arg name="name" value="haohao"/>
</bean>
实例工厂方法
,也就是非静态工厂方法产生Bean实例,与静态工厂方式比较,该方式需要先有工厂对象,在用工厂对象去调用非静态方法,所以在进行配置时,要先配置工厂Bean,在配置目标Bean
//工厂类
public class UserDaoFactoryBean2 {
//非静态工厂方法
public UserDao getUserDao(String name){
//可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<!-- 配置实例工厂Bean -->
<bean id="userDaoFactoryBean2" class="com.itheima.factory.UserDaoFactoryBean2"/>
<!-- 配置实例工厂Bean的哪个方法作为工厂方法 -->
<bean id="userDao" factory-bean="userDaoFactoryBean2" factory-method="getUserDao">
<constructor-arg name="name" value="haohao"/>
</bean>
上面不管是静态工厂方式还是非静态工厂方式,都是自定义的工厂方法,Spring提供了FactoryBean的接口规范
,FactoryBean接口定义如下:
定义工厂实现FactoryBean
public class UserDaoFactoryBean3 implements FactoryBean<UserDao> {
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
配置FactoryBean交由Spring管理即可
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean3"/>
3.7 Bean的依赖注入配置
Bean的依赖注入有两种方式:
注入方式 | 配置方式 |
---|---|
通过Bean的set方法注入 | < property name=“userDao” ref=“userDao”/> < property name=“userDao” value=“haohao”/> |
通过构造Bean的方法进行注入 | < constructor-arg name=“name” ref=“userDao”/>< constructor-arg name=“name” value=“haohao”/> |
依赖注入的数据类型有如下三种:
- 普通数据类型,例如:String、int、boolean等,通过value属性指定。
- 引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定。
- 集合数据类型,例如:List、Map、Properties等。
3.7.1 自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在 标签中使用 autowire 属性去配置自动注入方式,属
性值有两个:
- byName:通过属性名自动装配,即去匹配 setXxx 与 id=“xxx”(name=“xxx”)是否一致;
- byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" autowire="byType">
3.8 Spring的get方法
方法定义 | 返回值和参数 |
---|---|
Object getBean (String beanName) | 根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转 |
T getBean (Class type) | 根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转 |
T getBean (String beanName,Class type) | 根据beanName从容器中获得Bean实例,返回值为Class类型实例,无需强转 |
3.8 Spring 配置非自定义Bean
以上在 xml 中配置的Bean都是自己定义的,例如:UserDaoImpl,UserServiceImpl。但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置
配置非自定义的Bean需要考虑如下两个问题:
- 被配置的Bean的实例化方式是什么?无参构造、有参构造、静态工厂方式还是实例工厂方式;
- 被配置的Bean是否需要注入必要属性。
3.9 Bean实例化的基本流程
Spring容器在进行初始化时,会将xml配置的的信息封装成一个BeanDefinition
对象,所有的
BeanDefinition存储到一个名为BeanDefinitionMap
的Map集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象
,创建好的Bean对象存储在一个名为singletonObjects的Map
集合中,当调用getBean方法
时则最终从该Map集合中取出Bean实例对象返回。
3.9.1 Bean信息定义对象-BeanDefinition
DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap
public class DefaultListableBeanFactory extends ... implements ... {
//存储<bean>标签对应的BeanDefinition对象
//key:是Bean的beanName,value:是Bean定义对象BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap;
}
Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作
Bean实例及单例池singletonObjects, beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类
3.9.2 总结
- 加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;
- 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
- ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
- 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
- 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。
3.10 Spring的后处理器
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
- BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;
- BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。
3.10.1 BeanFactoryPostProcessor
BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。
BeanFactoryPostProcessor 定义如下:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}
3.10.2 BeanPostProcessor
Bean被实例化后
,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。
案例:
对Bean方法进行执行时间日志增强
要求如下:
⚫ Bean的方法执行之前控制台打印当前时间;
⚫ Bean的方法执行之后控制台打印当前时间。
分析:
⚫ 对方法进行增强主要就是代理设计模式和包装设计模式;
⚫ 由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作;
⚫ 在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真是的目标Bean
编写BeanPostProcessor,增强逻辑编写在 after方法中
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//对Bean进行动态代理,返回的是Proxy代理对象
Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
long start = System.currentTimeMillis();
System.out.println("开始时间:" + new Date(start));
//执行目标方法
Object result = method.invoke(bean, args);
long end = System.currentTimeMillis();
System.out.println("结束时间:" + new Date(end));
return result;
});
//返回代理对象
return proxyBean;
}
3.11 SpringBean的生命周期
Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段
:
- Bean的
实例化阶段
:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化; - Bean的
初始化阶段(the most important)
:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等
spring高频面试题Bean的循环引用问题都是在这个阶段体现的; - Bean的
完成阶段
:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池
singletonObjects中去了,即完成了Spring Bean的整个生命周期。
3.11.1 Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
- Bean实例的属性填充
- Aware接口属性注入
- BeanPostProcessor的before()方法回调
- InitializingBean接口的初始化方法回调
- 自定义初始化方法init回调
- BeanPostProcessor的after()方法回调
Spring在进行属性注入
时,会分为如下几种情况:
- 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
- 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
- 注入双向对象引用属性时,就比较复杂了,涉及了循环引用
(循环依赖)问题
,下面会详细阐述解决方案。
3.11.1.1 循环依赖
多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环引用"
public class UserServiceImpl implements UserService{
public void setUserDao(UserDao userDao) {}
}
public class UserDaoImpl implements UserDao{
public void setUserService(UserService userService){}
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<property name="userService" ref="userService"/>
</bean>
代码验证后,分析出UserService与UserDao实例化与初始化的顺序如下:
Spring提供了 三级缓存存储
完整Bean实例
和 半成品Bean实例
,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:
public class DefaultSingletonBeanRegistry ... {
//1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
//3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下(解决循环依赖的大致过程
)
- UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
- UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
- UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
- UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
- UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
- UserService 注入UserDao;
- UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
问题:为什么不用二级缓存要用三级缓存?
看下图所示,如果对a和b对象都进行了增强,b对象中注入的只能是pa = proxy(a)
之前的a对象,此时a对象还并未被增强,所以b中注入的不是 proxy(a) 而是原始a对象,这就是二级缓存的弊端
3.11.2 常用的Aware接口
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。
Aware接口 | 回调方法 | 作用 |
---|---|---|
ServletContextAware | setServletContext(ServletContext context) | Spring框架回调方法注入ServletContext对象,web环境下才生效 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | Spring框架回调方法注入beanFactory对象 |
BeanNameAware | setBeanName(String beanName) | Spring框架回调方法注入当前Bean在容器中的beanName |
ApplicationContextAware | setApplicationContext(ApplicationContext applicationContext) | Spring框架回调方法注入applicationContext对象 |
3.12 Spring IoC 整体流程总结
4 基于 注解 的Spring应用
4.1 Bean的基本注解开发
基本Bean注解,主要是使用注解的方式替代原有xml的 标签及其标签属性的配置
<bean id="" name="" class="" scope="" lazy-init="" init-method="" destroy-method=""
abstract="" autowire="" factory-bean="" factory-method=""></bean>
使用@Component 注解替代< bean >标签
xml配置 | 注解 | 描述 |
---|---|---|
< bean id=“” class=“”> | @Component | 被该注解标识的类,会在指定扫描范围内被Spring加载并实例化 |
< bean scope=“”> | @Scope | 在类上或使用了@Bean标注的方法上,标注Bean的作用范围,取值为singleton或prototype |
< bean lazy-init=“”> | @Lazy | 在类上或使用了@Bean标注的方法上,标注Bean是否延迟加载,取值为true和false |
< bean init-method=“”> | @PostConstruct | 在方法上使用,标注Bean的实例化后执行的方法 |
< bean destroy-method=“”> | @PreDestroy | 在方法上使用,标注Bean的销毁前执行方法 |
由于JavaEE开发是分层的,为了每层Bean标识的注解语义化更加明确,@Component又衍生出如下三个注解:
@Component衍生注解 | 描述 |
---|---|
@Repository | 在Dao层类上使用 |
@Service | 在Service层类上使用 |
@Controller | 在Web层类上使用 |
4.2 Bean依赖注入注解开发
Bean依赖注入的注解,主要是使用注解的方式替代xml的 < property> 标签完成属性的注入操作
<bean id="" class="">
<property name="" value=""/>
<property name="" ref=""/>
</bean>
Spring主要提供如下注解,用于在Bean内部进行属性注入的:
属性注入注解 | 描述 |
---|---|
@Value | 使用在字段或方法上,用于注入普通数据 |
@Autowired | 使用在字段或方法上,用于根据类型(byType)注入引用数据,若类型有多个,则找该类型变量名是否有对应的实例名字 |
@Qualifier | 使用在字段或方法上,结合@Autowired,根据名称注入 |
@Resource | 等价于(@Autowired+@Qualifier)使用在字段或方法上,根据类型或名称进行注入 |
4.3.1 @Autowired注解
用于根据类型进行注入
//使用在属性上直接注入
@Autowired
private UserDao userDao;
//使用在方法上直接注入
@Autowired
public void setUserDao(UserDao userDao){
System.out.println(userDao);
}
当容器中同一类型的Bean实例有多个时,会尝试自动根据名字进行匹配
:
//匹配当前Bean
@Repository("userDao")
public class UserDaoImpl implements UserDao{}
@Repository("userDao2")
public class UserDaoImpl2 implements UserDao{}
当容器中同一类型的Bean实例有多个时,且名字与被注入Bean名称不匹配时会报错
4.3.2 @Qualifier注解
@Qualifier配合@Autowired可以完成根据名称注入Bean实例,使用@Qualifier指定名称
@Autowired
@Qualifier("userDao2")
private UserDao userDao;
@Autowired
@Qualifier("userDao2")
public void setUserDao(UserDao userDao){
System.out.println(userDao);
}
4.3.3 @Resource注解
@Resource注解既可以根据类型注入,也可以根据名称注入,无参就是根据类型注入,有参数就是根据名称注入
@Resource
private UserDao userDao;
@Resource(name = "userDao2")
public void setUserDao(UserDao userDao){
System.out.println(userDao);
}
PS:@Resource注解存在与 javax.annotation 包中,Spring对其进行了解析
4.3 非自定义Bean注解开发
非自定义Bean不能像自定义Bean一样使用@Component进行管理,非自定义Bean要通过工厂的方式进行实例化
,使用@Bean标注方法即可,@Bean的属性为beanName,如不指定为当前工厂方法名称
PS:工厂方法所在类必须要被Spring管理
如果@Bean工厂方法需要参数的话,则有如下几种注入方式:
- 使用@Autowired 根据类型自动进行Bean的匹配,@Autowired可以省略
- 使用@Qualifier 根据名称进行Bean的匹配;
- 使用@Value 根据名称进行普通数据类型匹配。
4.4 bean配置类的注解开发
@Component等注解替代了< bean>标签,但是像< import>、< context:componentScan> 等非< bean> 标签怎样去使用注解替代呢?
定义一个配置类替代原有的xml配置文件
,< bean>标签以外的标签,一般都是在配置类上使用注解完成的
@Configuration
注解标识的类为配置类,替代原有xml配置文件,该注解第一个作用是标识该类是一个配置类,第二个作用是具备@Component作用
@ComponentScan
组件扫描配置,替代原有xml文件中<context:component-scan base-package=“”/>
base-package的配置方式:
- 指定一个或多个包名:扫描指定包及其子包下使用注解的类
- 不配置包名:扫描当前@componentScan注解配置类所在包及其子包下的类
@PropertySource
注解用于加载外部properties资源配置,替代原有xml中的 <context:property-placeholder location=“”/> 配置
@Import
用于加载其他配置类,替代原有xml中的< import resource=“classpath:beans.xml”/>配置
4.5 Spring配置其他注解
扩展:@Primary注解用于标注相同类型的Bean优先被使用权,@Primary 是Spring3.0引入的,与@Component和@Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过@Autowired根据类型进行注入时,会选用优先级更高的
扩展:@Profile 注解的作用同于xml配置时学习profile属性,是进行环境切换使用的
注解 @Profile 标注在类或方法上,标注当前产生的Bean从属于哪个环境,只有激活了当前环境,被标注的Bean才
能被注册到Spring容器里,不指定环境的Bean,任何环境下都能注册到Spring容器里
4.6 Spring注解的解析原理
使用@Component等注解配置完毕后,要配置组件扫描才能使注解生效
-
xml配置组件扫描:
<context:component-scan base-package="com.itheima"/>
-
配置类配置组件扫描:
@Configuration @ComponentScan("com.itheima") public class AppConfig { }
4.6.1 使用xml方式配置组件扫描
使用xml方式配置组件扫描,而component-scan是一个context命名空间下的自定义标签,所以要找到对应的命名空间处理器NamespaceHandler 和 解析器,查看spring-context包下的spring.handlers文件
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
查看 ContextNamespaceHandler 类
public void init() {
this.registerBeanDefinitionParser("component-scan", new
ComponentScanBeanDefinitionParser());
}
将ComponentScanBeanDefinitionParser进行了注册,对其源码进行跟踪,最终将标注的@Component的类,生成对应的BeanDefinition进行了注册
4.6.2 使用配置类配置组件扫描
使用配置类配置组件扫描,使用AnnotationConfigApplicationContext容器在进行创建时,内部调用了如下代码,该工具注册了几个Bean后处理器:
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
5 AOP
5.1 AOP简介
5.1.1 AOP的概念
AOP,面向切面编程。OOP是纵向对一个事物的抽象,一个对象包括对象静态的属性信息,包括动态的方法信息等。而AOP是横向对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程
5.1.2 AOP思想的实现方案
动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入其他增强逻辑或其他对象的方法
5.1.3 模拟AOP的基础代码
目标对象:UserServiceImpl中有两个方法,要对这两个方法进行增强
public class UserServiceImpl implements UserService {
@Override
public void show1() {
System.out.println("show1....");
}
@Override
public void show2() {
System.out.println("show2....");
}
}
增强(通知)对象:MyAdvice,里面有两个增强方法
public class MyAdvice {
public void beforeAdvice(){
System.out.println("前置的增强...");
}
public void afterAdvice(){
System.out.println("后置的增强...");
}
}
创建MockAopBeanPostProcessor类实现BeanPostProcessor,可以获取到bean的前后置方法(这两个方法是在bean属性填充完之后)。目标对象的bean初始化的过程中,获取该对象,通过反射创建该实例对象的代理对象,在执行InvocationHandler方法时,就可以对其方法进行增强,然后返回代理对象
。这时候容器中对应的对象都会被增强后返回代理对象,存入单例池当中
,执行对象的方法也是被增强后的方法
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//目的是要对UserServiceImpl中的show1()和show2()方法进行增强,增强的方法存在于MyAdvice中
//问题1:筛选service.impl包下的所有类的所有方法都可以进行增强?解决方案:if-else
//问题2:MyAdvice怎么获取?解决方法:从Spring容器中获得MyAdvice
if (bean.getClass().getPackage().getName().equals("com.itheima.service.impl")){
//生成当前Bean的proxy对象
Object beanProxy = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
//执行增强对象的before方法
MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);
myAdvice.beforeAdvice();
//执行目标对象的目标方法
Object result = method.invoke(bean, args);
//执行增强对象的after方法
myAdvice.afterAdvice();
return result;
}
);
return beanProxy;
}
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
执行结果:
前置的增强...
show2....
后置的增强...
5.1.4 AOP相关概念
概念 | 单词 | 解释 |
---|---|---|
目标对象 | Target | 被增强的方法所在的对象 |
代理对象 | Proxy | 对目标对象进行增强后的对象,客户端实际调用的对象 |
连接点 | Joinpoint | 目标对象中可以被增强的方法 |
切入点 | Pointcut | 目标对象中实际被增强的方法 |
通知/增强 | Advice | 增强部分的逻辑代码 |
切面 | Aspect | 增强和切入点的结合 |
织入 | Weaving | 将通知和切入点组合动态组合的过程 |
5.2 基于xml配置的AOP
5.2.1 xml方式AOP快速入门
之前写的AOP基础代码存在一些问题:
- 被增强的报名在代码写死了
- 通知对象的方法在代码中写死了
通过配置文件的方式去解决上述问题
- 配置哪些包、哪些类、哪些方法需要被增强
- 配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强
配置方式的设计、配置文件(注解)的解析工作,Spring已经帮我们封装好了
xml方式配置AOP步骤:
1.导入AOP相关坐标
2.准备目标类、准备增强类,并配置给Spring管理
3.配置切点表达式(哪些方法被增强)
4.配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)
<!--配置目标对象-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" ></bean>
<!--配置的通知类-->
<bean id="myAdvice" class="com.itheima.advice.MyAdvice"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切点表达式,目的就是要指定哪些方法被增强-->
<aop:pointcut id="myPointcut" expression="execution(void com.itheima.service.impl.UserServiceImpl.show1())"/>
<!--配置织如,目的是要指定哪些切点与哪些通知进行结合-->
<aop:aspect ref="myAdvice">
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
5.2.2 xml方式AOP配置详解
5.2.2.1 切点表达式
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下
execution([访问修饰符]返回值类型 包名.类名.方法名(参数))
其中
- 访问修饰符可以省略不写
- 返回值类型、某一级包名、方法名 可以使用
*
表示任意 - 包名与类名之间是用单点
.
表示该包下的类,是用双点..
表示该包及其子包下的类 - 参数列表可以使用两个点
..
表示任意参数
例子:
//表示访问修饰符为public、无返回值、在com.itheima.aop包下的TargetImpl类的无参方法show
execution(public void com.itheima.aop.TargetImpl.show ( ) )
//表述com.itheima. aop包下的TargetImpl类的任意方法
(多)execution(* com.itheima.aop.TargetImpl.* (..) )
//表示com.itheima.aop包下的任意类的任意方法
(最多)execution(* com.itheima.aop.*.* (..) )
//表示com.itheima.aop包及其子包下的任意类的任意方法
execution(* com.itheima.aop..* .* (..) )
//表示任意包中的任意类的任意方法
execution(* *..*.* (..) )
5.2.2.2 五种通知类型
AspectJ的通知有以下五种类型
通知名称 | 配置方式 | 执行时机 |
---|---|---|
前置通知 | <aop:before > | 目标方法执行之前执行 |
后置通知 | <aop:after-returning > | 目标方法执行之后执行,目标方法异常时,不在执行 |
环绕通知 | <aop:around > | 目标方法执行前后执行,目标方法异常时,环绕后方法不在执行 |
异常通知 | <aop:after-throwing > | 目标方法抛出异常时执行 |
最终通知 | <aop:after > | 不管目标方法是否有异常,最终都会执行 |
通知方法在被调用时,Spring可以为其传递一些必要的参数
参数类型 | 作用 |
---|---|
JoinPoint | 连接点对象,任何通知都可使用,可以获得当前目标对象、目标方法参数等信息 |
ProceedingJoinPoint | JoinPoint子类对象,主要是在环绕通知中执行proceed(),进而执行目标方法 |
Throwable | 异常对象,使用在异常通知中,需要在配置文件中指出异常对象名称 |
JoinPoint对象
public void 通知方法名称(JoinPoint joinPoint) {
//获得目标方法的参数
System. out.println(joinPoint.getArgs ());
//获得目标对象
system.out.println (joinPoint.getTarget () );
//获得精确的切点表达式信息
System.out.println (joinPoint. getstaticPart () );
}
Throwable对象
public void afterThrowing (JoinPoint joinPoint, Throwable e){
/获得异常信息
System.out.println ("异常对象是:"+e+"异常信息是:"+e.getMessage ()) ;
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="e"/>
5.2.2.3 两种切面配置 aspect 和 advisor
AOP的xml有两种配置方式,如下:
- 使用配置切面
- 使用配置切面
Spring定义了一个Advice接口,实现了该接口的类都可以作为通知类出现
public interface Advice {
}
定义类实现MethodBeforeAdvice(前置通知)、AfterReturningAdvice(后置通知)、MethodInterceptor(环绕通知)接口
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知...............");
}
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("后置通知...............");
}
}
public class MyAdvice3 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前************");
//1.执行目标
Object result = methodInvocation.getMethod().invoke(methodInvocation.getThis(), methodInvocation.getArguments());
System.out.println("环绕后************");
return result;
}
}
xml的配置如下:
<!--配置目标对象-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" ></bean>
<!--配置的通知类-->
<bean id="myAdvice2" class="com.itheima.advice.MyAdvice2"></bean>
<bean id="myAdvice3" class="com.itheima.advice.MyAdvice3"></bean>
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(void com.itheima.service.impl.*.*(..))"/>
<aop:advisor advice-ref="myAdvice3" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
AOP配置的两种语法形式不同点:
语法形式不同:
- advisar是通过实现接口来确认通知的类型
- aspect是通过配置确认通知的类型,更加灵活
可配置的切面数量不同:
- 一个advisor只能配置一个固定通知和一个切点表达式
- 一个aspect可以配置多个通知和多个切点表达式任意组合
使用场景不同:
- 允许随意搭配情况下可以使用aspect进行配置
- 如果通知类型单一、切面单一的情况下可以使用advisor进行配置
- 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如后面要学习的Spring事务控制的配置
5.2.3 xml方式AOP原理剖析
找到AOP包下的META-INF,去找spring.handlers文件
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
最终加载的是AopNamespaceHandler,该Handler的init方法注册了config标签对应的解析器
this.registerBeanDefinitionParser ( " config", new ConfigBeanDefinitionParser());
以ConfigBeanDefinitionParser作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator进入到Spring容器中,那该类作用是什么呢?看一下集成体系图
AspectJAwareAdvisorAutoProxyCreator的上上级父类AbstractAutoProxyCreator中的postProcessAfterlnitialization方法
//参数bean:为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean,String beanName) {
if (bean ! = nu11){
Object cacheKey = this.getCacheKey(bean.getclass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
//如果需要被增强,则wrapIfNecessary方法最终返回的就是一个Proxy对象
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
通过断点方式观察,当bean是匹配切点表达式时,this.wrapIfNecessary(bean, beanName, cacheKey)返回的是一个JDKDynamicAopProxy
可以在深入一点,对wraplfNecessary在剖析一下,看看是不是我们熟知的通过JDK的
Proxy.newProxyInstance(ClassLoader loader, Class<?> [] interfaces,InvocationHandler h)的方式创建的代理对象呢?经过如下一系列源码跟踪
5.2.3.1 动态代理的实现选择(基于JDK和CGLIB)
动态代理的实现的选择,在调用getProxy()方法时,我们可选用的 AopProxy接口有两个实现类,如上图,这两种都是动态生成代理对象的方式,一种就是基于JDK的,一种是基于Cglib的
代理技术 | 使用条件 | 配置方式 |
---|---|---|
JDK动态代理技术 | 目标类有接口,是基于接口动态生成实现类的代理对象 | 目标类有接口的情况下,默认方式 |
Cglib动态代理技术 | 目标类无接口且不能使用final修饰,是基于被代理对象动态生成子对象为代理对象 | 目标类无接口时,默认使用该方式;目标类有接口时,手动配置<aop:config proxy-target-class=“true”>强制使用Cglib方式 |
总结:
基于JDK的动态代理:
- 只提供类的代理,无法提供类的代理
- 返回的代理类就是目标的类的实现类,对目标类的原有实现进行增强
基于CGLIB的动态代理:
- 如果代理类没有实现接口,Spring会选择该类创建代理对象
- 创建的代理类是目标对象的子类
5.3 基于注解配置的AOP
5.3.1 注解方式AOP的基本使用
@Component
@Aspect
public class MyAdvice {
//<aop:before method="beforeAdvice" pointcut="execution(void com.itheima.service.impl.*.*(..))"/>
@Before("execution(void com.itheima.service.impl.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("当前目标对象是:"+joinPoint.getTarget());
System.out.println("当前参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("当前Signature:"+joinPoint.getSignature());
System.out.println("当前getThis为:"+joinPoint.getThis());
System.out.println("前置的增强...");
}
public void afterAdvice(){
System.out.println("最终的增强...");
}
public void afterReturningAdvice(){
System.out.println("后置的增强...");
}
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//环绕目标方法
System.out.println("环绕前的增强...");
Object res = joinPoint.proceed();//执行目标方法
System.out.println("环绕后的增强");
return res;
}
public void afterThrowingAdvice(Throwable e){
System.out.println("当前的异常信息为:"+e.getMessage());
System.out.println("异常抛出的增强...报异常才执行");
}
}
但是要在配置文件中配置,开启AOP的自动代理功能,能够识别@Before、@After等注解
<!--使用注解配置AOP,需要开启AOP自动代理-->
<aop:aspectj-autoproxy/>
5.3.2 注解方式AOP配置详情
可以将其切点表达式抽取出来
@Pointcut("execution(void com.itheima.service.impl.*.*(..))")
public void MyPointcut(){}
在通知类型上以这种方式引用即可
@Around("MyAdvice.MyPointcut()")
具体代码详情如下:
@Component
@Aspect
public class MyAdvice {
//切点表达式的抽取
@Pointcut("execution(void com.itheima.service.impl.*.*(..))")
public void MyPointcut(){}
//<aop:before method="beforeAdvice" pointcut="execution(void com.itheima.service.impl.*.*(..))"/>
// @Before("execution(void com.itheima.service.impl.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("当前目标对象是:"+joinPoint.getTarget());
System.out.println("当前参数为:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("当前Signature:"+joinPoint.getSignature());
System.out.println("当前getThis为:"+joinPoint.getThis());
System.out.println("前置的增强...");
}
// @After("execution(void com.itheima.service.impl.*.*(..))")
public void afterAdvice(){
System.out.println("最终的增强...");
}
// @AfterReturning("execution(void com.itheima.service.impl.*.*(..))")
public void afterReturningAdvice(){
System.out.println("后置的增强...");
}
@Around("MyAdvice.MyPointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//环绕目标方法
System.out.println("环绕前的增强...");
Object res = joinPoint.proceed();//执行目标方法
System.out.println("环绕后的增强");
return res;
}
// @AfterThrowing(value = "execution(void com.itheima.service.impl.*.*(..))",throwing = "e")
public void afterThrowingAdvice(Throwable e){
System.out.println("当前的异常信息为:"+e.getMessage());
System.out.println("异常抛出的增强...报异常才执行");
}
}
5.3.3. 注解方式AOP原理剖析
之前在使用xml配置AOP时,是借助的Spring的外部命名空间的加载方式完成的,使用注解配置后,就抛弃了< aop:config>标签,而该标登最终加载了名为AspectJAwareAdvisorAutoProxyCreator的BeanPostProcessor,最终,在该BeanPostProcessor中完成了代理对象的生成。
<!--使用注解配置AOP,需要开启AOP自动代理-->
<aop:aspectj-autoproxy/>
同样,从aspectj-autoproxy标签的解析器入手
this.registerBeanDefinitionParser ( "aspectj-autoproxy", newAspectJAutoProxyBeanDefinitionParser() ) ;
而AspectJAutoProxyBeanDefinitionParser代码内部,最终也是执行了和xml方式AOP一样的代码
registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class,registry,source)
如果使用的是核心配置类的话
@Configuration
@ComponentScan({"com.itheima"})
@EnableAspectJAutoProxy
public class SpringConfig {
}
查看@EnableAspectJAutoProxy源码,使用的也是@lmport导入相关解析类
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
5.4 基于AOP的声明式事务控制
5.4.1 Spring事务编程概述
事务是开发中必不可少的东西,使用JDBC开发时,我们使用connection对事务进行控制,使用MyBatis时,使用SqlSession对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化,Spring就将这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制
和声明式事务控制
事务控制方式 | 解释 |
---|---|
编程式事务控制 | Spring提供了事务控制的类和方法,使用编码的方式对业务代码进行事务控制,事务控制代码和业务操作代码耦合到了一起,开发中不使用 |
声明式事务控制 | Spring将事务控制的代码封装,对外提供了Xml和注解配置方式,通过配置的方式完成事务的控制,可以达到事务控制与业务操作代码解耦合,开发中推荐使用 |
Spring事务编程相关的类有如下三个
事务控制相关类 | 解释 |
---|---|
平台事务管理器PlatformTransactionManager | 是一个接口标准,实现类都具备事务提交、回滚和获得事务对象的功能,不同持久层框架可能会有不同实现方案 |
事务定义 TransactionDefinition | 封装事务的隔离级别、传播行为、过期时间等属性信息 |
事务状态TransactionStatus | 存储当前事务的状态信息,如果事务是否提交、是否回滚、是否有回滚点等 |
虽然编程式事务控制我们不学习,但是编程式事务控制对应的这些类我们需要了解一下,因为我们在通过配置的方式进行声明式事务控制时也会看到这些类的影子
5.4.2 基于xml声明式事务控制
isolation属性:指定事务的隔离级别,事务并发存在三大问题:脏读、不可重复读、幻读/虚读。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READ_COMMITTED和REPEATABLE_READ
isolation属性 | 解释 |
---|---|
DEFAULT | 默认隔离级别,取决于当前数据库隔离级别,例如MySQL默认隔离级别是 |
READ_UNCOMMITTED | A事务可以读取到B事务尚未提交的事务记录,不能解决任何并发问题,安全性最低,性能最高 |
READ_COMMITTED | A事务只能读取到其他事务已经提交的记录,不能读取到未提交的记录。可以解决脏读问题,但是不能解决不可重复读和幻读 |
REPEATABLE_READ | A事务多次从数据库读取某条记录结果一致,可以解决不可重复读,不可以解决幻读 |
SERIALIZABLE | 串行化,可以解决任何并发问题,安全性最高,但是性能最低 |
<tx:method name="*" isolation="READ_COMMITTED"/>
read-only属性:设置当前的只读状态,如果是查询则设置为true,可以提高查询性能,如果是更新(增删改)操作则设置为false
<!--一般查询相关的业务操作都会设置为只读模式-->
<tx :method name="select*" read-only="true"/>
<tx : method name="find*" read-only="true"/>
timeout属性:设置事务执行的超时时间,单位是秒,如果超过该时间限制但事务还没有完成,则自动回滚事务,不在继续执行。默认值是-1,即没有超时时间限制
<!--设置查询操作的超时时间是3秒-->
<tx:method name="select*" read-only="true" timeout="3"/>
propagation属性
:设置事务的传播行为
,主要解决是A方法调用B方法时,事务的传播方式问题的,例如:使用单方的事务,还是A和B都使用自己的事务等。事务的传播行为有如下七种属性值可配置
事务传播行为 | 解释 |
---|---|
REQUIRED(默认值) | A调用B,B需要事务,如果A有事务B就加入A的事务中,如果A没有事务,B就自己创建一个事务 |
REQUIRED_NEW | A调用B,B需要新事务,如果A有事务就挂起,B自己创建一个新的事务 |
SUPPORTS | A调用B,B有无事务无所谓,A有事务就加入到A事务中,A无事务B就以非事务方式执行 |
NOT_SUPPORTS | A调用B,B以无事务方式执行,A如有事务则挂起 |
NEVER | A调用B,B以无事务方式执行,A如有事务则抛出异常 |
MANDATORY | A调用B,B要加入A的事务中,如果A无事务就抛出异常 |
NESTED | A调用B, B创建一个新事务,A有事务就作为嵌套事务存在,A没事务就以创建的新事务执行 |
5.4.3 基于注解声明式事务控制
开启注解驱动的配置文件替换为注解@EnableTransactionManagement
<!--事务的自动代理(注解驱动)-->
<tx:annotation-driven/>
然后将对应的实例创建注入容器即可
对应配置类代码如下:
@Configuration
@ComponentScan({"com.itheima"})
@PropertySource("classpath:jdbc.properties")
@MapperScan({"com.itheima.mapper"})
@EnableTransactionManagement
public class SpringConfig {
@Bean
public DataSource dataSource(
@Value("${jdbc.driver}") String driver,
@Value("${jdbc.url}") String url,
@Value("${jdbc.username}") String username,
@Value("${jdbc.password}") String password
) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
其中,name属性名称指定哪个方法要进行哪些事务的属性配置,此处需要区分的是切点表达式指定的方法与此处指定的方法的区别?切点表达式,是过滤哪些方法可以进行事务增强;事务属性信息的name,是指定哪个方法要进行哪些事务属性的配置