关于java:Autowired背后实现的原理你都知道吗

54次阅读

共计 12060 个字符,预计需要花费 31 分钟才能阅读完成。

前言

应用 spring 开发时,进行配置次要有两种形式,一是 xml 的形式,二是 java config 的形式。

spring 技术本身也在一直的倒退和扭转,从以后 springboot 的炽热水平来看,java config 的利用是越来越宽泛了,在应用 java config 的过程当中,咱们不可避免的会有各种各样的注解打交道,其中,咱们应用最多的注解应该就是 @Autowired 注解了。这个注解的性能就是为咱们注入一个定义好的 bean。

那么,这个注解除了咱们罕用的属性注入形式之外还有哪些应用形式呢?它在代码层面又是怎么实现的呢?这是本篇文章着重想探讨的问题。

@Autowired 注解用法

在剖析这个注解的实现原理之前,咱们无妨先来回顾一下 @Autowired 注解的用法。

将 @Autowired 注解利用于构造函数,如以下示例所示

public class MovieRecommender {
 
    private final CustomerPreferenceDao customerPreferenceDao;
 
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}
 
    // ...
}

将 @Autowired 正文利用于 setter 办法

public class SimpleMovieLister {
 
    private MovieFinder movieFinder;
 
    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}
 
    // ...
}

将 @Autowired 正文利用于具备任意名称和多个参数的办法

public class MovieRecommender {
 
    private MovieCatalog movieCatalog;
 
    private CustomerPreferenceDao customerPreferenceDao;
 
    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }
 
    // ...
}

您也能够将 @Autowired 利用于字段,或者将其与构造函数混合,如以下示例所示

public class MovieRecommender {
 
    private final CustomerPreferenceDao customerPreferenceDao;
 
    @Autowired
    private MovieCatalog movieCatalog;
 
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;}
 
    // ...
}

间接利用于字段是咱们应用的最多的一种形式,然而应用构造方法注入从代码层面却是更加好的。除此之外,还有以下不太常见的几种形式

将 @Autowired 正文增加到须要该类型数组的字段或办法,则 spring 会从 ApplicationContext 中搜查合乎指定类型的所有 bean,如以下示例所示:

public class MovieRecommender {
 
    @Autowired
    private MovieCatalog[] movieCatalogs;
 
    // ...
}

数组能够,咱们能够马上触类旁通,那容器也能够吗,答案是必定的,上面是 set 以及 map 的例子:

public class MovieRecommender {
 
    private Set<MovieCatalog> movieCatalogs;
 
    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}
 
    // ...
}
public class MovieRecommender {
 
    private Map<String, MovieCatalog> movieCatalogs;
 
    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}
 
    // ...
}

以上就是 @Autowired 注解的次要应用形式,常常应用 spring 的话应该对其中罕用的几种不会感到生疏。

@Autowired 注解的作用到底是什么

@Autowired 这个注解咱们常常在应用,当初,我想问的是,它的作用到底是什么呢?

首先,咱们从所属范畴来看,事实上这个注解是属于 spring 的容器配置的一个注解,与它同属容器配置的注解还有:@Required,@Primary, @Qualifier 等等。因而 @Autowired 注解是一个用于容器 (container) 配置的注解。

其次,咱们能够间接从字面意思来看,@autowired 注解来源于英文单词 autowire, 这个单词的意思是主动拆卸的意思。主动拆卸又是什么意思?这个词语原本的意思是指的一些工业上的用机器代替人口,主动将一些须要实现的组装工作,或者别的一些工作实现。而在 spring 的世界当中,主动拆卸指的就是应用将 Spring 容器中的 bean 主动的和咱们须要这个 bean 的类组装在一起。

因而,笔者集体对这个注解的作用下的定义就是: 将 Spring 容器中的 bean 主动的和咱们须要这个 bean 的类组装在一起协同应用。

接下来,咱们就来看一下这个注解背地到底做了些什么工作。

@Autowired 注解是如何实现的

事实上,要答复这个问题必须先弄明确的是 java 是如何反对注解这样一个性能的。

java 的注解实现的核心技术是反射,让咱们通过一些例子以及本人实现一个注解来了解它工作的原理。

例如注解 @Override

@Override 注解的定义如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override 注解应用 java 官网提供的注解,它的定义外面并没有任何的实现逻辑。留神,所有的注解简直都是这样的,注解只能是被看作元数据,它不蕴含任何业务逻辑。 注解更像是一个标签,一个申明,外表被正文的这个中央,将具备某种特定的逻辑。

那么,问题接踵而至,注解自身不蕴含任何逻辑,那么注解的性能是如何实现的呢?答案必然是别的某个中央对这个注解做了实现。以 @Override 注解为例,他的性能是重写一个办法,而他的实现者就是 JVM,java 虚拟机,java 虚拟机在字节码层面实现了这个性能。

然而对于开发人员,虚拟机的实现是无法控制的货色,也不能用于自定义注解。所以,如果是咱们本人想定义一个举世无双的注解的话,则咱们须要本人为注解写一个实现逻辑,换言之,咱们须要实现本人注解特定逻辑的性能。

本人实现一个注解

在本人写注解之前咱们有一些基础知识须要把握,那就是咱们写注解这个性能首先是须要 java 反对的,java 在 jdk5 当中反对了这一性能,并且在 java.lang.annotation 包中提供了四个注解,仅用于编写注解时应用,他们是:

上面咱们开始本人实现一个注解,注解仅反对 primitivesstring和 enumerations这三种类型。注解的所有属性都定义为办法,也能够提供默认值。咱们先实现一个最简略的注解。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@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 和他最大的区别应该仅仅在于注解的实现逻辑,其余利用反射获取注解等等步骤应该都是统一的。先来看一下 @Autowired 这个注解在 spring 的源代码里的定义是怎么的,如下所示:

package org.springframework.beans.factory.annotation;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {boolean required() default true;
}

浏览代码咱们能够看到,Autowired 注解能够利用在构造方法,一般办法,参数,字段,以及注解这五种类型的中央,它的保留策略是在运行时。上面,咱们不多说间接来看 spring 对这个注解进行的逻辑实现.

在 Spring 源代码当中,Autowired 注解位于包 org.springframework.beans.factory.annotation 之中,该包的内容如下:

通过剖析,不难发现 Spring 对 autowire 注解的实现逻辑位于类:AutowiredAnnotationBeanPostProcessor之中, 已在上图标红。其中的外围解决代码如下:

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 办法其实现逻辑如下:

/**
 * Either this or {@link #getResourceToInject} needs to be overridden.
 */
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;

那么在这里实现的就相当于给这个 objecTest 援用赋值了。

对于办法的话,实质就是去调用这个办法,因而这里调用的是 method.invoke.

getResourceToInject 办法的参数就是要注入的 bean 的名字,这个办法的性能就是依据这个 bean 的名字去拿到它。

以上,就是 @Autowire 注解实现逻辑的全副剖析。联合源代码再看一遍的话,会更加分明一点。上面是 spring 容器如何实现 @AutoWired 主动注入的过程的图:

总结起来一句话:应用 @Autowired 注入的 bean 对于指标类来说,从代码构造上来讲也就是一个一般的成员变量,@Autowired 和 spring 一起工作,通过反射为这个成员变量赋值,也就是将其赋为冀望的类实例。

问题

注解的无效周期是什么?

各种正文之间的第一个次要区别是,它们是在编译时应用,而后被抛弃(如 @Override),还是被放在编译的类文件中,并在运行时可用(如 Spring 的 @Component)。这是由正文的“@Retention”策略决定的。如果您正在编写本人的正文,则须要决定该正文在运行时(可能用于主动配置)还是仅在编译时(用于查看或代码生成)有用。

当用正文编译代码时,编译器看到正文就像看到源元素上的其余修饰符一样,比方拜访修饰符(public/private)或.。当遇到正文时,它运行一个正文处理器,就像一个插件类,示意对特定的正文感兴趣。正文处理器通常应用反射 API 来查看正在编译的元素,并且能够简略地对它们执行查看、批改它们或生成要编译的新代码。

@Override 是一个示例;它应用反射 API 来确保可能在其中一个超类中找到办法签名的匹配,如果不能,则应用 @Override 会导致编译谬误。

注入的 bean 和用它的 bean 的关系是如何保护的?

无论以何种形式注入,注入的 bean 就相当于类中的一个一般对象利用,这是它的实例化是 spring 去容器中找合乎的 bean 进行实例化,并注入到类当中的。他们之间的关系就是一般的一个对象持有另一个对象援用的关系。只是这些对象都是 spring 当中的 bean 而已。

为什么注入的 bean 不能被定义为 static 的?

从设计的角度来说,应用动态字段会激励应用静态方法。静态方法是 evil 的。依赖注入的次要目标是让容器为您创建对象并进行连贯。而且,它使测试更加容易。

一旦开始应用静态方法,您就不再须要创建对象的实例,并且测试变得更加艰难。同样,您不能创立给定类的多个实例,每个实例都注入不同的依赖项(因为该字段是隐式共享的,并且会创立全局状态)。

动态变量不是 Object 的属性,而是 Class 的属性。spring 的 autowire 是在对象上实现的,这样使得设计很洁净。在 spring 当中咱们也能够将 bean 对象定义为单例,这样就能从性能上实现与动态定义雷同的目标。

然而从纯正技术的层面,咱们能够这样做:

将 @Autowired 能够与 setter 办法一起应用,而后能够让 setter 批改动态字段的值。然而这种做法十分不举荐。

正文完
 0