Elim的博客

用来记录一些原创性的总结


Elim

16 使用注解进行bean定义

除了在Spring配置文件中通过bean元素定义一个bean外。我们还可以通过在Class上使用特定的注解进行标注,然后让Spring去扫描这些特定的注解,并把它们当做一个bean进行定义。

16.1 @Component

@Component是Spring中用来定义bean最基本的注解,通过在Class类上标注对应的注解就可以让Spring扫描到对应的定义,并把其作为一个bean定义到对应的bean容器中。

@Component
public class Hello {
	
}

当Spring扫描到这样一个使用@Component进行标注的类时,默认会定义一个beanName为我们的类名的bean,对应首字母小写。所以在上述示例中默认就会生成一个类型为Hello,beanName为“hello”的bean。当然我们也可以通过@Component的value属性来指定需要生成的bean的beanName。如下示例中就通过value属性指定了对应生成的bean的beanName为“abc”。

@Component("abc")
public class Hello {
	
}

除了@Component外,Spring默认还支持对@Controller、@Service和@Repository注解的扫描。它们可以用来标注在特定bean上,如通常会将@Controller标注在Controller层的类上,将@Service标注在Service层的类上,然后将@Repository标注在Dao层的类上。这些都只是不成文的规定而已,如果用户愿意,我们也可以在Service层上使用@Controller、@Component等进行标注,Spring在扫描到之后都会将它们以对应的bean进行定义。之所以有这么几种不同的注解可以用来定义bean,其中一个主要的原因是我们可以通过不同的注解来区分不同的bean类型,然后可以让Spring在进行扫描的时候只扫描特定类型的注解。当我们使用SpringMVC时,我们常会让SpringMVC对应的ApplicationContext只扫描@Controller,然后让传统的ApplicationContext只扫描除@Controller以外的其它类型的注解,用以将不同类型的bean定义在不同的bean容器中,这样某些针对特定bean容器中的操作就只会针对某些bean产生作用。@Controller、@Service和@Repository也可以通过value属性来指定对应需要生成的bean定义的beanName,默认都是当前Class类名的首字母小写后的结果。

16.2 定义自己的@Component

我们也可以通过@Component定义自己的注解,即在自己的注解上使用@Component进行标注,这样当我们使用自定义的注解标注在Class上时,Spring默认也会将其作为一个bean进行定义。如下我们定义了一个@MyComponent,然后在其上面使用@Component进行了标注,当然这里使用@Controller、@Service和@Repository等进行标注也是可以的,实际上它们的定义也都是使用@Component进行了标注的。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
public @interface MyComponent {

	public String value() default "";
	
}

之后我们可以在Class上使用@MyComponent进行标注,Spring在进行扫描时默认也会将它们作为一个bean进行定义。

@MyComponent("abc")
public class Hello {
	
}

其实,我们还可以让Spring扫描自定义的注解,让其将标注了自定义注解的Class定义为对应的bean,而我们的自定义注解可以不必使用@Component等原始就支持的注解进行标记。当然,这种用法也是需要进行些许配置的,主要就是告诉Spring需要扫描哪些注解,这部分内容将在后续讲解扫描时的过滤器时进行讲解。

16.3 设置扫描类路径

其实光使用@Component等标注在Class上还不足以使Spring进行扫描并将它们作为一个bean进行定义。我们必须通过配置来告诉Spring进行扫描的类路径。首先我们必须在Spring的配置文件中引入context命名空间,然后通过<context:component-scan/>标签定义启用Spring扫描@Component等注解的功能。<context:component-scan/>标签有一个必须指定的属性,base-package,其是用来指定我们需要进行扫描的基本包路径的,指定后Spring将扫描base-package指定的包及其子孙包。如果需要指定多个基础包,则可以通过逗号进行分隔。如下示例,则表示Spring将扫描类路径下com.app.service包及其子孙包,以及com.app.dao包及其子孙包下的所有类以寻找使用@Component等注解进行标注的Class定义为对应的bean。

<?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.xsd
		http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="com.app.service,com.app.dao" />	

</beans>

定义了<context:component-scan/>标签后,Spring默认将隐式的启用对注解的支持,即之前介绍的@Autowired等注解的使用,所以使用了该标签以后我们没必要再在Spring配置文件中显式的定义<context:annotation-config/>了。这可以通过annotation-config属性进行定义,默认是true,改成false就可以禁用自动启用对注解支持。

<context:component-scan base-package="com.app" annotation-config="true"/>	

除了必须指定的属性base-package之外,<context:component-scan/>还可以指定如下属性。

16.4 使用@Scope指定scope

Spring在通过<context:component-scan/>自动检测需要添加到bean容器中的bean定义时,默认会将检测到的类单例形式添加到bean容器。如果用户希望使用其它类型的scope,则可以通过在类定义上使用@Scope进行指定。具体的scope是由@Scope的value属性进行指定的,默认是单例。如下是一个通过@Scope指定对应bean的scope为prototype的示例,即是多例形式,每次从bean容器中请求时都会获取一个全新的bean对象。

@Component
@Scope("prototype")
public class Hello {
	
}

此外,我们还可以通过@Scope的proxyMode属性指定当前bean的代理模式。其对应ScopedProxyMode类型的枚举,可选值有DEFAULT、NO、INTERFACES和TARGET_CLASS,默认是不代理。DEFAULT的作用等同于NO。

@Component
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class Hello {
	
	
}

16.5 过滤器

默认情况下,Spring将扫描所有@Component、@Controller、@Service和@Repository标注的类,以及这几个注解所派生出来的注解所标注的类,如之前介绍的自定义的@MyComponent注解,并将它们作为一个bean定义在对应的bean容器中。Spring提供了两个filter定义可以让我们对需要扫描的类进行进一步的过滤,一个是<context:include-filter/>,一个是<context:exclude-filter/>。这两个filter的配置基本是一样的,所不同的是它们的功能。<context:include-filter/>对需要扫描的内容进行过滤,即告诉Spring除了按照默认的逻辑进行扫描以外,还需要对哪些内容进行扫描。而<context:exclude-filter/>则是用来对不需要进行扫描的内容进行过滤,即告诉Spring应该排除对哪些原本应该扫描的内容进行扫描。它们都是作为<context:component-scan/>的子元素进行定义的,我们可以在一个<context:component-scan/>标签下单独同时定义多个<context:include-filter/>或者是多个<context:exclude-filter/>,但是如果在一个<context:component-scan/>标签下同时定义<context:include-filter/><context:exclude-filter/>则需要先定义<context:include-filter/>,再定义<context:exclude-filter/>

在定义<context:include-filter/><context:exclude-filter/>时我们必须指定type属性和expression属性。type属性表示按照哪种类型进行过滤,expression则表示需要进行过滤的表达式。

对于type属性而言,我们可选的值有如下五种类型。

16.5.1 annotation

如下示例表示我们将排除对使用了@Service注解进行标注的类的扫描,即不将对应的Class加入bean容器中。

<context:component-scan base-package="com.app">	
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

这里再重申一点,那就是使用Spring在扫描时默认是会扫描base-package指定的所有包及其子孙包下所有使用@Component、@Controller、@Service和@Repository及其其它使用这些注解进行标注的注解,使用这些注解进行标注过的Class都会被加入bean容器中。通过<context:include-filter/>我们可以将其它不在这些范畴的Class加入到bean容器中,通过<context:exclude-filter/>我们可以将在这个范畴的Class不纳入到加入bean容器中的范畴。

所以我们也可以通过<context:include-filter/>来将对使用了某种类型的注解进行标注的Class加入bean容器中。如下示例,我们将之前介绍的@MyComponent定义成一个普通的注解,然后将其纳入到bean容器的范畴即可让Spring将使用@MyComponent进行标注的Class加入到bean容器中。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {

	public String value() default "";
	
}
<context:component-scan base-package="com.app">	
	<context:include-filter type="annotation" expression="com.app.MyComponent"/>
</context:component-scan>

16.5.2 assignable

assignable表示根据继承的类或实现的接口进行扫描。如下表示将扫描所有继承或实现java.lang.Object的类,并将其加入bean容器,通过这种方式我们可以让Spring将某些包下的所有的类都加入Spring的bean容器。

<context:component-scan base-package="com.app">	
	<context:include-filter type="assignable" expression="java.lang.Object"/>
</context:component-scan>

16.5.3 aspect

当指定type为aspect时表示将以Spring定义切面的形式来指定对应的需要进行匹配的Class。如下示例则表示匹配com.app包及其子孙包下所有名称以Bean开头的Class,并将它们加入bean容器。使用该type时需要将spring-aspects-xxx.jar加入类路径下。

<context:component-scan base-package="com.app">	
	<context:include-filter type="aspectj" expression="com.app..Bean*"/>
</context:component-scan>

16.5.4 regex

regex表示将采用正则表达式进行匹配。如下示例就表示通过正则表达式匹配所有的Class的名称以Bean开始的类,并将它们加入bean容器中(点“.”在正则表达式中代表任意字符)。

<context:component-scan base-package="com.app">	
	<context:include-filter type="regex" expression=".*\.Bean.*"/>
</context:component-scan>

16.5.5 custom

custom表示使用自己定义的TypeFilter进行匹配。下面来看一个示例,我们来定义一个自己的TypeFilter,其实现简单的根据Class的名称进行匹配,只要Class的名称中包含Bean即表示匹配成功。

public class MyTypeFilter implements TypeFilter {

	public boolean match(MetadataReader metadataReader,
			MetadataReaderFactory metadataReaderFactory) throws IOException {
		String className = metadataReader.getClassMetadata().getClassName();
		return className.contains("Bean");
	}

}

之后我们在定义filter时就可以通过type指定为custom,然后对应的expression属性指定我们的自定义TypeFilter即可。

<context:component-scan base-package="com.app">	
	<context:include-filter type="custom" expression="com.app.MyTypeFilter"/>
</context:component-scan>

(注:本文是基于Spring4.1.0所写)