controller的请求方法为什么不可以用private修饰

1问题描述

同事开发中遇到这样一个问题,就是controller的请求方法用private修饰,会导致controller通过@Autowired或者@Resource注入的属性为null,在进行请求调用的时候,用到注入的属性就会报空指针

2问题复现

我们先来尝试复现问题,写个demo,尝试调用,结果如下:

可以看到是private方法,怎么testService注入不是为null?这就疑问了,难道搞错了?

再细看公司项目,发现有个全局的日志切面,对controller做了动态代理

我们加下切面再看看

再尝试调用,结果如下:

 确实service为null,问题复现出来了

3问题分析

3.1初步分析

既然是加了动态代理才会出现问题,说明是动态代理对private不生效

细看一下controller被代理后是怎样的,如下图:

 可以看到是做了cglib代理

但是这就疑问了,不是说代理对private方法不生效吗?怎么代理对象还能调用到我们目标业务方法test?到底controller的代理是怎么做的?源码是怎样的?

当对现象理解不通时,就只有查看源码,才能捋清楚了..下面开始深入源码级别分析

3.2深入源码分析

第一步:了解cglib代理的原理

(此处不对cglib的整体源码流程做详细分析,需要的自行学习)

cglib的代理示例:

//要被代理的TestController 
public class TestController {
    //send是public方法,用于做对比
    public void send() {
        System.out.println("send");
}

//send2是private方法,用于测试问题
    private void send2() {
        System.out.println("send2");
    }
}

//代理拦截器MyMethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置处理");
        Object object = methodProxy.invokeSuper(o, args); //调用目标业务方法
        System.out.println("后置处理");
        return object;
    }
}

//代理测试CglibTest
public class CglibTest {
    public static void main(String[] args) throws Exception{
        //打印cglib生成的代理类class
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\cglibProxyClass");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TestController.class);//设置被代理类
        enhancer.setCallback(new MyMethodInterceptor());//设置拦截器
        TestController proxyService = (TestController) enhancer.create();//创建代理类

        //反射调用public修饰的send方法
        Method sendMethod = TestController.class.getDeclaredMethod("send");
        sendMethod.setAccessible(true);
        sendMethod.invoke(proxyService);

        //反射调用private修饰的send2方法
        Method send2Method = TestController.class.getDeclaredMethod("send2");
        send2Method.setAccessible(true);
        send2Method.invoke(proxyService);
    }
}

分析:

准备了2个方法调用测试,send2是private方法,用于测试问题,send是public方法,用于做对比。CglibTest 测试类生成代理对象proxyService后,通过反射调用send和send2方法,因为springmvc是通过反射调用的controller方法,所以为了更好的复现和分析问题,这里也用反射调用。

查看生成的代理class

在调式send和send2方法调用前,我们先单纯执行生成代理方法(如下图)

public class CglibTest {
    public static void main(String[] args) throws Exception{
        //打印cglib生成的代理类class
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\cglibProxyClass");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TestController.class);//设置被代理类
        enhancer.setCallback(new MyMethodInterceptor());//设置拦截器
        TestController proxyService = (TestController) enhancer.create();//创建代理类
    }
}

通过DebuggingClassWriter打印cglib生成的代理字节码,这里放在了D:\\cglibProxyClass目录下

 我们看第二个字节码类

//生成的代理类是继承了要被代理的TestController
public class TestController$$EnhancerByCGLIB$$8ee56bef extends TestController implements Factory {

    //动态代理执行回调的拦截器(就是MyMethodInterceptor)
    private MethodInterceptor CGLIB$CALLBACK_0;

    //拦截器中调用methodProxy.invokeSuper(o, args) 会调到这个方法
    final void CGLIB$send$0() {
        super.send(); //调用父类方法
    }

    //代理类重写父类TestController的send方法
    public final void send() {
        //获取拦截器
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            //如果拦截器不为空,则执行拦截器逻辑
            var10000.intercept(this, CGLIB$send$0$Method, CGLIB$emptyArgs, CGLIB$send$0$Proxy);
        } else {
            //如果拦截器为空,则直接调用父类方法
            super.send();
        }
    }
}

分析:

  • 可以看到代理类继承了要被代理的TestController,确实是生成了代理子类
  • 代理类重写了父类的方法send,所以在用代理对象调用send方法时候,实际调用就不是父类的send方法了,而是代理类的send方法,接着代理类的send方法会回到拦截器的intercept方法。整个流程就比较清晰了,这也明白了cglib是如何回调拦截器了
  • 重点关注:没有重写send2方法,这也看出了没法代理private方法
  • 再看拦截器的逻辑,当回调到拦截器之后,可以做前置处理和后置处理,当然中间就是通过methodProxy.invokeSuper(o, args)调用父类的send方法(如下图)

       

        methodProxy.invokeSuper(o, args)具体的调用源码不再细看,主要的逻辑就是cglib通过标志位的方式,回调到上面源码看的 CGLIB$send$0方法(如下图),CGLIB$send$0里面就通过super.send()来调用到父类的send的类方法

        ​

 

源码的调用逻辑大致就清楚了,总结一下调用流程:

==》代理对象调用send方法(实际调用的是代理对象的send方法)

==》send方法里面回调了拦截器的intercept方法

==》intercept方法调用了methodProxy.invokeSuper方法

==》methodProxy.invokeSuper方法里面通过标志位调用到了CGLIB$send$0方法

==》CGLIB$send$0方法通过super.send()调用到了父类send方法

debug调用1:看send方法的调用

1生成代理对象

 分析:断点打在调用send方法的地方,这里只是生成代理对象,还没开始调用send方法

2然后调用到代理类的send方法(此处debug不了,直接看源码解释)

 分析:send方法里面会调用到拦截器

3然后调用到拦截器的intercept方法

 分析:这里调用了methodProxy.invokeSuper(o, args),会回调到父类的send方法

4然后看父类send方法

 分析:

  • public修饰的send,确实经过了代理增强逻辑,又回调了目标send方法
  • 注意看具体调用的对象实例是代理类实例

debug调用2:看send2方法的调用

1生成代理对象

 分析:

  • 断点打在调用send2方法的地方,这里只是生成代理对象,还没开始调用send2方法

2然后看send2方法的调用

这里就有意思了,这个调用,调到哪去?我们上面在看代理类class的时候,是明确知道代理是没有重写send2方法的(如下图)

 那调到哪去呢?????

当代理子类没有重写send2方法的时候,就是直接调用到父类的send2方法了

3然后看父类的send2方法

 分析:

  • 这里就有意思了,首先通过反射,能不能调用到父类的private方法?答案是:能。
  • 虽然调用到了父类的方法,但是少了中间增强逻辑的环节呀。所以cglib代理对private方法的代理情况是:通过反射也是能调用到private方法,但是没法走增强逻辑,实际就是没法代理。

第二步:了解cglib在spring中应用做法

Cglib在spring中应用的做法跟上面cglib的原生做法大体是一致的。

不同的点是spring在MethodInterceptor拦截器中回调父类方法的方式跟上面的不一样。

我们知道,上面回调父类方法是通过代理对象调用super.send()方法这种方式

那spring是怎么做的呢?

我们知道,spring在项目启动的时候,会创建TestController的bean对象,因为我们这里有LogAspect切面对TestController做代理,那spring会在实例化TestController之后,又生成TestController的代理对象,在生成代理对应的时候,会把原本的TestController对象放到MethodInterceptor里面,类似如下这种写法:

(注:只是大致表达spring的写法,实际spring的写法肯定是更抽象,封装更多)

public class MyMethodInterceptor implements MethodInterceptor {
    
    private TestController testController;

    public MyMethodInterceptor(TestController testController){
        this.testController = testController;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
                                                                         throws Throwable {
        System.out.println("前置处理");
        Object object = method.invoke(testController, args); //通过反射调用目标业务方法
        System.out.println("后置处理");
        return object;
    }
}

public static void main(String[] args) throws Exception{
        TestController testController = new TestController();//原本实例对象

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TestController.class);//设置被代理类

        //设置拦截器,把testController放到MyMethodInterceptor里面
        enhancer.setCallback(new MyMethodInterceptor(testController));
        
        TestController proxyController = (TestController) enhancer.create();//创建代理类
}

通过上面示例可以看到,把testController 放到MyMethodInterceptor 里面,在intercept回调业务方法的时候,就通过testController 实例反射调用目标方法method.invoke(testController, args),注意,这种调用方式,调用目标方法的对象就不是testController 的代理对象了,而是testController 原生对象,这个是spring和cglib代理调用目标方法的很大的区别。

思考:为什么spring不直接复用cglib原生的调用目标方法的方式呢?Cglib的代理对象是目标类的子类,调用目标方法只需要用super.send()就可以了,这么便捷为啥不沿用呢?

答:

先说沿用会出现的问题:

如果沿用super.send()的方式,那么调用的时候,TestController 注入的属性都会为null,这样就会报空指针

 原因分析:

Spring在实例化TestController 的时候,是先做了属性填充,把TestController 依赖的testService填充进来,然后再做初始化,在初始化后置处理器里生成TestController 的代理对象,所以TestController的代理对象是对TestController 做了一层封装,代理对象是没有testService属性的

 

 

这样就明白了spring非要用原生对象调用目标方法的用苦良心了。

第三步:基于上面的背景进一步分析问题

好了,上面对动态代理做了一大堆的原理介绍,下面就基于这些知识来分析我们的业务问题。

这里从源码分析,要结合springmvc调用controller的源码

分正常情况和异常情况剖析

正常情况:controller方法为public

1 请求调用,走到springmvc调用controller

 分析:

  • 这里是springmvc通过反射调用controller
  • 这里的this.getBean(),也就是反射调用bean是代理对象

         

  • 既然走的是代理对象,那肯定不是直接就调用到目标方法,这里是正常情况,方法是用public修饰的,所以根据上面的知识,应该是走到了拦截器的intercept里面。在spring里面,cglib的拦截器是DynamicAdvisedInterceptor

2然后看DynamicAdvisedInterceptor的intercept方法

 

 分析:根据上面的spring知识,这里会通过TestController实例,通过反射调用到目标方法

3然后看目标方法

 分析:调用到目标方法的实例是TestController原生对象,TestService也是有值的,符合预期,没问题

异常情况:controller方法为private

1 请求调用,走到springmvc调用controller

 分析:

  • 这里是springmvc通过反射调用controller
  • 这里的this.getBean(),也就是反射调用bean是代理对象

        

  • 看着没啥毛病,好像跟正常情况一样呀。仔细一想,结合上面的知识点,就会发现,cglib是没代理private方法的,所以这里没法像上面正常情况一样走到拦截器里面,所以这里就是直接调用到了目标方法

2然后看目标方法

 分析:

  • 有了上面知识的铺垫,我们再次看到这个问题场景,已经没有疑问了。
  • 虽然代理对象也能调用到private方法,但是实际走的只是单纯的反射调用,中间没有经过增强逻辑的处理
  • 通过上面spring的知识可以知道,如果经过中间增强逻辑的处理,实际调用到目标方法的对象就不会是代理对象,而是原生对象
  • 这里没有经过增强逻辑的处理,所以实际调用到目标方法的对象就是代理对象,通过上面的知识又可以知道,代理对象的testSerivce是没有赋值的,所以这里testSerivce为null,就没有疑问了。

至此,问题就分析清楚了........

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值