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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 这个才是我们想要看的结果 ,我们可以简单分析一下 , 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比较重要的几个过程 , 可以稍微了解一下 , 方便对后面的源码的阅读

相关文章
|
14天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
55 13
|
29天前
|
XML 安全 Java
|
7天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
3天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
24 13
|
14天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
63 14
|
9天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
51 1
|
15天前
|
IDE Java 测试技术
互联网应用主流框架整合之Spring Boot开发
通过本文的介绍,我们详细探讨了Spring Boot开发的核心概念和实践方法,包括项目结构、数据访问层、服务层、控制层、配置管理、单元测试以及部署与运行。Spring Boot通过简化配置和强大的生态系统,使得互联网应用的开发更加高效和可靠。希望本文能够帮助开发者快速掌握Spring Boot,并在实际项目中灵活应用。
30 5
|
25天前
|
缓存 Java 数据库连接
Spring框架中的事件机制:深入理解与实践
Spring框架是一个广泛使用的Java企业级应用框架,提供了依赖注入、面向切面编程(AOP)、事务管理、Web应用程序开发等一系列功能。在Spring框架中,事件机制是一种重要的通信方式,它允许不同组件之间进行松耦合的通信,提高了应用程序的可维护性和可扩展性。本文将深入探讨Spring框架中的事件机制,包括不同类型的事件、底层原理、应用实践以及优缺点。
54 8
|
3月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
248 2
|
7天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)

推荐镜像

更多