zoukankan      html  css  js  c++  java
  • 不建议现场注入– Spring IOC

    介绍

    在运行静态代码分析工具或从IDE检查/分析代码时,您可能会遇到有关@Autowired字段的以下警告

    不建议现场注入

    不建议现场注入

    这篇文章展示了Spring中可用的不同类型的注入,以及推荐使用每种注入的模式。

    注射类型

    尽管当前有关Spring Framework (5.0.3)的文档仅定义了两种主要的注入类型,但实际上有三种:

    • 基于构造函数的依赖注入
    • 基于Setter的依赖注入
    • 基于字段的依赖注入

    后者是静态代码分析工具所抱怨的一种,但是它被定期且广泛地使用。

    您甚至可以在一些Spring指南中看到这种注入方法,尽管在文档中不建议这样做:

    带自动接线字段的弹簧导向

    基于构造函数的依赖注入

    在基于构造函数的依赖关系注入中,类构造函数使用注释,@Autowired并随要注入的对象包含可变数量的参数。

    基于构造函数的注入的主要优点是,您可以将注入的字段声明为final,因为它们将在类实例化期间启动。这对于所需的依赖关系很方便。

    基于Setter的依赖注入

    在基于setter的依赖项注入中,setter方法用注释@Autowired一旦使用无参数构造函数或无参数静态工厂方法实例化Bean,Spring容器就会调用这些setter方法,以注入Bean的依赖关系。

    基于字段的依赖注入

    在基于字段的依赖项注入中,字段/属性用注释@Autowired类实例化后,Spring容器将设置这些字段。

    如您所见,这是注入依赖项的最干净的方法,因为它避免了添加样板代码,并且无需为该类声明构造函数。代码看起来不错,简洁,简洁,但是正如代码检查员已经向我们暗示的那样,这种方法存在一些缺点。

    基于字段的依赖注入缺点

    禁止使用不可变的字段声明

    基于字段的依赖项注入不适用于声明为final / immutable的字段,因为必须在类实例化时实例化此字段。声明不可变依赖性的唯一方法是使用基于构造函数的依赖性注入。

    减轻违反单一责任原则的情况

    如您所知,在面向对象的计算机编程中,SOLID的首字母缩写定义了五个设计原则,这些原则将使您的代码易于理解,灵活和可维护。

    SOLID中S代表单一责任原则,这意味着类仅应负责软件应用程序功能的单个部分,并且其所有服务应与该责任狭义地保持一致。

    使用基于字段的依赖项注入,在类中拥有很多依赖项确实很容易,而且一切看起来都很好。如果改用基于构造函数的依赖注入,则随着更多的依赖关系添加到您的类中,构造函数变得越来越大,并且代码开始散发出臭味,发出明确的信号表明有问题。

    拥有超过十个参数的构造函数是一个明显的信号,表明该类有太多的协作者,这也许是个很好的时机,可以开始将类拆分为更小和更可维护的部分。

    因此,尽管现场注入并不能直接导致违反单一责任原则,但可以肯定的是,它可以通过隐藏信号来提供足够的帮助,否则这些信号将是很清楚的。

    与依赖注入容器紧密耦合

    使用基于字段的注入的主要原因是避免获取器和设置器的样板代码或为类创建构造函数。最后,这意味着可以设置这些字段的唯一方法是通过Spring容器实例化该类并使用反射将其注入,否则这些字段将保持为空,并且您的类将被破坏/无用。

    依赖项注入设计模式将类依赖关系的创建与类本身分开,从而将此责任传递给类注入器,从而使程序设计可以松散耦合,并遵循单责任和依赖关系反转原则(再次为SOLID)。因此最后,通过再次与类注入器(在本例中为Spring)耦合,使通过自动装配其字段的类实现的解耦丢失,从而使该类在Spring容器之外无效。

    这意味着,如果您想在应用程序容器之外使用类,例如用于单元测试,则由于没有其他可能的方法(除了反射)来设置自动装配字段,您不得不使用Spring容器来实例化您的类。

    隐藏的依赖

    在使用依赖项注入模式时,受影响的类应通过使用构造函数公开必需的依赖项或使用方法(设置程序)公开可选的依赖项,以使用公共接口明确公开这些依赖项。使用基于字段的依赖项注入时,该类将这些依赖项固有地隐藏在外部世界中。

    结论

    我们已经看到,由于其许多缺点,无论看起来多么优雅,都应尽可能避免进行基于字段的注入。然后推荐的方法是使用基于构造函数和基于setter的依赖项注入。建议将基于构造函数的注入用于所需的依赖项,以使它们成为不可变的并防止它们为null。对于可选的依赖项,建议使用基于Setter的注入。

    https://blog.marcnuri.com/field-injection-is-not-recommended/

    @Autowired注解注入实现原理

    1.解析@Autowired的类AutowiredAnnotationBeanPostProcessor

    public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware
    

    spring中通过AutowiredAnnotationBeanPostProcessor来解析注入注解为目标注入值。该class实现了InstantiationAwareBeanPostProcessorAdapter,MergedBeanDefinitionPostProcessor接口,重写的方法将在IOC创建bean的时候被调用。

    2. 调用AutowiredAnnotationBeanPostProcessor的方法流程

    刚才已经提到了是spring创建bean的时候调用,即
    当使用DefaultListableBeanFactory来获取想要的bean的时候。如下:
    在这里插入图片描述
    在这里插入图片描述
    调用的是AbstractBeanFactory中的getBean方法,继续追踪源码最后在AbstractAutowireCapableBeanFactory类中的createBean方法中找到了调用解析@autowired的方法:

    在这里插入图片描述

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
    throws BeanCreationException {
    ...
    ...
    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
    if (!mbd.postProcessed) {
    try {
    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); // 调用postProcessMergedBeanDefinition方法
    }
    catch (Throwable ex) {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
    "Post-processing of merged bean definition failed", ex);
    }
    mbd.postProcessed = true;
    }
    }
    ... ...
    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
    populateBean(beanName, mbd, instanceWrapper); // 调用postProcessAfterInstantiation方法postProcessPropertyValues方法
    if (exposedObject != null) {
    exposedObject = initializeBean(beanName, exposedObject, mbd); 
    }
    } 
    ... 
    ...
    

    然后来看AutowiredAnnotationBeanPostProcessor的两个类做了些什么

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    if (beanType != null) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null); // 从缓存中获取InjectionMetadata,未取到则构造注入的metadata
    metadata.checkConfigMembers(beanDefinition);
    }
    }
    private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>();
    Class<?> targetClass = clazz;
    do {
    final LinkedList<InjectionMetadata.InjectedElement> currElements =
    new LinkedList<InjectionMetadata.InjectedElement>();
    // 成员变量
    ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {
    @Override
    public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
    AnnotationAttributes ann = findAutowiredAnnotation(field); // 获取成员变量中带有注入注解(@Autowired,@value,@inject)并加入AnnotationAttributes列表
    if (ann != null) {
    if (Modifier.isStatic(field.getModifiers())) {
    if (logger.isWarnEnabled()) {
    logger.warn("Autowired annotation is not supported on static fields: " + field);
    }
    return;
    }
    boolean required = determineRequiredStatus(ann); // required属性是false or true
    currElements.add(new AutowiredFieldElement(field, required)); // 添加到注入列表
    }
    }
    });
    // 方法
    ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
    @Override
    public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
    Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); // 有桥接方法的返回桥接方法的最原始方法,否则返回自身
    if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
    return;
    }
    AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); // 获取方法上带有注入注解(@Autowired,@value,@inject)并加入AnnotationAttributes列表
    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.getParameterTypes().length == 0) {
    if (logger.isWarnEnabled()) {
    logger.warn("Autowired annotation should only be used on methods with parameters: " +
    method);
    }
    }
    boolean required = determineRequiredStatus(ann); // required属性是false or true
    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); // 获取目标class中某成员拥有读或写方法与桥接方法一直的PropertyDescriptor
    currElements.add(new AutowiredMethodElement(method, required, pd)); // 添加到注入列表
    }
    }
    });
    elements.addAll(0, currElements);
    targetClass = targetClass.getSuperclass(); // 有父类继续解析
    }while (targetClass != null && targetClass != Object.class);
    return new InjectionMetadata(clazz, elements); // 返回需要注入的信息
    }
    

    具体注入postProcessPropertyValues:

    @Override
    public PropertyValues postProcessPropertyValues(
    PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); // 从缓存中获取InjectionMetadata,未取到则构造注入的metadata
    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;
    }
    
    // 注入
    public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
    Collection<InjectedElement> elementsToIterate =
    (this.checkedElements != null ? this.checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
    boolean debug = logger.isDebugEnabled();
    for (InjectedElement element : elementsToIterate) {
    if (debug) {
    logger.debug("Processing injected element of bean '" + beanName + "': " + element);
    }
    element.inject(target, beanName, pvs); // 真实注入
    }
    }
    }
    /**
    * Either this or {@link #getResourceToInject} needs to be overridden.
    */
    protected void inject(Object target, String requestingBeanName, 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();
    }
    }
    }
    

    3. AutowiredAnnotationBeanPostProcessor中注入方法的解析

    看一下postProcessMergedBeanDefinition方法和postProcessPropertyValues干了什么

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    if (beanType != null) {
    	InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null); // 从缓存中获取InjectionMetadata,未取到则构造注入的metadata信息
    	metadata.checkConfigMembers(beanDefinition);//
    }
    }
    // 构造metadata
    private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>();
    Class<?> targetClass = clazz;
    do {
    final LinkedList<InjectionMetadata.InjectedElement> currElements =
    new LinkedList<InjectionMetadata.InjectedElement>();
    // 成员变量
    ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {
    @Override
    public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
    AnnotationAttributes ann = findAutowiredAnnotation(field); // 获取成员变量中带有注入注解(@Autowired,@value,@inject)并加入AnnotationAttributes列表
    if (ann != null) {
    if (Modifier.isStatic(field.getModifiers())) {
    if (logger.isWarnEnabled()) {
    logger.warn("Autowired annotation is not supported on static fields: " + field);
    }
    return;
    }
    boolean required = determineRequiredStatus(ann); // required属性是false or true
    currElements.add(new AutowiredFieldElement(field, required)); // 添加到注入列表
    }
    }
    });
    // 方法
    ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
    @Override
    public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
    Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); // 有桥接方法的返回桥接方法的最原始方法,否则返回自身(注释中表示这样更加安全的去调用方法,跳过桥接的检测)
    if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
    return;
    }
    AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); // 获取方法上带有注入注解(@Autowired,@value,@inject)并加入AnnotationAttributes列表
    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.getParameterTypes().length == 0) {
    if (logger.isWarnEnabled()) {
    logger.warn("Autowired annotation should only be used on methods with parameters: " +
    method);
    }
    }
    boolean required = determineRequiredStatus(ann); // required属性是false or true
    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); // 获取目标class中某成员拥有读或写方法与桥接方法一直的PropertyDescriptor
    currElements.add(new AutowiredMethodElement(method, required, pd)); // 添加到注入列表
    }
    }
    });
    elements.addAll(0, currElements);
    targetClass = targetClass.getSuperclass(); // 有父类继续解析
    }while (targetClass != null && targetClass != Object.class);
    return new InjectionMetadata(clazz, elements); // 返回需要注入的信息
    }
    

    具体注入的方法:postProcessPropertyValues:

    @Override
    public PropertyValues postProcessPropertyValues(
    PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); // 从缓存中获取InjectionMetadata,未取到则构造注入的metadata
    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;
    }
    
    // 注入
    public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
    Collection<InjectedElement> elementsToIterate =
    (this.checkedElements != null ? this.checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
    boolean debug = logger.isDebugEnabled();
    for (InjectedElement element : elementsToIterate) {
    if (debug) {
    logger.debug("Processing injected element of bean '" + beanName + "': " + element);
    }
    element.inject(target, beanName, pvs); // 真实注入
    }
    }
    }
    
    // Field注入,method注入
    /**
    * Either this or {@link #getResourceToInject} needs to be overridden.
    */
    protected void inject(Object target, String requestingBeanName, 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();
    }
    }
    }
    

    4. 构造函数注入

    看到这里可能会产生疑问。@Autowired应该还支持构造方法的注入的啊?仔细想想,AutowiredAnnotationBeanPostProcessor的这两个方法是对非静态field和method的,那对于构造函数来说,其调用一定再这之前(创建bean的时候),而AutowiredAnnotationBeanPostProcessor的determineCandidateConstructors方法为其获取带有注入注解的构造方法(这里不再做解析)。回头看一下创建bean的代码:
    在这里插入图片描述

    createBeanInstance方法中有对构造函数注入的方法,直接看一下源码:

    Protected BeanWrapper autowireConstructor(
    StringbeanName,RootBeanDefinition mbd,@NullableConstructor<?>[]ctors,@NullableObject[]explicitArgs){
    	Return new ConstructorResolver(this).autowireConstructor(beanName,mbd,ctors,explicitArgs);  // 构造实例化
    }
    https://blog.csdn.net/qq_38951372/article/details/88809878
  • 相关阅读:
    thinkphp3.2新部署是错
    淘宝code
    面试感悟----一名3年工作经验的程序员应该具备的技能---转载
    【海量之道】海量之道之SET模型
    看过年人流高峰,浅聊并发之战[架构篇]
    docker启动遇到的问题
    监听数据配置
    Python+requests+unittest+excel实现接口自动化测试框架
    冒泡排序及优化
    jmeter监控的一些插件
  • 原文地址:https://www.cnblogs.com/Chary/p/14231123.html
Copyright © 2011-2022 走看看