【小家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
相关文章
|
30天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
29天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
29天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
6天前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
17 0
|
2天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
72 17
Spring Boot 两种部署到服务器的方式
|
2天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
33 17
springboot自动配置原理
|
7天前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
50 11
|
10天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
167 12
|
29天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
15天前
|
Java 测试技术 应用服务中间件
Spring Boot 如何测试打包部署
本文介绍了 Spring Boot 项目的开发、调试、打包及投产上线的全流程。主要内容包括: 1. **单元测试**:通过添加 `spring-boot-starter-test` 包,使用 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 注解进行测试类开发。 2. **集成测试**:支持热部署,通过添加 `spring-boot-devtools` 实现代码修改后自动重启。 3. **投产上线**:提供两种部署方案,一是打包成 jar 包直接运行,二是打包成 war 包部署到 Tomcat 服务器。
41 10

推荐镜像

更多