【小家Spring】细说Spring IOC容器的自动装配(@Autowired),以及Spring4.0新特性之【泛型依赖注入】的源码级解析(下)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】细说Spring IOC容器的自动装配(@Autowired),以及Spring4.0新特性之【泛型依赖注入】的源码级解析(下)

泛型依赖注入


有了上面的源码解析,详细下面的案例结果,我们是能够猜到的:


// 向容器内注入Bean(此处忽略)
    @Autowired
    private GenericBean<String, Object> objectGenericBean;  // 这样注入报错:说找不到Bean
//
    @Autowired
    private GenericBean objectGenericBean; // 依然报错,但是和上面报错不同,这里报错是找到了2个,匹配不到
// 因此这种情况要特别特别特别的注意:如果字段名不是objectGenericBean,而是objectGeneric,就不会报错了,具体原因参考上文。。。


这里面为了协助理解,附图解释:


image.png


从上图可以看出,如果我们注入的时候不指定的泛型,它就是两个 ,属于通配符。所以能够匹配容器里的同类型的所有的Bean,所以如果筛选不出来only one,那就报错了。(因此,如果容器了只有这一个类型的Bean,那就木有问题,就是它了


接下来再看看这个case:


// 我向容器里只注入一个该类型的Bean,
    @Bean
    public GenericBean objectGeneric() {
        return new GenericBean<Object, Object>("obj1", 2);
    }
// 注入方式一:
    @Autowired
    private GenericBean objectGenericBean; //GenericBean(t=obj1, w=2)  显然,这是最正常不过的注入了
// 注入方式二:
    @Autowired
    private GenericBean<Integer, Integer> integerGenericBean; // GenericBean(t=obj1, w=2) 也没有任何问题,可以正常注入,但需要下面这个情况:
  // 我们可以注入任意泛型标注的(以及不用泛型标注的bean,但是使用时候需要注意)
    @Override
    public Object hello() {
        System.out.println(integerGenericBean);
        //java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
        // 这里不能用Integer直接接收,只能使用Object接受,否则一定报错~
        Integer t = integerGenericBean.getT();
        Integer w = integerGenericBean.getW();
        return "service hello";
    }
// 注入方式三: 看看注入多次,是否是同一个Bean(泛型不同的情况下)
    @Autowired
    private GenericBean<Integer, Integer> integerGenericBean;
    @Autowired
    private GenericBean<String, String> stringGenericBean;
    @Override
    public Object hello() {
        // 这样直接比较会编译报错哦
        //System.out.println(integerGenericBean == stringGenericBean);
        //但是我们这样来看看到底是不是同一个对象
        System.out.println(System.identityHashCode(integerGenericBean)); //72085469
        System.out.println(System.identityHashCode(stringGenericBean)); //72085469
        return "service hello";
    }
因此我们可以大胆的说:注入的就是同一个Bean。哪怕泛型不同,也是同一个对象
毕竟Spring管理的Bean,默认都是单例的


Spring Boot中RedisTemplate<Object, Object>和StringRedisTemplate的注入问题


最近有个小伙伴问我问题,说项目中他们注入RedisTemplate的时候,好像可以随便注入,有的同时注入StringRedisTemplate,有的注入RedisTemplate,有的注入RedisTemplate<String,Object>。。。


相信如果没见到这篇文章之前,很多小伙伴也不能理解,但是有了上面的解释,相信一看就知道啥原因了。看了看他们的配置文件,有自己配置注入的RedisTemplate,且没有给泛型。所以按照上面的例子的说明,我们是可以注入任意泛型的RedisTemplate的,但是使用的时候需要注意~~~~~~~


下面我们来看看,Spring Boot自动为我们注入的情况:


  // 容器中不存在name为redisTemplate的Bean,这个会注入
  @Bean
  @ConditionalOnMissingBean(name = "redisTemplate")
  public RedisTemplate<Object, Object> redisTemplate(
      RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
  // 当前容易没有StringRedisTemplate 类型的(注意不是RedisTemplate类型)Bean,它就会注入
  // 说明一点:StringRedisTemplate 是RedisTemplate的子类
  @Bean
  @ConditionalOnMissingBean 
  public StringRedisTemplate stringRedisTemplate(
      RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }


若我们自己什么都不配置,那么我们只能这么注入,且只能这两种情况。


    // 这样注入是失败的,因为容器中并没有该类型的(泛型类型)的Bean
    //@Autowired
    //private RedisTemplate<Integer, Integer> integerRedisTemplate;
    @Autowired
    private RedisTemplate<Object, Object> objectRedisTemplate;
    @Autowired
    private RedisTemplate<String, String> stringRedisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate2;
    @Test
    public void fun1() {
        System.out.println(objectRedisTemplate); //org.springframework.data.redis.core.RedisTemplate@3b27b497
        // 下面两个Bean,显然其实是同一个Bean,都是SpringBoot为我们配置的StringRedisTemplate
        System.out.println(stringRedisTemplate); //org.springframework.data.redis.core.StringRedisTemplate@b1534d3
        System.out.println(stringRedisTemplate2); //org.springframework.data.redis.core.StringRedisTemplate@b1534d3
    }
//从这里面我们可以看出,Spring IOC容器里是只有两个RedisTemplate的=====


如果我们自己手动注入一个不带泛型的Bean呢?


// 我们自己注入一个Bean  RedisTemplate
    //备注:这里bean名称不要叫为redisTemplate,让SpringBoot也能注入(为了测试)
    //生产环境:建议名称就叫redisTemplate,因为我们既然自定义了,就没必要再多余注入一个了
    @Bean
    public RedisTemplate myRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
// 单元测试一番:
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    public void fun1() {
        applicationContext.getBeansOfType(RedisTemplate.class)
                .forEach((k, v) -> System.out.println(k + "-->" + v));
        //备注:若我们注册Bean名称为redisTemplate,那就只会有两个的
        //myRedisTemplate-->org.springframework.data.redis.core.RedisTemplate@6a818392
        //redisTemplate-->org.springframework.data.redis.core.RedisTemplate@489091bd
        //stringRedisTemplate-->org.springframework.data.redis.core.StringRedisTemplate@512d6e60   
    }

@Autowired和@Resource的区别


直观的错误理解:


@Autowired根据类型进行注入,若没有找到该类型Bean会报错

@Autowired根据类型进行注入, 若出现多个类型的Bean,会报错

@Resource根据名称进入注入


解答这些误解(给出正确答案):

1.@Autowired根据类型进行注入这话没毛病,但是若没有找到该类型的Bean,若设置了属性required=false也是不会报错的


2.@Autowired注入若找到多个类型的Bean,也不会报错,比如下面三种情况,都不会报错~

// 向容器中注入两个相同类型的Bean,并且都不使用@Primary标注
@Configuration
public class RootConfig {
    @Bean
    public Parent parentOne() {
        return new Parent();
    }
    @Bean
    public Parent parentTwo() {
        return new Parent();
    }
}
注入方式如下:
    @Autowired
    private Parent parent;
// 若什么都不处理,会异常:NoUniqueBeanDefinitionException: No qualifying bean of type 'com.fsx.bean.Parent' available: expected single matching bean but found 2: parentOne,parentTwo
// 方案一:向容器注入Bean的时候加上@Primary注解(略)
// 方案二:使用@Qualifier(略)
// 方案三:使得字段名,和容器里的Bean名称一致,比如改成下面字段名,就不会报错了
    @Autowired
    private Parent parentOne;
    @Autowired
    private Parent parentTwo;


关于方案三:因为大多数小伙伴都认为@Autowired注解找到两个Bean就直接报错了,是不被允许的。没想到最后它还会根据字段名进行一次过滤,完全找不到再报错。

因为我使用的Spring版本为:5.0.6.RELEASE 因此有可能是版本原因Spring做了这步处理,这里我也不再去测试从哪个版本开始支持的了,若有小伙伴清楚了,欢迎留言告知,万分感激~


需要说明的是,它和@Qualifier的区别:他们的生效阶段不一样。

@Qualifier:它在寻早同类型的Bean的时候就生效了,在方法findAutowireCandidates这里去寻找候选的Bean的时候就生效了,只会找出一个(或者0个出来)

@Autowired自带的根据字段名匹配:发生在若找出多个同类型Bean的情况下,会根据此字段名称determine一个匹配上的出来


@Resource·装配顺序解释:


  • 如果既没有指定name,又没有指定type,则自动先按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配
  • 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  • 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  • 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常
  • 需要注意的是:@Resource并不支持@Primary

@Resource并不支持@Primary这句话此处做出说明:在Spring4.2之前它是不支持的,但是在4.2之后,@Resource已经全面支持了@Primary以及提供了对@Lazy的支持。


注意:它对@Lazy支持并不是ContextAnnotationAutowireCandidateResolver来处理的,全部由CommonAnnotationBeanPostProcessor这个类里面的方法处理


它的核心处理办法是ResourceElement.inject,最终调用ResourceElement.getResourceToInject方法:

  private class ResourceElement extends LookupElement {
    @Override
    protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
      return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : getResource(this, requestingBeanName));
    }
  }


public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
  ...
  protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {
    TargetSource ts = new TargetSource() {
      @Override
      public Class<?> getTargetClass() {
        return element.lookupType;
      }
      @Override
      public boolean isStatic() {
        return false;
      }
      @Override
      public Object getTarget() {
        return getResource(element, requestingBeanName);
      }
      @Override
      public void releaseTarget(Object target) {
      }
    };
    ProxyFactory pf = new ProxyFactory();
    pf.setTargetSource(ts);
    if (element.lookupType.isInterface()) {
      pf.addInterface(element.lookupType);
    }
    ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
        ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null);
    return pf.getProxy(classLoader);
  }
  protected Object getResource(LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException {
    ...
    return autowireResource(this.resourceFactory, element, requestingBeanName);
  }
  protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException {
      ...
      resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
      ...
  }
  ...
}


它最终也是依赖于beanFactory.resolveDependency()去处理,所以对@Primary也提供了支持~~~


在Spring生态下开发,强烈建议全部使用Spring技术栈的注解,而不推荐使用JSR规范或者JavaEE提供的注解(毕竟Spring对自己亲儿子的支持是最佳的)


JSR330提供的注解@Inject和@Named和@Autowired和@Qualifire一模一样,他们可以混用(唯一区别是:只有Spring的亲儿子@Autowired只是required = false)

@Autowired等三个注解都可议注入List、Map等等。但是要求必须至少有一个,否则请写@Autowired(required = false),显然此时如果你用的@Resource或者@Inject就只能眼睁睁看着它报错了~所以推荐大家使用Spring的亲儿子:@Autowired吧~~~ 并且大都情况下推荐构造函数注入


泛型依赖注入的另一优点实例(Base基类设计)


泛型依赖注入在书写Base基类的时候,有非常大的用处,可以省略不少的代码,更好的规划和设计代码。

我在书写公司Base类的时候,很好的使用到了这一优点。


下面简单贴出代码如下:(不做过多解释了)

// 我定义的BaseDao接口如下:
public interface IBaseDBDao<T extends BaseDBEntity<T, PK>, PK extends Number>{ ... }
// 定义的BaseService如下:
public interface IBaseDBService<T extends BaseDBEntity<T, PK>, PK extends Number> { ... }
// 因为service层,所以我肯定更希望的是提供一些基础的、共用的实现,否则抽取的意义就不大了,因此此处就用到了泛型依赖注入:
//BaseServiceImpl基础共用实现如下:
public abstract class BaseDBServiceImpl<T extends BaseDBEntity<T, PK>, PK extends Number> implements IBaseDBService<T, PK> {
  // 这里就是泛型依赖注入的核心,子类无需再关心dao层的注入,由基类完成dao的注入即可,非常的自动化,切方便管理
  // 这里子类继承,对对应的注入到具体类型的Dao接口代理类,而不用子类关心
  // 如果这是在Spring4之前,我之前做就得提供一个abstract方法给子类,让子类帮我注入给我,我才能书写公用逻辑。
  //然后这一把泛型依赖注入,大大方便了继承者的使用
  // 可以说完全做到了无侵入、赋能的方式加强子类
    @Autowired
    private IBaseDBDao<T, PK> baseDao;
}


冷知识:使用@Value进行依赖注入


其实上面已经提到了,AutowiredAnnotationBeanPostProcessor不仅处理@Autowired也处理@Value,所以向这么写,使用@Value注解也是能够实现依赖注入的:

@Configuration
public class RootConfig {
    @Bean
    public Person person() {
        return new Person();
    }
  // 这样就能够实现依赖注入了~~~
    @Value("#{person}")
    private Person person;
}


细节:

1、只能是#{person}而不能是${person}

2、person表示beanName,因此请保证此Bean必须存在。比如若写成这样@Value("#{person2}")就报错:


Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'person2' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?
  at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:217)
  at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104)


可以看出,报的是Spel的解析时候找不到Bean的错~


@Value结合el表达式也有这样的能力(原理是StandardBeanExpressionResolver和StandardEvaluationContext),只是一般我们不这么干。


@Value(#{})与@Value(${})的区别


    1.@Value(#{}): SpEL表达式

    @Value("#{}") 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法。当然还有可以表示常量


   2.@Value(${}):获取配置文件中的属性值


它俩可以结合使用:比如:@Value("#{'${spring.redis.cluster.nodes}'.split(',')}")是一个结合使用的案例~ 这样就可以把如下配置解析成List了

spring.redis.cluster.nodes=10.102.144.94:7535,10.102.144.94:7536,10.102.144.95:7535,10.102.144.95:7536,10.102.148.153:7535,10.102.148.153:7536
相关文章
|
11天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
40 2
|
1月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
39 3
|
1月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
44 3
|
30天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
18天前
|
编译器 C# 开发者
C# 9.0 新特性解析
C# 9.0 是微软在2020年11月随.NET 5.0发布的重大更新,带来了一系列新特性和改进,如记录类型、初始化器增强、顶级语句、模式匹配增强、目标类型的新表达式、属性模式和空值处理操作符等,旨在提升开发效率和代码可读性。本文将详细介绍这些新特性,并提供代码示例和常见问题解答。
33 7
C# 9.0 新特性解析
|
17天前
|
C# 开发者
C# 10.0 新特性解析
C# 10.0 在性能、可读性和开发效率方面进行了多项增强。本文介绍了文件范围的命名空间、记录结构体、只读结构体、局部函数的递归优化、改进的模式匹配和 lambda 表达式等新特性,并通过代码示例帮助理解这些特性。
28 2
|
19天前
|
PHP 开发者
PHP 7新特性深度解析及其最佳实践
【10月更文挑战第31天】本文将深入探讨PHP 7带来的革新,从性能提升到语法改进,再到错误处理机制的变革。我们将通过实际代码示例,展示如何高效利用这些新特性来编写更加健壮和高效的PHP应用。无论你是PHP新手还是资深开发者,这篇文章都将为你打开一扇窗,让你看到PHP 7的强大之处。
|
20天前
|
安全 编译器 PHP
PHP 8新特性解析与实践应用####
————探索PHP 8的创新功能及其在现代Web开发中的实际应用
|
23天前
|
Kubernetes Cloud Native 调度
云原生批量任务编排引擎Argo Workflows发布3.6,一文解析关键新特性
Argo Workflows是CNCF毕业项目,最受欢迎的云原生工作流引擎,专为Kubernetes上编排批量任务而设计,本文主要对最新发布的Argo Workflows 3.6版本的关键新特性做一个深入的解析。
|
30天前
|
PHP 数据安全/隐私保护 开发者
PHP 7新特性解析与实践
【10月更文挑战第20天】本文将深入浅出地介绍PHP 7的新特性,包括性能提升、语法改进等方面。我们将通过实际代码示例,展示如何利用这些新特性优化现有项目,提高开发效率。无论你是PHP新手还是资深开发者,都能从中获得启发和帮助。

推荐镜像

更多
下一篇
无影云桌面