Spring | 3.10 类路径扫描和管理组件

3.10 类路径扫描和管理组件

本章中的大多数示例使用XML指定配置元数据,这些元数据在Spring容器中生成每个BeanDefinition。上一节(第3.9节,“基于注解的容器配置”)演示了如何通过源级注解提供大量配置元数据。然而,即使在这些示例中,“基本”bean定义也是在XML文件中显式定义的,而注解只驱动依赖项注入。本节描述通过扫描类路径隐式检测候选组件的选项。候选组件是与筛选条件匹配的类,并且具有与容器注册的相应bean定义。这样就不需要使用XML来执行bean注册;相反,你可以使用注解(例如@Component)、AspectJ类型表达式或你自己的自定义过滤器标准来选择哪些类将具有在容器中注册的bean定义。

注意:从Spring 3.0开始,Spring JavaConfig项目提供的许多特性都是Spring核心框架的一部分。这允许你使用Java定义bean,而不是使用传统的XML文件。查看@Configuration、@Bean、@Import和@DependsOn注解,了解如何使用这些新特性。

3.10.1 @Component和进一步的原型注解

@Repository注解是任何满足数据库角色或原型(也称为数据访问对象或DAO)的类的标记。该标记的用途之一是自动翻译异常,如第16.2.2节“异常翻译”所述。
Spring提供了更多的原型注解:@Component, @Service和@Controller。@Component是任何spring-managed组件的通用构造型。@Repository、@Service和@Controller是@Component针对更具体用例的专门化,例如,分别在持久性层、服务层和表示层。因此,你可以使用@Component来注解组件类,但是通过使用@Repository、@Service或@Controller来注解它们,你的类更适合通过工具进行处理或与方面关联。例如,这些原型注解是切入点的理想目标。在Spring框架的未来版本中,@Repository、@Service和@Controller也可能携带额外的语义。因此,如果你在使用@Component或@Service作为服务层之间进行选择,@Service显然是更好的选择。类似地,如上所述,@Repository已经被支持作为持久层中自动异常转换的标记。

3.10.2 元注解

Spring提供的许多注解都可以在你自己的代码中用作元注解。元注解只是可以应用于另一个注解的注解。例如,上面提到的@Service注解使用@Component进行元注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

	// ....
}

还可以组合元注解来创建组合注解。例如,Spring MVC中的@RestController注解由@Controller和@ResponseBody组成

此外,组合注解可以选择性地从元注解重新声明属性,以允许用户自定义。当你只想公开元注解属性的子集时,这尤其有用。例如,Spring的@SessionScope注解将范围名称硬编码到会话,但仍然允许定制proxyMode。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

	/**
	 * Alias for {@link Scope#proxyMode}.
	 * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
	 */
	@AliasFor(annotation = Scope.class)
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}
@SessionScope可以在不声明proxyMode的情况下使用:
@Service
@SessionScope
public class SessionScopedService {
	// ...
}

或使用proxyMode的重写值如下:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
	// ...
}

要了解更多细节,请参考Spring注解编程模型。

3.10.3 自动检测类并注册bean定义

Spring可以自动检测原型类,并在ApplicationContext中注册相应的BeanDefinitions。例如,以下两个类可以进行自动检测:

@Service
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	@Autowired
	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

}

@Repository
public class JpaMovieFinder implements MovieFinder {
	// implementation elided for clarity
}

要自动检测这些类并注册相应的bean,需要将@ComponentScan添加到@Configuration类中,其中basePackages属性是这两个类的公共父包。(或者,你可以指定一个逗号/分号/空格分隔的列表,其中包含每个类的父包。)

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
   	...
}

注意:为了简单起见,上面可能使用了注解的value属性,即@ComponentScan(“org.example”)。

下面是使用XML的一种替代方法:

<?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: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="org.example"/>

</beans>

注意:使用< context:component-scan>隐式地启用了< context:annotation-config>的功能。当使用< context:component-scan>时,通常不需要包含< context:annotation-config>元素。

注意:扫描类路径包需要类路径中存在相应的目录条目。当你使用Ant构建JAR时,请确保你没有激活JAR任务的file -only开关。此外,在某些环境中,类路径目录可能不会根据安全策略公开,例如JDK 1.7.0_45或更高版本上的独立应用程序(这需要在清单中设置“可信库”;见http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

此外,在使用组件扫描(component-scan)元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都是隐式包含的。这意味着这两个组件是自动检测和连接在一起的——所有这些都不需要用XML提供任何bean配置元数据。

注意:通过包含带false值的annotation-config属性,可以禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。

3.10.4 使用过滤器自定义扫描

默认情况下,使用@Component注解的类、使用@Repository注解的类、使用@Service注解的类、使用@Controller注解的类或使用@Component注解的自定义注解是唯一检测到的候选组件。但是,你可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan注解的includeFilters或excludeFilters参数(或作为component-scan元素的include-filter或excludefilter子元素)。每个筛选器元素都需要类型和表达式属性。下表描述了过滤选项。
表3.5 过滤器类型

过滤器类型示例表达式描述
annotation (默认)org.example.SomeAnnotation要在目标组件的类型级别上显示的注解。
assignableorg.example.SomeClass目标组件可分配给(扩展/实现)的类(或接口)。
aspectjorg.example…*Service+要由目标组件匹配的AspectJ类型表达式。
regexorg.example.Default.*由目标组件类名匹配的正则表达式。
customorg.example.MyTypeFilter实现org.springframework.core.TypeFilter接口的自定义

下面的示例显示了忽略所有@Repository注解而使用“stub”存储库的配置。

@Configuration
@ComponentScan(basePackages = "org.example",
   		includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
   		excludeFilters = @Filter(Repository.class))
public class AppConfig {
   	...
   }

使用XML进行等效:

<beans>
	<context:component-scan base-package="org.example">
		<context:include-filter type="regex"
				expression=".*Stub.*Repository"/>
		<context:exclude-filter type="annotation"
				expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>
</beans>

注意:你还可以通过在注解上设置useDefaultFilters=false或提供use-default-filters="false"作为< component-scan/>元素的属性来禁用默认过滤器。这实际上将禁用自动检测带有@Component、@Repository、@Service、@Controller或@Configuration注解的类。

3.10.5 在组件中定义bean元数据

Spring组件还可以向容器提供bean定义元数据。你可以使用@Configuration注解类中用于定义bean元数据的@Bean注解来完成此操作。下面是一个简单的例子:

@Component
public class FactoryMethodComponent {

	@Bean
	@Qualifier("public")
	public TestBean publicInstance() {
		return new TestBean("publicInstance");
	}

	public void doWork() {
		// Component method implementation omitted
	}

}

该类是一个Spring组件,其doWork()方法中包含特定于应用程序的代码。但是,它还提供了一个bean定义,其中包含一个引用publicInstance()方法的工厂方法。@Bean注解标识工厂方法和其他bean定义属性,例如通过@Qualifier注解标识限定符值。可以指定的其他方法级别注解有@Scope、@Lazy和定制限定符注解。

注意:除了用于组件初始化之外,@Lazy注解还可以放在用@Autowired或@Inject标记的注入点上。在这种情况下,它会导致延迟解析代理的注入。

如前所述,支持自动装配字段和方法,另外还支持@Bean方法的自动装配:

@Component
public class FactoryMethodComponent {

	private static int i;

	@Bean
	@Qualifier("public")
	public TestBean publicInstance() {
		return new TestBean("publicInstance");
	}

	// use of a custom qualifier and autowiring of method parameters
	@Bean
	protected TestBean protectedInstance(
			@Qualifier("public") TestBean spouse,
			@Value("#{privateInstance.age}") String country) {
		TestBean tb = new TestBean("protectedInstance", 1);
		tb.setSpouse(spouse);
		tb.setCountry(country);
		return tb;
	}

	@Bean
	private TestBean privateInstance() {
		return new TestBean("privateInstance", i++);
	}

	@Bean
	@RequestScope
	public TestBean requestScopedInstance() {
		return new TestBean("requestScopedInstance", 3);
	}

}

该示例将String方法参数country自动拖拽到另一个名为privateInstance的bean上的Age属性的值。Spring表达式语言元素通过符号#{< Expression >}定义属性的值。对于@Value注解,表达式解析器预先配置为在解析表达式文本时查找bean名称。
从Spring Framework 4.3开始,你还可以声明一个类型为InjectionPoint的工厂方法参数(或其更具体的子类DependencyDescriptor),以便访问触发当前bean创建的请求注入点。注意,这只适用于bean实例的实际创建,而不适用于现有实例的注入。因此,该特性对于原型范围内的bean最有意义。对于其他范围,工厂方法只会看到触发在给定范围内创建新bean实例的注入点:例如,触发创建惰性单例bean的依赖项。在这些场景中使用提供的具有语义关怀的注入点元数据。

@Component
public class FactoryMethodComponent {

	@Bean @Scope("prototype")
	public TestBean prototypeInstance(InjectionPoint injectionPoint) {
		return new TestBean("prototypeInstance for " + injectionPoint.getMember());
	}
}

常规Spring组件中的@Bean方法与Spring @Configuration类中的对应方法处理方式不同。不同之处在于,@Component类没有使用CGLIB增强来拦截方法和字段的调用。CGLIB代理是在@Configuration类中调用@Bean方法中的方法或字段来创建对协作对象的bean元数据引用的方法;这些方法不是用普通的Java语义调用的,而是遍历容器,以便提供通常的Spring bean生命周期管理和代理,即使在通过对@Bean方法的编程调用引用其他bean时也是如此。相反,在普通@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,不需要使用特殊的CGLIB处理或其他约束。

注意:你可以将@Bean方法声明为静态方法,这样就可以调用它们,而不需要创建包含它们的配置类作为实例。这在定义后处理器bean时特别有意义,例如BeanFactoryPostProcessor或BeanPostProcessor类型的bean,因为这样的bean将在容器生命周期的早期初始化,并且应该避免在那时触发配置的其他部分。
注意,对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也不会被拦截(参见上面)。这是由于技术限制:CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法将具有标准的Java语义,从而直接从工厂方法本身返回一个独立的实例。
@Bean方法的Java语言可见性对Spring容器中生成的bean定义没有直接影响。你可以自由地声明你认为适合于非@ configuration类的工厂方法,也可以在任何地方声明静态方法。但是,@Configuration类中的常规@Bean方法需要被覆盖,也就是说,它们不能声明为私有或final。
@Bean方法还将在给定组件或配置类的基类上发现,以及在组件或配置类实现的接口中声明的Java 8缺省方法上发现。这在组合复杂的配置安排时提供了很大的灵活性,从Spring 4.2开始,甚至可以通过Java 8缺省方法进行多重继承。
最后,请注意,一个类可能包含同一个bean的多个@Bean方法,这是根据运行时可用的依赖关系使用的多个工厂方法的一种安排。这与在其他配置场景中选择“最贪婪”构造函数或工厂方法的算法相同:具有最大数量可满足依赖关系的变体将在构建时选择,类似于容器如何在多个@Autowired构造函数之间进行选择。

3.10.6 命名自动检测的组件

当一个组件作为扫描过程的一部分被自动检测时,它的bean名称由扫描器所知道的BeanNameGenerator策略生成。默认情况下,任何包含名称值的Spring原型注解(@Component、@Repository、@Service和@Controller)都会将该名称提供给相应的bean定义。
如果这样的注解不包含名称值或任何其他检测到的组件(例如自定义过滤器发现的组件),则默认bean名称生成器返回未大写的非限定类名。例如,如果检测到以下两个组件,名称将是myMovieLister和movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
	// ...
}

@Repository
public class MovieFinderImpl implements MovieFinder {
	// ...
}

注意:如果不希望依赖默认的bean命名策略,可以提供自定义bean命名策略。首先,实现BeanNameGenerator接口,并确保包含一个默认的no-arg构造函数。然后,在配置扫描器时提供完全限定的类名:

@Configuration
   @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
   public class AppConfig {
   	...
   }
<beans>
	<context:component-scan base-package="org.example"
		name-generator="org.example.MyNameGenerator" />
</beans>

一般来说,当其他组件可能显式引用该名称时,请考虑使用注解指定名称。另一方面,只要容器负责连接,自动生成的名称就足够了。

3.10.7 为自动检测组件提供范围

与一般的spring管理组件一样,自动检测组件的默认且最常见的作用域是单例的。但是,有时你需要一个不同的范围,可以通过@Scope注解来指定。只需在注解中提供作用域的名称:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
	// ...
}

有关特定于web的范围的详细信息,请参见3.5.4节“请求、会话、应用程序和WebSocket范围”。

要为范围解析提供自定义策略,而不是依赖于基于注解的方法,请实现ScopeMetadataResolver接口,并确保包含一个默认的无arg构造函数。然后,在配置扫描器时提供完全限定的类名:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
   	...
   }
<beans>
	<context:component-scan base-package="org.example"
			scope-resolver="org.example.MyScopeResolver" />
</beans>

当使用某些非单例范围时,可能需要为范围对象生成代理。推理在“范围bean作为依赖项”一节中进行了描述。为此,component-scan元素上有一个作用域代理属性。这三个可能的值是:no、interface和targetClass。例如,下面的配置将导致标准JDK动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
   	...
   }
<beans>
	<context:component-scan base-package="org.example"
		scoped-proxy="interfaces" />
</beans>
3.10.8 使用注解提供限定符元数据

@Qualifier注解将在3.9.4节中讨论,“使用限定符微调基于注解的自动装配”。本节中的示例演示了在解析autowire候选项时使用@Qualifier注解和自定义qualifier注解提供细粒度控制。由于这些示例基于XML bean定义,所以使用XML中bean元素的限定符或元元素在候选bean定义上提供了限定符元数据。当依赖类路径扫描来自动检测组件时,你可以在候选类上提供带有类型级别注解的限定符元数据。下面三个例子演示了这种技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
	// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
	// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
	// ...
}

注意:与大多数基于注解的替代方法一样,请记住注解元数据绑定到类定义本身,而XML的使用允许同一类型的多个bean在限定符元数据中提供变体,因为该元数据是按实例而不是按类提供的。

3.10.9 生成候选组件索引

不需要扫描类路径来查找组件,还可以在编译时生成索引。当ApplicationContext检测到这样的索引时,它将自动使用它,而不是扫描类路径。这可以减少刷新ApplicationContext所需的时间,因为扫描大型应用程序可能会花费大量时间。
要生成索引,只需向每个包含组件的模块添加额外的依赖项,这些组件是组件扫描指令的目标:

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-indexer</artifactId>
		<version>5.0.0.M5</version>
		<optional>true</optional>
	</dependency>
</dependencies>

或者使用Gradle:

dependencies {
	compileOnly("org.springframework:spring-context-indexed:5.0.0.M5")
}

该过程将生成一个META-INF/spring.components文件,该文件将包含在jar中。

注意:当在类路径上找到META-INF/spring.components时,将自动启用该索引。如果某个索引对某些库(或用例)部分可用,但不能为整个应用程序构建,则可以通过设置spring.index返回到常规的类路径安排(即根本不存在索引)。忽略true,无论是作为系统属性还是在类路径根目录下的spring.propertie文件。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值