需求
设计一个分发系统,对视频或图文进行多种渠道的分发,发布渠道类型被设计成视频、图文、音频、短文本等,针对不同的发布内容,需要分发至不同的发布渠道,我们需要一个发布接口,并且这些不同的发布渠道需要实现这个发布接口,这就涉及到了接口实现类的动态调用。
流程示意图:
getBeansOfType方法可以根据接口类型返回相应的所有bean。
<T> Map<String, T> getBeansOfType(@Nullable Class<T> var1) throws BeansException;
1、 定义视频发布接口
public interface VideoPublishService<T extends BasePayload> {
/**
* 上传信息
*
* @param payload 上传信息荷载
* @return 返回值外部作品id
*/
Result<String> publish(T payload);
}
2、渠道类型枚举定义
public enum ChannelEnum {
KUAISHOU("kuaishou", "快手"),
DOUYIN("douyin", "抖音"),
BILIBILI("bilibili", "哔哩哔哩");
}
3、定义视频发布接口实现类
@ChannelService(value = "bilibiliVideoPublishService", channel = ChannelEnum.BILIBILI)
public class BilibiliVideoPublishServiceImpl implements VideoPublishService<BilibiliPayload> {
@Override
public Result<String> publish(BilibiliPayload payload) {
return "哔哩哔哩发布成功!";
}
}
@ChannelService(value = "kuaishouVideoPublishService", channel = ChannelEnum.KUAISHOU)
public class KuaishouVideoPublishServiceImpl implements VideoPublishService<KuaishouPayload> {
@Override
public Result<String> publish(KuaishouPayload payload) {
return "快手发布成功!";
}
}
4、自定义Service注解@ChannelService
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ChannelService {
@AliasFor(annotation = Component.class)
String value() default "";
/**
* 渠道名称
* @return
*/
ChannelEnum channel();
}
@AliasFor 表示别名,它可以注解到自定义注解的两个属性上,表示这两个互为别名,也就是说这两个属性其实同一个含义。
此处的别名是使用Component而不是Service,看一下Service注解,它的别名已经是Component了
5、工厂类
@Slf4j
@Component
public class VideoPublishDispatchService {
private HashMap<String, VideoPublishService> mediaMap = Maps.newHashMap();
@Autowired
private ApplicationContextUtils applicationContextUtils;
/**
* 初始化授权服务
*/
@PostConstruct
public void init() throws Exception {
ApplicationContext context = applicationContextUtils.getApplicationContext();
// 得到所有的实现类
Map<String, VideoPublishService> beanMap = context.getBeansOfType(VideoPublishService.class);
for (Map.Entry<String, VideoPublishService> mediaPublishServiceEntry : beanMap.entrySet()) {
Class<?> targetClass = AopUtils.getTargetClass(mediaPublishServiceEntry.getValue());
ChannelService channelServiceInfo = targetClass.getAnnotation(ChannelService.class);
if(null == channelServiceInfo){
log.error("渠道名称未设置,class:{},channel:{}",targetClass.getName(), ChannelService.class.getName());
throw new Exception("服务:"+targetClass.getName()+" 需要配置对应的渠道名称");
}
mediaMap.put(channelServiceInfo.channel().getChannelId(),mediaPublishServiceEntry.getValue());
}
}
/**
* 获取服务
*
* @param channelId 服务类型
* @return
*/
public MediaPublishService getService(String channelId) {
return mediaMap.get(channelId);
}
}
/**
* 上下文获取工具类
*/
@Slf4j
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getApplicationContext() {
return this.applicationContext;
}
}
@PostConstruct说明
servlet的生命周期:服务器加载servlet -> PostConstruct -> init -> doGet/doPost -> destroy -> PreDestroy -> 完毕
当使用依赖注入时,使用@Autowired将A注入到B中,首先需要生成这两个对象,那么Autowired是在构造方法Constructor后执行的。如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
执行顺序是:Constructor -> Autowired -> PostConstruct
6、调用
VideoPublishService videoWorker = VideoPublishDispatchService.getService(channelId);