spring5源码学习(一)

spring 5.3 源码学习(一)

spring核心原理学习

注:该文章均不涉及spring的源码,从spring原理方面进行理解,在后续文章会展示spring的源码

spring解析

在以前学习spring的会经常使用到
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
通过ClassPathXmlApplicationContext加载spring的配置文件,现在这个类已经过时,在新版的springmvc和springboot中都采用AnnotationConfigApplicationContext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class)
其作用也是一样加载AppConfig配置类的信息

@Component
public class UserService  {

	public void test() {
		System.out.println("test");
	}
}

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

}

public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = (UserService) context.getBean("userService");
		userService.test();
	}
}

通过以上代码getBean(“userService”)方法创建一个叫做userService的bean并调用该bean的方法。但是创建出来的userService怎么知道是UserService 类型的呢?
我们可以想到spring不会把所有的类都创建,那么必须要知道哪些类需要创建哪些类不需要创建。所以spring第一步需要进行包扫描,通过扫描路径下那些类有@Component、 @Service等注解。那么扫描路径哪里来,可以想到是通过某个配置把路径传进去。然后userService如何对应UserService 这个类呢。应该是一个Map类把创建bean的name作为key,对应的类型作为value保存起来,这样通过getBean(“userService”)就能找到对应的类了。
总结
spring会通过解析AppConfig这个类获取扫描路径
通过扫描路径扫描哪些类上有@Component、 @Service这些注解。
把有这些注解的类创建出来放入到一个Map<String, Class>当中保存起来
然后通过getBean(“userService”);bean的名称获取到bean对应的类型。
那么问题来了bean是如何创建的呢

bean的创建流程

创建一个对象往往通过new xxx();其实就是调用了类的构造方法,那么spring也应当是调用了类的构造方法(默认为无参构造)来创建对象,那么创建了对象之后,对象的属性是怎么赋值的呢,我们在使用spring的时候经常使用的@Autowired,然后这个属性就有值了,为什么呢?
同样spring不会把bean中的每个属性都进行赋值,而是通过扫描属性上是否有@Autowired注解,有的话在进行属性注入。那么这是怎么做到的呢,下面通过代码来大致展示该流程。

//假设下面这行代码是扫描之后,通过全限定名加载得到的类对象
Class<?> clazz = Class.forName("com.mydemo.service.UserService");
//实例化对象
UserService bean = (UserService)clazz.newInstance();
//对象属性赋值
for (Field field : clazz.getFields()) {
	if (field.isAnnotationPresent(Autowired.class)) {
		field.set(bean,??);
	}
}

那么在属性注入之后就成为一个bean了吗,还不是,还需要进行初始化前,初始化,初始化后。
初始化前
为什么要有初始化前这个步骤,举个例子,如果在属性注入的时候,假设我们需要注入一个User类到UserService中,User类包含了用户名和密码,需要从数据库中获取,那么单纯的通过上面的代码就不能完成。这时候我们需要在依赖注入之后,初始化之前把数据库中的用户信息查出来放到User类中,这就是初始化前(当然这只是举个例子而已)。我们把查询用户数据封装成一个方法,只需要在初始化前调用即可。那么spring怎么知道那些方法是在初始化前执行的呢?这就需要@PostConstruct注解。

@Component
public class User {
	private String username;
	private String password;
}

@Component
public class UserService  {

	@Autowired
	private User user;

	@PostConstruct
	private void a(){
		//从数据库中查询信息 赋值给User
	}
	public void test() {
		System.out.println("test");
	}
}

//判断哪些方法需要初始化前之情
for (Method method : clazz.getMethods()) {
	if (method.isAnnotationPresent(PostConstruct.class)) {
		method.invoke(bean,null);
	}

}

可以理解为初始化前是来处理@PostConstruct注解

初始化
和初始化前类似,只不过初始化用来处理实现了InitializingBean接口的类。实现了该接口要重写afterPropertiesSet()这个方法。上面的例子也可用在此处。代码大致如下:

@Component
public class UserService implements InitializingBean {

	@Autowired
	private User user;
	public void test() {
		System.out.println("test");
	}
	@Override
	public void afterPropertiesSet() throws Exception {
		//从数据库中查询信息 赋值给User
	}
}

//判断是否实现InitializingBean
if (bean instanceof InitializingBean){
	((InitializingBean)bean).afterPropertiesSet();
}

初始化后
初始化后用来处理一个非常重要的概念就是AOP。

总结
bean的创建流程就是bean的生命周期。spring的生命周期大致如下:

  1. 利用类的构造方法实例化得到一个对象(一个类中有多个构造方法,是如何选择呢,这个叫构造方法推断)
  2. 得到一个对象之后spring会扫描这个对象属性中是否含有@Autowired,有的话依赖注入
  3. 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、 BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前 对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、 setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调,以后再说)
  4. Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解 了,如果存在,Spring会调用当前对象的此方法(初始化前)
  5. 然后Spring会判断该对象是否实现了InitializingBean接口,如果实现了,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化)
  6. 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后)

推断构造方法

spring在生成bean的过程中,需要利用该类的构造方法实例化得到一个对象,但是如果一个类有多个构造方法spring会用哪个呢?

public class Test {

    public static void main(String[] args) {
        ZhouyuApplicationContext applicationContext = new ZhouyuApplicationContext(AppConfig.class);
        OrderService orderService = (OrderService) applicationContext.getBean("orderService");
        orderService.test();
    }
}

@Component
public class UserService {
	//定义两个普通类OrderService 、GoodsService  分别加上@Component
	public UserService(OrderService orderService,GoodsService goodsService) {
		System.out.println(orderService);
		System.out.println(goodsService);
	}

	public UserService(OrderService orderService) {
		System.out.println(orderService);
	}
	public void test() {
		System.out.println("test");
	}
}

当存在两个构造参数时执行Test的main方法会报错:Failed to instantiate [com.mydemo.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.zhouyu.service.UserService.<init>();说找不到默认的构造方法。我们知道当构建了有参构造而不显示申明无参构造的话,那么这个类就没有无参构造方法。
如果显示申明了无参构造,再来执行上述代码就不会报错,如果只有一个有参构造,再来执行上述代码也不会报错。但是如果有多个构造方法怎么办呢?只需要在构造方法上添加@Autowired,spring就会知道调用这个构造方法,如果不添加@Autowired,那么就会默认调用无参构造,如果没有无参构造就会报错。
另一个问题,spring怎么知道有参构造的参数对应哪些类型呢?之前有提过spring会把创建出来的类放到一个Map中,然后根据入参的类型去找,如果只找到一个,就作为入参,如果找到多个,再根据参数名来找,如果还找不到就会报错。
总结
spring在生成bean时会调用bean的构造方法,具体判断如下:

  1. 如果只有一个构造方法,不管是有参还是无参都会调用
  2. 如果有多个,默认调用无参构造,但是如果有某个构造方法上添加了@Autowired注解,就会调用有@Autowired注解的构造方法。如果多个构造方法中没有@Autowired也没有无参构造spring就会报错No default constructor found;
  3. 在调用有参构造时,会根据入参的类型和名称在spring生成的bean中寻找。spring在生成一个bean后会把bean的名称作为key,bean的类型作为value保存到Map中(以单例为例子)。因此spring会先根据入参类型查找,找到唯一就作为入参,如果有多个再根据bean的名称确定唯一,如果找不到bean作为入参参数,spring就会报错No qualifying bean of type 'com.mydemo.service.OrderService' available: expected at least 1 bean which qualifies as autowire candidate
    这个过程就是推断构造方法

AOP大致流程

之前有说到在初始化后的这个阶段用来处理AOP。它是怎么处理呢?把上述代码案例改造一下

//修改配置类开启动态代理
@ComponentScan("com.mydemo")
@EnableAspectJAutoProxy
public class AppConfig {
}
//给UserService的test方法添加切面
@Aspect
@Component
public class MyAspect {
	@Before("execution(public void com.mydemo.service.UserService.test())")
	public void demoBefore(JoinPoint joinPoint) {
		System.out.println("AOPbefore");
	}

}
//其他代码不变

spring会判断这个类有没有相关切面(怎么判断后面再说),没有就直接生成bean。有的话就生成代理对象。
先说说怎么生成这个代理对象。spring的代理采用cglib动态代理。代码类似于

class UserServiceProxy extends UserService{
		public void test(){
			//执行切面的方法
			TODO
			//执行自己的方法
			super.test();
		}
	}

大致创建过程:

  1. 生成代理类UserServiceProxy,代理类继承UserService
  2. 代理类中重写了父类的方法,比如UserService中的test()方法
  3. 代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过 UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的对象)
  4. 代理类中的test()方法被执行时的逻辑如下: a. 执行切面逻辑(@Before) b. 调用target.test()

测试代码

@Component
public class UserService {
	@Autowired
	private OrderService orderService;

	public void test() {
		System.out.println(orderService);
	}

}

public class Test {

	public static void main(String[] args) throws Exception {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = (UserService) context.getBean("userService");
		userService.test();
	}
}

在userService.test();处打上断点
在这里插入图片描述
可以看到得到的userService 是一个代理对象,同时orderService没有值,也就是说虽然在UserService类中的OrderService属性添加了@Autowired 但是并没有赋值。但是最后public void test() {System.out.println(orderService);}输出结果是有值的
在这里插入图片描述
为什么呢?再看下面这个图
在这里插入图片描述
也就是是说userService的代理对象中的OrderService是没有值的,但是一开始生成的userService对象(初始化后之前)的OrderService是有值的。也就是说初始化后生成代理类没有进行OrderService的依赖注入。
因此上面代理的代码应该要改成

class UserServiceProxy extends UserService{
	UserService target
		public void test(){
			//执行切面的方法
			TODO
			//执行自己的方法
			target.test();
		}
	}

spring在生成代理对象UserServiceProxy的时候会把之前普通的UserService传给target,让后target调用test(),实际上调用test方法的是UserService 而不是 UserServiceProxy,执行切面方法的则是UserServiceProxy。
然后再回到前面提到的spring是如何判断这个类是不是需要生成代理类?

  1. 找出所有的切面Bean(也就是标注有@Aspect的类)
  2. 遍历切面中的每个方法,看是否写了@Before、@After等注解
  3. 如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
  4. 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP

总结

  • 在初始化后spring先判断那些bean需要生成代理对象
    通过扫描所有类找出有@Aspect的类作为切面bean
    遍历每个切面bean中每个方法,把标注有@Before、@After等注解等注解的方法,和对应的切点封装的一个map中
    每次创建在初始化后通过这个map看能否找到对应代理信息,如果有则这个类需要生成代理类
  • 通过cglib给需要代理的类创建代理对象
    通过创建一个proxy继承于该类,重写该类被代理的方法。
    把被代理的类注入到proxy的target中。这个target就是初始化前创建的类
    通过proxy调用代理方法,在代理方法中执行切面方法,然后通过target调用具体的方法
  • 最后得到的最终bean就是代理对象
    因此我们在使用AOP的时候,为什么失效,就要考虑是不是代理对象在调用AOP,如果不是自然不会生效

spring事务大致流程

事务流程和aop相似。当我们在某个方法上加了@Transactional注解后,就表示该方法在调用时会开启Spring事 务,而这个方法所在的类所对应的Bean对象会是该类的代理对象。
Spring事务的代理对象执行某个方法时的步骤:

  1. 判断当前执行的方法是否存在@Transactional注解
  2. 如果存在,则利用事务管理器(TransactionMananger)新建一个数据库连接
  3. 修改数据库连接的autocommit为false
  4. 执行target.test(),执行程序员所写的业务逻辑代码,也就是执行sql
  5. 执行完了之后如果没有出现异常,则提交,否则回滚 Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法

被调用时,要判断到底是不是直接被代理对象调用的,如果是则事务会生效,如果不是则失效。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值