- 外观模式 情景
- 外观模式任务实战
- 不使用外观模式的写法
- 使用外观模式的写法 (行动)
外观模式用于降低调用方的使用接口的复杂逻辑组合。对服务中的通用性复杂逻辑进行中间件层包装,让使用方可以只关心业务开发。
外观模式任务实战此案例是对接口的访问权限进行控制, 只对添加了白名单的人, 进行接口访问.
不使用外观模式的写法创建tutorials-13.0-1项目 白名单的创建方式如下. 直接在接口中进行白名单控制, 这样代码耦合度太高, 且每个接口都这样改的话, 工作量也大.
public class HelloWorldController {
public UserInfo queryUserInfo(@RequestParam String userId) {
// 做白名单拦截
List userList = new ArrayList();
userList.add("1001");
userList.add("aaaa");
userList.add("ccc");
if (!userList.contains(userId)) {
return new UserInfo("1111", "非白名单可访问用户拦截!");
}
return new UserInfo("虫虫:" + userId, 19, "天津市南开区旮旯胡同100号");
}
}
使用外观模式的写法 (行动)
创建tutorials-13.0-0模块, 引入pom如下
4.0.0
tutorials-13.0-0
org.example
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.1.2.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-test
test
javax.servlet
javax.servlet-api
provided
4.0.0
javax.servlet
jstl
1.2
com.alibaba
fastjson
1.2.58
dom4j
dom4j
1.6.1
org.apache.commons
commons-lang3
3.8
com.thoughtworks.xstream
xstream
1.4.10
org.projectlombok
lombok
1.16.18
provided
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-compiler-plugin
3.2
1.8
1.8
UTF-8
org.apache.maven.plugins
maven-jar-plugin
3.2.0
false
true
true
true
${maven.build.timestamp}
创建一个接口如下
@RestController
public class HelloWorldController {
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
return new UserInfo("张三: " + userId, 18, "广州市白云区");
}
}
创建启动类
@SpringBootApplication
public class HelloWordApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWordApplication.class, args);
}
}
创建tutorials-13.0-2模块 该模块是进行外观模式的改造, uml图如下 创建自定义注解DoDoor
package com.thc.design.door.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 "";
// 预设拦截时返回值,是返回对象的Json
String returnJson() default "";
}
创建获取配置的类
public class StarterService {
private String userStr;
public StarterService(String userStr) {
this.userStr = userStr;
}
// 根据自定义的符号切割获取配置数组
public String[] split(String separatorChar) {
return this.userStr.split(separatorChar);
}
}
@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());
}
}
创建DoJoinPoint切面处理类
import com.alibaba.fastjson.JSON;
import com.thc.design.door.annotation.DoDoor;
import com.thc.design.door.config.StarterService;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 类名称:DoJoinPoint
* 类描述:白名单切面逻辑处理
* 创建时间:2022/3/26 14:36
*/
@Aspect
@Component
public class DoJoinPoint {
private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);
@Autowired
private StarterService starterService;
// 定义切面,这里采用的是注解路径,也就是所有的加入这个注解的方法都会被切面进行管理。
@Pointcut("@annotation(com.thc.design.door.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("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 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 (StringUtils.isBlank(filedValue)) {
filedValue = BeanUtils.getProperty(arg, filed);
} else {
break;
}
} catch (Exception e) {
// e.printStackTrace();
if (args.length == 1) {
return args[0].toString();
}
}
}
return filedValue;
}
private Method getMethod(ProceedingJoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
private Class getClass(JoinPoint jp) {
return jp.getTarget().getClass();
}
}
创建spring.factories文件, 对配置类进行加载.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.thc.design.door.config.StarterAutoConfigure
在tutorials-13.0-0模块中, 引入tutorials-13.0-2模块的jar包
org.springframework.boot
tutorials-13.0-2
1.0-SNAPSHOT
在HelloWorldController加上权限注解
@RestController
public class HelloWorldController {
@DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名单可访问用户拦截!\"}")
@RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET)
public UserInfo queryUserInfo(@RequestParam String userId) {
return new UserInfo("张三: " + userId, 18, "广州市白云区");
}
}
在application.yml中配置如下
server:
port: 8088
spring:
application:
name: door-demo
itstack:
door:
enabled: true
userStr: 1001,aaaa,ccc
定义了开启权限, 并且只指定了三个有权限的用户id 启动tutorials-13.0-0 模块. 访问http://localhost:8088/api/queryUserInfo?userId=1006
响应如下 访问
http://localhost:8088/api/queryUserInfo?userId=ccc
结果: 可以看到成功实现了权限访问 . 并且把权限控制写在接口外.
此节的疑问 DoJoinPoint 类中, getFiledValue方法似乎有问题, 一直进入catch中报异常. arg只是参数, 不是对象, 不知为何要用BeanUtils.getProperty
方法. 应该是直接使用 return args[0].toString() 即可.