Spring第一版中定义组件的注解只有一个 @Component
,从2.5版本开始,根据分层的需要,把它拆成了四个,达到所见即所得的目标,可以让开发人员一看到注解,立即知道类的性质,它们分别是@Component
、@Repository
、@Service
、@Controller
。另外用于组件依赖注入的注解有3个,分别是@Resource
、@Autowired
、@Qualifier
。
1、定义组件(bean)的注解
当你需要定义某个类为一个bean时,需要在类名之前使用定义组件(bean)的注解,如@Service(“XXX”),这表示将该类定义一个bean,bean名称为 XXX
。以下是@Component、@Repository、@Service、@Controller注解的详细介绍。
1.1 @Component 注解
该组件注解为通用注解,被该注解描述的类将被loC容器管理并实例化。一般情况下,我们会明确使用@Repository、@Service、@Controller注解中的一个来定义组件,只有当一个组件不好归类的时候,我们才使用@Component注解来进行标注。
- 带此注解的类可以看为组件,当使用基于注解的配置和类路径扫描的时候,这些类就会被实例化。
- @Repository、@Service、@Controller这3个注解和@Component注解功能是等效的,从语义方面来讲是@Component的细化。
- 不指定bean的名称,默认为类名首字母小写。
代码示例:
- 不指定bean的名称,默认bean名称为类名首字母小写,该例中为
iocComponentImpl
。
import org.springframework.stereotype.Component;
@Component
public class IocComponentImpl implements IocComponent {
......
}
- 指定bean的名称,该例中指定为
iocComponent
。
import org.springframework.stereotype.Component;
@Component("iocComponent")
public class IocComponentImpl implements IocComponent {
......
}
1.2 @Controller 注解
该组件注解为语义注解,说明当前类是MVC应用中的控制器类,主要用于标注控制层组件(即通常定义的controller层)。将当前修饰的类注入Spring IOC容器,使得从该类所在的项目启动过程中,这个类能被实例化.
- @Controller在SpringMVC里面有强制的要求,即SpringMVC的表示层必须使用@Controller组件注解。
- 其他几个注解用乱了是不会报错的,一般不建议乱用,如果不遵守规范,别人就无法跟你协同开发了。
代码示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class IocAction {
@Autowired
private IocService service;
public void add(){
service.add();
}
}
1.3 @Service 注解
该组件注解为语义注解,说明当前类是Service业务服务类,主要用于标注业务层组件(即通常定义的service层)。该注解默认生成的bean名称为类名首字母小写,但是还有一个特殊情况:当类的名字是两个或两个以上的大写字母开头的话,bean名称会与类名保持一致。如类名为 IIocServiceImpl
,如果加了 @Service
注解,则bean名称和类名一致,为 IIocServiceImpl
。
我们可以看下源码:
代码示例:
- 不指定bean的名称,默认bean名称为类名首字母小写,该例中为
iocServiceImpl
。
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class IocServiceImpl implement impliments IIocService{
@Resource
private IIocDao iocDao;
public void add(){
iocDao.add();
}
}
- 指定bean的名称,该例中指定为
iocService
。
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("iocService")
public class IocServiceImpl implement impliments IIocService{
@Resource
private IIocDao iocDao;
public void add(){
iocDao.add();
}
}
1.4 @Repository 注解
该组件注解为语义注解,说明当前类用于Dao业务持久层,主要用于标注数据访问组件(即通常定义的DAO层)。
- 不指定bean的名称,默认bean名称为类名首字母小写,该例中为
iocDaoImpl
。
import org.springframework.stereotype.Repository;
@Repository
public class IocDaoImpl implements IIocDao{
public void add(){
System.out.println("调用了dao");
}
}
- 指定bean的名称,该例中指定为
iocDao
。
import org.springframework.stereotype.Repository;
@Repository("iocDao")
public class IocDaoImpl implements IIocDao{
public void add(){
System.out.println("调用了dao");
}
}
1.5 总结
如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用上述注解对分层中的类进行注释。
- @Controller用于标注控制层组件,即Controller组件。
- @Service用于标注业务层组件,即Service组件。
- @Repository用于标注数据访问组件,即DAO组件
- @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
2、用于组件依赖注入的注解
用于组件依赖注入的注解有3个,分别是@Resource
、@Autowired
、@Qualifier
。
2.1 @Autowired 注解
@Autowired 注解全路径是 org.springframework.beans.factory.annotation.Autowired
,可以看出来这是spring的包。它可以对类成员变量、方法及构造函数进行标注,完成自动注入/装配的工作。通过 @Autowired 注解的使用来消除 set ,get方法。
在Spring服务启动的时候,会根据声明先扫描配置文件中的bean(注解配置同理), 再根据bean中类的全限定名去寻找它,一般是我们自己声明的pojo(实体类), 然后这时如果有@Autowired注解则会根据类型寻找其它的被Spring托管的bean进行自动注入, 调用默认的构造函数。
-
@Autowired 注解是通过 byType 的方式去注入的,使用该注解,要求接口只能有一个实现类。
-
@Autowired 注解不支持设置别名,如果一个接口有多个实现类,则可以与@Qualifier(“alias”) 注解配合使用。
代码示例:
- 接口:IAnimal
public Interface IAnimal{
......
}
- 实现类:DogImpl ,实现了IAnimal接口
@Service("dogImpl")
public class DaoImpl impliments IAnimal{
...
}
- 业务类:AnimalController
public class AnimalController {
@Autowired
private IAnimal dogImpl;
......
}
假如有一个“动物”的接口 IAnimal, DogImpl类实现了接口 IAnimal,且该接口只有 DogImpl这一个实现类,那么在使用@Autowired 注解注入实现类的时候,我们使用的是实现类的接口(像上面程序步骤 3 展示的那样),这时Spring会按 byType 的方式寻找接口的实现类,并将其注入。
假如有另一个实现类 CatImpl 也实现了接口 IAnimal,这时候再按上面的方式去注入的话,IAnimal接口在同时存在两个实现类的情况下,会出现什么情况呢?
- 这时候spring会报错。这是因为 @Autowired 的注入方式是 byType 注入,当要注入的类型在容器中存在多个时,Spring无法确定要注入哪个实现类,所以会报错。
- 这种场景下,只能通过 byName 注入的方式。可以使用 @Resource 或 @Qualifier 注解。具体方式参考后续 2.2 @Qualifier 注解 或 ## 2.3 @Qualifier 注解。
2.2 @Qualifier 注解
@Qualifier注解的用处是当一个接口有多个实现的时候,可以用来指定具体调用哪个类的实现(通过byName注入)。@Qualifier的参数为我们之前定义bean名称。使用 @Autowired 注解是通过 byType 注入,如果需要 byName(byName就是通过id去标识)注入,需要增加 @Qualifier 注解。一般在候选Bean数目不为1时应该加 @Qualifier 注解。
-
Qualifier的意思是合格者,通过这个注解,表明了哪个实现类才是我们所需要的。
-
在默认情况下使用 @Autowired 注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。
-
当找不到一个匹配的 Bean 时,Spring 容器将抛出BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。
-
和找不到一个类型匹配 Bean 相反的一个错误是:如果 Spring 容器中拥有多个候选 Bean,Spring 容器在启动时也会抛出 BeanCreationException 异常。
-
Spring 允许我们通过 @Qualifier 注解指定注入 Bean 的名称,这样歧义就消除了。
在 2.1 @Autowired 注解 中接口 IAnimal有多个实现,若使用 @Autowired 注解自动注入,则spring容器启动的时候会报无法创建bean的异常,可以使用 @Qualifier 注解指定注入 Bean 的名称,这样歧义就消除了。具体示例如下:
- 注意 @Qualifier 注解需要与 @Autowired 注解结合使用。
代码示例:
public class AnimalController {
@Autowired
@Qualifier("dogImpl") //实现类1中 @Service注解中标定的名称
private IAnimal dogImpl;
......
}
@Qualifier 注解也可以在方法签名中使用:
- @Qualifier(“office”)中的office是Bean的名称,所以@Autowired和@Qualifier结合使用时,自动注入的策略就从byType转变成byName了。
@Autowired
public void setOffice(@Qualifier("office")Office office) {
this.office =office;
}
@Autowired可以对成员变量、方法以及构造函数进行注释,而@Qualifier的标注对象是成员变量、方法入参、构造函数入参。正是由于注释对象的不同,所以Spring不将 @Autowired和@Qualifier统一成一个注释类。
@Qualifier 只能和@Autowired 结合使用,是对@Autowired有益的补充。
一般来讲,@Qualifier对方法签名中入参进行注释会降低代码的可读性,而对成员变量注释则相对好一些。
2.3 @Resource 注解
@Resource 注解的作用相当于 @Autowired 注解,只不过 @Autowired 按 byType 自动注入,而 @Resource 默认按 byName 自动注入罢了。@Resource 注解的全路径是 javax.annotation.Resource
,可以看到它是javaEE中的包,并不是spring中的。
- @Resource 有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
- @Resource 可以通过 byName 和 byType的方式注入,默认先按 byName的方式进行匹配,如果匹配不到,再按 byType的方式进行匹配。
- @Resource 如果使用name属性,则使用 byName 的自动注入策略,而使用type属性时则使用 byType 自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用 byName 自动注入策略。
@Resource装配顺序
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
在 2.1 @Autowired 注解 中接口 IAnimal有多个实现,若使用 @Autowired 注解自动注入,则spring容器启动的时候会报无法创建bean的异常,可以使用 @Resource 注解指定注入 Bean 的名称,这样歧义就消除了。具体示例如下:
- 注意:@Resource 注解单独使用,不需要和 @Autowired 注解结合使用。
代码示例:
public class AnimalController {
@Resource(name="dogImpl") //实现类1中 @Service注解中标定的名称
private IAnimal dogImpl;
......
}
- 需要注意:
@Resource(name="dogImpl")
中需要指定name
。
其他代码示例:
- 当@Resource不设置任何值时,isDefaultName会为true,当对应字段名称的bean或者BeanDefinition已存在时会走byName的形式,否则走byType的形式。
public class AnimalController {
@Resource
private IAnimal dogImpl;
......
}
- 只指定了type属性时,首先还是会通过注解的属性名去匹配bean,只有当对应的名称不存在对应的bean或BeanDefinition,才会通过byType找到唯一的一个类型匹配的bean。
- isDefaultName的值还是为true,所以执行的还是resolveDependency方法。但是由于添加了类型的限制,所以也就不会匹配到多个Bean,而产生异常。
public class AnimalController {
@Resource(type = DaoImpl.class)
private IAnimal dogImpl;
......
}
- 只指定了name属性,会执行getBean方法,根据指定的name来获取bean;
public class AnimalController {
@Resource(name="dogImpl")
private IAnimal dogImpl;
......
}
- 既指定了name属性,又指定了type属性,会先根据 name 查找对应的bean,然后进行 type 类型比较。
- 示例中 name 属性被设置为 dogImpl,isDefaultName 变为false,执行 resolveBeanByName 方法,但是由于找不到对应beanName为 dogImpl,但是类型又为 Apple.class 的 bean,还是会抛出异常。所以既指定 name,又指定 type 时,必须 2 个属性都完全正确。
public class AnimalController {
@Resource(name="dogImpl", type = Apple.class)
private IAnimal dogImpl;
......
}
多种@Resource不同使用情况所执行方法如下所示;
2.4 总结
- @Resource是java的注解,@Autowired是spring的注解。
- @Autowired是byType注入的,可以独立使用。
- @Resource默认按name注入,可以通过name和type属性进行选择性注入。
- @Qualifier是在无法按类型区分(一个接口有多个实现类)的情况下和@Autowired结合使用,这俩个注解组合起来相当于@Resource注解。
- @Qualifier的作用是在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用(但是在给方法参数注入时可以单独使用),因此@Qualifier注解很受限制,因此用的不是很多。@Qualifier常常组合@Autowired一起使用,用来指明具体名字的自动装配
- @Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。如果没有指定name,则回退为按照byType注入。
评论区