Browse Source

Young -> init project

youngknowsum 2 years ago
commit
e0c06b7b0a
27 changed files with 1191 additions and 0 deletions
  1. 31 0
      .gitignore
  2. 43 0
      pom.xml
  3. 33 0
      spring-boot-dynamic-methods-starter/pom.xml
  4. 24 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/DynamicClass.java
  5. 36 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/DynamicClient.java
  6. 22 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/DynamicMethod.java
  7. 58 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/EnableDynamicClients.java
  8. 17 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DefaultTargeter.java
  9. 70 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicClientFactoryBean.java
  10. 160 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicClientRegistrar.java
  11. 31 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicMethodsAutoConfiguration.java
  12. 28 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicMethodsEndpointRegistrar.java
  13. 23 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/Targeter.java
  14. 15 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/endpoint/DynamicMethodWrapper.java
  15. 47 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/endpoint/MethodEndpoint.java
  16. 167 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/processor/DynamicMethodAnnotationBeanPostProcessor.java
  17. 17 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/DynamicClientCreator.java
  18. 38 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/InvocationHandlerFactory.java
  19. 91 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/ReflectiveDynamicClientCreator.java
  20. 74 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/Target.java
  21. 21 0
      spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/utils/CheckUtil.java
  22. 34 0
      support-demo/pom.xml
  23. 16 0
      support-demo/src/main/java/cn/nosum/Application.java
  24. 12 0
      support-demo/src/main/java/cn/nosum/client/DynamicClientTest.java
  25. 29 0
      support-demo/src/main/java/cn/nosum/controller/DynamicController.java
  26. 13 0
      support-demo/src/main/java/cn/nosum/service/DynamicService.java
  27. 41 0
      support-demo/src/main/java/cn/nosum/service/impl/DynamicServiceImpl.java

+ 31 - 0
.gitignore

@@ -0,0 +1,31 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/

+ 43 - 0
pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.4.1</version>
+        <relativePath/>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.nosum</groupId>
+    <artifactId>nosum-support</artifactId>
+    <packaging>pom</packaging>
+    <version>1.0-SNAPSHOT</version>
+    <description>young spring support</description>
+
+    <modules>
+        <module>support-demo</module>
+        <module>spring-boot-dynamic-methods-starter</module>
+    </modules>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>1.18.4</version>
+                <scope>provided</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+</project>

+ 33 - 0
spring-boot-dynamic-methods-starter/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>nosum-support</artifactId>
+        <groupId>cn.nosum</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spring-boot-dynamic-methods-starter</artifactId>
+    <name>dynamic-methods</name>
+    <description>dynamic-methods</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 24 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/DynamicClass.java

@@ -0,0 +1,24 @@
+package cn.nosum.support.annotation;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * DynamicClass Annotation.
+ *
+ * @author Young
+ */
+@Component
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DynamicClass {
+
+    @AliasFor(
+            annotation = Component.class
+    )
+    String value() default "";
+
+}

+ 36 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/DynamicClient.java

@@ -0,0 +1,36 @@
+package cn.nosum.support.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * DynamicClient Annotation.
+ *
+ * @author Young
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DynamicClient {
+
+    /**
+     * 名称
+     */
+    String name() default "";
+
+    /**
+     * 引用的名称
+     */
+    String refName() default "";
+
+    /**
+     * 引用的类
+     */
+    Class<?> refType() default void.class;
+
+
+    String contextId() default "";
+
+    String qualifier() default "";
+
+    boolean primary() default true;
+}

+ 22 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/DynamicMethod.java

@@ -0,0 +1,22 @@
+package cn.nosum.support.annotation;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * DynamicMethod Annotation.
+ *
+ * @author Young
+ */
+@Component
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DynamicMethod {
+
+    /**
+     * 名称.
+     */
+    String name() default "";
+}

+ 58 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/annotation/EnableDynamicClients.java

@@ -0,0 +1,58 @@
+package cn.nosum.support.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import cn.nosum.support.config.DynamicClientRegistrar;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Scans for interfaces that declare they are feign clients (via
+ * {@link DynamicClient} <code>@FeignClient</code>).
+ * Configures component scanning directives for use with
+ * {@link org.springframework.context.annotation.Configuration}
+ * <code>@Configuration</code> classes.
+ *
+ * @author Spencer Gibb
+ * @author Dave Syer
+ * @since 1.0
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Import(DynamicClientRegistrar.class)
+public @interface EnableDynamicClients {
+
+	/**
+	 * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
+	 * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
+	 * {@code @ComponentScan(basePackages="org.my.pkg")}.
+	 * @return the array of 'basePackages'.
+	 */
+	String[] value() default {};
+
+	/**
+	 * Base packages to scan for annotated components.
+	 * <p>
+	 * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
+	 * <p>
+	 * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
+	 * package names.
+	 * @return the array of 'basePackages'.
+	 */
+	String[] basePackages() default {};
+
+	/**
+	 * Type-safe alternative to {@link #basePackages()} for specifying the packages to
+	 * scan for annotated components. The package of each class specified will be scanned.
+	 * <p>
+	 * Consider creating a special no-op marker class or interface in each package that
+	 * serves no purpose other than being referenced by this attribute.
+	 * @return the array of 'basePackageClasses'.
+	 */
+	Class<?>[] basePackageClasses() default {};
+
+}

+ 17 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DefaultTargeter.java

@@ -0,0 +1,17 @@
+package cn.nosum.support.config;
+
+import cn.nosum.support.proxy.InvocationHandlerFactory;
+import cn.nosum.support.proxy.ReflectiveDynamicClientCreator;
+import cn.nosum.support.proxy.Target;
+
+/**
+ * The proxy class creates the wrapper.
+ *
+ * @author Young
+ */
+class DefaultTargeter implements Targeter {
+    @Override
+    public <T> T target(DynamicClientFactoryBean factory, InvocationHandlerFactory handlerFactory, Target<T> target) {
+        return new ReflectiveDynamicClientCreator(handlerFactory).newInstance(target);
+    }
+}

+ 70 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicClientFactoryBean.java

@@ -0,0 +1,70 @@
+package cn.nosum.support.config;
+
+import cn.nosum.support.proxy.InvocationHandlerFactory;
+import cn.nosum.support.proxy.Target;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.util.StringUtils;
+
+/**
+ * Dynamic client factory bean.
+ *
+ * @author Young
+ */
+public class DynamicClientFactoryBean implements FactoryBean<Object>, ApplicationContextAware {
+
+    private ApplicationContext applicationContext;
+
+    private String name;
+    private Class<?> type;
+    private String refName;
+    private Class<?> refType;
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setType(Class<?> type) {
+        this.type = type;
+    }
+
+    public void setRefName(String refName) {
+        this.refName = refName;
+    }
+
+    public void setRefType(Class<?> refType) {
+        this.refType = refType;
+    }
+
+    @Override
+    public Object getObject() throws Exception {
+        return getTarget();
+    }
+
+    <T> T getTarget() {
+        Targeter targeter = applicationContext.getBean(Targeter.class);
+        InvocationHandlerFactory handlerFactory = applicationContext.getBean(InvocationHandlerFactory.class);
+        if (!StringUtils.hasText(this.refName)){
+            String[] beanNamesForType = applicationContext.getBeanNamesForType(this.refType);
+            this.refName = beanNamesForType[0];
+        }
+        return (T) targeter.target(this, handlerFactory,new Target.DefaultTarget(this.name, this.type, this.refName, this.refType) );
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return this.type;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+    }
+}

+ 160 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicClientRegistrar.java

@@ -0,0 +1,160 @@
+package cn.nosum.support.config;
+
+import cn.nosum.support.annotation.DynamicClient;
+import cn.nosum.support.annotation.EnableDynamicClients;
+import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Dynamic class registration.
+ *
+ * @author Young
+ */
+public class DynamicClientRegistrar implements ImportBeanDefinitionRegistrar , ResourceLoaderAware, EnvironmentAware {
+
+    private ResourceLoader resourceLoader;
+
+    private Environment environment;
+
+    @Override
+    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
+        this.registerClients(metadata,registry);
+    }
+
+    public void registerClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
+
+        ClassPathScanningCandidateComponentProvider scanner = getScanner();
+        scanner.setResourceLoader(this.resourceLoader);
+
+        Set<String> basePackages = getBasePackages(metadata);;
+        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(DynamicClient.class);
+        scanner.addIncludeFilter(annotationTypeFilter);
+
+        for (String basePackage : basePackages) {
+            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
+            for (BeanDefinition candidateComponent : candidateComponents) {
+                if (candidateComponent instanceof AnnotatedBeanDefinition) {
+                    // verify annotated class is an interface
+                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
+                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
+                    Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
+
+                    Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(DynamicClient.class.getCanonicalName());
+                    assert attributes != null;
+                    registerFeignClient(registry, annotationMetadata, attributes);
+                }
+            }
+        }
+
+    }
+
+    private void registerFeignClient(BeanDefinitionRegistry registry,
+                                     AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
+        String name = (String) attributes.get("name");
+        if (!StringUtils.hasText(name)){
+            name = annotationMetadata.getClassName();
+        }
+        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(DynamicClientFactoryBean.class);
+        definition.addPropertyValue("name", name);
+        definition.addPropertyValue("type", annotationMetadata.getClassName());
+        definition.addPropertyValue("refName",attributes.get("refName"));
+        definition.addPropertyValue("refType",attributes.get("refType"));
+        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
+
+        String alias = name + "DynamicClient";
+        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
+
+        // has a default, won't be
+        boolean primary = (Boolean) attributes.get("primary");
+        // null
+        beanDefinition.setPrimary(primary);
+        String qualifier = getQualifier(attributes);
+        if (StringUtils.hasText(qualifier)) {
+            alias = qualifier;
+        }
+        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, name, new String[] { alias });
+        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
+    }
+
+    protected ClassPathScanningCandidateComponentProvider getScanner() {
+        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
+            @Override
+            protected boolean isCandidateComponent(
+                    AnnotatedBeanDefinition beanDefinition) {
+                boolean isCandidate = false;
+                if (beanDefinition.getMetadata().isIndependent()) {
+                    if (!beanDefinition.getMetadata().isAnnotation()) {
+                        isCandidate = true;
+                    }
+                }
+                return isCandidate;
+            }
+        };
+    }
+
+    private String getQualifier(Map<String, Object> client) {
+        if (client == null) {
+            return null;
+        }
+        String qualifier = (String) client.get("qualifier");
+        if (StringUtils.hasText(qualifier)) {
+            return qualifier;
+        }
+        return null;
+    }
+
+    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
+        Map<String, Object> attributes = importingClassMetadata
+                .getAnnotationAttributes(EnableDynamicClients.class.getCanonicalName());
+
+        Set<String> basePackages = new HashSet<>();
+        for (String pkg : (String[]) attributes.get("value")) {
+            if (StringUtils.hasText(pkg)) {
+                basePackages.add(pkg);
+            }
+        }
+        for (String pkg : (String[]) attributes.get("basePackages")) {
+            if (StringUtils.hasText(pkg)) {
+                basePackages.add(pkg);
+            }
+        }
+        for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
+            basePackages.add(ClassUtils.getPackageName(clazz));
+        }
+
+        if (basePackages.isEmpty()) {
+            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
+        }
+        return basePackages;
+    }
+
+    @Override
+    public void setResourceLoader(ResourceLoader resourceLoader) {
+        this.resourceLoader = resourceLoader;
+    }
+
+    @Override
+    public void setEnvironment(Environment environment) {
+        this.environment = environment;
+    }
+}

+ 31 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicMethodsAutoConfiguration.java

@@ -0,0 +1,31 @@
+package cn.nosum.support.config;
+
+import cn.nosum.support.processor.DynamicMethodAnnotationBeanPostProcessor;
+import cn.nosum.support.proxy.InvocationHandlerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Dynamic methods automatically inject configuration classes.
+ *
+ * @author Young
+ */
+@Configuration
+public class DynamicMethodsAutoConfiguration {
+
+
+    @Bean
+    public Targeter feignTargeter() {
+        return new DefaultTargeter();
+    }
+
+    @Bean
+    public InvocationHandlerFactory invocationHandlerFactory(){
+        return new InvocationHandlerFactory.Default();
+    }
+
+    @Bean
+    public DynamicMethodAnnotationBeanPostProcessor dynamicMethodAnnotationBeanPostProcessor(){
+        return new DynamicMethodAnnotationBeanPostProcessor();
+    }
+}

+ 28 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/DynamicMethodsEndpointRegistrar.java

@@ -0,0 +1,28 @@
+package cn.nosum.support.config;
+
+import cn.nosum.support.endpoint.DynamicMethodWrapper;
+import cn.nosum.support.endpoint.MethodEndpoint;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Dynamic method registry.
+ *
+ * @author Young
+ */
+public class DynamicMethodsEndpointRegistrar {
+
+    private final static Map<String, Map<String, MethodEndpoint>> METHOD_ENDPOINT_MAP = new ConcurrentHashMap<>(64);
+
+    public static void registerEndpoint(MethodEndpoint endpoint, DynamicMethodWrapper dynamicMethod) {
+        if (null == METHOD_ENDPOINT_MAP.get(endpoint.getBeanName())) {
+            METHOD_ENDPOINT_MAP.put(endpoint.getBeanName(),new ConcurrentHashMap<>(8));
+        }
+        METHOD_ENDPOINT_MAP.get(endpoint.getBeanName()).put(dynamicMethod.getName(), endpoint);
+    }
+
+    public static Map<String, MethodEndpoint> getEndpoint(String beanName) {
+        return METHOD_ENDPOINT_MAP.get(beanName);
+    }
+}

+ 23 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/config/Targeter.java

@@ -0,0 +1,23 @@
+package cn.nosum.support.config;
+
+import cn.nosum.support.proxy.InvocationHandlerFactory;
+import cn.nosum.support.proxy.Target;
+
+/**
+ * The proxy class creates the wrapper.
+ *
+ * @author Young
+ */
+interface Targeter {
+
+    /**
+     * Create proxy class
+     *
+     * @param factory        bean factory
+     * @param handlerFactory proxy factory
+     * @param target         target object information
+     * @return proxy instance
+     */
+    <T> T target(DynamicClientFactoryBean factory, InvocationHandlerFactory handlerFactory, Target<T> target);
+
+}

+ 15 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/endpoint/DynamicMethodWrapper.java

@@ -0,0 +1,15 @@
+package cn.nosum.support.endpoint;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Dynamic method annotation packaging  {@link cn.nosum.support.annotation.DynamicMethod}.
+ *
+ * @author Young
+ */
+@Data
+@Builder
+public class DynamicMethodWrapper {
+    private String name;
+}

+ 47 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/endpoint/MethodEndpoint.java

@@ -0,0 +1,47 @@
+package cn.nosum.support.endpoint;
+
+import lombok.Data;
+import lombok.SneakyThrows;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.lang.reflect.Method;
+
+/**
+ * Dynamic method section wrapper class.
+ *
+ * @author Young
+ */
+@Data
+public class MethodEndpoint {
+
+    private final Log logger = LogFactory.getLog(getClass());
+
+    private Object bean;
+
+    private String beanName;
+
+    private Method method;
+
+    public boolean isMatcher(Object... args) {
+        if (null == args || args.length == 0) {
+            return method.getParameterTypes().length == 0;
+        }
+        Class<?>[] parameterTypes = method.getParameterTypes();
+
+        if (args.length == parameterTypes.length) {
+            for (int i = 0; i < parameterTypes.length; i++) {
+                if (!(parameterTypes[i] == args[i].getClass())) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @SneakyThrows
+    public Object invoke(Object... args) {
+        return method.invoke(bean, args);
+    }
+}

+ 167 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/processor/DynamicMethodAnnotationBeanPostProcessor.java

@@ -0,0 +1,167 @@
+package cn.nosum.support.processor;
+
+import cn.nosum.support.annotation.DynamicClass;
+import cn.nosum.support.annotation.DynamicMethod;
+import cn.nosum.support.config.DynamicMethodsEndpointRegistrar;
+import cn.nosum.support.endpoint.DynamicMethodWrapper;
+import cn.nosum.support.endpoint.MethodEndpoint;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.aop.framework.Advised;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.core.MethodIntrospector;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * Bean post-processor that registers methods annotated with {@link DynamicMethod}.
+ *
+ * @author Young
+ */
+public class DynamicMethodAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered {
+
+    private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
+
+    private final Log logger = LogFactory.getLog(getClass());
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
+    }
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+        if (this.nonAnnotatedClasses.contains(bean.getClass())) {
+            return bean;
+        }
+        Class<?> targetClass = AopUtils.getTargetClass(bean);
+        Collection<DynamicClass> classLevelDynamicMethods = findConsumerClass(targetClass);
+        if (classLevelDynamicMethods.isEmpty()){
+            return bean;
+        }
+
+        Map<Method, Set<DynamicMethod>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
+                (MethodIntrospector.MetadataLookup<Set<DynamicMethod>>) method -> {
+                    Set<DynamicMethod> listenerMethods = findConsumerClass(method);
+                    return (!listenerMethods.isEmpty() ? listenerMethods : null);
+                });
+        Set<Method> allMethods = Arrays.stream(bean.getClass().getDeclaredMethods()).filter(method-> !annotatedMethods.containsKey(method)).collect(Collectors.toSet());
+        if (allMethods.isEmpty() && annotatedMethods.isEmpty()) {
+            this.nonAnnotatedClasses.add(bean.getClass());
+            if (this.logger.isTraceEnabled()) {
+                this.logger.trace("No @DynamicMethod annotations found on bean type: " + bean.getClass());
+            }
+        } else {
+            // Non-empty set of methods
+            for (Map.Entry<Method, Set<DynamicMethod>> entry : annotatedMethods.entrySet()) {
+                Method method = entry.getKey();
+                for (DynamicMethod dynamicMethod : entry.getValue()) {
+                    processDynamicMethod(DynamicMethodWrapper.builder().name(dynamicMethod.name()).build(), method, bean, beanName);
+                }
+            }
+            // empty set of methods
+            for (Method method : allMethods) {
+                processDynamicMethod(DynamicMethodWrapper.builder().name(method.getName()).build(), method, bean, beanName);
+            }
+
+            if (this.logger.isDebugEnabled()) {
+                this.logger.debug(annotatedMethods.size() + " @DynamicMethod methods processed on bean '"
+                        + beanName + "': " + annotatedMethods);
+            }
+        }
+        return bean;
+    }
+
+    protected void processDynamicMethod(DynamicMethodWrapper dynamicMethod, Method method, Object bean, String beanName) {
+        Method methodToUse = checkProxy(method, bean);
+        MethodEndpoint endpoint = new MethodEndpoint();
+        endpoint.setBean(bean);
+        endpoint.setMethod(method);
+        processMethod(endpoint, dynamicMethod, bean, methodToUse, beanName);
+    }
+
+    protected void processMethod(MethodEndpoint endpoint,
+                                 DynamicMethodWrapper dynamicMethod, Object bean, Object adminTarget, String beanName) {
+        endpoint.setBeanName(beanName);
+        DynamicMethodsEndpointRegistrar.registerEndpoint(endpoint,dynamicMethod);
+    }
+
+    /**
+     * 检查代理对象
+     *
+     * @param methodArg 方法
+     * @param bean      Bean 实例
+     * @return 如果目标类已经被代理,则检查注解是否存在于类本身
+     */
+    private Method checkProxy(Method methodArg, Object bean) {
+        Method method = methodArg;
+        if (AopUtils.isJdkDynamicProxy(bean)) {
+            try {
+                method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
+                Class<?>[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces();
+                for (Class<?> iface : proxiedInterfaces) {
+                    try {
+                        method = iface.getMethod(method.getName(), method.getParameterTypes());
+                        break;
+                    } catch (NoSuchMethodException ignored) {
+                    }
+                }
+            } catch (SecurityException ex) {
+                ReflectionUtils.handleReflectionException(ex);
+            } catch (NoSuchMethodException ex) {
+                throw new IllegalStateException(String.format(
+                        "@DynamicMethod method '%s' found on bean target class '%s', " +
+                                "but not found in any interface(s) for bean JDK proxy. Either " +
+                                "pull the method up to an interface or switch to subclass (CGLIB) " +
+                                "proxies by setting proxy-target-class/proxyTargetClass " +
+                                "attribute to 'true'", method.getName(),
+                        method.getDeclaringClass().getSimpleName()), ex);
+            }
+        }
+        return method;
+    }
+
+    /**
+     * 获取类上的 {@link DynamicMethod} 注解
+     *
+     * @param clazz 类
+     * @return {@link DynamicMethod} 注解集合
+     */
+    private Collection<DynamicClass> findConsumerClass(Class<?> clazz) {
+        Set<DynamicClass> consumerMethods = new HashSet<>();
+        DynamicClass ann = AnnotationUtils.findAnnotation(clazz, DynamicClass.class);
+        if (ann != null) {
+            consumerMethods.add(ann);
+        }
+        return consumerMethods;
+    }
+
+    /**
+     * 获取方法上的 {@link DynamicMethod} 注解
+     *
+     * @param method 方法
+     * @return {@link DynamicMethod} 注解集合
+     */
+    private Set<DynamicMethod> findConsumerClass(Method method) {
+        Set<DynamicMethod> dynamicMethods = new HashSet<>();
+        DynamicMethod ann = AnnotatedElementUtils.findMergedAnnotation(method, DynamicMethod.class);
+        if (ann != null) {
+            dynamicMethods.add(ann);
+        }
+        return dynamicMethods;
+    }
+
+    @Override
+    public int getOrder() {
+        return LOWEST_PRECEDENCE;
+    }
+}

+ 17 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/DynamicClientCreator.java

@@ -0,0 +1,17 @@
+package cn.nosum.support.proxy;
+
+/**
+ * dynamic method client instance creator。
+ *
+ * @author Young
+ */
+public abstract class DynamicClientCreator {
+
+    /**
+     * Create a dynamic method client instance
+     *
+     * @param target target object information
+     * @return dynamic method client instance
+     */
+    public abstract <T> T newInstance(Target<T> target);
+}

+ 38 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/InvocationHandlerFactory.java

@@ -0,0 +1,38 @@
+package cn.nosum.support.proxy;
+
+import java.lang.reflect.InvocationHandler;
+
+
+/**
+ * InvocationHandlerFactory.
+ *
+ * @author Young
+ */
+public interface InvocationHandlerFactory {
+
+
+    /**
+     * create InvocationHandler。
+     *
+     * @param target Target Object Information
+     * @return InvocationHandler
+     */
+    InvocationHandler create(Target target);
+
+    /**
+     * Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
+     * single method.
+     */
+    interface MethodHandler {
+        Object invoke(Object[] argv) throws Throwable;
+    }
+
+
+    final class Default implements InvocationHandlerFactory {
+
+        @Override
+        public InvocationHandler create(Target target) {
+            return new ReflectiveDynamicClientCreator.DynamicMethodsInvocationHandler(target);
+        }
+    }
+}

+ 91 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/ReflectiveDynamicClientCreator.java

@@ -0,0 +1,91 @@
+package cn.nosum.support.proxy;
+
+import cn.nosum.support.config.DynamicMethodsEndpointRegistrar;
+import cn.nosum.support.endpoint.MethodEndpoint;
+import cn.nosum.support.utils.CheckUtil;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Map;
+
+/**
+ * Reflective dynamic method client instance creator。
+ *
+ * @author Young
+ */
+public class ReflectiveDynamicClientCreator extends DynamicClientCreator {
+
+
+    private final InvocationHandlerFactory factory;
+
+    public ReflectiveDynamicClientCreator(InvocationHandlerFactory factory) {
+        this.factory = factory;
+    }
+
+    @Override
+    public <T> T newInstance(Target<T> target) {
+        InvocationHandler handler = factory.create(target);
+        return (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);
+    }
+
+    static class DynamicMethodsInvocationHandler implements InvocationHandler {
+
+        private final Target target;
+        private Map<String, MethodEndpoint> nameToEndpoint;
+
+        DynamicMethodsInvocationHandler(Target target) {
+            this.target = target;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            if (null == nameToEndpoint){
+                nameToEndpoint = DynamicMethodsEndpointRegistrar.getEndpoint(target.refName());
+            }
+
+            if ("equals".equals(method.getName())) {
+                try {
+                    Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
+                    return equals(otherHandler);
+                } catch (IllegalArgumentException e) {
+                    return false;
+                }
+            } else if ("hashCode".equals(method.getName())) {
+                return hashCode();
+            } else if ("toString".equals(method.getName())) {
+                return toString();
+            }
+
+            if (null != args){
+                String arg0 = String.valueOf(args[0]);
+                MethodEndpoint methodEndpoint = CheckUtil.checkNotNull(nameToEndpoint.get(arg0),"method is not found!!!");
+                Object[] newArgs = new Object[args.length-1];
+                System.arraycopy(args, 1, newArgs, 0, args.length - 1);
+                if (methodEndpoint.isMatcher(newArgs)){
+                   return methodEndpoint.invoke(newArgs);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof DynamicMethodsInvocationHandler) {
+                DynamicMethodsInvocationHandler other = (DynamicMethodsInvocationHandler) obj;
+                return target.equals(other.target);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return target.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return target.toString();
+        }
+    }
+}

+ 74 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/proxy/Target.java

@@ -0,0 +1,74 @@
+package cn.nosum.support.proxy;
+
+/**
+ * Target Object Information
+ *
+ * @author Young.
+ */
+public interface Target<T> {
+
+    /**
+     * Dynamic Client Name.
+     *
+     * @return name
+     */
+    String name();
+
+    /**
+     * Dynamic Client Type
+     *
+     * @return type
+     */
+    Class<T> type();
+
+    /**
+     * Dynamic Client RefName.
+     *
+     * @return refName
+     */
+    String refName();
+
+    /**
+     * Dynamic Client RefType.
+     *
+     * @return refType
+     */
+    Class<?> refType();
+
+
+    class DefaultTarget<T> implements Target<T> {
+        private final Class<T> type;
+        private final String name;
+
+        private final String refName;
+        private final Class<?> refType;
+
+        public DefaultTarget(String name, Class<T> type, String refName, Class<?> refType) {
+            this.name = name;
+            this.type = type;
+            this.refName = refName;
+            this.refType = refType;
+        }
+
+        @Override
+        public Class type() {
+            return type;
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public String refName() {
+            return refName;
+        }
+
+        @Override
+        public Class<?> refType() {
+            return refType;
+        }
+    }
+
+}

+ 21 - 0
spring-boot-dynamic-methods-starter/src/main/java/cn/nosum/support/utils/CheckUtil.java

@@ -0,0 +1,21 @@
+package cn.nosum.support.utils;
+
+import static java.lang.String.format;
+
+/**
+ * Checking Tool.
+ *
+ * @author Young
+ */
+public class CheckUtil {
+
+    public static <T> T checkNotNull(T reference,
+                                     String errorMessageTemplate,
+                                     Object... errorMessageArgs) {
+        if (reference == null) {
+            // If either of these parameters is null, the right thing happens anyway
+            throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
+        }
+        return reference;
+    }
+}

+ 34 - 0
support-demo/pom.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>nosum-support</artifactId>
+        <groupId>cn.nosum</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>support-demo</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.nosum</groupId>
+            <artifactId>spring-boot-dynamic-methods-starter</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 16 - 0
support-demo/src/main/java/cn/nosum/Application.java

@@ -0,0 +1,16 @@
+package cn.nosum;
+
+import cn.nosum.support.annotation.EnableDynamicClients;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author Young
+ */
+@EnableDynamicClients
+@SpringBootApplication
+public class Application {
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+}

+ 12 - 0
support-demo/src/main/java/cn/nosum/client/DynamicClientTest.java

@@ -0,0 +1,12 @@
+package cn.nosum.client;
+
+import cn.nosum.service.DynamicService;
+import cn.nosum.support.annotation.DynamicClient;
+
+@DynamicClient(refType = DynamicService.class)
+public interface DynamicClientTest {
+
+    void testVoid(String name,String a,String b,String c);
+
+    String testParams(String name,String a,String b,String c);
+}

+ 29 - 0
support-demo/src/main/java/cn/nosum/controller/DynamicController.java

@@ -0,0 +1,29 @@
+package cn.nosum.controller;
+
+import cn.nosum.client.DynamicClientTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author Young
+ */
+@RestController
+@RequestMapping("/")
+public class DynamicController {
+
+    @Autowired
+    private DynamicClientTest dynamicClientTest;
+
+    @RequestMapping("/void/{methodName}")
+    public String testVoid(@PathVariable(value = "methodName") String methodName){
+        dynamicClientTest.testVoid(methodName,"a","b","c");
+        return null;
+    }
+
+    @RequestMapping("/param/{methodName}")
+    public String testParam(@PathVariable(value = "methodName") String methodName){
+        return dynamicClientTest.testParams(methodName,"a","b","c");
+    }
+}

+ 13 - 0
support-demo/src/main/java/cn/nosum/service/DynamicService.java

@@ -0,0 +1,13 @@
+package cn.nosum.service;
+
+/**
+ * @author Young
+ */
+public interface DynamicService {
+
+    void testVoid1(String a,String b,String c);
+    void testVoid2(String a,String b,String c);
+
+    String testParams1(String a,String b,String c);
+    String testParams2(String a,String b,String c);
+}

+ 41 - 0
support-demo/src/main/java/cn/nosum/service/impl/DynamicServiceImpl.java

@@ -0,0 +1,41 @@
+package cn.nosum.service.impl;
+
+import cn.nosum.service.DynamicService;
+import cn.nosum.support.annotation.DynamicClass;
+import cn.nosum.support.annotation.DynamicMethod;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Young
+ */
+@Slf4j
+@DynamicClass
+public class DynamicServiceImpl implements DynamicService {
+
+    @Override
+//    @DynamicMethod(name = "testVoid1")
+    public void testVoid1(String a, String b, String c) {
+        log.info("testVoid1 a is {},b is {},c is {}", a, b, c);
+    }
+
+    @Override
+//    @DynamicMethod(name = "testVoid2")
+    public void testVoid2(String a, String b, String c) {
+        log.info("testVoid2 a is {},b is {},c is {}", a, b, c);
+    }
+
+    @Override
+//    @DynamicMethod(name = "testParams")
+    public String testParams1(String a, String b, String c) {
+        log.info("testParams1 a is {},b is {},c is {}", a, b, c);
+        return a + b + c;
+    }
+
+    @Override
+    @DynamicMethod(name = "testParams2")
+    public String testParams2(String a, String b, String c) {
+        log.info("testParams2 a is {},b is {},c is {}", a, b, c);
+        return a + b + c;
+    }
+}
+