• springboot反射执行private方法@Autowired字段为空


    描述

    业务代码写完之后,懒得写mock代码,想直接暴露个接口测一下。但是因为目标方法是个private方法,所以不能直接用@Autowired注入类实例然后调用方法,便想到用反射来调用方法。然后,不试不知道,一试吓一跳。代码看着没问题,但是执行的时候报空指针异常(私有方法中有调用注入的其他service方法,这个service是空)

    错误复现

    controller

    package com.yichen.casetest.controller;
    // ... 其他import
    @Controller
    @RequestMapping("/test")
    @Slf4j
    public class TestController {
    
    	@Autowired
        private ReflectServiceImpl reflectService;
    
        @PostMapping("/reflectTest")
        @ResponseBody
        public Object reflectTest(@RequestParam String name, @RequestParam String age){
            try {
                Method method = ReflectServiceImpl.class.getDeclaredMethod("reflectTest", String.class, String.class);
                return method.invoke(reflectService, name, age);
    
            }
            catch (Exception e){
                log.error("reflectTest出现错误{}", e.getMessage(), e);
            }
            return "error";
        }
    
        @PostMapping("/reflectTest1")
        @ResponseBody
        public Object reflectTest1(@RequestParam String name, @RequestParam String age){
            try {
    
    
                Method method = ReflectServiceImpl.class.getDeclaredMethod("reflectTest1", String.class, String.class);
                method.setAccessible(true);
                return method.invoke(reflectService, name, age);
    
            }
            catch (Exception e){
                log.error("reflectTest出现错误{}", e.getMessage(), e);
            }
            return "error";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    service

    ReflectServiceImpl

    package com.yichen.casetest.test.service.reflect.impl;
    
    import com.yichen.casetest.test.service.reflect.ReflectCallService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    @Slf4j
    public class ReflectServiceImpl extends AbstractReflectService {
    
        @Autowired
        private ReflectCallService reflectCallService;
    
        public String reflectTest(String name, String age){
            String s = name + "-" + age;
            log.info("==> {} ==> ???", s);
            return reflectCallService.getName();
        }
    
        private String reflectTest1(String name, String age){
            String s = name + "-" + age;
            log.info("==> {} ==> ???", s);
            return reflectCallService.getName();
        }
    
        @Override
        public String getCombineData(String name, String age){
            String s = name + "-" + age;
            log.info("==> {}", s);
            return reflectCallService.getName();
        }
    
        @Override
        public String addressFrom() {
            rainNow();
            return reflectCallService.getName();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    ReflectCallService

    package com.yichen.casetest.test.service.reflect;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ReflectCallService {
    
        @Autowired
        private TransactionService transactionService;
    
    
        public String getName(){
            transactionService.save();
            return "shanliang";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    service 层切面

    package com.yichen.casetest.aspect;
    
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    
    @Aspect
    @Component
    @Slf4j
    public class LogAspect {
    
        @Pointcut("execution(* com.yichen.casetest.test.service..*.*(..))")
    //    @Pointcut("execution(* com.yichen.casetest.test.service.reflect.impl.ReflectServiceImpl.*(..))")
        public void logAspect() {
    
        }
    
        @Before("logAspect()")
        public void before(JoinPoint joinPoint){
            log.info("{} logAspect before", joinPoint.getTarget().getClass().getName());
        }
    
        @After("logAspect()")
        public void after(JoinPoint joinPoint){
            log.info(" {} logAspect after", joinPoint.getTarget().getClass().getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    debug 结果图

    调用 reflectTest 方法(public反射)

    public方法反射

    可以看到,AutoWired字段是有值的,而是是一个cglib代理。

    调用 reflectTest1方法(private反射)

    私有方法反射

    这里可以看到,AutoWired字段是个null

    分析

    如果把LogAspect注销,则privatepublic都可以通过反射执行。那么问题就是出在cglib代理身上了。具体看一下cglib代理的描述:

    动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

    可以看到它是通过生成子类的方法来创建代理。而java中子类是不继承父类的private方法的。这一点就是导致上面问题的原因。具体点说,因为启用了cglib代理,所以类的属性都通过代理绑定了,实际属性字段是null
    在这里插入图片描述
    如果是非private,以及非final修饰的方法,都会通过代理最终调用实际对象,而实际对象的@autowired字段是有值的。但如果是privatefinal修饰的方法,则会直接用属性,而此时属性为null,因为被代理了。。

    参考

    Spring AOP中private(踩坑)实践总结

  • 相关阅读:
    中创算力九月员工生日会 | 愿尔祯祥,岁岁如常
    ⑧【MySQL】数据库查询:内连接、外连接、自连接、子查询、多表查询
    Redis网络相关的结构体 和 reactor模式
    日常学习记录随笔-redis实战
    Disco Diffusion 快速入门
    《吐血整理》高级系列教程-吃透Fiddler抓包教程(35)-Fiddler如何抓取微信小程序的包-下篇
    SNAT和DNAT
    锐捷——Telent登录时使用 用户名及密码登陆路由器
    回文数-第14届蓝桥杯Scratch选拔赛真题
    微信小程序-自定义组件checkbox
  • 原文地址:https://blog.csdn.net/weixin_42241455/article/details/128049603