感兴趣的话大家可以关注一下公众号 : 猿人刘先生 , 欢迎大家一起学习 , 一起进步 , 一起来交流吧!

说明

本系列文章以spring-framework-5.3.10为例 , 本篇文章的目的就是使各位读者能在使用Spring的基础上对Spring的一些比较核心的内容有一个大概的认识,并不是特别全面,会在后续的文章中一一讲解,不仅仅是停留在Spring简单的使用 , 而是方便后面源码的阅读以及实现方式的理解 , 文章仅是作者自己在学习Spring过程中的案例演示以及知识总结 , 如果表达不当 , 还请及时指教

Spring的IOC(控制反转)和DI(依赖注入)

首先我们来看一段代码

	public static void main(String[] args) {

		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		
		ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("Spring.xml");
		
		UserService userService = (UserService) applicationContext.getBean("userService");
		userService.test();
		
		System.out.println("userService..."  + userService);
		
	}

APPConfig代码

@ComponentScan("com.lyh")
public class AppConfig{
}

从代码实现方式不难看出 , ClassPathXmlApplicationContext是用来加载xml配置文件的Context,而AnnotationConfigApplicationContext是用来加载注解配置的Context。虽然直接父类不同,但是都有一个共同的祖先类AbstractApplicationContext。以及同样有一个对象BeanFactory提供容器作用。
但是用ClassPathXmlApplicationContext其实已经过时了,在新版的Spring MVC和Spring Boot的底层主要用的都是AnnotationConfigApplicationContext

Spring容器创建完之后 , 接下来就是从Spring容器中getBean() , 那么这个bean一定是Spring容器帮我们创建的 , 这也就是Spring容器的一个核心: IOC(控制反转)

IOC(控制反转) : 将创建对象及生命周期的管理交给Spring容器 , 在开发中 , 我们不需要关注对象的创建以及生命周期的管理 , 而是由Spring来提供 , 这个由Spring来管理创建对象以及生命周期的机制就称为控制反转 , 也就是IOC

那么我们getBean()得到的对象和我们直接new UserService()得到的对象有什么不同呢?我们用代码举个例子

@Component
public class OrderService {

	@Autowired
	private UserService userService;
	
}

@Component
public class UserService {

}

现在我们有两个类 , 一个是OrderServer , 一个是UserServer , 通过@Autowired将UserServer 注入到OrderServer 中, 那么通过getBean()拿到OrderServer 这个对象 , 它的UserServer属性一定是有值的 , 而通过new OrderServer()的方式拿到的对象 , UserServer的值一定是空的 , 所以区别也就显而易见了

通过getBean()拿到的对象 , 它的属性是是有值的 , 而直接new出来的对象, 它的属性就是空的

而给属性赋值的过程就可以称为DI(依赖注入)

Spring是如何创建这个对象的

其实通过上面的代码示例可以稍微进行推测 , 其实不管是AnnotationConfigApplicationContext还是ClassPathXmlApplicationContext,目前,我们都可以简单的将它们理解为就是用来创建Java对象的容器,比如调用getBean()就可能去创建对象 , 但是也有可能不创建 , 在Java语言中,肯定是根据某个类来创建一个对象的

当我们调用context.getBean(“userService”)时,就会去创建一个对象,但是getBean方法内部怎么知道"userService"对应的是UserService类呢?

我们可以分析出来当new AnnotationConfigApplicationContext(AppConfig.class)的时候, 他就会大概进行一些操作

  1. 解析AppConfig , 然后得到扫描路径
  2. 遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String, Class>。(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap,后续文章会有说明)
  3. Spring会根据某个规则生成当前类对应的beanName,作为key存入Map,当前类作为value

然后我们getBean()的时候就可以根据"userService"找到UserService类,从而就可以去创建对象了

Bean的创建过程

通过以上示例代码我们大概可以看出一个简单的过程就是这样

1.找到需要创建的类(扫描)
2.通过默认的无参构造方法得到一个对象(但是如何一个类中有多个构造方法,Spring则会进行选择,这个叫做推断构造方法)
3. 给加了@Autowired等注解的属性进行赋值(依赖注入)
4. …
5. 然后得到一个bean

一个比较完整的流程大致如下

1.找到需要创建的类(扫描)
2.通过默认的无参构造方法得到一个对象(但是如何一个类中有多个构造方法,Spring则会进行选择,这个叫做推断构造方法)
3.给加了@Autowired等注解的属性进行赋值(依赖注入)
4.依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调

5.Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring会调用当前对象的此方法(初始化前

6.紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化

7.最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后

那么Bean对象创建出来之后

1.如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就是单例池)
2.如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次getBean时会再次执行上述创建过程,得到一个新的Bean对象

如图所示
在这里插入图片描述

上述Bean生命周期并不是完整的 , 它中间还有好多 , 后面的文章再慢慢详解, 接下来把上述步骤做一个说明

推断构造方法

什么是推断构造方法?

Spring需要根据某一个类的构造方法得到一个普通对象 , 它首先得判断它使用哪个构造方法以及需要什么参数 , 下面用一些例子来进行演示

例1:如果有一个无参构造和一个有参构造 , 那么Spring会使用哪个构造方法?

代码如下:

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	public UserService() {
		System.out.println("无参构造");
	}

	public UserService(OrderService orderService) {
		this.orderService = orderService;
		System.out.println("有参构造");
	}

}

运行之后可以发现 , Spring使用的是无参构造方法

例2:如果有两个有参构造呢?

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	public UserService(OrderService orderService) {
		this.orderService = orderService;
		System.out.println("有参构造1");
	}

	public UserService(OrderService orderService , OrderService orderService1) {
		this.orderService = orderService;
		System.out.println("有参构造2");
	}

}

直接就是运行报错 , 报错信息如下 , 那么为什么会运行报错呢?不妨把有参构造注释掉一个

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.lyh.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.lyh.service.UserService.()

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	// public UserService(OrderService orderService) {
	//	this.orderService = orderService;
	//	System.out.println("有参构造1");
	// }

	public UserService(OrderService orderService , OrderService orderService1) {
		this.orderService = orderService;
		System.out.println("有参构造2");
	}

}

运行之后发现 , 没有报错 , 首先证明了一点, 这样写是没有问题的 , 那么想一下 , Spring要通过构造方法去创建对象 , 但是现在有两个 , 那么到底用哪一个呢?Spring是不知道的 , 所以就抛了异常

接下来我们再分析报错信息 : No default constructor found , 没有找到一个默认的构造方法 , 现在我们是因为写了多个有参的构造从而报的错 , 那么为什么会报一个没有默认的构造方法的错呢?
其实Spring在有多个有参构造时会去找无参的构造方法 ,因为无参的构造也有一种默认的意义, 而我们又没有默认的构造方法 , 所以就抛了这个异常

如果非要有两个有参构造 , 也不是不行 , 既然它不知道用哪一个 , 那么就加一个@Autowired告诉它用哪一个 , 例如这样:

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	public UserService(OrderService orderService) {
		this.orderService = orderService;
		System.out.println("有参构造1");
	}
	
	@Autowired
	public UserService(OrderService orderService , OrderService orderService1) {
		this.orderService = orderService;
		System.out.println("有参构造2");
	}

}

例3:只有一个有参构造方法的情况下 ,这个方法的入参会不会有值?

首先说明 , 如果只有一个有参构造 , 那么就会覆盖整个默认无参的构造方法 , 如果还需要有无参的 , 就需要自己把它定义出来

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	public UserService(OrderService orderService) {
	    System.out.println("orderService : " + orderService);
		this.orderService = orderService;
		System.out.println("有参构造1");
	}

}

运行之后发现是有值的 , 那么这个值会从哪里来呢?

首先在创建UserService 这个类的时候 , 会使用这个唯一的有参构造 ,Spring就会找一个OrderService的Bean来赋值 , 前提是OrderService必须是一个Bean

那么根据什么找呢?

通常我们说一个Bean , 这个Bean肯定有一个类型的 , 还有一个名称 ,那无非就是入参的类型:OrderService, 以及参数名称: orderService

可能有人会想到 , 在Bean创建完成之后 , 会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象 , 也就是单例池 , 那么把参数名称orderService当作key , 来这个map获取 , 这种方式可行吗?

可行是可行 , 但是有一个问题,类型是不对应的 , 很有可能注入的时候是这样注入的:
@Autowired
private OrderService memberService;
那么这样直接通过名称过去出来的类型就是不对应的 , 由此可见 , 这个名称其实不是那么的重要 ,重要的是类型

所以最保险的方式就是根据类型去找 , 但是通过类型会找到多个 , 比如代码是这样写的情况:

@ComponentScan("com.lyh")
public class AppConfig{

	@Bean
	public OrderService orderService1(){
		return new OrderService();
	}

	@Bean
	public OrderService orderService2(){
		return new OrderService();
	}

}

可以思考一个问题 ,现在Spring容器中有几个OrderService 类型的Bean?

AppConfig中定义了两个 , 还有我们通过@Component来声明的一个 , 那么就是三个

我们可以运行代码看看结果

	public static void main(String[] args) {

		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

		Object orderService = applicationContext.getBean("orderService");
		System.out.println("orderService : " + orderService);

		Object orderService1 = applicationContext.getBean("orderService1");
		System.out.println("orderService1 : " + orderService1);

		Object orderService2 = applicationContext.getBean("orderService2");
		System.out.println("orderService2 : " + orderService2);

	}

运行结果:

orderService : com.lyh.service.OrderService@49b0b76 orderService1 :
com.lyh.service.OrderService@769f71a9 orderService2 :
com.lyh.service.OrderService@4c9f8c13

很显然 , 都是有值的 , 那么通过类型找到三个 , 不可能把这三个都传进来 , 需要确定一个 , 怎么去确定其中一个呢?

我们可以通过有参构造入参的参数名称orderService 去找 , 这样是不是就可以找到一个 , 然后赋值 , 这个名字是不会重名的,如果有重名,可能会直接覆盖 , 因为他是存在map中的 , 而map的key是不允许重复的

例4:只有一个有参构造方法的情况下 ,参数名称改变 , 会抛出异常?还是正常执行

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	public UserService(OrderService orderService3) {
		this.orderService = orderService3;
		System.out.println("有参构造1");
	}

}

运行之后肯定是报错的 , 报错信息如下:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.lyh.service.OrderService’ available: expected single matching bean but found 3: orderService,orderService1,orderService2

大概意思就是找到了三个Bean : orderService,orderService1,orderService2 , 但是没有符合条件的bean,这个条件就是参数名称是orderService3的

例5:只有一个有参同时只定义一个OrderService类型的bean, 然后参数名称改变

对上面的代码进行简单改造 , 使OrderService类型的bean只有一个 , 注掉AppConfig类的相关代码

@ComponentScan("com.lyh")
public class AppConfig{

	//@Bean
	//public OrderService orderService1(){
	//	return new OrderService();
	//}

	//@Bean
	//public OrderService orderService2(){
	//	return new OrderService();
	//}

}
	

UserService

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	public UserService(OrderService orderService3) {
		this.orderService = orderService3;
		System.out.println("有参构造 : " + orderService3);
	}

}

OrderService

//@Component
public class OrderService {

	public OrderService() {
	}

}

运行之后,程序正常

有参构造 : com.zhouyu.service.OrderService@d83da2e

因为它通过类型就从Spring容器中找到了一个 , 所以直接赋值

例6:那么取消定义OrderService这个Bean呢

//@Component
public class OrderService {
	
	public OrderService() {
	
	}
	
}

运行之后发现报错了 , 报错类型如下

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.lyh.service.OrderService’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

大概就是说预计有一个OrderService的bean , 但是没有找到这个bean , 因为他想从Spring容器中找一个OrderService类型的bean来注入 ,但是现在没有这个bean , 所以就报了这个错误

构造方法推断完成之后就得到了一个普通的对象 , 那么Spring拿到整个对象之后就可以为属性去赋值了 , 那么也就是接下来要说的DI(依赖注入)

DI(依赖注入)

例1: Spring给属性赋值 , 赋的是什么值呢?去哪里找这个值呢?

AppConfig

@ComponentScan("com.lyh")
public class AppConfig{
}

UserService

@Component
public class UserService {

	@Autowired
	private OrderService orderService;// 赋的是什么值呢?

	public UserService(OrderService orderService) {
		this.orderService = orderService;
		System.out.println("有参构造 : " + orderService);
	}

}

OrderService

@Component
public class OrderService {

	public OrderService() {
	}

}

先根据类型 , 再根据名称 , 也就是先byType , 再byName , 结合上面的推断构造方法是不是就对依赖注入也通畅了一点

初始化前

初始化前也就是执行@PostConstruct注解的方法 , 通过反射去判断那些方法加了@PostConstruct , 然后执行

初始化

初始化也就是执行实现了InitializingBean接口的afterPropertiesSet()方法

初始化后

初始化后也就是我们的AOP ,那么我们还是通过例子来说明

例1:使某个类成为代理对象 , 那么代理对象中的属性会不会有值

@Component
public class UserService {

	@Autowired
	private OrderService orderService;
	
	public void test(){
		System.out.println("UserService test 方法执行");
	}

}

定义切面类

@Aspect
@Component
public class LyhAspect {

	@Before("execution(public void com.lyh.service.UserService.test())")
	public void lyhBefore(JoinPoint joinPoint) {
		System.out.println("zhouyuBefore");
	}

}

开启代理

@ComponentScan("com.lyh")
@EnableAspectJAutoProxy
public class AppConfig{

}

测试

public class TestSpring {

	public static void main(String[] args) {

		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = (UserService) applicationContext.getBean("userService");
		userService.test();

	}
}

我们可以现在userService.test();这行代码打个断点
在这里插入图片描述
可以发现 ,他确实是一个代理对象 , 同时也可以看到orderService = null , 表示它确实没有值 , 为什么呢?难道开了AOP之后依赖注入不生效了吗?其实不是这样的 , 通过阅读上面的文章我们发现 , 再bean初始化之后并没有依赖注入这个步骤 , 也就是说Spring就没有这么设计

然后我们在UserService这个类中的test()方法中的 System.out.println(“UserService test 方法执行”); 打个断点 , 然后继续往下走 , 你会发现 , 一旦进入到test方法 , 它orderService属性就有值了
在这里插入图片描述
也就是说@Autowired注解还是生效的 , 并且在test方法中也可以使用

那么为什么代码在进入到test()方法之后orderService属性才有值?
这个时候我们首先要明白Cglib的工作原理 , 他的原理大概是这样: 动态生成被代理类的子类 , 对于上面的例子而言 , 也就是说Cglib会生成一个userService的子类 , 并且继承userService , 伪代码如下

public class UserServiceProxyCGlib extends UserService {

	@Override
	public void test() {
		// @Before切面逻辑
		super.test();
		// @after切面逻辑...等等
	}
}

那么我们在调用test()方法的时候 , 其实执行的是UserServiceProxyCGlib的test();然后再执行super.test(); , 这个时候肯定没有值 , 因为他只是一个方法的调用而已 , 但是我们又希望他有值 . 那么怎么做呢? 上面提到过 , 推断完构造方法之后会得到一个普通对象 , 然后给这个普通对象进行依赖注入 , 那么我们是不是就可以使用这个普通对象,代码原理代码实现如下所示

public class UserServiceProxyCGlib extends UserService {

	UserService target;

	@Override
	public void test() {
		// @Before切面逻辑
		target.test();
		// @after切面逻辑...等等
	}
}

这样的话 , 等你这正执行test()方法的时候 , 已经是经过依赖注入的对象 , 那么就肯定是有值的, 通过debug也可以很清楚的看到 , 因为从Srping容器获取到的就是一个代理对象 , 那么执行代理对象的test()方法 , orderService的值肯定是空的 , 但是target是有值的 , 所以通过执行target.test();orderService也就是有值的
在这里插入图片描述

Spring它怎么知道这个Bean要进行AOP呢?

首先他肯定会从容器中拿到所有的切面Bean , 然后去遍历拿出来的这些切面Bean , 然后再遍历这些切面Bean中的方法来判断它有没有@Before之类的注解 , 如果有那么就判断这个注解里面定义的表达式是否和我当前创建的Bean匹配 , 如果匹配 , 难么Spring就知道当前类是需要AOP的 , 然后Spring会把所有和当前创建类匹配的所有的方法给缓存起来 , 存到一个map中 ,等真正要执行切面逻辑的时候 , 再从缓存获取 , 然后直接执行
这个过程还是非常重要的 ,在这里先简单讲一下原理 ,等后面会用源码来验证

自此 , Bean生命周期几个比较重要的流程也就大概过了一遍 , 后面的文章还会源码的分析来一步步看到这些过程

Spring事务

例1: 开始事务 , 然后执行sql并抛异常 , 数据库会不会有数据?

@ComponentScan("com.lyh")
@EnableTransactionManagement
public class AppConfig{

	@Bean
	public JdbcTemplate jdbcTemplate() {
		return new JdbcTemplate(dataSource());
	}

	@Bean
	public PlatformTransactionManager transactionManager() {
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource());
		return transactionManager;
	}

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/world?characterEncoding=utf-8&useSSL=false");
		dataSource.setUsername("root");
		dataSource.setPassword("root");
		return dataSource;
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
		sessionFactoryBean.setDataSource(dataSource());
		return sessionFactoryBean.getObject();
	}

}

@Component
public class UserService {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Transactional
	public void test(){
		jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('2', '2')");
		throw new NullPointerException();
	}

}

然后我们来运行

public class TestSpring {

	public static void main(String[] args) {

		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = (UserService) applicationContext.getBean("userService");
		userService.test();

	}
}

通过观察数据库的数据 , 发现数据确实是写到数据库了 , 但是为什么呢?不是跑了异常吗?不应该回滚吗?
其实我们的代码都没有问题 , 但是我们少加了一个@Configuration的注解 , 我们把它加到AppConfig试一试 ,再次操作 , 发现依然报错 , 但是数据是没有写进去了 , 这是为什么呢?

首先我们需要明白的一点就是 , 当我们加了@Transactional注解之后 , Spring在创建这个类时会创建一个代理对象 , 所以我们从Spring容器拿出来的也是一个代理对象 , 现在它就不是AOP的代理对象 , 他是Spring事务所产生的代理对象 , 那么他的逻辑就是这样的

public class UserServiceProxyCGlib extends UserService {

	UserService target;
	
	@Override
	public void test() {
		// 判断有没有加@Transactional
		// 如果有 , 那么由Spring事务管理器创建一个数据库连接
		// 设置autocommit属性为false , 这个属性默认为true ,就是自动提交的意思 , 如果每执行一个sql提交一次, 那么@Transactional的意义何在呢?如果前面的sql执行完了 , 后面抛异常了 , 那我还回滚什么东西呢?
		target.test();
		// 执行完之后调用commit()方法提交
		// 如果抛异常 , 那么调用rollback()方法
	}
}

这是Spring事务他大体的一个工作原理

我们来看transactionManager()和jdbcTemplate()方法都是在调用dataSource() , 调用dataSource()方法就会产生一个DriverManagerDataSource对象 , 所以调用两次就会产生两个不同的DriverManagerDataSource对象 , 所以问题也就出来了 : 我们执行jdbcTemplate.execute()时的datasource对象是两个不同的对象 , 也就是说Spring事务管理器创建的一个数据库连接根本没有用 , 但是如果加了@Configuration ,(当然还有其他一些逻辑) 那么就能保证它的datasource就是同一个 , 但是前提是datasource为同一个 , 那么为什么会是同一个呢?这里也和代理模式是有关系 , 加了@Configuration之后AppConfig也会成为一个代理对象 , 那么代理逻辑就是当我们执行datasource()方法的时候 , 它会先去Spring容器看看有没有 , 如果有那么就直接返回 , 如果没有那就真正执行datasource()方法 , 但是如果是这样写的就有问题

@ComponentScan("com.lyh")
@EnableTransactionManagement
public class AppConfig{

	@Bean
	public JdbcTemplate jdbcTemplate() {
		return new JdbcTemplate(dataSource());
	}

	@Bean
	public PlatformTransactionManager transactionManager() {
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(**dataSource1()**);
		return transactionManager;
	}

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/world?characterEncoding=utf-8&useSSL=false");
		dataSource.setUsername("root");
		dataSource.setPassword("root");
		return dataSource;
	}

	@Bean
	public DataSource dataSource1() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/world?characterEncoding=utf-8&useSSL=false");
		dataSource.setUsername("root");
		dataSource.setPassword("root");
		return dataSource;
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
		sessionFactoryBean.setDataSource(dataSource());
		return sessionFactoryBean.getObject();
	}

}

配了两个dataSource , 那么还是会出现问题的

例2: 事务失效场景之一

改在一个UserService类代码如下 , 并且把传播度等级设置为NEVER , NEVER就表示 , 如果有一个事务的话 , 就会抛异常

@Component
public class UserService {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Transactional
	public void test(){
		jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('3', '3')");
		this.test1();
	}

	@Transactional(propagation = Propagation.NEVER)
	public void test1(){

	}

}

按理说他这个如果运行是要报错的 , 但是真正运行发现没有报错 , 那么是为什呢?
我们可以仔细理解一下 , 这个test1()方法到底是谁在调用?他的执行逻辑还是相同

public class UserServiceProxyCGlib extends UserService {

	UserService target;
	
	@Override
	public void test() {
		// 判断有没有加@Transactional
		// 如果有 , 那么由Spring事务管理器创建一个数据库连接
		// 设置autocommit属性为false , 这个属性默认为true ,就是自动提交的意思 , 如果每执行一个sql提交一次, 那么@Transactional的意义何在呢?如果前面的sql执行完了 , 后面抛异常了 , 那我还回滚什么东西呢?
		target.test();
		// 执行完之后调用commit()方法提交
		// 如果抛异常 , 那么调用rollback()方法
	}
}

但是是由target调用的test()方法 , target不就是UserService吗 , 所以test1()方法的调用者也是target , 那么target是一个执行这个test1()方法 , 这个@Transactional注解有什么用呢?

这就是我们经常会遇到的一个事务失效的场景 , 那么怎么解决呢?
首先我们需要根据业务理清楚加了@Transactional注解的方法到底是谁在调用 , 在调用的时候是不是代理对象在调这个test1()方法 , 只要是代理对象在调用 , 那么@Transactional注解就是生效的
或许有人是这样解决的
新增一个类 :

@Component
public class UserServiceBase {
	
	@Transactional(propagation = Propagation.NEVER)
	public void test1(){

	}

}

然后这样调用

@Component
public class UserService {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Autowired
	private UserServiceBase userServiceBase;

	@Transactional
	public void test(){
		jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('3', '3')");
		userServiceBase.test1();
	}

}

然后我们可以运行 , 会发现报错了

Exception in thread “main” org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’

这个才是我们想要看的结果 ,我们可以简单分析一下 , userServiceBase的test1()方法也是有事务存在的 , 同时userServiceBase也是一个Bean , 它最终也会产生一个代理对象去当做一个Bean , 碎玉UserService而言 , 我要给userServiceBase这个属性去赋值 , 那么他肯定要从Spring容器中找到一个userServiceBase的一个Bean来赋值 , 所以他找到的就是Spring事务所产生的userServiceBase的代理对象 , 所以这个注解就是有用的

其实如果明白了上面的逻辑 , 那么还有一种简单的办法 , 就是自己注入自己

@Component
public class UserService {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Autowired
	private UserService userService;

	@Transactional
	public void test(){
		jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('3', '3')");
		userService.test1();
	}

	@Transactional(propagation = Propagation.NEVER)
	public void test1(){

	}

}

最终还是要给这个userService属性去赋值 , 那么就会从Spring容器中去找 , 找到的就是一个userService的代理对象

以上就是对Spring比较重要的几个过程 , 可以稍微了解一下 , 方便对后面的源码的阅读

Logo

长江两岸老火锅,共聚山城开发者!We Want You!

更多推荐