Spring学习笔记

97 篇文章 2 订阅
24 篇文章 1 订阅

Spring是如何创建一个Bean对象的

OrderService这是一个Bean
在这里插入图片描述
UserService这也是一个Bean,依赖了OrderService这个Bean
在这里插入图片描述
AppConfig表示一个配置类,定义了一个扫描的路径
在这里插入图片描述
test类中的main方法,先去创建Spring容器,然后再通过Spring容器我们去getBean,就会得到一个UserService的Bean对象,然后再去调这个对象的test方法
在这里插入图片描述
众所周知,在java中,想要创建一个对象,必须要基于一个类,或者说是基于这个类里面的构造方法才能去构造一个对象。
在Spring的内部,想要创建一个Bean对象,首先肯定是要利用这个类的无参的构造方法,先去得到一个对象。

UserService类 --> 无参的构造方法 --> 对象

得到的这个对象,有一个特点,它里面的OrderService对象是没有值的。
因为我们通过无参的构造方法构造对象,其实就相当于去new了一个
在这里插入图片描述
这个对象和我们理解的Bean对象之前还是有一些区别的,中间缺少了依赖注入

UserService类 --> 无参的构造方法 --> 对象 --> 依赖注入 -->  --> Bean对象

说白了,依赖注入就是给前面的那些属性去赋值,给哪些属性呢,就是给加了@Autowired或者@Resource等等这些属性去赋值
依赖注入大概有两个步骤:

  • 1、找到你前面生成出来的这个对象里面有哪些属性上面是加了比如@Autowired注解的
  • 2、然后去给这些属性去赋值

下面可以看一下简单的伪代码
在这里插入图片描述

什么是单例池?作用是什么?

在这里插入图片描述
对于我UserService来说,默认就是单例Bean,所以这三个对象是同一个对象
在这里插入图片描述
那么它的底层是怎么实现的呢?是用的Map<beanName,Bean对象>
我getBean的时候,创建了UserService类,然后我就去Map里面去找,看有没有key是userService的,如果有,那我就把对应的value给返回
如果Map中没有,那就去创建这个Bean对象,然后放入Map中去

UserService类 --> 无参的构造方法 --> 对象 --> 依赖注入 --> 放入Map单例池 --> Bean对象

这个单例池在Spring的源码里面有个名字叫singletonObjects,它的类型是ConcurrentHashMap
如果你创建的UserService是一个多例Bean的话,创建的时候就不需要有这一步

UserService类 --> 无参的构造方法 --> 对象 --> 依赖注入 --> Bean对象

Bean对象和普通对象之间的区别是什么?

我们通过无参的构造方法生成的对象和最终的Bean对象,大部分情况下是同一个对象,只不过处于不同的时期而已,也就是说通过无参构造方法创建的普通对象,经过一系列的步骤之后,就可以被称为Bean对象了

@PostConstruct注解是如何工作的?

UserService类 --> 无参的构造方法 --> 对象 --> 依赖注入 --> 初始化前 --> 初始化 --> 初始化后 --> 放入Map单例池 --> Bean对象

首先新增一个User类表示用户
在这里插入图片描述
然后在USerService里面新增一个属性,叫做admin,也就是管理员,信息是唯一并且存在数据库里面的

在这里插入图片描述

在这里插入图片描述
这个时候如果希望我们拿到UserService的Bean,这个admin属性就有值,并且是从数据库中查出的值,封装成一个User对象,然后赋值给这个admin属性,应该怎么来实现呢?
这个时候就需要我们自己去写一个方法
在这里插入图片描述
这个时候Spring会调a方法

UserService类 --> 无参的构造方法 --> 对象 --> 依赖注入 --> 初始化前(a()) --> 初始化 --> 初始化后 --> 放入Map单例池 --> Bean对象

因为getBean就会创建UserService这个Bean,而在创建的过程中,他其实就会调用这个UserService这个对象的a方法,只要调了a方法,就会执行里面的逻辑,这样admin里面就有值了
但是怎么才能让Spring在初始化之前调用a方法呢,只需要加一个@PostConstruct注解
在这里插入图片描述
就是先去找有@PostConstruct注解的方法,然后用反射去调用这个方法
在这里插入图片描述

Bean的初始化是如何工作的?

我们也可以实现InitializingBean接口,然后将要执行的逻辑放在afterPropertiesSet()方法里面
在这里插入图片描述

在这里插入图片描述

UserService类 --> 无参的构造方法 --> 对象 --> 依赖注入 --> 初始化前(@PostConstruct) --> 初始化(InitializingBean) --> 初始化后 --> 放入Map单例池 --> Bean对象

在Spring的源码中,有一个doCreateBean()方法就是创建Bean对象的过程
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后就调用initializeBean()方法,把前面构造方法实例化的那个对象传进来
在这里插入图片描述
然后就是在这个方法里面,去处理@PostConstruct注解的对应的方法去执行在这里插入图片描述
执行完了以后就来执行invokeInitMethods()这个初始化方法
在这里插入图片描述
可以看到,这里就是之前说到的那个接口
在这里插入图片描述
去判断有没有实现这个接口,如果实现了,就是true,然后就会去打印一个日志,然后去判断安全管理器有没有打开,默认情况下没有打开,没有打开的话就进了else,将这个对象进行强制转换,然后调用afterPropertiesSet()方法
在这里插入图片描述

Spring初始化和实例化区别是什么?

Bean的实例化实际上就是通过构造方法去得到一个对象
Bean的初始化就是去执行你实例化出来的对象里面的某一个方法

什么是初始化后?

UserService类 --> 无参的构造方法 --> 普通对象 --> 依赖注入 --> 初始化前(@PostConstruct) --> 初始化(InitializingBean) --> 初始化后(AOP) --> 代理对象 --> 放入Map单例池 --> Bean对象

如果没有AOP,那么是把普通对象放到单例池里面去,如果有AOP,那么是把代理对象放到单例池里面去

构造方法的使用

如果一个类里面有两个构造方法,Spring默认使用无参的构造方法,如果没有无参的构造方法,那么就会使用加了@Autowired在注解的构造方法

Spring的DI注解

使用Spring框架自身提供的注解:Autowired

Autowired和Qualifier标签:
1、通过@Autowired标签可以让Spring自动的把对象需要的属性从Spring容器中找出来,并注入(设置)给该属性。
2、第三方程序:
Spring3.0之前,需要手动配置@Autowired注解解析程序;
在Spring3.0开始,Spring就会自动的加入针对@Autowired标签的解析程序。
3、@Autowired标签贴在字段或者setter方法上。
4、@Autowired可以同时为一个属性注入多个对象。
public void setXxx(OtherBean1 other1,OtherBean2 other2) {}
5、使用@Autowired标签可以注入Spring内置的重要对象,比如BeanFactory,ApplicationContext。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SpringTest {undefined
@Autowired
private ApplicationContext ctx;
}
6、默认情况下@Autowired标签必须要能找到对应的对象,否则报错。不过,可使用required=false来避免该问题: @Autowired(required=false)
7、@Autowired找bean的方式:
1)首先按照依赖对象的类型找,如果没有找到,默认会报错;如果找到一个匹配的对象,直接注入;
2)如果在Spring上下文中找到多个匹配(2个或者2个以上)的类型,再按照名字去找,如果没有匹配则报错;
3)可以通过使用@Qualifier(“otherBean”)标签来规定依赖对象按照bean的id+类型去找;

使用JavaEE规范提供的注解:Resource

@Resource标签:
1、@Resource标签是JavaEE规范的标签;
2、@Resource标签也可以作用于字段或者setter方法;
3、也可以使用@Resource标签注入一些spring内置的重要对象,比如BeanFactory.ApplicationContext;
4、@Resource必须要求有匹配的对象;
5、Spring容器自动加载了@Resource的注解解析器
6、@Resource标签找bean的方式:
1)首先按照名字去找,如果找到,就使用setter或者字段注入;
2)如果按照名字找不到,再按照类型去找,但如果找到多个类型匹配类型,报错;
3)可以直接使用name属性指定bean的名称;但是,如果指定的name,就只能按照name去找,如果找不到,就不会再按照类型去找;

小结

1、@Autowired和@Resource注解的最多用法是贴在类中的属性中,表示从容器中找到需要的属性并自动给属性注入值.
2、两个注解都可以贴在setter方法上.
3、两个注解都可以注入容器对象.
4、两个注解都是使用"< context:annotation-config>"为注解的解析器.
5、@Autowired和@setter方法的区别:
@setter注解:lombok包中的注解,只是为属性添加一个setter方法
@Autowired注解:spring框架的注解,表示将容器中的对象自动的注入被贴的属性中.

@Autowired和@Resource注解的区别

1.@Autowired可以同时为两个属性注入值,@Resource一次只能为一个属性注入值
2.@Resource要求容器中必须有匹配的对象;@Autowired默认要求容器中必须有匹配对象,但是可以通过"required=false"修改其默认设置.
3.@Autowired首先首先通过类型寻找匹配对象,当有多个相同类型时再按照名字找;
@Resource注解首先按照名字寻找匹配对象,没有找到时在按照类型来找.

Spring AOP底层是怎么工作的?

Spring AOP是使用cglib技术的,cglib技术是基于父子类来进行动态代理的。
我要生成代理类,这个代理类其实就是父类,我要生成代理对象,首先要生成代理类,这个代理类是动态生成的,是cglib帮我们生成的。

父子类

UserServiceProxy对象 --> UserService对象 --> UserService对象.target=普通对象 --> 放入Map单例池
UserService对象.test()

class UserServiceProxy extends UserService{

	UserService target;
	
	public void test(){
	
		//@Before切面逻辑
		//target.test(); //普通对象.test();
	}
}

因为普通对象是进行了依赖注入的,所以orderService是有值的,所以执行UserService里面的test方法的时候,orderService是有值的
在这里插入图片描述
在这里插入图片描述
这里this就表示当前哪个对象在执行这个方法,2578,很明显可以看出是普通对象,所以orderService是有值的

Spring事务底层是怎么工作的?

假设先写一个简单的sql,使用了@Transactional注解开启事务,我们给他估计抛一个异常,运行一下看会不会回滚
在这里插入图片描述
运行以后果然抛了空指针异常
在这里插入图片描述

然后去数据库中看,发现数据已经插入成功了,根本就没有回滚
回去找原因发现是没有加@Confoguration注解
在这里插入图片描述

所以是怎么实现的呢,以之前的为例

父子类

UserServiceProxy对象 --> UserService对象 --> UserService对象.target=普通对象 --> 放入Map单例池
UserService对象.test()

class UserServiceProxy extends UserService{

	UserService target;
	
	public void test(){
	
		//@Transactional
		//事务管理器新建一个数据库连接conn
		//conn.autocommit=false
		
		//target.test(); //普通对象.test(); sql1 sql2 sql3

		//conn.commit(); conn.rollback();
	}
}

事务管理器:
在这里插入图片描述
有时候Spring事务的注解也会失效,什么情况下会失效呢,我们来研究一下
在这里插入图片描述

在这里插入图片描述
NEVER就是Spring事务的七大传播机制之一,是以非事务的方式执行,如果当前存在事务,则抛出异常
这里插一个知识点

Spring事务的七大传播机制

required(默认属性)

如果存在一个事务,则支持当前事务,如果没有事务,则开始一个新的事务

Mandatory

支持当前事务,如果当前没事务,则抛出异常

Never

以非事务的方式执行操作,如果当前存在事务,则抛出异常

Not_supports

以非事务的方式执行操作,如果当前存在事务,则把挂起

requires_new

新建事务,如果当前存在事务,则把挂起

Supports

支持当前事务,如果当前没有事务,则以非事务的方式执行

Nested

支持当前事务,新增Save point点,与当前事务同步提交或回滚

然后运行,发现并没有抛出异常,两个sql都执行了,这是为什么呢?
原因是因为,调用test方法的是普通对象,而我们都知道,普通对象其实就相当于是new UserService(),因此Spring事务的注释会失效
只有UserService的代理对象在调用a()方法的时候才会生效
那如果想要让Spring事务的注释生效应该怎么做呢?
可以创建一个类,然后把a()方法放到这个类里面
在这里插入图片描述

在这里插入图片描述
运行以后果然就因为存在事务所以抛出了异常
在这里插入图片描述
那么是为什么可以成功呢?
首先UserServiceBase肯定是一个Bean,里面有@Transactional的注解,所以肯定也是一个代理对象,所以在UserService中依赖了UserServiceBase,就会把UserServiceBase的代理对象找出来赋值给UserServiceBase,就用代理对象调用了a()方法
如果不想那么麻烦再创建一个类,就可以自己注入自己

在这里插入图片描述

@Configuration注解的作用是什么?

父子类

UserServiceProxy对象 --> UserService对象 --> UserService对象.target=普通对象 --> 放入Map单例池
UserService对象.test()

class UserServiceProxy extends UserService{

	UserService target;
	
	public void test(){
	
		//@Transactional
		//事务管理器新建一个数据库连接conn ThreadLocal<Map<DataSource,conn>>
		//conn.autocommit=false
		
		//target.test(); //普通对象.test(); jdbcTemplate DataSource-->new DataSource() sql1 sql2 sql3

		//conn.commit(); conn.rollback();
	}
}

是这样的,事务管理器新建一个数据库连接以后会放到线程本地存储ThreadLocal里,存放的格式是Map,key就是DataSource,value就是数据库连接conn
我们运行时候,jdbcTemplate就拿自己配的DataSource作为key去ThreadLocal的Map里面对比事务管理器配的DataSource是不是同一个,是同一个,就拿到conn,如果不是同一个,那么就自己新建一个,当然新建出来的数据库连接的autocommit是true,所以事务失效
我们之前没配@Configuration就事务失效就是这个原因
在这里插入图片描述
那为什么加上了就不失效了呢?
因为加上了@Configuration注解以后AppConfig调用AppConfig的代理对象,调用其中的代理方法,就会先去判断调用的方法里面的dataSource这个名字在Spring容器中有没有对应的Bean,如果有这个Bean,我就直返回这个Bean,如果没有这个Bean,我才真正去执行dataSource()方法

Spring为什么要用三级缓存来解决循环依赖

@Component
public class AService {
	@Autowired
	private BService bService;

	public void test() {
		System.out.println(bService);
	}
}
@Component
public class BService {
	@Autowired
	private AService aService;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面是AOP的方法,下面是不走AOP的方法

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值