You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@servicecomb.apache.org by GitBox <gi...@apache.org> on 2017/12/27 09:17:58 UTC

[GitHub] liubao68 closed pull request #215: ??AOP?????????????

liubao68 closed pull request #215: ??AOP?????????????
URL: https://github.com/apache/incubator-servicecomb-java-chassis/pull/215
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/foundations/foundation-advance/pom.xml b/foundations/foundation-advance/pom.xml
new file mode 100644
index 000000000..19d39b2fe
--- /dev/null
+++ b/foundations/foundation-advance/pom.xml
@@ -0,0 +1,62 @@
+<!--
+  ~ Copyright 2017 Huawei Technologies Co., Ltd
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>io.servicecomb</groupId>
+		<version>0.4.0-SNAPSHOT</version>
+		<artifactId>foundations</artifactId>
+	</parent>
+	<artifactId>foundation-advance</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>io.servicecomb</groupId>
+			<artifactId>swagger-invocation-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-context</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-log4j12</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.netflix.hystrix</groupId>
+			<artifactId>hystrix-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.netflix.hystrix</groupId>
+			<artifactId>hystrix-javanica</artifactId>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/foundations/foundation-advance/src/main/java/io/servicecomb/foundation/advance/async/AsyncHystrixAspect.java b/foundations/foundation-advance/src/main/java/io/servicecomb/foundation/advance/async/AsyncHystrixAspect.java
new file mode 100644
index 000000000..659887bdb
--- /dev/null
+++ b/foundations/foundation-advance/src/main/java/io/servicecomb/foundation/advance/async/AsyncHystrixAspect.java
@@ -0,0 +1,96 @@
+package io.servicecomb.foundation.advance.async;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+
+import com.netflix.hystrix.HystrixCommand;
+import com.netflix.hystrix.HystrixCommandGroupKey;
+import com.netflix.hystrix.HystrixCommandKey;
+import com.netflix.hystrix.HystrixThreadPoolKey;
+import com.netflix.hystrix.contrib.javanica.utils.AopUtils;
+
+import io.servicecomb.swagger.invocation.context.ContextUtils;
+import io.servicecomb.swagger.invocation.context.InvocationContext;
+import io.servicecomb.swagger.invocation.exception.ExceptionFactory;
+
+/**
+ * AspectJ aspect to process methods which annotated with {@link AsyncMethod} annotation.
+ */
+@Aspect
+public class AsyncHystrixAspect {
+
+  @Pointcut("@annotation(io.servicecomb.foundation.advance.async.AsyncMethod)")
+  public void asyncMethodAnnotationPointcut() {
+  }
+
+  @Around("asyncMethodAnnotationPointcut()&&@annotation(asyncMethod)")
+  public Object methodsAnnotatedWithAsyncMethod(final ProceedingJoinPoint joinPoint, final AsyncMethod asyncMethod)
+      throws Throwable {
+    CompletableFuture future = new CompletableFuture();
+    InvocationContext invocationContext = ContextUtils.getAndRemoveInvocationContext();
+    HystrixCommand.Setter setter = HystrixCommand.Setter
+        .withGroupKey(HystrixCommandGroupKey.Factory.asKey(asyncMethod.groupKey())).andCommandKey(
+            HystrixCommandKey.Factory.asKey(asyncMethod.commandKey()))
+        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(asyncMethod.threadPoolKey()));
+    AsyncHystrixCommand asyncHystrixCommand = new AsyncHystrixCommand(setter, joinPoint, asyncMethod,
+        invocationContext);
+    asyncHystrixCommand.observe().subscribe(future::complete, future::completeExceptionally);
+
+    ContextUtils.setAsyncFuture(future);
+    return null;
+  }
+
+  private static final class AsyncHystrixCommand extends HystrixCommand<Object> {
+    final ProceedingJoinPoint joinPoint;
+
+    final InvocationContext invocationContext;
+
+    final AsyncMethod asyncMethod;
+
+    protected AsyncHystrixCommand(HystrixCommand.Setter setter, final ProceedingJoinPoint joinPoint,
+        final AsyncMethod asyncMethod, final InvocationContext invocationContext) {
+      super(setter);
+      this.joinPoint = joinPoint;
+      this.invocationContext = invocationContext;
+      this.asyncMethod = asyncMethod;
+    }
+
+    @Override
+    protected Object run() throws Exception {
+      ContextUtils.setInvocationContext(invocationContext);
+      try {
+        try {
+          return joinPoint.proceed();
+        } catch (Throwable throwable) {
+          throw ExceptionFactory.convertProducerException(throwable);
+        }
+      } finally {
+        ContextUtils.removeInvocationContext();
+      }
+    }
+
+    @Override
+    protected Object getFallback() {
+      if (StringUtils.isNotBlank(asyncMethod.fallbackMethod())) {
+        Method fallbackMethod = AopUtils.getMethodFromTarget(joinPoint, asyncMethod.fallbackMethod());
+        fallbackMethod.setAccessible(true);
+        try {
+          return fallbackMethod.invoke(joinPoint.getTarget());
+        } catch (IllegalAccessException e) {
+          throw ExceptionFactory.convertProducerException(e);
+        } catch (InvocationTargetException e) {
+          throw ExceptionFactory.convertProducerException(e);
+        }
+      } else {
+        throw ExceptionFactory.createProducerException("No fall back method.");
+      }
+    }
+  }
+}
diff --git a/foundations/foundation-advance/src/main/java/io/servicecomb/foundation/advance/async/AsyncMethod.java b/foundations/foundation-advance/src/main/java/io/servicecomb/foundation/advance/async/AsyncMethod.java
new file mode 100644
index 000000000..eedd59dcd
--- /dev/null
+++ b/foundations/foundation-advance/src/main/java/io/servicecomb/foundation/advance/async/AsyncMethod.java
@@ -0,0 +1,69 @@
+package io.servicecomb.foundation.advance.async;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface AsyncMethod {
+
+  /**
+   * The command group key is used for grouping together commands such as for reporting,
+   * alerting, dashboards or team/library ownership.
+   * <p/>
+   * default => the runtime class name of annotated method
+   *
+   * @return group key
+   */
+  String groupKey() default "";
+
+  /**
+   * Hystrix command key.
+   * <p/>
+   * default => the name of annotated method. for example:
+   * <code>
+   *     ...
+   *     @HystrixCommand
+   *     public User getUserById(...)
+   *     ...
+   *     the command name will be: 'getUserById'
+   * </code>
+   *
+   * @return command key
+   */
+  String commandKey() default "";
+
+  /**
+   * The thread-pool key is used to represent a
+   * HystrixThreadPool for monitoring, metrics publishing, caching and other such uses.
+   *
+   * @return thread pool key
+   */
+  String threadPoolKey() default "";
+
+  /**
+   * Specifies a method to process fallback logic.
+   * A fallback method should be defined in the same class where is HystrixCommand.
+   * Also a fallback method should have same signature to a method which was invoked as hystrix command.
+   * for example:
+   * <code>
+   *      @HystrixCommand(fallbackMethod = "getByIdFallback")
+   *      public String getById(String id) {...}
+   *
+   *      private String getByIdFallback(String id) {...}
+   * </code>
+   * Also a fallback method can be annotated with {@link com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand}
+   * <p/>
+   * default => see {@link com.netflix.hystrix.contrib.javanica.command.GenericCommand#getFallback()}
+   *
+   * @return method name
+   */
+  String fallbackMethod() default "";
+
+}
diff --git a/foundations/pom.xml b/foundations/pom.xml
index 77f64d51a..5a9d083bc 100644
--- a/foundations/pom.xml
+++ b/foundations/pom.xml
@@ -31,5 +31,6 @@
       <module>foundation-metrics</module>
       <module>foundation-ssl</module>
       <module>foundation-test-scaffolding</module>
+      <module>foundation-advance</module>
   </modules>
 </project>
\ No newline at end of file
diff --git a/java-chassis-dependencies/pom.xml b/java-chassis-dependencies/pom.xml
index 1f0908e16..5370e4b86 100644
--- a/java-chassis-dependencies/pom.xml
+++ b/java-chassis-dependencies/pom.xml
@@ -684,6 +684,11 @@
         <artifactId>foundation-common</artifactId>
         <version>0.4.0-SNAPSHOT</version>
       </dependency>
+      <dependency>
+        <groupId>io.servicecomb</groupId>
+        <artifactId>foundation-advance</artifactId>
+        <version>0.4.0-SNAPSHOT</version>
+      </dependency>
       <dependency>
         <groupId>io.servicecomb</groupId>
         <artifactId>foundation-test-scaffolding</artifactId>
diff --git a/samples/springmvc-sample/springmvc-provider/pom.xml b/samples/springmvc-sample/springmvc-provider/pom.xml
index 9ec079e7c..3b5edfc16 100644
--- a/samples/springmvc-sample/springmvc-provider/pom.xml
+++ b/samples/springmvc-sample/springmvc-provider/pom.xml
@@ -56,5 +56,13 @@
             <groupId>io.servicecomb.samples</groupId>
             <artifactId>commmon-schema</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.servicecomb</groupId>
+            <artifactId>foundation-advance</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aop</artifactId>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/samples/springmvc-sample/springmvc-provider/src/main/java/io/servicecomb/samples/springmvc/provider/SpringmvcHelloImpl.java b/samples/springmvc-sample/springmvc-provider/src/main/java/io/servicecomb/samples/springmvc/provider/SpringmvcHelloImpl.java
index 9430b6e2a..4112d0abd 100644
--- a/samples/springmvc-sample/springmvc-provider/src/main/java/io/servicecomb/samples/springmvc/provider/SpringmvcHelloImpl.java
+++ b/samples/springmvc-sample/springmvc-provider/src/main/java/io/servicecomb/samples/springmvc/provider/SpringmvcHelloImpl.java
@@ -19,21 +19,25 @@
 
 import javax.ws.rs.core.MediaType;
 
+import org.springframework.stereotype.Component;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 
+import io.servicecomb.foundation.advance.async.AsyncMethod;
 import io.servicecomb.provider.rest.common.RestSchema;
 import io.servicecomb.samples.common.schema.Hello;
 import io.servicecomb.samples.common.schema.models.Person;
 
 @RestSchema(schemaId = "springmvcHello")
 @RequestMapping(path = "/springmvchello", produces = MediaType.APPLICATION_JSON)
+@Component
 public class SpringmvcHelloImpl implements Hello {
 
   @Override
   @RequestMapping(path = "/sayhi", method = RequestMethod.POST)
+  @AsyncMethod
   public String sayHi(@RequestParam(name = "name") String name) {
     return "Hello " + name;
   }
diff --git a/samples/springmvc-sample/springmvc-provider/src/main/resources/META-INF/spring/pojo.provider.bean.xml b/samples/springmvc-sample/springmvc-provider/src/main/resources/META-INF/spring/pojo.provider.bean.xml
index 2acd82877..2218eacaf 100644
--- a/samples/springmvc-sample/springmvc-provider/src/main/resources/META-INF/spring/pojo.provider.bean.xml
+++ b/samples/springmvc-sample/springmvc-provider/src/main/resources/META-INF/spring/pojo.provider.bean.xml
@@ -19,10 +19,18 @@
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
        xmlns:util="http://www.springframework.org/schema/util" xmlns:cse="http://www.huawei.com/schema/paas/cse/rpc"
        xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="
 		http://www.springframework.org/schema/beans classpath:org/springframework/beans/factory/xml/spring-beans-3.0.xsd
 		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
-		http://www.huawei.com/schema/paas/cse/rpc classpath:META-INF/spring/spring-paas-cse-rpc.xsd">
+		http://www.huawei.com/schema/paas/cse/rpc classpath:META-INF/spring/spring-paas-cse-rpc.xsd
+    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
 
     <context:component-scan base-package="io.servicecomb.samples.pojo.server" />
+
+    <aop:aspectj-autoproxy/>
+
+    <bean id="asyncHystrixAspect" class="io.servicecomb.foundation.advance.async.AsyncHystrixAspect"/>
+
 </beans>
\ No newline at end of file
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/engine/SwaggerProducerOperation.java b/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/engine/SwaggerProducerOperation.java
index 533415931..583d3b299 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/engine/SwaggerProducerOperation.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/engine/SwaggerProducerOperation.java
@@ -17,6 +17,7 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.concurrent.CompletableFuture;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -26,6 +27,7 @@
 import io.servicecomb.swagger.invocation.SwaggerInvocation;
 import io.servicecomb.swagger.invocation.arguments.producer.ProducerArgumentsMapper;
 import io.servicecomb.swagger.invocation.context.ContextUtils;
+import io.servicecomb.swagger.invocation.exception.ExceptionFactory;
 import io.servicecomb.swagger.invocation.exception.InvocationException;
 import io.servicecomb.swagger.invocation.response.producer.ProducerResponseMapper;
 
@@ -61,6 +63,7 @@ public void setName(String name) {
 
   public void setProducerClass(Class<?> producerClass) {
     this.producerClass = producerClass;
+    checkProxyMethod();
   }
 
   public Object getProducerInstance() {
@@ -69,6 +72,7 @@ public Object getProducerInstance() {
 
   public void setProducerInstance(Object producerInstance) {
     this.producerInstance = producerInstance;
+    checkProxyMethod();
   }
 
   public Method getProducerMethod() {
@@ -77,6 +81,7 @@ public Method getProducerMethod() {
 
   public void setProducerMethod(Method producerMethod) {
     this.producerMethod = producerMethod;
+    checkProxyMethod();
   }
 
   public Method getSwaggerMethod() {
@@ -106,25 +111,70 @@ public void setResponseMapper(ProducerResponseMapper responseMapper) {
   public void invoke(SwaggerInvocation invocation, AsyncResponse asyncResp) {
     ContextUtils.setInvocationContext(invocation);
 
-    Response response = doInvoke(invocation);
+    Response response = doInvoke(invocation, asyncResp);
 
     ContextUtils.removeInvocationContext();
 
-    asyncResp.handle(response);
+    if (null != response) {
+      asyncResp.handle(response);
+    }
   }
 
   public Response doInvoke(SwaggerInvocation invocation) {
+    return doInvoke(invocation, null);
+  }
+
+  protected Response doInvoke(final SwaggerInvocation invocation, final AsyncResponse asyncResp) {
     Response response = null;
     try {
       Object[] args = argumentsMapper.toProducerArgs(invocation);
       Object result = producerMethod.invoke(producerInstance, args);
-      response = responseMapper.mapResponse(invocation.getStatus(), result);
+      CompletableFuture future = ContextUtils.getAndRemoveAsyncFuture();
+      if (null != future) {
+        // when application use async feature
+        if (null != asyncResp) {
+          future.whenComplete((res, err) -> asyncResp.handle(processResponse(invocation, res, (Throwable) err)));
+        } else {
+          response = responseMapper.mapResponse(invocation.getStatus(), future.get());
+        }
+      } else {
+        response = responseMapper.mapResponse(invocation.getStatus(), result);
+      }
     } catch (Throwable e) {
       response = processException(e);
     }
     return response;
   }
 
+  protected void checkProxyMethod() {
+    if (null != this.producerMethod && null != this.producerClass && null != this.producerInstance) {
+      if (this.producerInstance.getClass() != this.producerClass) {
+        try {
+          this.producerMethod = this.producerInstance.getClass()
+              .getDeclaredMethod(this.producerMethod.getName(), this.producerMethod.getParameterTypes());
+        } catch (NoSuchMethodException e) {
+          throw ExceptionFactory.createProducerException(e);
+        }
+      }
+    }
+  }
+
+  protected Response processResponse(SwaggerInvocation invocation, Object result, Throwable e) {
+    Response response;
+    try {
+      if (null != result) {
+        response = responseMapper.mapResponse(invocation.getStatus(), result);
+      } else if (null != e) {
+        response = processException(e);
+      } else {
+        response = processException(new IllegalStateException());
+      }
+    } catch (Throwable throwable) {
+      response = processException(throwable);
+    }
+    return response;
+  }
+
   protected Response processException(Throwable e) {
     if (InvocationTargetException.class.isInstance(e)) {
       e = ((InvocationTargetException) e).getTargetException();
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/invocation/context/ContextUtils.java b/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/invocation/context/ContextUtils.java
index ce23db577..e52422e83 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/invocation/context/ContextUtils.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/io/servicecomb/swagger/invocation/context/ContextUtils.java
@@ -16,6 +16,9 @@
 
 package io.servicecomb.swagger.invocation.context;
 
+import java.util.concurrent.CompletableFuture;
+
+
 /**
  * ????????????
  */
@@ -25,6 +28,8 @@ private ContextUtils() {
 
   private static ThreadLocal<InvocationContext> contextMgr = new ThreadLocal<>();
 
+  private static ThreadLocal<CompletableFuture> asyncFuture = new ThreadLocal<>();
+
   public static InvocationContext getInvocationContext() {
     return contextMgr.get();
   }
@@ -37,10 +42,22 @@ public static InvocationContext getAndRemoveInvocationContext() {
     return context;
   }
 
+  public static CompletableFuture getAndRemoveAsyncFuture() {
+    CompletableFuture future = asyncFuture.get();
+    if (future != null) {
+      asyncFuture.remove();
+    }
+    return future;
+  }
+
   public static void setInvocationContext(InvocationContext invocationContext) {
     contextMgr.set(invocationContext);
   }
 
+  public static void setAsyncFuture(CompletableFuture future) {
+    asyncFuture.set(future);
+  }
+
   public static void removeInvocationContext() {
     contextMgr.remove();
   }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services