# 定义
外观模式也叫门面模式,主要解决的是降低调用方的使用接口的复杂逻辑组合。这样调用方与实际的接口提供方提供方提供了一个中间层,用于包装逻辑提供API接口。有些时候外观模式也被用在中间件层,对服务中的通用性复杂逻辑进行中间件层包装,让使用方可以只关心业务开发。
优点:
1. 减少系统相互依赖。
2. 提高灵活性。
3. 提高了安全性。
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
使用场景:
1. 为复杂的模块或子系统提供外界访问的模块。
2. 子系统相对独立。
3. 预防低水平人员带来的风险。
>注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。
# 实践
这里模拟一个将所有服务接口添加白名单的场景。
使用外观模式也可以说门面模式,结合SpringBoot中的自定义starter中间件开发的方式,统一处理所有需要白名单的地方。
项目的结构如下:

设计思路:

配置服务类:
```java
public class StarterService {
private String userStr;
public StarterService(String userStr) {
this.userStr = userStr;
}
public String[] split(String separatorChar) {
return StringUtils.split(this.userStr, separatorChar);
}
}
```
配置类注解定义:
```java
@ConfigurationProperties("lixj.door")
public class StarterServiceProperties {
private String userStr;
public String getUserStr() {
return userStr;
}
public void setUserStr(String userStr) {
this.userStr = userStr;
}
}
```
自定义配置类信息获取
```java
@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());
}
}
```
切面注解定义:
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {
String key() default "";
String returnJson() default "";
}
```
白名单切面逻辑
```java
@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();,否则返回自定义的拦截提示信息。
配置文件:
```
lixj:
door:
enabled: true
userStr: 1001,aaaa,ccc #白名单用户ID,多个逗号隔开
```
```java
@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, "深圳市南山区");
}
}
```
```
@SpringBootApplication
@Configuration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
```java
@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


# 总结
以上我们通过中间件的方式实现外观模式,这样的设计可以很好的增强代码的隔离性,以及复用性,不仅使用上非常灵活也降低了每一个系统都开发这样的服务带来的风险。
外观模式封装了系统之间复杂的交互和依赖关系,为客户提供了单一简单界面,减低了系统的复杂性。
-4f240a2c2b2b48739efa2636d77b6d91.jpg)
【设计模式】外观模式