《剖析Spring3.x的“自动性”》
前言
其实这篇博文,只是有感而发,或者说是对之前博客(
详解SSJ(Spring3.x mvc + Spring3.x Core + JPA2.x)轻量级集成开发目录汇总
)不全面或者遗漏的一种补充。算了,不扯那么远了,直接进入正题。
目录
一、基于注解的自动装配;
二、自动装配时如果被装配的对象构造带参数怎么办?
三、为什么更多人会使用@Resource取代@Autowired?
四、自动扫描组件;
五、自动扫描组件中的过滤器;
一、基于注解的自动装配
其实笔者之前在SSJ的系列博文的中,已经对组件装配进行过详细且深入的讲解,但是笔者发现,还是有很多开发人员对注解一知半解。因此咱们从头再来学习一遍。从Spring2.5开始,已经开始支持自动装配了,其中基于注解的自动装配是未来的趋势。
简单来说,要想让Spring支持基于注解的自动装配,那么要做的事情,只有几点,首先第一件事情就是在Spring配置文件中声明"<context:annotation-config/>"标签,该标签的作用,就是用于支持基于注解的自动装配,当然使用这个标签的时候,千万不要忘记了导入context的命令空间,否则该标签将不可用。当在Spring的配置文件中声明好context命名空间和"<context:annotation-config/>"标签后,基本上事情就成功一大半了,接下来要做的事情就是在需要被注入代码位置添加@Autowired注解进行标注即可。如下所示:
定义需要被装配的bean组件:
public class UserBean { @Value("JohnGao") private String userName; @Value("123456") private String passWord; /* 此处省略set和get方法 */ }
不知道大家是否用过@Value注解,一般来说,对于字面值的定义,我们没有必要在Spring2.5以后还在配置文件中定义,然后使用@Autowired注解的方式进行装配,直接使用@Value注解即可。或许有人脑袋瓜子转得快,既然是硬编码定义字面值,我为么不直接定义,还需要多绕几个圈子跑到@Value注解上去定义,这岂不是脱了裤子放屁,多此一举?其实使用@Value真正的目的是配合SpEL(Spring3.x后提供)表达式语言,这才会体现出使用@Value注解的强大和灵活。这里笔者仅仅举个例子:@Value("#{userBean2.userName}"),这句话的意思,就是引用userBean2中userName字段的字面值,关于SpEL表达式,笔者在后续章节中会开贴专门进行讲解,前提是我得有时间。
spring配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <!-- 自动装配注解 --> <context:annotation-config /> <bean id="userBean" class="com.johngao.test.UserBean" /> </beans>
使用基于注解的自动装配后,配置文件中几乎没有什么内容了,大家明显可以发现原本需要投值注入的标签<property>或者构造注入的<constructor-arg>标签都统统消失了。通过注解的方式对配置代码进行了“瘦身”,这对于不喜欢,甚至厌恶XML配置的人,完全可以算得上是一种福音。
测试用例:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:*-context.xml") public class UseTest { @Autowired private UserBean userBean; @Test public void testA() { if (null != userBean) { System.out.println(userBean.getUserName() + "\n" + userBean.getPassWord()); } } }
上述程序示例非常简单,使用@Autowired注解进行自动装配。有一点笔者得提出来,@Autowired注解缺省是强制要求被标注的目标是必须能够被装配的,否则将会抛出令人讨厌的org.springframework.beans.factory.NoSuchBeanDefinitionException异常。当然如果你是在不想看到这个异常,@Autowired注解提供了一项参数“required”,缺省情况该参数是为“true”,如果我们将其设置为“false”的时候,一旦Spring发现被标注的目标无法被装配时,也不会再抛出NoSuchBeanDefinitionException异常。
二、自动装配时如果被装配的对象构造带参数怎么办?
习惯性使用基于注解进行自动装配的开发人员,应该发现了一个问题,就像笔者之前演示的例子一样,被装配的目标对象都是无参构造,但是如果带参呢?那么如果是构造函数带参,有2个方式可以解决,一种是放弃使用基于注解的自动装配而采用传统的基于配置文件形式,而另外一种则还是使用@Autowired。
就像笔者之前所说,很多开发人员对注解的一知半解导致很多情况下觉得基于注解的方式是有局限性的,当然笔者承认局限性是有,但是一定不是你所想象的那样。接下来笔者将会对@Autowired注解进行细致的讲解,如果你不懂,麻烦你看仔细了。首先第一步,我们首先打开源码,看看@Autowired注解的可用作用域,如下所示:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { /** * Declares whether the annotated dependency is required. * <p>Defaults to <code>true</code>. */ boolean required() default true; }
从源码可以明显看出, @Autowired注解的作用域是比较广的,不仅可以作用域字段、方法上,还可以作用在构造函数上。那么这就意味着,如果需要被装配的目标对象构造带参数,这不要紧,只需要在其构造函数上方标注@Autowired注解即可解决。
三、为什么更多人会使用@Resource取代@Autowired?
这个问题,其实笔者之前在系列博文也讲过,如果你没有映像了,没关系,现在也可以在笔者的带领下进行回顾。我们都知道Spring最早诞生的时候,所承诺的就是低侵入性,没错Spring确实也做到了,基本上在Spring2.5的时候,业务逻辑中几乎没有Spring的代码,但是2.5以后,随着注解的逐渐流行,第三方的注解确实已经不知不觉嵌入在我们的代码中了,尽管这并不是太大的问题(反向依赖),但仍然具备侵入性。如果想完全做到无侵入性,那么只能够使用JSR规范提供的组件注解,这样一来,哪怕某一天我不用Spring,而使用其他厂商提供的IOC组件时,我也不用更改嵌套在我业务逻辑中的注解代码,明白了吗,各位亲?
JSR提供了几种注解,比如笔者最常用的@Resource和最讨厌的@Inject,前者是JSR-250提供的,而后者则是JSR-330,如果你不懂什么是JSP和JCP,麻烦自行google补脑,废话不多说。这里我简单先说一下,@Autowired和@Inject是基于类型的装配,一旦代码中有多个类型相同的类型时,Spring就会抛出异常,通常这种情况下我们可以使用@Autowired和@Qualifier进行注解联用,这样一来@Autowired就变成名称装配,不会再因为定义的类型相同而触发异常(IOC容器规定Bean名称必须唯一)。
@Resource由J2EE(JSR-250)规范提供,目的很简单就是取缔@Autowired。J2EE推荐使用@Resource是因为其更加优秀。@Resource不仅支持类型装配,还支持名称装配。@Resource缺省按照名称装配,只有在找不到指定的bean时才按照类型进行装配。
四、自动扫描组件;
自动装配爽吗?你肯定已经感受到它的便捷了,那么除了自动装配外,Spring在2.5之后还提供了自动扫描组件。其实所谓的自动扫描,指的就是<bean>标签你也完全不需要在Spring配置文件中进行定义了,这样一来,配置文件中的内容,几乎“清理”干净了。Spring提供了如下4个注解,用于声明哪些需要被自动扫描的组件,如下所示:
1、@Component:用于声明不好归类的组件;
2、@Controller:用于声明Action组件;
3、@Service:用于声明Service组件;
4、@Repository:用于声明Dao组件;
当然要想让Spring支持自动扫描组件,还需要在配置文件中定义一项标签“<context:component-scan base-package="com.*.*.test"/>”,该标签的作用就是用于支持自动组件扫描。这里有一点笔者必须提醒大家,是否还记得之前我们定义的用于支持自动装配的“<context:annotation-config />”标签?干掉他,是的,你没有听错,因为“<context:component-scan>”标签具有和它完全一样的功能,并且还支持自动组件扫描,那么有了“<context:component-scan>”之后,顺理成章“<context:annotation-config />”标签也就是多余的了。
五、自动扫描组件中的过滤器;
“<context:component-scan>”标签中还包含有2个子标签,分别是“<context:exclude-filter>”和“<context:include-filter>”标签,其实这2个标签的作用就是对自动扫描组件的一种补充,使得“<context:component-scan>”标签更加灵活。或许你会问我,什么场景下需要使用到这2个子标签?试想一下,如果某些情况下,我们希望所使用的第三方组件(只有字节码),也能够让Spring支持自动扫描组件时,由于无法在别人的源码上添加@Component注解,因此按常理来说,我们是没有办法办到的,但是“<context:include-filter>”过滤器将会让这个“梦想”具备可能性。如下所示:
<context:component-scan base-package="com.*.*.test"> <context:include-filter type="assignable" expression="com.johngao.test.TestA"/> </context:component-scan>
上述代码示例中,笔者的TestA类是没有使用@Component注解的,但是一旦我配置有“<context:exclude-filter>”标签后,TestA类以及其所有派生类都不需要再加上@Component注解进行标注,都可以被Spring执行自动组件扫描,以便于被实现自动装配。当然使用“<context:exclude-filter>”需要注意的是,该过滤器的过滤目标是依赖“<context:component-scan>”标签的“ base-package”属性的。相反“<context:exclude-filter>”过滤器指的就是哪些组件不需要被自动扫描并注册为Spring Bean。
关于过滤器的属性“type”,则有5种类型,分别为assignable、annotation、aspectj、custom,以及regex,关于各个类型的区别,请自定google补脑。