springAop代理后导致controller中service为null的bug原因

1.现象:

@RequestMapping("/test")
@RestController
public class TestController {

    @Autowired
    private TestService testService;
    
    @GetMapping
    @TestAop
    private String getName(){
        return testService.getName();
    }
}

就是一个简单的controller的类,然后切面为:

@Aspect
@Component
public class AopTest {

    @Before("@annotation(TestAop)")
    public void before(){
        System.out.println("test aop");
    }

}

直接访问返回如下错误:

java.lang.NullPointerException: null
    at com.aop.privatebug.demo.TestController.getName(TestController.java:18) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.23.jar:5.3.23]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.23.jar:5.3.23]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.65.jar:9.0.65]

打断点发现是service为空了。

2,原因查找

  这里controller没有继承故所以采用的cglib的代理方式代理的。所以先设置将cglib代理生成的对象写到指定目录查看生成的代理对象。

  System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\temp");

  

  设置好后打开cglib代理生成的类。这里只copy了部分代码,发现private方法的getName()并没有生成代理的方法。

public class TestController$$EnhancerBySpringCGLIB$$9e3f2bbd extends TestController implements SpringProxy, Advised, Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private MethodInterceptor CGLIB$CALLBACK_1;
    private NoOp CGLIB$CALLBACK_2;
    private Dispatcher CGLIB$CALLBACK_3;
    private Dispatcher CGLIB$CALLBACK_4;
    private MethodInterceptor CGLIB$CALLBACK_5;
    private MethodInterceptor CGLIB$CALLBACK_6;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$equals$0$Method;
    private static final MethodProxy CGLIB$equals$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$toString$1$Method;
    private static final MethodProxy CGLIB$toString$1$Proxy;
    private static final Method CGLIB$hashCode$2$Method;
    private static final MethodProxy CGLIB$hashCode$2$Proxy;
    private static final Method CGLIB$clone$3$Method;
    private static final MethodProxy CGLIB$clone$3$Proxy;

    static void CGLIB$STATICHOOK3() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.aop.privatebug.demo.TestController$$EnhancerBySpringCGLIB$$9e3f2bbd");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$0$Method = var10000[0];
        CGLIB$equals$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$0");
        CGLIB$toString$1$Method = var10000[1];
        CGLIB$toString$1$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$1");
        CGLIB$hashCode$2$Method = var10000[2];
        CGLIB$hashCode$2$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$2");
        CGLIB$clone$3$Method = var10000[3];
        CGLIB$clone$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$3");
    }

    final boolean CGLIB$equals$0(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_5;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_5;
        }

        if (var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$0$Method, new Object[]{var1}, CGLIB$equals$0$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.equals(var1);
        }
    }

查看springmvc触发controller的方式为通过method.invoke()反射的方式触发。所以怀疑可能是cglib生成的代理子类没有注入service,导致直接触发method的反射时,获取代理类中的service没有拿到。故查看spring生成代理类的代码为位置:

在AbstractAutoProxyCreator的postProcessAfterInitialization()方法中调用wrapIfNecessary()中生成的cglib的代理类果然testService没有注入serivce的值。而自动注入的AutowiredAnnotationBeanPostProcessor的执行是在AbstractAutoProxyCreator
之前执行的。

3.结论

  a.导致springAop代理后导致controller中service为null的bug原因的为private修改的方法cglib是不会代理的,而生成的代理类中的成员变量是不会注入spring的值的。所有会导致没有代理的方法,获取不到spring自动注入的值。

   b.代理后的方法为什么就拿的到spring自动注入的值呢?原因如下:

  查看cglib生成的类的代码方法的代码:

  然后methodInterceptor,从上面spring的aop生成代理类的时候发现CGLIB$CALLBACK_0类型为DynamicAdvisedInterceptor类型,然后发现调用时获取了targetSource的target,而target就是spring中上面的未代理前TestController,而TestController经过

AutowiredAnnotationBeanPostProcessor的自动注入过程时有service的,所以在这里调用的时候用target调用反射是获取的到service
 private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
        private final AdvisedSupport advised;

        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        @Nullable
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object oldProxy = null;
            boolean setProxyContext = false;
            Object target = null;
            TargetSource targetSource = this.advised.getTargetSource();

            Object var16;
            try {
                if (this.advised.exposeProxy) {
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }

                target = targetSource.getTarget();
                Class<?> targetClass = target != null ? target.getClass() : null;
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                Object retVal;
                if (chain.isEmpty() && CglibAopProxy.CglibMethodInvocation.isMethodProxyCompatible(method)) {
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = CglibAopProxy.invokeMethod(target, method, argsToUse, methodProxy);
                } else {
                    retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
                }

                retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
                var16 = retVal;
            } finally {
                if (target != null && !targetSource.isStatic()) {
                    targetSource.releaseTarget(target);
                }

                if (setProxyContext) {
                    AopContext.setCurrentProxy(oldProxy);
                }

            }

            return var16;
        }

 

posted @ 2022-10-18 20:59  fantastiLi  阅读(857)  评论(0)    收藏  举报