JAVA【设计模式】外观模式
一、定义
外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口,这种设计属于结构式设计,向外部提供了一个接口,来隐藏系统的复杂性。
二、示例:
模拟场景:
1、模拟对服务接口添加白名单的场景,公司需要对服务接口的白名单设计,那么如果在每⼀个接⼝中都添加白名单人员信息这样的逻辑,就会⾮常麻烦且不易维护。另外这是⼀类具备通⽤逻辑的共性需求,⾮常适合开发成组件,以此来治理服务,让研发⼈员更多的关⼼业务功能开发。这时我们可以考虑用aop的思想去实现
基础部分
@RestController
public class HelloWorldController {
@Value("${server.port}")
private int port;
/**
* @DoDoor 自定义注解
* key:需要从入参取值的属性字段,如果是对象则从对象中取值,如果是单个值则直接使用
* returnJson:预设拦截时返回值,是返回对象的Json
* <p>
* http://localhost:8080/api/queryUserInfo?userId=1001
* http://localhost:8080/api/queryUserInfo?userId=小团团
*/
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
return new UserInfo("虫虫:" + userId, 19, "天津市南开区旮旯胡同100号");
}
}
传统编码方式
在每个接口的内部都做白名单逻辑代码的编写,这样导致代码冗余,重复不可维护
package com.qf.apprance.tradition;
import com.qf.apprance.domain.UserInfo;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.List;
public class HelloWorldController {
public UserInfo queryUserInfo(@RequestParam String userId) {
// 做白名单拦截
List<String> userList = new ArrayList<String>();
userList.add("1001");
userList.add("aaaa");
userList.add("ccc");
if (!userList.contains(userId)) {
return new UserInfo("1111", "非白名单可访问用户拦截!");
}
return new UserInfo("虫虫:" + userId, 19, "天津市南开区旮旯胡同100号");
}
}
外观模式设计
自定义注解@DoDoor
,key为用户的白名单信息,returnJson为非白名单用户被拦截信息
package com.qf.apprance.design.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoDoor {
String key() default "" ;
String returnJson() default "";
}
自动配置到application.yml文件下面,后期修改只需要修改配置文件
itstack:
door:
enabled: true
userStr: 1001,aaaa,ccc #白名单用户ID,多个逗号隔开
读取配置文件的内容,并把值输入到StarterService
@ConfigurationProperties("itstack.door")
public class StarterServiceProperties {
private String userStr;
public String getUserStr() {
return userStr;
}
public void setUserStr(String userStr) {
this.userStr = userStr;
}
}
@Configuration
@ConditionalOnClass(StarterService.class)
@EnableConfigurationProperties(StarterServiceProperties.class)
public class StarterAutoConfigure {
@Autowired
private StarterServiceProperties properties;
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "itstack.door", value = "enabled", havingValue = "true")
StarterService starterService() {
return new StarterService(properties.getUserStr());
}
}
public class StarterService {
private String userStr;
public StarterService(String userStr) {
this.userStr = userStr;
}
public String[] split(String separatorChar) {
String[] split = this.userStr.split(separatorChar);
return split;
}
}
AOP切面处理@DoDoor
逻辑信息
@Aspect
@Component
public class DoJoinPoint {
private Logger logger= LoggerFactory.getLogger(DoJoinPoint.class);
@Autowired
private StarterService starterService;
@Around(value = "@annotation(com.qf.apprance.design.annotation.DoDoor)")
public Object doAop(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = getMethod(joinPoint);
DoDoor annotation = method.getAnnotation(DoDoor.class);
String filedValue = getFiledValue(annotation.key(), joinPoint.getArgs());
logger.info("itstack door handler method:{} value:{}", method.getName(), filedValue);
if (null == filedValue || "".equals(filedValue)) return joinPoint.proceed();
String[] split = starterService.split(",");
for (String s : split) {
if (s.equals(filedValue)){
return joinPoint.proceed();
}
}
return returnObject(annotation,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 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;
}
//返回对象
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);
}
}
package com.qf.apprance.web;
import com.qf.apprance.design.annotation.DoDoor;
import com.qf.apprance.domain.UserInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!
* 博 客:http://bugstack.cn
* Create by 小傅哥 on @2020
*/
@RestController
public class HelloWorldController {
@Value("${server.port}")
private int port;
/**
* @DoDoor 自定义注解
* key:需要从入参取值的属性字段,如果是对象则从对象中取值,如果是单个值则直接使用
* returnJson:预设拦截时返回值,是返回对象的Json
* <p>
* http://localhost:8080/api/queryUserInfo?userId=1001
* http://localhost:8080/api/queryUserInfo?userId=小团团
*/
@DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名单可访问用户拦截!\"}")
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
return new UserInfo("虫虫:" + userId, 19, "天津市南开区旮旯胡同100号");
}
}
UML关系图
总结:
以上我们通过中间件的⽅式实现外观模式,这样的设计可以很好的增强代码的隔离性,以及复⽤性,不仅使⽤上⾮常灵活也降低了每⼀个系统都开发这样的服务带来的⻛险。
可能⽬前你看这只是⾮常简单的⽩名单控制,是否需要这样的处理。但往往⼀个⼩⼩的开始会影响着后续⽆限的扩展,实际的业务开发往往也要复杂的很多,不可能如此简单。因⽽使⽤设计模式来让代码结构更加⼲净整洁。