关注「Java视界」公众号,获取更多技术干货

属性值注入、bean的注入方式详解!

属性值的注入利用@Value注解完成;@Autowired、@Resource、@Inject注解用于完成bean的注入,不同的是@Resource、@Inject是java提供的注解,而@Autowired是由spring提供,配合@Qualifier和@Primary注解可以实现灵活注入组件。

一、@Value

使用@Value注解可以为属性注入相关的值,注入的方式有三种:

  1. 注入静态值
  2. SpEL表达式:#{}
  3. ${},获取配置文件中的值(或者运行环境中的值)
@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
  1.  ApplicationContextAware接口的setApplicationContext()就是在自定义的bean中注入spring容器
  2.  BeanNameAware接口的setBeanName()就是在自定义bean中设置该bean的名称
  3. EmbeddedValueResolverAware接口的setEmbeddedValueResolver()是来解析配置文件中的配置信息
  4. ....

可以看到,要想在自定义的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包中提供了四个注解,仅用于编写注解时使用,他们是:

 

下面我们开始自己实现一个注解,注解仅支持 primitivesstring和 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对象定义为单例,这样就能从功能上实现与静态定义相同的目的。

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(327)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货