为什么你写的Controller里,private方法中的bean=null?

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/q258523454/article/details/118118553

目录

1.前言

2.小知识

3.原因分析

4.总结

5.解决方案


1.前言

bean=null的原因有很多种,这篇文章只讨论使用AOP的情况。

出现场景:使用AOP切面后,private方法中bean=null

环境 :Springboot 2.0 

真的是因为AOP无法代理private方法吗?

2.小知识

问:SpringBoot默认AOP代理方式是什么?

答:CGLIB

证明:

第一种方式:我们可以通过@EnableAutoConfiguration这个注解找到SpringBoot去AOP启动类

包路径: org.springframework.boot.autoconfigure.aop.AopAutoConfiguration


@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 {

   }

}

上面几个参数大概意思如下:


matchIfMissing = true : 可以没有该配置项和属性值(此时spring.factories来默认实例化),但非空时候必须等于havingValue,否则不实例化

matchIfMissing = false: 必须有该配置项和属性值,且必须等于havingValue, 否则不实例化, 默认是 false

havingValue表示 当 prefix.value 相等的时候, 配置才会实例化  默认 havingValue = ""

由此看出SpringBoot 2.0 以后默认AOP用的Cglib代理,具体项目会切换两种代理模式。

默认使用Cglib代理,当配置修改为JDK代理时且预代理对象实现接口时,Spring就会用JDK的动态代理

注意:如果配置是jdk代理,类必须有实现接口,否则仍然为Cglib代理

第二种方式:直接通过在org.springframework.web.method.support.InvocableHandlerMethod类中的方法法doInvoke()设置断点查看当前执行AOP用的什么代理模式

我们可以看到默认就是CGLIB 。

第三种方式:

直接断点到 ProxyFactory类的getProxy()

/**
 * Create a new proxy according to the settings in this factory.
 * <p>Can be called repeatedly. Effect will vary if we've added
 * or removed interfaces. Can add and remove interceptors.
 * <p>Uses the given class loader (if necessary for proxy creation).
 * @param classLoader the class loader to create the proxy with
 * (or {@code null} for the low-level proxy facility's default)
 * @return the proxy object
 */
public Object getProxy(@Nullable ClassLoader classLoader) {
 return createAopProxy().getProxy(classLoader);
}

如果是 Cglib代理 执行 CglibAopProxy 类中的 getProxy()
如果是 jdk代理 执行 JdkDynamicAopProxy 类中的 getProxy()
以Cglib代理为例,查看 CglibAopProxy

通过代码看出,CGLIB在做初始化的时候本身是没有bean属性注入的

3. bean=null 原因分析

定义一个简单的AOP切面

@Aspect

@Component

@Slf4j

public class AopTest {

    @Pointcut("execution(java.lang.String *..controller..*Controller.*(..))")

    public void controller() {

    }


    @Before("controller()")

    public void before(JoinPoint joinPoint) {

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();

        log.info("Controller切面 @Before:URL = {} ", httpServletRequest.getRequestURI());

    }


    @After(value = "controller()")

    public void after(JoinPoint joinPoint) {

        log.info("Controller切面-@After");

    }


    @AfterReturning(pointcut = "controller()", returning = "result")

    public void afterreturning(JoinPoint joinPoint, Object result) {

        log.info("Controller切面-@AfterReturning:{}", JSON.toJSONString(result));

    }

}

"execution(java.lang.String *..controller..*Controller.*(..))"表示AOP拦截的是所有controller包下面以_Controller结尾的类且方法返回参数必须是String。

AOP生效日志:

我们看一下在使用bean的情况下,public和private方法使用bean有什么区别。

定义一个Servive和Impl

public interface TestService {

    void print();

}
@Service

@Slf4j

public class TestServiceImpl implements TestService {

    @Override

    public void print() {

        log.info("TestService print.");

    }

}

写一个可以被AOP拦截的Controller和方法

@RestController

@Slf4j

public class TestController {


    @Autowired

    private TestService testService;


    @PostConstruct

    public void init() {

        log.info("testService bean:" + testService);

    }


    @GetMapping(value = "/publicPrint")

    public String publicPrint() {

        testService.print();

        return "ok";

    }


    @GetMapping(value = "/privatePrint")

    private String privatePrint() {

        testService.print();

        return "ok";

    }

}

启动项目后,会发现启日志里面有一行这样的日志打印:

testService bean:com.aop.service.impl.TestServiceImpl@410f8424

这说明bean是可以被正常被注入的。

重点来了,先请求public方法,正常返回,bean使用没问题。

再看private方法,直接报错 java.lang.NullPointerException: null (注意:这个controller是被AOP拦截的,普通Controller,如果没有AOP,private方法中bean是正常的)

我们都知道CGLIB的代理方式是setSuperClass,是不会代理父类的private方法的。也就是说AOP无法代理private,那么到底是不是这原因导致bean=null呢?继续往下看。

我们通过org.springframework.web.method.support.InvocableHandlerMethod中的doInvoke断点查看这个对象,我们可以直接看到代理后的Controller中的private方法中的bean普通(没有被代理)Controller中的private方法中使用的Bean不一样。第一张图是展示的代理对象InvocableHandlerMethod中的bean,如下图所示。通过AOP的private和public方法断点,testService都是null。

下图所示展示的是实例对象本身,所以这个private方法内使用的bean不是null,可以正常请求。

现在我们可以确定的是:定义在切面AOP下的Controller类会走代理,不管private还是public方法bean都是null值。

方法为private的时候,由于没有被AOP拦截,它继续使用代理类,而代理类中的 bean=null(如前面的图所示)。

因此我们可以判定public方法在AOP过程中有执行其他操作,不然bean的属性也是null,调试模式继续走,你会发现 被AOP拦截的 controller 中 private方法调用路线是这样的:

InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl

先调用 org.springframework.web.method.support.InvocableHandlerMethod 类 doInvoke 方法

	/**
	 * Invoke the handler method with the given argument values.
	 */
	protected Object doInvoke(Object... args) throws Exception {
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
			return getBridgedMethod().invoke(getBean(), args);
		}
	...

再调用 java.lang.reflect.Method 类 Invoke 方法

    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

最后调用  sun.reflect.DelegatingMethodAccessorImpl 类 Invoke 方法

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }

 而 被AOP拦截的 controller 中 public / protected方法的调用路线,除了

InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl

 之外还有2个关键类:

CglibAopProxy 类下 静态类 DynamicAdvisedInterceptor
CglibAopProxy 类下 静态类 invokeJoinpoint

 也就是说 public / protected 的调用路线为:

InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl ——> CglibAopProxy(DynamicAdvisedInterceptor) ——> CglibAopProxy(invokeJoinpoint)

由此看来,public 方法还执行了 CglibAopProxy

org.springframework.aop.framework.CglibAopProxy类中有一个静态内部类CglibMethodInvocation,其中有一个方法invokeJoinpoint()是这样写的

/**
* Gives a marginal performance improvement versus using reflection to
* invoke the target when invoking public methods.
*/
@Override
protected Object invokeJoinpoint() throws Throwable {
	if (this.publicMethod) {
	   return this.methodProxy.invoke(this.target, this.arguments);
	}
	else {
	   return super.invokeJoinpoint();
	}
}

bean就是在这个代理类中进行“属性注入”。

public 方法——执行 invoke(this.target, this.arguments)

protected方法——执行 super.invokeJoinpoint()

CglibAopProxy 下执行的时候,上面无论哪个方法都会用实际对象来进行反射调用,实际对象的bean属性值我们之前已经看到了,是已经注入的。因此public方法的bean会重新赋值,即:用实际对象来代替原有的代理对象。

4.总结

private方法中bean=null的根本原因并不是private方法无法被代理,我们按照public方法的调试,public方法在InvocableHandlerMethod中显示的bean属性也是null。

根本原因是:

private 没有被真正的代理类拦截—— 虽然代理类 InvocableHandlerMethod中 private 方法执行了doInvoke,但是并没有被 CglibAopProxy 拦截,因此private方法无法获取被代理目标对象,也就无法获取注入的bean属性

5.解决方案

方法一:直接改private为public

方法二:SpringContextHolder工具类SpringContextHolder.getBean(Bean.class)显式获取。

public class SpringContextHolder implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);

    private static ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口, 注入Context到静态变量中.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        logger.info("ServletContextUtil is init");
        SpringContextHolder.applicationContext = applicationContext;
    }

    /**
     * 获取静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        assertContextInjected();
        return applicationContext;
    }


    /**
     * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> requiredType) {
        assertContextInjected();
        return applicationContext.getBean(requiredType);
    }


    public static <T> Map<String, T> getBeansOfType(Class<T> requiredType) {
        assertContextInjected();
        return applicationContext.getBeansOfType(requiredType);
    }


    /**
     * 检查ApplicationContext不为空.
     */
    private static void assertContextInjected() {
        if (applicationContext == null) {
            throw new IllegalStateException("Application Context Have Not Injection");
        }
    }

    /**
     * 清除SpringContextHolder中的ApplicationContext为Null.
     */
    public static void clearHolder() {
        applicationContext = null;
    }

}

  • 19
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
好的,我可以为您提供一个基本的Java代码示例,用于设计员工信息页面的Controller、Service、DAO和Bean。请注意,这只是一个示例,实际应用可能需要根据具体需求进行修改和优化。以下是示例代码: Employee.javaBean): ```java public class Employee { private int id; private String name; private int age; private String department; // 构造函数、getter和setter方法省略 } ``` EmployeeDao.java(DAO): ```java public interface EmployeeDao { List<Employee> getAllEmployees(); Employee getEmployeeById(int id); void addEmployee(Employee employee); void updateEmployee(Employee employee); void deleteEmployee(int id); } ``` EmployeeDaoImpl.java(DAO实现): ```java public class EmployeeDaoImpl implements EmployeeDao { private List<Employee> employeeList = new ArrayList<>(); // 假设员工数据存储在List @Override public List<Employee> getAllEmployees() { return employeeList; } @Override public Employee getEmployeeById(int id) { for (Employee employee : employeeList) { if (employee.getId() == id) { return employee; } } return null; } @Override public void addEmployee(Employee employee) { employeeList.add(employee); } @Override public void updateEmployee(Employee employee) { for (Employee emp : employeeList) { if (emp.getId() == employee.getId()) { emp.setName(employee.getName()); emp.setAge(employee.getAge()); emp.setDepartment(employee.getDepartment()); break; } } } @Override public void deleteEmployee(int id) { Iterator<Employee> iterator = employeeList.iterator(); while (iterator.hasNext()) { Employee employee = iterator.next(); if (employee.getId() == id) { iterator.remove(); break; } } } } ``` EmployeeService.java(Service): ```java public interface EmployeeService { List<Employee> getAllEmployees(); Employee getEmployeeById(int id); void addEmployee(Employee employee); void updateEmployee(Employee employee); void deleteEmployee(int id); } ``` EmployeeServiceImpl.java(Service实现): ```java public class EmployeeServiceImpl implements EmployeeService { private EmployeeDao employeeDao = new EmployeeDaoImpl(); @Override public List<Employee> getAllEmployees() { return employeeDao.getAllEmployees(); } @Override public Employee getEmployeeById(int id) { return employeeDao.getEmployeeById(id); } @Override public void addEmployee(Employee employee) { employeeDao.addEmployee(employee); } @Override public void updateEmployee(Employee employee) { employeeDao.updateEmployee(employee); } @Override public void deleteEmployee(int id) { employeeDao.deleteEmployee(id); } } ``` EmployeeController.javaController): ```java @RestController @RequestMapping("/employee") public class EmployeeController { private EmployeeService employeeService = new EmployeeServiceImpl(); @GetMapping("/") public List<Employee> getAllEmployees() { return employeeService.getAllEmployees(); } @GetMapping("/{id}") public Employee getEmployeeById(@PathVariable int id) { return employeeService.getEmployeeById(id); } @PostMapping("/") public void addEmployee(@RequestBody Employee employee) { employeeService.addEmployee(employee); } @PutMapping("/") public void updateEmployee(@RequestBody Employee employee) { employeeService.updateEmployee(employee); } @DeleteMapping("/{id}") public void deleteEmployee(@PathVariable int id) { employeeService.deleteEmployee(id); } } ``` 以上就是一个基本的Java代码示例,用于设计员工信息页面的Controller、Service、DAO和Bean。当然,这只是一个简单的示例,实际应用还需要考虑更多的因素,如数据校验、异常处理等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值