Spring mvc3中的<context:annotation-config>和<context:component-scan>到底什么区别呢?平时是不是都在混用?今天看到一篇很不错的问答,解释的非常好。
原文地址如下:
http://stackoverflow.com/questions/7414794/difference-between-contextannotation-config-vs-contextcomponent-scan
<context:annotation-config> 是用来
激活已经在spring配置文件applicationContext.xml中注册过的bean(不管这个bean是在xml中定义的,还是包含在扫描包中的)。
<context:component-scan> 能做<context:annotation-config>所能做的事,除此之外,<context:component-scan>还能找到并
注册在applicationContext.xml中配置的bean。
这么说很抽象,激活和注册的具体概念和区别又是什么呢?下面用一些小例子来说明个中的相同点和不同点。
现在有三个bean,A,B和C。类A中还注入了bean B和bean C。
package com.xxx;
public class B {
public B() {
System.out.println("creating bean B: " + this);
}
}
package com.xxx;
public class C {
public C() {
System.out.println("creating bean C: " + this);
}
}
package com.yyy;
import com.xxx.B;
import com.xxx.C;
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("creating bean A: " + this);
}
public void setBbb(B bbb) {
System.out.println("setting A.bbb with " + bbb);
this.bbb = bbb;
}
public void setCcc(C ccc) {
System.out.println("setting A.ccc with " + ccc);
this.ccc = ccc;
}
}
在XML中配置如下:
<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A">
<property name="bbb" ref="bBean" />
<property name="ccc" ref="cBean" />
</bean>
启动项目后输出如下:
creating bean B: com.xxx.B@c2ff5
creating bean C: com.xxx.C@1e8a1f6
creating bean A: com.yyy.A@1e152c5
setting A.bbb with com.xxx.B@c2ff5
setting A.ccc with com.xxx.C@1e8a1f6
这是我们想要的结果,但是这种方式太老旧了,接下来我们要用注释来代过段时间上述的xml配置。
首先,在类A中,我们加入@Autowired标签来注入属性bbb和ccc。如下:
package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import com.xxx.B;
import com.xxx.C;
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("creating bean A: " + this);
}
@Autowired
public void setBbb(B bbb) {
System.out.println("setting A.bbb with " + bbb);
this.bbb = bbb;
}
@Autowired
public void setCcc(C ccc) {
System.out.println("setting A.ccc with " + ccc);
this.ccc = ccc;
}
}
这样我们就可以
删除在xml中bean A中的property(属性)了。
<property name="bbb" ref="bBean" />
<property name="ccc" ref="cBean" />
所以xml可以简化成如下:
<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A" />
当我启动修改后的项目后,得到的输出如下:
creating bean B: com.xxx.B@5e5a50
creating bean C: com.xxx.C@54a328
creating bean A: com.yyy.A@a3d4cf
有没有发现,跟我们预想的不一样,中间出了错(在类A中没有成功设值bean B和bean C)。
注释确实是个很方便的技术,但到目前为止,它们还没有发挥出功效。它们如同待执行的任务一样,需要有一个好的工具去找到它们并且让它们能真正的启动起来。
标签<context:annotation-config>就是派来解救注解的。此话怎讲呢,这个标签可以找到那些在applicationContext中定义过的bean的注解。
由此,将xml改为:
<context:annotation-config />
<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A" />
重启项目,结果如预期所想:
creating bean B: com.xxx.B@15663a2
creating bean C: com.xxx.C@cd5f8b
creating bean A: com.yyy.A@157aa53
setting A.bbb with com.xxx.B@15663a2
setting A.ccc with com.xxx.C@cd5f8b
第二种方案看起来不错,但我删掉了两行xml的同时,又必须再加上一行。看起来不是一个大改动。注解的思想应该是需要删除掉xml配置才对。于是我们接着把定义bean的xml用注释来代替:
package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class B {
public B() {
System.out.println("creating bean B: " + this);
}
}
package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class C {
public C() {
System.out.println("creating bean C: " + this);
}
}
package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.xxx.B;
import com.xxx.C;
@Component
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("creating bean A: " + this);
}
@Autowired
public void setBbb(B bbb) {
System.out.println("setting A.bbb with " + bbb);
this.bbb = bbb;
}
@Autowired
public void setCcc(C ccc) {
System.out.println("setting A.ccc with " + ccc);
this.ccc = ccc;
}
}
这样的话,xml就只剩下:
<context:annotation-config />
启动项目,看看输出结果,结果就是空白,没有任何的输出。这意味着没有bean被标签autowired激活了,这样的结果出乎意料。
就如同文章一开始提到的,标签<context:annotation-config />的作用对象权权是那些已经在applicationContext中定义过的bean(但并没有激活)。当我们把三个定义的bean的xml语句移掉后,标签<context:annotation-config />就找不到“目标”了。
但这个问题并不存在于标签<context:component-scan>中,标签<context:component-scan>可以扫描指定的java包,找到“目标”。(就算bean没有在xml中定义也没关系)。
我们将xml中的配置修改如下:
<context:component-scan base-package="com.xxx" />
启动项目,得到了如下的输出:
creating bean B: com.xxx.B@1be0f0a
creating bean C: com.xxx.C@80d1ff
好像少了类A的加载,为什么呢?
如果我们仔细观察这些类,会发现类A的java包是com.yyy,并不是标签<context:component-scan>中定义的com.xxx,所以这才是为什么漏掉整个A类的加载的原因。
为了修补这个bug,我们将base-package修改如下:
<context:component-scan base-package="com.xxx,com.yyy" />
如同魔术般的,得到预期结果:
creating bean B: com.xxx.B@cd5f8b
creating bean C: com.xxx.C@15ac3c9
creating bean A: com.yyy.A@ec4a87
setting A.bbb with com.xxx.B@cd5f8b
setting A.ccc with com.xxx.C@15ac3c9
至此,我们完成了符合注释风格的代码。
题外话,如果我们执意将base-package只定义为com.xxx,那有没有办法做到对类A的加载呢?那必须定义bean A。
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
这样定义的话,我们依然能得到正确的结果:
creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87
尽管类A并没有包含在扫描的java包中,标签<context:component-scan>还是会找到所有在applicationContext.xml中定义的其它所有bean(就像手动定义的beanA)。
那么,当我们在一个xml中同时配置标签<context:annotation-config />和<context:component-scan>,会发生什么呢?如下的配置会让类A重复加载吗?
<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
答案是否。我们得到的结果依然如下:
creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87
这是因为如果两个标签同时存在的时候,标签<context:annotation-config />会被忽略。
甚至当你定义同一个bean很多次,Spring总是能确保它们只会被加载一次,如下:
<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
<bean id="bla" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla1" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla2" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla3" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
结果依然一样:
creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@25d2b2
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87