共计 2672 个字符,预计需要花费 7 分钟才能阅读完成。
每个优秀的人,都有一段沉默的时光。那段时光,是付出了很多努力,却得不到结果的日子,我们把它叫作扎根。
Question:
这个问题困扰了我好久,一直疑问这个接口的 bean 是怎么注入进去的?因为只看到使用 @Service 注入了实现类 serviceImpl,使用时怎么能获取的接口,而且还能调用到实现类的方法,难道这个接口是在什么时候自动注入了进去,且和实现类关联上了?
接口
public interface TestService {public String test();
}
实现类 impl
@Service
public class TestServiceImpl implements TestService {
@Override
public String test() {return "TestServiceImpl";}
}
Controller 的调用:
@RestController
public class TestCtl {
@Autowired
private TestService testService;
@RequestMapping("/test")
public String test() {return testService.test();
}
}
请求结果:
Answwer:
后来才知道,并没有注入接口的 bean,只注入了实现类 serviceImpl 的 bean,接口只是用来接收的。
这里就要说到 @Autowired/@Resource 的注入原理了:@Autowired 是 Spring 的注解,Autowired 默认先按 byType,如果发现找到多个 bean,则,又按照 byName 方式比对,如果还有多个,则报出异常;@Resource 是 JDK1.6 支持的注解,默认按照名称 (Byname) 进行装配, 如果没有指定 name 属性,当注解写在字段上时,默认取字段名,按照名称查找,如果注解写在 setter 方法上默认取属性名进行装配。当找不到与名称匹配的 bean 时才按照类型进行装配。但是需要注意的是,如果 name 属性一旦指定,就只会按照名称进行装配。
再来说 Controller 获取实例的过程:使用 @Autowired,程序在 spring 的容器中查找类型是 TestService 的 bean,刚好找到有且只有一个此类型的 bean,即 testServiceImpl,所以就把 testServiceImpl 自动装配到了 controller 的实例 testService 中,testService 其实就是 TestServiceImpl 实现类;
如果使用的是 @Resource,则是先在容器中查找名字为 testService 的 bean,但并没有找到,因为容器中的 bean 名字是 TestServiceImpl(如果 @Service 没指定 bean 的 value 属性,则注入 bean 的名字就是类名,如果指定了则是指定的名字),然后再通过类型查找 TestService 类型的 bean,找到唯一的了个 TestService 类型 bean(即 TestServiceImpl),所以就自动装配实例成功了。更多面试题,欢迎关注公众号 Java 面试题精选
Note:
byName 通过参数名 自动装配,如果一个 bean 的 name 和另外一个 bean 的 property 相同,就自动装配。
byType 通过参数的数据类型自动自动装配,如果一个 bean 的数据类型和另外一个 bean 的 property 属性的数据类型兼容,就自动装配
效率上来说 @Autowired/@Resource 差不多,不过推荐使用 @Resource 一点,因为当接口有多个实现时 @Resource 直接就能通过 name 属性来指定实现类,而 @Autowired 还要结合 @Qualifier 注解来使用,且 @Resource 是 jdk 的注释,可与 Spring 解耦。
Question:
如果一个接口有多个实现类时,通过注解获取实例时怎么知道应该获取的是哪一个实现类 serviceImpl 呢?
再增加了一个实现类 TestServiceImpl2
@Service
public class TestServiceImpl2 implements TestService {
@Override
public String test() {return "TestServiceImpl2";}
}
Answer:
多个实现类的话可通过以下 2 种方式来指定具体要使用哪一个实现:
1、通过指定 bean 的名字来明确到底要实例哪一个类
@Autowired 需要结合 @Qualifier 来使用,如下:
@Autowired
@Qualifier("testServiceImpl")
private TestService testService;
@Resource 可直接通过指定 name 属性的值即可,不过也可以使用 @Qualifier(有点多此一举了…)
@Resource(name = "testServiceImpl")
private TestService testService;
@Resource 如果不显示的指定 name 值,就会自动把实例变量的名称作为 name 的值的,所以也可以直接这样写:
@Resource
private TestService testServiceImpl;
2、通过在实现类上添加 @Primary 注解来指定默认加载类
@Service
@Primary
public class TestServiceImpl2 implements TestService {
@Override
public String test() {return "TestServiceImpl2";}
}
这样如果在使用 @Autowired/@Resource 获取实例时如果不指定 bean 的名字,就会默认获取 TestServiceImpl2 的 bean,如果指定了 bean 的名字则以指定的为准。
Question:
为什么非要调用接口来多此一举,而不直接调用实现类 serviceImpl 的 bean 来得简单明了呢?
Answer:
1、直接获取实现类 serviceImpl 的 bean 也是可以的;
2、至于加一层接口的原因:一是 AOP 程序设置思想指导,给别人调用的接口,调用者只想知道方法和功能,而对于这个方法内部逻辑怎么实现的并不关心;二是可以降低各个模块间的关联,实现松耦合、程序分层、高扩展性,使程序更加灵活,他除了在规范上有卓越贡献外,最精髓的是在多态上的运用;继承只能单一继承,接口却可以多实现
3、当业务逻辑简单,变更较少,项目自用时,省略掉接口直接使用实现类更简单明了;反之则推荐使用接口;