├── spring-boot-data-aggregator-example ├── src │ ├── test │ │ ├── resources │ │ │ └── .gitignore │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── lvyahui8 │ │ │ └── spring │ │ │ ├── .gitignore │ │ │ └── example │ │ │ └── DataBeanAggregateQueryFacadeTest.java │ └── main │ │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── lvyahui8 │ │ │ └── spring │ │ │ ├── .gitignore │ │ │ └── example │ │ │ ├── model │ │ │ ├── Post.java │ │ │ ├── Category.java │ │ │ └── User.java │ │ │ ├── service │ │ │ ├── PostService.java │ │ │ ├── UserService.java │ │ │ ├── FollowService.java │ │ │ ├── CategoryService.java │ │ │ ├── HomepageService.java │ │ │ └── impl │ │ │ │ ├── PostServiceImpl.java │ │ │ │ ├── UserServiceImpl.java │ │ │ │ ├── FollowServiceImpl.java │ │ │ │ ├── HomepageServiceImpl.java │ │ │ │ └── CategoryServiceImpl.java │ │ │ ├── configuration │ │ │ ├── ExampleProperties.java │ │ │ └── AggregatorCustomConfiguration.java │ │ │ ├── context │ │ │ ├── RequestContext.java │ │ │ └── ExampleAppContext.java │ │ │ ├── interceptor │ │ │ ├── PerSetupAggregateQueryInterceptor.java │ │ │ └── SampleAggregateQueryInterceptor.java │ │ │ ├── ExampleApplication.java │ │ │ ├── aggregate │ │ │ └── UserAggregate.java │ │ │ ├── aspect │ │ │ └── AggregateQueryLoggingAspect.java │ │ │ ├── wrapper │ │ │ └── CustomAsyncQueryTaskWrapper.java │ │ │ └── facade │ │ │ └── UserQueryFacade.java │ │ └── resources │ │ └── application.properties └── pom.xml ├── spring-boot-data-aggregator-core ├── src │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── lvyahui8 │ │ │ └── spring │ │ │ └── .gitignore │ └── main │ │ └── java │ │ └── io │ │ └── github │ │ └── lvyahui8 │ │ └── spring │ │ ├── aggregate2 │ │ ├── readme.md │ │ ├── ResourceTree.java │ │ ├── ExceptionHandler.java │ │ ├── StrongDependentException.java │ │ ├── DefaultExceptionHandler.java │ │ ├── Context.java │ │ ├── AggregateServiceV2.java │ │ └── ResourceNode.java │ │ ├── aggregate │ │ ├── func │ │ │ ├── MultipleArgumentsFunction.java │ │ │ ├── GroupKey.java │ │ │ ├── FunctionGroup.java │ │ │ ├── Function2.java │ │ │ ├── Function3.java │ │ │ ├── Function4.java │ │ │ └── Function5.java │ │ ├── model │ │ │ ├── InvokeParameterDefinition.java │ │ │ ├── MethodArg.java │ │ │ ├── DependType.java │ │ │ ├── DataConsumeDefinition.java │ │ │ ├── DataProvideDefinition.java │ │ │ └── InvokeSignature.java │ │ ├── config │ │ │ └── RuntimeSettings.java │ │ ├── consts │ │ │ └── AggregationConstant.java │ │ ├── service │ │ │ ├── AsyncQueryTaskWrapperAdapter.java │ │ │ ├── AsyncQueryTaskWrapper.java │ │ │ ├── ProviderService.java │ │ │ ├── AbstractAsyncQueryTask.java │ │ │ ├── DataBeanAggregateService.java │ │ │ └── impl │ │ │ │ ├── ProviderServiceImpl.java │ │ │ │ └── DataBeanAggregateServiceImpl.java │ │ ├── context │ │ │ └── AggregationContext.java │ │ ├── repository │ │ │ ├── DataProviderRepository.java │ │ │ └── impl │ │ │ │ └── DataProviderRepositoryImpl.java │ │ ├── interceptor │ │ │ ├── impl │ │ │ │ ├── AggregateQueryInterceptorAdapter.java │ │ │ │ └── AggregateQueryInterceptorChainImpl.java │ │ │ ├── AggregateQueryInterceptor.java │ │ │ └── AggregateQueryInterceptorChain.java │ │ ├── facade │ │ │ ├── impl │ │ │ │ └── DataBeanAggregateQueryFacadeImpl.java │ │ │ └── DataBeanAggregateQueryFacade.java │ │ └── util │ │ │ └── DefinitionUtils.java │ │ ├── enums │ │ └── ExceptionProcessingMethod.java │ │ └── annotation │ │ ├── DynamicParameter.java │ │ ├── InvokeParameter.java │ │ ├── DataProvider.java │ │ └── DataConsumer.java └── pom.xml ├── spring-boot-data-aggregator-starter ├── src │ ├── main │ │ ├── java │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── lvyahui8 │ │ │ │ └── spring │ │ │ │ └── .gitkeep │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring.provides │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── lvyahui8 │ │ └── spring │ │ └── .gitkeep └── pom.xml ├── spring-boot-data-aggregator-autoconfigure ├── src │ ├── main │ │ ├── java │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── lvyahui8 │ │ │ │ └── spring │ │ │ │ ├── .gitignore │ │ │ │ ├── facade │ │ │ │ ├── FacadeInitializer.java │ │ │ │ └── DataFacade.java │ │ │ │ └── autoconfigure │ │ │ │ ├── BeanAggregateProperties.java │ │ │ │ └── BeanAggregateAutoConfiguration.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring.factories │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── lvyahui8 │ │ └── spring │ │ └── .gitignore └── pom.xml ├── test_cases.xmind ├── README.assets └── image-20200309230202047.png ├── README_EN.assets └── image-20200309230301680.png ├── .travis.yml ├── .gitignore ├── README.md ├── README_EN.md ├── pom.xml └── LICENSE /spring-boot-data-aggregator-example/src/test/resources/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/test/java/io/github/lvyahui8/spring/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/test/java/io/github/lvyahui8/spring/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-starter/src/main/java/io/github/lvyahui8/spring/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-starter/src/test/java/io/github/lvyahui8/spring/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/src/main/java/io/github/lvyahui8/spring/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/src/test/java/io/github/lvyahui8/spring/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_cases.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvyahui8/spring-boot-data-aggregator/HEAD/test_cases.xmind -------------------------------------------------------------------------------- /spring-boot-data-aggregator-starter/src/main/resources/META-INF/spring.provides: -------------------------------------------------------------------------------- 1 | provides: spring-boot-data-aggregator-autoconfigure -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/readme.md: -------------------------------------------------------------------------------- 1 | ## v2版原理 2 | 3 | 并行汇聚,本质是对一颗树进行类似fork&join的操作。 4 | 5 | -------------------------------------------------------------------------------- /README.assets/image-20200309230202047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvyahui8/spring-boot-data-aggregator/HEAD/README.assets/image-20200309230202047.png -------------------------------------------------------------------------------- /README_EN.assets/image-20200309230301680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvyahui8/spring-boot-data-aggregator/HEAD/README_EN.assets/image-20200309230301680.png -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.github.lvyahui8.spring.autoconfigure.BeanAggregateAutoConfiguration -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | sudo: false 5 | script: "mvn cobertura:cobertura" 6 | after_success: 7 | - bash <(curl -s https://codecov.io/bash) 8 | cache: 9 | directories: 10 | - $HOME/.m2 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.class 3 | target 4 | *.tar.gz 5 | *.tgz 6 | *.zip 7 | *.rar 8 | *.7z 9 | *.iml 10 | *.versionsBackup 11 | .gradle 12 | spring-boot-data-aggregator-assistant/build 13 | spring-boot-data-aggregator-assistant/lib -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/ResourceTree.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate2; 2 | 3 | /** 4 | * @author feego lvyahui8@gmail.com 5 | * @date 2022/4/1 6 | */ 7 | public class ResourceTree { 8 | ResourceNode treeRoot; 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate2; 2 | 3 | /** 4 | * @author feego lvyahui8@gmail.com 5 | * @date 2022/4/2 6 | */ 7 | public interface ExceptionHandler 8 | { 9 | Object handle(Exception e) ; 10 | } 11 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/func/MultipleArgumentsFunction.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.func; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/7/13 23:18 6 | */ 7 | public interface MultipleArgumentsFunction { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.main.banner-mode=off 2 | io.github.lvyahui8.spring.base-packages=io.github.lvyahui8.spring.example 3 | io.github.lvyahui8.spring.ignore-exception=false 4 | io.github.lvyahui8.spring.task-wrapper-class=io.github.lvyahui8.spring.example.wrapper.CustomAsyncQueryTaskWrapper 5 | example.logging=true -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/model/Post.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/6/2 16:44 8 | */ 9 | @Data 10 | public class Post { 11 | private String title; 12 | private String content; 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/model/InvokeParameterDefinition.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/6/2 22:14 8 | */ 9 | @Data 10 | public class InvokeParameterDefinition { 11 | private String key; 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/StrongDependentException.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate2; 2 | 3 | /** 4 | * @author feego lvyahui8@gmail.com 5 | * @date 2022/4/2 6 | */ 7 | public class StrongDependentException extends RuntimeException { 8 | public StrongDependentException(Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/config/RuntimeSettings.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.config; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/6/15 2:46 8 | */ 9 | @Data 10 | public class RuntimeSettings { 11 | private boolean ignoreException; 12 | private Long timeout; 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/func/GroupKey.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.func; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author feego lvyahui8@gmail.com 7 | * @date 2022/2/5 8 | */ 9 | @Target({ElementType.PARAMETER}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface GroupKey { 13 | String value(); 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/DefaultExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate2; 2 | 3 | /** 4 | * @author feego lvyahui8@gmail.com 5 | * @date 2022/4/2 6 | */ 7 | public class DefaultExceptionHandler implements ExceptionHandler 8 | { 9 | @Override 10 | public Object handle(Exception e) { 11 | // nothing to do 12 | return null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/consts/AggregationConstant.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.consts; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/6/28 23:53 6 | */ 7 | public interface AggregationConstant 8 | { 9 | Object EMPTY_MODEL = new Object(); 10 | int DEFAULT_INITIAL_CAPACITY = Runtime.getRuntime().availableProcessors() << 2; 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/func/FunctionGroup.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.func; 2 | 3 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 4 | 5 | /** 6 | * 7 | * 一组类实现此接口,并注解上groupKey以并发调用 8 | * 9 | * @author feego lvyahui8@gmail.com 10 | * @date 2022/2/5 11 | */ 12 | public interface FunctionGroup { 13 | Object apply(AggregationContext context); 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/model/Category.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/6/28 23:34 10 | */ 11 | @Data 12 | public class Category { 13 | private Long id; 14 | private String name; 15 | List topCategoryNames; 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/model/MethodArg.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Parameter; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/6/3 22:39 10 | */ 11 | @Data 12 | public class MethodArg { 13 | private String annotationKey; 14 | private DependType dependType; 15 | private Parameter parameter; 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/model/User.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/6/2 16:38 10 | */ 11 | @Data 12 | public class User { 13 | private Long id; 14 | private String username; 15 | private String email; 16 | List posts ; 17 | List followers; 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/PostService.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service; 2 | 3 | import io.github.lvyahui8.spring.example.model.Post; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/6/2 16:53 10 | */ 11 | public interface PostService { 12 | /** 13 | * s 14 | * @param userId 15 | * @return 16 | */ 17 | List getPosts(Long userId); 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/model/DependType.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.model; 2 | 3 | /** 4 | * The dependency type of the parameter of the data provider's method 5 | * 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/6/3 22:44 8 | */ 9 | public enum DependType { 10 | /** 11 | * Caller passed parameters 12 | */ 13 | INVOKE_PARAM, 14 | /** 15 | * Parameters that require automatic injection 16 | */ 17 | OTHER_MODEL 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/func/Function2.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.func; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/7/13 23:19 6 | */ 7 | @FunctionalInterface 8 | @SuppressWarnings("unused") 9 | public interface Function2 extends MultipleArgumentsFunction { 10 | /** 11 | * support two parameters 12 | * 13 | * @param t param 1 14 | * @param u param 2 15 | * @return return value 16 | */ 17 | R apply(T t, U u); 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/Context.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate2; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.Executor; 8 | 9 | /** 10 | * @author feego lvyahui8@gmail.com 11 | * @date 2022/4/1 12 | */ 13 | @Data 14 | public class Context { 15 | Map resultMap; 16 | Map paramMap; 17 | Executor executor; 18 | ExceptionHandler exceptionHandler = new DefaultExceptionHandler(); 19 | long defaultTimeout; 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/UserService.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service; 2 | 3 | import io.github.lvyahui8.spring.example.model.User; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/6/2 16:36 8 | */ 9 | public interface UserService { 10 | /** 11 | * s 12 | * @param id 13 | * @return 14 | */ 15 | User get(Long id); 16 | 17 | /** 18 | * 获取已登录用户用户名 19 | * 20 | * @return xx 21 | */ 22 | String getLoggedUsername(); 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/enums/ExceptionProcessingMethod.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.enums; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/6/15 23:13 6 | */ 7 | public enum ExceptionProcessingMethod { 8 | /** 9 | * Ignore exception thrown by asynchronous execution, method returns null value 10 | */ 11 | IGNORE, 12 | /** 13 | * Follow the default handling of the framework 14 | */ 15 | BY_DEFAULT, 16 | /** 17 | * Abort execution 18 | */ 19 | SUSPEND 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/configuration/ExampleProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.configuration; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/7/8 22:04 10 | */ 11 | @Component 12 | @ConfigurationProperties(prefix = "example") 13 | @Data 14 | public class ExampleProperties 15 | { 16 | private boolean logging = false; 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/func/Function3.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.func; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/7/13 23:21 6 | */ 7 | @FunctionalInterface 8 | @SuppressWarnings("unused") 9 | public interface Function3 extends MultipleArgumentsFunction { 10 | /** 11 | * support three parameters 12 | * 13 | * @param t param 1 14 | * @param u param 2 15 | * @param v param 3 16 | * @return return value 17 | */ 18 | R apply(T t, U u,V v); 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/service/AsyncQueryTaskWrapperAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.service; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/12/25 22:57 6 | */ 7 | public class AsyncQueryTaskWrapperAdapter implements AsyncQueryTaskWrapper{ 8 | 9 | @Override 10 | public void beforeSubmit() { 11 | 12 | } 13 | 14 | @Override 15 | public void beforeExecute(Thread taskFrom) { 16 | 17 | } 18 | 19 | @Override 20 | public void afterExecute(Thread taskFrom) { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/func/Function4.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.func; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/7/14 14:23 6 | */ 7 | @FunctionalInterface 8 | @SuppressWarnings("unused") 9 | public interface Function4 extends MultipleArgumentsFunction { 10 | /** 11 | * support four parameters 12 | * 13 | * @param t param 1 14 | * @param u param 2 15 | * @param v param 3 16 | * @param w param 4 17 | * @return return value 18 | */ 19 | R apply(T t, U u, V v ,W w); 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/src/main/java/io/github/lvyahui8/spring/facade/FacadeInitializer.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.facade; 2 | 3 | import io.github.lvyahui8.spring.aggregate.facade.DataBeanAggregateQueryFacade; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2020/2/13 8 | */ 9 | public class FacadeInitializer { 10 | public static void initFacade(DataBeanAggregateQueryFacade facade) { 11 | if (DataFacade.getFacade() != null) { 12 | throw new UnsupportedOperationException("DataFacade can be initialized only once."); 13 | } 14 | DataFacade.setFacade(facade); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/annotation/DynamicParameter.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/11/6 23:22 8 | */ 9 | @Target({ElementType.PARAMETER}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface DynamicParameter { 13 | /** 14 | * Method originally required parameter key. 15 | */ 16 | String targetKey(); 17 | 18 | /** 19 | * The new key used to replace the original parameter key 20 | */ 21 | String replacementKey(); 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/context/RequestContext.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.context; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/7/22 23:11 6 | */ 7 | public class RequestContext { 8 | private static ThreadLocal TENANT_ID = new ThreadLocal<>(); 9 | 10 | public static Long getTenantId() { 11 | return TENANT_ID.get(); 12 | } 13 | 14 | public static void setTenantId(Long tenantId) { 15 | TENANT_ID.set(tenantId); 16 | } 17 | 18 | public static void removeTenantId() { 19 | TENANT_ID.remove(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/FollowService.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service; 2 | 3 | import io.github.lvyahui8.spring.example.model.User; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/6/11 21:31 10 | */ 11 | public interface FollowService { 12 | /** 13 | * xxx 14 | * 15 | * @param userId userId 16 | * @return xx 17 | */ 18 | List getFollowers(Long userId); 19 | 20 | /** 21 | * xxx 22 | * 23 | * @return xx 24 | */ 25 | List getLoggedUserFollowers(); 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/func/Function5.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.func; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/7/14 14:23 6 | */ 7 | @FunctionalInterface 8 | @SuppressWarnings("unused") 9 | public interface Function5 extends MultipleArgumentsFunction { 10 | /** 11 | * support four parameters 12 | * 13 | * @param t param 1 14 | * @param u param 2 15 | * @param v param 3 16 | * @param w param 4 17 | * @param x param 5 18 | * @return return value 19 | */ 20 | R apply(T t, U u, V v ,W w,X x); 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service; 2 | 3 | import io.github.lvyahui8.spring.example.model.Category; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/6/28 23:34 10 | */ 11 | public interface CategoryService { 12 | Category getRootCategory(List topCategoryNames,Long id); 13 | 14 | String getCategoryTitle(Category category, List topCategoryNames); 15 | 16 | List getTopCategoryNames(); 17 | 18 | Object cycleDependA(Object dependB); 19 | 20 | Object cycleDependB(Object dependA); 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/model/DataConsumeDefinition.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/6/2 22:13 10 | */ 11 | @Data 12 | public class DataConsumeDefinition { 13 | private String id; 14 | /** 15 | * consumer定义在那个接口类中 16 | */ 17 | private Class clazz; 18 | /** 19 | * 是否忽略consumer调用产生的异常 20 | */ 21 | private Boolean ignoreException; 22 | private Map dynamicParameterKeyMap; 23 | private String originalParameterName; 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/service/AsyncQueryTaskWrapper.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.service; 2 | 3 | /** 4 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 5 | * @since 2019/12/24 23:07 6 | */ 7 | public interface AsyncQueryTaskWrapper { 8 | /** 9 | * 任务提交之前执行. 此方法在提交任务的那个线程中执行 10 | */ 11 | void beforeSubmit(); 12 | 13 | /** 14 | * 任务开始执行前执行. 此方法在异步线程中执行 15 | * @param taskFrom 提交任务的那个线程 16 | */ 17 | void beforeExecute(Thread taskFrom); 18 | 19 | /** 20 | * 任务执行结束后执行. 此方法在异步线程中执行 21 | * 注意, 不管用户的方法抛出何种异常, 此方法都会执行. 22 | * @param taskFrom 提交任务的那个线程 23 | */ 24 | void afterExecute(Thread taskFrom); 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/service/ProviderService.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.service; 2 | 3 | import io.github.lvyahui8.spring.aggregate.func.MultipleArgumentsFunction; 4 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 5 | 6 | /** 7 | * @author feego lvyahui8@gmail.com 8 | * @date 2022/4/2 9 | */ 10 | public interface ProviderService { 11 | /** 12 | * 通过MultipleArgumentsFunction获取provider实例 13 | * 14 | * @param function 多参函数 15 | * @return provider实例 16 | * @throws IllegalAccessException ignored 17 | */ 18 | DataProvideDefinition getProvider(MultipleArgumentsFunction function) throws IllegalAccessException; 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/annotation/InvokeParameter.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.annotation; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * 数据聚合时需要的输入参数 9 | * 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/6/2 16:32 12 | */ 13 | @Target({ElementType.PARAMETER}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Documented 16 | public @interface InvokeParameter { 17 | /** 18 | * Manually passed parameter key 19 | */ 20 | @AliasFor("key") 21 | String value() default ""; 22 | 23 | /** 24 | * same as value(); 25 | */ 26 | @AliasFor("value") 27 | String key() default ""; 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/HomepageService.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service; 2 | 3 | import io.github.lvyahui8.spring.example.model.Category; 4 | import io.github.lvyahui8.spring.example.model.Post; 5 | import io.github.lvyahui8.spring.example.model.User; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 11 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 12 | * @since 2019/7/22 22:25 13 | */ 14 | public interface HomepageService { 15 | /** 16 | * 获取顶部菜单类目 17 | * 18 | * @return 顶部类目 19 | */ 20 | List topMenu(); 21 | 22 | /** 23 | * 文章列表 24 | * 25 | * @return xx 26 | */ 27 | List postList(); 28 | 29 | /** 30 | * 粉丝数据 31 | * @return xxx 32 | */ 33 | List allFollowers(); 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/model/DataProvideDefinition.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 10 | * @since 2019/6/2 21:30 11 | */ 12 | @Data 13 | public class DataProvideDefinition { 14 | private String id; 15 | private Method method; 16 | private Object target; 17 | private Long timeout; 18 | private List depends; 19 | private List params; 20 | private List methodArgs; 21 | private boolean idempotent; 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/context/AggregationContext.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.context; 2 | 3 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 4 | import io.github.lvyahui8.spring.aggregate.model.InvokeSignature; 5 | import lombok.Data; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/7/21 22:37 12 | */ 13 | @Data 14 | public class AggregationContext { 15 | /** 16 | * 发起一次递归查询的主调线程 17 | */ 18 | Thread rootThread; 19 | /** 20 | * 根provider 21 | */ 22 | DataProvideDefinition rootProvideDefinition; 23 | /** 24 | * 此次查询生命周期中的缓存 25 | */ 26 | Map cacheMap; 27 | 28 | /** 29 | * 传入的参数 30 | */ 31 | Map invokeParams; 32 | } 33 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/repository/DataProviderRepository.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.repository; 2 | 3 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/6/2 21:26 8 | */ 9 | public interface DataProviderRepository { 10 | /** 11 | * 存放provide定义 12 | * @param dataProvideDefinition provider定义 13 | */ 14 | void put(DataProvideDefinition dataProvideDefinition); 15 | 16 | /** 17 | * 获取provider 18 | * 19 | * @param id data provider id 20 | * @return data provider 21 | */ 22 | DataProvideDefinition get(String id); 23 | 24 | /** 25 | * 是否包含指定Provider 26 | * @param id data provider id 27 | * @return 是否存在provider 28 | */ 29 | boolean contains(String id); 30 | } 31 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/interceptor/PerSetupAggregateQueryInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.interceptor; 2 | 3 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 4 | import io.github.lvyahui8.spring.aggregate.interceptor.impl.AggregateQueryInterceptorAdapter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/9/7 23:02 12 | */ 13 | @Component 14 | @Order(1) 15 | @Slf4j 16 | public class PerSetupAggregateQueryInterceptor extends AggregateQueryInterceptorAdapter { 17 | @Override 18 | public boolean querySubmitted(AggregationContext aggregationContext) { 19 | log.info("current thread {}", Thread.currentThread().getName()); 20 | return super.querySubmitted(aggregationContext); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | spring-boot-data-aggregator 7 | io.github.lvyahui8 8 | 1.1.3 9 | 10 | 4.0.0 11 | 12 | spring-boot-data-aggregator-starter 13 | 14 | spring-boot-data-aggregator-starter 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | io.github.lvyahui8 23 | spring-boot-data-aggregator-autoconfigure 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/annotation/DataProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.annotation; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * 数据提供者 Data provider 9 | * 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/6/1 0:05 12 | */ 13 | @Target({ElementType.METHOD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Documented 16 | public @interface DataProvider { 17 | 18 | /** 19 | * Unique identifier of the data 20 | */ 21 | @AliasFor("value") 22 | String id() default ""; 23 | 24 | /** 25 | * Same as id() 26 | */ 27 | @AliasFor("id") 28 | String value() default ""; 29 | 30 | /** 31 | * Asynchronous execution method timeout 32 | */ 33 | long timeout() default -1; 34 | 35 | /** 36 | * The call to this data providing method should be idempotent, 37 | * which determines whether its execution result will be cached. 38 | */ 39 | boolean idempotent() default true; 40 | } 41 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/AggregateServiceV2.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate2; 2 | 3 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 4 | import io.github.lvyahui8.spring.aggregate.service.DataBeanAggregateService; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author feego lvyahui8@gmail.com 11 | * @date 2022/4/2 12 | */ 13 | public class AggregateServiceV2 implements DataBeanAggregateService { 14 | @Override 15 | public T get(String id, Map invokeParams, Class resultType) throws InterruptedException, InvocationTargetException, IllegalAccessException { 16 | final Context context = new Context(); 17 | context.paramMap = invokeParams; 18 | return null; 19 | } 20 | 21 | @Override 22 | public T get(DataProvideDefinition provider, Map invokeParams, Class resultType) throws InterruptedException, InvocationTargetException, IllegalAccessException { 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/ExampleApplication.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.ConfigurableApplicationContext; 7 | 8 | import java.util.concurrent.ExecutorService; 9 | 10 | /** 11 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 12 | * @since 2019/6/1 0:13 13 | */ 14 | 15 | @SpringBootApplication 16 | @Slf4j 17 | public class ExampleApplication { 18 | 19 | public static void main(String[] args) throws Exception { 20 | ConfigurableApplicationContext context = null; 21 | try{ 22 | context = SpringApplication.run(ExampleApplication.class,args); 23 | } finally { 24 | if(context != null) { 25 | ExecutorService executorService = (ExecutorService) context.getBean("aggregateExecutorService"); 26 | executorService.shutdown(); 27 | } 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/context/ExampleAppContext.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.context; 2 | 3 | import io.github.lvyahui8.spring.example.model.User; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/7/18 22:19 8 | */ 9 | public class ExampleAppContext { 10 | /** 11 | * 这里必须使用, InheritableThreadLocal, 只有InheritableThreadLocal会向子线程传递. 12 | */ 13 | private static ThreadLocal LOGGED_USER = new InheritableThreadLocal<>(); 14 | 15 | public static void setLoggedUser(User user) { 16 | LOGGED_USER.set(user); 17 | } 18 | 19 | public static boolean isLogged() { 20 | return LOGGED_USER.get() != null; 21 | } 22 | 23 | public static Long getUserId() { 24 | return LOGGED_USER.get().getId(); 25 | } 26 | 27 | public static String getUsername() { 28 | return LOGGED_USER.get() != null ? LOGGED_USER.get().getUsername() : null; 29 | } 30 | 31 | public static User getUser() { 32 | return LOGGED_USER.get(); 33 | } 34 | 35 | public static void remove() { 36 | LOGGED_USER.remove(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/annotation/DataConsumer.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.annotation; 2 | 3 | import io.github.lvyahui8.spring.enums.ExceptionProcessingMethod; 4 | import org.springframework.core.annotation.AliasFor; 5 | 6 | import java.lang.annotation.*; 7 | 8 | /** 9 | * 数据依赖项 Data consumer 10 | * 11 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 12 | * @since 2019/6/1 0:05 13 | */ 14 | @Target({ElementType.PARAMETER}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | public @interface DataConsumer { 18 | /** 19 | * Unique identifier of the data 20 | */ 21 | @AliasFor("value") 22 | String id() default ""; 23 | 24 | /** 25 | * Same as id(); 26 | */ 27 | @AliasFor("id") 28 | String value() default ""; 29 | 30 | /** 31 | * The parameter key required by the method being consumed will be dynamically replaced 32 | */ 33 | DynamicParameter [] dynamicParameters() default {}; 34 | 35 | /** 36 | * Exception handling, default by global configuration 37 | */ 38 | ExceptionProcessingMethod exceptionProcessingMethod() 39 | default ExceptionProcessingMethod.BY_DEFAULT; 40 | } 41 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/configuration/AggregatorCustomConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.configuration; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.scheduling.concurrent.CustomizableThreadFactory; 6 | 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.LinkedBlockingDeque; 9 | import java.util.concurrent.ThreadPoolExecutor; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 14 | * @since 2019/8/4 16:14 15 | */ 16 | @Configuration 17 | public class AggregatorCustomConfiguration { 18 | /** 19 | * 自定义ExecutorService, 替代aggregator库使用的executorService 20 | * @return 21 | */ 22 | @Bean 23 | public ExecutorService aggregateExecutorService() { 24 | return new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),Runtime.getRuntime().availableProcessors() * 2 , 25 | 2L, TimeUnit.HOURS, 26 | new LinkedBlockingDeque<>(1024), 27 | new CustomizableThreadFactory("example-async")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/model/InvokeSignature.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Arrays; 7 | import java.util.Objects; 8 | 9 | /** 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/6/28 22:42 12 | */ 13 | @Data 14 | public class InvokeSignature { 15 | private Method method; 16 | private Object[] args; 17 | 18 | public InvokeSignature(Method method, Object[] args) { 19 | this.method = method; 20 | this.args = args; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) { 26 | return true; 27 | } 28 | if (o == null || getClass() != o.getClass()) { 29 | return false; 30 | } 31 | InvokeSignature that = (InvokeSignature) o; 32 | return Objects.deepEquals(method, that.method) && 33 | Arrays.deepEquals(args, that.args); 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | int result = Objects.hash(method); 39 | result = 31 * result + Arrays.hashCode(args); 40 | return result; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/aggregate/UserAggregate.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.aggregate; 2 | 3 | import io.github.lvyahui8.spring.annotation.DataConsumer; 4 | import io.github.lvyahui8.spring.annotation.DataProvider; 5 | import io.github.lvyahui8.spring.example.model.Post; 6 | import io.github.lvyahui8.spring.example.model.User; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 13 | * @since 2019/6/2 16:42 14 | */ 15 | @Component 16 | public class UserAggregate { 17 | @DataProvider("userWithPosts") 18 | public User userWithPosts( 19 | @DataConsumer(id = "user") User user, 20 | @DataConsumer("posts") List posts) { 21 | user.setPosts(posts); 22 | return user; 23 | } 24 | 25 | @DataProvider("userFullData") 26 | public User userFullData(@DataConsumer("user") User user, 27 | @DataConsumer("posts") List posts, 28 | @DataConsumer("followers") List followers) { 29 | user.setFollowers(followers); 30 | user.setPosts(posts); 31 | return user; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | spring-boot-data-aggregator 7 | io.github.lvyahui8 8 | 1.1.3 9 | 10 | 4.0.0 11 | 12 | spring-boot-data-aggregator-core 13 | 14 | spring-boot-data-aggregator-core 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.projectlombok 23 | lombok 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter 28 | 29 | 30 | org.apache.commons 31 | commons-lang3 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/impl/PostServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service.impl; 2 | 3 | import io.github.lvyahui8.spring.annotation.DataProvider; 4 | import io.github.lvyahui8.spring.annotation.InvokeParameter; 5 | import io.github.lvyahui8.spring.example.model.Post; 6 | import io.github.lvyahui8.spring.example.service.PostService; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.util.Assert; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 15 | * @since 2019/6/2 16:54 16 | */ 17 | @Service 18 | public class PostServiceImpl implements PostService { 19 | @DataProvider("posts") 20 | @Override 21 | public List getPosts(@InvokeParameter("userId") Long userId) { 22 | Assert.isTrue(userId != null && userId != 0, "userId must be not null and gt 0!"); 23 | try { 24 | Thread.sleep(1000L); 25 | } catch (InterruptedException e) { 26 | // 27 | } 28 | Post post = new Post(); 29 | post.setTitle("spring data aggregate example"); 30 | post.setContent("No active profile set, falling back to default profiles"); 31 | return Collections.singletonList(post); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service.impl; 2 | 3 | import io.github.lvyahui8.spring.annotation.DataProvider; 4 | import io.github.lvyahui8.spring.annotation.InvokeParameter; 5 | import io.github.lvyahui8.spring.example.context.ExampleAppContext; 6 | import io.github.lvyahui8.spring.example.model.User; 7 | import io.github.lvyahui8.spring.example.service.UserService; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 12 | * @since 2019/6/2 16:36 13 | */ 14 | @Service 15 | public class UserServiceImpl implements UserService { 16 | 17 | @DataProvider("user") 18 | @Override 19 | public User get(@InvokeParameter("userId") Long id) { 20 | /* */ 21 | try { 22 | Thread.sleep(1000L); 23 | } catch (InterruptedException e) { 24 | // 25 | } 26 | /* mock a user*/ 27 | User user = new User(); 28 | user.setId(id); 29 | user.setEmail("lvyahui8@gmail.com"); 30 | user.setUsername("lvyahui8"); 31 | return user; 32 | } 33 | 34 | @DataProvider("loggedUsername") 35 | @Override 36 | public String getLoggedUsername() { 37 | return ExampleAppContext.getUsername(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/interceptor/impl/AggregateQueryInterceptorAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.interceptor.impl; 2 | 3 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 4 | import io.github.lvyahui8.spring.aggregate.interceptor.AggregateQueryInterceptor; 5 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 6 | 7 | /** 8 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 9 | * @since 2019/8/4 15:16 10 | */ 11 | public class AggregateQueryInterceptorAdapter implements AggregateQueryInterceptor { 12 | @Override 13 | public boolean querySubmitted(AggregationContext aggregationContext) { 14 | return true; 15 | } 16 | 17 | @Override 18 | public void queryBefore(AggregationContext aggregationContext, DataProvideDefinition provideDefinition) { 19 | 20 | } 21 | 22 | @Override 23 | public Object queryAfter(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Object result) { 24 | return result; 25 | } 26 | 27 | @Override 28 | public void exceptionHandle(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Exception e) { 29 | 30 | } 31 | 32 | @Override 33 | public void queryFinished(AggregationContext aggregationContext) { 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/repository/impl/DataProviderRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.repository.impl; 2 | 3 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 4 | import io.github.lvyahui8.spring.aggregate.repository.DataProviderRepository; 5 | import org.springframework.util.Assert; 6 | 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/6/2 21:27 12 | */ 13 | public class DataProviderRepositoryImpl implements DataProviderRepository { 14 | 15 | private final ConcurrentHashMap providerMap = new ConcurrentHashMap<>(); 16 | 17 | @Override 18 | public void put(DataProvideDefinition dataProvideDefinition) { 19 | Assert.notNull(dataProvideDefinition.getId(),"data provider id must be not null!"); 20 | String id = dataProvideDefinition.getId(); 21 | Assert.isTrue(! providerMap.containsKey(id),"data provider exist! id: " + id); 22 | providerMap.put(id, dataProvideDefinition); 23 | } 24 | 25 | @Override 26 | public DataProvideDefinition get(String id) { 27 | return providerMap.get(id); 28 | } 29 | 30 | @Override 31 | public boolean contains(String id) { 32 | return providerMap.containsKey(id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/service/AbstractAsyncQueryTask.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.service; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | /** 6 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 7 | * @since 2019/12/25 22:40 8 | */ 9 | public abstract class AbstractAsyncQueryTask implements Callable { 10 | /** 11 | * 任务来源线程 12 | */ 13 | private Thread taskFromThread; 14 | /** 15 | * 异步任务包装器 16 | */ 17 | private AsyncQueryTaskWrapper asyncQueryTaskWrapper; 18 | 19 | protected AbstractAsyncQueryTask(Thread taskFromThread, AsyncQueryTaskWrapper asyncQueryTaskWrapper) { 20 | this.taskFromThread = taskFromThread; 21 | this.asyncQueryTaskWrapper = asyncQueryTaskWrapper; 22 | } 23 | 24 | @Override 25 | public T call() throws Exception { 26 | try { 27 | if(asyncQueryTaskWrapper != null) { 28 | asyncQueryTaskWrapper.beforeExecute(taskFromThread); 29 | } 30 | return execute(); 31 | } finally { 32 | if (asyncQueryTaskWrapper != null) { 33 | asyncQueryTaskWrapper.afterExecute(taskFromThread); 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * 异步任务的实际内容 40 | * 41 | * @return 异步任务返回值 42 | * @throws Exception 异步任务允许抛出异常 43 | */ 44 | public abstract T execute() throws Exception; 45 | } 46 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/aspect/AggregateQueryLoggingAspect.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.aspect; 2 | 3 | import io.github.lvyahui8.spring.example.configuration.ExampleProperties; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | /** 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/7/7 23:17 12 | */ 13 | @Slf4j 14 | @Deprecated 15 | public class AggregateQueryLoggingAspect { 16 | 17 | @Autowired 18 | private ExampleProperties exampleProperties; 19 | 20 | @Around("execution(* io.github.lvyahui8.spring.aggregate.service.impl.DataBeanAggregateServiceImpl.get(..))") 21 | public Object doLogging(ProceedingJoinPoint joinPoint) throws Throwable { 22 | long startTime = System.currentTimeMillis(); 23 | Object retVal ; 24 | try { 25 | retVal = joinPoint.proceed(); 26 | } finally { 27 | Object[] args = joinPoint.getArgs(); 28 | if(log.isInfoEnabled() && exampleProperties.isLogging()) { 29 | log.info("query id: {}, " + 30 | "costTime: {}ms, " , 31 | args[0], 32 | System.currentTimeMillis() - startTime 33 | ); 34 | } 35 | } 36 | return retVal; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/service/DataBeanAggregateService.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.service; 2 | 3 | import io.github.lvyahui8.spring.aggregate.func.MultipleArgumentsFunction; 4 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2019/6/2 21:49 12 | */ 13 | public interface DataBeanAggregateService { 14 | 15 | /** 16 | * 并发+递归获取并拼装数据 17 | * 18 | * @param id data id 19 | * @param invokeParams query parameters 20 | * @param resultType final result type 21 | * @param final result type 22 | * @return final result 23 | * @throws InterruptedException 中断 24 | * @throws InvocationTargetException 反射异常 25 | * @throws IllegalAccessException 反射异常 26 | */ 27 | T get(String id, Map invokeParams, Class resultType) 28 | throws InterruptedException, InvocationTargetException, IllegalAccessException; 29 | 30 | 31 | /** 32 | * 并发+递归获取并拼装数据 33 | * 34 | * @param provider data Provider 35 | * @param invokeParams query parameters 36 | * @param resultType final result type 37 | * @param final result type 38 | * @return final result 39 | * @throws InterruptedException 中断 40 | * @throws InvocationTargetException 反射异常 41 | * @throws IllegalAccessException 反射异常 42 | */ 43 | T get(DataProvideDefinition provider, Map invokeParams, Class resultType) 44 | throws InterruptedException, InvocationTargetException, IllegalAccessException; 45 | 46 | 47 | } -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/wrapper/CustomAsyncQueryTaskWrapper.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.wrapper; 2 | 3 | import io.github.lvyahui8.spring.aggregate.service.AsyncQueryTaskWrapper; 4 | import io.github.lvyahui8.spring.example.context.ExampleAppContext; 5 | import io.github.lvyahui8.spring.example.context.RequestContext; 6 | import io.github.lvyahui8.spring.example.model.User; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 11 | * @since 2020/1/5 13:10 12 | */ 13 | @Slf4j 14 | public class CustomAsyncQueryTaskWrapper implements AsyncQueryTaskWrapper { 15 | 16 | private Long tenantId; 17 | private User user; 18 | 19 | @Override 20 | public void beforeSubmit() { 21 | /* 提交任务前, 先从当前线程拷贝出ThreadLocal中的数据到任务中 */ 22 | log.info("asyncTask beforeSubmit. threadName: {}",Thread.currentThread().getName()); 23 | this.tenantId = RequestContext.getTenantId(); 24 | this.user = ExampleAppContext.getUser(); 25 | } 26 | 27 | @Override 28 | public void beforeExecute(Thread taskFrom) { 29 | /* 任务提交后, 执行前, 在异步线程中用数据恢复ThreadLocal(Context) */ 30 | log.info("asyncTask beforeExecute. threadName: {}, taskFrom: {}",Thread.currentThread().getName(),taskFrom.getName()); 31 | RequestContext.setTenantId(tenantId); 32 | ExampleAppContext.setLoggedUser(user); 33 | } 34 | 35 | @Override 36 | public void afterExecute(Thread taskFrom) { 37 | /* 任务执行完成后, 清理异步线程中的ThreadLocal(Context) */ 38 | log.info("asyncTask afterExecute. threadName: {}, taskFrom: {}",Thread.currentThread().getName(),taskFrom.getName()); 39 | RequestContext.removeTenantId(); 40 | ExampleAppContext.remove(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | spring-boot-data-aggregator 7 | io.github.lvyahui8 8 | 1.1.3 9 | 10 | 4.0.0 11 | 12 | spring-boot-data-aggregator-autoconfigure 13 | 14 | spring-boot-data-aggregator-autoconfigure 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-configuration-processor 24 | true 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-autoconfigure 29 | 30 | 31 | org.projectlombok 32 | lombok 33 | 34 | 35 | 36 | org.reflections 37 | reflections 38 | 0.9.11 39 | 40 | 41 | io.github.lvyahui8 42 | spring-boot-data-aggregator-core 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/impl/FollowServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service.impl; 2 | 3 | import io.github.lvyahui8.spring.annotation.DataProvider; 4 | import io.github.lvyahui8.spring.annotation.InvokeParameter; 5 | import io.github.lvyahui8.spring.example.context.ExampleAppContext; 6 | import io.github.lvyahui8.spring.example.model.User; 7 | import io.github.lvyahui8.spring.example.service.FollowService; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | /** 15 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 16 | * @since 2019/6/11 21:32 17 | */ 18 | @Service 19 | public class FollowServiceImpl implements FollowService { 20 | @DataProvider("followers") 21 | @Override 22 | public List getFollowers(@InvokeParameter("userId") Long userId) { 23 | try { Thread.sleep(1000L); } catch (InterruptedException e) {} 24 | int size = 10; 25 | List users = new ArrayList<>(size); 26 | for(int i = 0 ; i < size; i++) { 27 | User user = new User(); 28 | user.setUsername("name"+i); 29 | user.setEmail("email"+i+"@fox.com"); 30 | user.setId((long) i); 31 | users.add(user); 32 | } 33 | return users; 34 | } 35 | 36 | @DataProvider("loggedUserFollowers") 37 | @Override 38 | public List getLoggedUserFollowers() { 39 | Long userId = ExampleAppContext.getUserId(); 40 | String username = ExampleAppContext.getUsername(); 41 | User follower = new User(); 42 | follower.setId(userId + 10000L); 43 | follower.setUsername(username + "_follower"); 44 | return Collections.singletonList(follower); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/interceptor/AggregateQueryInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.interceptor; 2 | 3 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 4 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 5 | 6 | /** 7 | * 查询过程中各个生命周期的拦截处理 8 | * 9 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 10 | * @since 2019/7/21 22:28 11 | */ 12 | public interface AggregateQueryInterceptor { 13 | /** 14 | * 查询正常提交, Context已经创建 15 | * 16 | * @param aggregationContext 查询上下文 17 | * @return 返回为true才继续执行 18 | */ 19 | boolean querySubmitted(AggregationContext aggregationContext) ; 20 | 21 | /** 22 | * 每个Provider方法执行前, 将调用此方法. 存在并发调用 23 | * 24 | * @param aggregationContext 查询上下文 25 | * @param provideDefinition 将被执行的Provider 26 | */ 27 | void queryBefore(AggregationContext aggregationContext, DataProvideDefinition provideDefinition); 28 | 29 | /** 30 | * 每个Provider方法执行成功之后, 调用此方法. 存在并发调用 31 | * 32 | * @param aggregationContext 查询上下文 33 | * @param provideDefinition 被执行的Provider 34 | * @param result 查询结果 35 | * @return 返回结果, 如不修改不, 请直接返回参数中的result 36 | */ 37 | Object queryAfter(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Object result); 38 | 39 | /** 40 | * 每个Provider执行时, 如果抛出异常, 将调用此方法. 存在并发调用 41 | * 42 | * @param aggregationContext 查询上下文 43 | * @param provideDefinition 被执行的Provider 44 | * @param e Provider抛出的异常 45 | */ 46 | void exceptionHandle(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Exception e); 47 | 48 | /** 49 | * 一次查询全部完成. 50 | * 51 | * @param aggregationContext 查询上下文 52 | */ 53 | void queryFinished(AggregationContext aggregationContext); 54 | } 55 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/impl/HomepageServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service.impl; 2 | 3 | import io.github.lvyahui8.spring.annotation.DataProvider; 4 | import io.github.lvyahui8.spring.example.context.RequestContext; 5 | import io.github.lvyahui8.spring.example.model.Category; 6 | import io.github.lvyahui8.spring.example.model.Post; 7 | import io.github.lvyahui8.spring.example.model.User; 8 | import io.github.lvyahui8.spring.example.service.HomepageService; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.util.Assert; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 16 | * @since 2019/7/22 23:08 17 | */ 18 | @Service 19 | public class HomepageServiceImpl implements HomepageService { 20 | 21 | @DataProvider("topMenu") 22 | @Override 23 | public List topMenu() { 24 | /* will be null */ 25 | Long tenantId = RequestContext.getTenantId(); 26 | Assert.notNull(tenantId,"tenantId must be not null"); 27 | // ... The content hereafter will be omitted. 28 | return null; 29 | } 30 | 31 | @DataProvider("postList") 32 | @Override 33 | public List postList() { 34 | /* will be null */ 35 | Long tenantId = RequestContext.getTenantId(); 36 | Assert.notNull(tenantId,"tenantId must be not null"); 37 | // ... The content hereafter will be omitted. 38 | return null; 39 | } 40 | 41 | @DataProvider("allFollowers") 42 | @Override 43 | public List allFollowers() { 44 | /* will be null */ 45 | Long tenantId = RequestContext.getTenantId(); 46 | Assert.notNull(tenantId,"tenantId must be not null"); 47 | // ... The content hereafter will be omitted. 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/src/main/java/io/github/lvyahui8/spring/facade/DataFacade.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.facade; 2 | 3 | import io.github.lvyahui8.spring.aggregate.facade.DataBeanAggregateQueryFacade; 4 | import io.github.lvyahui8.spring.aggregate.func.MultipleArgumentsFunction; 5 | import lombok.AccessLevel; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 14 | * @since 2020/2/13 15 | */ 16 | public class DataFacade { 17 | 18 | @Setter(AccessLevel.PACKAGE) 19 | @Getter(AccessLevel.PACKAGE) 20 | private static DataBeanAggregateQueryFacade facade; 21 | 22 | public static T get(String id, Map invokeParams, Class clazz) 23 | throws InterruptedException, IllegalAccessException, InvocationTargetException { 24 | return facade.get(id,invokeParams,clazz); 25 | } 26 | 27 | public static T get(Map invokeParams, MultipleArgumentsFunction multipleArgumentsFunction) 28 | throws InterruptedException, IllegalAccessException, InvocationTargetException { 29 | return facade.get(invokeParams,multipleArgumentsFunction); 30 | } 31 | 32 | public static T get(Map invokeParams, MultipleArgumentsFunction multipleArgumentsFunction,Long timeout) 33 | throws InterruptedException, IllegalAccessException, InvocationTargetException { 34 | return facade.get(invokeParams,multipleArgumentsFunction,timeout); 35 | } 36 | 37 | public static T groupGet(String groupKey,Map invokeParams,Class clazz) 38 | throws InterruptedException, IllegalAccessException, InvocationTargetException { 39 | throw new UnsupportedOperationException("Not yet supported"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | spring-boot-data-aggregator 7 | io.github.lvyahui8 8 | 1.1.3 9 | 10 | 4.0.0 11 | 12 | spring-boot-data-aggregator-example 13 | 14 | spring-boot-data-aggregator-example 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | test 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-aop 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-configuration-processor 42 | true 43 | 44 | 45 | io.github.lvyahui8 46 | spring-boot-data-aggregator-starter 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/src/main/java/io/github/lvyahui8/spring/autoconfigure/BeanAggregateProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.autoconfigure; 2 | 3 | import io.github.lvyahui8.spring.aggregate.service.AsyncQueryTaskWrapper; 4 | import io.github.lvyahui8.spring.aggregate.service.AsyncQueryTaskWrapperAdapter; 5 | import lombok.Data; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | /** 9 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 10 | * @since 2019/5/31 23:50 11 | */ 12 | @ConfigurationProperties(prefix = "io.github.lvyahui8.spring") 13 | @Data 14 | public class BeanAggregateProperties { 15 | /** 16 | * Packages that need to scan for aggregated annotations 17 | */ 18 | private String[] basePackages; 19 | /** 20 | * Thread name prefix for asynchronous threads 21 | */ 22 | private String threadPrefix = "aggregateTask-"; 23 | /** 24 | * Thread size of the asynchronous thread pool 25 | */ 26 | private int threadNumber = Runtime.getRuntime().availableProcessors() * 3; 27 | /** 28 | * The size of the queue that holds the task to be executed 29 | */ 30 | private int queueSize = 1000; 31 | /** 32 | * Set a default timeout for the method of providing data 33 | */ 34 | private Long defaultTimeout = 3000L; 35 | /** 36 | * Allow output log 37 | */ 38 | @Deprecated 39 | private Boolean enableLogging = true; 40 | /** 41 | * Ignore exception thrown by asynchronous execution, method returns null value 42 | */ 43 | private boolean ignoreException = false; 44 | /** 45 | * Async task implement 46 | */ 47 | private Class taskWrapperClass = AsyncQueryTaskWrapperAdapter.class; 48 | } 49 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/interceptor/SampleAggregateQueryInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.interceptor; 2 | 3 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 4 | import io.github.lvyahui8.spring.aggregate.interceptor.AggregateQueryInterceptor; 5 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 12 | * @since 2019/8/4 17:24 13 | */ 14 | @Component 15 | @Order(2) 16 | @Slf4j 17 | public class SampleAggregateQueryInterceptor implements AggregateQueryInterceptor { 18 | @Override 19 | public boolean querySubmitted(AggregationContext aggregationContext) { 20 | log.info("begin query. root:{}",aggregationContext.getRootProvideDefinition().getMethod().getName()); 21 | return true; 22 | } 23 | 24 | @Override 25 | public void queryBefore(AggregationContext aggregationContext, DataProvideDefinition provideDefinition) { 26 | log.info("query before. provider:{}",provideDefinition.getMethod().getName()); 27 | } 28 | 29 | @Override 30 | public Object queryAfter(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Object result) { 31 | log.info("query after. provider:{},result:{}",provideDefinition.getMethod().getName(),result != null ? 32 | result.toString() : "null"); 33 | return result; 34 | } 35 | 36 | @Override 37 | public void exceptionHandle(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Exception e) { 38 | log.error(e.getMessage()); 39 | } 40 | 41 | @Override 42 | public void queryFinished(AggregationContext aggregationContext) { 43 | log.info("query finish. root: {}",aggregationContext.getRootProvideDefinition().getMethod().getName()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/service/impl/ProviderServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.service.impl; 2 | 3 | import io.github.lvyahui8.spring.aggregate.func.MultipleArgumentsFunction; 4 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 5 | import io.github.lvyahui8.spring.aggregate.repository.DataProviderRepository; 6 | import io.github.lvyahui8.spring.aggregate.service.ProviderService; 7 | import io.github.lvyahui8.spring.aggregate.util.DefinitionUtils; 8 | import lombok.Setter; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Modifier; 13 | 14 | /** 15 | * @author feego lvyahui8@gmail.com 16 | * @date 2022/4/2 17 | */ 18 | public class ProviderServiceImpl implements ProviderService { 19 | @Setter 20 | DataProviderRepository repository; 21 | @Override 22 | public DataProvideDefinition getProvider(MultipleArgumentsFunction multipleArgumentsFunction) throws IllegalAccessException { 23 | DataProvideDefinition provider = repository.get(multipleArgumentsFunction.getClass().getName()); 24 | if(provider != null) { 25 | return provider; 26 | } 27 | Method[] methods = multipleArgumentsFunction.getClass().getMethods(); 28 | Method applyMethod = null; 29 | 30 | 31 | for (Method method : methods) { 32 | if(! Modifier.isStatic(method.getModifiers()) && ! method.isDefault()) { 33 | applyMethod = method; 34 | break; 35 | } 36 | } 37 | 38 | if(applyMethod == null) { 39 | throw new IllegalAccessException(multipleArgumentsFunction.getClass().getName()); 40 | } 41 | 42 | provider = DefinitionUtils.getProvideDefinition(applyMethod); 43 | provider.setTarget(multipleArgumentsFunction); 44 | provider.setId(multipleArgumentsFunction.getClass().getName()); 45 | repository.put(provider); 46 | return provider; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/interceptor/AggregateQueryInterceptorChain.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.interceptor; 2 | 3 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 4 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 5 | 6 | /** 7 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 8 | * @since 2019/8/4 15:10 9 | */ 10 | public interface AggregateQueryInterceptorChain { 11 | /** 12 | * 新增一个拦截器 13 | * 14 | * @param queryInterceptor 新增的拦截器 15 | */ 16 | void addInterceptor(AggregateQueryInterceptor queryInterceptor) ; 17 | 18 | /** 19 | * 顺序调用拦截器前置处理, 查询正常提交, Context已经创建 20 | * 21 | * @param aggregationContext 查询上下文 22 | * @return 是否提前结束 23 | */ 24 | boolean applyQuerySubmitted(AggregationContext aggregationContext); 25 | /** 26 | * 顺序调用拦截器前置处理, 每个Provider方法执行前, 将调用此方法. 存在并发调用 27 | * 28 | * @param aggregationContext 查询上下文 29 | * @param provideDefinition 将被执行的Provider 30 | */ 31 | void applyQueryBefore(AggregationContext aggregationContext, DataProvideDefinition provideDefinition); 32 | 33 | /** 34 | * 顺序调用拦截器前置处理, 每个Provider方法执行成功之后, 调用此方法. 存在并发调用 35 | * @param aggregationContext 查询上下文 36 | * @param provideDefinition 被执行的Provider 37 | * @param result 查询结果 38 | * @return 链式处理后的结果 39 | */ 40 | Object applyQueryAfter(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Object result); 41 | 42 | /** 43 | * 顺序调用拦截器前置处理, 每个Provider执行时, 如果抛出异常, 将调用此方法. 存在并发调用 44 | * 45 | * @param aggregationContext 查询上下文 46 | * @param provideDefinition 被执行的Provider 47 | * @param e Provider抛出的异常 48 | */ 49 | void applyExceptionHandle(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Exception e); 50 | 51 | /** 52 | * 顺序调用拦截器前置处理, 一次查询全部完成. 53 | * 54 | * @param aggregationContext 查询上下文 55 | */ 56 | void applyQueryFinished(AggregationContext aggregationContext); 57 | } 58 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate2/ResourceNode.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate2; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.CountDownLatch; 11 | 12 | /** 13 | * @author feego lvyahui8@gmail.com 14 | * @date 2022/4/1 15 | */ 16 | @Data 17 | public class ResourceNode { 18 | Object target; 19 | Method method; 20 | String key; 21 | 22 | Map dependents; 23 | 24 | @Data 25 | class Dependent { 26 | boolean strong; 27 | ResourceNode node; 28 | } 29 | 30 | public Object invoke(Context context) throws Exception { 31 | if (context.getResultMap().containsKey(key)) { 32 | return context.getResultMap().get(key); 33 | } 34 | List args = new LinkedList<>(); 35 | if (dependents != null) { 36 | List> asyncList = new LinkedList<>(); 37 | for (Map.Entry entry : dependents.entrySet()) { 38 | final Dependent dependent = entry.getValue(); 39 | asyncList.add(CompletableFuture.supplyAsync(() -> { 40 | try { 41 | return dependent.node.invoke(context); 42 | } catch (Exception e) { 43 | if (dependent.isStrong()) { 44 | throw new StrongDependentException(e); 45 | } else { 46 | return context.getExceptionHandler().handle(e); 47 | } 48 | } 49 | }, context.getExecutor())); 50 | } 51 | final CompletableFuture allOf = CompletableFuture.allOf(asyncList.toArray(new CompletableFuture[0])); 52 | allOf.wait(context.getDefaultTimeout()); 53 | for (CompletableFuture future : asyncList) { 54 | args.add(future.get()); 55 | } 56 | } 57 | return method.invoke(target,args.toArray(new Object[0])); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/service/impl/CategoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.service.impl; 2 | 3 | import io.github.lvyahui8.spring.annotation.DataConsumer; 4 | import io.github.lvyahui8.spring.annotation.DataProvider; 5 | import io.github.lvyahui8.spring.annotation.InvokeParameter; 6 | import io.github.lvyahui8.spring.example.model.Category; 7 | import io.github.lvyahui8.spring.example.service.CategoryService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | import java.util.Random; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.Stream; 15 | 16 | /** 17 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 18 | * @since 2019/6/28 23:38 19 | */ 20 | @Service 21 | @Slf4j 22 | public class CategoryServiceImpl implements CategoryService { 23 | 24 | @DataProvider("rootCategory") 25 | @Override 26 | public Category getRootCategory(@DataConsumer("topCategoryNames") List topCategoryNames,@InvokeParameter("categoryId") Long id) { 27 | Category category = new Category(); 28 | category.setId(id); 29 | category.setName("youtube"); 30 | category.setTopCategoryNames(topCategoryNames); 31 | return category; 32 | } 33 | 34 | @DataProvider("categoryTitle") 35 | @Override 36 | public String getCategoryTitle(@DataConsumer("rootCategory") Category category, 37 | @DataConsumer("topCategoryNames") List topCategoryNames) { 38 | if(category.getTopCategoryNames() == topCategoryNames){ 39 | log.info("'user.getFollowers()' and 'followers' may be the same object " + 40 | "because the query cache is used. "); 41 | } 42 | return category.getName(); 43 | } 44 | 45 | @DataProvider("topCategoryNames") 46 | @Override 47 | public List getTopCategoryNames() { 48 | Random r = new Random(System.currentTimeMillis()); 49 | return Stream.of("feego", "figo", "sam").map(item -> item+r.nextInt(1000)) 50 | .collect(Collectors.toList()); 51 | } 52 | 53 | 54 | /// @DataProvider("cycleDependA") 55 | @Override 56 | public Object cycleDependA(@DataConsumer("cycleDependB") Object dependB) { 57 | return null; 58 | } 59 | 60 | /// @DataProvider("cycleDependB") 61 | @Override 62 | public Object cycleDependB(@DataConsumer("cycleDependA") Object dependA) { 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/main/java/io/github/lvyahui8/spring/example/facade/UserQueryFacade.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example.facade; 2 | 3 | import io.github.lvyahui8.spring.aggregate.facade.DataBeanAggregateQueryFacade; 4 | import io.github.lvyahui8.spring.example.model.Post; 5 | import io.github.lvyahui8.spring.example.model.User; 6 | import io.github.lvyahui8.spring.example.service.FollowService; 7 | import io.github.lvyahui8.spring.example.service.PostService; 8 | import io.github.lvyahui8.spring.example.service.UserService; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.concurrent.*; 16 | 17 | /** 18 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 19 | * @since 2019/6/11 21:46 20 | */ 21 | @Component 22 | public class UserQueryFacade { 23 | @Autowired 24 | private FollowService followService; 25 | @Autowired 26 | private PostService postService; 27 | @Autowired 28 | private UserService userService; 29 | 30 | @Autowired 31 | private DataBeanAggregateQueryFacade dataBeanAggregateQueryFacade; 32 | 33 | public User getUserData(Long userId) { 34 | User user = userService.get(userId); 35 | user.setPosts(postService.getPosts(userId)); 36 | user.setFollowers(followService.getFollowers(userId)); 37 | return user; 38 | } 39 | 40 | public User getUserDataByParallel(Long userId) throws InterruptedException, ExecutionException { 41 | ExecutorService executorService = Executors.newFixedThreadPool(3); 42 | CountDownLatch countDownLatch = new CountDownLatch(3); 43 | Future userFuture = executorService.submit(() -> { 44 | try{ 45 | return userService.get(userId); 46 | }finally { 47 | countDownLatch.countDown(); 48 | } 49 | }); 50 | Future> postsFuture = executorService.submit(() -> { 51 | try{ 52 | return postService.getPosts(userId); 53 | }finally { 54 | countDownLatch.countDown(); 55 | } 56 | }); 57 | Future> followersFuture = executorService.submit(() -> { 58 | try{ 59 | return followService.getFollowers(userId); 60 | }finally { 61 | countDownLatch.countDown(); 62 | } 63 | }); 64 | countDownLatch.await(); 65 | User user = userFuture.get(); 66 | user.setFollowers(followersFuture.get()); 67 | user.setPosts(postsFuture.get()); 68 | return user; 69 | } 70 | 71 | public User getUserFinal(Long userId) throws InterruptedException, 72 | IllegalAccessException, InvocationTargetException { 73 | return dataBeanAggregateQueryFacade.get("userFullData", 74 | Collections.singletonMap("userId", userId), User.class); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/interceptor/impl/AggregateQueryInterceptorChainImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.interceptor.impl; 2 | 3 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 4 | import io.github.lvyahui8.spring.aggregate.interceptor.AggregateQueryInterceptor; 5 | import io.github.lvyahui8.spring.aggregate.interceptor.AggregateQueryInterceptorChain; 6 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 13 | * @since 2019/8/4 15:22 14 | */ 15 | public class AggregateQueryInterceptorChainImpl implements AggregateQueryInterceptorChain { 16 | 17 | private List interceptors; 18 | 19 | private List initInterceptors() { 20 | if(this.interceptors == null) { 21 | synchronized (this) { 22 | if(this.interceptors == null) { 23 | this.interceptors = new ArrayList<>(1); 24 | } 25 | } 26 | } 27 | return this.interceptors; 28 | } 29 | 30 | @Override 31 | public void addInterceptor(AggregateQueryInterceptor queryInterceptor) { 32 | this.initInterceptors().add(queryInterceptor); 33 | } 34 | 35 | @Override 36 | public boolean applyQuerySubmitted(AggregationContext aggregationContext) { 37 | for (AggregateQueryInterceptor interceptor : this.initInterceptors()) { 38 | if(!interceptor.querySubmitted(aggregationContext)) { 39 | return false; 40 | } 41 | } 42 | return true; 43 | } 44 | 45 | @Override 46 | public void applyQueryBefore(AggregationContext aggregationContext, DataProvideDefinition provideDefinition) { 47 | for (AggregateQueryInterceptor interceptor : this.initInterceptors()) { 48 | interceptor.queryBefore(aggregationContext,provideDefinition); 49 | } 50 | } 51 | 52 | @Override 53 | public Object applyQueryAfter(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Object result) { 54 | for (AggregateQueryInterceptor interceptor : this.initInterceptors()) { 55 | result = interceptor.queryAfter(aggregationContext,provideDefinition,result); 56 | } 57 | return result; 58 | } 59 | 60 | @Override 61 | public void applyExceptionHandle(AggregationContext aggregationContext, DataProvideDefinition provideDefinition, Exception e) { 62 | for (AggregateQueryInterceptor interceptor : this.initInterceptors()) { 63 | interceptor.exceptionHandle(aggregationContext,provideDefinition,e); 64 | } 65 | } 66 | 67 | @Override 68 | public void applyQueryFinished(AggregationContext aggregationContext) { 69 | for (AggregateQueryInterceptor interceptor : this.initInterceptors()) { 70 | interceptor.queryFinished(aggregationContext); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/facade/impl/DataBeanAggregateQueryFacadeImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.facade.impl; 2 | 3 | import io.github.lvyahui8.spring.aggregate.facade.DataBeanAggregateQueryFacade; 4 | import io.github.lvyahui8.spring.aggregate.func.MultipleArgumentsFunction; 5 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 6 | import io.github.lvyahui8.spring.aggregate.service.DataBeanAggregateService; 7 | import io.github.lvyahui8.spring.aggregate.service.ProviderService; 8 | import org.springframework.util.Assert; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Method; 12 | import java.util.Collections; 13 | import java.util.Map; 14 | 15 | 16 | /** 17 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 18 | * @since 2019/6/1 0:22 19 | */ 20 | public class DataBeanAggregateQueryFacadeImpl implements DataBeanAggregateQueryFacade { 21 | 22 | private final DataBeanAggregateService dataBeanAggregateService; 23 | 24 | private final ProviderService providerService; 25 | 26 | public DataBeanAggregateQueryFacadeImpl(DataBeanAggregateService dataBeanAggregateService,ProviderService providerService) { 27 | this.dataBeanAggregateService = dataBeanAggregateService; 28 | this.providerService = providerService; 29 | } 30 | 31 | @Override 32 | public T get(String id, Map invokeParams, Class clazz) throws InterruptedException, IllegalAccessException, InvocationTargetException { 33 | Assert.notNull(id,"id must be not null!"); 34 | Assert.notNull(clazz,"clazz must be not null !"); 35 | if(invokeParams == null) { 36 | invokeParams = Collections.emptyMap(); 37 | } 38 | return dataBeanAggregateService.get(id,invokeParams,clazz); 39 | } 40 | 41 | @Override 42 | public T get(Map invokeParams, MultipleArgumentsFunction multipleArgumentsFunction) throws InterruptedException, IllegalAccessException, InvocationTargetException { 43 | return get(invokeParams,multipleArgumentsFunction,null); 44 | } 45 | 46 | @Override 47 | public T get(Map invokeParams, MultipleArgumentsFunction multipleArgumentsFunction, Long timeout) throws InterruptedException, IllegalAccessException, InvocationTargetException{ 48 | if(invokeParams == null) { 49 | invokeParams = Collections.emptyMap(); 50 | } 51 | 52 | DataProvideDefinition provider = providerService.getProvider(multipleArgumentsFunction); 53 | Method applyMethod = provider.getMethod(); 54 | boolean accessible = applyMethod.isAccessible(); 55 | if(! accessible) { 56 | applyMethod.setAccessible(true); 57 | } 58 | try { 59 | @SuppressWarnings("unchecked") 60 | T ret = (T) dataBeanAggregateService.get(provider, invokeParams, applyMethod.getReturnType()); 61 | 62 | return ret; 63 | } finally { 64 | if(! accessible) { 65 | applyMethod.setAccessible(accessible); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/facade/DataBeanAggregateQueryFacade.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.facade; 2 | 3 | import io.github.lvyahui8.spring.aggregate.func.MultipleArgumentsFunction; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.util.Map; 7 | 8 | /** 9 | * The only API for this framework 10 | * 11 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 12 | * @since 2019/6/1 0:22 13 | */ 14 | public interface DataBeanAggregateQueryFacade { 15 | /** 16 | * Used to query data. It has the following three functions 17 | * 1. Automatic analysis dependence 18 | * 2. Parallel access dependency 19 | * 3. Automatic injection 20 | * 21 | * @param id Data id 22 | * @param invokeParams Fixed parameters that need to be passed in the query process 23 | * @param clazz Return value type bytecode object 24 | * @param Return value type 25 | * @return Return value 26 | * @throws InterruptedException If the thread is interrupted, this exception will be thrown 27 | * @throws IllegalAccessException Thrown if the data provider cannot be executed 28 | * @throws InvocationTargetException If the data provider throws an unhandled exception, this exception will be thrown 29 | */ 30 | T get(String id, Map invokeParams, Class clazz) 31 | throws InterruptedException, IllegalAccessException, InvocationTargetException; 32 | 33 | /** 34 | * Used to query data. It has the following three functions 35 | * 1. Automatic analysis dependence 36 | * 2. Parallel access dependency 37 | * 3. Automatic injection 38 | * 39 | * @param invokeParams Fixed parameters that need to be passed in the query process 40 | * @param multipleArgumentsFunction Multiple arguments function 41 | * @param Return value type 42 | * @return Return value 43 | * @throws InterruptedException If the thread is interrupted, this exception will be thrown 44 | * @throws IllegalAccessException Thrown if the data provider cannot be executed 45 | * @throws InvocationTargetException If the data provider throws an unhandled exception, this exception will be thrown 46 | */ 47 | T get(Map invokeParams, MultipleArgumentsFunction multipleArgumentsFunction) 48 | throws InterruptedException, IllegalAccessException, InvocationTargetException; 49 | 50 | /** 51 | * Used to query data. It has the following three functions 52 | * 1. Automatic analysis dependence 53 | * 2. Parallel access dependency 54 | * 3. Automatic injection 55 | * 56 | * @param invokeParams Fixed parameters that need to be passed in the query process 57 | * @param multipleArgumentsFunction Multiple arguments function 58 | * @param timeout Timeout 59 | * @param Return value type 60 | * @return Return value 61 | * @throws InterruptedException If the thread is interrupted, this exception will be thrown 62 | * @throws IllegalAccessException Thrown if the data provider cannot be executed 63 | * @throws InvocationTargetException If the data provider throws an unhandled exception, this exception will be thrown 64 | */ 65 | T get(Map invokeParams, MultipleArgumentsFunction multipleArgumentsFunction,Long timeout) 66 | throws InterruptedException, IllegalAccessException, InvocationTargetException; 67 | } 68 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/util/DefinitionUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.util; 2 | 3 | import io.github.lvyahui8.spring.aggregate.model.*; 4 | import io.github.lvyahui8.spring.annotation.DataConsumer; 5 | import io.github.lvyahui8.spring.annotation.DynamicParameter; 6 | import io.github.lvyahui8.spring.annotation.InvokeParameter; 7 | import io.github.lvyahui8.spring.enums.ExceptionProcessingMethod; 8 | import org.springframework.core.annotation.AnnotationUtils; 9 | import org.springframework.util.Assert; 10 | import org.springframework.util.StringUtils; 11 | 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Parameter; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 21 | * @since 2019/7/14 13:34 22 | */ 23 | public class DefinitionUtils { 24 | 25 | /** 26 | * get provider's consume definitions 27 | * 28 | * @param method provider method 29 | * @return result 30 | */ 31 | public static DataProvideDefinition getProvideDefinition(Method method){ 32 | List consumeDefinitions = new ArrayList<>(); 33 | Parameter[] parameters = method.getParameters(); 34 | 35 | DataProvideDefinition provider = new DataProvideDefinition(); 36 | List methodArgs = new ArrayList<>(method.getParameterCount()); 37 | provider.setDepends(new ArrayList<>(method.getParameterCount())); 38 | provider.setParams(new ArrayList<>(method.getParameterCount())); 39 | provider.setMethod(method); 40 | 41 | for (Parameter parameter : parameters) { 42 | dealMethodParameter(provider, methodArgs, parameter); 43 | } 44 | provider.setMethodArgs(methodArgs); 45 | return provider; 46 | } 47 | 48 | private static void dealMethodParameter(DataProvideDefinition provideDefinition, 49 | List methodArgs, Parameter parameter) { 50 | DataConsumer dataConsumer = AnnotationUtils.findAnnotation(parameter, DataConsumer.class); 51 | InvokeParameter invokeParameter = AnnotationUtils.findAnnotation(parameter,InvokeParameter.class); 52 | Assert.isTrue(dataConsumer != null || invokeParameter != null, 53 | "Parameters must be added @InvokeParameter or @DataConsumer annotation"); 54 | MethodArg methodArg = new MethodArg(); 55 | if(dataConsumer != null) { 56 | String dataId = dataConsumer.id(); 57 | Assert.isTrue(! StringUtils.isEmpty(dataId),"data id must be not null!"); 58 | methodArg.setAnnotationKey(dataId); 59 | methodArg.setDependType(DependType.OTHER_MODEL); 60 | DataConsumeDefinition dataConsumeDefinition = new DataConsumeDefinition(); 61 | dataConsumeDefinition.setClazz(parameter.getType()); 62 | dataConsumeDefinition.setId(dataId); 63 | if(dataConsumer.dynamicParameters().length > 0) { 64 | Map parameterKeyMap = new HashMap<>(dataConsumer.dynamicParameters().length); 65 | for (DynamicParameter dynamicParameter : dataConsumer.dynamicParameters()) { 66 | parameterKeyMap.put(dynamicParameter.targetKey(),dynamicParameter.replacementKey()); 67 | } 68 | dataConsumeDefinition.setDynamicParameterKeyMap(parameterKeyMap); 69 | } 70 | dataConsumeDefinition.setOriginalParameterName(parameter.getName()); 71 | if(! dataConsumer.exceptionProcessingMethod().equals(ExceptionProcessingMethod.BY_DEFAULT)) { 72 | dataConsumeDefinition.setIgnoreException( 73 | dataConsumer.exceptionProcessingMethod().equals(ExceptionProcessingMethod.IGNORE) 74 | ); 75 | } 76 | provideDefinition.getDepends().add(dataConsumeDefinition); 77 | } else { 78 | methodArg.setAnnotationKey(invokeParameter.value()); 79 | methodArg.setDependType(DependType.INVOKE_PARAM); 80 | InvokeParameterDefinition parameterDefinition = new InvokeParameterDefinition(); 81 | parameterDefinition.setKey(invokeParameter.value()); 82 | provideDefinition.getParams().add(parameterDefinition); 83 | } 84 | methodArg.setParameter(parameter); 85 | methodArgs.add(methodArg); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot 并行数据聚合库 2 | 3 | [![Build Status](https://travis-ci.org/lvyahui8/spring-boot-data-aggregator.svg?branch=develop)](https://travis-ci.org/lvyahui8/spring-boot-data-aggregator) 4 | [![Codecov](https://codecov.io/gh/lvyahui8/spring-boot-data-aggregator/branch/develop/graph/badge.svg)](https://codecov.io/gh/lvyahui8/spring-boot-data-aggregator/branch/develop) 5 | [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) 6 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.lvyahui8/spring-boot-data-aggregator-starter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.lvyahui8/spring-boot-data-aggregator-starter) 7 | [![GitHub release](https://img.shields.io/github/release/lvyahui8/spring-boot-data-aggregator.svg)](https://github.com/lvyahui8/spring-boot-data-aggregator/releases) 8 | 9 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/lvyahui8/spring-boot-data-aggregator.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lvyahui8/spring-boot-data-aggregator/alerts/) 10 | [![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/lvyahui8/spring-boot-data-aggregator.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lvyahui8/spring-boot-data-aggregator/context:java) 11 | 12 | 基于注解实现并行地依赖注入(调用),可以看做 Spring `@Async` 注解的升级版。 13 | 14 | ![image-20200309230202047](README.assets/image-20200309230202047.png) 15 | 16 | ## 特性 17 | 18 | - **异步获取依赖** 19 | 20 | 所有 `@DataConsumer` 定义的依赖将异步获取. 当provider方法参数中的所有依赖获取完成, 才执行provider方法 21 | 22 | - **不限级嵌套** 23 | 24 | 依赖关系支持深层嵌套. 下面的示例只有一层 25 | 26 | - **异常处理** 27 | 28 | 目前支持两种处理方式: 忽略or终止 29 | 30 | 忽略是指provider方法在执行时, 忽略抛出的异常并return null值; 终止是指一旦有一个provider方法抛出了异常, 将逐级向上抛出, 终止后续处理. 31 | 32 | 配置支持consumer级或者全局, 优先级 : consumer级 > 全局 33 | 34 | - **查询缓存** 35 | 36 | 在调用Facade的query方法的一次查询生命周期内, **方法调用结果可能复用, 只要方法签名以及传参一致, 则默认方法是幂等的, 将直接使用缓存的查询结果.** 但这个不是绝对的, 考虑到多线程的特性, 可能有时候不会使用缓存 37 | 38 | - **超时控制** 39 | 40 | `@DataProvider` 注解支持配置timeout, 超时将抛出中断异常 (InterruptedException), 遵循异常处理逻辑 41 | 42 | ## 使用方法 43 | 44 | ### 1. 配置 45 | 46 | pom.xml 47 | 48 | ```xml 49 | 50 | io.github.lvyahui8 51 | spring-boot-data-aggregator-starter 52 | {$LATEST_VERSION} 53 | 54 | ``` 55 | 56 | application.properties 57 | 58 | ```properties 59 | # 指定要扫描注解的包 60 | io.github.lvyahui8.spring.base-packages=io.github.lvyahui8.spring.example 61 | ``` 62 | 63 | ### 2. 添加注解 64 | 65 | - `@DataProvider` 定义数据提供者 66 | - `@DataConsumer` 定义方法参数依赖类型为其他接口返回值, 其他接口是一个`@DataProvider` 67 | - `@InvokeParameter` 定义方法参数依赖类型为用户输入值 68 | 69 | ### 3. 查询 70 | 71 | 通过 `DataFacade.get` 静态门面查询指定数据 72 | 73 | ## 示例 74 | 75 | 开发一个用户汇总数据接口, 包括用户的基础信息和博客列表 76 | 77 | ### 1. 定义提供基础数据的"原子"服务 78 | 79 | 使用`@DataProvider`定义接口为数据提供者 80 | 81 | 使用`@InvokeParameter`指定要传递的用户输入参数 82 | 83 | **博客列表服务** 84 | 85 | 需要参数`userId` 86 | 87 | ```java 88 | @Service 89 | public class PostServiceImpl implements PostService { 90 | @DataProvider("posts") 91 | @Override 92 | public List getPosts(@InvokeParameter("userId") Long userId) { 93 | ``` 94 | 95 | **用户基础信息查询服务** 96 | 97 | 需要参数`userId` 98 | 99 | ```java 100 | @Service 101 | public class UserServiceImpl implements UserService { 102 | @DataProvider("user") 103 | @Override 104 | public User get(@InvokeParameter("userId") Long id) { 105 | ``` 106 | 107 | ### 2. 调用聚合接口 108 | 109 | #### 方式一: 函数式调用 110 | 111 | 注意这里不能将函数式调用改为Lambda表达式, 两者的实际行为是不一致的. 112 | 113 | ```java 114 | User user = DataFacade.get( 115 | Collections.singletonMap("userId", 1L), 116 | new Function2, User>() { 117 | @Override 118 | public User apply(@DataConsumer("user") User user, 119 | @DataConsumer("posts") List posts) { 120 | user.setPosts(posts); 121 | return user; 122 | } 123 | }); 124 | Assert.notNull(user,"User must not be NULL"); 125 | Assert.notNull(user.getPosts(),"User's posts must not be NULL"); 126 | ``` 127 | 128 | #### 方式二: 定义聚合层查询 129 | 130 | 组合`@DataProvider` \ `@DataConsumer` \ `@InvokeParameter` 实现汇聚功能 131 | 132 | ```java 133 | @Component 134 | public class UserAggregate { 135 | @DataProvider("userWithPosts") 136 | public User userWithPosts( 137 | @DataConsumer("user") User user, 138 | @DataConsumer("posts") List posts) { 139 | user.setPosts(posts); 140 | return user; 141 | } 142 | } 143 | ``` 144 | 145 | 指定要查询的data id, 查询参数, 返回值类型, 并调用`facade.get`方法即可 146 | 147 | ```java 148 | User user = DataFacade.get(/*data id*/ "userWithPosts", 149 | /*Invoke Parameters*/ 150 | Collections.singletonMap("userId",1L), 151 | User.class); 152 | Assert.notNull(user,"User must not be NULL"); 153 | Assert.notNull(user.getPosts(),"User's posts must not be NULL"); 154 | ``` 155 | 156 | **运行结果** 157 | 158 | 可以看到, user 和posts是由异步线程执行查询, 而userWithPosts是主调线程执行, 其中 159 | 160 | - 基础user信息查询耗费时间 1000ms 161 | - 用户博客列表查询耗费时间 1000ms 162 | - **总的查询时间 1005ms** 163 | 164 | ``` 165 | [aggregateTask-1] query id: user, costTime: 1000ms, resultType: User, invokeMethod: UserServiceImpl#get 166 | [aggregateTask-2] query id: posts, costTime: 1000ms, resultType: List, invokeMethod: PostServiceImpl#getPosts 167 | [ main] query id: userWithPosts, costTime: 1010ms, resultType: User, invokeMethod: UserAggregate#userWithPosts 168 | [ main] user.name:lvyahui8,user.posts.size:1 169 | ``` 170 | 171 | ## 贡献者 172 | 173 | - Feego(lvyauhi8@gmail.com) 174 | - Iris G 175 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Data Parallel Aggregation Library 2 | 3 | [![Build Status](https://travis-ci.org/lvyahui8/spring-boot-data-aggregator.svg?branch=develop)](https://travis-ci.org/lvyahui8/spring-boot-data-aggregator) 4 | [![Codecov](https://codecov.io/gh/lvyahui8/spring-boot-data-aggregator/branch/develop/graph/badge.svg)](https://codecov.io/gh/lvyahui8/spring-boot-data-aggregator/branch/develop) 5 | [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) 6 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.lvyahui8/spring-boot-data-aggregator-starter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.lvyahui8/spring-boot-data-aggregator-starter) 7 | [![GitHub release](https://img.shields.io/github/release/lvyahui8/spring-boot-data-aggregator.svg)](https://github.com/lvyahui8/spring-boot-data-aggregator/releases) 8 | 9 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/lvyahui8/spring-boot-data-aggregator.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lvyahui8/spring-boot-data-aggregator/alerts/) 10 | [![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/lvyahui8/spring-boot-data-aggregator.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lvyahui8/spring-boot-data-aggregator/context:java) 11 | 12 | Annotation-based parallel dependency injection (calling). Think of it as an upgraded version of the Spring `@Async` annotation. 13 | 14 | ![image-20200309230301680](README_EN.assets/image-20200309230301680.png) 15 | 16 | ## Features 17 | 18 | - **Getting dependencies asynchronously** 19 | 20 | All dependencies defined by `@DataConsumer` will be got asynchronously. The provider method is executed when all the dependencies of the provider method parameters are got . 21 | 22 | - **Unlimited nesting** 23 | 24 | Dependencies support deep nesting. The following example has only one layer of nesting relationship. 25 | 26 | - **Exception handling** 27 | 28 | Currently supports two processing methods: ignore or stop  29 | 30 | Ignore means that the provider method ignores the exception and returns a null value when it is executed. Stop means that once a provider method throws an exception, it will be thrown up step by step, and stop subsequent processing. 31 | 32 | Exception handling configuration item supports consumer level or global level, and consumer level is priority to global level 33 | 34 | - **Query Cache** 35 | 36 | In one query life cycle of calling the Facade's query method, the result called by `@DataProvider` method may be reused. As long as the method signature and the parameters are consistent, the default method is idempotent, and the cached query result will be used directly.** However, this Not an absolute. Considering the multi-threading feature, sometimes the cache is not used. 37 | 38 | - **Timeout Control** 39 | 40 | `@DataProvider` annotation supports configuration timeout, query timeout will throw interrupt exception (InterruptedException), follow exception handling logic. 41 | 42 | 43 | ## Getting Started 44 | 45 | ### 1. Configuration 46 | 47 | pom.xml 48 | 49 | ```xml 50 | 51 | io.github.lvyahui8 52 | spring-boot-data-aggregator-starter 53 | {$LATEST_VERSION} 54 | 55 | ``` 56 | 57 | application.properties 58 | 59 | ```properties 60 | # Specify the package to scan the annotations 61 | io.github.lvyahui8.spring.base-packages=io.github.lvyahui8.spring.example 62 | ``` 63 | 64 | ### 2. Annotation 65 | 66 | - `@DataProvider`: define the data provider 67 | 68 | - `@DataConsumer`: define the method parameter dependency type as return the value of other interfaces, the other interface is a `@DataProvider` 69 | 70 | - `@InvokeParameter`: define the method parameter dependency type as the user input value 71 | 72 | ### 3. Query 73 | 74 | Query the specified data via `DataFacade.get` static facade 75 | 76 | ## Example 77 | 78 | Developing a user summary data interface that includes the user's basic information and blog list. 79 | 80 | ### 1. Define an "atomic" service to provide user data 81 | 82 | Use `@DataProvider` to define the interface as a data provider. 83 | 84 | Use `@InvokeParameter` to specify the input parameters to pass. 85 | 86 | **Blog list service** 87 | 88 | require input parameter `userId`. 89 | 90 | ```java 91 | @Service 92 | public class PostServiceImpl implements PostService { 93 |     @DataProvider("posts") 94 |     @Override 95 |     public List getPosts(@InvokeParameter("userId") Long userId) { 96 | ``` 97 | 98 | **User basic information query service** 99 | 100 | require input parameter `userId`. 101 | 102 | ```java 103 | @Service 104 | public class UserServiceImpl implements UserService { 105 |     @DataProvider("user") 106 |     @Override 107 |     public User get(@InvokeParameter("userId") Long id) { 108 | ``` 109 | 110 | ### 2. Call the aggregation interface 111 | 112 | #### Method 1: Functional call 113 | 114 | 115 | ```java 116 | User user = DataFacade.get( 117 | Collections.singletonMap("userId", 1L), 118 | new Function2, User>() { 119 | @Override 120 | public User apply(@DataConsumer("user") User user, 121 | @DataConsumer("posts") List posts) { 122 | user.setPosts(posts); 123 | return user; 124 | } 125 | }); 126 | Assert.notNull(user,"User must not be NULL"); 127 | Assert.notNull(user.getPosts(),"User's posts must not be NULL"); 128 | ``` 129 | 130 | #### Method 2: Define and implement an aggregation layer 131 | 132 | Combine `@DataProvider` ( `@DataConsumer` \ `@InvokeParameter` ) to achieve aggregation function 133 | 134 | ```java 135 | @Component 136 | public class UserAggregate { 137 |     @DataProvider("userWithPosts") 138 |     public User userWithPosts( 139 |             @DataConsumer("user") User user, 140 |             @DataConsumer("posts") List posts) { 141 |         user.setPosts(posts); 142 |         return user; 143 |     } 144 | } 145 | ``` 146 | 147 | Specify queried data id, invoke parameters, and return type to invoke `facade.get` method 148 | 149 | ```java 150 | User user = DataFacade.get(/*data id*/ "userWithPosts", 151 |                             /*Invoke Parameters*/ 152 |                             Collections.singletonMap("userId",1L), 153 |                             User.class); 154 | Assert.notNull(user,"User must not be NULL"); 155 | Assert.notNull(user.getPosts(),"User's posts must not be NULL"); 156 | ``` 157 | 158 | **Invoke result** 159 | 160 | As you can see, `user` interface and `posts` interface are executed by the asynchronous thread while `userWithPosts` service is executed by the calling thread. 161 | 162 | - Basic user information query takes 1000ms 163 | - User blog list query takes 1000ms 164 | - **Total query takes 1005ms** 165 | 166 | ``` 167 | [aggregateTask-1] query id: user, costTime: 1000ms, resultType: User, invokeMethod: UserServiceImpl#get 168 | [aggregateTask-2] query id: posts, costTime: 1000ms, resultType: List, invokeMethod: PostServiceImpl#getPosts 169 | [ main] query id: userWithPosts, costTime: 1010ms, resultType: User, invokeMethod: UserAggregate#userWithPosts 170 | [ main] user.name:lvyahui8,user.posts.size:1 171 | ``` 172 | 173 | ## Contributors 174 | 175 | - Feego (lvyauhi8@gmail.com) 176 | 177 | - Iris G 178 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-example/src/test/java/io/github/lvyahui8/spring/example/DataBeanAggregateQueryFacadeTest.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.example; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.common.collect.Lists; 5 | import io.github.lvyahui8.spring.aggregate.func.Function2; 6 | import io.github.lvyahui8.spring.aggregate.func.Function3; 7 | import io.github.lvyahui8.spring.annotation.DataConsumer; 8 | import io.github.lvyahui8.spring.annotation.DynamicParameter; 9 | import io.github.lvyahui8.spring.autoconfigure.BeanAggregateProperties; 10 | import io.github.lvyahui8.spring.example.context.ExampleAppContext; 11 | import io.github.lvyahui8.spring.example.context.RequestContext; 12 | import io.github.lvyahui8.spring.example.facade.UserQueryFacade; 13 | import io.github.lvyahui8.spring.example.model.Category; 14 | import io.github.lvyahui8.spring.example.model.Post; 15 | import io.github.lvyahui8.spring.example.model.User; 16 | import io.github.lvyahui8.spring.facade.DataFacade; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.test.context.SpringBootTest; 22 | import org.springframework.test.context.junit4.SpringRunner; 23 | import org.springframework.util.Assert; 24 | 25 | import java.util.Collections; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | /** 30 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 31 | * @since 2019/6/16 0:07 32 | */ 33 | @RunWith(SpringRunner.class) 34 | @SpringBootTest 35 | @Slf4j 36 | public class DataBeanAggregateQueryFacadeTest { 37 | 38 | private static final int NUM = 100; 39 | 40 | @Autowired 41 | private BeanAggregateProperties beanAggregateProperties; 42 | 43 | @Autowired 44 | UserQueryFacade userQueryFacade; 45 | 46 | @Test 47 | public void testSample() throws Exception { 48 | { 49 | User user = DataFacade.get("userWithPosts", Collections.singletonMap("userId",1L), User.class); 50 | Assert.notNull(user,"user not null"); 51 | Assert.notNull(user.getPosts(),"user posts not null"); 52 | log.info("user.name:{},user.posts.size:{}", 53 | user.getUsername(),user.getPosts().size()); 54 | } 55 | 56 | log.info("------------------------------------------------------------------"); 57 | 58 | { 59 | User user = userQueryFacade.getUserFinal(1L); 60 | Assert.notNull(user.getFollowers(),"user followers not null"); 61 | } 62 | 63 | log.info("------------------------------------------------------------------"); 64 | 65 | { 66 | for (int i = 0; i < NUM; i ++) { 67 | String s = DataFacade.get("categoryTitle", Collections.singletonMap("categoryId", 1L), String.class); 68 | Assert.isTrue(org.apache.commons.lang3.StringUtils.isNotEmpty(s),"s not null"); 69 | } 70 | } 71 | } 72 | 73 | @Test 74 | public void testExceptionProcessing() throws Exception { 75 | boolean success = false; 76 | if(! beanAggregateProperties.isIgnoreException()) { 77 | try { 78 | DataFacade.get("userWithPosts", 79 | Collections.singletonMap("userId", 1L), User.class); 80 | } catch (Exception e) { 81 | log.info("default settings is SUSPEND, catch an exception: {}",e.getMessage(),e); 82 | } 83 | } else { 84 | User user = DataFacade.get("userWithPosts", 85 | Collections.singletonMap("userId", 1L), User.class); 86 | Assert.notNull(user,"user must be not null!"); 87 | } 88 | } 89 | 90 | @Test 91 | public void testGetByMultipleArgumentsFunction() throws Exception { 92 | Map singletonMap = Collections.singletonMap("userId", 1L); 93 | User user = DataFacade.get(singletonMap, new Function2, User>() { 94 | @Override 95 | public User apply(@DataConsumer("user") User user, @DataConsumer("posts") List posts) { 96 | user.setPosts(posts); 97 | return user; 98 | } 99 | },null); 100 | Assert.notNull(user,"user never not be null!"); 101 | try { 102 | user = DataFacade.get(singletonMap, (Function2, User>) (user1, posts) -> { 103 | user1.setPosts(posts); 104 | return user1; 105 | },null); 106 | } catch (Exception e) { 107 | log.info("don't support lambda!!! eMsg:{}",e.getMessage()); 108 | } 109 | } 110 | 111 | @Test 112 | public void testInheritableThreadLocals() throws Exception { 113 | try { 114 | User user = new User(); 115 | user.setUsername("bob"); 116 | user.setId(100000L); 117 | ExampleAppContext.setLoggedUser(user); 118 | DataFacade.get(null, new Function2,User>() { 119 | @Override 120 | public User apply(@DataConsumer("loggedUsername") String loggedUsername, 121 | @DataConsumer("loggedUserFollowers") List loggedUserFollowers) { 122 | Assert.notNull(loggedUsername, "loggedUsername must be not null"); 123 | Assert.notNull(loggedUserFollowers, "loggedUserFollowers must be not null"); 124 | Assert.notNull(ExampleAppContext.getUsername(),"ExampleAppContext.getUsername() must be not null"); 125 | log.info("everything is normal~"); 126 | return null; 127 | } 128 | }); 129 | } finally { 130 | ExampleAppContext.remove(); 131 | } 132 | } 133 | 134 | @Test 135 | public void testThreadLocal() throws Exception { 136 | try { 137 | RequestContext.setTenantId(10000L); 138 | Object result = DataFacade.get(null, 139 | new Function3, List, List, Object>() { 140 | @Override 141 | public Object apply( 142 | @DataConsumer("topMenu") List categories, 143 | @DataConsumer("postList") List posts, 144 | @DataConsumer("allFollowers") List users) { 145 | return new Object[] { 146 | categories,posts,users 147 | }; 148 | } 149 | }); 150 | } finally { 151 | RequestContext.removeTenantId(); 152 | } 153 | } 154 | 155 | @Test 156 | public void testDynamicParameter() throws Exception { 157 | ImmutableMap params = ImmutableMap.builder() 158 | .put("userA_Id", 1L) 159 | .put("userB_Id", 2L) 160 | .put("userC_Id", 3L).build(); 161 | Object specialUserCollection = DataFacade.get(params, 162 | new Function3() { 163 | @Override 164 | public Object apply( 165 | @DataConsumer(id = "user", 166 | dynamicParameters = {@DynamicParameter(targetKey = "userId" ,replacementKey = "userA_Id")}) User userA, 167 | @DataConsumer(id = "user", 168 | dynamicParameters = {@DynamicParameter(targetKey = "userId" ,replacementKey = "userB_Id")}) User userB, 169 | @DataConsumer(id = "user", 170 | dynamicParameters = {@DynamicParameter(targetKey = "userId" ,replacementKey = "userC_Id")}) User userC) { 171 | return Lists.newArrayList(userA,userB,userC); 172 | } 173 | }); 174 | System.out.println(specialUserCollection instanceof List); 175 | System.out.println(specialUserCollection); 176 | } 177 | 178 | @Test 179 | public void testParameterTypeException() throws Exception { 180 | try{ 181 | DataFacade.get(Collections.singletonMap("userId", "1"), 182 | new Function2, User>() { 183 | @Override 184 | public User apply(@DataConsumer("user") User user, 185 | @DataConsumer("posts") List posts) { 186 | return user; 187 | } 188 | }); 189 | throw new IllegalStateException("must throw IllegalArgumentException"); 190 | } catch (Exception e) { 191 | log.error("eMsg:",e); 192 | Assert.isTrue(e instanceof IllegalArgumentException,"e must be typeof IllegalArgumentException"); 193 | } 194 | } 195 | 196 | @Test 197 | public void testAnonymousProviderCache() throws Exception { 198 | Function2, String> userFunction = new Function2, String>() { 199 | @Override 200 | public String apply(@DataConsumer("rootCategory") Category category, 201 | @DataConsumer("topCategoryNames") List topCategoryNames) { 202 | return category.getName(); 203 | } 204 | }; 205 | Map map = Collections.singletonMap("userId", 1L); 206 | Exception exp = null; 207 | for (int i = 0; i < 1000; i ++) { 208 | try { 209 | String name = DataFacade.get(map,userFunction); 210 | } catch (Exception e) { 211 | exp = e; 212 | log.error("exp:" + e.getMessage()); 213 | } 214 | } 215 | Assert.isNull(exp,"exp must be null!"); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | 21 | io.github.lvyahui8 22 | spring-boot-data-aggregator 23 | 1.1.3 24 | 25 | spring-boot-data-aggregator-core 26 | spring-boot-data-aggregator-autoconfigure 27 | spring-boot-data-aggregator-example 28 | spring-boot-data-aggregator-starter 29 | 30 | pom 31 | 32 | spring-boot-data-aggregator 33 | 34 | 35 | 36 | UTF-8 37 | 1.8 38 | ${java.version} 39 | ${java.version} 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-parent 45 | 2.1.1.RELEASE 46 | 47 | 48 | 49 | 50 | oss 51 | https://oss.sonatype.org/content/repositories/snapshots 52 | 53 | 54 | oss 55 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 1.18.8 66 | 67 | 68 | 69 | junit 70 | junit 71 | 4.13.1 72 | 73 | 74 | 75 | io.github.lvyahui8 76 | spring-boot-data-aggregator-autoconfigure 77 | ${project.version} 78 | 79 | 80 | 81 | io.github.lvyahui8 82 | spring-boot-data-aggregator-core 83 | ${project.version} 84 | 85 | 86 | 87 | io.github.lvyahui8 88 | spring-boot-data-aggregator-example 89 | ${project.version} 90 | 91 | 92 | 93 | io.github.lvyahui8 94 | spring-boot-data-aggregator-starter 95 | ${project.version} 96 | 97 | 98 | 99 | 100 | org.apache.commons 101 | commons-lang3 102 | 3.9 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | release 116 | 117 | 118 | 119 | 120 | org.sonatype.plugins 121 | nexus-staging-maven-plugin 122 | 1.6.7 123 | true 124 | 125 | ossrh 126 | https://oss.sonatype.org/ 127 | true 128 | 129 | 130 | 131 | org.apache.maven.plugins 132 | maven-release-plugin 133 | 2.5 134 | 135 | true 136 | false 137 | release 138 | deploy 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-source-plugin 144 | 2.2.1 145 | 146 | 147 | attach-sources 148 | 149 | jar-no-fork 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-javadoc-plugin 157 | 2.9.1 158 | 159 | 160 | attach-javadocs 161 | 162 | jar 163 | 164 | 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-gpg-plugin 170 | 1.5 171 | 172 | 173 | sign-artifacts 174 | verify 175 | 176 | sign 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | Apache License 2.0 189 | https://www.apache.org/licenses/LICENSE-2.0 190 | 191 | 192 | 193 | 194 | https://github.com/lvyahui8/spring-boot-data-aggregator 195 | https://github.com/lvyahui8/spring-boot-data-aggregator.git 196 | https://github.com/lvyahui8 197 | 198 | 199 | 200 | 201 | Feego 202 | lvyahui8@gmail.com 203 | https://github.com/lvyahui8 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | org.codehaus.mojo 212 | license-maven-plugin 213 | 1.20 214 | 215 | 216 | aggregate-download-licenses 217 | 218 | aggregate-download-licenses 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | org.codehaus.mojo 228 | cobertura-maven-plugin 229 | 2.7 230 | 231 | 232 | html 233 | xml 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-autoconfigure/src/main/java/io/github/lvyahui8/spring/autoconfigure/BeanAggregateAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.autoconfigure; 2 | 3 | import io.github.lvyahui8.spring.aggregate.config.RuntimeSettings; 4 | import io.github.lvyahui8.spring.aggregate.facade.DataBeanAggregateQueryFacade; 5 | import io.github.lvyahui8.spring.aggregate.facade.impl.DataBeanAggregateQueryFacadeImpl; 6 | import io.github.lvyahui8.spring.aggregate.interceptor.AggregateQueryInterceptor; 7 | import io.github.lvyahui8.spring.aggregate.interceptor.AggregateQueryInterceptorChain; 8 | import io.github.lvyahui8.spring.aggregate.interceptor.impl.AggregateQueryInterceptorChainImpl; 9 | import io.github.lvyahui8.spring.aggregate.model.DataConsumeDefinition; 10 | import io.github.lvyahui8.spring.aggregate.model.DataProvideDefinition; 11 | import io.github.lvyahui8.spring.aggregate.repository.DataProviderRepository; 12 | import io.github.lvyahui8.spring.aggregate.repository.impl.DataProviderRepositoryImpl; 13 | import io.github.lvyahui8.spring.aggregate.service.DataBeanAggregateService; 14 | import io.github.lvyahui8.spring.aggregate.service.impl.DataBeanAggregateServiceImpl; 15 | import io.github.lvyahui8.spring.aggregate.service.impl.ProviderServiceImpl; 16 | import io.github.lvyahui8.spring.aggregate.util.DefinitionUtils; 17 | import io.github.lvyahui8.spring.annotation.DataProvider; 18 | import io.github.lvyahui8.spring.facade.FacadeInitializer; 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.apache.commons.lang3.StringUtils; 21 | import org.reflections.Reflections; 22 | import org.reflections.scanners.MethodAnnotationsScanner; 23 | import org.springframework.beans.BeansException; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.beans.factory.annotation.Qualifier; 26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 27 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 28 | import org.springframework.context.ApplicationContext; 29 | import org.springframework.context.ApplicationContextAware; 30 | import org.springframework.context.annotation.Bean; 31 | import org.springframework.context.annotation.Configuration; 32 | import org.springframework.core.Ordered; 33 | import org.springframework.core.annotation.AnnotationUtils; 34 | import org.springframework.core.annotation.Order; 35 | import org.springframework.scheduling.concurrent.CustomizableThreadFactory; 36 | import org.springframework.util.Assert; 37 | 38 | import java.lang.reflect.Method; 39 | import java.lang.reflect.Modifier; 40 | import java.util.*; 41 | import java.util.concurrent.ExecutorService; 42 | import java.util.concurrent.LinkedBlockingDeque; 43 | import java.util.concurrent.ThreadPoolExecutor; 44 | import java.util.concurrent.TimeUnit; 45 | import java.util.stream.Collectors; 46 | 47 | /** 48 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 49 | * @since 2019/5/31 23:28 50 | */ 51 | @Configuration 52 | @EnableConfigurationProperties(BeanAggregateProperties.class) 53 | @Slf4j 54 | public class BeanAggregateAutoConfiguration implements ApplicationContextAware { 55 | private ApplicationContext applicationContext; 56 | 57 | @Autowired 58 | private BeanAggregateProperties properties; 59 | 60 | @Override 61 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 62 | this.applicationContext = applicationContext; 63 | } 64 | 65 | @Bean 66 | @ConditionalOnMissingBean 67 | public DataBeanAggregateQueryFacade dataBeanAggregateQueryFacade( 68 | @Qualifier("dataProviderRepository") DataProviderRepository dataProviderRepository) { 69 | final ProviderServiceImpl providerService = new ProviderServiceImpl(); 70 | providerService.setRepository(dataProviderRepository); 71 | DataBeanAggregateQueryFacadeImpl facade = new DataBeanAggregateQueryFacadeImpl(dataBeanAggregateQueryService(dataProviderRepository),providerService); 72 | FacadeInitializer.initFacade(facade); 73 | return facade; 74 | } 75 | 76 | private void checkCycle(Map> graphAdjMap) { 77 | Map visitStatusMap = new HashMap<>(graphAdjMap.size() * 2); 78 | for (Map.Entry> item : graphAdjMap.entrySet()) { 79 | if (visitStatusMap.containsKey(item.getKey())) { 80 | continue; 81 | } 82 | dfs(graphAdjMap,visitStatusMap,item.getKey()); 83 | } 84 | } 85 | 86 | private void dfs(Map> graphAdjMap,Map visitStatusMap, String node) { 87 | if (visitStatusMap.containsKey(node)) { 88 | if(visitStatusMap.get(node) == 1) { 89 | List relatedNodes = new ArrayList<>(); 90 | for (Map.Entry item : visitStatusMap.entrySet()) { 91 | if (item.getValue() == 1) { 92 | relatedNodes.add(item.getKey()); 93 | } 94 | } 95 | throw new IllegalStateException("There are loops in the dependency graph. Related nodes:" + StringUtils.join(relatedNodes)); 96 | } 97 | return ; 98 | } 99 | visitStatusMap.put(node,1); 100 | log.info("visited:{}", node); 101 | for (String relateNode : graphAdjMap.get(node)) { 102 | dfs(graphAdjMap,visitStatusMap,relateNode); 103 | } 104 | visitStatusMap.put(node,2); 105 | } 106 | 107 | @Bean 108 | @ConditionalOnMissingBean 109 | public DataBeanAggregateService dataBeanAggregateQueryService ( 110 | @Qualifier("dataProviderRepository") DataProviderRepository dataProviderRepository) { 111 | if(properties.getBasePackages() != null) { 112 | Map> provideDependMap = new HashMap<>(64); 113 | for (String basePackage : properties.getBasePackages()) { 114 | Reflections reflections = new Reflections(basePackage, new MethodAnnotationsScanner()); 115 | Set providerMethods = reflections.getMethodsAnnotatedWith(DataProvider.class); 116 | for (Method method : providerMethods) { 117 | DataProvider beanProvider = AnnotationUtils.findAnnotation(method, DataProvider.class); 118 | @SuppressWarnings("ConstantConditions") 119 | String dataId = beanProvider.id(); 120 | Assert.isTrue(Modifier.isPublic(method.getModifiers()),"data provider method must be public"); 121 | Assert.isTrue(! StringUtils.isEmpty(dataId),"data id must be not null!"); 122 | DataProvideDefinition provider = DefinitionUtils.getProvideDefinition(method); 123 | 124 | provider.setId(dataId); 125 | provider.setIdempotent(beanProvider.idempotent()); 126 | provider.setTimeout(beanProvider.timeout() > 0 ? beanProvider.timeout() : properties.getDefaultTimeout()); 127 | Assert.isTrue(! dataProviderRepository.contains(dataId), "Data providers with the same name are not allowed. dataId: " + dataId); 128 | provideDependMap.put(dataId,provider.getDepends().stream().map(DataConsumeDefinition::getId).collect(Collectors.toSet())); 129 | dataProviderRepository.put(provider); 130 | } 131 | } 132 | checkCycle(provideDependMap); 133 | } 134 | 135 | DataBeanAggregateServiceImpl service = new DataBeanAggregateServiceImpl(); 136 | RuntimeSettings runtimeSettings = new RuntimeSettings(); 137 | runtimeSettings.setIgnoreException(properties.isIgnoreException()); 138 | runtimeSettings.setTimeout(properties.getDefaultTimeout()); 139 | service.setRepository(dataProviderRepository); 140 | service.setRuntimeSettings(runtimeSettings); 141 | service.setExecutorService(aggregateExecutorService()); 142 | service.setInterceptorChain(aggregateQueryInterceptorChain()); 143 | service.setTaskWrapperClazz(properties.getTaskWrapperClass()); 144 | service.setApplicationContext(applicationContext); 145 | return service; 146 | } 147 | 148 | /** 149 | * 允许用户自定义线程池 150 | * 151 | * @return 线程池服务 152 | */ 153 | @Bean(name = "aggregateExecutorService") 154 | @ConditionalOnMissingBean(name = "aggregateExecutorService",value=ExecutorService.class) 155 | public ExecutorService aggregateExecutorService() { 156 | return new ThreadPoolExecutor( 157 | properties.getThreadNumber(), 158 | properties.getThreadNumber() , 159 | 2L, TimeUnit.HOURS, 160 | new LinkedBlockingDeque<>(properties.getQueueSize()), 161 | new CustomizableThreadFactory(properties.getThreadPrefix())); 162 | } 163 | 164 | /** 165 | * 允许用户自定义provider存储 166 | * 167 | * @return provider数据仓库 168 | */ 169 | @Bean(name = "dataProviderRepository") 170 | @ConditionalOnMissingBean(DataProviderRepository.class) 171 | public DataProviderRepository dataProviderRepository() { 172 | return new DataProviderRepositoryImpl(); 173 | } 174 | 175 | 176 | @Bean(name = "aggregateQueryInterceptorChain") 177 | @ConditionalOnMissingBean(AggregateQueryInterceptorChain.class) 178 | public AggregateQueryInterceptorChain aggregateQueryInterceptorChain() { 179 | Map interceptorMap = applicationContext.getBeansOfType(AggregateQueryInterceptor.class); 180 | AggregateQueryInterceptorChainImpl interceptorChain = new AggregateQueryInterceptorChainImpl(); 181 | if(! interceptorMap.isEmpty()) { 182 | List interceptors = new ArrayList<>(interceptorMap.values()); 183 | interceptors.sort((o1, o2) -> { 184 | Order order1 = o1.getClass().getAnnotation(Order.class); 185 | Order order2 = o2.getClass().getAnnotation(Order.class); 186 | int oi1 = order1 == null ? Ordered.LOWEST_PRECEDENCE : order1.value(); 187 | int oi2 = order2 == null ? Ordered.LOWEST_PRECEDENCE : order2.value(); 188 | return oi1 - oi2; 189 | }); 190 | for (AggregateQueryInterceptor interceptor : interceptors) { 191 | interceptorChain.addInterceptor(interceptor); 192 | } 193 | } 194 | return interceptorChain; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /spring-boot-data-aggregator-core/src/main/java/io/github/lvyahui8/spring/aggregate/service/impl/DataBeanAggregateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.lvyahui8.spring.aggregate.service.impl; 2 | 3 | import io.github.lvyahui8.spring.aggregate.config.RuntimeSettings; 4 | import io.github.lvyahui8.spring.aggregate.consts.AggregationConstant; 5 | import io.github.lvyahui8.spring.aggregate.context.AggregationContext; 6 | import io.github.lvyahui8.spring.aggregate.func.MultipleArgumentsFunction; 7 | import io.github.lvyahui8.spring.aggregate.interceptor.AggregateQueryInterceptorChain; 8 | import io.github.lvyahui8.spring.aggregate.model.*; 9 | import io.github.lvyahui8.spring.aggregate.repository.DataProviderRepository; 10 | import io.github.lvyahui8.spring.aggregate.service.AbstractAsyncQueryTask; 11 | import io.github.lvyahui8.spring.aggregate.service.AsyncQueryTaskWrapper; 12 | import io.github.lvyahui8.spring.aggregate.service.DataBeanAggregateService; 13 | import io.github.lvyahui8.spring.aggregate.util.DefinitionUtils; 14 | import lombok.Setter; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.context.ApplicationContext; 17 | 18 | import java.lang.reflect.InvocationTargetException; 19 | import java.lang.reflect.Method; 20 | import java.lang.reflect.Modifier; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.concurrent.*; 26 | 27 | /** 28 | * @author lvyahui (lvyahui8@gmail.com,lvyahui8@126.com) 29 | * @since 2019/6/2 21:50 30 | */ 31 | @Slf4j 32 | public class DataBeanAggregateServiceImpl implements DataBeanAggregateService { 33 | 34 | @Setter 35 | private DataProviderRepository repository; 36 | 37 | @Setter 38 | private ApplicationContext applicationContext; 39 | 40 | @Setter 41 | private ExecutorService executorService; 42 | 43 | @Setter 44 | private RuntimeSettings runtimeSettings; 45 | 46 | @Setter 47 | private AggregateQueryInterceptorChain interceptorChain; 48 | 49 | @Setter 50 | private Class taskWrapperClazz; 51 | 52 | private AggregationContext initQueryContext(DataProvideDefinition rootProvider, Map queryCache) { 53 | AggregationContext aggregationContext = new AggregationContext(); 54 | aggregationContext.setRootThread(Thread.currentThread()); 55 | aggregationContext.setRootProvideDefinition(rootProvider); 56 | aggregationContext.setCacheMap(queryCache); 57 | return aggregationContext; 58 | } 59 | 60 | 61 | @Override 62 | public T get(String id, Map invokeParams, Class resultType) 63 | throws InterruptedException, InvocationTargetException, IllegalAccessException { 64 | return get(repository.get(id),invokeParams,resultType); 65 | } 66 | 67 | @Override 68 | public T get(DataProvideDefinition provider, Map invokeParams, Class resultType) 69 | throws InterruptedException, InvocationTargetException, IllegalAccessException { 70 | Map queryCache = new ConcurrentHashMap<>(AggregationConstant.DEFAULT_INITIAL_CAPACITY); 71 | AggregationContext aggregationContext = initQueryContext(provider, queryCache); 72 | aggregationContext.setInvokeParams(invokeParams); 73 | interceptorChain.applyQuerySubmitted(aggregationContext); 74 | try { 75 | return innerGet(provider,invokeParams,resultType,aggregationContext,null); 76 | } finally { 77 | interceptorChain.applyQueryFinished(aggregationContext); 78 | } 79 | } 80 | 81 | private T innerGet(DataProvideDefinition provider, Map invokeParams, Class resultType, 82 | AggregationContext context, DataConsumeDefinition causedConsumer) 83 | throws InterruptedException, InvocationTargetException, IllegalAccessException{ 84 | Map dependObjectMap; 85 | try { 86 | interceptorChain.applyQueryBefore(context,provider); 87 | if(provider.getDepends() != null && ! provider.getDepends().isEmpty()) { 88 | List consumeDefinitions = provider.getDepends(); 89 | Long timeout = provider.getTimeout() != null ? provider.getTimeout() : runtimeSettings.getTimeout(); 90 | dependObjectMap = getDependObjectMap(invokeParams, consumeDefinitions, timeout, context); 91 | } else { 92 | dependObjectMap = Collections.emptyMap(); 93 | } 94 | /* 拼凑dependObjects和invokeParams */ 95 | Object [] args = new Object[provider.getMethod().getParameterCount()]; 96 | 97 | for (int i = 0 ; i < provider.getMethodArgs().size(); i ++) { 98 | MethodArg methodArg = provider.getMethodArgs().get(i); 99 | if (methodArg.getDependType().equals(DependType.OTHER_MODEL)) { 100 | args[i] = dependObjectMap.get(methodArg.getAnnotationKey() + "_" + methodArg.getParameter().getName()); 101 | } else { 102 | String paramKey ; 103 | if(causedConsumer != null && causedConsumer.getDynamicParameterKeyMap() != null 104 | && causedConsumer.getDynamicParameterKeyMap().containsKey(methodArg.getAnnotationKey())) { 105 | paramKey = causedConsumer.getDynamicParameterKeyMap().get(methodArg.getAnnotationKey()); 106 | } else { 107 | paramKey = methodArg.getAnnotationKey(); 108 | } 109 | args[i] = invokeParams.get(paramKey); 110 | } 111 | if (args[i] != null && ! methodArg.getParameter().getType().isAssignableFrom(args[i].getClass())) { 112 | throw new IllegalArgumentException("param type not match, param:" 113 | + methodArg.getParameter().getName()); 114 | } 115 | } 116 | Map queryCache = context.getCacheMap(); 117 | /* 如果调用方法是幂等的, 那么当方法签名和方法参数完全一致时, 可以直接使用缓存结果 */ 118 | InvokeSignature invokeSignature = null; 119 | Object resultModel = null; 120 | if(provider.isIdempotent()) { 121 | invokeSignature = new InvokeSignature(provider.getMethod(),args); 122 | if (queryCache.containsKey(invokeSignature)) { 123 | resultModel = queryCache.get(invokeSignature); 124 | } 125 | } 126 | 127 | if (resultModel == null ){ 128 | resultModel = provider.getMethod() 129 | .invoke(provider.getTarget() == null 130 | ? applicationContext.getBean(provider.getMethod().getDeclaringClass()) 131 | : provider.getTarget(), args); 132 | if(provider.isIdempotent()) { 133 | /* Map 中可能不能放空value */ 134 | queryCache.put(invokeSignature,resultModel != null ? resultModel : AggregationConstant.EMPTY_MODEL); 135 | } 136 | } 137 | Object result = resultModel != AggregationConstant.EMPTY_MODEL ? resultModel : null; 138 | return resultType.cast(interceptorChain.applyQueryAfter(context,provider,result)); 139 | } catch (Exception e) { 140 | interceptorChain.applyExceptionHandle(context,provider,e); 141 | throw e; 142 | } 143 | } 144 | 145 | private Map getDependObjectMap(Map invokeParams, 146 | List consumeDefinitions, 147 | Long timeout, 148 | AggregationContext context) 149 | throws InterruptedException, InvocationTargetException, IllegalAccessException { 150 | Map dependObjectMap; 151 | CountDownLatch stopDownLatch = new CountDownLatch(consumeDefinitions.size()); 152 | Map> futureMap = new HashMap<>(consumeDefinitions.size()); 153 | dependObjectMap = new HashMap<>(consumeDefinitions.size()); 154 | Map consumeDefinitionMap = new HashMap<>(consumeDefinitions.size()); 155 | for (DataConsumeDefinition depend : consumeDefinitions) { 156 | consumeDefinitionMap.put(depend.getId(),depend); 157 | AsyncQueryTaskWrapper taskWrapper; 158 | try { 159 | taskWrapper = taskWrapperClazz.newInstance(); 160 | } catch (InstantiationException e) { 161 | throw new RuntimeException("task wrapper instance create failed.",e); 162 | } 163 | taskWrapper.beforeSubmit(); 164 | Future future = executorService.submit(new AbstractAsyncQueryTask(Thread.currentThread(),taskWrapper) { 165 | @Override 166 | public Object execute() throws Exception { 167 | try { 168 | Object o = innerGet(repository.get(depend.getId()),invokeParams, depend.getClazz(),context,depend); 169 | return depend.getClazz().cast(o); 170 | } finally { 171 | stopDownLatch.countDown(); 172 | } 173 | } 174 | }); 175 | futureMap.put(depend.getId() + "_" + depend.getOriginalParameterName(),future); 176 | } 177 | stopDownLatch.await(timeout, TimeUnit.MILLISECONDS); 178 | if(! futureMap.isEmpty()){ 179 | for (Map.Entry> item : futureMap.entrySet()) { 180 | Future future = item.getValue(); 181 | Object value = null; 182 | DataConsumeDefinition consumeDefinition = consumeDefinitionMap.get(item.getKey().substring(0,item.getKey().indexOf('_'))); 183 | try { 184 | value = future.get(); 185 | } catch (ExecutionException e) { 186 | if (consumeDefinition.getIgnoreException() != null ? ! consumeDefinition.getIgnoreException() 187 | : ! runtimeSettings.isIgnoreException()) { 188 | throwException(e); 189 | } 190 | } 191 | dependObjectMap.put(item.getKey(),value); 192 | } 193 | } 194 | return dependObjectMap; 195 | } 196 | 197 | private void throwException(ExecutionException e) throws InterruptedException, 198 | InvocationTargetException, IllegalAccessException { 199 | Throwable cause = e.getCause(); 200 | if (cause instanceof InterruptedException) { 201 | throw (InterruptedException) cause; 202 | } else if (cause instanceof InvocationTargetException){ 203 | throw (InvocationTargetException) cause; 204 | } else if (cause instanceof IllegalAccessException) { 205 | throw (IllegalAccessException) cause; 206 | } else { 207 | throw (RuntimeException) cause; 208 | } 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------