spring核心原理解析

目录

简介

Spring入门使用

Spring如何去创建一个对象

Bean的创建过程

Spring的推断构造方法

Aware回调

AOP的大致流程

Spring的事务

spring对事务的处理逻辑

事务的传播行为:

spring事务生效和失效的简单判定


简介

Spring是现在在我们日常开发中用到的非常多的一个东西,最新的调研也表明,Spring全家桶占用了开发场景中超过50%的覆盖

spring全家桶包括很多内容,spring framework,springboot,springcloud,springdata等

本篇文章分享的主要是spring framework,Spring framework是一个很重要很基础的东西,就像springboot,也是基于spring framework做的

当然,如同标题写的,本次是大概介绍核心原理,至于底层的真正源码,本次不会涉及,后期也会慢慢自己学习整理处理分享给大家

Spring入门使用

首先是Spring的入门使用

在我们刚接触Spring的时候,那个时候还不知道Spring boot这种的,我们对Spring的第一次使用,还是用的xml的配置形式,当然,直到现在,很多程序员还是在用这种方式,很多老项目也是这种方式

我们先来写一个Spring的入门使用代码

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

这就是spring的一个入门使用,可能很多人对这个代码有着莫名的熟悉感哈哈哈哈~

虽然这是Spring的入门级使用,但是,其实这个使用方式已经渐渐开始被淘汰了,在新的Springmvc和Spring boot中,已经使用了 AnnotationConfigApplicationContext,熟悉的人应该一看名字就知道,这是一个基于注解的使用方式

我们来写一个基于这个 AnnotationConfigApplicationContext 的入门使用

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

其实大家可以看出来,AnnotationConfigApplicationContext 的使用方法和 ClassPathXmlApplicationContext 的使用方法是非常非常相似的,他们的区别就是,ClassPathXmlApplicationContext 传入的是一个xml文件,而 AnnotationConfigApplicationContext 传入的是一个bean

其实无论是哪个方式,都是指明了spring的配置,也可以指定Spring扫描的路径,可以直接去定义bean

我们来看看区别:

Spring.xml的写法

<context:component-scan base-package="com.study"/>
<bean id="userService" class="com.study.service.UserService"/>

我们再看看注解的写法

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

	@Bean
	public UserService userService(){
		return new UserService();
	}

}

所以说,其实不管是哪种方法,本质上都是一样的

从现在的角度看,我们都很少使用这两种方法,是直接使用的springmvc和springboot,但是它们其实都是基于上面的方式的,都是需要去在内部创建一个 ApplicationContext

关于Springmvc和Spring boot的区别,其实也是上面两种方式的区别,Springmvc创建的是 XmlWebApplicationContext ,而Spring boot创建的是 AnnotationConfigApplicationContext

以下是关于Spring的一些接口的简单介绍

- 接口:BeanFactory:spring容器的最顶层接口。功能相对不是那么强大。
- 接口:ApplicationContext:是BeanFactory的子接口,他继承了BeanFactory接口,功能进行了扩展。开发中应该尽量使用该接口。
- 类:FileSystemXmlApplicationContext 配置文件在磁盘或网络某个地址,这个类用来读取这样的文件
- 类:ClassPathXmlApplicationContext 读取位于classpath目录下的xml文件
- 类:AnnotationConfigApplicationContext 读取使用注解的spring配置

Spring如何去创建一个对象

不管是上面的 AnnotationConfigApplicationContext 也好,ClassPathXmlApplicationContext 也罢,我们都可以暂时将它们简单理解为是去创建java对象的

但是Spring是如何去创建一个对象的呢?

我们都知道,Java创建对象,肯定是基于一个类的,我们再来回顾一下上面的Spring入门代码

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

可以看出来,当我们调用了getBean() 方法的时候,就会创建一个userService对象,但是在getBean 的内部,它一定是做了什么,不然怎么会知道我们传入的要获取的 "userService"对应的是UserService类呢?

所以,我们就可以推断出来,在调用 AnnotationConfigApplicationContext 的构造方法的时候,会去做这么一些事情

1、解析传进去的 Application.class ,得到扫描路径

2、遍历扫描路径下的所有Java类,如果发现某个类存在@Component、@Service等注解,Spring就会记录这个类,存放在一个内部map中

3、Spring根据某种自己的规则生成当前类的bean name,把bean name当作key,当前类当作value,存入map中,

这样,当我们调用 getBean() 方法的时候,就可以根据名字去获取到对应的类了

Bean的创建过程

我们知道了是怎么获取到一个bean的,但是,Spring是怎么创建一个bean的呢?

这就是bean创建的生命周期

bean创建的生命周期大概是这样的:

1、利用要创建的类的构造方法实例化得到一个对象(注意,此处这个类的构造方法可能有多个,Spring会采用叫做 ‘推断构造方法’ 的规则)

2、得到一个对象后,Spring会判断这个对象中是否存在被 @Autowired 注解修饰的属性,如果存在,Spring会把这些属性找出来,并进行赋值,这个找出来赋值的操作,其实就是依赖注入

3、依赖注入后,Spring会判断这个对象是不是实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数,这就是所谓的Aware回调

4、Aware回调之后,Spring会判断该对象中,是否存在某个方法被@PostConstruct注解修饰了,如果存在,Spring会调用当前对象的此方法,这个过程是在初始化前

5、然后,Spring会判断当前对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法,这个就是初始化

6、最后,Spring会判断这个对象需不需要进行AOP,如果不需要,那么这个bean就创建完成了,如果需要进行AOP处理,那就会进行动态代理生成一个代理对象作为bean,注意⚠️,经过AOP的,不再是纯净的对象,而是一个被代理过的对象

类→构造方法→对象→依赖注入→→→bean

在上面第二步的时候,我们说到了依赖注入,那这个依赖注入的属性该怎么做呢?

首先是肯定需要找到这个类哪些属性使用了 @Autowired 注解,我们可以获取class文件,获取所有属性,判断是否有这个注解

Field[] fields = orderService.getClass().getFields();
for (Field field : fields) {
  boolean hasAnnotation = field.isAnnotationPresent(Autowired.class);
}

如果有这个注解,我们就可以给这个类的这个字段赋值,无非就是给这个属性赋值什么,这个我们可以之后再找

Field[] fields = orderService.getClass().getFields();
for (Field field : fields) {
    boolean hasAnnotation = field.isAnnotationPresent(Autowired.class);
    if (hasAnnotation) {
        field.set(orderService, ?);
    }
}

Spring的推断构造方法

Spring在基于某一个类生成bean的过程中,需要使用该类的构造方法来进行实例化,但是如果一个类存在多个构造方法,Spring会使用哪个呢?

先说答案:

1、如果一个类只存在一个构造方法,不管这个构造方法是有参还是无参,都会用这个构造方法,因为没得选

2、如果一个类存在多个构造方法,Spring会找有没有默认的无参构造方法,如果有无参构造方法,就选择无参构造方法,毕竟无参构造方法本身代表了一种默认的含义

3、如果一个类存在多个构造方法,并且没有无参的构造方法,Spring就会报错

4、如果一个类存在多个构造方法,也没有无参的构造方法,但是,有一个构造方法使用了@Autowired注解,那么Spring就会使用这个构造方法

有一个地方需要注意,如果Spring选择了一个有参的构造方法,Spring在调用这个有参的构造方法的时候,需要传入参数,so,这个参数从哪儿来呢?

Spring会根据入参的类型和入参的名字,从Spring容器中找bean对象,如果根据类型找到了多个,那就再根据入参的名字确定唯一的一个,如果最后没有办法确定,那就会报错,无法创建当前bean对象

Spring这个确定用哪个构造方法,确定入参的bean对象,这种行为过程,就叫做推断构造方法

在java中,无参的构造方法就是认为是默认的

如果提供一个有参的构造方法,传入参数,这个参数是有值的,这个值会从spring容器里去找,也就是说,这个参数必须是一个bean,不是bean的话,不在spring容器里,就无法找到,就不会有值,会抛异常,因为初始化要求你必须有一个值

spring的bean的默认命名是类名首字母小写

如果在spring容器中,先根据名字去找,找到的bean类型,可能并不是正确的类型,如果根据类型去找,找到一个,那就直接用,但是可能会找到多个,找到多个的话,那就根据名字再找,如果找不到,那就报错,名字相同的bean,会发生bean覆盖

同名bean,@Bean注解会覆盖@Compont注解

Aware回调

假设这么一个需求,我想要在注入userService这个bean的时候,它里面有一个goods属性,我希望这个goods属性,我取出来的时候就是我数据库里存的指定的值,这个该怎么办呢?注意,是指定值

@Component("userService")
public class UserService {

    private Goods goods;

}

怎么办呢,可不可以这样,在生成这个 userService 这个bean之前,我们进行一次赋值如何?说干就干

@Component("userService")
public class UserService {

    private Goods goods;
    
    private void updateGoods( ) {
        // todo ......
        // todo ......
        // todo ......
        // todo ......
        this.goods = ;
    }

}

我们提供一个方法来赋值,如果spring在成为bean前调用了这个方法,那不就OK了么

所以我们就用到了 @PostConstruct 注解,加了这个注解,spring 就知道它要调用这个方法,要注意,这个方法会影响这个bean的生成速度及结果,之前我也出过这个错,具体的错误请看我这篇总结:

开发错误总结---@PostConstruct注解导致的程序无法启动(@PostConstruct的执行)_希望每天都能get到新知识的小严惜的博客-CSDN博客

那,spring怎么知道哪些方法需要执行呢,说白了,就是spring需要知道哪些方法加了注解,跟上面给注入的属性赋值一样,我们写一个伪代码

for (Method method : userService.getClass().getMethods()) {
    boolean hasAnnotation = method.isAnnotationPresent(PostConstruct.class);
}

如果判断出来有注解,就可以进行赋值

for (Method method : userService.getClass().getMethods()) {
    boolean hasAnnotation = method.isAnnotationPresent(PostConstruct.class);
    if (hasAnnotation) {
        method.invoke(userService, ?);
    }
}

当然,除了 @Postconstruct 之外,还可以实现 InitializingBean,重写afterPropertiesSet 方法

@Component("userService")
public class UserService implements InitializingBean {

    private Goods goods;

    @PostConstruct
    private void updateGoods( ) {
        // todo ......
        // todo ......
        // todo ......
        // todo ......
        this.goods = null;
    }

    @Override
    public void afterPropertiesSet() {
        
    }
}

当然,相应的spring也需要判断是否实现了 InitializingBean

boolean b = userService instanceof InitializingBean;

在spring的底层,spring判断,如果实现了InitializingBean 接口,那就强转为 InitializingBean,调用 .afterPropertiesSet()

AOP的大致流程

先说一点,AOP是在初始化后做的,但是是在生成bean之前,进行AOP后,我们就会真的得到一个bean,而得到的bean,是一个被代理的对象,不再是纯净的对象

这儿有一个地方需要注意,在进行AOP代理时,产生的代理的bean,里面注入的属性是null,我们来写一下代码试试

package own.study.domain;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import own.study.domain.oneex.Application;
import own.study.domain.oneex.spring.UserService;
import own.study.vo.User;


public class TestMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Application.class);
        UserService userService = (UserService) annotationConfigApplicationContext.getBean("myservice");
        userService.printMessage();
    }
}
package own.study.domain.oneex.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("myservice")
public class UserService {

    @Autowired
    private GoodsService goodsService;

    public void printMessage () {
        System.out.println("this is userservice's method output");
    }

}
package own.study.domain.oneex.spring;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Aspect
@Component
@EnableAspectJAutoProxy
public class SpringAop {

    @Before(value = "execution (* own.study.domain.oneex.spring.UserService.*(..))")
    public void doAop( ) {
        System.out.println("before aop");
    }

}
package own.study.domain.oneex.spring;

import org.springframework.stereotype.Component;

@Component
public class GoodsService {
}

我们可以看到,userService已经被代理了,而且内部的goodsService是null,原因是因为,AOP的作用是在初始化后,初始化后并没有进行一个赋值,并没有进行一个依赖注入

虽然AOP后的代理对象注入的属性没有值,但是在执行方法时,注入的属性就有值了

原因是因为,CJLIB会先生成一个代理类,就可以重写被代理的类的方法,当我们执行方法,其实执行的是代理类的方法,然后会调用父类,也就是被代理类的方法,当然,会先执行切面方法(CJLIB是基于类的,基于继承关系,而JDK的动态代理,是基于接口的)

可能你会觉得,代理的方法其实就是super.方法(),如果真的是这样,是调用到了父类的方法,但是别忘了, 这中间只是去执行了切面方法,并没有做任何事,父类方法就算被调用了,也影响不到注入的属性,注入的属性依旧没有值

要解决这个问题,其实也有办法,在AOP的代理类最外层声明一个被代理类的对象,假设属性名叫tag,在生成AOP的代理类对象之前,先生成这个被代理类bean放入spring容器中,然后给AOP生成的代理类对象的tag赋值已经生成好的被代理类的对象bean,执行这个tag的方法,不就ok了吗?

要知道这个tag,是经过依赖注入的,是一个纯净的普通对象,这个普通对象可是进行过初始化的,是创建出来的,注入的属性是有值的

 有可能有人有一个问题,为什么不给代理对象也加一个依赖注入的流程呢?但其实并没有什么意义,因为且面是为了切一个方法,最重要的意义是,执行某个特定的方法的时候,会执行切面方法,你额外加了一步依赖注入,没什么必要了就,就算你真的要用,因为是切面方法,直接从入参去getTarget不也能拿到么

那么,在初始化bean的时候,怎么判断这个bean需不需要进行AOP?初始化一个bean的时候,spring怎么知道这个类被切了呢,这个原因也很简单,切面类就是切面bean,在spring里面,先找出所有的切面bean,遍历所有的切面bean,拿到每一个切面bean里面的方法,判断是否有@Before啊啥的注解,如果有,判断和正在创建的bean类型是否匹配,如果匹配,那就要进行AOP,并将切面方法缓存起来,当需要执行的时候,直接从缓存里将全部的符合的方法拿出来执行,缓存map的结构,可以理解为,key是正在创建的bean的相关信息,value是对应的方法集合

到了这里,Bean的创建的生命周期已经是这样:

类→构造方法(推断构造方法)→普通对象→依赖注入(属性赋值)→初始化前(@PostConstruct)→初始化(InitializingBean)→初始化后(AOP)→代理对象→bean

Spring的事务

spring对事务的处理逻辑

1、判断是否存在 @Transactional 注解

2、由事务管理器创建一个数据库连接(事务内包含的操作,一定要是同一个DataSource)

3、将自动提交改为false

4、执行开发者的业务逻辑

5、如果执行完没有异常,那就提交,有异常,那就回滚

事务的传播行为:

    存在事务,那就加入,不存在,那就创建一个新事务(默认)
    存在事务,那就加入,不存在,那就用非事务方式运行
    存在事务,那就加入,不存在,那就抛异常
    创建一个新事务,如果当前有事务,那就把当前事务挂起
    以非事务运行,如果当前有事务,把当前事务挂起
    以非事务运行,如果当前有事务,就抛异常
    存在事务,那就创建一个嵌套事务,没有事务,那就创建一个新事务

spring事务生效和失效的简单判定

是否是代理类进行的调用,如果是被代理类调用,那是不会生效的

当然,事务失效的原因很多,这只是代理方面的失效,解决代理方面的失效其实很简单,可以新建一个类,交给spring管理,然后调用,就没问题,但是这种比较麻烦,最简单的,其实可以被代理类自己注入自己,然后调用方法,这样也能解决事务失效的问题

总之,核心就一个,让代理类去调用

这里有一个地方一定要注意,jdbctemplate和事务管理器,一定要是同一个DataSource,事务才能生效,如果不是同一个DataSource,也会出现事务不生效的情况

好啦,本次的分享就到这儿,就像上面说的,本次知识大概分享一下spring比较重要的一些原理,后期会慢慢去研究源码啊什么的更深入的东西

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值