spring的自动注入

Spring自动注入

spring的ioc

在刚开始学习spring的时候肯定都知道spring的两个特点:ioc,aop,控制反转和切面编程,这篇就只说说ioc

ioc是什么:在我们原来的代码中,如果A依赖了B,那么我们会自己在A类中来new B,创建B的实例来使用,是程序主动的去创建依赖,但是我们在使用spring的了之后还会在A中主动的去创建B吗?基本不会,因为创建对象的这个操作从原来的我们来控制变成了spring来管理,这个过程就称为控制反转,ioc并不是一种技术,而是一种编程思想,代码的设计思路

那么这样做的好处是什么?

  • 解耦,将对象之间的依赖关系交给spring来处理,避免硬编码导致高度耦合

  • 资源的集中易于管理和配置

而spring实现ioc使用的方法是通过DI(依赖注入),当我们大部分的对象都被spring管理后那么spring也需要将我们A中所依赖的B,C,D...都给填充到A中,这个过程是由spring来管理的,我们只需要按照spring的规则声明或指定,那么spring也会帮我们完成依赖的注入

自动注入

了解完spring的基本思想后,来回想一下当时学习spring的入门,有没有说过spring的一个特点就是可以自动注入?

 @Component
public class A {
@Autowired
private B b;
}
 @Component
public class B {
}

简单的一段代码,将A,B交给spring管理,在A中属性b上添加一个

@Autowired
,当我们从spring的容器中取出A后发现属性b居然有值,这个不就是自动注入吗?

我是认为添加@Autowired注解是不算自动注入的,原因如下

1.名词解释

首先,什么叫自动( 给翻译翻译什么叫tm的自动 ),生活中肯定都见过自动门,通过过自动门,当我们靠近的时候门会自动打开,通过后门自动关闭,自动就是指我们不需要手动的去开门/关门,那么这里的自动注入也是,我们不需要去手动的在需要注入的属性上添加一个@Autowired注解

2.官方文档

https://docs.spring.io/spring-framework/docs/current/reference/html/

在官方文档的注入方式中,能看到

 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.

Code is cleaner with the DI principle, and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies and does not know the location or class of the dependencies. As a result, your classes become easier to test, particularly when the dependencies are on interfaces or abstract base classes, which allow for stub or mock implementations to be used in unit tests.

DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.

翻译就是

 依赖项注入(DI)是一个过程,对象仅通过构造函数参数、工厂方法的参数或对象实例构造或从工厂方法返回后设置的属性来定义其依赖项(即与它们一起工作的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或位置。

使用DI原则,代码更干净,当对象具有依赖关系时,解耦更有效。对象不查找其依赖项,也不知道依赖项的位置或类别。因此,您的类变得更容易测试,尤其是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。

DI有两种主要变体:基于构造函数的依赖项注入和基于Setter的依赖项注入。

注意最后一段话:有两种 主要 的变体:基于构造和setter方法的依赖注入,也就是说还有其他的注入方式,下面再细说

那么你可能会问?我需要怎么指定或声明让他去使用构造或setter方法进行注入而不是我手动指定@Autowired呢?

自动注入模式

还是spring的官网,网页翻译的

可以看到spring的自动装配模式有下面的4种,而默认的是no,也就是不自动注入

那么会发现自动注入以及DI的主要实现这里面并没有所说的 "@Autowired"

那么我们怎么使用自动注入呢?

在xml配置中,只需要将头定义中加上一句

default-autowire="byType"

 <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"
default-autowire="byType">

而在javaconfig中要麻烦一点,我们需要自定义一个配置类,实现 BeanFactoryPostProcessorBeanDefinitionRegisterPostProcessor 来修改beanDefinition的注入模型

 @Component
public class BeanFactoryPostProcessor implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
ScannedGenericBeanDefinition a =(ScannedGenericBeanDefinition) beanFactory.getBeanDefinition("a");
a.setAutowireMode(2);
}
}

这个2是啥意思呢,在接口 AutowireCapableBeanFactory 中,spring定义了每个注入模型的值

 public interface AutowireCapableBeanFactory extends BeanFactory {
int AUTOWIRE_NO = 0;

int AUTOWIRE_BY_NAME = 1;

int AUTOWIRE_BY_TYPE = 2;

int AUTOWIRE_CONSTRUCTOR = 3;
@Deprecated
int AUTOWIRE_AUTODETECT = 4;
....
}

那么当我们设置了byName或byType后,只需要提供一个setter方法即可,那么只要这个属性名称的bean存在于spring容器中就会被注入

 public interface Test {
}
 @Component
public class TestA implements Test {
}
 @Component
public class A {

@Autowired
private Test testA;

public void setTestA(Test testA) {
System.out.println("走set方法啦");
this.testA = testA;
}

public A() {
System.out.println("走无参构造方法啦");
}

public A(Test testA) {
System.out.println("走有参构造方法啦");
this.testA = testA;
}

@Override
public String toString() {
return "A{" +
"testA=" + testA +
'}';
}
}

结果:

 走无参构造方法啦
走set方法啦
A{testA=com.jame.pojo.test.TestA@721e0f4f}

当我设置为byType后结果一样,就不再粘贴代码了

而当我设置为根据构造注入后,将注入的模型改成3

 @Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
ScannedGenericBeanDefinition a =(ScannedGenericBeanDefinition) beanFactory.getBeanDefinition("a");
a.setAutowireMode(3);
}
}

结果

 走有参构造方法啦
A{testA=com.jame.pojo.test.TestA@28864e92}

那么现在你会明白了所谓的@Autowired并不是自动注入,只要指定了这个bean的注入模型为byType/byName/构造后才是自动注入

@Autowired

@Autowired是使用setter注入是构造注入呢?是使用byName还是byType呢?

回答第一个问题:@Autowired是使用setter注入是构造注入呢?

将我的配置类 BeanFactoryPostProcessor 上的@Component注解去掉,然后在Test testA添加@Autowired

 @Component
public class A {


@Autowired
private Test testA;

//下面的代码和上面粘贴出的的代码一样

}

结果

 走无参构造方法啦
A{testA=com.jame.pojo.test.TestA@546a03af}

??什么情况,setter和构造都没走,因为@Autowired底层使用的反射filed.set()来填充的属性

DI有两种主要变体:基于构造函数的依赖项注入和基于Setter的依赖项注入

主要的是使用构造和setter,而其他的说的就是这种@Autowired的注入方式

第二个问题:是使用byName还是byType呢?

答案是两个都是,或者两个都不是,来看例子

 public interface Test {
}
 @Component
public class TestA implements Test {
}
 @Component
public class A {
@Autowired
private Test test;//注意这里的属性为test

....省略
}

这样写然后从spring容器中获取肯定能获取到结果,A中的testA属性肯定有值的,就不演示了

但是,我给Test接口添加一个实现类TestB

 @Component
public class TestB implements Test{

}

继续执行代码,发现报错了

 Error creating bean with name 'a': Unsatisfied dependency expressed through field 'test'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.jame.pojo.test.Test' available: expected single matching bean but found 2: testA,testB 
 创建名称为“a”的 bean 时出错:通过字段“test”表示的依赖关系不满足;嵌套异常是 org.springframework.beans.factory.NoUniqueBeanDefinitionException:没有“com.jame.pojo.test.Test”类型的合格 bean 可用:预期单个匹配 bean,但找到 2:testA,testB 

到这里是不是就可以说明@Autowired是byType呢?,那怎么证明它也是byName呢?

修改A中的Test test属性为testA时

 @Component
public class A {


@Autowired
private Test testA;//注意这里的属性为testA

.....省略
}

继续执行代码发现又可以了

 走无参构造方法啦
A{testA=com.jame.pojo.test.TestA@28864e92}

我们把属性改为Test testB后继续测试

 走无参构造方法啦
A{testB=com.jame.pojo.test.TestB@28864e92}

发现也是可以的,那么结论就是:@Autowired既是byType也是byName

当Spring容器中只有一个匹配的类时,会根据Type直接注入,而存在多个匹配的时候(接收的属性定义为接口,有多个实现类),会直接抛出异常

这时候我们可以使用@Qualifier("testA")来指定具体哪一个类来注入

或者修改属性的名字为需要注入类的名称(首字母小写)

测试了一下@Qualifier()是高于byName的,也就是说即使存在两个匹配的类,即使属性名叫testB,我只要使用@Qualifier("testA")来指定bean,那么注入的就是testA

芜湖没了,拜拜

标签: Java

添加新评论