Spring--类路径扫描和Spring管理的组件

1. @Componet和模式注解

@Repository用于标注任何满足存储库角色或模式的类(也被称为DAO)。该标记的一个用途是异常的自动转换,如Exception Translation中所述。

Spring提供了更进一步的模式化注解:@Component,@Service@Controller@Component是任何Spring管理所组件的通用模式。@Repository,@Service@Controller@Component在具体情景下的特定表达(持久层,服务层和表现层)。因此,你可以使用@Component来标注你的组件类,但如果使用@Repository,@Service@Controller来标注它们,你的类将更适合通过工具来处理或者与切面相关联。比如,这些模式注解是理想的切入点。@Repository,@Service@Controller在Spring之后的发布版中可能有更多的语义。所以,当你为服务层选择@Component@Service注解时,@Service显然更加合适。同理,@Repository已经支持在你的持久层中对异常进行自动转换。

2. 使用元注解和组合式注解

Spring提供的很多注解都可以被用作元注解。元注解可以被用于定义另一个注解。例如,@Service注解使用了@Component作为元注解:

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

	// ...
}

你也可以将元注解组合使用,创建一个组合式注解。比如,SpringMVC中的@RestController注解就是由@Controller@ResponseBody组合而成的。

另外,组合式注解可以选择重新声明元注解中的属性以实现自定义。当你只想暴露元注解中的部分属性时,这特别有用。比如,Spring@SessionScope将scope的名称硬编码为session,但仍然允许自定义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;

}

你可以在不声明proxyMode的情况下使用@SessionScope

@Service
@SessionScope
public class SessionScopedService {
	// ...
}

你可以重写proxyMode的值:

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

更多详细内容,请参考Spring Annotation Programming Model

3. 自动检测和注册bean

Spring会自动检测模式化的类,并通过ApplicationContext注册对应的BeanDefinition实例。比如,下面的两个类将会被自动检测到:

@Service
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
	// implementation elided for clarity
}

为了能够自动检测到这些类,并注册对应的bean,你需要在你的@Configuration类上添加@ComponentScan,其中的basePackages属性表述这两个类所共有的父包。(你也可以用一个用逗号,分号或空格分开的列表来指定每个类的父包)。

当你使用SpringBoot时,启动类上的@SpringBootApplication实际上包括了@ComponentScan,Spring会自动扫描启动类所在的包。你也可以在自己的配置类上使用@ComponentScan来自定义包扫描。

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

简洁起见,可直接写为 @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
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://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>

此外,当你使用@ComponentScan时, AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor都会被隐式包含。也就是说这两个组件都会被自动检测到并装配。

你可以通过将annotation-config属性设置为false来禁用AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor的注册。

4. 使用过滤器自定义扫描

默认情况下,使用@Component,@Respository,@Service,@Controller,@Configuration或自定义注解标记的类才会成为候选组件。但是,你可以通过自定义过滤器来修改和扩展这一行为。你可以通过@ComponentScan上的includeFiltersexcludeFilters属性来添加它们(或者<context:component-scan>下的子标签<context:include-filter><context:exclude-filter>)。

Filter TypeExample ExpressionDescription
annotation(default)org.example.SomeAnnotation作为注解或元注解,在类定义或注解定义时使用
assignableorg.example.SomeClass用来创建目标组件的类,或者它的父类和实现的接口
aspectjorg.example..*Service+与目标组件相匹配的切面类
regexorg\.example\.Default.*根据目标组件的类名进行正则匹配
customorg.example.MyTypeFilterorg.springframework.core.type.TypeFilter 接口的自定义实现

下面的代码中会跳过所有@Repository标注的类,并使用stub下的repository(不需要通过注解声明为bean):

@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>

你可以在@ComponentScan中设置useDefaultFilters=false来禁用默认过滤器。

5. 在组件中定义 Bean

除了@Configuration标注的类之外,Spring中的其他组件中也可以定义bean:

@Component
public class FactoryMethodComponent {

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

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

你可以将@Lazy注解放置在标有 @Autowired @Inject 的注入点上。但如果需要对延迟加载过程有更精细的控制,尤其是和可选依赖结合使用,更推荐使用ObjectProvider<TargetBean>

标注了@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);
	}
}

名为privateInstance的bean的age属性将会被注入到protectedInstance方法中的country参数。这里值得关注的是@Value注解中SpEL的使用。

从 Spring 4.3 开始,你还可以声明一个类型为 InjectionPoint(或其更具体的子类:DependencyDescriptor)的工厂方法参数,以访问创建这个bean实例的注入点。下面的代码中,injectionPoint参数就包含了注入点的信息。

请注意,这仅适用于创建 bean 实例,而不适用于现有实例的注入(比如单例)。所以此功能更适用于prototype作用域的bean。

@Component
public class FactoryMethodComponent {

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

下面是一个可能的TestBean类定义:

public class TestBean {

    String value;
    public TestBean(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

测试代码如下:

private TestBean testBean;

@Autowired
public void setTestBean(TestBean testBean) {
    this.testBean = testBean;
}

@Test
void testInjectPointType() {
    System.out.println(testBean.getValue());
}

输出结果为:

prototypeInstance for public void com.weedien.ioccfg.IocCfgApplicationTests.setTestBean(com.weedien.ioccfg.bean.TestBean)

在Spring常规组件中定义的@Bean方法和@Configuration中定义的是有区别的。不同之处在于,对于@Component中定义的@Bean方法,当你调用bean的属性和方法时,不会通过CGLIB进行增强。

6. 给组件取名

组件的名称由BeanNameGenerator生成。默认情况下,组件的名称为小写的非限定类名。下面两个组件的名称分别为myMovieListermovieFinderImpl

@Service("myMovieLister")
public class SimpleMovieLister {
	// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
	// ...
}

你可以提供自定义的bean命名策略。定义一个类,比如MyNameGenerator,实现BeanNameGenerator接口,并提供一个默认无参构造函数。然后配置scanner的时候提供类名:

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

或者使用XML进行配置:

<beans>
	<context:component-scan base-package="org.example"
		name-generator="org.example.MyNameGenerator" />
</beans>

如果组件存在相同的非限定类名,则可以考虑定义一个默认将全限定类名作为bean名称的BeanNameGenerator。在Spring 5.2.3的org.springframework.context.annotation中提供了FullyQualifiedAnnotationBeanNameGenerator

7. 给组件提供scope

Spring管理的组件中,最常用的scope是singleton(默认值)。singleton作用域下全局共享同一个实例;prototype作用域下,每次请求注入时会创建一个新的实例。

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

在web中使用的scope请参阅Request, Session, Application, and WebSocket Scopes。你也可以组装自己的作用域注解,比如使用@Scope("prototype")作为元注解。

通过实现ScopeMetadataResolver接口,你可以自定义作用域解析策略。需要创建一个默认的无参构造器。然后你在配置scanner的时候提供类名。

@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>

当使用某个非单例的scope时,可能需要为scoped对象生成代理。 Scoped Beans as Dependencies中解释了原因。@ComponentScan中提供了scopedProxy属性,3个可选值为:no,interfacestargetClass

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
	// ...
}
<beans>
	<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

关于作用域的详细介绍,可参考:Spring–Bean的作用域

8. 使用@Qualifier

qualifier可以翻译为限定符或修饰符,可以看作是一种标签,可用于区分不同的bean,或者将多个bean划分为不同的类别。

通过使用@Qualifier或自定义的qualifier注解,可以对自动装配的候选者进行更加精细化的管理。

下面的示例中,qualifier注解声明在类定义上,与特定的类相互绑定,这意味着,通过这个类创建的多个bean都将具备这个属性。你也可以通过工厂方法(@Bean),为每个bean指定qualifier,这样一来,使用同一个类创建的多个bean就可以拥有不一样的qualifier。

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

下面使用的是自定义注解:

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

关于qualifier的详细介绍,可参考:Spring–使用Qualifiers微调

9. 为候选组件创建索引

在编译时创建静态的候选者列表有助于提高启动性能。在这种模式下,作为组件扫描目标的所有模块都必须使用这种机制。

ApplicationContext检测到索引时,会自动使用索引,而不再扫描类路径。

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

spring-context-indexer会生成一个META_INF/spring.components文件。

当你在IDE中使用这种模式的时候,spring-context-indexer需要注册成一个注解处理器,以确保当候选者发生变化时,索引能及时更新。

META_INF/spring.components存在于类路径时,索引会自动开启。如果索引只可用于部分库(或用例)但无法为整个应用构建时,你可以通过jvm参数或者 SpringPropertiesspring.index.ignore设为true,回退到常规的类路径扫描(仿佛没有提供索引)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值