SpringSpring底层核心原理解析

Posted Cry丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSpring底层核心原理解析相关的知识,希望对你有一定的参考价值。

本文内容索引:

1.Bean的生命周期底层原理
2.依赖注入底层原理
3.初始化底层原理
4.推断构造方法底层原理
5.AOP底层原理
6.Spring事务底层原理

​但都只是大致流程,后续会针对每个流程详细深入的分析源码实现。

先来看看入门使用Spring的代码:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
  • 第一行代码,会构造一个ClassPathXmlApplicationContext对象,ClassPathXmlApplicationContext该如何理解,调用该构造方法除开会实例化得到一个对象,还会做哪些事情?
  • 第二行代码,会调用ClassPathXmlApplicationContext的getBean方法,会得到一个UserService对象,getBean()是如何实现的?返回的UserService对象和我们自己直接new的UserService对象有区别吗?
  • 第三行代码,就是简单的调用UserService的test()方法,不难理解。

但是用ClassPathXmlApplicationContext其实已经过时了,在新版的Spring MVC和Spring Boot的底层主要用的都是AnnotationConfigApplicationContext,比如:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

可以看到AnnotationConfigApplicationContext的用法和ClassPathXmlApplicationContext是非常类似的,只不过需要传入的是一个class,而不是一个xml文件。

而AppConfig.class和spring.xml一样,表示Spring的配置,比如可以指定扫描路径,可以直接定义Bean,比如:

spring.xml中的内容为:

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

AppConfig中的内容为:

@ComponentScan("com.cry")
public class AppConfig 

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


所以spring.xml和AppConfig.class本质上是一样的。

目前,我们基本很少直接使用上面这种方式来用Spring,而是使用Spring MVC,或者Spring Boot,但是它们都是基于上面这种方式的,都需要在内部去创建一个ApplicationContext的,只不过:

  • Spring MVC创建的是XmlWebApplicationContext,和ClassPathXmlApplicationContext类似,都是基于XML配置的
  • Spring Boot创建的是AnnotationConfigApplicationContext

其实不管是AnnotationConfigApplicationContext还是ClassPathXmlApplicationContext,目前,我们都可以简单的将它们理解为就是用来创建Java对象的,比如调用getBean()就会去创建对象(此处不严谨,getBean可能也不会去创建对象,后续解释)

在Java语言中,肯定是根据某个类来创建一个对象的。我们在看一下实例代码:

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

​当我们调用context.getBean(“userService”)时,就会去创建一个对象,但是getBean方法内部怎么知道"userService"对应的是UserService类呢?

所以,我们就可以分析出来,在调用AnnotationConfigApplicationContext的构造方法时,也就是第一行代码,会去做一些事情:

  • 1.解析AppConfig.class,得到扫描路径
  • 2.遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String, Class>。(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap,后续会讲到)
  • 3.Spring会根据某个规则生成当前类对应的beanName,作为key存入Map,当前类作为value

这样,但调用context.getBean(“userService”)时,就可以根据"userService"找到UserService类,从而就可以去创建对象了。

SpringBean创建的生命周期

SpringFramework帮我们创建的bean, 和普通的object有什么区别?

UserService userService = (UserService) applicationContext.getBean("userService");
UserService userService1 = new UserService();

Spring创建bean所需要经过的流程:
UserService --> 无参构造方法 --> object --> 依赖注入(给object中带了@Autowired注解的属性赋值) --> bean

DI(依赖注入):

for (Field field: userService.gatClass().getFields())
    if (field.isAnnotationPresent(Autowired.class))
        field.set(userService, ??)
    

从依赖注入到最终Spring帮我们生成bean之间还有一些步骤:初始化前 --> 初始化 --> 初始化后

@Component
public class Admin 

	private String username;
	private String password;

@Autowired
Admin admin; //new Admin()  //mysql --> Admin对象 --> admin属性

/**
* 只需要把调用这个赋值方法的时机放在bean生成之前就可以了
*/
public void setAttribute()
//在这里查出mysql中的属性, 手动赋值给admin对象

我们现在有个Admin类,映射mysql中查出来的数据,那么在Spring通过@Autowired帮我们生成这个bean后,他是没有属性的,具体的属性username/password需要我们自己调用setAttribute()方法来手动注入,那么有没有办法在bean生成前自动调用setAttribute()方法来让Spring做DI的这一步呢?肯定是有的。

我们只需要在bean生命周期的初始化前(setAttribute),执行这个过程就行了。

@PostConstruct
public void setAttribute()
//在这里查出mysql中的属性, 手动赋值给admin对象

我们只需要在方法上加一个@PostConstruct注解,Spring就知道要在bean的初始化前来调这个方法了。

为DI注入的对象自动赋值:

for (Method method: userService.gatClass().getMethods())
    if (method.isAnnotationPresent(PostConstruct.class))
        method.invoke(userService,null);
    

还可以通过实现InitializingBean并重写afterPropertiesSet()方法来实现类似功能,唯一不同是Spring判断当前类是否实现InitializingBean接口的过程是在初始化(afterPropertiesSet)时

public class UserService implements InitializingBean
	@Override
	public void afterPropertiesSet() throws Exceptions

	

判断一个对象是否实现了某个接口:object instantof InterfaceName

if (object instant of InitializingBean)
	(InitializingBean)object.afterPropertiesSet();

Spring源码:搜索doCreateBean,进入initializeBean(beanName, exposedObject, mbd),进入invokeInitMethods(beanName, wrappedBean, mbd),

随后就是经过:初始化后(AOP) -> 代理对象 -> bean,最终得到一个bean

我们来总结一下,一个SpringBean创建的生命周期就是:

BeanClass–> 无参构造方法(推断构造方法) --> 普通对象–> 依赖注入(给object中带了@Autowired注解的属性赋值) -->初始化前(调用带了@PostConstruct注解的方法) --> 初始化(判断object instant of initializeBean,调用重写的afterPropertiesSet) --> 初始化后(AOP切面得到代理对象) --> SpringBean

我们再来仔细的分析一下创建SpringBean生命周期过程中的一些细节:

细节1:推断构造方法

public UserService()     //1
   System.out.println("1");


public UserService(OrderService orderService)   //2 
   this.orderService = orderService;
   System.out.println("2");


public UserService(OrderService orderService,OrderService orderService1)   //3
   this.orderService = orderService;
   System.out.println("3");

比如我们现在UserService有3个构造方法,Spring会默认选择无参的构造方法,那么如果我们现在把默认的无参构造方法注释掉,Spring会怎么选择呢?

 No default constructor found; nested exception is java.lang.NoSuchMethodException: com.zhouyu.service.UserService.<init>()

对的,他会报一个异常,因为当只有2个有参构造方法的时候,Spring不知道要选择使用哪个有参构造方法,他会去尝试寻找default constructor,但是他没有找到,所以报了这个异常。可以通过在你想要让Spring调用的构造方法上加上@Autowired注解来让Spring能够正确识别。

细节2:Bean的创建过程

当我们只有一个有参构造方法的时候,他的入参有没有值呢? 如果有的话那么是什么时候创建的呢?

public UserService(OrderService orderService)   //2 
   this.orderService = orderService;
   System.out.println("2");

我们能看到入参是有值的,这个对象是怎么来的呢? 肯定是从Spring容器中来的

Map<name,orderService> map // 根据名字查
for(int i=0;i<map.size();i++)
    if(map.get(name).getClass() == object.getClass)  // 根据类型查
        count++

if(count == 1)
    return map.get(name);

细节3:AOP大致流程

开启AOP后调用userService.test(),会看到userService里的orderService属性为null,但进入test()方法后,会看到userService里的orderService是有值的,为什么?

分析下来可以发现,是因为"没有必要"

cglib动态代理核心思想就是继承了目标类的父类:

public class CglibProxy extends UserService 

	private UserService target;

	/**
	 * target赋值
	 *
	 * @param wrappedBean 普通对象
	 */
	public void applyBeanPostProcessorsAfterInitialization(Object wrappedBean) 
		target = (UserService) wrappedBean;
	

	@Override
	public void test() 
		// 执行@Before的切面逻辑
		target.test();
		// 执行@After的切面逻辑
	

jdk动态代理核心思想就是通过Class.forName().getMethod()拿到这个类中需要代理的那个方法,然后调用method.invoke()反射调用目标方法:

public class JdkProxy 

	private static Method m;

	/**
	 * JDK动态代理类会先在静态代码块中通过反射把所有方法都拿到并存在静态变量中
	 */
	static 
		try 
			m = Class.forName("service.IUserService")
					.getMethod("test",new Class[]);
		 catch (NoSuchMethodException e) 
			e.printStackTrace();
		 catch (ClassNotFoundException e) 
			e.printStackTrace();
		
	

	public void test()
		try 
			// todo: before增强逻辑
			m.invoke(this,null);
			// todo: after增强逻辑
		 catch (IllegalAccessException e) 
			e.printStackTrace();
		 catch (InvocationTargetException e) 
			e.printStackTrace();
		
	


Cglib在Spring中的实现:

public class CglibDynProxy implements Callback 

    /**
     * 要代理的目标对象
     */
    private Object target;

    /**
     * 代理逻辑:  通过字节码生成目标类的子类
     */
    public Object proxy()
        // 1.创建代理增强对象
        Enhancer enhancer = new Enhancer();
        // 2.设置父类,也就是代理目标类,CGLIB是通过生成代理类子类的方式来实现动态代理的
        enhancer.setSuperclass(target.getClass());
        // 3.设置回调函数,这个this其实就是代理逻辑实现类,也就是切面,等价于JDK动态代理的InvocationHandler
        enhancer.setCallback(this);
        // 4.创建代理对象,也就是目标类的子类
        return enhancer.create();
    

jdk动态代理在Spring中的实现:

public class JdkDynProxy 

	private static Method m;

	/**
	 * JDK动态代理类会先在静态代码块中通过反射把所有方法都拿到并存在静态变量中
	 */
	static 
		try 
			m = Class.forName("service.IUserService")
					.getMethod("test",new Class[]);
		 catch (NoSuchMethodException e) 
			e.printStackTrace();
		 catch (ClassNotFoundException e) 
			e.printStackTrace();
		
	

	private InvocationHandler getInvocationHandler()
		return (InvocationHandler) new JdkDynProxy();
	

	public final void test() throws Throwable 
		InvocationHandler invocationHandler = getInvocationHandler();
		invocationHandler.invoke(this,m,new Object[]);
	


创建UserServiceProxy类 —> 生成userServiceProxy代理对象 —> 代理对象.target = 普通对象

Spring在applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)方法中就把DI填充属性后的普通对象传进去了,然后创建一个AOP代理对象,让它的target属性 = wrappedBean;

AOP切面的总体创建过程:

遍历所有@Aspect注解的类,遍历其中@Before@After等注解的方法,全部取出来匹配一下是不是和Target类相关,然后丢到一个Map容器中缓存起来,代码运行时就可以直接使用了。

细节4:Spring事务

在@Transactional对某个方法开启事务后,需要在启动类上加上@Configuration注解,不然事务无法正常生效,为什么?

public class CglibProxy extends UserService 

	private UserService target;

	/**
	 * target赋值
	 *
	 * @param wrappedBean 普通对象
	 */
	public void applyBeanPostProcessorsAfterInitialization(Object wrappedBean) 
		target = (UserService) wrappedBean;
	

	@Override
	public void test() 
		// 1.判断方法上是否有@Transactional注解
        // 2.创建一个数据库连接conn(事务管理器通过dataSource创建)
        // 3.conn.autocommit = false
		target.test();
        // 如果方法中没有抛出异常: conn.commit()
        // 如果方法中抛出异常: conn.rollback()
	

Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法被调用时,要判断到底是不是直接被代理对象调用的,如果是则事务会生效,如果不是则失效。

可以通过另起一个类,注入的方式解决;或可以通过在当前类中注入当前类解决。

回到开始的问题,为什么在配置类上不加@Configuration或者@Component,就无法正常启动事务呢?

是因为Spring用到了代理模式,Spring会生成一个AppConfig的代理对象,代理会先去Spring容器中查找dataSource,如果有的话就直接返回,如果没有才真正调用AppConfig类中的dataSource()方法。而如果没有@Configuration或者@Component注解的话,jdbcTemplate和transactionManager所用到的dataSource是2个对象,他们并不相关,所以我们在用jdbcTemplate操作数据库的时候,自然无法受到transactionManager的影响了。

以上是关于SpringSpring底层核心原理解析的主要内容,如果未能解决你的问题,请参考以下文章

spring底层原理解析

Spring源码解读---底层核心原理解析

Spring源码解读---底层核心原理解析

SpringSpring IOC原理及源码解析之scope=requestsession

git的核心命令使用和底层原理解析

RxSwift之深入解析核心逻辑Observable的底层原理