所有分类
  • 所有分类
  • 未分类

Spring–BeanPostProcessor无法使用AOP–原因/解决方案

简介

本文介绍BeanPostProcessor为什么无法使用AOP以及其解决方案。

原因

简述

BeanPostProcessor以及依赖的bean很有可能无法使用AOP。

此时,会有类似这样的打印信息:trationDelegate$BeanPostProcessorChecker : Bean ‘myBean’ of type [com.example.processor.MyBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

源码位置

这个是由BeanPostProcessorChecker类打印出来的,在源码中搜索它,发现此类是PostProcessorRegistrationDelegate的静态内部类。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
            this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
        if (logger.isInfoEnabled()) {
            logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() +
                    "] is not eligible for getting processed by all BeanPostProcessors " +
                    "(for example: not eligible for auto-proxying)");
        }
    }
    return bean;
}

打印此日志的原因

若注册到beanFactory的BeanPostProcessor的数量少于总的BeanPostProcessor的数量。即:有其他后置处理器还没准备好,这个bean(此处是myBean)就被实例化了。这个bean被实例化的原因一般是:BeanPostProcessor的实现类引用了这个bean,导致这个bean的实例化。

官网文档

Spring官方文档的原话:

BeanPostProcessor instances and AOP auto-proxying

Classes that implement the BeanPostProcessor interface are special and are treated differently by the container. All BeanPostProcessor instances and beans that they directly reference are instantiated on startup, as part of the special startup phase of the ApplicationContext. Next, all BeanPostProcessor instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessor instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.

For any such bean, you should see an informational log message: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

翻译:

实现了BeanPostProcessor 接口的类是特殊的,它们会被容器不同地对待。所有的BeanPostProcessor 实例和它们直接引用的beans在启动时被实例化,它们作为ApplicationContext的特殊启动阶段的一部分。然后,所有的BeanPostProcessor 实例都被以一个排序方式注册而且被应用到下一步的beans中。因为Spring的AOP自动代理是通过实现BeanPostProcessor接口来做的,所以BeanPostProcessor的实现类和它们直接引用的bean不满足AOP自动代理的条件,因此,它们无法被织入到AOP中

对于这些bean,你会看到一些INFO级别的信息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

问题复现

代码

BeanPostProcessor实现类

刻意实现Ordered接口,模拟MyProcessor的初始化早于其他BeanPostProcessor实现类的情况,否则,不易复现。本处我测试时,将其指定为最高优先级或者最低优先级,效果是一样的。优先级见:Spring-BeanPostProcessor-作用/介绍 – 自学精灵

另外,此处必须实现Ordered接口才能指明顺序,如果使用@Order是无效的。猜测:@Order也是通过实现BeanPostProcessor接口来做的,所以,也无效。

package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class MyProcessor implements BeanPostProcessor, Ordered {
    @Autowired
    private MyBean myBean;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyBean) {
            System.out.println("postProcessBeforeInitialization==> " + "This is MyBean");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyBean) {
            System.out.println("postProcessAfterInitialization==> " + "This is MyBean");
        }
        return bean;
    }

    // 此方法用来测试AOP,作为切点
    public void testAOP() {
        System.out.println("testAOP方法(MyProcessor)");
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

Bean

package com.example.processor;

import org.springframework.stereotype.Component;

@Component
public class MyBean {
    // 此方法用来测试AOP,用作切点
    public void testAOP() {
        System.out.println("testAOP方法(MyBean)");
    }
}

切面

package com.example.processor;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class BeanPostProcessorAspect {
    
	// 此方法织入PostBean的testAOP方法
    @Before("execution(* com.example.processor.MyProcessor.testAOP(..))")
    public void before() {
        System.out.println("before MyProcessor#testAOP");
    }

    // 此方法织入MyBean的testAOP方法
    @Before("execution(* com.example.processor.MyBean.testAOP(..))")
    public void before2() {
        System.out.println("before MyBean#testAOP");
    }
}

ApplicationContextHolder工具类

package com.example.processor;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;
 
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        ApplicationContextHolder.context = context;
    }
 
    public static ApplicationContext getContext() {
        return context;
    }
}

测试类

package com.example.controller;

import com.example.processor.MyBean;
import com.example.processor.MyProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    MyProcessor myProcessor;

    @Autowired
    MyBean myBean;

    @GetMapping("/test1")
    public String test1() {
        myProcessor.testAOP();
        myBean.testAOP();

        return "test1 success";
    }

    // @GetMapping("/test1")
    // public String test1() {
    //     MyProcessor myProcessor = ApplicationContextHolder.getContext().getBean(MyProcessor.class);
    //     myProcessor.testAOP();
    //     MyBean myBean = ApplicationContextHolder.getContext().getBean(MyBean.class);
    //     myBean.testAOP();
    //     return "test1 success";
    // }
}

测试

1.复现无法AOP的现象

启动:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2021-03-06 12:23:36.622  INFO 14368 --- [           main] com.example.DemoApplication              : Starting DemoApplication on DESKTOP-QI6B9ME with PID 14368 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot)
2021-03-06 12:23:36.625  INFO 14368 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2021-03-06 12:23:37.237  INFO 14368 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'myBean' of type [com.example.processor.MyBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-06 12:23:37.492  INFO 14368 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-03-06 12:23:37.500  INFO 14368 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-03-06 12:23:37.500  INFO 14368 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.35]
2021-03-06 12:23:37.590  INFO 14368 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-03-06 12:23:37.590  INFO 14368 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 927 ms
2021-03-06 12:23:37.718  INFO 14368 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-03-06 12:23:37.847  INFO 14368 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-03-06 12:23:37.855  INFO 14368 --- [           main] com.example.DemoApplication              : Started DemoApplication in 1.553 seconds (JVM running for 2.454)

访问:http://localhost:8080/test1

结果:(BeanPostProcessor实现类注入的普通bean和BeanPostProcessor实现类都未被AOP)说明:使用Controller中的第二种方法也是同样的结果。 

testAOP方法(MyProcessor)
testAOP方法(MyBean)

2.去掉BeanPostProcessor即可AOP

将 BeanPostProcessor实现类的implements BeanPostProcessor去掉,并去掉其覆写的方法。(其他都不变,比如:仍然实现Ordered接口)

结果为:(BeanPostProcessor实现类注入的普通bean和BeanPostProcessor实现类都被AOP)

before MyProcessor#testAOP
testAOP方法(MyProcessor)
before MyBean#testAOP
testAOP方法(MyBean)

源码追踪

本处追踪上边“1.复现无法AOP的现象”的代码,入口:MyBean被实例化的源码

bean的实例化入口都是:AbstractBeanFactory#doGetBean,在这个地方打个条件断点:

启动项目,发现这个断点到达了三次,也就是说,有三个地方想实例化MyBean。

第一次:BeanPostProcessor实现类引用了MyBean

第二次:HelloController引用了MyBean

第三次:MyBean本身加入容器,当然要去实例化它

解决方案

方案1:延迟初始化

代码

BeanPostProcessor的实现类中这样引入MyBean:

@Lazy
@Autowired
private MyBean myBean;

测试

启动时:

postProcessBeforeInitialization==> This is MyBean
postProcessAfterInitialization==> This is MyBean

访问:http://localhost:8080/test1

结果(BeanPostProcessor实现类注入的普通bean可以AOP,BeanPostProcessor实现类未被AOP)

testAOP方法(MyProcessor)
before MyBean#testAOP
testAOP方法(MyBean)

方案2:ApplicationContext 

当然,此法在本处不好应用。可以应用在配置Shiro时注入Service。

private UserService getUserService() {
    return (UserService) applicationContext.getBean(UserService.class);
}
0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录