作者:小付哥
常恐秋节至,焜黄华叶衰
微信公众号:bugstack虫洞栈 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例、SpringBoot中间件开发[Ing]等。
Spring Boot + 领域驱动设计使得微服务越来越火热,而随着微服务越来越多,服务的治理就显得尤为重要。
在我们的业务领域开发中,经常会有一些通用性功能搭建,比如;白名单、黑名单、限流、熔断等,为了更好的开发业务功能,我们需要将非业务功能的通用逻辑提取出来开发出通用组件,以便于业务系统使用。而不至于Copy来Copy去,让代码乱的得加薪才能修改的地步!
通常一个中间件开发会需要用到;自定义xml配置、自定义Annotation注解、动态代理、反射调用、字节码编程(javaassist、ASM等),以及一些动态注册服务中心和功能逻辑开发等。本案例会使用Spring Boot开发方式定义自己的starter。
通过我们使用一个公用的starter的时候,只需要将相应的依赖添加的Maven的配置文件当中即可,免去了自己需要引用很多依赖类,并且SpringBoot会自动进行类的自动配置。而我们自己开发一个starter也需要做相应的处理;
中间件工程: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 & 自定义注解
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 & 配置信息装配
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 & 属性配置
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 & 自定义切面
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 & 部分配置内容
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入口配置
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 & 配置白名单拦截服务
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,多个逗号隔开
java {"code":"0000","info":"success","name":"虫虫:1001","age":19,"address":"天津市南开区旮旯胡同100号"}
java {"code":"1111","info":"非白名单可访问用户拦截!","name":null,"age":null,"address":null}
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