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

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
性能测试 PTS,5000VUM额度
简介: Spring Boot + 领域驱动设计使得微服务越来越火热,而随着微服务越来越多,服务的治理就显得尤为重要。在我们的业务领域开发中,经常会有一些通用性功能搭建,比如;白名单、黑名单、限流、熔断等,为了更好的开发业务功能,我们需要将非业务功能的通用逻辑提取出来开发出通用组件,以便于业务系统使用。而不至于Copy来Copy去,让代码乱的得加薪才能修改的地步!

前言介绍

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,它可以用来启用框架扩展和替换组件。

  1. 根据 spring.factories配置加载AutoConfigure类。
  2. 根据 @Conditional注解的条件,进行自动配置并将Bean注入Spring Context 上下文当中。也可以使用@ImportAutoConfiguration({MyServiceAutoConfiguration.class}) 指定自动配置哪些类。
  3. 日常使用的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.java
13    │   └── resources    
14    │       └── META_INF
15    │           └── spring.factories    
16    └── test
17        └── java
18            └── org.itstack.demo.test
19                └── ApiTest.java

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

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

  • 自定义注解,用于AOP切面
  • key;获取入参类属性中某个值
  • returnJson;拦截返回Json内容
1@Retention(RetentionPolicy.RUNTIME)
2@Target(ElementType.METHOD)
3public @interface DoDoor {
4
5    String key() default "";
6
7    String returnJson() default "";
8
9}

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    @Bean
10    @ConditionalOnMissingBean
11    @ConditionalOnProperty(prefix = "itstack.door", value = "enabled", havingValue = "true")
12    StarterService starterService() {
13        return new StarterService(properties.getUserStr());
14    }
15
16}

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    }
 9
10    public void setUserStr(String userStr) {
11        this.userStr = userStr;
12    }
13
14}

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;
 9
10    @Pointcut("@annotation(org.itstack.door.annotation.DoDoor)")
11    public void aopPoint() {
12    }
13
14    @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    }
34
35    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    }
40
41    private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {
42        return jp.getTarget().getClass();
43    }
44
45    //返回对象
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    }
54
55    //获取属性值
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    }
73
74}

pom.xml & 部分配置内容

  • 中间件开发用到了切面,因此需要引入spring-boot-starter-aop
  • 为了使调用端不用关心中间件都引入那些包,可以将额外的包一起打包给中间件
1<dependency>
 2    <groupId>org.springframework.boot</groupId>
 3    <artifactId>spring-boot-starter-aop</artifactId>
 4</dependency>
 5
 6<plugin>
 7    <groupId>org.apache.maven.plugins</groupId>
 8    <artifactId>maven-jar-plugin</artifactId>
 9    <version>2.3.2</version>
10    <configuration>
11        <archive>
12            <addMavenDescriptor>false</addMavenDescriptor>
13            <index>true</index>
14            <manifest>
15                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
16                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
17            </manifest>
18            <manifestEntries>
19                <Implementation-Build>${maven.build.timestamp}</Implementation-Build>
20            </manifestEntries>
21        </archive>
22    </configuration>
23</plugin>

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.java
11    │   └── resources    
12    │       └── application.yml    
13    └── test
14        └── java
15            └── org.itstack.demo.test
16                └── 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    }
 9
10}

application.yml & Yml配置

  • 添加白名单配置,英文逗号隔开
1server:
 2  port: 8080
 3
 4spring:
 5  application:
 6    name: itstack-demo-springboot-helloworld
 7
 8# 自定义中间件配置
 9itstack:
10  door:
11    enabled: true
12    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: default
112019-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 in
152019-12-03 23:25:42.611  INFO 177110 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
162019-12-03 23:25:42.612  INFO 177110 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2421 ms
172019-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 ms
232019-12-03 23:26:56.171  INFO 177110 --- [nio-8080-exec-1] org.itstack.door.DoJoinPoint             : itstack door handler method:queryUserInfo value:1001
242019-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是比较的趋势,我会不断的深挖以及开发一些服务组件。锻炼自己也帮助他人,逐渐构建服务生态,也治理服务。


目录
相关文章
|
9天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
527 8
|
20天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
20天前
|
Java 数据库连接 Maven
Spring基础1——Spring(配置开发版),IOC和DI
spring介绍、入门案例、控制反转IOC、IOC容器、Bean、依赖注入DI
Spring基础1——Spring(配置开发版),IOC和DI
|
2月前
|
人工智能 自然语言处理 Java
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
文章介绍了Spring AI,这是Spring团队开发的新组件,旨在为Java开发者提供易于集成的人工智能API,包括机器学习、自然语言处理和图像识别等功能,并通过实际代码示例展示了如何快速集成和使用这些AI技术。
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
|
28天前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
58 2
|
29天前
|
NoSQL 前端开发 Java
使用 Spring Boot + Neo4j 实现知识图谱功能开发
在数据驱动的时代,知识图谱作为一种强大的信息组织方式,正逐渐在各个领域展现出其独特的价值。本文将围绕使用Spring Boot结合Neo4j图数据库来实现知识图谱功能开发的技术细节进行分享,帮助读者理解并掌握这一技术栈在实际项目中的应用。
100 4
|
1月前
|
安全 Java 开发者
强大!Spring Cloud Gateway新特性及高级开发技巧
在微服务架构日益盛行的今天,网关作为微服务架构中的关键组件,承担着路由、安全、监控、限流等多重职责。Spring Cloud Gateway作为新一代的微服务网关,凭借其基于Spring Framework 5、Project Reactor和Spring Boot 2.0的强大技术栈,正逐步成为业界的主流选择。本文将深入探讨Spring Cloud Gateway的新特性及高级开发技巧,助力开发者更好地掌握这一强大的网关工具。
109 6
|
1月前
|
IDE Java 开发工具
还在为繁琐的配置头疼吗?一文教你如何用 Spring Boot 快速启动,让开发效率飙升,从此告别加班——打造你的首个轻量级应用!
【9月更文挑战第2天】Spring Boot 是一款基于 Spring 框架的简化开发工具包,采用“约定优于配置”的原则,帮助开发者快速创建独立的生产级应用程序。本文将指导您完成首个 Spring Boot 项目的搭建过程,包括环境配置、项目初始化、添加依赖、编写控制器及运行应用。首先需确保 JDK 版本不低于 8,并安装支持 Spring Boot 的现代 IDE,如 IntelliJ IDEA 或 Eclipse。
83 5
|
2月前
|
IDE Java 开发工具
快速上手指南:如何用Spring Boot开启你的Java开发之旅?
【8月更文挑战第22天】Spring Boot由Pivotal团队开发,简化了Spring应用的创建过程。本文详述了从零开始搭建Spring Boot项目的步骤:首先确保安装了新版JDK、Maven/Gradle及IDE如IntelliJ IDEA或Eclipse;接着访问Spring Initializr网站(start.spring.io),选择所需依赖(如Web模块)并生成项目;最后,使用IDE打开生成的项目,添加`@SpringBootApplication`注解及main方法来启动应用。通过这些步骤,即便是新手也能快速上手,专注于业务逻辑的实现。
34 1
|
2月前
|
XML 数据库 数据格式
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
这篇文章是Spring5框架的实战教程的终结篇,介绍了如何使用注解而非XML配置文件来实现JdbcTemplate的数据库操作,包括增删改查和批量操作,通过创建配置类来注入数据库连接池和JdbcTemplate对象,并展示了完全注解开发形式的项目结构和代码实现。
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
下一篇
无影云桌面