Spring Boot 中间件开发(一)《服务治理中间件之统一白名单验证》

Stella981
• 阅读 199

Spring Boot 中间件开发(一)《服务治理中间件之统一白名单验证》

作者:小付哥

常恐秋节至,焜黄华叶衰

微信公众号:bugstack虫洞栈
沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例、SpringBoot中间件开发[Ing]等。

前言介绍

Spring Boot + 领域驱动设计使得微服务越来越火热,而随着微服务越来越多,服务的治理就显得尤为重要。

在我们的业务领域开发中,经常会有一些通用性功能搭建,比如;白名单、黑名单、限流、熔断等,为了更好的开发业务功能,我们需要将非业务功能的通用逻辑提取出来开发出通用组件,以便于业务系统使用。而不至于Copy来Copy去,让代码乱的得加薪才能修改的地步!

通常一个中间件开发会需要用到;自定义xml配置、自定义Annotation注解、动态代理、反射调用、字节码编程(javaassist、ASM等),以及一些动态注册服务中心和功能逻辑开发等。本案例会使用Spring Boot开发方式定义自己的starter。

原理简述

通过我们使用一个公用的starter的时候,只需要将相应的依赖添加的Maven的配置文件当中即可,免去了自己需要引用很多依赖类,并且SpringBoot会自动进行类的自动配置。而我们自己开发一个starter也需要做相应的处理;

  1. SpringBoot 在启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的Jar包去扫描项目所依赖的Jar包,这类似于 Java 的 SPI 机制。

    SPI 全称 Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

  2. 根据 spring.factories配置加载AutoConfigure类。

  3. 根据 @Conditional注解的条件,进行自动配置并将Bean注入Spring Context 上下文当中。也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class}) 指定自动配置哪些类。

  4. 日常使用的Spring官方的Starter一般采取spring-boot-starter-{name} 的命名方式,如 spring-boot-starter-web 。而非官方的Starter,官方建议 artifactId 命名应遵循{name}-spring-boot-starter 的格式。 例如:door-spring-boot-starter 。

环境准备

  1. jdk 1.8.0

  2. Maven 3.x

  3. IntelliJ IDEA Community Edition 2018.3.1 x64

工程示例

中间件工程:door-spring-boot-starter

 1door-spring-boot-starter 2└── src 3    ├── main 4    │   ├── java 5    │   │   └── org.itstack.door 6    │   │       ├── annotation 7    │   │       │    └── DoDoor.java  8    │   │       ├── config 9    │   │       │    ├── StarterAutoConfigure.java   10    │   │       │    ├── StarterService.java 11    │   │       │    └── StarterServiceProperties.java   12    │   │       └── DoJoinPoint.java13    │   └── resources    14    │       └── META_INF15    │           └── spring.factories    16    └── test17        └── java18            └── org.itstack.demo.test19                └── ApiTest.java

演示部分重点代码块,完整代码下载关注公众号;bugstack虫洞栈,回复:中间件开发

door/annotation/DoDoor.java & 自定义注解

  • 自定义注解,用于AOP切面

  • key;获取入参类属性中某个值

  • returnJson;拦截返回Json内容

    1@Retention(RetentionPolicy.RUNTIME)2@Target(ElementType.METHOD)3public @interface DoDoor {45    String key() default "";67    String returnJson() default "";89}

config/StarterAutoConfigure.java & 配置信息装配

  • 通过注解;@Configuration、@ConditionalOnClass、@EnableConfigurationProperties,来实现自定义配置获取值

  • prefix = "itstack.door",用于在yml中的配置

    1@Configuration 2@ConditionalOnClass(StarterService.class) 3@EnableConfigurationProperties(StarterServiceProperties.class) 4public class StarterAutoConfigure { 5 6    @Autowired 7    private StarterServiceProperties properties; 8 9    @Bean10    @ConditionalOnMissingBean11    @ConditionalOnProperty(prefix = "itstack.door", value = "enabled", havingValue = "true")12    StarterService starterService() {13        return new StarterService(properties.getUserStr());14    }1516}

config/StarterServiceProperties.java & 属性配置

  • @ConfigurationProperties("itstack.door"),注解获取配置

  • userStr白名单用户

    1@ConfigurationProperties("itstack.door") 2public class StarterServiceProperties { 3 4    private String userStr; 5 6    public String getUserStr() { 7        return userStr; 8    } 910    public void setUserStr(String userStr) {11        this.userStr = userStr;12    }1314}

DoJoinPoint.java & 自定义切面

  • 自定义切面获取方法和属性值

  • 通过属性值判断此用户ID是否属于白名单范围

  • 属于白名单则放行通过jp.proceed();

  • 对于拦截的用于需要通过returnJson反序列为对象返回

    1@Aspect 2@Component 3public class DoJoinPoint { 4 5    private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class); 6 7    @Autowired 8    private StarterService starterService; 910    @Pointcut("@annotation(org.itstack.door.annotation.DoDoor)")11    public void aopPoint() {12    }1314    @Around("aopPoint()")15    public Object doRouter(ProceedingJoinPoint jp) throws Throwable {16        //获取内容17        Method method = getMethod(jp);18        DoDoor door = method.getAnnotation(DoDoor.class);19        //获取字段值20        String keyValue = getFiledValue(door.key(), jp.getArgs());21        logger.info("itstack door handler method:{} value:{}", method.getName(), keyValue);22        if (null == keyValue || "".equals(keyValue)) return jp.proceed();23        //配置内容24        String[] split = starterService.split(",");25        //白名单过滤26        for (String str : split) {27            if (keyValue.equals(str)) {28                return jp.proceed();29            }30        }31        //拦截32        return returnObject(door, method);33    }3435    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {36        Signature sig = jp.getSignature();37        MethodSignature methodSignature = (MethodSignature) sig;38        return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());39    }4041    private Class getClass(JoinPoint jp) throws NoSuchMethodException {42        return jp.getTarget().getClass();43    }4445    //返回对象46    private Object returnObject(DoDoor doGate, Method method) throws IllegalAccessException, InstantiationException {47        Class returnType = method.getReturnType();48        String returnJson = doGate.returnJson();49        if ("".equals(returnJson)) {50            return returnType.newInstance();51        }52        return JSON.parseObject(returnJson, returnType);53    }5455    //获取属性值56    private String getFiledValue(String filed, Object[] args) {57        String filedValue = null;58        for (Object arg : args) {59            try {60                if (null == filedValue || "".equals(filedValue)) {61                    filedValue = BeanUtils.getProperty(arg, filed);62                } else {63                    break;64                }65            } catch (Exception e) {66                if (args.length == 1) {67                    return args[0].toString();68                }69            }70        }71        return filedValue;72    }7374}

pom.xml & 部分配置内容

  • 中间件开发用到了切面,因此需要引入spring-boot-starter-aop

  • 为了使调用端不用关心中间件都引入那些包,可以将额外的包一起打包给中间件

    1 2    org.springframework.boot 3    spring-boot-starter-aop 4 5 6 7    org.apache.maven.plugins 8    maven-jar-plugin 9    2.3.210    11        12            false13            true14            15                true16                true17            18            19                ${maven.build.timestamp}20            21        22    23

            

spring.factories & spring入口配置

  • 将自己的XxxConfigue配置到这里,用于spring启动时候扫描到

    1org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.itstack.door.config.StarterAutoConfigure

测试工程:itstack-demo-springboot-helloworld

 1itstack-demo-springboot-helloworld 2└── src 3    ├── main 4    │   ├── java 5    │   │   └── org.itstack.demo 6    │   │       ├── domain 7    │   │       │    └── UserInfo.java    8    │   │       ├── web     9    │   │       │    └── HelloWorldController.java   10    │   │       └── HelloWorldApplication.java11    │   └── resources    12    │       └── application.yml    13    └── test14        └── java15            └── org.itstack.demo.test16                └── ApiTest.java

演示部分重点代码块,完整代码下载关注公众号;bugstack虫洞栈,回复:中间件开发

pom.xml & 引入中间件配置

1<dependency>2    <groupId>org.itatack.demo</groupId>3    <artifactId>door-spring-boot-starter</artifactId>4    <version>1.0.1-SNAPSHOT</version>5</dependency>

web/HelloWorldController.java & 配置白名单拦截服务

  • 在需要拦截的方法上添加@DoDoor注解;@DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名单可访问用户拦截!\"}")

  • key;需要从入参取值的属性字段,如果是对象则从对象中取值,如果是单个值则直接使用

  • returnJson;预设拦截时返回值,是返回对象的Json

    1@RestController 2public class HelloWorldController { 3 4    @DoDoor(key = "userId", returnJson = "{"code":"1111","info":"非白名单可访问用户拦截!"}") 5    @RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET) 6    public UserInfo queryUserInfo(@RequestParam String userId) { 7        return new UserInfo("虫虫:" + userId, 19, "天津市南开区旮旯胡同100号"); 8    } 910}

application.yml & Yml配置

  • 添加白名单配置,英文逗号隔开

    1server: 2  port: 8080 3 4spring: 5  application: 6    name: itstack-demo-springboot-helloworld 7 8# 自定义中间件配置 9itstack:10  door:11    enabled: true12    userStr: 1001,aaaa,ccc #白名单用户ID,多个逗号隔开

测试验证

  1. 启动工程(可以Debug调试);itstack-demo-springboot-helloworld

  2. 访问连接;

  3. 白名单用户:http://localhost:8080/api/queryUserInfo?userId=1001

    java {"code":"0000","info":"success","name":"虫虫:1001","age":19,"address":"天津市南开区旮旯胡同100号"}

  4. 非名单用户:http://localhost:8080/api/queryUserInfo?userId=小团团

    java {"code":"1111","info":"非白名单可访问用户拦截!","name":null,"age":null,"address":null}

  5. 服务度日志;

    1  .   ____          _            __ _ _ 2 /\ / _' __ _ ()_ __  __ _ \ \ \ \ 3( ( )__ | '_ | '| | ' / ` | \ \ \ \ 4 \/  _)| |)| | | | | || (_| |  ) ) ) ) 5  '  || .|| ||| |_, | / / / / 6 =========||==============|_/=//_// 7 :: Spring Boot ::        (v2.1.2.RELEASE) 8 92019-12-03 23:25:40.128  INFO 177110 --- [           main] org.itstack.demo.HelloWorldApplication   : Starting HelloWorldApplication on FUZHENGWEI with PID 177110 (E:\itstack\github.com\itstack-demo-springboot-helloworld\target\classes started by fuzhengwei in E:\itstack\github.com\itstack-demo-springboot-helloworld)102019-12-03 23:25:40.133  INFO 177110 --- [           main] org.itstack.demo.HelloWorldApplication   : No active profile set, falling back to default profiles: default112019-12-03 23:25:42.446  INFO 177110 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)122019-12-03 23:25:42.471  INFO 177110 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]132019-12-03 23:25:42.471  INFO 177110 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.14]142019-12-03 23:25:42.483  INFO 177110 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in152019-12-03 23:25:42.611  INFO 177110 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext162019-12-03 23:25:42.612  INFO 177110 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2421 ms172019-12-03 23:25:43.063  INFO 177110 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'182019-12-03 23:25:43.317  INFO 177110 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''192019-12-03 23:25:43.320  INFO 177110 --- [           main] org.itstack.demo.HelloWorldApplication   : Started HelloWorldApplication in 3.719 seconds (JVM running for 4.294)202019-12-03 23:26:56.107  INFO 177110 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'212019-12-03 23:26:56.107  INFO 177110 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'222019-12-03 23:26:56.113  INFO 177110 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 6 ms232019-12-03 23:26:56.171  INFO 177110 --- [nio-8080-exec-1] org.itstack.door.DoJoinPoint             : itstack door handler method:queryUserInfo value:1001242019-12-03 23:27:04.090  INFO 177110 --- [nio-8080-exec-3] org.itstack.door.DoJoinPoint             : itstack door handler method:queryUserInfo value:小团团25

综上总结

  • 此版本中间件还只是一个功能非常简单的雏形,后续还需继续拓展。比如;白名单用户自动更新、黑名单、熔断、降级、限流等。

  • 中间件开发可以将很多重复性工作抽象后进行功能整合,以提升我们使用工具的效率。

  • 鉴于Spring Boot是比较的趋势,我会不断的深挖以及开发一些服务组件。锻炼自己也帮助他人,逐渐构建服务生态,也治理服务。


Spring Boot 中间件开发(一)《服务治理中间件之统一白名单验证》

微信公众号:bugstack虫洞栈 & 沉淀、分享、成长,让自己和他人都能有所收获!

本文分享自微信公众号 - bugstack虫洞栈(bugstack)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
小傅哥 小傅哥
2年前
设计模式PDF下载了4.0万本!那,再肝一本《Java面经手册》吧!
作者:小傅哥博客:https://bugstack.cn(https://bugstack.cn)沉淀、分享、成长,让自己和他人都能有所收获!😄一
小傅哥 小傅哥
2年前
握草,你竟然在代码里下毒!
作者:小傅哥博客:https://bugstack.cn(https://bugstack.cn)沉淀、分享、成长,让自己和他人都能有所收获!😄
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Stella981 Stella981
1年前
DDD专题案例三《领域驱动设计架构基于SpringCloud搭建微服务》
!(https://oscimg.oschina.net/oscnet/ea8c4fbbc514341be35cc89aa9874e02209.jpg)作者:付政委成长总是来自于对未知领域的探索| 库布齐50公里穿行微信公众号:bugstack虫洞栈|关注获取源码沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分
可莉 可莉
1年前
20年3月27日,Github被攻击。我的GitPage博客也挂了,紧急修复之路,也教会你搭建 Jekyll 博客!
!(https://oscimg.oschina.net/oscnet/14ef929224e43ba1302bb7ce55cdd3559ea.jpg)作者:小傅哥小心驶得万年船,不小心也能万年传!小傅哥| https://bugstack.cn沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他
Stella981 Stella981
1年前
Spring Cloud(七)《基于RabbitMQ消息总线方式刷新配置服务》
!(https://oscimg.oschina.net/oscnet/2bcf28bf1419a03a3e9ecd562c8fc5dbcd5.jpg)作者:付政委读书不觉已春深,一寸光阴一寸金。不是道人来引笑,周情孔思正追寻。微信公众号:bugstack虫洞栈沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识
Stella981 Stella981
1年前
BATJTMD,大厂招聘,都招什么样Java程序员?
持续坚持原创输出,点击蓝字关注我吧!(https://oscimg.oschina.net/oscnet/1ff569760b5c46afa7f103809ffe6aaf.png)作者:小傅哥博客:https://bugstack.cn沉淀、分享、成长,让自己和他人都能有所收获!😜
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue