前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring复杂的IOC容器之短小的注解篇

Spring复杂的IOC容器之短小的注解篇

作者头像
大忽悠爱学习
发布2022-05-10 16:02:11
3280
发布2022-05-10 16:02:11
举报
文章被收录于专栏:c++与qt学习

Spring复杂的IOC容器之短小的注解篇


本系列文章:

Spring复杂的BeanFactory继承体系该如何理解? ----上

Spring复杂的BeanFactory继承体系该如何理解? ----中

Spring复杂的BeanFactory继承体系该如何理解?—中下

Spring复杂的BeanFactory继承体系该如何理解?—下

本篇文章作为对上述系列关于IOC容器部分的一个小补充


注解版的自动绑定(@Autowired)

1. 从自动绑定(autowire)到@Autowired

在使用依赖注入绑定FXNews相关实现类时,为了减少配置量,我们可以采用Spring的IoC容器提供的自动绑定功能,如下所示:

代码语言:javascript
复制
<beans default-autowire="byType"> 
 <bean id="newsProvider" class="..FXNewsProvider" autowire="byType"/> 
 <bean id="djNewsListener" class="..DowJonesNewsListener"/> 
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/> 
</beans> 

可以通过default-autowire来指定默认的自动绑定方式,也可以通过每个bean定义上的autowire来指定每个bean定义各自的自动绑定方式,它们都是触发容器对相应对象给予依赖注入的标志。而将自动绑定的标志用注解来表示时,也就得到了基于注解的依赖注入,或者更确切地称为基于注解的自动绑定。

@Autowired是基于注解的依赖注入的核心注解,它的存在可以让容器知道需要为当前类注入哪些依赖。比如可以使用@Autowired对FXNewsProvider类进行标注,以表明要为FXNewsProvider注入的依赖。

使用@Autowired标注后的FXNewsProvider

代码语言:javascript
复制
public class FXNewsProvider 16 
{ 
 private IFXNewsListener newsListener; 
 private IFXNewsPersister newPersistener;
 @Autowired 
 public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) 
 { 
 this.newsListener = newsListner; 
 this.newPersistener = newsPersister; 
 } 
 ... 
}

与原有的byType类型的自动绑定方式类似,@Autowired也是按照类型匹配进行依赖注入的,只不过,它要比byType更加灵活,也更加强大。

@Autowired可以标注于类定义的多个位置,包括如下几个。

域(Filed)或者说属性(Property)。不管它们声明的访问限制符是private、protected还是public,只要标注了@Autowired,它们所需要的依赖注入需求就都能够被满足,如下所示:

代码语言:javascript
复制
public class FXNewsProvider 
{ 
 @Autowired 
 private IFXNewsListener newsListener; 
 @Autowired 
 private IFXNewsPersister newPersistener; 
 ... 
}

构造方法定义(Constructor)。标注于类的构造方法之上的@Autowired,相当于抢夺了原有自动绑定功能中“constructor”方式的权利,它将根据构造方法参数类型,来决定将什么样的依赖对象注入给当前对象。从最初的代码示例中,我们可以看到标注于构造方法之上的@Autowired的用法。

方法定义(Method)。@Autowired不仅可以标注于传统的setter方法之上,而且还可以标注于任意名称的方法定义之上,只要该方法定义了需要被注入的参数。下面代码给出了一个标注于这种任意名称方法之上的@Autowired使用示例代码。

标注于方法之上的@Autowired代码示例

代码语言:javascript
复制
public class FXNewsProvider 
{ 
 private IFXNewsListener newsListener; 
 private IFXNewsPersister newPersistener; 
 
 @Autowired 
 public void setUp(IFXNewsListener newsListener,IFXNewsPersister newPersistener) 
 { 
 this.newsListener = newsListener; 
 this.newPersistener = newPersistener; 
 } 
 ... 
} 

现在,虽然可以随意地在类定义的各种合适的地方标注@Autowired,希望这些被@Autowired标注的依赖能够被注入,但是,仅将@Autowired标注于类定义中并不能让Spring的IoC容器聪明到自己去查看这些注解,然后注入符合条件的依赖对象。容器需要某种方式来了解,哪些对象标注了@Autowired,哪些对象可以作为可供选择的依赖对象来注入给需要的对象。


在考虑使用什么方式实现这一功能之前,我们先比较一下原有的自动绑定功能与使用@Autowired之后产生了哪些差别。

使用自动绑定的时候,我们将所有对象相关的bean定义追加到了容器的配置文件中,然后使用default-autowire或者autowire告知容器,依照这两种属性指定的绑定方式,将容器中各个对象绑定到一起。

在使用@Autowired之后,default-autowire或者autowire的职责就转给了@Autowired,所以,现在,容器的配置文件中就只剩下了一个个孤伶伶的bean定义,如下所示:

代码语言:javascript
复制
<beans> 
<bean id="newsProvider" class="..FXNewsProvider"/> 2 
 <bean id="djNewsListener" class="..DowJonesNewsListener"/> 
 <bean id="djNewsPersister" class="..DowJonesNewsPersister"/> 
</beans>

为了给容器中定义的每个bean定义对应的实例注入依赖,可以遍历它们,然后通过反射,检查每个bean定义对应的类上各种可能位置上的@Autowired。

如果存在的话,就可以从当前容器管理的对象中获取符合条件的对象,设置给@Autowired所标注的属性域、构造方法或者方法定义。

容器遍历@Autowired并进行依赖注入的原型代码示例

代码语言:javascript
复制
Object[] beans = ...; 

 for(Object bean:beans) 
{ 

 if(autowiredExistsOnField(bean)) 
 { 
 Field f = getQulifiedField(bean)); 
 setAccessiableIfNecessary(f); 
 f.set(getBeanByTypeFromContainer()); 
} 

 if(autowiredExistsOnConstructor(bean)) 
 { 
 ... 
 } 

 if(autowiredExistsOnMethod(bean)) 
 { 
 ... 
 } 

}

看到以上的原型代码所要完成的功能以及我们的设想,你一定想到了,我们可以提供一个Spring的IoC容器使用的BeanPostProcessor自定义实现,让这个BeanPostProcessor在实例化bean定义的过程中,来检查当前对象是否有@Autowired标注的依赖需要注入。

org.springframework.beans. factory.annotation.AutowiredAnnotationBeanPostProcessor就是Spring提供的用于这一目的的BeanPostProcessor实现。所以,很幸运,我们不用自己去实现它了。

将FXNews相关类定义使用@Autowired标注之后,只要在IoC容器的配置文件中追加AutowiredAnnotationBeanPostProcessor就可以让整个应用开始运作了,如下所示:

代码语言:javascript
复制
<beans> 
<bean class="org.springframework.beans.factory.annotation. ➥
 AutowiredAnnotationBeanPostProcessor"/> 
 
<bean id="newsProvider" class="..FXNewsProvider"/> 15 
 <bean id="djNewsListener" class="..DowJonesNewsListener"/> 
 <bean id="djNewsPersister" class="..DowJonesNewsPersister"/> 
</beans> 

当然,这需要我们使用ApplicationContext类型的容器,否则还得做点儿多余的准备工作

注意

看着依赖注入相关的信息,一半分散在 看着依赖注入相关的信息,一半分散在Java源代码中(@Autowired标注的信息),一半 标注的信息),一半依然留在XML配置文件里,你心里一定觉得很不爽。实际上,我也是,这不是折腾人吗?

不过,别急,让我们先解决眼前的另一个问题,稍后再回过头来看看怎么进一步统一这两片国土。


2. @Qualifier的陪伴

@Autowired是按照类型进行匹配,如果当前@Autowired标注的依赖在容器中只能找到一个实例与之对应的话,那还好。可是,要是能够同时找到两个或者多个同一类型的对象实例,又该怎么办呢?

我们自己当然知道应该把具体哪个实例注入给当前对象,可是,IoC容器并不知道,所以,得通过某种方式告诉它。这时,就可以使用@Qualifier对依赖注入的条件做进一步限定,使得容器不再迷茫。

@Qualifier实际上是byName自动绑定的注解版,既然IoC容器无法自己从多个同一类型的实例中选取我们真正想要的那个,那么我们不妨就使用@Qualifier直接点名要哪个好了。假设FXNewsProvider使用的IFXNewsListener有两个实现,一个是DowJonesNewsListener,一个是ReutersNewsListener,二者相关配置如下

代码语言:javascript
复制
<beans> 
 <bean class="org.springframework.beans.factory.annotation. ➥
 AutowiredAnnotationBeanPostProcessor"/> 
 
 <bean id="newsProvider" class="..FXNewsProvider"/> 
 <bean id="djNewsListener" class="..DowJonesNewsListener"/> 
 <bean id="reutersNewsListner" class="..ReutersNewsListener"/> 
 <bean id="djNewsPersister" class="..DowJonesNewsPersister"/> 
</beans> 

如果我们想让FXNewsProvider使用ReutersNewsListener,那么就可以在FXNewsProvider的类定义中使用@Qualifier指定这一选择结果,如下:

代码语言:javascript
复制
public class FXNewsProvider 
{ 
 @Autowired 
@Qualifier("reutersNewsListner") 
 private IFXNewsListener newsListener; 
 @Autowired 
 private IFXNewsPersister newPersistener; 
 ... 
} 

以上我们使用的是标注于属性域的@Autowired进行依赖注入。如果使用@Autowired来标注构造方法或者方法定义的话,同样可以使用@Qualifier标注方法参数来达到限定注入实例的目的。

标注于方法参数之上的@Qualifier

代码语言:javascript
复制
public class FXNewsProvider 
{ 
 ... 
 
 @Autowired 
 public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener) 
 { 
 this.newsListener = newsListener; 
 this.newPersistener = newPersistener; 
public class FXNewsProvider 
{ 
 ... 
 
 @Autowired 
 public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener ➥
 newsListener,IFXNewsPersister newPersistener) 
 { 
 this.newsListener = newsListener; 
 this.newPersistener = newPersistener; 
 } 
 ... 
}

除此之外,@Qualifier还可以用于标注注解类型,这主要用于自定义@Qualifier的场合。有关自定义@Qualifier的内容我们这里就不做赘述了。


@Autowired之外的选择——使用JSR250 标注依赖注入关系

Spring 2.5提供的基于注解的依赖注入,除了可以使用Spring提供的@Autowired和@Qualifier来标注相应类定义之外,还可以使用JSR250的@Resource和@PostConstruct以及@PreDestroy对相应类进行标注,这同样可以达到依赖注入的目的。

@Resource与@Autowired不同,它遵循的是byName自动绑定形式的行为准则,也就是说,IoC容器将根据@Resource所指定的名称,到容器中查找beanName与之对应的实例,然后将查找到的对象实例注入给@Resource所标注的对象。同样的FXNewsProvider,如若使用@Resource进行标注以获取依赖注入的话,类似如下的样子:

代码语言:javascript
复制
public class FXNewsProvider 
{ 
 @Resource(name="djNewsListener") 7 
private IFXNewsListener newsListener; 
 @Resource(name="djNewsPersister") 
private IFXNewsPersister newPersistener; 
 ... 
}

JSR250规定,如果@Resource标注于属性域或者方法之上的话,相应的容器将负责把指定的资源注入给当前对象,所以,除了像我们这样直接在属性域上标注@Resource,还可以在构造方法或者普通方法定义上标注@Resource,这与@Autowired能够存在的地方大致相同。

确切地说,@PostConstruct和@PreDestroy不是服务于依赖注入的,它们主要用于标注对象生命周期管理相关方法,这与Spring的InitializingBean和DisposableBean接口,以及配置项中的init-method和destroy-method起到类似的作用。

使用@PostConstruct和@PreDestroy标注对象的生命周期管理方法

代码语言:javascript
复制
public class LifecycleEnabledClass 
{ 
 @PostConstruct 
public void setUp() 
 { 
 ... 
 } 
 @PreDestroy 
 public void destroy() 
 { 
 ... 
 } 
}

如果想某个方法在对象实例化之后被调用,以做某些准备工作,或者想在对象销毁之前调用某个方法清理某些资源,那么就可以像我们这样,使用@PostConstruct和@PreDestroy来标注这些方法。

当然,是使用@PostConstruct和@PreDestroy,还是使用Spring的InitializingBean和DisposableBean接口,或者init-method和destroymethod配置项,可以根据个人的喜好自己决定。

天上永远不会掉馅饼,我们只是使用@Resource或者@PostConstruct和@PreDestroy标注了相应对象,并不能给该对象带来想要的东西。所以,就像@Autowired需要AutowiredAnnotationBeanPostProcessor为它与IoC容器牵线搭桥一样,JSR250的这些注解也同样需要一个BeanPostProcessor帮助它们实现自身的价值。这个BeanPostProcessor就是org.springframework.context. annotation.CommonAnnotationBeanPostProcessor,只有将CommonAnnotationBeanPostProcessor添加到容器,JSR250的相关注解才能发挥作用,通常如下添加相关配置即可:

代码语言:javascript
复制
<beans> 
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/> 
 
<bean id="newsProvider" class="..FXNewsProvider"/> 
<bean id="djNewsListener" class="..DowJonesNewsListener"/> 
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/> 
</beans>

既然不管是@Autowired还是@Resource都需要添加相应的BeanPostProcessor到容器,那么我们就可以在基于XSD的配置文件中使用一个<context:annotation-config >配置搞定以上所有的BeanPostProcessor配置。

使用<context:annotation-config />激活注解的相关功能

代码语言:javascript
复制
<beans xmlns="http://www.springframework.org/schema/beans" ➥
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ➥
xmlns:context="http://www.springframework.org/schema/context" ➥
xmlns:p="http://www.springframework.org/schema/p" ➥
xsi:schemaLocation=" ➥
http://www.springframework.org/schema/beans ➥
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ➥
http://www.springframework.org/schema/context ➥
http://www.springframework.org/schema/context/spring-context-2.5.xsd"> ➥
 
<context:annotation-config/> 
 
 <bean id="newsProvider" class="..FXNewsProvider"/> 
 <!--其他bean定义--> 
 ... 
</beans> 

< context:annotation-config> 不但帮我们把 AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor注册到容器,同时还会把PersistenceAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor一并进行注册,可谓一举四得啊!


注意 Spring提供的@Autowired加上@Qualifier和JSR250提供的@Resource等注解属于两个派系。

如果要实现依赖注入的话,使用一个派别的注解就可以了。

当然,既然<context:annotation-config >对两个派系都提供了BeanPostProcessor的支持,混合使用也是没有问题的,只要别造成使用上的混乱就行


将革命进行得更彻底一些(classpath-scanning 功能介绍)

好了,该来解决让我们不爽的那个问题了。到目前为止,我们还是需要将相应对象的bean定义,一个个地添加到IoC容器的配置文件中。

与之前唯一的区别就是,不用在配置文件中明确指定依赖关系了(改用注解来表达了嘛)。既然使用注解来表达对象之间的依赖注入关系,那为什么不搞的彻底一点儿,将那些几乎“光秃秃”的bean定义从配置文件中彻底消灭呢?

OK,我们想到了,Spring开发团队也想到了,classpath-scanning的功能正是因此而诞生的!

使用相应的注解对组成应用程序的相关类进行标注之后,classpath-scanning功能可以从某一顶层包(base package)开始扫描。

当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。

这之后所发生的事情就不用我说了,既然相关的类已经添加到了容器,那么后面BeanPostProcessor为@Autowired或者@Resource所提供的注入肯定是有东西拿咯!

classpath-scanning功能的触发是由<context:component-scan >决定的。按照如下代码,在XSD形式(也只能是XSD形式)的配置文件中添加该项配置之后,classpath-scanning功能立即开启:

代码语言:javascript
复制
<beans xmlns="http://www.springframework.org/schema/beans" ➥
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ➥
xmlns:context="http://www.springframework.org/schema/context" ➥ 6 
xsi:schemaLocation="http://www.springframework.org/schema/beans ➥
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ➥
7 http://www.springframework.org/schema/context ➥
http://www.springframework.org/schema/context/spring-context-2.5.xsd"> 

<context:component-scan base-package="org.spring21"/> 

</beans> 

现在<context:component-scan >将遍历扫描org.spring21路径下的所有类型定义,寻找标注了相应注解的类,并添加到IoC容器。

提示 如果要扫描的类定义存在于不同的源码包下面,也可以为base-package指定多个以逗号分隔的扫描路径。需要的话,不要犹豫!

<context:component-scan >默认扫描的注解类型是@Component。

不过,在@Component语义基础上细化后的@Repository、@Service和@Controller也同样可以获得< context:component-scan> 的青睐。

@Component的语义更广、更宽泛,而@Repository、@Service和@Controller的语义则更具体。所以,同样对于服务层的类定义来说,使用@Service标注它,要比使用@Component更为确切。

对于其他两种注解也是同样道理,我们暂且使用语义更广的@Component来标注FXNews相关类,以便摆脱每次都要向IoC容器配置添加bean定义的苦恼。


使用@Component标注后的FXNews相关类定义

代码语言:javascript
复制
@Component 
public class FXNewsProvider 
{ 
 @Autowired  
private IFXNewsListener newsListener; 
 @Autowired 
private IFXNewsPersister newPersistener; 
 ... 
} 


@Component("djNewsListener") 
public class DowJonesNewsListener implements IFXNewsListener
{ 
 ... 
} 


@Component 
public class DowJonesNewsPersister implements IFXNewsPersister 
{ 
 ... 
}

< context:component-scan>在扫描相关类定义并将它们添加到容器的时候,会使用一种默认的命名规则,来生成那些添加到容器的bean定义的名称(beanName)。

比如DowJonesNewsPersister通过默认命名规则将获得dowJonesNewsPersister作为bean定义名称。如果想改变这一默认行为,就可 以像以上DowJonesNewsListener所对应的@Component那样,指定一个自定义的名称。

另一种方法就是使用自定义实现的BeanNameGenerator,通过<context:component-scan >的name-generator属性指定我们自己的BeanNameGenerator实现类来替换掉默认的BeanNameGenerator,也可以改变默认的bean定义 名称生成规则。

现在,除了<context:component-scan >是唯一需要添加到IoC容器的配置内容,所有的工作都可以围绕着使用注解的Java源代码来完成了。如果现在加载配置文件,启动FXNewProvider来处理外汇新闻的话,我们可以得到预期的运行效果,运行的代码如下所示:

代码语言:javascript
复制
ApplicationContext ctx = new ClassPathXmlApplicationContext("../conf.xml"); 
FXNewProvider provider = (FXNewProvider)ctx.getBean("FXNewsProvider"); 
provider.getAndPersistNews(); 

注意,我们通过FXNewsProvider作为bean定义名称来获取FXNewsProvider的实例,对于开头都是大写的类名来 说,bean定义名称实际上就相当于类名,这与默认的命名生成规则有些小小的差异。

你或许会觉得有些诧异,因为我们并没有使用< context:annotation-config >甚至直接将相应的BeanPostProcessor添加到容器中,而FXNewsProvider怎么会获得相应的依赖注入呢?

这个得怪< context:component-scan>“多管闲事”,它同时将AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor一并注册到了容器中,所以,依赖注入的需求得以满足。

如果你不喜欢,非要自己通过 < context:annotation-config> 或者直接添加相关 BeanPostProcessor的方式来满足@Autowired或者@Resource的需求,可以将<context:component-scan >的annotation-config属性值从默认的true改为false。不过,我想没有太好的理由非要这么做吧?

< context:component-scan>的扫描行为可以进一步定制,默认情况下它只关心@Component、@Repository、@Service和@Controller四位大员,但我们可以丰富这一范围,或者对默认的扫描结果进行过滤以排除某些类,< context:component-scan>的嵌套配置项可以帮我们达到这一目的。

<context:component-scan >部分嵌套配置项的使用示例

代码语言:javascript
复制
<beans xmlns="http://www.springframework.org/schema/beans" ➥
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ➥
xmlns:context="http://www.springframework.org/schema/context" ➥
xsi:schemaLocation="http://www.springframework.org/schema/beans ➥
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ➥
http://www.springframework.org/schema/context ➥
http://www.springframework.org/ schema/context/spring-context-2.5.xsd"> 

<context:component-scan base-package="org.spring21"> 

<context:include-filter type="annotation" ➥
 expression="cn.spring21.annotation.FXService"/> 

 <context:exclude-filter type="aspectj" expression=".."/> 
 </context:component-scan> 

</beans>

include-filter和exclude-filter可以使用的type类型有annotation、assignable、regex和aspectj四种。它们的更多信息可以参考最新的Spring 参考文档。上例中,我们增加了@FXService作为新的被扫描注解对象,并使用aspectj表达式排除某些扫描结果。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring复杂的IOC容器之短小的注解篇
  • 注解版的自动绑定(@Autowired)
    • 1. 从自动绑定(autowire)到@Autowired
      • 2. @Qualifier的陪伴
        • @Autowired之外的选择——使用JSR250 标注依赖注入关系
          • 将革命进行得更彻底一些(classpath-scanning 功能介绍)
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档