前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【设计模式】外观模式

【设计模式】外观模式

作者头像
Li_XiaoJin
发布2022-06-10 18:28:47
2870
发布2022-06-10 18:28:47
举报
文章被收录于专栏:Lixj's BlogLixj's Blog

定义

外观模式也叫门面模式,主要解决的是降低调用方的使用接口的复杂逻辑组合。这样调用方与实际的接口提供方提供方提供了一个中间层,用于包装逻辑提供API接口。有些时候外观模式也被用在中间件层,对服务中的通用性复杂逻辑进行中间件层包装,让使用方可以只关心业务开发。

优点:

  1. 减少系统相互依赖。
  2. 提高灵活性。
  3. 提高了安全性。

缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

使用场景:

  1. 为复杂的模块或子系统提供外界访问的模块。
  2. 子系统相对独立。
  3. 预防低水平人员带来的风险。

注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。

实践

这里模拟一个将所有服务接口添加白名单的场景。

使用外观模式也可以说门面模式,结合SpringBoot中的自定义starter中间件开发的方式,统一处理所有需要白名单的地方。

项目的结构如下:

设计思路:

配置服务类:

代码语言:javascript
复制
public class StarterService {

    private String userStr;

    public StarterService(String userStr) {
        this.userStr = userStr;
    }

    public String[] split(String separatorChar) {
        return StringUtils.split(this.userStr, separatorChar);
    }

}

配置类注解定义:

代码语言:javascript
复制
@ConfigurationProperties("lixj.door")
public class StarterServiceProperties {

    private String userStr;

    public String getUserStr() {
        return userStr;
    }

    public void setUserStr(String userStr) {
        this.userStr = userStr;
    }

}

自定义配置类信息获取

代码语言:javascript
复制
@Configuration
@ConditionalOnClass(StarterService.class)
@EnableConfigurationProperties(StarterServiceProperties.class)
public class StarterAutoConfigure {

    @Autowired
    private StarterServiceProperties properties;

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "lixj.door", value = "enabled", havingValue = "true")
    StarterService starterService() {
        return new StarterService(properties.getUserStr());
    }

}

切面注解定义:

代码语言:javascript
复制
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {

    String key() default "";

    String returnJson() default "";

}

白名单切面逻辑

代码语言:javascript
复制
@Slf4j
@Aspect
@Component
public class DoJoinPoint {


    @Autowired
    private StarterService starterService;

    @Pointcut("@annotation(fun.lixj.design.annotation.DoDoor)")
    public void aopPoint() {
    }

    @Around("aopPoint()")
    public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
        //获取内容
        Method method = getMethod(jp);
        DoDoor door = method.getAnnotation(DoDoor.class);
        //获取字段值
        String keyValue = getFiledValue(door.key(), jp.getArgs());
        log.info("itstack door handler method:{} value:{}", method.getName(), keyValue);
        if (null == keyValue || "".equals(keyValue)) {
            return jp.proceed();
        }
        //配置内容
        String[] split = starterService.split(",");
        //白名单过滤
        for (String str : split) {
            if (keyValue.equals(str)) {
                return jp.proceed();
            }
        }
        //拦截
        return returnObject(door, method);
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {
        return jp.getTarget().getClass();
    }

    //返回对象
    private Object returnObject(DoDoor doGate, Method method) throws IllegalAccessException, InstantiationException {
        Class<?> returnType = method.getReturnType();
        String returnJson = doGate.returnJson();
        if ("".equals(returnJson)) {
            return returnType.newInstance();
        }
        return JSON.parseObject(returnJson, returnType);
    }

    //获取属性值
    private String getFiledValue(String filed, Object[] args) {
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (null == filedValue || "".equals(filedValue)) {
                    filedValue = BeanUtils.getProperty(arg, filed);
                } else {
                    break;
                }
            } catch (Exception e) {
                if (args.length == 1) {
                    return args[0].toString();
                }
            }
        }
        return filedValue;
    }

}

@Pointcut("@annotation(org.itstack.demo.design.door.annotation.DoDoor)")定义切面,这里采用的是注解路径,也就是所有的加入这个注解的方法都会被切面进行管理。getFiledValue获取指定key也就是获取入参中的某个属性,这里主要是获取用户ID,通过ID进行拦截校验。returnObject返回拦截后的转换对象,也就是说当非白名单用户访问时则返回一些提示信息。doRouter切面核心逻辑,这一部分主要是判断当前访问的用户ID是否白名单用户,如果是则放行jp.proceed();,否则返回自定义的拦截提示信息。

配置文件:

代码语言:javascript
复制
lixj:
  door:
    enabled: true
    userStr: 1001,aaaa,ccc #白名单用户ID,多个逗号隔开
代码语言:javascript
复制
@RestController
public class HelloController {

    @DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名单可访问用户拦截!\"}")
    @GetMapping(value = "/api/queryUserInfo")
    public UserInfo queryUserInfo(@RequestParam String userId) {
        return new UserInfo("懒羊羊:" + userId, 88, "深圳市南山区");
    }
}
代码语言:javascript
复制
@SpringBootApplication
@Configuration
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
代码语言:javascript
复制
@Data
public class UserInfo {
    private String code;
    private String info;

    private String name;
    private Integer age;
    private String address;

    public UserInfo() {

    }

    public UserInfo(String code, String info) {
        this.code = code;
        this.info = info;
    }

    public UserInfo(String name, Integer age, String address) {
        this.code = "0000";
        this.info = "success";
        this.name = name;
        this.age = age;
        this.address = address;
    }

}

启动项目进行验证:

分别访问: http://localhost:8088/api/queryUserInfo?userId=1001 http://localhost:8088/api/queryUserInfo?userId=1111

总结

以上我们通过中间件的方式实现外观模式,这样的设计可以很好的增强代码的隔离性,以及复用性,不仅使用上非常灵活也降低了每一个系统都开发这样的服务带来的风险。

外观模式封装了系统之间复杂的交互和依赖关系,为客户提供了单一简单界面,减低了系统的复杂性。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可 Links: https://lixj.fun/archives/设计模式-外观模式

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义
  • 实践
  • 总结
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档