业务代码写完之后,懒得写
mock
代码,想直接暴露个接口测一下。但是因为目标方法是个private
方法,所以不能直接用@Autowired
注入类实例然后调用方法,便想到用反射来调用方法。然后,不试不知道,一试吓一跳。代码看着没问题,但是执行的时候报空指针异常(私有方法中有调用注入的其他service
方法,这个service
是空)
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";
}
}
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();
}
}
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";
}
}
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());
}
}
可以看到,
AutoWired
字段是有值的,而是是一个cglib
代理。
这里可以看到,
AutoWired
字段是个null
如果把
LogAspect
注销,则private
和public
都可以通过反射执行。那么问题就是出在cglib
代理身上了。具体看一下cglib
代理的描述:
动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
可以看到它是通过生成子类的方法来创建代理。而
java
中子类是不继承父类的private
方法的。这一点就是导致上面问题的原因。具体点说,因为启用了cglib
代理,所以类的属性都通过代理绑定了,实际属性字段是null
如果是非private
,以及非final
修饰的方法,都会通过代理最终调用实际对象,而实际对象的@autowired
字段是有值的。但如果是private
、final
修饰的方法,则会直接用属性,而此时属性为null
,因为被代理了。。