Spring框架(一) 底层核心原理解析

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 这个才是我们想要看的结果 ,我们可以简单分析一下 , userServiceBase的test1()方法也是有事务存在的 , 同时userServiceBase也是一个Bean , 它最终也会产生一个代理对象去当做一个Bean , 碎玉UserService而言 , 我要给userServiceBase这个属性去赋值 , 那么他肯定要从Spring容器中找到一个userServiceBase的一个Bean来赋值 , 所以他找到的就是Spring事务所产生的userServiceBase的代理对象 , 所以这个注解就是有用的

说明

本系列文章以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)的时候, 他就会大概进行一些操作


解析AppConfig , 然后得到扫描路径

遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String, Class>。(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap,后续文章会有说明)

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对象


如图所示

f43881ac4ba0954575ec28bd9a75c820_d5c97eef21a146be96a9d8142f9b3280.png


上述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();这行代码打个断点

d5946ff7f7c954a8585a066d26e3f4c6_802f700be88f4d00823f9aeee0b3915c.png

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


然后我们在UserService这个类中的test()方法中的 System.out.println(“UserService test 方法执行”); 打个断点 , 然后继续往下走 , 你会发现 , 一旦进入到test方法 , 它orderService属性就有值了

23b8bbe8fa5a4a86153d4935fd14064b_5e18731253bb49cfb7016e543e0b6315.png

也就是说@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也就是有值的

3331d447a026703e5b427a2eee6c3045_dd10ee9086fe417fbc38d4f299fc9079.png


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();
  }
}
@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比较重要的几个过程 , 可以稍微了解一下 , 方便对后面的源码的阅读

相关文章
|
4天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
20天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
16天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
34 2
|
15天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
66 1
|
17天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
31 1
|
4天前
|
存储 供应链 物联网
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
|
4天前
|
存储 供应链 安全
深度解析区块链技术的核心原理与应用前景
深度解析区块链技术的核心原理与应用前景
12 0
|
11天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
12 0
|
19天前
|
供应链 安全 分布式数据库
探索区块链技术:从原理到应用的全面解析
【10月更文挑战第22天】 本文旨在深入浅出地探讨区块链技术,一种近年来引起广泛关注的分布式账本技术。我们将从区块链的基本概念入手,逐步深入到其工作原理、关键技术特点以及在金融、供应链管理等多个领域的实际应用案例。通过这篇文章,读者不仅能够理解区块链技术的核心价值和潜力,还能获得关于如何评估和选择适合自己需求的区块链解决方案的实用建议。
37 0
|
Java Spring
Spring原理学习系列之五:IOC原理之Bean加载
其实很多同学都想通过阅读框架的源码以汲取框架设计思想以及编程营养,Spring框架其实就是个很好的框架源码学习对象。我们都知道Bean是Spring框架的最小操作单元,Spring框架通过对于Bean的统一管理实现其IOC以及AOP等核心的框架功能,那么Spring框架是如何把Bean加载到环境中来进行管理的呢?本文将围绕这个话题进行详细的阐述,并配合Spring框架的源码解析。
Spring原理学习系列之五:IOC原理之Bean加载

推荐镜像

更多