现在有三个类ClassA、ClassB和ClassC,ClassA和ClassB、ClassC不在同一个包下,其中ClassA中有两个引用属性classB和classC,它们分别是ClassB和ClassC的实例。
package com.codeproject.jackie.springdive.beanconfig.yyy; public class ClassB { public ClassB() { System.out.println("creating instance of ClassB :" + this); } } package com.codeproject.jackie.springdive.beanconfig.yyy; public class ClassC { public ClassC() { System.out.println("creating instance of ClassC :" + this); } } package com.codeproject.jackie.springdive.beanconfig.xxx; import com.codeproject.jackie.springdive.beanconfig.yyy.ClassB; import com.codeproject.jackie.springdive.beanconfig.yyy.ClassC; public class ClassA { private ClassB classB; private ClassC classC; public ClassA() { System.out.println("creating instance of ClassA :" + this); } public void setClassB(ClassB classB) { System.out.println("setting field classB with " + classB + " in ClassA"); this.classB = classB; } public void setClassC(ClassC classC) { System.out.println("setting field classC with " + classC + " in ClassA"); this.classC = classC; } }
下面是相应的XML配置:
<bean id="classB" class="com.codeproject.jackie.springdive.beanconfig.yyy.ClassB" /> <bean id="classC" class="com.codeproject.jackie.springdive.beanconfig.yyy.ClassC" /> <bean id="classA" class="com.codeproject.jackie.springdive.beanconfig.xxx.ClassA"> <property name="classB" ref="classB" /> <property name="classC" ref="classC" /> </bean>
加载Spring容器后输出以下结果:
creating instance of ClassB :com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@10d09ad3 creating instance of ClassC :com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@4178460d creating instance of ClassA :com.codeproject.jackie.springdive.beanconfig.xxx.ClassA@3f3f210f setting field classB with com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@10d09ad3 in ClassA setting field classC with com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@4178460d in ClassA
使用注解就是为了简化XML配置,下面对上面的例子做些改动。在ClassA中的setter方法上增加@Autowired注解。
package com.codeproject.jackie.springdive.beanconfig.xxx; import com.codeproject.jackie.springdive.beanconfig.yyy.ClassB; import com.codeproject.jackie.springdive.beanconfig.yyy.ClassC; import org.springframework.beans.factory.annotation.Autowired; public class ClassA { private ClassB classB; private ClassC classC; public ClassA() { System.out.println("creating instance of ClassA :" + this); } @Autowired public void setClassB(ClassB classB) { System.out.println("setting field classB with " + classB + " in ClassA"); this.classB = classB; } @Autowired public void setClassC(ClassC classC) { System.out.println("setting field classC with " + classC + " in ClassA"); this.classC = classC; } }XML配置文件修改如下:
<bean id="classB" class="com.codeproject.jackie.springdive.beanconfig.yyy.ClassB" /> <bean id="classC" class="com.codeproject.jackie.springdive.beanconfig.yyy.ClassC" /> <bean id="classA" class="com.codeproject.jackie.springdive.beanconfig.xxx.ClassA" />再次加载Spring容器,输出以下结果:
creating instance of ClassB :com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@48082d37 creating instance of ClassC :com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@10aadc97 creating instance of ClassA :com.codeproject.jackie.springdive.beanconfig.xxx.ClassA@4178460d
从结果中我们可以发现属性classB和classC没有自动注入到ClassA中。
这是因为我们还缺少能够找到并处理这些注解的工具---注解处理器,注解本身并不能够做任何其它事情,它们只是用来注释一些东西。
为了让Spring能够自动装配属性classB和classC以解决上面的问题,可以在XML文件中注册AutowiredAnnotationBeanPostProcessor这个Bean后处理器。
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />通常我们会采用一种更优雅的方式---即使用 <context:annotation-config>。
<context:annotation-config>会隐式地向Spring容器中注册AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor以及RequiredAnnotationBeanPostProcessor这四个Bean后处理器。注册这四个Bean后处理器的作用是为了在系统中识别并激活相应的注解,比如@Autowired、@Resource 、@PostConstruct、@PreDestroy、@PersistenceContext以及@Required等注解。
现在将XML配置文件修改如下:
<context:annotation-config /> <bean id="classB" class="com.codeproject.jackie.springdive.beanconfig.yyy.ClassB" /> <bean id="classC" class="com.codeproject.jackie.springdive.beanconfig.yyy.ClassC" /> <bean id="classA" class="com.codeproject.jackie.springdive.beanconfig.xxx.ClassA" />再次加载Spring容器,输出结果如下:
creating instance of ClassB :com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@236527f creating instance of ClassC :com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@59556d12 creating instance of ClassA :com.codeproject.jackie.springdive.beanconfig.xxx.ClassA@1a0fced4 setting field classB with com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@236527f in ClassA setting field classC with com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@59556d12 in ClassA输出的确是我们想要的结果。但是XML配置文件并没有简化多少。下面我删掉XML中的bean声明并使用用 @Component 注解来替换它们。
package com.codeproject.jackie.springdive.beanconfig.yyy; import org.springframework.stereotype.Component; @Component public class ClassB { public ClassB() { System.out.println("creating instance of ClassB :" + this); } } package com.codeproject.jackie.springdive.beanconfig.yyy; import org.springframework.stereotype.Component; @Component public class ClassC { public ClassC() { System.out.println("creating instance of ClassC :" + this); } } package com.codeproject.jackie.springdive.beanconfig.xxx; import com.codeproject.jackie.springdive.beanconfig.yyy.ClassB; import com.codeproject.jackie.springdive.beanconfig.yyy.ClassC; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class ClassA { private ClassB classB; private ClassC classC; public ClassA() { System.out.println("creating instance of ClassA :" + this); } @Autowired public void setClassB(ClassB classB) { System.out.println("setting field classB with " + classB + " in ClassA"); this.classB = classB; } @Autowired public void setClassC(ClassC classC) { System.out.println("setting field classC with " + classC + " in ClassA"); this.classC = classC; } }而XML文件中只保留下面这个:
<context:annotation-config />加载Spring容器后发现什么都没有输出,没有创建bean,也没有注入bean。为什么呢?
Spring通过<context:component-scan>提供了强大的组件扫描功能,在这个XML元素中必须指定base-package这个属性,其值即为要扫描的包,Spring会自动扫描指定的包和子包下所有使用了@Component、@Repository、@Service以及@Controller注解的类,将它们注册为Spring bean。其中@Component注解是基本注解,@Repository、@Service和@Controller分别表示持久层、服务层和表现层中的组件
下面使用<context:component-scan>来简化XML配置:
<context:component-scan base-package="com.codeproject.jackie.springdive.beanconfig" />再次 加载Spring容器, 得到正确的输出结果:
creating instance of ClassA :com.codeproject.jackie.springdive.beanconfig.xxx.ClassA@1fbbd7b2 creating instance of ClassB :com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@34d507e9 setting field classB with com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@34d507e9 in ClassA creating instance of ClassC :com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@214a7a12 setting field classC with com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@214a7a12 in ClassA现在将XML配置文件修改如下:
<context:component-scan base-package="com.codeproject.jackie.springdive.beanconfig.yyy" /> <bean id="classA" class="com.codeproject.jackie.springdive.beanconfig.xxx.ClassA" />加载Spring容器,也可以得到正确的结果:
creating instance of ClassB :com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@bb273cc creating instance of ClassC :com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@45660d6 creating instance of ClassA :com.codeproject.jackie.springdive.beanconfig.xxx.ClassA@3288df60 setting field classB with com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@bb273cc in ClassA setting field classC with com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@45660d6 in ClassA可见,即使id为classA的这个bean不是通过包扫描而是在XML文件中手工注册的, 注解处理器仍被<context:component-scan>应用于所有在Spring容器中注册的bean上,亦即<context:component-scan>有着和<context:annotation-config />相同的功能。
那如果在XML配置文件中同时指定了<context:annotation-config/>和 <context:component-scan>呢?
<context:annotation-config /> <context:component-scan base-package="com.codeproject.jackie.springdive.beanconfig.yyy" /> <bean id="classA" class="com.codeproject.jackie.springdive.beanconfig.xxx.ClassA" />没有重复注册id为classA的这个bean,再一次获得了期望的结果:
creating instance of ClassB :com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@bb273cc creating instance of ClassC :com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@45660d6 creating instance of ClassA :com.codeproject.jackie.springdive.beanconfig.xxx.ClassA@3288df60 setting field classB with com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@bb273cc in ClassA setting field classC with com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@45660d6 in ClassA这是因为两个标记注册了相同的注解处理器 (如果指定了<context:component-scan>那么<context:annotation-config />就会被忽略) ,但是Spring只会运行它们一次。
即使你自己注册注解处理器多次,Spring将仍然保证只执行一次。修改XML配置如下:
<context:annotation-config /> <context:component-scan base-package="com.codeproject.jackie.springdive.beanconfig.yyy" /> <bean id="classA" class="com.codeproject.jackie.springdive.beanconfig.xxx.ClassA" /> <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 instance of ClassB :com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@70d05c13 creating instance of ClassC :com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@42ef83d3 creating instance of ClassA :com.codeproject.jackie.springdive.beanconfig.xxx.ClassA@3801318b setting field classB with com.codeproject.jackie.springdive.beanconfig.yyy.ClassB@70d05c13 in ClassA setting field classC with com.codeproject.jackie.springdive.beanconfig.yyy.ClassC@42ef83d3 in ClassA
<context:annotation-config>是用来激活那些已经在Spring容器里注册过的bean(无论是通过xml还是包扫描定义的)上面的注解,而<context:component-scan>除了具有<context:annotation-config>的功能之外,它还可以扫描package来查找并注册bean。