设计模式之外观模式


前言

本篇会用外观模式来设计一个处理白名单业务的中间件,将外观模式实际用到项目中。
注意:设计模式学的是设计思想,而不是固定的实现方式。

外观模式介绍

定义:
外观模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

外观模式是“迪米特法则”的典型应用,它有以下主要优点:

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类;
  2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易;
  3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

通过以上介绍可以发现第三方SDK、开源类库,基本上都使用了外观模式。

违背设计模式实现

伪代码如下:

public static boolean whiteList(Sting userId){
	//白名单列表
	List<String> list =new ArrayList();
	list.add("1001");
	list.add("1002");
	list.add("1003");
	
	//校验是在白名单中
	if(!list.contains(userId)){
		return false;
	}
	return true;
}

写上一个静态方法简单实现白名单功能,在controller层的接口都调用这个接口,如果是一个服务勉强还能接受,如果涉及是多个服务则需要将这个方法copy到其他相关服务中了,如果这块的业务需要变更涉及的服务都改,这种设计方式耦合度太高了,代码也冗余不够优雅。

设计模式重构

1.AOP切面注解

pom(示例):

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>facade</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>facade</name>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.8.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>

2.配置服务类

代码如下(示例):

/**
 * 配置服务类
 * @author winter
 */
public class StarterService {
    private String userStr;
    public StarterService(String userStr) {
        this.userStr = userStr;
    }
    public String[] split(String separatorChar) {
        return StringUtils.split(this.userStr, separatorChar);
    }
}

为了获取springboot中配置文件的信息内容。


3.配置类注解定义

代码如下(示例):

/**
 * 配置类注解定义
 * @author winter
 */
@ConfigurationProperties("white.list")
public class StarterServiceProperties {
    private String userStr;
    public String getUserStr() {
        return userStr;
    }
    public void setUserStr(String userStr) {
        this.userStr = userStr;
    }
}

获取到 application.yml 中添加 white.list的配置信息。

4.获取自定义配置类信息

/**
 * 获取自定义配置类信息
 * @author winter
 */
@Configuration
@ConditionalOnClass(StarterService.class)
@EnableConfigurationProperties(StarterServiceProperties.class)
public class StarterAutoConfigure {
    @Autowired
    private StarterServiceProperties properties;
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "white.list", value = "enabled", havingValue = "true")
    StarterService starterService() {
        return new StarterService(properties.getUserStr());
    }
}

主要是对注解@Configuration、@ConditionalOnClass、@EnableConfigurationProperties的定义,直接读取配置文件中数据并将StarterService加载到bean中。

5.切面注解定义

/**
 * 注解定义
 * @author winter
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {
    String key() default "";
}

切面注解定义了外观模式切面注解。

6.白名单切面逻辑

/**
 * 切面定义
 * @author winter
 */
@Aspect
@Component
public class DoJoinPoint {
    private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);
    @Autowired
    private StarterService starterService;
    @Pointcut("@annotation(com.example.facade.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());
        logger.info("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(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(Method method) throws IllegalAccessException, InstantiationException {
        Class<?> returnType = method.getReturnType();

        return JSON.parseObject("用户id不在白名单中", 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;
    }
}

主要是用切面方式实现白名单校验的功能。

7.引入中间件POM

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>facade</artifactId>
            <version>1.0</version>
            <scope>compile</scope>
        </dependency>

备注:一般都是直接上传到maven上。

8.配置application.yml

white:
  list:
    enabled: true
    user-str: 1001,1002,1003

9.在controller中添加自定义注解

    @GetMapping("/user")
    @DoDoor(key = "userId")
    public String getUser(@RequestParam String userId){
        return "获取到用户信息";
    }

总结

通过中间件的方式实现外观模式,这种设计可以很好的增强代码的隔离性及复用性,不仅使用非常灵活,也降低了对每一个系统开发白名单拦截服务带来的风险及测试成本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值