这里我们尝试总结开发中常用的那些注解使用与区别。
【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模式意义上)或类似的东西。
@Targ@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" }