点击上方名片关注我,为你带来更多踩坑案例
引导
废话不多说,本文从现象-原因-解决办法三个方面来简述
解决办法有多种,大家根据自己的情况自行选择
想看解决办法的直接拉到最下面
现象
使用springboot,在执行类似如下代码的时候
AbstractDataAuthHandler是一个抽象类
类下面有几个标有DataAuthHandler注解的子类,分别实现了不同的功能
想把注解中指定的value作为key,handler作为value,来实现一个简单的根据不同的value调用不同处理类的
@PostConstructpublic void init() {Map 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 { //cglibreturn 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 {@Beanpublic ProjectDataAuthHandler projectDataAuthHandler() {return new ProjectDataAuthHandler();}
}
@PostConstructpublic void init() {Map 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);}}
上述方式也是可行的,只不过子类比较多的时候,可能代码量会略多一些