赞
踩
@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
已经支持在你的持久层中对异常进行自动转换。
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
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
时, AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
都会被隐式包含。也就是说这两个组件都会被自动检测到并装配。
你可以通过将
annotation-config
属性设置为false
来禁用AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
的注册。
默认情况下,使用@Component
,@Respository
,@Service
,@Controller
,@Configuration
或自定义注解标记的类才会成为候选组件。但是,你可以通过自定义过滤器来修改和扩展这一行为。你可以通过@ComponentScan
上的includeFilters
和excludeFilters
属性来添加它们(或者<context:component-scan>
下的子标签<context:include-filter>
和<context:exclude-filter>
)。
Filter Type | Example Expression | Description |
---|---|---|
annotation(default) | org.example.SomeAnnotation | 作为注解或元注解,在类定义或注解定义时使用 |
assignable | org.example.SomeClass | 用来创建目标组件的类,或者它的父类和实现的接口 |
aspectj | org.example..*Service+ | 与目标组件相匹配的切面类 |
regex | org\.example\.Default.* | 根据目标组件的类名进行正则匹配 |
custom | org.example.MyTypeFilter | org.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
来禁用默认过滤器。
除了@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进行增强。
组件的名称由BeanNameGenerator
生成。默认情况下,组件的名称为小写的非限定类名。下面两个组件的名称分别为myMovieLister
和movieFinderImpl
:
@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
。
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
,interfaces
和targetClass
:
@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的作用域
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微调
在编译时创建静态的候选者列表有助于提高启动性能。在这种模式下,作为组件扫描目标的所有模块都必须使用这种机制。
当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参数或者SpringProperties
将spring.index.ignore
设为true
,回退到常规的类路径扫描(仿佛没有提供索引)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。