1、问题描述

org.springframework.core.ParameterNameDiscoverer#getParameterNames(java.lang.reflect.Method)

获取正常实现类的方法参数正常,但是通过调用接口代理类的方法则获取不到方法参数名称。

切面相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Aspect
@Component
@Slf4j
public class WxFuncAuthAspect {
/**
* 用于SpEL表达式解析.
*/
private final SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

@Before(value = "@annotation(wxFuncAuth)")
public void wxFuncAuth(JoinPoint joinPoint, WxFuncAuth wxFuncAuth) {
//获取appId
String appId = generateKeyBySpringEL(wxFuncAuth.appId(), joinPoint);
}

/**
* 根据el表达式字符串生成key
*
* @param elString el表达式字符串
* @param joinPoint joinPoint
* @return key
*/
private String generateKeyBySpringEL(String elString, JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Expression expression = parser.parseExpression(elString);
EvaluationContext context = new MethodBasedEvaluationContext(new Object(), methodSignature.getMethod(), joinPoint.getArgs(), nameDiscoverer);
return Objects.requireNonNull(expression.getValue(context)).toString();
}
}

MethodBasedEvaluationContext类内部获取方法参数名数组核心逻辑:

1
2
//这里的parameterNameDiscoverer为DefaultParameterNameDiscoverer
String[] paramNames = this.parameterNameDiscoverer.getParameterNames(this.method);

1.1、正常例子

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class WxFuncAuthAnnotationCaller {
/***
* 正常场景测试
* @param appId appId
* @return String
*/
@WxFuncAuth(appId = "#appId", funcs = {"1", "2"})
public String callNormal(String appId) {
return appId + ": I'm very good";
}
}

例子中,WxFuncAuthAnnotationCaller类没有实现接口,切面WxFuncAuthAspect中的generateKeyBySpringEL方法中,methodSignature.getMethod()获取的值为:public java.lang.String com.f6car.ranger.service.test.aop.WxFuncAuthAnnotationCaller.callNormal(java.lang.String)

img

可正常获取参数名称:

img

1.2、异常例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface WechatTagsService {
WxTagsVo getWxTags(String appId, String accessToken);
}
@Service
@Slf4j
public class WechatTagsServiceImpl implements WechatTagsService {
@Override
@WxFuncAuth(appId = "#appId", funcs = {WxFuncscopeCategoryConstant.USER_MANAGEMENT})
public WxTagsVo getWxTags(String appId, String accessToken) {
WxHttpBaseRequest wxHttpBaseRequest = new WxHttpBaseRequest();
String url = String.format(WechatTagsUrlConst.WX_TAGS_GET, accessToken);
wxHttpBaseRequest.setUrl(url);
wxHttpBaseRequest.setAppId(appId);
String response = OkHttpUtils.doGetSSL(wxHttpBaseRequest);
return JSON.parseObject(response, WxTagsVo.class);
}
}
public class WechatTagsServiceTest extends com.f6car.ranger.test.base.BaseTest {
@Autowired
private WechatOpenManager wechatOpenManager;
//注意这里,注入的是接口的代理实现类
@Autowired
private WechatTagsService wechatTagsService;

/**
* 获取某个微信公众号下已经创建好的标签
*/
@Test
public void testGetWxTags() {
String appId = "wx8739309c6a848344";
//接口调用凭证
String accessToken = wechatOpenManager.getAuthAccessTokenByAppId(appId);
WxTagsVo wxTagsVo = wechatTagsService.getWxTags(appId, accessToken);
logger.info(JSON.toJSONString(wxTagsVo));
}

}

WechatTagsServiceImpl实现了WechatTagsService接口,调用时通过接口调用方法。切面WxFuncAuthAspect中的generateKeyBySpringEL方法中,methodSignature.getMethod()获取的值入下图所示,获取的并不是具体实现类的方法:

img

方法参数获取不到:

image.png

2、解决思路

想到以前使用的@Cacheable也是使用springEL表达式拼接缓存key,也是通过接口调用,那必然是能拿到代理实现类方法的参数名称的,为什么springframework.cache就可获取到方法的参数,而我写的获取不到呢?

使用@Cacheable的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据场景ID查询消息场景信息
*
* @param msgSceneSo 过滤条件 msgSceneId
* @return 消息场景信息
*/
@Override
@Transactional(rollbackFor = Exception.class, timeout = 1)
@DataSource(DataSourceType.READONLY)
@Cacheable(value = "msgScene", key = "'queryMsgSceneById'+#msgSceneSo.msgSceneId", condition = "#msgSceneSo.msgSceneId!=null", unless = "#result==null")
public MsgSceneVo queryMsgSceneById(MsgSceneSo msgSceneSo) {
return queryMsgSceneMethod(msgSceneSo);
}

所以开始了边debug边啃源码,终于找到了有意思的地方:

org.springframework.cache.interceptor.CacheOperationExpressionEvaluator#createEvaluationContext(java.util.Collection, java.lang.reflect.Method, java.lang.Object[], java.lang.Object, java.lang.Class, java.lang.Object, org.springframework.beans.factory.BeanFactory)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Create an {@link EvaluationContext}.
* @param caches the current caches
* @param method the method
* @param args the method arguments
* @param target the target object
* @param targetClass the target class
* @param result the return value (can be {@code null}) or
* {@link #NO_RESULT} if there is no return at this time
* @return the evaluation context
*/
public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
Method method, Object[] args, Object target, Class<?> targetClass, Object result,
BeanFactory beanFactory) {

CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
caches, method, args, target, targetClass);
//看这里!!!!!
Method targetMethod = getTargetMethod(targetClass, method);
CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
rootObject, targetMethod, args, getParameterNameDiscoverer());
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
}
else if (result != NO_RESULT) {
evaluationContext.setVariable(RESULT_VARIABLE, result);
}
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}

看到一行感觉好像没用的代码:Method targetMethod = getTargetMethod(targetClass, method);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}

/**
* Given a method, which may come from an interface, and a target class used
* in the current AOP invocation, find the corresponding target method if there
* is one. E.g. the method may be {@code IFoo.bar()} and the target class
* may be {@code DefaultFoo}. In this case, the method may be
* {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
* <p><b>NOTE:</b> In contrast to {@link org.springframework.util.ClassUtils#getMostSpecificMethod},
* this method resolves Java 5 bridge methods in order to retrieve attributes
* from the <i>original</i> method definition.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation.
* May be {@code null} or may not even implement the method.
* @return the specific target method, or the original method if the
* {@code targetClass} doesn't implement it or is {@code null}
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, Class<?> targetClass) {
//!!!!实现类的方法
Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original method.
return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
}

debug之后发现了这里的秘密:AopUtils.getMostSpecificMethod工具类可以获取真正实现类的方法。

3、解决

所以修改WxFuncAuthAspect.generateKeyBySpringEL方法,增加一行代码解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 根据el表达式字符串生成key
*
* @param elString el表达式字符串
* @param joinPoint joinPoint
* @return key
*/
private String generateKeyBySpringEL(String elString, JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//增加了这一行代码!!!!
Method targetMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), AopUtils.getTargetClass(joinPoint.getTarget()));
Expression expression = parser.parseExpression(elString);
EvaluationContext context = new MethodBasedEvaluationContext(new Object(), targetMethod, joinPoint.getArgs(), nameDiscoverer);
return Objects.requireNonNull(expression.getValue(context)).toString();
}

4、验证

method值变成了具体实现类的方法:public com.f6car.ranger.vo.wx.tags.WxTagsVo com.f6car.ranger.service.impl.wx.tags.WechatTagsServiceImpl.getWxTags(java.lang.String,java.lang.String)

img

方法的参数名也获取到了,问题解决.

img

5、总结

  • 自己想的一些技术思路,其实很多前人都已经实现,要善于使用工具。
  • 在使用工具的同时,要简单了解其实现原理,便于问题定位及技术参考。