View Javadoc
1   /*
2    * Copyright © 2025 rosestack.github.io
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.springframework.messaging.handler.invocation;
17  
18  import io.github.rose.core.util.StringPool;
19  import io.github.rose.mybatis.tenant.util.TenantContextHolder;
20  import io.github.rose.mybatis.tenant.util.TenantUtils;
21  import org.springframework.core.DefaultParameterNameDiscoverer;
22  import org.springframework.core.MethodParameter;
23  import org.springframework.core.ParameterNameDiscoverer;
24  import org.springframework.core.ResolvableType;
25  import org.springframework.lang.Nullable;
26  import org.springframework.messaging.Message;
27  import org.springframework.messaging.handler.HandlerMethod;
28  import org.springframework.util.ObjectUtils;
29  
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  import java.lang.reflect.Type;
33  import java.nio.charset.StandardCharsets;
34  import java.util.Arrays;
35  import java.util.Objects;
36  
37  import static io.github.rose.core.util.Constants.HEADER_TENANT_ID;
38  
39  /**
40   * Extension of {@link org.springframework.messaging.handler.HandlerMethod} that invokes
41   * the underlying method with argument values resolved from the current HTTP request
42   * through a list of
43   * {@link org.springframework.web.method.support.HandlerMethodArgumentResolver}.
44   * <p>
45   * 针对 rabbitmq-spring 和 kafka-spring,不存在合适的拓展点,可以实现 Consumer 消费前,读取 Header 中的 tenant-id
46   * 设置到 {@link TenantContextHolder} 中  持续跟进,看看有没新的拓展点
47   *
48   * @author Rossen Stoyanchev
49   * @author Juergen Hoeller
50   * @since 4.0
51   */
52  public class InvocableHandlerMethod extends HandlerMethod {
53  
54      private static final Object[] EMPTY_ARGS = new Object[0];
55  
56      private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
57  
58      private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
59  
60      /**
61       * Create an instance from a {@code HandlerMethod}.
62       */
63      public InvocableHandlerMethod(HandlerMethod handlerMethod) {
64          super(handlerMethod);
65      }
66  
67      /**
68       * Create an instance from a bean instance and a method.
69       */
70      public InvocableHandlerMethod(Object bean, Method method) {
71          super(bean, method);
72      }
73  
74      /**
75       * Construct a new handler method with the given bean instance, method name and
76       * parameters.
77       *
78       * @param bean           the object bean
79       * @param methodName     the method name
80       * @param parameterTypes the method parameter types
81       * @throws NoSuchMethodException when the method cannot be found
82       */
83      public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
84          throws NoSuchMethodException {
85  
86          super(bean, methodName, parameterTypes);
87      }
88  
89      /**
90       * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for
91       * resolving method argument values.
92       */
93      public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
94          this.resolvers = argumentResolvers;
95      }
96  
97      /**
98       * Set the ParameterNameDiscoverer for resolving parameter names when needed (e.g.
99       * default request attribute name).
100      * <p>
101      * Default is a {@link DefaultParameterNameDiscoverer}.
102      */
103     public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
104         this.parameterNameDiscoverer = parameterNameDiscoverer;
105     }
106 
107     /**
108      * Invoke the method after resolving its argument values in the context of the given
109      * message.
110      * <p>
111      * Argument values are commonly resolved through {@link HandlerMethodArgumentResolver
112      * HandlerMethodArgumentResolvers}. The {@code providedArgs} parameter however may
113      * supply argument values to be used directly, i.e. without argument resolution.
114      * <p>
115      * Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
116      * resolved arguments.
117      *
118      * @param message      the current message being processed
119      * @param providedArgs "given" arguments matched by type, not resolved
120      * @return the raw value returned by the invoked method
121      * @throws Exception raised if no suitable argument resolver can be found, or if the
122      *                   method raised an exception
123      * @see #getMethodArgumentValues
124      * @see #doInvoke
125      */
126     @Nullable
127     public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
128         Object[] args = getMethodArgumentValues(message, providedArgs);
129         if (logger.isTraceEnabled()) {
130             logger.trace("Arguments: " + Arrays.toString(args));
131         }
132         // 注意:如下是本类的改动点!!!
133         // 情况一:无租户编号的情况
134         String tenantId = parseTenantId(message);
135         // 情况二:有租户的情况下
136         return TenantUtils.execute(tenantId, () -> doInvoke(args));
137     }
138 
139     private String parseTenantId(Message<?> message) {
140         Object tenantId = message.getHeaders().get(HEADER_TENANT_ID);
141         if (tenantId == null) {
142             return StringPool.UNKNOWN;
143         }
144         if (tenantId instanceof Long) {
145             return String.valueOf(tenantId);
146         }
147         if (tenantId instanceof Number) {
148             return String.valueOf(tenantId);
149         }
150         if (tenantId instanceof String) {
151             return (String) tenantId;
152         }
153         if (tenantId instanceof byte[]) {
154             return new String((byte[]) tenantId, StandardCharsets.UTF_8);
155         }
156         throw new IllegalArgumentException("未知的数据类型:" + tenantId);
157     }
158 
159     /**
160      * Get the method argument values for the current message, checking the provided
161      * argument values and falling back to the configured argument resolvers.
162      * <p>
163      * The resulting array will be passed into {@link #doInvoke}.
164      *
165      * @since 5.1.2
166      */
167     protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
168         MethodParameter[] parameters = getMethodParameters();
169         if (ObjectUtils.isEmpty(parameters)) {
170             return EMPTY_ARGS;
171         }
172 
173         Object[] args = new Object[parameters.length];
174         for (int i = 0; i < parameters.length; i++) {
175             MethodParameter parameter = parameters[i];
176             parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
177             args[i] = findProvidedArgument(parameter, providedArgs);
178             if (args[i] != null) {
179                 continue;
180             }
181             if (!this.resolvers.supportsParameter(parameter)) {
182                 throw new MethodArgumentResolutionException(
183                     message, parameter, formatArgumentError(parameter, "No suitable resolver"));
184             }
185             try {
186                 args[i] = this.resolvers.resolveArgument(parameter, message);
187             } catch (Exception ex) {
188                 // Leave stack trace for later, exception may actually be resolved and
189                 // handled...
190                 if (logger.isDebugEnabled()) {
191                     String exMsg = ex.getMessage();
192                     if (exMsg != null
193                         && !exMsg.contains(parameter.getExecutable().toGenericString())) {
194                         logger.debug(formatArgumentError(parameter, exMsg));
195                     }
196                 }
197                 throw ex;
198             }
199         }
200         return args;
201     }
202 
203     /**
204      * Invoke the handler method with the given argument values.
205      */
206     @Nullable
207     protected Object doInvoke(Object... args) throws Exception {
208         try {
209             return getBridgedMethod().invoke(getBean(), args);
210         } catch (IllegalArgumentException ex) {
211             assertTargetBean(getBridgedMethod(), getBean(), args);
212             String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
213             throw new IllegalStateException(formatInvokeError(text, args), ex);
214         } catch (InvocationTargetException ex) {
215             // Unwrap for HandlerExceptionResolvers ...
216             Throwable targetException = ex.getTargetException();
217             if (targetException instanceof RuntimeException) {
218                 throw (RuntimeException) targetException;
219             } else if (targetException instanceof Error) {
220                 throw (Error) targetException;
221             } else if (targetException instanceof Exception) {
222                 throw (Exception) targetException;
223             } else {
224                 throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
225             }
226         }
227     }
228 
229     MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) {
230         return new AsyncResultMethodParameter(returnValue);
231     }
232 
233     private class AsyncResultMethodParameter extends HandlerMethodParameter {
234 
235         @Nullable
236         private final Object returnValue;
237 
238         private final ResolvableType returnType;
239 
240         public AsyncResultMethodParameter(@Nullable Object returnValue) {
241             super(-1);
242             this.returnValue = returnValue;
243             this.returnType =
244                 ResolvableType.forType(super.getGenericParameterType()).getGeneric();
245         }
246 
247         protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
248             super(original);
249             this.returnValue = original.returnValue;
250             this.returnType = original.returnType;
251         }
252 
253         @Override
254         public Class<?> getParameterType() {
255             if (this.returnValue != null) {
256                 return this.returnValue.getClass();
257             }
258             if (!ResolvableType.NONE.equals(this.returnType)) {
259                 return this.returnType.toClass();
260             }
261             return super.getParameterType();
262         }
263 
264         @Override
265         public Type getGenericParameterType() {
266             return this.returnType.getType();
267         }
268 
269         @Override
270         public boolean equals(Object o) {
271             if (o == null || getClass() != o.getClass()) return false;
272             if (!super.equals(o)) return false;
273             AsyncResultMethodParameter that = (AsyncResultMethodParameter) o;
274             return Objects.equals(returnValue, that.returnValue) && Objects.equals(returnType, that.returnType);
275         }
276 
277         @Override
278         public int hashCode() {
279             return Objects.hash(super.hashCode(), returnValue, returnType);
280         }
281     }
282 }