spring 集合注入案例-票台接口设计


spring 支持集合注入,list,set,map等。集合注入一般按类型注入,把此类型的所有实例用集合的方式管理。


集合注入有时候对我们的设计很有帮助,下面以票台接口设计为例。



如果我们要实现一个订票系统,我们可能接入的航空公司不止一家,但对于我们向外提供的接口必须是一致的,而不同航空


航司的接口肯定是不同的,我们必须屏蔽掉这些差异。



下面是票台接口的定义(只列举两个方法吧):


package org.sdcuike.demo.airline.service;

import org.sdcuike.demo.airline.domain.dto.BookOrder2RequestDto;
import org.sdcuike.demo.airline.domain.dto.BookOrderResponseDto;
import org.sdcuike.demo.airline.domain.dto.FlightInfo2Dto;
import org.sdcuike.demo.airline.domain.dto.SearchFlightRequestDto;

import java.util.List;

/**
 * Created by beaver on 2017/7/18.
 * <p>
 * 票台接口
 */
public interface AirlineService {
    
    /**
     * 查询航班:查询所有航空公司的航班信息
     *
     * @param requestDto
     * @return
     */
    List<FlightInfo2Dto> searchFlights(SearchFlightRequestDto requestDto);
    
    /**
     * 预定订单
     *
     * @param bookOrderRequestDto
     * @return
     */
    BookOrderResponseDto bookOrder(BookOrder2RequestDto bookOrderRequestDto);
    
}



第一个方法,查询航班,我们必须是查询系统所有航空公司的航班信息,第二个接口预定航班订单,我们必须知道用户预定的


哪家航空公司,去哪家航空公司预定机票。此接口也是票台接口定义,封装不同航空公司的接口。



为了实现不同航空公司对这些的实现,我们定义具体航空公司的接口:




package org.sdcuike.demo.airline.service.impl;

import org.sdcuike.demo.airline.domain.AirlineCompanyEnum;
import org.sdcuike.demo.airline.service.AirlineService;

/**
 * Created by beaver on 2017/7/18.
 * <p>
 * 各家航空公司票台接口
 */
interface AirlineCompanyService extends AirlineService {
    
    /**
     * 获取航空公司
     *
     * @return
     */
    AirlineCompanyEnum getCompany();
}





package org.sdcuike.demo.airline.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by beaver on 2017/7/18.
 * <p>
 * 航空公司列表
 * </p>
 */
@Getter
@AllArgsConstructor
@ToString
public enum AirlineCompanyEnum {
    /**
     * 春秋,id=1
     */
    SPRINGAIRLINE(1, "春秋航空", true);
    
    /**
     * 航空ID标识
     */
    private Integer airlineCompanyId;
    
    /**
     * 航司名称
     */
    private String name;
    
    /**
     * 启用(目前没用途)
     */
    private boolean enable = true;
    
    
    public static AirlineCompanyEnum of(Integer airlineCompanyId) {
        return airlineCompanyID2Enum.get(airlineCompanyId);
    }
    
    private static Map<Integer, AirlineCompanyEnum> airlineCompanyID2Enum = new HashMap<>();
    
    static {
        for (AirlineCompanyEnum e : AirlineCompanyEnum.values()) {
            airlineCompanyID2Enum.put(e.getAirlineCompanyId(), e);
        }
    }
}




票台不同航空公司的具体实现,必须实现接口AirlineCompanyService,以春秋航空公司为例:



package org.sdcuike.demo.airline.service.impl;

import org.sdcuike.demo.airline.domain.AirlineCompanyEnum;
import org.sdcuike.demo.airline.domain.dto.BookOrder2RequestDto;
import org.sdcuike.demo.airline.domain.dto.BookOrderResponseDto;
import org.sdcuike.demo.airline.domain.dto.FlightInfo2Dto;
import org.sdcuike.demo.airline.domain.dto.SearchFlightRequestDto;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by beaver on 2017/7/18.
 */
@Service
class SpringairlineServiceImpl implements AirlineCompanyService {
    
    private final AirlineCompanyEnum SpringairlineCompany = AirlineCompanyEnum.SPRINGAIRLINE;
    
    @Override
    public AirlineCompanyEnum getCompany() {
        return SpringairlineCompany;
    }
    
    @Override
    public List<FlightInfo2Dto> searchFlights(SearchFlightRequestDto requestDto) {
        //TODO:
        return new ArrayList<>();
    }
    
    @Override
    public BookOrderResponseDto bookOrder(BookOrder2RequestDto bookOrderRequestDto) {
        
        //demo
        return new BookOrderResponseDto();
    }
}



不同航空公司的具体实现,都有属于自己的枚举值,如上面代码中:



private final AirlineCompanyEnum SpringairlineCompany = AirlineCompanyEnum.SPRINGAIRLINE;
    
    @Override
    public AirlineCompanyEnum getCompany() {
        return SpringairlineCompany;
    }





这样做的目的是我们能用此值寻找到具体航空公司接口的实现。



大家也需注意到具体航空公司的接口及实现的访问限制都是包级别的,不错,这些不是向外提供的,只是我们系统的内部实现,是会经常变动的,没必要公开他们。



票台接口的实现:查询航班,我们必须查询所有接口的航空公司的航班,我们只要把所有航空公司的实现注入进来就可以了,还


好,spring支持集合方式注入,不用我们一个个放到集合里面,免去了我们对此集合的维护;但预定及支付都要确定是哪个航空公


Map<AirlineCompanyEnum, AirlineCompanyService>


此数据结构,我们可以很方便的找到具体航空公司的实现。



那我们如何做的呢,见票台接口实现:



package org.sdcuike.demo.airline.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.sdcuike.demo.airline.domain.AirlineCompanyEnum;
import org.sdcuike.demo.airline.domain.dto.BookOrder2RequestDto;
import org.sdcuike.demo.airline.domain.dto.BookOrderResponseDto;
import org.sdcuike.demo.airline.domain.dto.FlightInfo2Dto;
import org.sdcuike.demo.airline.domain.dto.SearchFlightRequestDto;
import org.sdcuike.demo.airline.service.AirlineService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

/**
 * Created by beaver on 2017/7/18.
 * 票台接口实现,委托给具体航空公司处理
 * <p>
 * <p>
 * spring set方法注入示例
 */
@Service("airlineService")
@Slf4j
public class AirlineServiceImpl implements AirlineService {
    private Map<AirlineCompanyEnum, AirlineCompanyService> airlineCompanyServiceMap;
    
    @Autowired
    public void setAirlineCompanyService(final List<AirlineCompanyService> airlineCompanyServices) {
        airlineCompanyServiceMap = airlineCompanyServices
                .stream()
                .collect(toMap(t -> t.getCompany(), t -> t));
        
    }
    
    @Override
    public List<FlightInfo2Dto> searchFlights(SearchFlightRequestDto requestDto) {
        //http://fahdshariff.blogspot.com/2016/06/java-8-completablefuture-vs-parallel.html?spref=tw
        final List<CompletableFuture<List<FlightInfo2Dto>>> futureList = airlineCompanyServiceMap
                .values().stream()
                .map(t -> CompletableFuture.<List<FlightInfo2Dto>>supplyAsync(() -> {
                            {
                                try {
                                    return t.searchFlights(requestDto);
                                } catch (Exception e) {
                                    log.error("searchFlights error", e);
                                }
                                
                                return new ArrayList<>();
                            }
                        })
                
                ).collect(toList());
        
        List<List<FlightInfo2Dto>> result = futureList.stream()
                                                      .map(CompletableFuture::join)
                                                      .collect(toList());
        
        
        return result.stream().flatMap(t -> t.stream()).collect(toList());
    }
    
    @Override
    public BookOrderResponseDto bookOrder(BookOrder2RequestDto bookOrderRequestDto) {
        final AirlineCompanyService airlineCompanyService = airlineCompanyServiceMap.get(bookOrderRequestDto.getAirlineCompany());
        return airlineCompanyService.bookOrder(bookOrderRequestDto);
    }
}




上面的代码,我们利用spring的set注入方法(用构造函数方法注入也可以),注入所有实现接口AirlineCompanyService的实例,


这样,所有具体航空公司 的实现都在final List<AirlineCompanyService> airlineCompanyServices参数中了,为了快速定位具体航空公司


的实例,我们再做一层变量,放到实例变量    private Map<AirlineCompanyEnum, AirlineCompanyService> airlineCompanyServiceMap


中即可。


这样,查询航班信息,我们可以并发调用每个航空公司的具体实现,合并结果返回,订票、支付等接口,我们用前端传递过来的


航空公司的枚举即可定位具体航空公司 的实例(当然,查询航班信息时候,我们必须把航空公司的枚举也要给前端)。






demo代码见: https://github.com/sdcuike/Spring5-demo/tree/master/DependencyInjection/src/main/java/org/sdcuike/demo/airline