属性值注入、bean的注入方式详解!
属性值的注入利用@Value注解完成;@Autowired、@Resource、@Inject注解用于完成bean的注入,不同的是@Resource、@Inject是java提供的注解,而@Autowired是由spring提供,配合@Qualifier和@Primary注解可以实现灵活注入组件。
一、@Value
使用@Value注解可以为属性注入相关的值,注入的方式有三种:
- 注入静态值
- SpEL表达式:#{}
- ${},获取配置文件中的值(或者运行环境中的值)
@Data
public class Person {
private String name;
private Integer age;
private String sex;
}
/**
* @Value 注解
* 1、注入静态值
* 2、SpEL表达式:#{}
* 3、${},获取配置文件中的值(或者运行环境中的值)
*/
@Data
public class Person01 {
@Value("狗蛋") // 注入静态值
private String name;
@Value("#{100-90}") // SpEL表达式
private Integer age;
@Value("${person.sex}") // 获取配置文件中的值
private String sex;
}
@Configuration
public class Config01 {
@Bean("person-value")
public Person person(){
return new Person();
}
@Bean("person-value01")
public Person01 person01(){
return new Person01();
}
}
@RestController
@RequestMapping("/value")
public class ValueController implements ApplicationContextAware {
private WebApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = (WebApplicationContext) applicationContext;
}
/**
* 未使用@Value注解时,这里的bean的属性值都是空
*/
@GetMapping("/test")
public Person test() {
Person person = context.getBean(Person.class);
Console.log(person);
return person;
}
/**
* 使用@Value注解时,这里的bean的属性值已经绑定相应的值
*/
@GetMapping("/test1")
public Person01 test1() {
String property = context.getEnvironment().getProperty("person.sex");
Console.log("重环境对象中获取的配置信息:" + property);
Person01 person = context.getBean(Person01.class);
Console.log(person);
return person;
}
}
重环境对象中获取的配置信息:男
Person01(name=狗蛋, age=10, sex=男)
二、@Autowired
@Data
@Repository
public class TestDAO {
private int label = 1;
}
@Service
@ToString
public class TestService {
@Autowired
private TestDAO testDAO;
}
/**
* @Autowired 注解
* 1、可以看到该注解可以为TestService注入TestDAO
*/
@Test
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutowiredConfig.class);
TestService bean = applicationContext.getBean(TestService.class);
Console.log(bean);
}
TestService(testDAO=TestDAO(label=1))
可以看到TestService有个TestDAO类型的属性,即TestService依赖TestDAO,而@Autowired注解可以为TestService注入TestDAO。
那如果容器中有多个TestDAO类型的bean,使用@Autowired注解会为TestService注入哪一个TestDAO类型的bean呢?如下:
@Data
@Repository
public class TestDAO {
private int label = 1;
}
@Configuration
@ComponentScan({"fengge.config.autowired"})
public class AutowiredConfig {
@Bean("testDAO02")
public TestDAO testDAO(){
TestDAO testDAO02 = new TestDAO();
testDAO02.setLabel(2);
return testDAO02;
}
}
@Service
@ToString
public class TestService {
@Autowired
private TestDAO testDAO;
}
/**
* @Autowired 注解
* 1、如果有两个同类型bean,在getBean(注释掉的代码)时会报 expected single matching bean but found 2: testDAO,testDAO02 错误
* 2、在有多个同类型bean时,根据注入的属性名称进行匹配,这里TestService中的TestDAO类型的属性名称是testDAO,所以注入的是label=1的bean
*/
@Test
public void test1() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutowiredConfig.class);
TestService bean = applicationContext.getBean(TestService.class);
Console.log(bean);
// TestDAO bean1 = applicationContext.getBean(TestDAO.class);
// Console.log(bean1);
}
TestService(testDAO=TestDAO(label=1))
以上使得容器中有两个TestDAO类型的bean(testDAO、testDAO02),在给TestService注入时,看控制台的输出结果,注入的是testDAO。
原因是TestService的属性名是testDAO,默认匹配的bean的名称是和这个属性名一致的,所以这里注入的是testDAO这个bean。
若这里想注入testDAO02这个bean,只需要将TestService属性名改成testDAO02即可:
@Service
@ToString
public class TestService {
@Autowired
private TestDAO testDAO02;
}
TestService(testDAO02=TestDAO(label=2))
那这样处理是不是最合适的呢?显然不是,每次某个类型的bean有多个时,想注入需要的bean还要去修改代码,多不智能。下面介绍下解决方法。
三、@Qualifier
@Qualifier 注解用来指定想要注入的bean:
@Data
@Repository
public class TestDAO {
private int label = 1;
}
@Service
@ToString
public class TestService01 {
@Qualifier("testDAO02")
@Autowired
private TestDAO testDAO;
}
/**
* @Qualifier 注解: 在有多个同类型bean时,@Qualifier 可以指定要注入哪个bean
* 1、这里在 TestService01 中设置了要注入 testDAO02,所以即使这里属性名称是 testDAO,注入的也是 testDAO02
*/
@Test
public void test3() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutowiredConfig01.class);
TestService01 bean = applicationContext.getBean(TestService01.class);
Console.log(bean);
}
TestService01(testDAO=TestDAO(label=2))
这里在 TestService01 中设置了要注入 testDAO02,所以即使这里属性名称是 testDAO,注入的也是 testDAO02。
四、@Primary
@Primary 可以指定要优先注入哪个bean。
@Data
@Repository
public class TestDAO {
private int label = 1;
}
@Configuration
@ComponentScan({"fengge.config.autowired"})
public class AutowiredConfig02 {
@Primary
@Bean("testDAO02")
public TestDAO testDAO(){
TestDAO testDAO02 = new TestDAO();
testDAO02.setLabel(2);
return testDAO02;
}
}
/**
* @Primary 注解: 在有多个同类型bean时,@Primary 可以指定要优先注入哪个bean
* 1、这里在 AutowiredConfig02 中设置了要注入 testDAO02,则优先注入的也是 testDAO02
*/
@Test
public void test4() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutowiredConfig02.class);
TestService01 bean = applicationContext.getBean(TestService01.class);
Console.log(bean);
}
TestService01(testDAO=TestDAO(label=2))
五、@Resource
@Resource 注解:和 @Autowired 注解 一样的功能,只是它是JSR250提供的注解 。
@Data
@Repository
public class TestDAO {
private int label = 1;
}
@Service
@ToString
public class TestService03 {
@Resource
private TestDAO testDAO;
}
@Configuration
@ComponentScan({"fengge.config.autowired"})
public class AutowiredConfig04 {
}
/**
* @Resource 注解:和 @Autowired 注解 一样,只是它是JSR250提供的注解
*/
@Test
public void test6() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutowiredConfig04.class);
TestService03 bean = applicationContext.getBean(TestService03.class);
Console.log(bean);
}
TestService03(testDAO=TestDAO(label=1))
六、 @Inject
@Inject 注解:和 @Autowired 注解 一样,只是它是JSR330提供的注解,在javax.inject.Inject包。
@Data
@Repository
public class TestDAO {
private int label = 1;
}
@Service
@ToString
public class TestService04 {
@Inject
private TestDAO testDAO;
}
@Configuration
@ComponentScan({"fengge.config.autowired"})
public class AutowiredConfig04 {
}
/**
* @Inject 注解:和 @Autowired 注解 一样,只是它是JSR330提供的注解
*/
@Test
public void test7() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutowiredConfig04.class);
TestService04 bean = applicationContext.getBean(TestService04.class);
Console.log(bean);
}
TestService04(testDAO=TestDAO(label=1))
@Resource和@Inject是java提供的注解,@Autowired是spring提供的注解。@Autowired更加灵活通用,建议使用。
七、自定义bean注入spring容器中的组件
想把spring容器中的一些底层组件注入自定义的bean,就需要实现对应的xxxAware接口。例如:
public class Ship implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
private String name;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
Console.log("[0]" + applicationContext);
}
@Override
public void setBeanName(String s) {
this.name = s;
Console.log("[1]" + s);
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("哈哈哈${person.sex}#{90-10}");
Console.log("[2]" + resolver);
Console.log("[3]" + s);
}
}
@Configuration
public class SelfConfig {
@Bean
public Ship person(){
return new Ship();
}
}
@GetMapping("/test2")
public Ship test2() {
Ship bean = context.getBean(Ship.class);
Console.log(bean);
return bean;
}
[1]person
[2]org.springframework.beans.factory.config.EmbeddedValueResolver@4fe1b0c5
[3]哈哈哈男80
[0]org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@694f3c94, started on Tue May 18 20:48:42 CST 2021, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@52d8d03b
- ApplicationContextAware接口的setApplicationContext()就是在自定义的bean中注入spring容器
- BeanNameAware接口的setBeanName()就是在自定义bean中设置该bean的名称
- EmbeddedValueResolverAware接口的setEmbeddedValueResolver()是来解析配置文件中的配置信息
- ....
可以看到,要想在自定义的bean中注入spring容器相关的组件就需要实现相应的xxxAware接口,这些接口均来自Aware接口。
补充一:@Autowired 注解 不仅可以注解在属性上,还可以注解在 方法、参数、构造方法上
1、在方法上
@Data
@Repository
public class TestDAO {
private int label = 1;
}
@Service
@ToString
public class TestService05 {
private TestDAO testDAO;
@Autowired
public void setTestDAO(TestDAO testDAO) {
this.testDAO = testDAO;
}
}
@Configuration
@ComponentScan({"fengge.config.autowired"})
public class AutowiredConfig04 {
}
/**
* @Autowired 注解 不仅可以注解在属性上,还可以注解在 方法、参数、构造方法上
* 1、在哪里效果是一样的
*/
@Test
public void test8() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AutowiredConfig04.class);
TestService05 bean = applicationContext.getBean(TestService05.class);
Console.log(bean);
}
TestService05(testDAO=TestDAO(label=1))
2、在构造方法上
其他不变,只是把放在构造方法上:
@Service
@ToString
public class TestService05 {
private TestDAO testDAO;
@Autowired
public TestService05(TestDAO testDAO) {
this.testDAO = testDAO;
}
}
TestService05(testDAO=TestDAO(label=1))
3、在参数上
其他不变,只是把放在方法参数上:
构造方法参数:
@Service
@ToString
public class TestService05 {
private TestDAO testDAO;
public TestService05(@Autowired TestDAO testDAO) {
this.testDAO = testDAO;
}
}
TestService05(testDAO=TestDAO(label=1))
特别地,只有一个参数则@Autowired注解可以省略不写,效果是一样的:
@Service
@ToString
public class TestService05 {
private TestDAO testDAO;
public TestService05(TestDAO testDAO) {
this.testDAO = testDAO;
}
}
TestService05(testDAO=TestDAO(label=1))
无论@Autowired放在方法、参数还是构造方法上,都是从spring容器中获取需要的bean组件。
补充二、注解是怎么实现它的功能逻辑的?
java的注解实现的核心技术是反射,让我们通过一些例子以及自己实现一个注解来理解它工作的原理。
例如注解@Override,定义如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
它的定义里面并没有任何的实现逻辑。注意,所有的注解几乎都是这样的,注解只能是被看作元数据,它不包含任何业务逻辑。 注解更像是一个标签,一个声明,表明被注释的这个地方,将具有某种特定的逻辑。
注解本身不包含任何逻辑,那么注解的功能是如何实现的呢?答案必然是别的某个地方对这个注解做了实现。以@Override注解为例,它的功能是重写一个方法,而它的实现者就是JVM,java虚拟机,java虚拟机在字节码层面实现了这个功能。
但是对于开发人员,虚拟机的实现是无法控制的东西,也不能用于自定义注解。所以,如果是我们自己想定义一个独一无二的注解的话,则我们需要自己为注解写一个实现逻辑,换言之,我们需要实现自己注解特定逻辑的功能。
我们写注解这个功能首先是需要java支持的,java在jdk5当中支持了这一功能,并且在java.lang.annotation
包中提供了四个注解,仅用于编写注解时使用,他们是:
下面我们开始自己实现一个注解,注解仅支持 primitives
, string
和 enumerations
这三种类型。注解的所有属性都定义为方法,也可以提供默认值。我们先实现一个最简单的注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleAnnotation {
String value();
}
上面这个注释里面只定义了一个字符传,它的目标注释对象是方法,保留策略是在运行期间。下面我们定义一个方法来使用这个注解:
public class UseAnnotation {
@SimpleAnnotation("testStringValue")
public void testMethod(){
//do something here
}
}
我们在这里使用了这个注解,并把字符串赋值为:testStringValue,到这里,定义一个注解并使用它,我们就已经全部完成。
我们虽然写了一个注解也用了它,可是它并没有产生任何作用啊。也没有对我们这里方法产生任何效果啊。是的现在确实是这样的,原因在于我们前面提到的一点,我们还没有为这个注解实现它的逻辑,现在我们就来为这个注解实现逻辑。
首先,我想给标注了这个注解的方法或字段实现功能,我们必须得知道,到底有哪些方法,哪些字段使用了这个注解吧,因此,这里我们很容易想到,这里应该会用到反射。
其次,利用反射,我们利用反射拿到这样目标之后,得为他实现一个逻辑,这个逻辑是这些方法本身逻辑之外的逻辑,这又让我们想起了代理,aop等知识,我们相当于就是在为这些方法做一个增强。事实上的实现主借的逻辑也大概就是这个思路。梳理一下大致步骤如下:
-
利用反射机制获取一个类的Class对象
-
通过这个class对象可以去获取他的每一个方法method,或字段Field等等
-
Method,Field等提供了类似于getAnnotation的方法来获取这个一个字段的所有注解
-
拿到注解之后,我们可以判断这个注解是否是我们要实现的注解,如果是则实现注解逻辑
private static void annotationLogic() {
Class useAnnotationClass = UseAnnotation.class;
for(Method method : useAnnotationClass.getMethods()) {
SimpleAnnotation simpleAnnotation = (SimpleAnnotation)method.getAnnotation(SimpleAnnotation.class);
if(simpleAnnotation != null) {
System.out.println(" Method Name : " + method.getName());
System.out.println(" value : " + simpleAnnotation.value());
System.out.println(" --------------------------- ");
}
}
}
在这里我们实现的逻辑就是打印几句话。从上面的实现逻辑我们不能发现,借助于java的反射我们可以直接拿到一个类里所有的方法,然后再拿到方法上的注解,当然,我们也可以拿到字段上的注解。借助于反射我们可以拿到几乎任何属于一个类的东西。
补充三、@Autowired注解是如何实现的?
上面补充二已经实现完了自己自定义注解的逻辑,现在我们再回过头来,看一下@Autowired注解是如何实现的。
- 我们查看@Autowired注解的具体实现逻辑,而
AutowiredAnnotationBeanPostProcessor
自动装配后置处理器就是用来实现@Autowired注解的具体逻辑的。- 在
AutowiredAnnotationBeanPostProcessor
类的方法中会获取bean的所有属性和方法,判断其是否使用了@Autowired注解,并将需要依赖注入的属性信息封装到InjectionMetadata
类中。InjectionMetadata类中包含了哪些需要注入的元素及元素要注入到哪个目标类中。- 最后就是将属性信息对应的bean注入进行,创建bean时(实例化对象和初始化),会调用各种
BeanPostProcessor
对bean初始化,AutowiredAnnotationBeanPostProcessor
负责将相关的依赖注入进来。
先来看一下@Autowired这个注解在spring的源代码里的定义是怎样的,如下所示:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
在Spring源代码当中,Autowired注解位于包org.springframework.beans.factory.annotation
之中,该包的内容如下:
其中的核心处理代码如下:
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();
Class<?> targetClass = clazz;//需要处理的目标类
do {
final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();
/*通过反射获取该类所有的字段,并遍历每一个字段,并通过方法findAutowiredAnnotation遍历每一个字段的所用注解,并如果用autowired修饰了,则返回auotowired相关属性*/
ReflectionUtils.doWithLocalFields(targetClass, field -> {
AnnotationAttributes ann = findAutowiredAnnotation(field);
if (ann != null) {//校验autowired注解是否用在了static方法上
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation is not supported on static fields: " + field);
}
return;
}//判断是否指定了required
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
//和上面一样的逻辑,但是是通过反射处理类的method
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
//用@Autowired修饰的注解可能不止一个,因此都加在currElements这个容器里面,一起处理
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return new InjectionMetadata(clazz, elements);
}
最后这个方法返回的就是包含所有带有autowire注解修饰的一个InjectionMetadata集合。这个类由两部分组成:
public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
this.targetClass = targetClass;
this.injectedElements = elements;
}
一是我们处理的目标类,二就是上述方法获取到的所以elements集合。
有了目标类,与所有需要注入的元素集合之后,我们就可以实现autowired的依赖注入逻辑了,实现的方法如下:
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
它调用的方法是InjectionMetadata中定义的inject方法,如下:
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
}
其逻辑就是遍历,然后调用inject方法,inject方法其实现逻辑如下:
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
throws Throwable {
if (this.isField) {
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
}
else {
if (checkPropertySkipping(pvs)) {
return;
}
try {
Method method = (Method) this.member;
ReflectionUtils.makeAccessible(method);
method.invoke(target, getResourceToInject(target, requestingBeanName));
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
在这里的代码当中我们也可以看到,是inject也使用了反射技术并且依然是分成字段和方法去处理的。在代码里面也调用了makeAccessible这样的可以称之为暴力破解的方法,但是反射技术本就是为框架等用途设计的,这也无可厚非。
对于字段的话,本质上就是去set这个字段的值,即对对象进行实例化和赋值,例如下面代码:
@Autowired
ObjectTest objectTest;
对于方法的话,本质就是去调用这个方法,因此这里调用的是method.invoke.
getResourceToInject方法的参数就是要注入的bean的名字,这个方法的功能就是根据这个bean的名字去拿到它。
总结起来一句话:使用@Autowired注入的bean对于目标类来说,从代码结构上来讲也就是一个普通的成员变量,@Autowired和spring一起工作,通过反射为这个成员变量赋值,也就是将其赋为期望的类实例。
以上,就是@Autowire注解实现逻辑的全部分析。
补充四、为什么注入的bean不能被定义为static的?
从设计的角度来说 ,使用静态字段会鼓励使用静态方法。静态方法是evil的。依赖注入的主要目的是让容器为您创建对象并进行连接。而且,它使测试更加容易。
一旦开始使用静态方法,您就不再需要创建对象的实例,并且测试变得更加困难。同样,您不能创建给定类的多个实例,每个实例都注入不同的依赖项(因为该字段是隐式共享的,并且会创建全局状态)。
静态变量不是Object的属性,而是Class的属性。spring的autowire是在对象上完成的,这样使得设计很干净。 在spring当中我们也可以将bean对象定义为单例,这样就能从功能上实现与静态定义相同的目的。