1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

还在用if-else,新的解耦方式你确定不了解下?

还在用if-else,新的解耦方式你确定不了解下?

前言

不知道各位小伙伴有没有这样的困惑,就是很多时候我们的业务实现很多,但是对外接口只有一个,这时候对于具体的业务我们需要根据业务编码进行判断,然后再根据不同的业务编码调用我们的具体业务,这时候我们会用到if-else或者swatch进行判断,大概就是这样:

public class MyserviceImpl implements Myservice {
    
    @Autowired
    private Hadler01001ContentService service1;
    @Autowired
    private Hadler01002ContentService service2;
    @Autowired
    private Hadler01003ContentService service3;
    
    @Override
    public WrapperResponse handler(WrapperRequest request) {
        RequestHeader header = request.getHeader();
        if(header != null) {
            String businessCode = header.getBusinessCode();
            if("01001".equals(businessCode)) {
                // 处理业务01001
               return service1.handler(request);
            } else if("01002".equals(businessCode)) {
                // 处理业务01002
                return service2.handler(request);
            } else if("01003".equals(businessCode)) {
                //  处理业务01001
                return service3.handler(request);
            }

        }
        return new WrapperResponse();
    }
}

当然,你说上面这样写有什么问题吗,我觉没有,都很ok,但你说有没有改进的空间,我觉得有,为什么要改进主要有如下两个原因:

  1. 耦合性太高,当增加新的业务类型时,需要修改实现的接口,不够优雅;
  2. 当业务数量增多,if-else太多,不够美观

基于上面个原因,以及我爱折腾、爱探索的个性,我想改善这种状况,当然也从侧面说明我的工作不够量,竟然还有时间探索折腾😂😂,好了,皮一下就可以了。

今天没什么核心技术点,主要有两个:一个是自定义注解,另一个是策略模式

正文

创建项目

今天的项目毫无疑问是springboot项目,具体创建过程就不赘述了,如果觉得spring官方的创建地址太慢,可以使用阿里巴巴的地址创建:

https://start.aliyun.com/

创建业务处理接口

这个接口并非是提供外部服务的接口,而是具体业务的接口,也就是我们的010010100201003要实现的接口:

public interface HandlerContentService<T> {
    WrapperResponse<T> handler(WrapperRequest<Object> request);
}

当然,抽象类也是可以的,看个人喜好吧

实现业务处理接口

下来我们要继承业务处理接口,写具体的业务逻辑和业务处理过程:

@Service
@BusinessType("01001")
public class Hadler01001ContentService implements HandlerContentService {
    @Override
    public WrapperResponse handler(WrapperRequest request) {
        WrapperResponse<String> stringWrapperResponse = new WrapperResponse<>();
        stringWrapperResponse.setMessage("01001");
        System.out.println("01001业务被调用");
        return stringWrapperResponse;
    }
}

其他业务接口类似,这里就不多说,其中@BusinessType是我们自定义的注解。

编写自定义注解

这里定义注解就是为了标记我们的业务实现类,然后从业务实现中拿到业务编号,需要注意的是因为我们需要拿到业务编号,所以定义的注解需要一个value,看下上面的业务实现你就懂了。

@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BusinessType {
    String value();
}

接下来就是与springboot的整合了,也是今天讲的关键,我会尽可能讲的清楚

创建业务选择器

public class HandlerContext {
    private Map<String,Class> handlerMap;

    public HandlerContext(Map<String, Class> handlerMap) {
        this.handlerMap = handlerMap;
    }

    public HandlerContentService getInstance(String type){
        Class clazz=handlerMap.get(type);
        if (clazz==null){
            throw new IllegalArgumentException("输入的参数类型有问题:"+type);
        }
        return (HandlerContentService) BeanTool.getBean(clazz);
    }
}

这个选择器的作用就是根据我们的业务编码,返回对应编码的业务实现对象。这里比较重要的是我们的是handlerMap,它里面存放的是业务代码和业务代码对应的class,我们根据业务编码从handlerMap中拿出对应的class,根据class返回相应的业务处理实例,所以接下来我们看下如何根据classspringboot中拿到其实例,也就是我们的BeanTool

从beanTool中获取业务实例

@Component
public class BeanTool implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    //自动初始化注入applicationContext
    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        if (applicationContext==null){
            applicationContext=context;
        }
    }

    /**通过bean的缩写的方式获取类型
     *
     * @param name
     * @return
     */
    public static Object getBean(String name){
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clazz){
        return applicationContext.getBean(clazz);
    }
}

这里也没有特别复杂,就是从springapplicationContext中直接获取,当然前提是你把你的类交给spring容器来管理。

当然还有一个更复杂的问题,就是handlerMap的初始化,即如何获取我们实现类和业务代码的map并把它注入到springboot中,接下来我们来解决这个问题

handlerMap的初始化和注入

这里我们要用到classScanner,首先要定义自己的类扫描器,核心方法就是scan,其作用就是扫描我们的业务实现类,并返回Set<Class<?>>,这个类我们不需要做任何修改,可以直接使用,你也可以把它作为工具类,在后续项目中继续使用。

需要注意的是,除了依赖org.apache.commons.lang3.ArrayUtils外,其他的依赖都是spring包下的

public class ClassScanner implements ResourceLoaderAware {
    private final List<TypeFilter> includeFilters=new LinkedList<>();
    private final List<TypeFilter> excludeFilters=new LinkedList<>();

    private ResourcePatternResolver resourcePatternResolver=new PathMatchingResourcePatternResolver();
    private MetadataReaderFactory metadataReaderFactory=new CachingMetadataReaderFactory(this.resourcePatternResolver);


    public static Set<Class<?>> scan(String[] basepackages, Class<? extends Annotation>...annoations) {
        ClassScanner cs=new ClassScanner();
        //需要扫描的注解
        if (ArrayUtils.isNotEmpty(annoations)){
            for (Class anno:annoations){
                cs.addIncludeFilter(new AnnotationTypeFilter(anno));
            }
        }
        Set<Class<?>> classes=new HashSet<>();
        for (String s:basepackages){
            classes.addAll(cs.doScan(s));
        }
        return classes;
    }
    public static Set<Class<?>> scan(String basePackage,Class<? extends Annotation> ...annoations){
        return scan(new String[]{basePackage},annoations);
    }

    private Set<Class<?>> doScan(String basePackage) {
        Set<Class<?>> classes=new HashSet<>();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/**/*.class";
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()){
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    if ((includeFilters.size()==0&&excludeFilters.size()==0)||matches(metadataReader)){
                        try {
                            classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }catch (IOException e){
            throw new BeanDefinitionStoreException("IO failure during classpath scanning",e);
        }
        return classes;
    }

    protected boolean matches(MetadataReader metadataReader) throws IOException {
        for(TypeFilter tf:this.excludeFilters){
            if (tf.match(metadataReader,this.metadataReaderFactory)){
                return false;
            }
        }
        for (TypeFilter tf:this.includeFilters){
            if (tf.match(metadataReader,this.metadataReaderFactory))
                return true;
        }
        return false;
    }

    /**
     * 设置解析器
     * @param resourceLoader
     */
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver= ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory=new CachingMetadataReaderFactory(resourceLoader);
    }

    public void addIncludeFilter(TypeFilter typeFilter){
        this.includeFilters.add(typeFilter);
    }

    public void addExcludeFilter(TypeFilter typeFilter){
        this.excludeFilters.add(0,typeFilter);
    }

    public final ResourceLoader getResourceLoader(){
        return this.resourcePatternResolver;
    }

    public void resetResourceLoader(){
        this.includeFilters.clear();
        this.excludeFilters.clear();
    }
}

其实上面这里只完成了类的扫描,所以接下来就是拿到Set<Class<?>>,然后循环遍历,生成我们需要的handlerMap

@Component
public class HandlerProcessor implements BeanFactoryPostProcessor {

    /**
     * 扫描hanglerMap注解并注入容器中
     *
     * @param beanFactory
     * @throws BeansException
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<String, Class> handlerMap = new HashMap<>(3);
        ClassScanner.scan(Const.HANDLER_PACAGER, BusinessType.class).forEach(clazz -> {
            String type = clazz.getAnnotation(BusinessType.class).value();
            handlerMap.put(type, clazz);
        });
        HandlerContext context = new HandlerContext(handlerMap);
        beanFactory.registerSingleton(HandlerContext.class.getName(), context);
    }
}

这里继承了BeanFactoryPostProcessor接口,然后实现了postProcessBeanFactory方法,在方法内,我们调用了ClassScanner.scan,方法入参需要我们指定扫描的包路径(Const.HANDLER_PACAGER),这里我把它定义成静态代变量了。

重要的事说三遍!!!

务必修改扫描的业务类的包路径!!!

务必修改扫描的业务类的包路径!!!

务必修改扫描的业务类的包路径!!!

然后在创建业务选择器的实例时,将handlerMap作为参数传入,并通过beanFactory.registerSingleton将业务选择器注入spring容器中,这样springboot在启动的时候,我们的handlerMap就被初始化并连同选择器被注入到spring的容器中,然后我们就可以在公共接口实现类中直接使用业务选择器了,公共接口就再不需要if-else了:

    @Autowired
    private HandlerContext handlerContext;

    @Override
    public WrapperResponse handler(WrapperRequest request) {
        RequestHeader header = request.getHeader();
        if(header != null) {
            String businessCode = header.getBusinessCode();          
            HandlerContentService instance = handlerContext.getInstance(businessCode);
            return instance.handler(request);

        }
        return new WrapperResponse();
    }

而且这时候你要增加新的业务实现就变得简单了,只需要编写你的业务实现了,然后增加@BusinessType注解即可,是不是很方便。

这里需要注意的是,HandlerContext上不需要任何spring的注解,因为我们是手动注入的,所以不需要spring再帮我们注入,如果加了,反而会有问题(亲测,不信你试下),虽然有提示,但并不影响。

测试

我这里写了一个controller进行简单测试:

@Autowired
    private Myservice myservice;

    @RequestMapping("/test")
    public String test() {
        WrapperRequest<String> stringWrapperRequest = new WrapperRequest<>();
        RequestHeader requestHeader = new RequestHeader();
        requestHeader.setBusinessCode("01001");
        stringWrapperRequest.setHeader(requestHeader);
        myservice.handler(stringWrapperRequest);
        return "success";
    }

    @RequestMapping("/test2")
    public String test2() {
        WrapperRequest<String> stringWrapperRequest = new WrapperRequest<>();
        RequestHeader requestHeader = new RequestHeader();
        requestHeader.setBusinessCode("01002");
        stringWrapperRequest.setHeader(requestHeader);
        myservice.handler(stringWrapperRequest);
        return "success2";
    }

    @RequestMapping("/test3")
    public String test3() {
        WrapperRequest<String> stringWrapperRequest = new WrapperRequest<>();
        RequestHeader requestHeader = new RequestHeader();
        requestHeader.setBusinessCode("01003");
        stringWrapperRequest.setHeader(requestHeader);
        myservice.handler(stringWrapperRequest);
        return "success3";
    }

分别访问,控制台打印如下信息:

至此,今天的主要内容就结束了,整个过程比较顺利,下来我们做一个简单的总结。

总结

今天核心就讲了一个内容,就是通过策略模式消灭业务层中业务判断部分的if-else,实现代码的解耦。当然,这里只提供了一种思路,你也可以通过其他方式来达到相同的效果。

posted @ 2020-09-12 14:06  云中志  阅读(302)  评论(0编辑  收藏  举报