1、定义一个接口
public interface IPrint {
void print(String msg);
}
2、实现这个接口
@Component
public class LogPrint implements IPrint {
@Override
public void print(String msg) {
System.out.println("logPrint print: " + msg);
}
}
如果只有这一个实现类,使用方式很灵活,如下:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Autowired
private IPrint logPrint;//因为该IPrint只有一个实现,这里的logPrint可以用任何字符串替代,如下:
//@Autowired
//private IPrint abc;
@GetMapping("/myPage")
@ResponseBody
public Map<Object, Object> expenseStatement(HttpServletRequest request, HttpServletResponse response) {
logPrint.print("zjg");
return new ConcurrentHashMap<>();
}
}
3、接口的另外一个实现类
@Component
public class ConsolePrint implements IPrint {
@Override
public void print(String msg) {
System.out.println("console print: " + msg);
}
}
IPrint该接口有多个实现类,此时再使用的时候,就要表明具体使用的是哪一个实现,如下:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Autowired
private IPrint abc;
@GetMapping("/myPage")
@ResponseBody
public Map<Object, Object> expenseStatement(HttpServletRequest request, HttpServletResponse response) {
abc.print("zjg");
return new ConcurrentHashMap<>();
}
}
运行上述代码,会抛出如下异常:
Description:
Field abc in com.example.demo.controller.MyPageHelperController required a single bean, but 2 were found:
- consolePrint: defined in file [/Users/zhangjiguo/workspace/backup/demo/target/classes/com/example/demo/service/impl/ConsolePrint.class]
- logPrint: defined in file [/Users/zhangjiguo/workspace/backup/demo/target/classes/com/example/demo/service/impl/LogPrint.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
正确的做法是:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Autowired
private IPrint logPrint / consolePrint; //这里的logPrint / consolePrint的名字就是实现类的bean的名字,所以可以正确引入
@GetMapping("/myPage")
@ResponseBody
public Map<Object, Object> expenseStatement(HttpServletRequest request, HttpServletResponse response) {
logPrint.print("zjg");
return new ConcurrentHashMap<>();
}
}
或者可以这样:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Autowired
private ConsolePrint abc;
//@Autowired
//private LogPrint abc;
@GetMapping("/myPage")
@ResponseBody
public Map<Object, Object> expenseStatement(HttpServletRequest request, HttpServletResponse response) {
abc.print("zjg");
return new ConcurrentHashMap<>();
}
}
上述情况,不管用了哪种方式,都是静态的绑定了IPrint该接口的具体实现类完成的,如何能做到在接口多实现的情况下,可以实现动态切换呢,如下:
我们在某一个实现类上添加注解:@Primary,表明该实现类是默认优先使用的
@Primary
@Component
public class LogPrint implements IPrint {
@Override
public void print(String msg) {
System.out.println("logPrint print: " + msg);
}
}
此时使用方式就很灵活,如下:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Autowired
private IPrint abc;//因为IPrint的实现类LogPrint添加了注解@Primary,所以在不特殊指定的情况下,abc指的就是logPrint
@GetMapping("/myPage")
@ResponseBody
public Map<Object, Object> expenseStatement(HttpServletRequest request, HttpServletResponse response) {
abc.print("zjg");
return new ConcurrentHashMap<>();
}
}
这种情况下,如果想使用该接口的其他实现,可以如下操作:
@RestController
@RequestMapping("/api/{edition}/page")
public class MyPageHelperController {
@Resource(name="consolePrint")//专门指定
private IPrint abc;
@GetMapping("/myPage")
@ResponseBody
public Map<Object, Object> expenseStatement() {
abc.print("zjg");
return new ConcurrentHashMap<>();
}
}
另外还有几种可以动态调用多个实现类的方法,如下:
方式一:利用 @Autowired 把多实现类注入到一个 Map
利用 @Autowired 注解,可以把同一接口的实现类,注入到集合类型中,比如 List,Map,这里使用 Map 介绍
(1)定义接口
public interface StudentService {
/**
* 根据id 查询学生名字
*
* @param stuId Integer
* @return String
*/
String findStudentName(Integer stuId);
}
(2)定义一个常量管理服务映射
public class ServiceMapConstants {
/**
* 学生服务
*/
public static final String STUDENT_SERVICE_PREFIX = "studentService";
public static class StudentServiceConstants {
/**
* 学生服务 - 中国站
*/
public static final String REQUEST_SITE_CN = "studentServiceCN";
/**
* 学生服务 - 泰国站
*/
public static final String REQUEST_SITE_TH = "studentServiceTH";
}
}
(3)定义两个实现类
中国区
@Service(ServiceMapConstants.StudentServiceConstants.REQUEST_SITE_CN)
public class StudentServiceCnImpl implements StudentService {
@Override
public String findStudentName(Integer stuId) {
return "小明 --from cn";
}
}
泰国区
@Service(ServiceMapConstants.StudentServiceConstants.REQUEST_SITE_TH)
public class StudentServiceThImpl implements StudentService {
@Override
public String findStudentName(Integer stuId) {
return "XiaoMing --from th";
}
}
(4)注入与调用服务
@Autowired
private Map<String, StudentService> studentServiceMap;
@ApiOperation(value = "根据 ID 获取学生名称")
@GetMapping("/getStudentName")
public String getStudentById(Integer studentId, String source) {
String key = ServiceMapConstants.STUDENT_SERVICE_PREFIX + source;
// 如果没找到默认为中国
StudentService studentService = Optional.ofNullable(studentServiceMap.get(key))
.orElse(studentServiceMap.get(ServiceMapConstants.StudentServiceConstants.REQUEST_SITE_CN));
return studentService.findStudentName(studentId);
}
方式二:利用 ApplicationContext
通过实现 ApplicationContextAware 接口,获取 ApplicationContext 对象,再通过名称或类型去获取具体的实现
(1)定义接口
public interface TeacherService {
/**
* 根据id 查询教师名字
*
* @param teacherId Integer
* @return String
*/
String findTeacherName(Integer teacherId);
}
(2)定义一个常量管理服务映射x
public interface TeacherService {
/**
* 根据id 查询教师名字
*
* @param teacherId Integer
* @return String
*/
String findTeacherName(Integer teacherId);
}
(3)定义两个实现类
中国区
@Service(ServiceMapConstants.TeacherServiceConstants.REQUEST_SITE_CN)
public class TeacherServiceCnImpl implements TeacherService {
@Override
public String findTeacherName(Integer teacherId) {
return "张老师 --from cn";
}
}
泰国区
@Service(ServiceMapConstants.TeacherServiceConstants.REQUEST_SITE_TH)
public class TeacherServiceThImpl implements TeacherService {
@Override
public String findTeacherName(Integer teacherId) {
return "Teacher Zhang. --from th";
}
}
(4)定义获取服务的 Context
@Component
public class ServiceBeanContext implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static Object getProvider(String providerKey) {
Assert.notNull(providerKey, "provider key not null");
return context.getBean(providerKey);
}
}
(5)注入与调用服务
@ApiOperation(value = "根据 ID 获取教师名称")
@GetMapping("/getTeacherName")
public String getStudentById(Integer teacherId, String source) {
try {
//String key = ServiceMapConstants.TEACHER_SERVICE_PREFIX + source;
String key = source;
TeacherService teacherService = (TeacherService) ServiceBeanContext.getProvider(key);
if (teacherService != null) {
return teacherService.findTeacherName(teacherId);
}
} catch (Exception e) {
log.error("TeacherController.getStudentById 发生异常!", e);
}
return "";
}
方式三:使用getBeansOfType
org.springframework.beans及org.springframework.context这两个包是Spring IoC容器的基础,
其中重要的类有BeanFactory,BeanFactory是IoC容器的核心接口,其职责包括:实例化、定位、配置应用程序中的
对象及建立这些对象间的依赖关系。
ApplicationContext作为BeanFactory的子类,在Bean管理的功能上得到了很大的增强,也更易于与Spring AOP集成使用。
今天我们要讨论的并不是BeanFactory或者ApplicationContext的实现原理,而是对ApplicationContext的一种实际应用方式。
问题的提出
在实际工作中,我们经常会遇到一个接口及多个实现类的情况,并且在不同的条件下会使用不同的实现类。从使用方式上看,有些类似SPI的用法,
但是由于SPI的使用并不是太方便,那么怎么办呢?我们可以借助ApplicationContext的getBeansOfType来实现我们需要的结果。
首先我们看一下这个方法的签名
Map<String, T> getBeansOfType(Class type) throws BeansException;
从上面的代码上我们可以看出来这个方法能返回一个接口的全部实现类(前提是所有实现类都必须由Spring IoC容器管理)。
代码如下:
接口:
/**
* 交通方式
*/
public interface TrafficMode {
/**
* 查询交通方式编码
* @return 编码
*/
TrafficCode getCode();
/**
* 查询交通方式的费用,单位:分
* @return 费用
*/
Integer getFee();
}
枚举:
/**
* 交通类型枚举
*/
public enum TrafficCode {
TRAIN,
BUS
}
接口有两个实现类:
/**
@Component
public class BusMode implements TrafficMode {
@Override
public TrafficCode getCode() {
return TrafficCode.BUS;
}
@Override
public Integer getFee() {
return 10000;
}
}
/**
@Component
public class TrainMode implements TrafficMode {
@Override
public TrafficCode getCode() {
return TrafficCode.TRAIN;
}
@Override
public Integer getFee() {
return 9000;
}
}
工厂类:
/**
@Component
public class TrafficModeFactory implements ApplicationContextAware {
private static Map<TrafficCode, TrafficMode> trafficBeanMap;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, TrafficMode> map = applicationContext.getBeansOfType(TrafficMode.class);
trafficBeanMap = new HashMap<>();
map.forEach((key, value) -> trafficBeanMap.put(value.getCode(), value));
}
public static <T extends TrafficMode> T getTrafficMode(TrafficCode code) {
return (T)trafficBeanMap.get(code);
}
}
验证
有了上面的代码之后,我们一起通过单元测试来看一下效果,单元测试代码片段如下:
@Test
public void testGetTrafficMode() {
TrafficMode mode = TrafficModeFactory.getTrafficMode(TrafficCode.BUS);
Assert.assertEquals(mode.getFee().intValue(), 10000);
mode = TrafficModeFactory.getTrafficMode(TrafficCode.TRAIN);
Assert.assertEquals(mode.getFee().intValue(), 9000);
}
运行之后的结果呢?必然是通过。