泛型依赖注入
有了上面的源码解析,详细下面的案例结果,我们是能够猜到的:
// 向容器内注入Bean(此处忽略) @Autowired private GenericBean<String, Object> objectGenericBean; // 这样注入报错:说找不到Bean // @Autowired private GenericBean objectGenericBean; // 依然报错,但是和上面报错不同,这里报错是找到了2个,匹配不到 // 因此这种情况要特别特别特别的注意:如果字段名不是objectGenericBean,而是objectGeneric,就不会报错了,具体原因参考上文。。。
这里面为了协助理解,附图解释:
从上图可以看出,如果我们注入的时候不指定的泛型,它就是两个 ?
,属于通配符。所以能够匹配容器里的同类型的所有的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