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

简介: 【小家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
相关文章
|
2月前
|
安全 Java 决策智能
Spring Boot自动装配
Spring Boot自动装配基于“约定优于配置”理念,通过条件化配置与Starters机制,智能推断并加载所需组件,大幅简化开发流程。它实现配置自动化,提升效率,降低维护成本,支持自定义扩展,推动微服务快速构建,是Java生态中开发范式的革新之作。(238字)
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
444 2
|
10月前
|
负载均衡 Java API
Spring Cloud是什么及基本特性都有哪些?
Spring Cloud 是用于构建健壮云应用的框架,包含多个子项目。其核心组件如Eureka(服务注册与发现)、Hystrix(熔断器)、Ribbon(负载均衡)等,帮助开发者快速实现微服务架构。Spring Cloud 提供了服务注册与发现、分布式配置、路由、断路器等功能,简化了微服务开发与管理。本文将重点介绍服务注册与发现及分布式配置两大特性。
569 5
|
4月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
5月前
|
Java 数据库连接 API
Java 8 + 特性及 Spring Boot 与 Hibernate 等最新技术的实操内容详解
本内容涵盖Java 8+核心语法、Spring Boot与Hibernate实操,按考试考点分类整理,含技术详解与代码示例,助力掌握最新Java技术与应用。
181 2
|
8月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
536 70
|
11月前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
462 6
|
9月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
259 0
|
11月前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
2824 11

推荐镜像

更多
  • DNS