如果Controller里有私有的方法,能成功访问吗?

背景

写代码的时候,复制粘贴的时候,没注意到方法的属性,就导致了Controller里有了一个私有的方法,然后访问这个接口的时候就报了空指针异常,找了好久才找到是这个原因。

来看一个例子

@Service
public class MyService {
    public String hello() {
        return "hello";
    }
}

@Slf4j
@RestController
@RequestMapping("/test")
public class MyController {

    @Autowired
    private MyService myService;

    @GetMapping("/public")
    public Object publicHello() {
        return myService.hello();
    }

    @GetMapping("/protected")
    protected Object protectedHello() {
        return myService.hello();
    }

    @GetMapping("/private")
    private Object privateHello() {
        return myService.hello();
    }
}

@EnableAspectJAutoProxy
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
访问
http://127.0.0.1:8081/test/public 200
http://127.0.0.1:8081/test/protected 200
http://127.0.0.1:8081/test/private 200

如果在这个基础之上再加一个切面:

@Slf4j
@Aspect
@Component
public class MyAspect {

    @Pointcut("execution(* cn.eagle.li.controller..*.*(..))")
    public void controllerSayings() {
    }

    @Before("controllerSayings()")
    public void sayHello() {
        log.info("注解类型前置通知");
    }
}
访问
http://127.0.0.1:8081/test/public 200
http://127.0.0.1:8081/test/protected 200
http://127.0.0.1:8081/test/private 500:报空指针异常,原因是myService为null的

原因

  • public 方法
    如果Controller里有私有的方法,能成功访问吗?
  • protected 方法
    如果Controller里有私有的方法,能成功访问吗?
  • private 方法
    如果Controller里有私有的方法,能成功访问吗?

大致可以看到原因,public方法和protected方法访问的时候,它的类都是真实的类

而private方法是代理的类

cglib代理的锅

Spring Boot 2.0 开始,默认使用的是cglib代理

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
        AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true",
        matchIfMissing = true)
public class AopAutoConfiguration {
    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class",
            havingValue = "false", matchIfMissing = false)
    public static class JdkDynamicAutoProxyConfiguration {
    }

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class",
            havingValue = "true", matchIfMissing = true)
    public static class CglibAutoProxyConfiguration {
    }
}

入口

如果Controller里有私有的方法,能成功访问吗?

如果Controller里有私有的方法,能成功访问吗?

不管public还是private的方法,都是这样执行的。

生成代理类字节码

    public static void main(String[] args) {
        /** 加上这句代码,可以生成代理类的class文件*/
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "org/springframework/cglib");
        SpringApplication.run(MyApplication.class, args);
    }

部分代理类字节码如下:

    protected final Object protectedHello() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            return var10000 != null ? var10000.intercept(this, CGLIB$protectedHello$1$Method, CGLIB$emptyArgs, CGLIB$protectedHello$1$Proxy) : super.protectedHello();
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }

   public final Object publicHello() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            return var10000 != null ? var10000.intercept(this, CGLIB$publicHello$0$Method, CGLIB$emptyArgs, CGLIB$publicHello$0$Proxy) : super.publicHello();
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }

public和protected方法会生成上述的方法,而private方法是不会生成这样的方法

    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
            @Override
        @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();
            try {
                if (this.advised.exposeProxy) {
                    // Make invocation available if necessary.
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }
                // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
                target = targetSource.getTarget();
                Class targetClass = (target != null ? target.getClass() : null);
                List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                Object retVal;
                // Check whether we only have one InvokerInterceptor: that is,
                // no real advice, but just reflective invocation of the target.
                if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                    // We can skip creating a MethodInvocation: just invoke the target directly.
                    // Note that the final invoker must be an InvokerInterceptor, so we know
                    // it does nothing but a reflective operation on the target, and no hot
                    // swapping or fancy proxying.
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = methodProxy.invoke(target, argsToUse);
                }
                else {
                    // We need to create a method invocation...
                    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
                }
                retVal = processReturnType(proxy, target, method, retVal);
                return retVal;
            }
            finally {
                if (target != null && !targetSource.isStatic()) {
                    targetSource.releaseTarget(target);
                }
                if (setProxyContext) {
                    // Restore old proxy.
                    AopContext.setCurrentProxy(oldProxy);
                }
            }
        }
    }

public和protected方法会调用 DynamicAdvisedInterceptor.intercept方法,这里面的 this.advised.getTargetSource()可以获得真实的目标类,这个目标类是注入成功。

换成JDK动态代理呢

增加配置:

spring:
  aop:
    proxy-target-class: false

增加接口:

@RestController
public interface MyControllerInterface {
    @RequestMapping("/hello/public")
    Object publicHello();

    @RequestMapping("/hello/default")
    default Object defaultHello() {
        return "hi default";
    }
}

@Slf4j
@RestController
@RequestMapping("/test")
public class MyController implements MyControllerInterface {

    @Autowired
    public MyService myService;

    @Override
    @GetMapping("/public")
    public Object publicHello() {
        return myService.hello();
    }

    @GetMapping("/protected")
    protected Object protectedHello() {
        return myService.hello();
    }

    @GetMapping("/private")
    private Object privateHello() {
        return myService.hello();
    }
}

MyControllerInterface头上加 @RestController的原因是:

    protected boolean isHandler(Class beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }
http://127.0.0.1:8081/test/public 404
http://127.0.0.1:8081/test/protected 404
http://127.0.0.1:8081/test/private 404

http://127.0.0.1:8081/hello/public 200
http://127.0.0.1:8081/hello/default 200

只能使用接口里的 @RequestMapping,实现类里的不生效

参考

听说SpringAOP 有坑?那就来踩一踩
源码角度深入理解JDK代理与CGLIB代理

Original: https://www.cnblogs.com/eaglelihh/p/16559415.html
Author: eaglelihh
Title: 如果Controller里有私有的方法,能成功访问吗?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/587187/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Mac Java(Springboot2.x)控制谷歌浏览器

    参考 https://blog.csdn.net/zhoukeguai/article/details/113247342 https://blog.csdn.net/weixin…

    Java 2023年5月29日
    081
  • Java基础— 小应用:比较两个数值大小

    在日常生活中,经常会要求比较两个数的大小。于是就想写个博客稍微总结一下。 package com.basic.day02; public class CompareTwoValue…

    Java 2023年6月7日
    066
  • mysql学习笔记-底层原理详解

    前言 我相信每一个程序员都避免不了和数据库打交道,其中Mysql以其轻量、开源成为当下最流行的关系型数据库。Mysql5.0以前以MyISAM作为默认存储引擎,在5.5版本以后,以…

    Java 2023年6月5日
    084
  • Mysql学习

    显示字符集编码 mysql架构 逻辑架构 Client : 提供连接MySQL服务器功能的常用工具集 Server : MySQL实例,真正提供数据存储和数据处理功能的MySQL服…

    Java 2023年6月8日
    080
  • 队列小哥哥喊你来排队了~(自带循环的那种)

    大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦。不过这篇文章呢,还是想跟大家聊一聊数据结构与算法,学校也是大二上才开设了数…

    Java 2023年6月5日
    091
  • 使springAOP生效不一定要加@EnableAspectJAutoProxy注解

    在上篇文章《springAOP和AspectJ有关系吗?如何使用springAOP面向切面编程》中遗留了一个问题,那就是在springboot中使用springAOP需要加@Ena…

    Java 2023年6月9日
    072
  • Java使用正则解决问题

    分析以下需求,并用代码实现(根据描述写匹配手机号和邮箱的正则表达式)1.根据描述写出正则表达式(1)手机号正则:第一位为1第二位为3或4或5或7或8第三~十一位为0~9的其中一个数…

    Java 2023年6月5日
    075
  • win10怎么切换两个桌面 win10切换第二桌面快捷键

    win10系统之中是可以支持用户同时运行多个桌面的毕竟一个桌面根本满足不了我们平时的使用需求,所以可以经常需要多开一个桌面去放置不同的文件,可是并不是所有的小伙伴都知道桌面使用wi…

    Java 2023年6月6日
    097
  • 我觉得 MQ 无用的理由

    不喜欢用 MQ。 如果是同一个系统内的不同模块,可以用数据库表,来传递消息;如果是不同系统间数据接口,可以用 webservice(同步,现在好像是 gRPC 有点热)、SFTP/…

    Java 2023年6月9日
    049
  • 面试官:线程池如何按照core、max、queue的执行循序去执行?(内附详细解析)

    前言 这是一个真实的面试题。 前几天一个朋友在群里分享了他刚刚面试候选者时问的问题: “线程池如何按照core、max、queue的执行循序去执行?”。 我…

    Java 2023年5月30日
    081
  • Java 入门阶段

    Java 帝国的诞生 java 特性和优势 JDK、JRE、JVM 搭建开发环境 HelloWorld IDEA 安装和介绍 C & C+ 1972年C诞生 贴近硬件,运行…

    Java 2023年6月8日
    0180
  • rocketmq之延迟队列(按照18个等级来发送)

    1 启动消费者等待传入的订阅消息 import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import …

    Java 2023年6月5日
    063
  • java读取pdf文件内容

    使用JAVA从PDF中获取文字信息,目前只能读取文字型PDF。图片型PDF尚在研究1.导入Maven依赖 <dependency> <groupid>org…

    Java 2023年6月6日
    079
  • SpringMVC

    SpringMVC 注解收集: -@component 组件 -@service service -@controller contro1ler /*代表这个类会被Spring接管…

    Java 2023年6月8日
    070
  • Python requests, pasel多线程爬取并下载小说

    使用PYTHON语言,用到的外部包有pasel, requests。 逻辑:首先得到该小说所有章节地址,再使用多线程访问链接,得到的内容放入object列表中,最后写入本地文件。 …

    Java 2023年6月9日
    092
  • Nginx入门–从核心配置与动静分离开始

    写在前面 优化我们项目,服务器部署,不仅仅可以是分布式,Nginx一样可以通过动静分离,负载均衡来减轻我们服务器的压力。Nginx的知识链,学习周期相对比较长,博主也是刚刚入门,这…

    Java 2023年6月5日
    064
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球