Spring源码分析(2)-Spring依赖注入与方法注入

Spring源码分析(2)-Spring依赖注入与方法注入

上一篇中分析了SpringIOC以及实例化对象的相关内容,本文主要涉及Spring的依赖注入的相关内容。

1 依赖注入

官网对于Spring DI的定义如下

A typical enterprise application does not consist of a single object (or bean in the Spring parlance). Even the simplest application has a few objects that work together to present what the end-user sees as a coherent application. This next section explains how you go from defining a number of bean definitions that stand alone to a fully realized application where objects collaborate to achieve a goal

那么IOC与DI到底有什么区别了?我认为IOC跟DI描述的是同一件事情,只是站在不同的角度而已。

Spring源码分析(2)-Spring依赖注入与方法注入

根据Spring官网介绍,注入的方法有两种

  1. 构造器注入
  2. Setter注入

Spring源码分析(2)-Spring依赖注入与方法注入

分别对以上两种方式进行测试,官网上用的是XML的方式,我这边就采用注解的方式了:

1.1 测试setter方法注入

测试代码如下,通过BloomTestController注入BloomTestService

package com.bloom.spring.di;

import org.springframework.stereotype.Component;

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 13:53
 **/
@Component
public class BloomTestService {
	BloomTestService() {
		System.out.println("BloomTestService create");
	}
}

package com.bloom.spring.di;

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

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 13:54
 **/
@Component
public class BloomTestController {
	private BloomTestService bloomTestService;

	public BloomTestController() {
		System.out.println("BloomTestController create");
	}

	public void test() {
		System.out.println(bloomTestService);
	}
	// 通过autowired指定使用set方法完成注入
	@Autowired
	public void setBloomTestService(BloomTestService bloomTestService) {
		System.out.println("注入bloomTestService by setter");
		this.bloomTestService = bloomTestService;
	}
}

package com.bloom.spring.di;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 13:56
 **/
@ComponentScan
public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Test.class);
		BloomTestController controller = ac.getBean("bloomTestController", BloomTestController.class);
		controller.test();

	}
}

输出的结果如下

BloomTestController create
BloomTestService create
注入bloomTestService by setter // 验证了确实是通过setter注入的
com.bloom.spring.di.BloomTestService@5fcfe4b2


1.2 测试构造函数注入

测试代码如下

package com.bloom.spring.di;

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

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 13:54
 **/
@Component
public class BloomTestController {
	private BloomTestService bloomTestService;

	public BloomTestController() {
		System.out.println("BloomTestController create by no args constructor");
	}
	@Autowired
	public BloomTestController(BloomTestService bloomTestService){
		System.out.println("注入bloomTestService by constructor with arg");
		this.bloomTestService = bloomTestService;
	}


	public void test() {
		System.out.println(bloomTestService);
	}
	// 通过autowired指定使用set方法完成注入
	/*@Autowired
	public void setBloomTestService(BloomTestService bloomTestService) {
		System.out.println("注入bloomTestService by setter");
		this.bloomTestService = bloomTestService;
	}*/
}

输出结果

BloomTestService create
注入bloomTestService by constructor with arg
bloomTestService create by constructor with arg
com.bloom.spring.di.BloomTestService@3b764bce
1.3 疑问
  1. @Autowired直接加到字段上跟加到set方法上有什么区别?为什么我们验证的时候需要将其添加到setter方法上?

    • 首先我们明确一点,直接添加@Autowired注解到字段上,不需要提供setter方法也能完成注入。以上面的例子来说,Spring会通过反射获取到Service中bloomTestService这个字段,然后通过反射包的方法,Filed.set(Service,bloomTestService)这种方式来完成注入

    • @Autowired添加到setter方法时,可以通过断点看一下方法的调用栈,如下:

Spring源码分析(2)-Spring依赖注入与方法注入

对于这种方式来说,最终是通过Method.invoke(object,args)的方式来完成注入的,这里的method对象就是我们的setter方法

  1. @Autowired为什么加到构造函数上可以指定使用这个构造函数?

    • 先测试将构造方法的@Autowired注解注释,然后运行发现

      BloomTestController create by no args constructor // 执行的为空参数构造
      BloomTestService create
      null
      
    • 将两个构造方法都加上@Autowired注解注释,然后运行发现

      警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bloomTestController': Invalid autowire-marked constructor: public com.bloom.spring.di.BloomTestController(com.bloom.spring.di.BloomTestService). Found constructor with 'required' Autowired annotation already: public com.bloom.spring.di.BloomTestController()
      Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bloomTestController': Invalid autowire-marked constructor: public com.bloom.spring.di.BloomTestController(com.bloom.spring.di.BloomTestService). Found constructor with 'required' Autowired annotation already: public com.bloom.spring.di.BloomTestController()
      	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:314)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1230)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1145)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:514)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:474)
      	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:276)
      	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:213)
      	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:274)
      	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:155)
      	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:805)
      	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971)
      	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:598)
      	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:88)
      	at com.bloom.spring.di.Test.main(Test.java:15)
      
      Process finished with exit code 1
      
      

    发现直接报错了,报错的大概意思是已经找到了一个被@Autowired注解标记的构造函数,同时这个注解中的required属性为true。后来我测试了将其中一个注解中的required属性改为false,发现还是报同样的错,最终将两个注解中的属性都改为false测试才通过,并且测试结果跟上面的一样,都是执行的无参构造。要说清楚这一点,涉及到两个知识

    • Spring中的注入模型,下篇文章专门讲这个
    • Spring对构造函数的推断。这个到源码阶段我打算专门写一篇文章,现在我们暂且记得:

    默认的注入模型*下,Spring如果同时找到了两个符合要求的构造函数*,那么Spring会采用默认的无参构造进行实例化,如果这个时候没有无参构造,那么此时会报错java.lang.NoSuchMethodException。什么叫符合要求的构造函数呢?就是构造函数中的参数Spring能找到,参数被Spring所管理。

    这里需要着重记得:一,默认注入模型;二,符合要求的构造函数

  2. 使用构造器注入并且加属性注入

    Spring虽然能在构造函数里完成属性注入,但是这属于实例化对象阶段做的事情,那么在后面真正进行属性注入的时候,肯定会将其覆盖掉。现在我们来验证我们的结论

    package com.bloom.spring.di;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * @program: bloom
     * @description:
     * @author: hao.yu
     * @create: 2020-04-17 13:54
     **/
    @Component
    public class BloomTestController {
    	private BloomTestService bloomTestService;
    //
    //	@Autowired
    	public BloomTestController() {
    		System.out.println("BloomTestController create by no args constructor");
    	}
    	@Autowired
    	public BloomTestController(BloomTestService bloomTestService){
    		System.out.println("注入bloomTestService by constructor with arg");
    		this.bloomTestService = bloomTestService;
    	}
    
    	public void test() {
    		System.out.println(bloomTestService);
    	}
    	// 通过autowired指定使用set方法完成注入
    	@Autowired
    	public void setBloomTestService(BloomTestService bloomTestService) {
    		System.out.println("注入bloomTestService by setter");
    		this.bloomTestService = null;
    	}
    }
    
    

    运行结果:

    BloomTestService create
    注入bloomTestService by constructor with arg
    注入bloomTestService by setter
    null

1.4 区别

Spring源码分析(2)-Spring依赖注入与方法注入

根据上图的说明可以得出结论:

  1. 构造函数注入跟setter方法注入可以混用
  2. 对于一些强制的依赖,我们最好使用构造函数注入,对于一些可选依赖我们可以采用setter方法注入
  3. Spring团队推荐使用构造函数的方式完成注入。但是对于一些参数过长的构造函数,Spring是不推荐的

2 方法注入(Method Injection)

n most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the dependency by defining one bean as a property of the other. A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container creates the singleton bean A only once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.

上面的一段文字是Spring官方对于方法注入的解释。

2.1 为什么需要方法注入?

考虑下面的使用场景

package com.bloom.spring.di;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 16:17
 **/
@Component
@Scope("prototype")
public class MyDao {
	int i;

	public MyDao() {
		System.out.println("luBan create ");
	}
	// 每次将当前对象的属性i+a然后打印
	public void addAndPrint(int a) {
		i+=a;
		System.out.println(i);
	}
}

package com.bloom.spring.di;

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

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 16:17
 **/
@Component
public class MyService {

	@Autowired
	private MyDao dao;



	public void test(int a){
		dao.addAndPrint(a);
	}

}

package com.bloom.spring.di;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 13:56
 **/
@ComponentScan
public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Test.class);
		MyService service = (MyService) ac.getBean("myService");
		service.test(1);
		service.test(2);
		service.test(3);
	}
}

在上面的代码中,我们有两个Bean,MyService为单例的Bean,MyDao为原型的Bean。我们的本意可能是希望每次都能获取到不同的LuBanService,预期的结果应该打印出:

1,2,3

实际输出:

1,3,6

这个结果说明每次MyDao都是同一个对象,因为在依赖注入的阶段Spring已经完成了MyDao的注入,之后在调用测试方法的时候,不会在进行注入,得到的对象将一直是通一个对象。

这样的话,就失去了设置原型的意义,每次都是同一个对象,所以怎么去解决这个问题?如果在每次使用Bean的时候能够重新获取就可以了,这个时候就需要用到方法注入来解决了。

2.2 通过注入上下文(ApplicationContex对象)
  • 实现ApplicationContextAware接口

    package com.bloom.spring.di;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    /**
     * @program: bloom
     * @description:
     * @author: hao.yu
     * @create: 2020-04-17 16:32
     **/
    @Component
    public class MyService2 implements ApplicationContextAware {
    	private ApplicationContext applicationContext;
    	public void test(int a) {
    		MyDao dao = ((MyDao) applicationContext.getBean("myDao"));
    		dao.addAndPrint(a);
    	}
    
    	@Override
    	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    		this.applicationContext = applicationContext;
    	}
    }
    
    
  • 直接注入上下文

package com.bloom.spring.di;

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

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 16:35
 **/
@Component
public class MyService3 {
   @Autowired
   private ApplicationContext applicationContext;

   public void test(int a) {
      MyDao dao = ((MyDao) applicationContext.getBean("myDao"));
      dao.addAndPrint(a);
   }
}
2.3 通过@LookUp
package com.bloom.spring.di;

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

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 16:48
 **/
@Component
public class MyService5 {
	public void test(int a) {
		MyDao dao = lookUp();
		dao.addAndPrint(a);
	}
	// 
	@Lookup
	public MyDao lookUp(){
		return null;
	}
}

2.4 方法注入 之 replace-method

方法注入还有一种方式,即通过replace-method这种形式,没有找到对应的注解,所以这里我们也就用XML的方式测试一下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="myBean" class="com.bloom.spring.bean.MyBean">
		<property name="myAge" value="100"/>
		<property name="myName" value="张三丰"/>
	</bean>

	<bean id="heBean" class="com.bloom.spring.bean.HeBean">
		<property name="heName" value="张翠山"/>
	</bean>
	<bean id="companyBean" class="com.bloom.spring.factory.CompanyFactoryBean">
		<property name="companyInfo" value="拉勾,中关村,500"/>
	</bean>

	<bean id="myService" class="com.bloom.spring.factory.ServiceFactory" factory-method="getSomeService">

	</bean>
	<bean id="myService4" class="com.bloom.spring.di.MyService4">
		<replaced-method replacer="replacer" name="test"/>
	</bean>
	<bean id="replacer" class="com.bloom.spring.di.MyReplacer"/>
</beans>
package com.bloom.spring.di;

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 16:37
 **/
public class MyService4 {

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

package com.bloom.spring.di;

import org.springframework.beans.factory.support.MethodReplacer;

import java.lang.reflect.Method;

/**
 * @program: bloom
 * @description:
 * @author: hao.yu
 * @create: 2020-04-17 16:39
 **/
public class MyReplacer implements MethodReplacer {
	@Override
	public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
		System.out.println("替代"+obj+"中的方法,方法名称:"+method.getName());
		System.out.println("执行新方法中的逻辑");
		return null;
	}
}


执行结果:

替代com.bloom.spring.di.MyService4$$EnhancerBySpringCGLIB$$c1404cb2@e73f9ac中的方法,方法名称:test
执行新方法中的逻辑

3 依赖注入与方法注入的总结

  • 我们首先要明确一点,什么是依赖(Dependencies)?来看官网中的一段话:

Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other objects with which they work) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes or the Service Locator pattern.

可以说,一个对象的依赖就是它自身的属性,Spring中的依赖注入就是属性注入

  • 一个对象由两部分组成:属性+行为(方法),可以说Spring通过属性注入+方法注入的方式掌控的整个bean。
  • 属性注入跟方法注入都是Spring提供给我们用来处理Bean之间协作关系的手段
  • 属性注入有两种方式:构造函数,Setter方法。
  • 方法注入(LookUp Method跟Replace Method)需要依赖动态代理完成
  • 方法注入对属性注入进行了一定程度上的补充,因为属性注入的情况下,原型对象可能会失去原型的意义.

Spring源码分析(2)-Spring依赖注入与方法注入

上一篇:C++常用的时间处理函数(检验 struct tm* 是否合法,获取当前标准时间,获取当前时间戳,标准时间转毫秒级时间戳,时间戳转标准时间,…)


下一篇:关于使用CPU缓存的一个小栗子