这里我们尝试总结开发中常用的那些注解使用与区别。
【1】定义/注入bean的注解
① @Component
标明带该注解的类是“组件”。当使用基于注解的配置和类路径扫描时,此类类被视为自动检测的候选类。@Component 注解作用于类,通常是通过路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。
如下所示,其只有一个属性value用来定义组件名称。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
对应的xml配置标签<context:component-scan>
base-package
属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包里及其子包中的所有类。当需要扫描多个包时, 可以使用逗号分隔。
如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern
属性过滤特定的类,示例:
<context:include-filter>
子节点表示要包含的目标类
<context:exclude-filter>
子节点表示要排除在外的目标类
<context:component-scan>
下可以拥有若干个 <context:include-filter>
和 <context:exclude-filter>
子节点。
<context:component-scan>
元素还会自动注册 AutowiredAnnotationBeanPostProcessor
实例, 该实例可以自动装配具有 @Autowired 和 @Resource 、@Inject
注解的属性。
② @Bean
作用于方法上的注解,用来定义/产生一个bean注入到spring容器中。
如下所示,注入一个bean到spring容器中,默认名字为myBean。
@Bean
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
如下所示,注入一个bean到spring容器中,自定义bean名称
// bean名称可以是一个数组
@Bean({"b1", "b2"})
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
与Profile, Scope, Lazy, DependsOn, Primary, Order结合,如下所示:
@Bean({"b1", "b2"})
@Profile("production")
@Scope("prototype")
@Lazy
@Primary
@Order(1)
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
通常,标注@Bean的方法是应用在标注@Configuration的类中,如下所示:
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
}
当然,标注@Bean的方法并非一定要在标注@Configuration的类,可以在一个标注@Component或者甚至一个普通类中。在这种情况下,在这种情况下,@Bean方法将以所谓的'lite'
模式进行处理。将会被作为一个类似的“工厂方法”进行调用,如下所示:
@Component
public class Calculator {
public int sum(int a, int b) {
return a+b;
}
@Bean
public MyBean myBean() {
return new MyBean();
}
}
如果想通过@Bean方法自定义一些BeanFactoryPostProcessor,如PropertySourcesPlaceholderConfigurer。那么建议使用静态方法以避免问题(因为这些处理器必须在容器生命周期早期阶段实例化):
@Bean
public static PropertySourcesPlaceholderConfigurer pspc() {
// instantiate, configure and return pspc ...
}
源码如下所示,属性比@Component多,表示其有更高的自定义性。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
// bean的名称
@AliasFor("value")
String[] name() default {};
// 是否允许通过名字或者type被其他bean注入,默认不允许
@Deprecated
Autowire autowire() default Autowire.NO;
// 是否作为自动注入的候选者
boolean autowireCandidate() default true;
// 初始化方法,默认为空
String initMethod() default "";
// 销毁方法
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
③ @Controller、@Service、@Repository
这三个与@Component作用一样,都是注入bean到容器中。不同的是,这三个是针对不同的使用场景所采取的特定功能化的注解组件。而@Component则可以应用于所有场景,是一个通用的Spring容器管理的单例bean组件。
① @Controller
标明当前组件是一个“Controller”,通常与@RequestMapping注解配合使用。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
// 组件名称
@AliasFor(annotation = Component.class)
String value() default "";
}
② @Service
标明当前组件是一个“Service”最初由领域驱动设计(Evans,2003)定义为“作为模型中独立的接口提供的操作,没有封装状态。”还可表示类是“业务服务外观”(在核心J2EE模式意义上)或类似的东西。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// 组件名称
@AliasFor(annotation = Component.class)
String value() default "";
}
③ @Repository
标明当前组件是一个“Repository”,最初由领域驱动设计(Evans,2003)定义为“用于封装存储、检索和搜索行为的机制,模拟对象集合”。实现“Data Access Object”等传统JavaEE模式的团队也可以将此原型应用于DAO类,不过在这样做之前,应该注意理解Data Access Object
和DDD-style repositories
之间的区别
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}
④ @Mapper
非Spring体系的,而是mybatis框架的,标记当前接口是MyBatis mappers。
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
public @interface Mapper {
// Interface Mapper
}
【2】装配类注解
@Autowired 是Spring注解,@Resource(JSR250)和@Inject(JSR330)是JAVA规范的注解。
① @Autowire
将构造函数、字段、setter方法或config方法标记为由Spring的依赖注入,是javax.inject.Inject的替代方案,提供了required
属性标明是否必须。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
// 是否必须,默认true
boolean required() default true;
}
@Autowire注解是被AutowiredAnnotationBeanPostProcessor
解析处理的,你不能在BeanPostProcessor
类型的bean中使用该注解。
@Authwired 使用注意事项:
- 默认情况下, 所有使用 @Authwired 注解的属性都需要被设置。
当 Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置 @Authwired 注解的 required 属性为 false。
- 默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作。
此时可以在 @Qualifier 注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter 已指定注入 Bean 的名称。
① 标记构造方法
作为spring bean使用时,只有一个构造器可以被@Autowired(“true”)声明。也就是使用该注解并且required属性为true只能标准在一个构造器上。
如果多个构造函数使用注解@Autowired(“false”),则它们将被视为自动装配的候选对象。将选择具有通过在Spring容器中匹配bean所能满足的最大数量依赖项的构造函数。如果没有一个候选者能够满足要求,那么将使用primary/default
构造函数(如果存在)。
类似地,如果一个类声明了多个构造函数,但没有一个用@Autowired注解,那么将使用primary/default
构造函数(如果存在)。
如果一个类一开始只声明一个构造函数,那么它将始终被使用,即使没有声明注解。带注解的构造函数不必是public的。
② 标记field
字段在构造bean之后,在调用任何配置方法之前被注入。这样的配置字段不必是public。这种场景也是我们经常使用的,如下所示:
@Controller
@RequestMapping("/course")
public class CourseController {
private static final Logger logger= LoggerFactory.getLogger(CourseController.class);
@Autowired
SysMajorService majorService;
//...
}
③ 标记方法
配置方法可以有任意名称和任意数量的参数,每个参数都将与Spring容器中的匹配bean自动关联。也就是说@Bean的方法参数从容器中获取,这与默认不写@Autowired效果是一样的,都能自动装配。
Bean属性set方法实际上只是这种通用配置方法的一个特例。这样的配置方法不必是公共的。
④ 标记Parameters
尽管{@code@Autowired}从技术上讲可以在SpringFramework 5.0以来的单个方法或构造函数参数上声明,但框架的大多数部分都忽略了此类声明。核心Spring框架中唯一积极支持autowired parameters
的部分是spring-test
模块中的JUnit Jupiter
⑤ Multiple Arguments and ‘required’ Semantics
对于多参数构造函数或方法,required属性适用于所有参数。单个参数如果声明为Java-8的Java.util.Optional,或者从Spring Framework 5.0开始的@Nullable,或者在Kotlin中声明为NOTNULL参数类型,就会覆盖基本的“required”语义。
⑥ Arrays, Collections, and Maps
对于arrays 、Collection或Map类型,容器自动装配与声明的值类型匹配的所有bean。为此,map的键必须声明为类型 String,该类型将被解析为相应的bean名称。考虑到目标组件的org.springframework.core.ordered和 org.springframework.core.annotation.Order@Order值,将对此类容器提供的集合进行排序,否则将遵循它们在容器中的注册顺序。
也就是说@Authwired 注解可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配。@Authwired 注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean。当@Authwired 注解用在 java.util.Map 上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值。
⑦ @Primary注解
当没有使用@Qualifier注解,而又找到了多个该类型的bean时,@Primary注解让Spring进行自动装配的时候,默认使用首选的bean。
@Configuration
@ComponentScan({"com.web.service","com.web.dao",
"com.web.controller","com.web.bean"})
public class MainConifgOfAutowired {
@Primary
@Bean("bookDao")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
⑧ 回顾xml方式使用
以前我们可能使用xml注入bean并完成bean的依赖:
<bean id="address" class="com.web.autowire.Address"
p:city="Beijing" p:street="huilongguan" >
</bean>
<bean id="car" class="com.web.autowire.Car"
p:brand="Audi" p:price="500000.0">
</bean>
<!-- 手工装配 -->
<bean id="person" class="com.web.autowire.Person"
p:name="Audi" p:address-ref="address" p:car-ref="car">
</bean>
使用Autowire方式
<!-- Autowire byName -->
<bean id="person2" class="com.web.autowire.Person"
p:name="Audi" autowire="byName">
</bean>
<!-- Autowire byType -->
<bean id="person3" class="com.web.autowire.Person"
p:name="Audi" autowire="byType">
</bean>
② @Resource
Resource 注解标记应用程序所需的资源。此注解可以应用于应用程序组件类,也可以应用于组件类的字段或方法。当注解应用于字段或方法时,容器将在组件初始化时将请求的资源的实例注入应用程序组件。如果注解应用于组件类,则注解将声明应用程序将在运行时查找的资源。
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
//资源的JNDI名称。对于字段,默认值为字段名。对于方法,默认值是与方法对应的JavaBeans属性名。
//对于类注解,没有默认值,这一点必须明确。
String name() default "";
// 寻找的资源的名称
String lookup() default "";
// 资源的Java type。
Class<?> type() default java.lang.Object.class;
//资源的两种可能的验证类型。
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
// 资源是否可以被分享
boolean shareable() default true;
// 资源映射的名称
String mappedName() default "";
String description() default "";
}
@Resource 默认按名称装配,当找不到与名称匹配的 bean 时才按照类型进行装配。名称可以通过 name 属性指定,如果没有指定 name 属性,当注解写在字段上时,默认取字段名,当注解写在 setter 方法上时,默认取属性名进行装配。注意:如果 name 属性一旦指定,就只会按照名称进行装配。
@Autowire和@Qualifier配合使用效果和@Resource一样
@Autowired(required = false)
@Qualifier("myBean")
private MyBean myBean;
@Resource(name = "myBean")
private MyBean myBean;
@Resource 装配顺序
-
如果同时指定 name 和 type,则从容器中查找唯一匹配的 bean 装配,找不到则抛出异常;
-
如果指定 name 属性,则从容器中查找名称匹配的 bean 装配,找不到则抛出异常;
-
如果指定 type 属性,则从容器中查找类型唯一匹配的 bean 装配,找不到或者找到多个抛出异常;
-
如果不指定,则自动按照 byName 方式装配,如果没有匹配,则回退一个原始类型进行匹配,如果匹配则自动装配。
③ @Inject
需要导入javax.inject的包,和Autowired的功能一样但是没有required=false的功能,支持@Primary注解。
<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
实例如下:
@Service
public class BookService {
@Inject
private BookDao bookDao;
//...
}
【3】配置类注解
① @Configuration
常见的就是@Configuration。标明一个类声明一个或多个@Bean方法,并可由Spring容器处理以在运行时为这些Bean生成Bean定义和服务请求,如下所示:
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}
① Configuration源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
// 组件名称,自定义名称适用于当前类被作为一个组件进行扫描
//或者直接通过 AnnotationConfigApplicationContext获取
@AliasFor(annotation = Component.class)
String value() default "";
// @Bean方法是否应该被代理
boolean proxyBeanMethods() default true;
}
② 注入配置类
通过AnnotationConfigApplicationContext
或者其web变异体AnnotationConfigWebApplicationContext
来注入@Configuration类,如下所示:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
MyBean myBean = ctx.getBean(MyBean.class);
// use myBean ...
也可以通过xml文件注入配置类如下所示:
<beans>
<context:annotation-config/>
<bean class="com.acme.AppConfig"/>
</beans>
上面实例的<context:annotation-config/>
标签是必须的,这样才能够使ConfigurationClassPostProcessor
和其他注解管理的后置处理器生效以处理@Configuration classes。关于<context:annotation-config/>
更多信息可以参考博文SpringMVC中context:annotation-config与mvc:annotation-driven和context:component-scan区别详解。
③ 组件扫描
因为该注解被@Component进行了元注解,故而声明@Configuration注解的类也可以被作为一个普通组件进行扫描如<context:component-scan/>
,也可以像 普通的组件一样使用@Autowired或者@Inject。特别是,如果存在单个构造函数,则将透明地为该构造函数应用自动装配语义:
@Configuration
public class AppConfig {
private final SomeBean someBean;
public AppConfig(SomeBean someBean) {
this.someBean = someBean;
}
// @Bean definition using "SomeBean"
}
④ 使用@ComponentScan
如下所示,定义了@Configuration
的类还可以同时使用@ComponentScan
注解:
@Configuration
@ComponentScan("com.jane.app.services")
public class AppConfig {
// various @Bean definitions ...
}
⑤ 使用Environment API
外部环境中的值可以通过注入Environment
来获取,如下所示:
@Configuration
public class AppConfig {
@Autowired
Environment env;
@Bean
public MyBean myBean() {
MyBean myBean = new MyBean();
myBean.setName(env.getProperty("bean.name"));
return myBean;
}
}
⑥ 使用@PropertySource
@PropertySource可以根据需要加载指定的配置文件(@ConfigurationProperties 默认从全局配置文件获取配置),将配置文件中的属性注入到系统环境中。
@Configuration
@PropertySource("classpath:/com/jane/app.properties")
public class AppConfig {
@Inject
Environment env;
@Bean
public MyBean myBean() {
return new MyBean(env.getProperty("bean.name"));
}
}
⑦ @Value注解
⑥中我们使用env.getProperty("bean.name")
来获取beanName,那么我们也可以直接使用@Value注解来获取环境中的属性。
@Configuration
@PropertySource("classpath:/com/jane/app.properties")
public class AppConfig {
@Value("${bean.name}")
String beanName;
@Bean
public MyBean myBean() {
return new MyBean(beanName);
}
}
⑧ @Import注解
类似Spring XML中配置的<import>
标签一样,@Configuration配置类可以使用@Import注解进行组合。
@Configuration
public class DatabaseConfig{
@Bean
public DataSource dataSource(){
//instantiate,configureandreturnDataSource
}
}
@Configuration
@Import(DatabaseConfig.class)
public class AppConfig{
private final DatabaseConfig dataConfig;
public AppConfig(DatabaseConfig dataConfig){
this.dataConfig=dataConfig;
}
@Bean
public MyBean myBean(){
//reference thed ataSource()bean method
return new MyBean(dataConfig.dataSource());
}
}
⑨ @Profile
@Configuration类可以用@Profile注解标记,以指示只有在给定的一个或多个配置文件处于活动状态时才应处理它们。
@Profile("development")
@Configuration
public class EmbeddedDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return embedded DataSource
}
}
@Profile("production")
@Configuration
public class ProductionDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return production DataSource
}
}
前面提到过,也可以在方法级别与@Bean注解一起使用,如下所示:
@Configuration
public class ProfileDatabaseConfig {
@Bean("dataSource")
@Profile("development")
public DataSource embeddedDatabase() { ... }
@Bean("dataSource")
@Profile("production")
public DataSource productionDatabase() { ... }
}
⑩ @ImportResource引入外部xml配置
可以使用@ImportResource注解引入外部的spring xml配置,xml中定义的bean可以自动被注入。
@Configuration
@ImportResource("classpath:/com/jane/database-config.xml")
public class AppConfig {
@Inject
DataSource dataSource; // from XML
@Bean
public MyBean myBean() {
// inject the XML-defined dataSource bean
return new MyBean(this.dataSource);
}
}
(11) 使用嵌套的@Configuration
@Configuration
public class AppConfig {
@Inject
DataSource dataSource;
@Bean
public MyBean myBean() {
return new MyBean(dataSource);
}
@Configuration
static class DatabaseConfig {
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder().build();
}
}
}
类似于上述实例时,只需要注册AppConfig 。因为AppConfig
与DatabaseConfig
已经清晰,故而不需要再使用@Import(DatabaseConfig.class)
。
(12) Test支持
spring-test模块中的Spring TestContext framework提供了@ContextConfiguration,其可以接受一个component 类数组,通常是@Configuration或者@Component类。
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {AppConfig.class, DatabaseConfig.class})
public class MyTests {
@Autowired MyBean myBean;
@Autowired DataSource dataSource;
@Test
public void test() {
// assertions against myBean ...
}
}
(13) @Enable注解
使用@Enable注解使内置的Spring features 起作用,如@EnableAsync、@EnableScheduling、@EnableTransactionManagement、@EnableAspectJAutoProxy、@EnableWebMvc。
(14)@Configuration注解的一些约束
- 必须是类,不能是工厂方法返回的实例;
- 必须不能是final的,除非proxyBeanMethods设置为false;
- 配置类必须是非本地的(即可能不在方法中声明),native 标注的方法;
- 嵌套的@Configuration必须是static;
- @Bean方法不能返回任何configuration 类。
【4】功能类注解
① @ControllerAdvice
① ControllerAdvice定义
一种特殊的Component类,其内部声明的@ExceptionHandler、@InitBinder或者@ModelAttribute可以被@Controller类共享。
在SpringMVC常见组件之HandlerAdapter分析中我们从源码角度分析了,当ControllerAdviceBean定义了@ExceptionHandler、@InitBinder或者@ModelAttribute方法时,其如何被应用。
声明了@ControllerAdvice注解的类可以直接被声明为Spring Bean或者通过类路径自动扫描注入。这些bean可以通过实现Ordered接口或者使用@Order/@Priority注解来进行排序,前者(实现接口)优先于后者(注解)。在系统运行时,就会根据其order值进行使用。不过值得一提的是,PriorityOrdered接口在排序上的优先级低于Ordered。当然,如果@ControllerAdvice bean被声明为一个request-scoped或者session-scoped bean,Ordered就将不那么重要。
为了处理异常,通常在第一个@ControllerAdvice bean中声明一个标注了@ExceptionHandler注解的方法。对于模型属性和数据绑定初始化,也就是@ModelAttribute和@InitBinder方法将会遵循order顺序。
对于@ExceptionHandler方法,在特定advice bean的方法中,根异常匹配比只匹配当前异常更可取。但是,与低优先级通知bean上的任何匹配(无论是根级别还是具体异常级别)相比,高优先级通知上的具体异常匹配仍然是首选的。因此,请按照相应的顺序在高优先级的advice bean上声明primary root exception映射。
默认情况下,@ControllerAdvice类的方法是全局性的,针对所有controller起作用。可以使用诸如annotations
、basePackageClasses
或者basePackages
来定义目标范围。如果定义了多个筛选条件,那么将会以“OR”的逻辑含义起作用。这个判断逻辑可以从如下源码展示:
// org.springframework.web.method.HandlerTypePredicate#test
@Override
public boolean test(Class<?> controllerType) {
// 如果没有选择器,直接返回true
if (!hasSelectors()) {
return true;
}
// 否则根据选择器定义范围进行判断
else if (controllerType != null) {
for (String basePackage : this.basePackages) {
if (controllerType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, controllerType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
return true;
}
}
}
return false;
}
② ControllerAdvice 源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
// 定义扫描包数组
@AliasFor("basePackages")
String[] value() default {};
//扫描的包路径,如下所示
// @ControllerAdvice(basePackages = "org.my.pkg")}
// @ControllerAdvice(basePackages = {"org.my.pkg", "org.my.other.pkg"})}
@AliasFor("value")
String[] basePackages() default {};
// 扫描的包的类型数组
Class<?>[] basePackageClasses() default {};
// 适应的类型-controller只要符合其中一个类型,将会被作为目标对象
Class<?>[] assignableTypes() default {};
// 作用目标-注解类型数组。controller只要标注了数组中一个注解,将会被作为目标对象
Class<? extends Annotation>[] annotations() default {};
}
使用@ControllerAdvice 对异常处理可以参考博文SpringMVC中异常处理与ControllerAdvice捕捉全局异常