点击上方名片关注我,为你带来更多踩坑案例
引导
废话不多说,本文从现象-原因-解决办法三个方面来简述
解决办法有多种,大家根据自己的情况自行选择
想看解决办法的直接拉到最下面
现象
使用springboot,在执行类似如下代码的时候
AbstractDataAuthHandler是一个抽象类
类下面有几个标有DataAuthHandler注解的子类,分别实现了不同的功能
想把注解中指定的value作为key,handler作为value,来实现一个简单的根据不同的value调用不同处理类的
@PostConstruct
public void init() {
Map<String, AbstractDataAuthHandler> abstractHandlerMap = context.getBeansOfType(AbstractDataAuthHandler.class);
for (String beanName : abstractHandlerMap.keySet()) {
AbstractDataAuthHandler handler = abstractHandlerMap.get(beanName);
DataAuthHandler authHandler = handler.getClass().getAnnotation(DataAuthHandler.class);
handlerMap.put(authHandler.value(), handler);
}
}
然后初始化设值的时候第7行直接报空指针
也就是说 authVlue 没获取到原因
其实原因很简单,问题无非就是出现在获取authValue注解的时候,debug仔细检查一下就会发现
handler变成了一个代理类???
果然,被spring代理了。
下面开始尝试解决办法
解决办法1-通过代理类获取原生对象
话不多说,直接上工具类
package com.iqiyi.scriptevaluationtool.sys.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import java.lang.reflect.Field;
@Slf4j
public class AopTargetUtils {
/**
* 获取 目标对象
* @param proxy 代理对象
* @return
* @throws Exception
*/
public static Object getTarget(Object proxy) {
try {
if(!AopUtils.isAopProxy(proxy)) {
return proxy;//不是代理对象
}
if(AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
} else { //cglib
return getCglibProxyTargetObject(proxy);
}
} catch (Exception e) {
log.error("获取目标对象{}error",proxy,e);
return proxy;
}
}
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
}
然后获取handler的原生类即可,此方法简单快捷,对原有业务改动不大
解决办法2-不用代理类
一般来说,类似于AbstractDataAuthHandler这种的类,以及它的子类,既然通过这种方式加载了,其实交付给spring托管的意义也不大,因为在其他地方大概率不会再单独用到了。
所以可以通过@Bean的方式自行加载即可
@Configuration
public class TestConfiguration {
@Bean
public ProjectDataAuthHandler projectDataAuthHandler() {
return new ProjectDataAuthHandler();
}
}
@PostConstruct
public void init() {
Map<String, AbstractHandler> abstractApiAuthHandlerMap = context.getBeansOfType(AbstractHandler.class);
for (String beanName : abstractApiAuthHandlerMap.keySet()) {
AbstractHandler handler = abstractApiAuthHandlerMap.get(beanName);
DataAuthHandler authHandler = handler.getClass().getAnnotation(DataAuthHandler.class);
handlerMap.put(authHandler.value(), handler);
}
}
上述方式也是可行的,只不过子类比较多的时候,可能代码量会略多一些